@wenaixi/dsh-ponytail 5.0.0 → 5.1.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/CHANGELOG.md +28 -3
- package/README.md +19 -10
- package/lib/client.js +258 -137
- package/lib/ponytail-config.d.ts +36 -19
- package/lib/ponytail-config.d.ts.map +1 -1
- package/lib/ponytail-config.js +38 -34
- package/lib/ponytail-config.js.map +1 -1
- package/lib/ponytail-priority.d.ts +8 -6
- package/lib/ponytail-priority.d.ts.map +1 -1
- package/lib/ponytail-priority.js +10 -9
- package/lib/ponytail-priority.js.map +1 -1
- package/lib/ponytail-remote.d.ts +56 -0
- package/lib/ponytail-remote.d.ts.map +1 -0
- package/lib/ponytail-remote.js +138 -0
- package/lib/ponytail-remote.js.map +1 -0
- package/lib/ponytail-settings.d.ts +96 -0
- package/lib/ponytail-settings.d.ts.map +1 -0
- package/lib/ponytail-settings.js +165 -0
- package/lib/ponytail-settings.js.map +1 -0
- package/lib/ponytail-state.d.ts +8 -4
- package/lib/ponytail-state.d.ts.map +1 -1
- package/lib/ponytail-state.js +40 -39
- package/lib/ponytail-state.js.map +1 -1
- package/lib/ponytail.d.ts +24 -4
- package/lib/ponytail.d.ts.map +1 -1
- package/lib/ponytail.js +152 -79
- package/lib/ponytail.js.map +1 -1
- package/locale/en.json +31 -27
- package/locale/zh.json +31 -27
- package/package.json +3 -1
- package/lib/ponytail-http.d.ts +0 -45
- package/lib/ponytail-http.d.ts.map +0 -1
- package/lib/ponytail-http.js +0 -162
- package/lib/ponytail-http.js.map +0 -1
package/lib/client.js
CHANGED
|
@@ -10,10 +10,10 @@ window.__ModuleLoader__.load({
|
|
|
10
10
|
let e = React.createElement;
|
|
11
11
|
|
|
12
12
|
// 官方 locale 接入:注册 ponytail 命名空间双语字典(真源 locale/*.json,构建期内嵌)
|
|
13
|
-
// 技能描述不翻译(用户边界):由
|
|
13
|
+
// 技能描述不翻译(用户边界):由 configForms 快照与快照通道下发,字典不含技能描述键
|
|
14
14
|
var NS = "ponytail";
|
|
15
|
-
var ZH = {"
|
|
16
|
-
var EN = {"meta
|
|
15
|
+
var ZH = {"button.reset":"恢复默认配置","chain.hit":"生效中","chain.shadowed":"被覆盖","chain.unset":"未设置","chain.valueUnset":"(未设置)","confirm.reset":"确定恢复默认配置吗?等级回到标准(full),并重新启用全部 6 个技能。","error.operation":"操作失败:","error.rejected":"本部署没有接受这次修改,值已保留供你核对。","level.env":"环境变量","level.fallback":"内置兜底","level.patch":"Profile 补丁","meta":{"title":"懒人模式","description":"常驻懒人资深开发者模式与七阶梯子(YAGNI 到最小实现),6 个中文原生技能(本体、评审、审计、债务、收益、帮助),零 Tool 注册。"},"meta.description":"DietrichGebert/ponytail 的 DSH 完整移植:常驻懒人 senior 模式与七阶梯子(YAGNI 到最小实现),6 个中文原生技能(本体、评审、审计、债务、收益、帮助),零 tool 注册。","meta.title":"懒人模式(ponytail)","mode.full":"标准","mode.hint":"控制梯子提示词的注入强度,改动即时生效并写入配置文件,对所有会话生效。","mode.lite":"轻量","mode.lockedHint":"当前有更高优先级的配置在生效,此处的修改不会改变实际运行等级。","mode.off":"关闭","mode.title":"运行强度等级","mode.ultra":"激进","panel.title":"懒人模式配置","priority.intro":"运行等级按下列顺序取第一个有效值;被更高优先级压制的项,在界面上修改不会生效。当前生效:","priority.title":"配置优先级","priority.unavailable":"诊断信息不可用,请确认宿主版本已包含优先级诊断接口。","problem.env":"值无效,已忽略","problem.patch":"值无效,已忽略","skills.enabledToast":"技能 {name} 已启用","skills.hiddenToast":"技能 {name} 已隐藏","skills.hint":"关闭后该技能不会出现在斜杠菜单,也不会被模型加载。","skills.title":"原生技能开关","skills.toggleOn":"启用 {name}","state.loading":"读取中…","state.readOnly":"本部署的设置为只读。","state.saved":"配置保存在当前 profile 的 ponytail 目录","toast.modeChanged":"运行等级已切为「{name}」","toast.resetDone":"已恢复默认配置"};
|
|
16
|
+
var EN = {"button.reset":"Restore defaults","chain.hit":"In effect","chain.shadowed":"Shadowed","chain.unset":"Unset","chain.valueUnset":"(unset)","confirm.reset":"Restore default config? Level returns to Standard (full) and all 6 skills are re-enabled.","error.operation":"Operation failed: ","error.rejected":"This deployment did not accept the change; your value was kept for you to review.","level.env":"Environment variable","level.fallback":"Built-in fallback","level.patch":"Profile patch","meta":{"title":"Lazy Senior Dev Mode","description":"Persistent lazy senior developer mode with a seven-rung ladder (YAGNI down to the smallest implementation), six native skills, and zero tool registration."},"meta.description":"A full DSH port of DietrichGebert/ponytail: the always-on lazy senior developer mode and its seven-rung ladder (YAGNI down to the minimum implementation), with six native Chinese skills (core, review, audit, debt, gain, help) and zero tool registrations.","meta.title":"Ponytail (lazy senior mode)","mode.full":"Standard","mode.hint":"Controls how strongly the ladder prompt is injected. Changes apply immediately and are written to the config file for all sessions.","mode.lite":"Lite","mode.lockedHint":"A higher-priority configuration is in effect; changes here will not affect the actual runtime level.","mode.off":"Off","mode.title":"Intensity Level","mode.ultra":"Ultra","panel.title":"Lazy Senior Dev Mode settings","priority.intro":"The runtime level takes the first valid value in the following order; items shadowed by a higher-priority source cannot be changed from the UI. Current: ","priority.title":"Config Priority","priority.unavailable":"Diagnostics unavailable. Please confirm the host version ships the priority diagnostics endpoint.","problem.env":"Invalid value, ignored","problem.patch":"Invalid value, ignored","skills.enabledToast":"Skill {name} enabled","skills.hiddenToast":"Skill {name} hidden","skills.hint":"Disabled skills disappear from the slash menu and are not loaded by the model.","skills.title":"Native Skills","skills.toggleOn":"Enable {name}","state.loading":"Loading…","state.readOnly":"This deployment stores settings read-only.","state.saved":"Config is stored in the ponytail directory of the current profile","toast.modeChanged":"Level switched to {name}","toast.resetDone":"Defaults restored"};
|
|
17
17
|
// t 在 apply 时经 ctx.locale.bind 赋值;组件读取 factory 级变量(不接收 ctx,铁律)
|
|
18
18
|
var localeCtx = null;
|
|
19
19
|
var t = function (key, params) {
|
|
@@ -63,8 +63,8 @@ window.__ModuleLoader__.load({
|
|
|
63
63
|
display: "flex",
|
|
64
64
|
flexDirection: "column",
|
|
65
65
|
gap: "2px",
|
|
66
|
-
minWidth:
|
|
67
|
-
flex:
|
|
66
|
+
minWidth: 0,
|
|
67
|
+
flex: 1,
|
|
68
68
|
},
|
|
69
69
|
chainName: {
|
|
70
70
|
fontSize: "13px",
|
|
@@ -87,8 +87,8 @@ window.__ModuleLoader__.load({
|
|
|
87
87
|
display: "flex",
|
|
88
88
|
flexDirection: "column",
|
|
89
89
|
gap: "2px",
|
|
90
|
-
minWidth:
|
|
91
|
-
flex:
|
|
90
|
+
minWidth: 0,
|
|
91
|
+
flex: 1,
|
|
92
92
|
},
|
|
93
93
|
rowName: {
|
|
94
94
|
fontSize: "13px",
|
|
@@ -110,27 +110,20 @@ window.__ModuleLoader__.load({
|
|
|
110
110
|
},
|
|
111
111
|
};
|
|
112
112
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
113
|
+
// 档位标签在 apply 时才绑定 locale(t 是 factory 级可变变量),因此按需取值而不是模块级求值
|
|
114
|
+
function modeOptions() {
|
|
115
|
+
return [
|
|
116
|
+
{ value: "off", label: t("mode.off") },
|
|
117
|
+
{ value: "lite", label: t("mode.lite") },
|
|
118
|
+
{ value: "full", label: t("mode.full") },
|
|
119
|
+
{ value: "ultra", label: t("mode.ultra") },
|
|
120
|
+
];
|
|
121
|
+
}
|
|
120
122
|
|
|
121
|
-
// 技能元数据从 SKILL.md frontmatter 构建期提取(唯一真源,与宿侧 readSkillMeta
|
|
123
|
+
// 技能元数据从 SKILL.md frontmatter 构建期提取(唯一真源,与宿侧 readSkillMeta 同源)。
|
|
124
|
+
// 「是否启用」不再随服务端下发:它就是 configForms 快照里的 disabledSkills 取反。
|
|
122
125
|
const SKILL_META = [{"id":"ponytail","description":"强制使用最懒但可用的解法,追求最简、最短、最精。像一位见过一切的老手:先质疑需求是否该存在(YAGNI),优先复用标准库、平台原生能力,一行能解决就不用五十行。支持 lite/full(默认)/ultra 三档强度。适用于任何编码任务:编写、新增、重构、修复、评审、设计代码,以及选型依赖。触发词:ponytail / 偷懒 / 懒人模式 / 最简解法 / 最小解法 / yagni / 少做一点 / 最短路径 / 讨厌过度设计、臃肿、样板代码、没必要的依赖时也请使用。非编码请求(常识、文案、翻译、总结、菜谱)请勿使用。\n"},{"id":"ponytail-audit","description":"全仓过度设计审计,类似 ponytail-review,但扫描的是整个代码库而非 diff:按可删行数排序,列出能删、能简化、能用标准库/原生替代的地方。触发词:审计代码库 / 审计过度设计 / 这个仓库能删什么 / 找臃肿 / ponytail-audit / /ponytail-audit。一次性报告,不直接改代码。\n"},{"id":"ponytail-debt","description":"把代码库中所有 `ponytail:` 注释收割为债务台账,让 ponytail 有意留下的捷径和延期不会悄悄烂成「以后再说就是永远不做」。触发词:ponytail debt / /ponytail-debt / ponytail 延期了什么 / 列出捷径 / ponytail 台账 / 标记了什么待做。一次性报告,不改代码。\n"},{"id":"ponytail-gain","description":"以精简看板展示 ponytail 的实测收益:更少代码、更低成本、更快速度,数据来自 benchmark 中位数。一次性展示,非持久模式,也非本仓实时统计。触发词:/ponytail-gain / ponytail gain / ponytail 能省多少 / 展示 ponytail 收益 / ponytail 看板。\n"},{"id":"ponytail-help","description":"ponytail 全量模式、技能与命令的速查卡,一次性展示,非持久模式。触发词:/ponytail-help / ponytail help / ponytail 有哪些命令 / 怎么用 ponytail。\n"},{"id":"ponytail-review","description":"专挑过度设计的代码评审,只找能删的东西:重复造的标准库、没必要的依赖、臆想的抽象、闲置的灵活性。每条发现一行写完:位置、该删什么、用什么替代。触发词:过度设计评审 / 能删什么 / 是否过度设计 / 简化评审 / ponytail-review / /ponytail-review。配合面向正确性的评审使用,本技能只猎复杂度。\n"}];
|
|
123
126
|
|
|
124
|
-
const EMPTY_CONFIG = {
|
|
125
|
-
currentMode: "full",
|
|
126
|
-
defaultMode: "full",
|
|
127
|
-
disabledSkills: [],
|
|
128
|
-
skills: SKILL_META.map(function (s) {
|
|
129
|
-
return { id: s.id, name: s.id, description: s.description, enabled: true };
|
|
130
|
-
}),
|
|
131
|
-
priority: null,
|
|
132
|
-
};
|
|
133
|
-
|
|
134
127
|
// 诊断链一行的状态语义:生效 / 被覆盖 / 未设置 / 有问题
|
|
135
128
|
function chainRowState(source) {
|
|
136
129
|
if (source.problem) {
|
|
@@ -192,83 +185,166 @@ window.__ModuleLoader__.load({
|
|
|
192
185
|
);
|
|
193
186
|
}
|
|
194
187
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
188
|
+
/**
|
|
189
|
+
* 只读快照的最小 store:{ status, value },够 React 的 useSyncExternalStore 订阅。
|
|
190
|
+
* 不引第三方 store 库——快照就一个对象,一个 Set 监听者 + 一个 getSnapshot 足够。
|
|
191
|
+
*/
|
|
192
|
+
function createStore(initial) {
|
|
193
|
+
var listeners = new Set();
|
|
194
|
+
var current = initial;
|
|
195
|
+
return {
|
|
196
|
+
getSnapshot: function () { return current; },
|
|
197
|
+
subscribe: function (listener) {
|
|
198
|
+
listeners.add(listener);
|
|
199
|
+
return function () { listeners.delete(listener); };
|
|
200
|
+
},
|
|
201
|
+
set: function (next) {
|
|
202
|
+
current = next;
|
|
203
|
+
listeners.forEach(function (listener) { listener(); });
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
}
|
|
203
207
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
208
|
+
/**
|
|
209
|
+
* 本插件在浏览器侧向官方网关声明的 Remote 贡献。
|
|
210
|
+
*
|
|
211
|
+
* 为什么必须显式声明:网关的命名空间不是按需自动开通的。浏览器侧装配时
|
|
212
|
+
* dsh-api-remotes 只遍历一份编译期写死的官方贡献清单并逐个 ctx.remote.$mount()
|
|
213
|
+
*(dsh-api-remotes/lib/client.js:13512-13538,25 项,不含本插件)。
|
|
214
|
+
* 宿主侧 TypertRemoteService 只负责把端点暴露出去;客户端不 $mount,
|
|
215
|
+
* remote.ponytail 就永远不会出现——面板因此永久停在「诊断信息不可用」,
|
|
216
|
+
* 而宿主完全正常。写法参照官方第三方插件 dsh-experimental-client-ui-voice-input。
|
|
217
|
+
*
|
|
218
|
+
* result.create 是网关不校验的钩子(requireStrictCodec 只看 mode === "strict",
|
|
219
|
+
* 见 dsh-api-gateway/lib/client.js:2073),解码走 result.decode,缺省即原样透传
|
|
220
|
+
* (同文件 1801 行)。因此返回恒等函数即可把服务端 JSON 原样送进面板,
|
|
221
|
+
* 不必为此引入 zod。
|
|
222
|
+
*/
|
|
223
|
+
var PONYTAIL_REMOTE = {
|
|
224
|
+
package: "@wenaixi/dsh-ponytail",
|
|
225
|
+
descriptors: [
|
|
226
|
+
{
|
|
227
|
+
id: "@wenaixi/dsh-ponytail#ponytailRemote/snapshot",
|
|
228
|
+
service: "ponytailRemote",
|
|
229
|
+
namespace: "ponytail",
|
|
230
|
+
method: "snapshot",
|
|
231
|
+
invocation: { kind: "direct" },
|
|
232
|
+
parameters: [],
|
|
233
|
+
result: {
|
|
234
|
+
mode: "strict",
|
|
235
|
+
typeSymbol: "@wenaixi/dsh-ponytail/types#PonytailSnapshot",
|
|
236
|
+
create: function (value) { return value; },
|
|
237
|
+
},
|
|
238
|
+
sourceLocation: {
|
|
239
|
+
file: "src/ponytail-remote.ts",
|
|
240
|
+
line: 1,
|
|
241
|
+
column: 1,
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
],
|
|
245
|
+
};
|
|
246
|
+
/** 挂载失败时的快照 store:面板按 status 降级为不显示该段。 */
|
|
247
|
+
function createUnavailableStore() {
|
|
248
|
+
var store = createStore({ status: "unavailable", value: null });
|
|
249
|
+
store.reload = function () {};
|
|
250
|
+
store.dispose = function () {};
|
|
251
|
+
return store;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* 读取本插件已 $mount 的命名空间 snapshot,作为只读推导值的来源。
|
|
256
|
+
*
|
|
257
|
+
* 不再有轮询:命名空间由本文件下方的 PONYTAIL_REMOTE 经 ctx.remote.$mount() 挂载,
|
|
258
|
+
* $mount 返回的 disposer 之前挂载一定已经完成(它内部 await fiber,
|
|
259
|
+
* dsh-api-gateway/lib/client.js:1636-1646),因此同步问一次即可。
|
|
260
|
+
* 挂载失败(精简宿主没有 remote 服务)时立即降级为不显示,不再空转——
|
|
261
|
+
* 上一版的 40×50ms 轮询掩盖的是「压根没挂载」这个事实,而不只是时序。
|
|
262
|
+
*/
|
|
263
|
+
function createRemoteStore(ctx, namespace) {
|
|
264
|
+
var store = createStore({ status: "loading", value: null });
|
|
265
|
+
var method = "snapshot";
|
|
266
|
+
|
|
267
|
+
var fail = function () {
|
|
268
|
+
store.set({ status: "unavailable", value: null });
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
var load = function () {
|
|
272
|
+
var ns = ctx.get("remote." + namespace);
|
|
273
|
+
if (ns === undefined || ns === null || typeof ns[method] !== "function") {
|
|
274
|
+
fail();
|
|
275
|
+
return;
|
|
220
276
|
}
|
|
221
|
-
|
|
277
|
+
Promise.resolve(ns[method]()).then(function (response) {
|
|
278
|
+
if (response && response.ok === true) store.set({ status: "ready", value: response.value });
|
|
279
|
+
else fail();
|
|
280
|
+
}).catch(fail);
|
|
281
|
+
};
|
|
282
|
+
load();
|
|
283
|
+
// 配置表单写入成功后由面板调用:重新拉一次服务端真值(当前等级可能已变)
|
|
284
|
+
store.reload = load;
|
|
285
|
+
store.dispose = function () {};
|
|
286
|
+
return store;
|
|
287
|
+
}
|
|
222
288
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
setState(function (prev) {
|
|
234
|
-
return Object.assign({}, prev, { loading: false, error: String(err) });
|
|
235
|
-
});
|
|
236
|
-
});
|
|
237
|
-
}, [applyResult]);
|
|
289
|
+
function PonytailConfigPanel(props) {
|
|
290
|
+
// 官方配置表单的快照:status / value / base / user / revision / writable
|
|
291
|
+
// (dsh-client-ui-settings 的 ConfigFormController,lib/client.js:1117-1131)。
|
|
292
|
+
const snapshot = props.usePonytailConfig(function (s) { return s; });
|
|
293
|
+
const [busy, setBusy] = React.useState(false);
|
|
294
|
+
const [note, setNote] = React.useState(null);
|
|
295
|
+
|
|
296
|
+
// 只读推导值来自远程命名空间;服务端不可用时降级为 null(不显示该段)
|
|
297
|
+
const snapshotRemote = props.usePonytailSnapshot(function (s) { return s; });
|
|
298
|
+
const remote = snapshotRemote.value;
|
|
238
299
|
|
|
239
|
-
|
|
240
|
-
load();
|
|
241
|
-
// 语言切换后重取(官方 LocaleFace subscribe 语义:字典注册 bump revision)
|
|
242
|
-
var off = localeCtx ? localeCtx.subscribe(function () { load(); }) : null;
|
|
243
|
-
return function () { if (off) off(); };
|
|
244
|
-
}, [load]);
|
|
300
|
+
const writable = snapshot.writable === true && snapshot.status === "ready";
|
|
245
301
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
302
|
+
// 写入:全部经官方 settings 通道,携带 revision 做冲突检测。
|
|
303
|
+
// 服务端拒绝时 mutate 返回 false,此时以服务端读回的下一份快照为准,
|
|
304
|
+
// 不做本地乐观改值——面板显示的必须是真值。
|
|
305
|
+
const applyOps = React.useCallback(function (ops, message) {
|
|
306
|
+
setBusy(true);
|
|
307
|
+
setNote(null);
|
|
308
|
+
// 写入经 face 注入的官方表单控制器(renderable 的 inject 面会把 hooks 绑成
|
|
309
|
+
// use<Name>,其余键原样作为 props)。此前这里读 props.scope,而 scope 是 apply
|
|
310
|
+
// 闭包里的变量、从未放进 face——点击即 TypeError,界面表现为「改了没反应且卡住」。
|
|
311
|
+
props.mutate(ops, snapshot.revision).then(function (landed) {
|
|
312
|
+
setBusy(false);
|
|
313
|
+
setNote(landed ? message : t("error.rejected"));
|
|
314
|
+
// 档位写入后重新拉一次服务端真值:当前生效等级可能随之改变,
|
|
315
|
+
// 而它不在 configForms 快照里(那是持久化配置,不是运行时推导值)。
|
|
316
|
+
if (landed && props.reloadRemote) props.reloadRemote();
|
|
249
317
|
});
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
.then(function (res) {
|
|
256
|
-
return res.json();
|
|
257
|
-
})
|
|
258
|
-
.then(function (data) {
|
|
259
|
-
applyResult(data, message);
|
|
260
|
-
})
|
|
261
|
-
.catch(function (err) {
|
|
262
|
-
setState(function (prev) {
|
|
263
|
-
return Object.assign({}, prev, { saving: false, error: String(err) });
|
|
264
|
-
});
|
|
265
|
-
});
|
|
266
|
-
};
|
|
318
|
+
}, [props.mutate, snapshot.revision]);
|
|
319
|
+
|
|
320
|
+
const setMode = React.useCallback(function (mode) {
|
|
321
|
+
applyOps([{ op: "set", path: ["defaultMode"], value: mode }], t("toast.modeChanged", { name: mode }));
|
|
322
|
+
}, [applyOps]);
|
|
267
323
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
324
|
+
// 技能开关:disabledSkills 是覆盖式数组,每次写入都带上「目标技能的最终启用态」
|
|
325
|
+
const disabled = snapshot.value && Array.isArray(snapshot.value.disabledSkills)
|
|
326
|
+
? snapshot.value.disabledSkills
|
|
327
|
+
: [];
|
|
328
|
+
const toggleSkill = React.useCallback(function (name, enabled) {
|
|
329
|
+
const next = enabled ? disabled.filter(function (item) { return item !== name; }) : disabled.concat([name]);
|
|
330
|
+
applyOps([{ op: "set", path: ["disabledSkills"], value: next }],
|
|
331
|
+
enabled ? t("skills.enabledToast", { name: name }) : t("skills.hiddenToast", { name: name }));
|
|
332
|
+
}, [applyOps, disabled]);
|
|
333
|
+
|
|
334
|
+
const resetAll = React.useCallback(function () {
|
|
335
|
+
applyOps([
|
|
336
|
+
{ op: "unset", path: ["defaultMode"] },
|
|
337
|
+
{ op: "unset", path: ["disabledSkills"] },
|
|
338
|
+
], t("toast.resetDone"));
|
|
339
|
+
}, [applyOps]);
|
|
340
|
+
|
|
341
|
+
const config = snapshot.value || {};
|
|
342
|
+
const defaultMode = typeof config.defaultMode === "string" ? config.defaultMode : null;
|
|
343
|
+
// 只有环境变量命中才锁:env 压过一切,此时改 patch 确实无效。
|
|
344
|
+
// fallback 命中必须放行——那是「补丁与 env 都没写」的兜底,不是更高优先级的配置,
|
|
345
|
+
// 用户改 patch 立刻生效(此前把 fallback 也当压制源,提示文案在说谎)。
|
|
346
|
+
const levelLocked = Boolean(remote && Array.isArray(remote.priority && remote.priority.chain) && remote.priority.chain.some(function (s) {
|
|
347
|
+
return s.level === "env" && s.hit;
|
|
272
348
|
}));
|
|
273
349
|
|
|
274
350
|
return e(
|
|
@@ -276,57 +352,51 @@ window.__ModuleLoader__.load({
|
|
|
276
352
|
{ style: { display: "flex", flexDirection: "column", gap: "16px", maxWidth: "720px" } },
|
|
277
353
|
e("h3", { style: { fontSize: "15px", fontWeight: "600", margin: "0", color: "var(--dsw-alias-label-primary)" } },
|
|
278
354
|
t("panel.title")),
|
|
279
|
-
e(PrioritySection, { priority:
|
|
355
|
+
e(PrioritySection, { priority: remote ? remote.priority : null }),
|
|
280
356
|
e(
|
|
281
357
|
"div",
|
|
282
358
|
{ style: L.section },
|
|
283
359
|
e("h4", { style: L.title }, t("mode.title")),
|
|
284
|
-
e("p", { style: L.hint },
|
|
285
|
-
t("mode.hint")),
|
|
360
|
+
e("p", { style: L.hint }, t("mode.hint")),
|
|
286
361
|
e(P.SegmentedControl, {
|
|
287
362
|
id: "ponytail-mode",
|
|
288
363
|
label: t("mode.title"),
|
|
289
|
-
value:
|
|
290
|
-
options:
|
|
291
|
-
disabled:
|
|
292
|
-
onChange:
|
|
293
|
-
post({ mode: next }, t("toast.modeChanged", { name: next }));
|
|
294
|
-
},
|
|
364
|
+
value: defaultMode === null ? remote && remote.priority ? remote.priority.effective : "full" : defaultMode,
|
|
365
|
+
options: modeOptions(),
|
|
366
|
+
disabled: busy || !writable || levelLocked,
|
|
367
|
+
onChange: setMode,
|
|
295
368
|
}),
|
|
296
369
|
levelLocked
|
|
297
|
-
? e("p", { style: Object.assign({}, L.hint, { color: "var(--dsw-alias-state-warn-primary)" }) },
|
|
298
|
-
|
|
370
|
+
? e("p", { style: Object.assign({}, L.hint, { color: "var(--dsw-alias-state-warn-primary)" }) }, t("mode.lockedHint"))
|
|
371
|
+
: null,
|
|
372
|
+
!writable && snapshot.status === "ready"
|
|
373
|
+
? e("p", { style: Object.assign({}, L.hint, { color: "var(--dsw-alias-state-warn-primary)" }) }, t("state.readOnly"))
|
|
299
374
|
: null
|
|
300
375
|
),
|
|
301
376
|
e(
|
|
302
377
|
"div",
|
|
303
378
|
{ style: L.section },
|
|
304
379
|
e("h4", { style: L.title }, t("skills.title")),
|
|
305
|
-
e("p", { style: L.hint },
|
|
306
|
-
t("skills.hint")),
|
|
380
|
+
e("p", { style: L.hint }, t("skills.hint")),
|
|
307
381
|
e(
|
|
308
382
|
"div",
|
|
309
383
|
{ style: { display: "flex", flexDirection: "column", gap: "6px" } },
|
|
310
|
-
|
|
384
|
+
SKILL_META.map(function (skill) {
|
|
385
|
+
const enabled = disabled.indexOf(skill.id) < 0;
|
|
311
386
|
return e(
|
|
312
387
|
"div",
|
|
313
388
|
{ key: skill.id, style: L.row },
|
|
314
389
|
e(
|
|
315
390
|
"div",
|
|
316
391
|
{ style: L.rowText },
|
|
317
|
-
e("span", { style: L.rowName }, "/" + skill.
|
|
392
|
+
e("span", { style: L.rowName }, "/" + skill.id),
|
|
318
393
|
e("span", { style: L.rowDesc }, skill.description)
|
|
319
394
|
),
|
|
320
395
|
e(P.Switch, {
|
|
321
|
-
checked:
|
|
322
|
-
disabled:
|
|
323
|
-
label: t("skills.toggleOn", { name: skill.
|
|
324
|
-
onChange: function (next) {
|
|
325
|
-
post(
|
|
326
|
-
{ toggleSkill: { name: skill.id, enabled: next } },
|
|
327
|
-
t(next ? "skills.enabledToast" : "skills.hiddenToast", { name: skill.name })
|
|
328
|
-
);
|
|
329
|
-
},
|
|
396
|
+
checked: enabled,
|
|
397
|
+
disabled: busy || !writable,
|
|
398
|
+
label: t("skills.toggleOn", { name: skill.id }),
|
|
399
|
+
onChange: function (next) { toggleSkill(skill.id, next); },
|
|
330
400
|
})
|
|
331
401
|
);
|
|
332
402
|
})
|
|
@@ -336,27 +406,40 @@ window.__ModuleLoader__.load({
|
|
|
336
406
|
"div",
|
|
337
407
|
{ style: Object.assign({}, L.footer, L.last) },
|
|
338
408
|
e("span", { style: L.hint },
|
|
339
|
-
|
|
340
|
-
?
|
|
341
|
-
:
|
|
342
|
-
? state.
|
|
343
|
-
: state.
|
|
344
|
-
? t("state.loading")
|
|
345
|
-
: t("state.saved")),
|
|
409
|
+
note !== null
|
|
410
|
+
? note
|
|
411
|
+
: snapshot.status === "loading"
|
|
412
|
+
? t("state.loading")
|
|
413
|
+
: t("state.saved")),
|
|
346
414
|
e(P.Button, {
|
|
347
415
|
variant: "outline",
|
|
348
416
|
size: "sm",
|
|
349
|
-
disabled:
|
|
417
|
+
disabled: busy || !writable,
|
|
350
418
|
onClick: function () {
|
|
351
419
|
if (!confirm(t("confirm.reset"))) return;
|
|
352
|
-
|
|
420
|
+
resetAll();
|
|
353
421
|
},
|
|
354
422
|
}, t("button.reset"))
|
|
355
423
|
)
|
|
356
424
|
);
|
|
357
425
|
}
|
|
358
426
|
|
|
359
|
-
function apply(ctx) {
|
|
427
|
+
async function apply(ctx) {
|
|
428
|
+
// 先把只读推导值的端点挂到官方网关上:命名空间不会自动出现,
|
|
429
|
+
// 必须由客户端显式声明并 $mount。$mount 返回的 disposer 即 effect 的清理函数。
|
|
430
|
+
// 挂载失败(部署没有 remote 服务)不阻断其余能力,卡片照常出现,只是优先级段不显示。
|
|
431
|
+
var mounted = null;
|
|
432
|
+
try {
|
|
433
|
+
mounted = await ctx.remote.$mount(PONYTAIL_REMOTE);
|
|
434
|
+
} catch (error) {
|
|
435
|
+
if (ctx.logger) ctx.logger.warn("[ponytail] remote 贡献挂载失败(优先级诊断段将不显示): " + String(error));
|
|
436
|
+
}
|
|
437
|
+
ctx.effect(function () {
|
|
438
|
+
return function () {
|
|
439
|
+
if (mounted) void mounted();
|
|
440
|
+
};
|
|
441
|
+
}, "ponytail: remote contribution");
|
|
442
|
+
|
|
360
443
|
// 官方 locale:注册 ponytail 命名空间双语字典并绑定 t(真源 locale/*.json 构建期内嵌)
|
|
361
444
|
ctx.effect(function () {
|
|
362
445
|
return ctx.locale.register(NS, { zh: ZH, en: EN });
|
|
@@ -364,17 +447,55 @@ window.__ModuleLoader__.load({
|
|
|
364
447
|
localeCtx = ctx.locale;
|
|
365
448
|
t = ctx.locale.bind(NS);
|
|
366
449
|
|
|
367
|
-
//
|
|
368
|
-
ctx.
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
450
|
+
// 官方配置表单:宿主 settings 服务投影出的 ponytail 命名空间
|
|
451
|
+
var scope = ctx.configForms.get("ponytail");
|
|
452
|
+
|
|
453
|
+
// 只读推导值:官方 Typert 通道(ctx.remote.ponytail.snapshot())。
|
|
454
|
+
// 远程方法返回的是 { ok, value } / { ok:false, error } 信封(网关 client 侧 lib/client.js:1795-1802),
|
|
455
|
+
// 这里自己拆包并折成一个最小快照 store:命名空间不可用或调用失败时停在 unavailable,
|
|
456
|
+
// 优先级段降级为不显示,不影响其余控件。此处已在上面的 $mount 完成后才读,无需重试。
|
|
457
|
+
var remoteStore = mounted === null
|
|
458
|
+
? createUnavailableStore()
|
|
459
|
+
: createRemoteStore(ctx, "ponytail");
|
|
460
|
+
|
|
461
|
+
// 插槽注册沿用 whileServed:命名空间没被宿主服务时卡片整体不出现,
|
|
462
|
+
// 部署若从未组合 settings 服务,页面上不留本插件的痕迹
|
|
463
|
+
// (与官方 dsh-client-ui-settings-shell 同一生命周期协议,ui-settings-shell/lib/client.js:182)。
|
|
464
|
+
//
|
|
465
|
+
// 数据面经 inject 提供:组件只从 props 消费,不触碰 ctx(渲染器铁律)。
|
|
466
|
+
// usePonytailConfig 读官方配置表单快照;usePonytailSnapshot 读远程快照;
|
|
467
|
+
// reloadRemote 在写入后让面板重新拉服务端真值。
|
|
468
|
+
var formStore = {
|
|
469
|
+
getSnapshot: function () { return scope.getSnapshot(); },
|
|
470
|
+
subscribe: function (listener) { return scope.subscribe(listener); },
|
|
471
|
+
};
|
|
472
|
+
var face = function () {
|
|
473
|
+
return {
|
|
474
|
+
hooks: {
|
|
475
|
+
ponytailConfig: formStore,
|
|
476
|
+
ponytailSnapshot: remoteStore,
|
|
477
|
+
},
|
|
478
|
+
// 直接透传官方表单控制器的方法:它自带写队列串行化、revision 冲突恢复
|
|
479
|
+
// 与镜像折入(dsh-client-ui-settings 的 ConfigFormController.mutate,lib/client.js:1177-1194)
|
|
480
|
+
mutate: function (ops, revision) { return scope.mutate(ops, revision); },
|
|
481
|
+
reloadRemote: function () { remoteStore.reload(); },
|
|
482
|
+
};
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
ctx.effect(function () {
|
|
486
|
+
return ctx.configForms.whileServed(["ponytail"], function () {
|
|
487
|
+
return ctx.slots.inject("plugins.bundle.config", function () {
|
|
488
|
+
return ctx.slots.register(
|
|
489
|
+
{ name: "plugins.bundle.config", key: "@wenaixi/dsh-ponytail", locale: NS, inject: face },
|
|
490
|
+
PonytailConfigPanel
|
|
491
|
+
);
|
|
492
|
+
});
|
|
493
|
+
});
|
|
494
|
+
}, "ponytail: config card");
|
|
374
495
|
}
|
|
375
496
|
|
|
376
497
|
exports.apply = apply;
|
|
377
|
-
exports.inject = ["slots", "locale"];
|
|
498
|
+
exports.inject = ["slots", "locale", "configForms", "remote"];
|
|
378
499
|
return module.exports;
|
|
379
500
|
}
|
|
380
501
|
});
|
package/lib/ponytail-config.d.ts
CHANGED
|
@@ -36,49 +36,66 @@ export declare function resolveDshHome(configured?: string, env?: Record<string,
|
|
|
36
36
|
* 配置目录:DSH 数据根下的 ponytail 子目录。
|
|
37
37
|
* 平台无关——路径分隔符一律由 node:path 生成,不含任何平台判断。
|
|
38
38
|
*/
|
|
39
|
-
export declare function getConfigDir(): string;
|
|
40
|
-
export declare function getConfigPath(): string;
|
|
39
|
+
export declare function getConfigDir(profileDir?: string): string;
|
|
40
|
+
export declare function getConfigPath(profileDir?: string): string;
|
|
41
41
|
/**
|
|
42
|
-
*
|
|
42
|
+
* 全局共享的旧配置路径(ADR-0005 时代的位置,被所有实例共享)。
|
|
43
43
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* ponytail: 兼容读取保留至下一个大版本(5.x 首发)后移除,届时可整段删除。
|
|
44
|
+
* 只在迁移窗口内被读:实例在「全局文件存在且 profile 内尚未生成」时导入,
|
|
45
|
+
* 导入后把全局文件改名标记为已迁,使后续实例从干净状态启动。
|
|
46
|
+
* 正常读写一律经 getConfigDir(),此路径不参与优先级链。
|
|
48
47
|
*/
|
|
49
|
-
export declare function
|
|
50
|
-
export declare function getLegacyConfigPath(): string | null;
|
|
48
|
+
export declare function getSharedConfigPath(): string;
|
|
51
49
|
/**
|
|
52
50
|
* 读取配置文件原文:新位置优先,缺失时一次性回退旧位置。
|
|
53
51
|
* 返回 null 表示两处都不存在或均不可读。
|
|
54
52
|
*/
|
|
55
|
-
export declare function readConfigFileText(): string | null;
|
|
56
|
-
export declare function getDefaultMode(): RuntimeMode;
|
|
53
|
+
export declare function readConfigFileText(profileDir?: string): string | null;
|
|
54
|
+
export declare function getDefaultMode(profileDir?: string): RuntimeMode;
|
|
57
55
|
export interface FullConfigData {
|
|
58
56
|
defaultMode: RuntimeMode;
|
|
59
57
|
disabledSkills: string[];
|
|
60
58
|
}
|
|
61
|
-
export declare function readFullConfig(): FullConfigData;
|
|
59
|
+
export declare function readFullConfig(profileDir?: string): FullConfigData;
|
|
62
60
|
/**
|
|
63
61
|
* 字段级 merge 写盘:保留 config.json 中用户手写的未知字段(不再重建为两键对象),
|
|
64
62
|
* defaultMode 经 normalizeMode 校验——非法值拒绝返回 null 不写盘(writeDefaultMode 语义统一)。
|
|
65
63
|
* 失败契约:写盘异常返回 null(与 resetFullConfig/writeDefaultMode 一致)。
|
|
66
64
|
*/
|
|
67
|
-
export declare function writeFullConfig(patch: Partial<FullConfigData
|
|
68
|
-
export declare function resetFullConfig(): FullConfigData | null;
|
|
69
|
-
export declare function writeDefaultMode(mode: string): RuntimeMode | null;
|
|
65
|
+
export declare function writeFullConfig(patch: Partial<FullConfigData>, profileDir?: string): FullConfigData | null;
|
|
66
|
+
export declare function resetFullConfig(profileDir?: string): FullConfigData | null;
|
|
67
|
+
export declare function writeDefaultMode(mode: string, profileDir?: string): RuntimeMode | null;
|
|
68
|
+
/**
|
|
69
|
+
* volatile 配置引用:宿主在 `.volatile()` 字段上放的活值容器(cosmokit/lib/index.js:102-108)。
|
|
70
|
+
* 写入经 settings 落到 profile 补丁;进程内由 loader 就地更新,无需重挂载插件。
|
|
71
|
+
*/
|
|
72
|
+
export interface VolatileRef<T> {
|
|
73
|
+
get(): T;
|
|
74
|
+
}
|
|
70
75
|
/**
|
|
71
76
|
* 插件配置(entry 与 skill provider 共享,避免 ponytail-skills 反向导入 entry 造成循环依赖)
|
|
72
77
|
* 默认值写 schema(Schemastery),review 不可作默认(#377)
|
|
78
|
+
*
|
|
79
|
+
* 迁移到官方配置组合后,两个可持久化字段声明为 volatile:解析结果是 VolatileRef 而非裸值,
|
|
80
|
+
* 读侧一律经 readVolatile()。未配置时引用内部是 undefined(不是缺字段),让位 resolvePriority。
|
|
73
81
|
*/
|
|
74
82
|
export interface PonytailConfig {
|
|
75
|
-
/**
|
|
76
|
-
disabledSkills?: string[]
|
|
83
|
+
/** 禁用的技能名称列表(volatile 引用) */
|
|
84
|
+
disabledSkills?: VolatileRef<string[]>;
|
|
77
85
|
/** 注册到 ctx.skills 的 provider 名称 */
|
|
78
86
|
providerName?: string;
|
|
79
87
|
/** skill 目录绝对路径,默认取包内 skills/ */
|
|
80
88
|
skillDir?: string;
|
|
81
|
-
/** 默认强度,off
|
|
82
|
-
defaultMode?: 'off' | 'lite' | 'full' | 'ultra'
|
|
89
|
+
/** 默认强度,off 则不自动激活(volatile 引用) */
|
|
90
|
+
defaultMode?: VolatileRef<'off' | 'lite' | 'full' | 'ultra'>;
|
|
83
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* 读一个可能是 volatile 引用、可能是裸值、也可能读到 undefined 的字段。
|
|
94
|
+
*
|
|
95
|
+
* 为什么必须容这三态:schemastery 对 `.volatile()` 字段无条件造出引用(其值取 schema 默认值,
|
|
96
|
+
* schemastery/lib/index.mjs:480),没有默认值时也是「引用包着 undefined」;
|
|
97
|
+
* 而直接构造 config 对象的测试路径拿到的是裸值。两者必须读出同一业务值,
|
|
98
|
+
* 否则同一字段在不同入口会给出不同结果。
|
|
99
|
+
*/
|
|
100
|
+
export declare function readVolatile<T>(field: VolatileRef<T> | T | undefined): T | undefined;
|
|
84
101
|
//# sourceMappingURL=ponytail-config.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ponytail-config.d.ts","sourceRoot":"","sources":["../src/ponytail-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAMH,eAAO,MAAM,YAAY,SAAS,CAAA;AAClC,eAAO,MAAM,aAAa,2CAA4C,CAAA;AAEtE,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAA;AAExD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAI9D;AAID,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAE9C;AAMD,mEAAmE;AACnE,eAAO,MAAM,YAAY,aAAa,CAAA;AAetC;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GAAG,MAAM,CAKjH;AAED;;;GAGG;AACH,wBAAgB,YAAY,
|
|
1
|
+
{"version":3,"file":"ponytail-config.d.ts","sourceRoot":"","sources":["../src/ponytail-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAMH,eAAO,MAAM,YAAY,SAAS,CAAA;AAClC,eAAO,MAAM,aAAa,2CAA4C,CAAA;AAEtE,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAA;AAExD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAI9D;AAID,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAE9C;AAMD,mEAAmE;AACnE,eAAO,MAAM,YAAY,aAAa,CAAA;AAetC;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GAAG,MAAM,CAKjH;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,wBAAgB,aAAa,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAWrE;AAED,wBAAgB,cAAc,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,WAAW,CAiB/D;AAGD,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,WAAW,CAAA;IACxB,cAAc,EAAE,MAAM,EAAE,CAAA;CACzB;AA0BD,wBAAgB,cAAc,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,cAAc,CAElE;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CA8B1G;AAED,wBAAgB,eAAe,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAK1E;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAMtF;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,GAAG,IAAI,CAAC,CAAA;CACT;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,6BAA6B;IAC7B,cAAc,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,CAAC,CAAA;IACtC,mCAAmC;IACnC,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iCAAiC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,mCAAmC;IACnC,WAAW,CAAC,EAAE,WAAW,CAAC,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAA;CAC7D;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,SAAS,GAAG,CAAC,GAAG,SAAS,CAMpF"}
|