dsh-custom-mode 1.13.0 → 2.0.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/README.i18n.yaml +2 -2
- package/README.md +2 -2
- package/README.zh.md +3 -3
- package/client.js +256 -203
- package/composition.mjs +29 -0
- package/index.mjs +95 -8
- package/locales.mjs +163 -144
- package/package.json +2 -1
- package/postures.mjs +74 -0
- package/preset/postures/chat.en.md +7 -0
- package/preset/postures/chat.zh.md +7 -0
- package/preset/postures/write.en.md +7 -0
- package/preset/postures/write.zh.md +7 -0
package/composition.mjs
CHANGED
|
@@ -1025,6 +1025,35 @@ export function overridesOf(text, modeId) {
|
|
|
1025
1025
|
return overrides
|
|
1026
1026
|
}
|
|
1027
1027
|
|
|
1028
|
+
/**
|
|
1029
|
+
* 按允许名单生成显式开关。
|
|
1030
|
+
*
|
|
1031
|
+
* 只写下和出厂状态不同的行,这样没被点名的行仍保持 `!!js` 平台条件。名单里没有、而出厂是开的行,
|
|
1032
|
+
* 写成关闭。上游以后新增的行不在名单里,因此对「写作 / 聊天」默认关闭。
|
|
1033
|
+
*
|
|
1034
|
+
* @param {string} modeId - one of {@link BASE_MODES}.
|
|
1035
|
+
* @param {string[]} allowIds - 希望保持可用的行 id。`persona` 与 `custom-prompt-tool` 总会被加进去。
|
|
1036
|
+
* @returns {Map<string, boolean>}
|
|
1037
|
+
*/
|
|
1038
|
+
export function allowlistOverrides(modeId, allowIds) {
|
|
1039
|
+
const allow = new Set(Array.isArray(allowIds) ? allowIds : [])
|
|
1040
|
+
allow.add('persona')
|
|
1041
|
+
allow.add('custom-prompt-tool')
|
|
1042
|
+
const rows = flattenRowObjects(collectRows(readBaseComposition(modeId)))
|
|
1043
|
+
for (const extra of EXTRA_ROWS) {
|
|
1044
|
+
if (rows.has(extra.id) === false) {
|
|
1045
|
+
rows.set(extra.id, { id: extra.id, disabled: false, disabledExpression: null })
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
const overrides = new Map()
|
|
1049
|
+
for (const [id, row] of rows) {
|
|
1050
|
+
const want = allow.has(id)
|
|
1051
|
+
const shippedOn = row.disabled !== true
|
|
1052
|
+
if (want !== shippedOn) overrides.set(id, want)
|
|
1053
|
+
}
|
|
1054
|
+
return overrides
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1028
1057
|
/**
|
|
1029
1058
|
* Rows this feature always adds, for the settings UI to show alongside the base
|
|
1030
1059
|
* mode's own rows.
|
package/index.mjs
CHANGED
|
@@ -9,8 +9,11 @@
|
|
|
9
9
|
* GET /api/custom-mode/history?id=…&n=… — one prompt revision (metadata comes with the state).
|
|
10
10
|
* POST /api/custom-mode/state — { id, mode, overrides, prompt, name, description }:
|
|
11
11
|
* validate, render a fresh `agent.cordis.yml`, write both files.
|
|
12
|
-
* POST /api/custom-mode/create — { name, description }: seed a new
|
|
13
|
-
*
|
|
12
|
+
* POST /api/custom-mode/create — { name, description, posture?, locale? }: seed a new
|
|
13
|
+
* assistant. Omitting `posture` (or sending `develop`) is the
|
|
14
|
+
* historical path: the packaged prompt and the standard base,
|
|
15
|
+
* with shipped row states. Other postures still use an official
|
|
16
|
+
* base; they only change the starter prompt and the initial switches.
|
|
14
17
|
* POST /api/custom-mode/delete — { id }: remove a locally authored assistant.
|
|
15
18
|
* POST /api/custom-mode/reorder — { id, direction }: move one assistant up/down in the
|
|
16
19
|
* picker order by writing `order` into each `preset.yml`.
|
|
@@ -52,6 +55,7 @@ import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
|
52
55
|
import { dirname, join } from 'node:path'
|
|
53
56
|
import { PROMPT_PATH, COMPOSITION_PATH, ROUTE_PATH, PRESET_DIR } from './paths.mjs'
|
|
54
57
|
import {
|
|
58
|
+
allowlistOverrides,
|
|
55
59
|
applyRowExclusivity,
|
|
56
60
|
BASE_MODE_IDS,
|
|
57
61
|
BASE_MODES,
|
|
@@ -72,6 +76,7 @@ import {
|
|
|
72
76
|
import { presetMetaText, readPresetMeta, writePresetMeta, presetMetaPath, PRESET_META_PATH } from './meta.mjs'
|
|
73
77
|
import { listHistory, readVersion, recordExternalChange, recordPrompt, HISTORY_SOURCE } from './journal.mjs'
|
|
74
78
|
import { packagedPresetDir, starterComposition } from './seed.mjs'
|
|
79
|
+
import { postureById, postureLocale, posturePrompt, publicPostures } from './postures.mjs'
|
|
75
80
|
import {
|
|
76
81
|
allocateId,
|
|
77
82
|
assistantDir,
|
|
@@ -196,6 +201,33 @@ export function promptFile(directory) {
|
|
|
196
201
|
return join(directory, 'prompt.md')
|
|
197
202
|
}
|
|
198
203
|
|
|
204
|
+
/**
|
|
205
|
+
* The prompt this assistant was created with.
|
|
206
|
+
*
|
|
207
|
+
* Kept beside `prompt.md` and never updated by a later save, so "restore the
|
|
208
|
+
* starter" means that text on Windows and Linux alike. Assistants created
|
|
209
|
+
* before this file existed fall back to the packaged coding prompt, which is
|
|
210
|
+
* what they were seeded with.
|
|
211
|
+
*/
|
|
212
|
+
export const STARTER_PROMPT_FILE = 'prompt.starter.md'
|
|
213
|
+
|
|
214
|
+
/** @param {string} directory */
|
|
215
|
+
export function starterPromptFile(directory) {
|
|
216
|
+
return join(directory, STARTER_PROMPT_FILE)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* @param {string} directory
|
|
221
|
+
* @returns {string | null}
|
|
222
|
+
*/
|
|
223
|
+
function readStarterPrompt(directory) {
|
|
224
|
+
try {
|
|
225
|
+
return readFileSync(starterPromptFile(directory), 'utf8')
|
|
226
|
+
} catch {
|
|
227
|
+
return null
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
199
231
|
/** Absolute path of one preset directory's composition file. */
|
|
200
232
|
export function compositionFile(directory) {
|
|
201
233
|
return join(directory, COMPOSITION_FILE)
|
|
@@ -398,7 +430,9 @@ export function readState(rows, id, options = {}) {
|
|
|
398
430
|
presetMetaPath: presetMetaPath(directory),
|
|
399
431
|
// 回退用的出厂文本。它不是"当前值",页面只把它填进编辑器,保存前不落盘 —— 所以
|
|
400
432
|
// 一次误点不会破坏任何东西(重新读取即可丢弃)。
|
|
401
|
-
|
|
433
|
+
// 回退用的起步文本。优先是这个助手新建时写下的 prompt.starter.md;
|
|
434
|
+
// 没有该文件的旧助手用打包的开发提示词,那就是它们当初得到的文本。
|
|
435
|
+
factoryPrompt: readStarterPrompt(directory) ?? (typeof options.factoryPrompt === 'string' ? options.factoryPrompt : null),
|
|
402
436
|
// 改动历史(只有元数据,正文按需取:见 GET /custom-mode/history)。
|
|
403
437
|
history: listHistory(directory),
|
|
404
438
|
// 本插件版本 + 本机装的那条 dsh 线上无法解析的行(页面据此显示版本与"按本线修复")。
|
|
@@ -436,7 +470,7 @@ export function readList(rows) {
|
|
|
436
470
|
const list = assistantsFromRoster(rows).map((item) =>
|
|
437
471
|
typeof item.broken === 'string' ? { ...item, brokenRows: brokenRowIds(item.broken) } : item,
|
|
438
472
|
)
|
|
439
|
-
return { ok: true, assistants: list, root: userPresetRoot(rows) }
|
|
473
|
+
return { ok: true, assistants: list, postures: publicPostures(), root: userPresetRoot(rows) }
|
|
440
474
|
}
|
|
441
475
|
|
|
442
476
|
/**
|
|
@@ -754,11 +788,41 @@ export function createAssistant(rows, input, templateDir = packagedPresetDir())
|
|
|
754
788
|
}
|
|
755
789
|
}
|
|
756
790
|
|
|
791
|
+
// 复制沿用源助手,不套姿态。省略姿态或显式 `develop` 与旧版创建相同:标准底、出厂开关、出厂提示词。
|
|
792
|
+
const requestedPosture = source !== null
|
|
793
|
+
? ''
|
|
794
|
+
: input !== null && typeof input === 'object' && typeof input.posture === 'string'
|
|
795
|
+
? input.posture.trim()
|
|
796
|
+
: ''
|
|
797
|
+
const posture = requestedPosture === '' || requestedPosture === 'develop' ? null : postureById(requestedPosture)
|
|
798
|
+
if (requestedPosture !== '' && requestedPosture !== 'develop' && posture === null) {
|
|
799
|
+
return {
|
|
800
|
+
ok: false,
|
|
801
|
+
code: 'postureUnknown',
|
|
802
|
+
params: { posture: requestedPosture },
|
|
803
|
+
error: '没有这个姿态:' + requestedPosture,
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
if (posture !== null && posture.hands !== 'allow') {
|
|
807
|
+
return {
|
|
808
|
+
ok: false,
|
|
809
|
+
code: 'postureUnknown',
|
|
810
|
+
params: { posture: posture.id },
|
|
811
|
+
error: '没有这个姿态:' + posture.id,
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
const createMode = source !== null ? source.mode : posture === null ? 'standard' : posture.base
|
|
815
|
+
|
|
757
816
|
let composition
|
|
758
817
|
try {
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
818
|
+
if (source !== null) {
|
|
819
|
+
composition = renderComposition(source.mode, source.overrides, { modeName: name, assistantId: id })
|
|
820
|
+
} else if (posture !== null && posture.hands === 'allow') {
|
|
821
|
+
const exclusive = applyRowExclusivity(posture.base, allowlistOverrides(posture.base, posture.allow))
|
|
822
|
+
composition = renderComposition(posture.base, exclusive.overrides, { modeName: name, assistantId: id })
|
|
823
|
+
} else {
|
|
824
|
+
composition = renderComposition('standard', new Map(), { modeName: name, assistantId: id })
|
|
825
|
+
}
|
|
762
826
|
} catch (error) {
|
|
763
827
|
// 与保存路径的 `renderFailed` 同一套:**每个**用户可见的结果都要带 `code`,否则页面无从本地化
|
|
764
828
|
// —— client.js 的 apiText 在没有 code 时直接退回下面这串中文,英文界面会在这一刻掉回中文
|
|
@@ -767,7 +831,7 @@ export function createAssistant(rows, input, templateDir = packagedPresetDir())
|
|
|
767
831
|
return {
|
|
768
832
|
ok: false,
|
|
769
833
|
code: error.code,
|
|
770
|
-
params: { mode:
|
|
834
|
+
params: { mode: createMode, detail: describe(error) },
|
|
771
835
|
error: '本机取不到出厂组成,暂时无法新建助手。',
|
|
772
836
|
}
|
|
773
837
|
}
|
|
@@ -789,15 +853,38 @@ export function createAssistant(rows, input, templateDir = packagedPresetDir())
|
|
|
789
853
|
}
|
|
790
854
|
|
|
791
855
|
// The template ships a starter prompt; a duplicate replaces it with the source's.
|
|
856
|
+
// 姿态只替换草稿,不改模板目录里的 prompt.md。`develop` 与省略姿态停在模板那一份。
|
|
792
857
|
if (source !== null) {
|
|
793
858
|
try {
|
|
794
859
|
writeAtomic(promptFile(created.dir), source.prompt)
|
|
795
860
|
} catch (error) {
|
|
796
861
|
return abortCreate({ ok: false, code: 'promptWriteFailed', params: { detail: describe(error) }, error: '写入提示词失败:' + describe(error) })
|
|
797
862
|
}
|
|
863
|
+
} else if (posture !== null) {
|
|
864
|
+
let starter
|
|
865
|
+
try {
|
|
866
|
+
starter = posturePrompt(posture.id, postureLocale(input.locale))
|
|
867
|
+
} catch (error) {
|
|
868
|
+
return abortCreate({
|
|
869
|
+
ok: false,
|
|
870
|
+
code: 'posturePromptMissing',
|
|
871
|
+
params: { posture: posture.id, detail: describe(error) },
|
|
872
|
+
error: '读不到这个姿态的起步提示词:' + describe(error),
|
|
873
|
+
})
|
|
874
|
+
}
|
|
875
|
+
try {
|
|
876
|
+
writeAtomic(promptFile(created.dir), starter)
|
|
877
|
+
} catch (error) {
|
|
878
|
+
return abortCreate({ ok: false, code: 'promptWriteFailed', params: { detail: describe(error) }, error: '写入提示词失败:' + describe(error) })
|
|
879
|
+
}
|
|
798
880
|
}
|
|
799
881
|
const metaResult = writePresetMeta(name, description === '' && source !== null ? source.description : description, created.dir)
|
|
800
882
|
if (metaResult.ok !== true) return abortCreate(metaResult)
|
|
883
|
+
try {
|
|
884
|
+
writeAtomic(starterPromptFile(created.dir), readFileSync(promptFile(created.dir), 'utf8'))
|
|
885
|
+
} catch (error) {
|
|
886
|
+
return abortCreate({ ok: false, code: 'promptWriteFailed', params: { detail: describe(error) }, error: '写入提示词失败:' + describe(error) })
|
|
887
|
+
}
|
|
801
888
|
|
|
802
889
|
return {
|
|
803
890
|
ok: true,
|