@wenaixi/dsh-ponytail 5.0.0 → 5.2.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 +140 -226
- package/README.md +102 -98
- package/lib/client.js +303 -140
- package/lib/ponytail-config.d.ts +47 -21
- package/lib/ponytail-config.d.ts.map +1 -1
- package/lib/ponytail-config.js +42 -36
- package/lib/ponytail-config.js.map +1 -1
- package/lib/ponytail-instructions.d.ts.map +1 -1
- package/lib/ponytail-instructions.js +31 -31
- package/lib/ponytail-instructions.js.map +1 -1
- package/lib/ponytail-priority.d.ts +10 -8
- package/lib/ponytail-priority.d.ts.map +1 -1
- package/lib/ponytail-priority.js +12 -11
- package/lib/ponytail-priority.js.map +1 -1
- package/lib/ponytail-remote.d.ts +65 -0
- package/lib/ponytail-remote.d.ts.map +1 -0
- package/lib/ponytail-remote.js +141 -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-skills.d.ts +10 -0
- package/lib/ponytail-skills.d.ts.map +1 -1
- package/lib/ponytail-skills.js +23 -5
- package/lib/ponytail-skills.js.map +1 -1
- 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 +28 -4
- package/lib/ponytail.d.ts.map +1 -1
- package/lib/ponytail.js +181 -83
- package/lib/ponytail.js.map +1 -1
- package/locale/en.json +37 -27
- package/locale/zh.json +37 -27
- package/package.json +3 -1
- package/skills/descriptions.en.json +8 -0
- package/skills/descriptions.zh.json +8 -0
- package/skills/ponytail/SKILL.md +89 -46
- package/skills/ponytail-audit/SKILL.md +27 -17
- package/skills/ponytail-debt/SKILL.md +27 -14
- package/skills/ponytail-gain/SKILL.md +32 -19
- package/skills/ponytail-help/SKILL.md +53 -32
- package/skills/ponytail-review/SKILL.md +36 -24
- 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 = {"
|
|
15
|
+
var ZH = {"button.reset":"恢复默认配置","chain.hit":"生效中","chain.shadowed":"被覆盖","chain.unset":"未设置","chain.valueUnset":"(未设置)","confirm.reset":"确定恢复默认配置吗?等级回到标准(full),并重新启用全部 6 个技能。","error.operation":"操作失败:","error.rejected":"本部署没有接受这次修改,值已保留供你核对。","lang.changed":"技能描述语言已切为「{name}」","lang.en":"English","lang.title":"技能描述语言","lang.zh":"中文","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":"激进","mode.unset":"未设置","mode.unsetHint":"Profile 补丁未配置运行等级,当前由内置兜底决定。点上方任一档位即写入补丁。","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.","lang.changed":"Skill description language switched to {name}","lang.en":"English","lang.title":"Skill description language","lang.zh":"中文","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","mode.unset":"Unset","mode.unsetHint":"The profile patch has no intensity level; the built-in fallback decides it now. Picking any level above writes it to the patch.","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,26 +110,29 @@ window.__ModuleLoader__.load({
|
|
|
110
110
|
},
|
|
111
111
|
};
|
|
112
112
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
113
|
+
// 档位标签在 apply 时才绑定 locale(t 是 factory 级可变变量),因此按需取值而不是模块级求值。
|
|
114
|
+
//
|
|
115
|
+
// configured=false(补丁未写 defaultMode)时末尾追加一段真实的「未设置」选项,而不是把 value
|
|
116
|
+
// 置成一个不在 options 里的值:官方 SegmentedControl 用 options.findIndex 求下标,
|
|
117
|
+
// 下标 -1 会让所有段 tabIndex=-1(键盘不可达)且指示器整颗滑出轨道左缘
|
|
118
|
+
// (dsh-client-ui-primitives/lib/index.js:3410-3492 与 SegmentedControl.module.css)。
|
|
119
|
+
// 「未设置」排在末尾而非首位:未配置态下 关闭/轻量/标准/激进 仍占 0-3 段,
|
|
120
|
+
// 配好之后四档位置不变,用户已有的肌肉记忆不被打断。
|
|
121
|
+
function modeOptions(configured) {
|
|
122
|
+
var options = [
|
|
123
|
+
{ value: "off", label: t("mode.off") },
|
|
124
|
+
{ value: "lite", label: t("mode.lite") },
|
|
125
|
+
{ value: "full", label: t("mode.full") },
|
|
126
|
+
{ value: "ultra", label: t("mode.ultra") },
|
|
127
|
+
];
|
|
128
|
+
if (!configured) options.push({ value: "unset", label: t("mode.unset") });
|
|
129
|
+
return options;
|
|
130
|
+
}
|
|
123
131
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
};
|
|
132
|
+
// 技能 id 列表是构建期常量;描述与语言来自 remote 快照(与模型目录同源)。
|
|
133
|
+
// 构建期内嵌描述会让语言切换停在旧值,所以这里只带 id。
|
|
134
|
+
// 「是否启用」不再随服务端下发:它就是 configForms 快照里的 disabledSkills 取反。
|
|
135
|
+
const SKILL_IDS = ["ponytail","ponytail-audit","ponytail-debt","ponytail-gain","ponytail-help","ponytail-review"];
|
|
133
136
|
|
|
134
137
|
// 诊断链一行的状态语义:生效 / 被覆盖 / 未设置 / 有问题
|
|
135
138
|
function chainRowState(source) {
|
|
@@ -192,141 +195,250 @@ window.__ModuleLoader__.load({
|
|
|
192
195
|
);
|
|
193
196
|
}
|
|
194
197
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
198
|
+
/**
|
|
199
|
+
* 只读快照的最小 store:{ status, value },够 React 的 useSyncExternalStore 订阅。
|
|
200
|
+
* 不引第三方 store 库——快照就一个对象,一个 Set 监听者 + 一个 getSnapshot 足够。
|
|
201
|
+
*/
|
|
202
|
+
function createStore(initial) {
|
|
203
|
+
var listeners = new Set();
|
|
204
|
+
var current = initial;
|
|
205
|
+
return {
|
|
206
|
+
getSnapshot: function () { return current; },
|
|
207
|
+
subscribe: function (listener) {
|
|
208
|
+
listeners.add(listener);
|
|
209
|
+
return function () { listeners.delete(listener); };
|
|
210
|
+
},
|
|
211
|
+
set: function (next) {
|
|
212
|
+
current = next;
|
|
213
|
+
listeners.forEach(function (listener) { listener(); });
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
}
|
|
203
217
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
218
|
+
/**
|
|
219
|
+
* 本插件在浏览器侧向官方网关声明的 Remote 贡献。
|
|
220
|
+
*
|
|
221
|
+
* 为什么必须显式声明:网关的命名空间不是按需自动开通的。浏览器侧装配时
|
|
222
|
+
* dsh-api-remotes 只遍历一份编译期写死的官方贡献清单并逐个 ctx.remote.$mount()
|
|
223
|
+
*(dsh-api-remotes/lib/client.js:13512-13538,25 项,不含本插件)。
|
|
224
|
+
* 宿主侧 TypertRemoteService 只负责把端点暴露出去;客户端不 $mount,
|
|
225
|
+
* remote.ponytail 就永远不会出现——面板因此永久停在「诊断信息不可用」,
|
|
226
|
+
* 而宿主完全正常。写法参照官方第三方插件 dsh-experimental-client-ui-voice-input。
|
|
227
|
+
*
|
|
228
|
+
* result.create 是网关不校验的钩子(requireStrictCodec 只看 mode === "strict",
|
|
229
|
+
* 见 dsh-api-gateway/lib/client.js:2073),解码走 result.decode,缺省即原样透传
|
|
230
|
+
* (同文件 1801 行)。因此返回恒等函数即可把服务端 JSON 原样送进面板,
|
|
231
|
+
* 不必为此引入 zod。
|
|
232
|
+
*/
|
|
233
|
+
var PONYTAIL_REMOTE = {
|
|
234
|
+
package: "@wenaixi/dsh-ponytail",
|
|
235
|
+
descriptors: [
|
|
236
|
+
{
|
|
237
|
+
id: "@wenaixi/dsh-ponytail#ponytailRemote/snapshot",
|
|
238
|
+
service: "ponytailRemote",
|
|
239
|
+
namespace: "ponytail",
|
|
240
|
+
method: "snapshot",
|
|
241
|
+
invocation: { kind: "direct" },
|
|
242
|
+
parameters: [],
|
|
243
|
+
result: {
|
|
244
|
+
mode: "strict",
|
|
245
|
+
typeSymbol: "@wenaixi/dsh-ponytail/types#PonytailSnapshot",
|
|
246
|
+
create: function (value) { return value; },
|
|
247
|
+
},
|
|
248
|
+
sourceLocation: {
|
|
249
|
+
file: "src/ponytail-remote.ts",
|
|
250
|
+
line: 1,
|
|
251
|
+
column: 1,
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
],
|
|
255
|
+
};
|
|
256
|
+
/** 挂载失败时的快照 store:面板按 status 降级为不显示该段。 */
|
|
257
|
+
function createUnavailableStore() {
|
|
258
|
+
var store = createStore({ status: "unavailable", value: null });
|
|
259
|
+
store.reload = function () {};
|
|
260
|
+
store.dispose = function () {};
|
|
261
|
+
return store;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* 读取本插件已 $mount 的命名空间 snapshot,作为只读推导值的来源。
|
|
266
|
+
*
|
|
267
|
+
* 不再有轮询:命名空间由本文件下方的 PONYTAIL_REMOTE 经 ctx.remote.$mount() 挂载,
|
|
268
|
+
* $mount 返回的 disposer 之前挂载一定已经完成(它内部 await fiber,
|
|
269
|
+
* dsh-api-gateway/lib/client.js:1636-1646),因此同步问一次即可。
|
|
270
|
+
* 挂载失败(精简宿主没有 remote 服务)时立即降级为不显示,不再空转——
|
|
271
|
+
* 上一版的 40×50ms 轮询掩盖的是「压根没挂载」这个事实,而不只是时序。
|
|
272
|
+
*/
|
|
273
|
+
function createRemoteStore(ctx, namespace) {
|
|
274
|
+
var store = createStore({ status: "loading", value: null });
|
|
275
|
+
var method = "snapshot";
|
|
276
|
+
|
|
277
|
+
var fail = function () {
|
|
278
|
+
store.set({ status: "unavailable", value: null });
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
var load = function () {
|
|
282
|
+
var ns = ctx.get("remote." + namespace);
|
|
283
|
+
if (ns === undefined || ns === null || typeof ns[method] !== "function") {
|
|
284
|
+
fail();
|
|
285
|
+
return;
|
|
220
286
|
}
|
|
221
|
-
|
|
287
|
+
Promise.resolve(ns[method]()).then(function (response) {
|
|
288
|
+
if (response && response.ok === true) store.set({ status: "ready", value: response.value });
|
|
289
|
+
else fail();
|
|
290
|
+
}).catch(fail);
|
|
291
|
+
};
|
|
292
|
+
load();
|
|
293
|
+
// 配置表单写入成功后由面板调用:重新拉一次服务端真值(当前等级可能已变)
|
|
294
|
+
store.reload = load;
|
|
295
|
+
store.dispose = function () {};
|
|
296
|
+
return store;
|
|
297
|
+
}
|
|
222
298
|
|
|
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]);
|
|
299
|
+
function PonytailConfigPanel(props) {
|
|
300
|
+
// 官方配置表单的快照:status / value / base / user / revision / writable
|
|
301
|
+
// (dsh-client-ui-settings 的 ConfigFormController,lib/client.js:1117-1131)。
|
|
302
|
+
const snapshot = props.usePonytailConfig(function (s) { return s; });
|
|
303
|
+
const [busy, setBusy] = React.useState(false);
|
|
304
|
+
const [note, setNote] = React.useState(null);
|
|
305
|
+
|
|
306
|
+
// 只读推导值来自远程命名空间;服务端不可用时降级为 null(不显示该段)
|
|
307
|
+
const snapshotRemote = props.usePonytailSnapshot(function (s) { return s; });
|
|
308
|
+
const remote = snapshotRemote.value;
|
|
238
309
|
|
|
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]);
|
|
310
|
+
const writable = snapshot.writable === true && snapshot.status === "ready";
|
|
245
311
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
312
|
+
// 写入:全部经官方 settings 通道,携带 revision 做冲突检测。
|
|
313
|
+
// 服务端拒绝时 mutate 返回 false,此时以服务端读回的下一份快照为准,
|
|
314
|
+
// 不做本地乐观改值——面板显示的必须是真值。
|
|
315
|
+
const applyOps = React.useCallback(function (ops, message) {
|
|
316
|
+
setBusy(true);
|
|
317
|
+
setNote(null);
|
|
318
|
+
// 写入经 face 注入的官方表单控制器(renderable 的 inject 面会把 hooks 绑成
|
|
319
|
+
// use<Name>,其余键原样作为 props)。此前这里读 props.scope,而 scope 是 apply
|
|
320
|
+
// 闭包里的变量、从未放进 face——点击即 TypeError,界面表现为「改了没反应且卡住」。
|
|
321
|
+
props.mutate(ops, snapshot.revision).then(function (landed) {
|
|
322
|
+
setBusy(false);
|
|
323
|
+
setNote(landed ? message : t("error.rejected"));
|
|
324
|
+
// 档位写入后重新拉一次服务端真值:当前生效等级可能随之改变,
|
|
325
|
+
// 而它不在 configForms 快照里(那是持久化配置,不是运行时推导值)。
|
|
326
|
+
if (landed && props.reloadRemote) props.reloadRemote();
|
|
249
327
|
});
|
|
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
|
-
};
|
|
328
|
+
}, [props.mutate, snapshot.revision]);
|
|
329
|
+
|
|
330
|
+
const setMode = React.useCallback(function (mode) {
|
|
331
|
+
applyOps([{ op: "set", path: ["defaultMode"], value: mode }], t("toast.modeChanged", { name: mode }));
|
|
332
|
+
}, [applyOps]);
|
|
267
333
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
334
|
+
// 技能开关:disabledSkills 是覆盖式数组,每次写入都带上「目标技能的最终启用态」
|
|
335
|
+
const disabled = snapshot.value && Array.isArray(snapshot.value.disabledSkills)
|
|
336
|
+
? snapshot.value.disabledSkills
|
|
337
|
+
: [];
|
|
338
|
+
const toggleSkill = React.useCallback(function (name, enabled) {
|
|
339
|
+
const next = enabled ? disabled.filter(function (item) { return item !== name; }) : disabled.concat([name]);
|
|
340
|
+
applyOps([{ op: "set", path: ["disabledSkills"], value: next }],
|
|
341
|
+
enabled ? t("skills.enabledToast", { name: name }) : t("skills.hiddenToast", { name: name }));
|
|
342
|
+
}, [applyOps, disabled]);
|
|
343
|
+
|
|
344
|
+
const resetAll = React.useCallback(function () {
|
|
345
|
+
applyOps([
|
|
346
|
+
{ op: "unset", path: ["defaultMode"] },
|
|
347
|
+
{ op: "unset", path: ["disabledSkills"] },
|
|
348
|
+
{ op: "unset", path: ["skillDescriptionLang"] },
|
|
349
|
+
], t("toast.resetDone"));
|
|
350
|
+
}, [applyOps]);
|
|
351
|
+
|
|
352
|
+
const config = snapshot.value || {};
|
|
353
|
+
const defaultMode = typeof config.defaultMode === "string" ? config.defaultMode : null;
|
|
354
|
+
// 配置值的有无,而不是运行时推导值的有无:defaultMode 无 Schema 默认值
|
|
355
|
+
// (src/ponytail.ts),所以快照里读得到它,就说明用户的 profile 补丁里写了。
|
|
356
|
+
const modeConfigured = defaultMode !== null;
|
|
357
|
+
// 只有环境变量命中才锁:env 压过一切,此时改 patch 确实无效。
|
|
358
|
+
// fallback 命中必须放行——那是「补丁与 env 都没写」的兜底,不是更高优先级的配置,
|
|
359
|
+
// 用户改 patch 立刻生效(此前把 fallback 也当压制源,提示文案在说谎)。
|
|
360
|
+
const levelLocked = Boolean(remote && Array.isArray(remote.priority && remote.priority.chain) && remote.priority.chain.some(function (s) {
|
|
361
|
+
return s.level === "env" && s.hit;
|
|
272
362
|
}));
|
|
273
363
|
|
|
364
|
+
// 描述语言:带 Schema 默认值(src/ponytail.ts),读得到即生效值,无需区分「配过没配过」。
|
|
365
|
+
var descLang = typeof config.skillDescriptionLang === "string" ? config.skillDescriptionLang : "zh";
|
|
366
|
+
var setDescLang = React.useCallback(function (lang) {
|
|
367
|
+
applyOps([{ op: "set", path: ["skillDescriptionLang"], value: lang }],
|
|
368
|
+
t("lang.changed", { name: t("lang." + lang) }));
|
|
369
|
+
}, [applyOps]);
|
|
370
|
+
|
|
274
371
|
return e(
|
|
275
372
|
"div",
|
|
276
373
|
{ style: { display: "flex", flexDirection: "column", gap: "16px", maxWidth: "720px" } },
|
|
277
374
|
e("h3", { style: { fontSize: "15px", fontWeight: "600", margin: "0", color: "var(--dsw-alias-label-primary)" } },
|
|
278
375
|
t("panel.title")),
|
|
279
|
-
e(PrioritySection, { priority:
|
|
376
|
+
e(PrioritySection, { priority: remote ? remote.priority : null }),
|
|
280
377
|
e(
|
|
281
378
|
"div",
|
|
282
379
|
{ style: L.section },
|
|
283
380
|
e("h4", { style: L.title }, t("mode.title")),
|
|
284
|
-
e("p", { style: L.hint },
|
|
285
|
-
t("mode.hint")),
|
|
381
|
+
e("p", { style: L.hint }, t("mode.hint")),
|
|
286
382
|
e(P.SegmentedControl, {
|
|
287
383
|
id: "ponytail-mode",
|
|
288
384
|
label: t("mode.title"),
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
385
|
+
// 控件只显示配置值,绝不回退到 priority.effective:那是运行时推导档
|
|
386
|
+
// (env / 补丁 / 兜底合并的结果),回退会让刚安装的用户看到「标准」被高亮,
|
|
387
|
+
// 像亲手选过一样。未配置就如实显示「未设置」段。
|
|
388
|
+
// 「当前生效」由上方优先级链表独占呈现,这里不重复。
|
|
389
|
+
value: modeConfigured ? defaultMode : "unset",
|
|
390
|
+
options: modeOptions(modeConfigured),
|
|
391
|
+
disabled: busy || !writable || levelLocked,
|
|
392
|
+
onChange: setMode,
|
|
295
393
|
}),
|
|
394
|
+
!modeConfigured
|
|
395
|
+
? e("p", { style: L.hint }, t("mode.unsetHint"))
|
|
396
|
+
: null,
|
|
296
397
|
levelLocked
|
|
297
|
-
? e("p", { style: Object.assign({}, L.hint, { color: "var(--dsw-alias-state-warn-primary)" }) },
|
|
298
|
-
|
|
398
|
+
? e("p", { style: Object.assign({}, L.hint, { color: "var(--dsw-alias-state-warn-primary)" }) }, t("mode.lockedHint"))
|
|
399
|
+
: null,
|
|
400
|
+
!writable && snapshot.status === "ready"
|
|
401
|
+
? e("p", { style: Object.assign({}, L.hint, { color: "var(--dsw-alias-state-warn-primary)" }) }, t("state.readOnly"))
|
|
299
402
|
: null
|
|
300
403
|
),
|
|
301
404
|
e(
|
|
302
405
|
"div",
|
|
303
406
|
{ style: L.section },
|
|
304
407
|
e("h4", { style: L.title }, t("skills.title")),
|
|
305
|
-
e("p", { style: L.hint },
|
|
306
|
-
|
|
408
|
+
e("p", { style: L.hint }, t("skills.hint")),
|
|
409
|
+
e(P.SegmentedControl, {
|
|
410
|
+
id: "ponytail-desc-lang",
|
|
411
|
+
label: t("lang.title"),
|
|
412
|
+
value: descLang,
|
|
413
|
+
options: [
|
|
414
|
+
{ value: "zh", label: t("lang.zh") },
|
|
415
|
+
{ value: "en", label: t("lang.en") },
|
|
416
|
+
],
|
|
417
|
+
disabled: busy || !writable,
|
|
418
|
+
onChange: setDescLang,
|
|
419
|
+
}),
|
|
307
420
|
e(
|
|
308
421
|
"div",
|
|
309
422
|
{ style: { display: "flex", flexDirection: "column", gap: "6px" } },
|
|
310
|
-
|
|
423
|
+
SKILL_IDS.map(function (id) {
|
|
424
|
+
const enabled = disabled.indexOf(id) < 0;
|
|
425
|
+
var meta = (remote && Array.isArray(remote.skills)
|
|
426
|
+
? remote.skills.find(function (s) { return s.id === id; })
|
|
427
|
+
: null);
|
|
311
428
|
return e(
|
|
312
429
|
"div",
|
|
313
|
-
{ key:
|
|
430
|
+
{ key: id, style: L.row },
|
|
314
431
|
e(
|
|
315
432
|
"div",
|
|
316
433
|
{ style: L.rowText },
|
|
317
|
-
e("span", { style: L.rowName }, "/" +
|
|
318
|
-
e("span", { style: L.rowDesc },
|
|
434
|
+
e("span", { style: L.rowName }, "/" + id),
|
|
435
|
+
e("span", { style: L.rowDesc }, meta ? meta.description : id)
|
|
319
436
|
),
|
|
320
437
|
e(P.Switch, {
|
|
321
|
-
checked:
|
|
322
|
-
disabled:
|
|
323
|
-
label: t("skills.toggleOn", { name:
|
|
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
|
-
},
|
|
438
|
+
checked: enabled,
|
|
439
|
+
disabled: busy || !writable,
|
|
440
|
+
label: t("skills.toggleOn", { name: id }),
|
|
441
|
+
onChange: function (next) { toggleSkill(id, next); },
|
|
330
442
|
})
|
|
331
443
|
);
|
|
332
444
|
})
|
|
@@ -336,27 +448,40 @@ window.__ModuleLoader__.load({
|
|
|
336
448
|
"div",
|
|
337
449
|
{ style: Object.assign({}, L.footer, L.last) },
|
|
338
450
|
e("span", { style: L.hint },
|
|
339
|
-
|
|
340
|
-
?
|
|
341
|
-
:
|
|
342
|
-
? state.
|
|
343
|
-
: state.
|
|
344
|
-
? t("state.loading")
|
|
345
|
-
: t("state.saved")),
|
|
451
|
+
note !== null
|
|
452
|
+
? note
|
|
453
|
+
: snapshot.status === "loading"
|
|
454
|
+
? t("state.loading")
|
|
455
|
+
: t("state.saved")),
|
|
346
456
|
e(P.Button, {
|
|
347
457
|
variant: "outline",
|
|
348
458
|
size: "sm",
|
|
349
|
-
disabled:
|
|
459
|
+
disabled: busy || !writable,
|
|
350
460
|
onClick: function () {
|
|
351
461
|
if (!confirm(t("confirm.reset"))) return;
|
|
352
|
-
|
|
462
|
+
resetAll();
|
|
353
463
|
},
|
|
354
464
|
}, t("button.reset"))
|
|
355
465
|
)
|
|
356
466
|
);
|
|
357
467
|
}
|
|
358
468
|
|
|
359
|
-
function apply(ctx) {
|
|
469
|
+
async function apply(ctx) {
|
|
470
|
+
// 先把只读推导值的端点挂到官方网关上:命名空间不会自动出现,
|
|
471
|
+
// 必须由客户端显式声明并 $mount。$mount 返回的 disposer 即 effect 的清理函数。
|
|
472
|
+
// 挂载失败(部署没有 remote 服务)不阻断其余能力,卡片照常出现,只是优先级段不显示。
|
|
473
|
+
var mounted = null;
|
|
474
|
+
try {
|
|
475
|
+
mounted = await ctx.remote.$mount(PONYTAIL_REMOTE);
|
|
476
|
+
} catch (error) {
|
|
477
|
+
if (ctx.logger) ctx.logger.warn("[ponytail] remote 贡献挂载失败(优先级诊断段将不显示): " + String(error));
|
|
478
|
+
}
|
|
479
|
+
ctx.effect(function () {
|
|
480
|
+
return function () {
|
|
481
|
+
if (mounted) void mounted();
|
|
482
|
+
};
|
|
483
|
+
}, "ponytail: remote contribution");
|
|
484
|
+
|
|
360
485
|
// 官方 locale:注册 ponytail 命名空间双语字典并绑定 t(真源 locale/*.json 构建期内嵌)
|
|
361
486
|
ctx.effect(function () {
|
|
362
487
|
return ctx.locale.register(NS, { zh: ZH, en: EN });
|
|
@@ -364,17 +489,55 @@ window.__ModuleLoader__.load({
|
|
|
364
489
|
localeCtx = ctx.locale;
|
|
365
490
|
t = ctx.locale.bind(NS);
|
|
366
491
|
|
|
367
|
-
//
|
|
368
|
-
ctx.
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
492
|
+
// 官方配置表单:宿主 settings 服务投影出的 ponytail 命名空间
|
|
493
|
+
var scope = ctx.configForms.get("ponytail");
|
|
494
|
+
|
|
495
|
+
// 只读推导值:官方 Typert 通道(ctx.remote.ponytail.snapshot())。
|
|
496
|
+
// 远程方法返回的是 { ok, value } / { ok:false, error } 信封(网关 client 侧 lib/client.js:1795-1802),
|
|
497
|
+
// 这里自己拆包并折成一个最小快照 store:命名空间不可用或调用失败时停在 unavailable,
|
|
498
|
+
// 优先级段降级为不显示,不影响其余控件。此处已在上面的 $mount 完成后才读,无需重试。
|
|
499
|
+
var remoteStore = mounted === null
|
|
500
|
+
? createUnavailableStore()
|
|
501
|
+
: createRemoteStore(ctx, "ponytail");
|
|
502
|
+
|
|
503
|
+
// 插槽注册沿用 whileServed:命名空间没被宿主服务时卡片整体不出现,
|
|
504
|
+
// 部署若从未组合 settings 服务,页面上不留本插件的痕迹
|
|
505
|
+
// (与官方 dsh-client-ui-settings-shell 同一生命周期协议,ui-settings-shell/lib/client.js:182)。
|
|
506
|
+
//
|
|
507
|
+
// 数据面经 inject 提供:组件只从 props 消费,不触碰 ctx(渲染器铁律)。
|
|
508
|
+
// usePonytailConfig 读官方配置表单快照;usePonytailSnapshot 读远程快照;
|
|
509
|
+
// reloadRemote 在写入后让面板重新拉服务端真值。
|
|
510
|
+
var formStore = {
|
|
511
|
+
getSnapshot: function () { return scope.getSnapshot(); },
|
|
512
|
+
subscribe: function (listener) { return scope.subscribe(listener); },
|
|
513
|
+
};
|
|
514
|
+
var face = function () {
|
|
515
|
+
return {
|
|
516
|
+
hooks: {
|
|
517
|
+
ponytailConfig: formStore,
|
|
518
|
+
ponytailSnapshot: remoteStore,
|
|
519
|
+
},
|
|
520
|
+
// 直接透传官方表单控制器的方法:它自带写队列串行化、revision 冲突恢复
|
|
521
|
+
// 与镜像折入(dsh-client-ui-settings 的 ConfigFormController.mutate,lib/client.js:1177-1194)
|
|
522
|
+
mutate: function (ops, revision) { return scope.mutate(ops, revision); },
|
|
523
|
+
reloadRemote: function () { remoteStore.reload(); },
|
|
524
|
+
};
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
ctx.effect(function () {
|
|
528
|
+
return ctx.configForms.whileServed(["ponytail"], function () {
|
|
529
|
+
return ctx.slots.inject("plugins.bundle.config", function () {
|
|
530
|
+
return ctx.slots.register(
|
|
531
|
+
{ name: "plugins.bundle.config", key: "@wenaixi/dsh-ponytail", locale: NS, inject: face },
|
|
532
|
+
PonytailConfigPanel
|
|
533
|
+
);
|
|
534
|
+
});
|
|
535
|
+
});
|
|
536
|
+
}, "ponytail: config card");
|
|
374
537
|
}
|
|
375
538
|
|
|
376
539
|
exports.apply = apply;
|
|
377
|
-
exports.inject = ["slots", "locale"];
|
|
540
|
+
exports.inject = ["slots", "locale", "configForms", "remote"];
|
|
378
541
|
return module.exports;
|
|
379
542
|
}
|
|
380
543
|
});
|