@lmzhen/dsh-evolution-ui 0.32.4 → 0.32.6
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 +3 -4
- package/lib/client.js +1686 -1068
- package/lib/types/panels/history/client/Panel.d.ts +3 -0
- package/lib/types/panels/history/client/Workbench.d.ts +53 -0
- package/lib/types/panels/history/client/index.d.ts +8 -4
- package/lib/types/panels/settings/ParamCard.d.ts +13 -5
- package/lib/types/panels/settings/api.d.ts +7 -0
- package/lib/types/panels/settings/generated-about.d.ts +1 -1
- package/lib/types/panels/settings/generated-params.d.ts +12 -11
- package/lib/types/panels/settings/index.d.ts +8 -23
- package/lib/types/panels/settings/messages.d.ts +18 -2
- package/lib/types/panels/settings/pending-state.d.ts +2 -0
- package/lib/types/panels/settings/seam.d.ts +12 -27
- package/package.json +4 -4
- package/lib/types/panels/settings/RowConfigCard.d.ts +0 -30
package/lib/client.js
CHANGED
|
@@ -5,163 +5,6 @@ window.__ModuleLoader__.load({
|
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
let react = require("react");
|
|
8
|
-
//#region ../evolution-client-kit/src/generated/routes.generated.ts
|
|
9
|
-
/**
|
|
10
|
-
* Every family host path, generated from evolution-core's HOST_ROUTES (the ONE declaration).
|
|
11
|
-
* A browser half cannot import the host package at runtime (client bundles keep @deepseek-ai/*
|
|
12
|
-
* external), so the table is mirrored here mechanically; a rename in core regenerates all three
|
|
13
|
-
* client copies, and the generator's --check makes a stale copy red.
|
|
14
|
-
*/
|
|
15
|
-
const ROUTES = {
|
|
16
|
-
approval: {
|
|
17
|
-
pending: "/api/dsh-evolution/approval/pending",
|
|
18
|
-
preview: "/api/dsh-evolution/approval/preview",
|
|
19
|
-
approve: "/api/dsh-evolution/approval/approve",
|
|
20
|
-
reject: "/api/dsh-evolution/approval/reject"
|
|
21
|
-
},
|
|
22
|
-
"skill-history": {
|
|
23
|
-
skills: "/api/dsh-evolution/skill-history/skills",
|
|
24
|
-
versions: "/api/dsh-evolution/skill-history/versions",
|
|
25
|
-
diff: "/api/dsh-evolution/skill-history/versions/diff",
|
|
26
|
-
body: "/api/dsh-evolution/skill-history/versions/body",
|
|
27
|
-
undo: "/api/dsh-evolution/skill-history/undo",
|
|
28
|
-
health: "/api/dsh-evolution/skill-history/health"
|
|
29
|
-
},
|
|
30
|
-
"skill-health": { rows: "/api/dsh-evolution/skill-health/rows" },
|
|
31
|
-
merge: {
|
|
32
|
-
view: "/api/dsh-evolution/merge/view",
|
|
33
|
-
rows: "/api/dsh-evolution/merge/rows",
|
|
34
|
-
run: "/api/dsh-evolution/merge/run",
|
|
35
|
-
runs: "/api/dsh-evolution/merge/runs",
|
|
36
|
-
body: "/api/dsh-evolution/merge/body",
|
|
37
|
-
history: "/api/dsh-evolution/merge/history"
|
|
38
|
-
},
|
|
39
|
-
diagnostics: { view: "/api/dsh-evolution/diagnostics" }
|
|
40
|
-
};
|
|
41
|
-
//#endregion
|
|
42
|
-
//#region ../evolution-client-kit/src/generated/route-client.generated.ts
|
|
43
|
-
/** Classify a non-2xx status: 401/403/404 keep their own kind, everything else is a server answer. */
|
|
44
|
-
function classify(status) {
|
|
45
|
-
if (status === 401) return {
|
|
46
|
-
kind: "unauthenticated",
|
|
47
|
-
status: 401
|
|
48
|
-
};
|
|
49
|
-
if (status === 403) return {
|
|
50
|
-
kind: "forbidden",
|
|
51
|
-
status: 403
|
|
52
|
-
};
|
|
53
|
-
if (status === 404) return {
|
|
54
|
-
kind: "unmounted",
|
|
55
|
-
status: 404
|
|
56
|
-
};
|
|
57
|
-
return {
|
|
58
|
-
kind: "server",
|
|
59
|
-
status
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* One request, no retries and no body parsing for the platform's admission answers.
|
|
64
|
-
* @param doFetch - the fetch in use (the browser's, or a spec's stub).
|
|
65
|
-
* @param path - the exact route path from the generated ROUTES table.
|
|
66
|
-
* @param init - standard RequestInit.
|
|
67
|
-
* @returns the data, or the classified failure (network/parse failures read as 'server').
|
|
68
|
-
*/
|
|
69
|
-
async function requestJson(doFetch, path, init) {
|
|
70
|
-
let response;
|
|
71
|
-
try {
|
|
72
|
-
response = await doFetch(path, init);
|
|
73
|
-
} catch (error) {
|
|
74
|
-
return {
|
|
75
|
-
ok: false,
|
|
76
|
-
failure: {
|
|
77
|
-
kind: "server",
|
|
78
|
-
status: 0,
|
|
79
|
-
detail: error instanceof Error ? error.message : String(error)
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
if (!response.ok) return {
|
|
84
|
-
ok: false,
|
|
85
|
-
failure: classify(response.status)
|
|
86
|
-
};
|
|
87
|
-
let body;
|
|
88
|
-
try {
|
|
89
|
-
body = await response.json();
|
|
90
|
-
} catch {
|
|
91
|
-
return {
|
|
92
|
-
ok: false,
|
|
93
|
-
failure: {
|
|
94
|
-
kind: "server",
|
|
95
|
-
status: response.status
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
if (typeof body === "object" && body !== null) {
|
|
100
|
-
const record = body;
|
|
101
|
-
if (record.ok === true) return {
|
|
102
|
-
ok: true,
|
|
103
|
-
data: record.data
|
|
104
|
-
};
|
|
105
|
-
const nested = record.error;
|
|
106
|
-
const errorRecord = typeof nested === "object" && nested !== null ? nested : void 0;
|
|
107
|
-
const code = typeof record.code === "string" && record.code !== "" ? record.code : typeof errorRecord?.code === "string" ? errorRecord.code : void 0;
|
|
108
|
-
const message = typeof record.message === "string" && record.message !== "" ? record.message : typeof errorRecord?.message === "string" ? errorRecord.message : void 0;
|
|
109
|
-
if (record.ok === false && code !== void 0 && message !== void 0 && message !== "") return {
|
|
110
|
-
ok: false,
|
|
111
|
-
failure: {
|
|
112
|
-
kind: "refused",
|
|
113
|
-
code,
|
|
114
|
-
message
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
return {
|
|
119
|
-
ok: false,
|
|
120
|
-
failure: {
|
|
121
|
-
kind: "server",
|
|
122
|
-
status: response.status
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
/** The one-sentence rendering legacy callers attach to a classified failure. */
|
|
127
|
-
function failureMessage(failure) {
|
|
128
|
-
switch (failure.kind) {
|
|
129
|
-
case "refused": return failure.message;
|
|
130
|
-
case "server": return failure.status === 0 ? failure.detail === void 0 || failure.detail === "" ? "网络错误:无法连接宿主" : failure.detail : "HTTP " + String(failure.status);
|
|
131
|
-
case "unauthenticated": return "HTTP 401";
|
|
132
|
-
case "forbidden": return "HTTP 403";
|
|
133
|
-
case "unmounted": return "HTTP 404";
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
//#endregion
|
|
137
|
-
//#region src/panels/settings/api.ts
|
|
138
|
-
/**
|
|
139
|
-
* The approval routes this bundle calls, and the ONE place a request is made.
|
|
140
|
-
*
|
|
141
|
-
* Structural types on purpose: the browser half cannot import the host package at runtime, so the row
|
|
142
|
-
* shape is declared by what the route hands over. Every answer — including a refusal — is parsed the
|
|
143
|
-
* same way, because a refusal is a business answer the card shows as a sentence, not a transport error.
|
|
144
|
-
* @module @lmzhen/dsh-evolution-ui/panels/settings/client/api
|
|
145
|
-
*/
|
|
146
|
-
/**
|
|
147
|
-
* Build the approval face over one fetch implementation.
|
|
148
|
-
* @param doFetch - the fetch to use (the platform's global in the browser, a fake in a spec).
|
|
149
|
-
* @returns the three calls the pending card makes.
|
|
150
|
-
*/
|
|
151
|
-
function createApprovalApi(doFetch) {
|
|
152
|
-
const post = async (path, id) => await requestJson(doFetch, path, {
|
|
153
|
-
method: "POST",
|
|
154
|
-
headers: { "content-type": "application/json" },
|
|
155
|
-
body: JSON.stringify({ id })
|
|
156
|
-
});
|
|
157
|
-
return {
|
|
158
|
-
pending: async (status) => await requestJson(doFetch, status === void 0 ? ROUTES.approval.pending : ROUTES.approval.pending + "?status=" + status),
|
|
159
|
-
approve: async (id) => await post(ROUTES.approval.approve, id),
|
|
160
|
-
reject: async (id) => await post(ROUTES.approval.reject, id),
|
|
161
|
-
preview: async (id) => await requestJson(doFetch, ROUTES.approval.preview + "?id=" + encodeURIComponent(id))
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
//#endregion
|
|
165
8
|
//#region src/panels/settings/generated-params.ts
|
|
166
9
|
const CLIENT_PARAM_SECTIONS = [
|
|
167
10
|
{
|
|
@@ -171,37 +14,55 @@ window.__ModuleLoader__.load({
|
|
|
171
14
|
{
|
|
172
15
|
id: "evolution-approval.enabled",
|
|
173
16
|
group: "deployment",
|
|
17
|
+
groupLabel: "默认",
|
|
18
|
+
groupLabelEn: "Defaults",
|
|
174
19
|
doc: "Master approval switch: run the approval pipeline for foreground writes.",
|
|
175
20
|
label: "启用批准流水线",
|
|
21
|
+
labelEn: "Enable approval pipeline",
|
|
176
22
|
hint: "关闭后前台写入不再经批准。",
|
|
23
|
+
hintEn: "Master approval switch: run the approval pipeline for foreground writes.",
|
|
177
24
|
control: "switch",
|
|
178
25
|
unit: "",
|
|
26
|
+
unitEn: "",
|
|
179
27
|
values: [],
|
|
180
28
|
valueLabels: [],
|
|
29
|
+
valueLabelsEn: [],
|
|
181
30
|
atLeast: ""
|
|
182
31
|
},
|
|
183
32
|
{
|
|
184
33
|
id: "evolution-approval.pendingTtlMs",
|
|
185
34
|
group: "deployment",
|
|
35
|
+
groupLabel: "默认",
|
|
36
|
+
groupLabelEn: "Defaults",
|
|
186
37
|
doc: "How long a staged write may wait before it expires: expired writes are closed as rejected and are NEVER executed; 0 (the default) means the window never expires.",
|
|
187
38
|
label: "待批过期时间",
|
|
39
|
+
labelEn: "Pending expiry",
|
|
188
40
|
hint: "一条待批写入最多等多久:超时就按「拒绝」关掉(不会执行,写效果不会落地);0 = 永不过期(默认)。",
|
|
41
|
+
hintEn: "How long a staged write may wait before it expires: expired writes are closed as rejected and are NEVER executed; 0 (the default) means the window never expires.",
|
|
189
42
|
control: "number",
|
|
190
43
|
unit: "毫秒",
|
|
44
|
+
unitEn: "ms",
|
|
191
45
|
values: [],
|
|
192
46
|
valueLabels: [],
|
|
47
|
+
valueLabelsEn: [],
|
|
193
48
|
atLeast: ""
|
|
194
49
|
},
|
|
195
50
|
{
|
|
196
51
|
id: "approvalConfirm",
|
|
197
52
|
group: "write-caps",
|
|
53
|
+
groupLabel: "写入上限",
|
|
54
|
+
groupLabelEn: "Write limits",
|
|
198
55
|
doc: "What the confirmation before an APPROVE does: execute immediately, or ask first.",
|
|
199
56
|
label: "批准前先确认",
|
|
57
|
+
labelEn: "Confirm before approving",
|
|
200
58
|
hint: "点「批准」时要不要再确认一次:直接执行 / 先确认。默认直接执行(不打断批量批准)。",
|
|
59
|
+
hintEn: "What the confirmation before an APPROVE does: execute immediately, or ask first.",
|
|
201
60
|
control: "select",
|
|
202
61
|
unit: "",
|
|
62
|
+
unitEn: "",
|
|
203
63
|
values: ["auto", "ask"],
|
|
204
64
|
valueLabels: ["直接执行", "先确认"],
|
|
65
|
+
valueLabelsEn: ["Execute directly", "Ask first"],
|
|
205
66
|
atLeast: ""
|
|
206
67
|
}
|
|
207
68
|
]
|
|
@@ -212,13 +73,19 @@ window.__ModuleLoader__.load({
|
|
|
212
73
|
fields: [{
|
|
213
74
|
id: "evolution-commands.maintainHandoffOnSettle",
|
|
214
75
|
group: "deployment",
|
|
76
|
+
groupLabel: "默认",
|
|
77
|
+
groupLabelEn: "Defaults",
|
|
215
78
|
doc: "Hand the maintenance report to the model automatically once a scan settles successfully (the same injection as maintain handoff); off by default — enabling it costs one extra model call per successful run.",
|
|
216
79
|
label: "扫描完成后自动交接",
|
|
80
|
+
labelEn: "Hand off after a scan",
|
|
217
81
|
hint: "维护扫描成功结束后,自动把报告交给模型(等同 maintain handoff);默认关闭,每次成功扫描会多花一次模型调用。",
|
|
82
|
+
hintEn: "Hand the maintenance report to the model automatically once a scan settles successfully (the same injection as maintain handoff); off by default — enabling it costs one extra model call per successful run.",
|
|
218
83
|
control: "switch",
|
|
219
84
|
unit: "",
|
|
85
|
+
unitEn: "",
|
|
220
86
|
values: [],
|
|
221
87
|
valueLabels: [],
|
|
88
|
+
valueLabelsEn: [],
|
|
222
89
|
atLeast: ""
|
|
223
90
|
}]
|
|
224
91
|
},
|
|
@@ -229,133 +96,199 @@ window.__ModuleLoader__.load({
|
|
|
229
96
|
{
|
|
230
97
|
id: "curatorIntervalHours",
|
|
231
98
|
group: "curator",
|
|
99
|
+
groupLabel: "技能整理",
|
|
100
|
+
groupLabelEn: "Skill tidy-up",
|
|
232
101
|
doc: "Minimum hours between deterministic curation passes.",
|
|
233
102
|
label: "自动整理间隔",
|
|
103
|
+
labelEn: "Tidy-up interval",
|
|
234
104
|
hint: "两次自动整理之间至少间隔多少小时。",
|
|
105
|
+
hintEn: "Minimum hours between deterministic curation passes.",
|
|
235
106
|
control: "number",
|
|
236
107
|
unit: "小时",
|
|
108
|
+
unitEn: "hours",
|
|
237
109
|
values: [],
|
|
238
110
|
valueLabels: [],
|
|
111
|
+
valueLabelsEn: [],
|
|
239
112
|
atLeast: ""
|
|
240
113
|
},
|
|
241
114
|
{
|
|
242
115
|
id: "staleAfterDays",
|
|
243
116
|
group: "curator",
|
|
117
|
+
groupLabel: "技能整理",
|
|
118
|
+
groupLabelEn: "Skill tidy-up",
|
|
244
119
|
doc: "Inactive days before a skill counts as stale.",
|
|
245
120
|
label: "多久没用算过时",
|
|
121
|
+
labelEn: "Days until stale",
|
|
246
122
|
hint: "技能连续多少天没被用到就算过时。",
|
|
123
|
+
hintEn: "Inactive days before a skill counts as stale.",
|
|
247
124
|
control: "number",
|
|
248
125
|
unit: "天",
|
|
126
|
+
unitEn: "days",
|
|
249
127
|
values: [],
|
|
250
128
|
valueLabels: [],
|
|
129
|
+
valueLabelsEn: [],
|
|
251
130
|
atLeast: ""
|
|
252
131
|
},
|
|
253
132
|
{
|
|
254
133
|
id: "archiveAfterDays",
|
|
255
134
|
group: "curator",
|
|
135
|
+
groupLabel: "技能整理",
|
|
136
|
+
groupLabelEn: "Skill tidy-up",
|
|
256
137
|
doc: "Inactive days before a stale skill is archived (must be >= staleAfterDays).",
|
|
257
138
|
label: "过时后归档",
|
|
139
|
+
labelEn: "Archive after stale",
|
|
258
140
|
hint: "过时技能再过多少天移入归档(不能小于上一项)。",
|
|
141
|
+
hintEn: "Inactive days before a stale skill is archived (must be >= staleAfterDays).",
|
|
259
142
|
control: "number",
|
|
260
143
|
unit: "天",
|
|
144
|
+
unitEn: "days",
|
|
261
145
|
values: [],
|
|
262
146
|
valueLabels: [],
|
|
147
|
+
valueLabelsEn: [],
|
|
263
148
|
atLeast: "staleAfterDays"
|
|
264
149
|
},
|
|
265
150
|
{
|
|
266
151
|
id: "minIdleHours",
|
|
267
152
|
group: "curator",
|
|
153
|
+
groupLabel: "技能整理",
|
|
154
|
+
groupLabelEn: "Skill tidy-up",
|
|
268
155
|
doc: "Idle hours required before an automatic curation pass runs.",
|
|
269
156
|
label: "最少空闲时长",
|
|
157
|
+
labelEn: "Minimum idle time",
|
|
270
158
|
hint: "你至少多少小时没用,才运行自动整理。",
|
|
159
|
+
hintEn: "Idle hours required before an automatic curation pass runs.",
|
|
271
160
|
control: "number",
|
|
272
161
|
unit: "小时",
|
|
162
|
+
unitEn: "hours",
|
|
273
163
|
values: [],
|
|
274
164
|
valueLabels: [],
|
|
165
|
+
valueLabelsEn: [],
|
|
275
166
|
atLeast: ""
|
|
276
167
|
},
|
|
277
168
|
{
|
|
278
169
|
id: "minIdleFailOpen",
|
|
279
170
|
group: "curator",
|
|
171
|
+
groupLabel: "技能整理",
|
|
172
|
+
groupLabelEn: "Skill tidy-up",
|
|
280
173
|
doc: "Let the idle gate open when the activity probe is unavailable.",
|
|
281
174
|
label: "检测不到活动也运行",
|
|
175
|
+
labelEn: "Run without activity signal",
|
|
282
176
|
hint: "无法判断你是否空闲时,仍然运行自动整理。",
|
|
177
|
+
hintEn: "Let the idle gate open when the activity probe is unavailable.",
|
|
283
178
|
control: "switch",
|
|
284
179
|
unit: "",
|
|
180
|
+
unitEn: "",
|
|
285
181
|
values: [],
|
|
286
182
|
valueLabels: [],
|
|
183
|
+
valueLabelsEn: [],
|
|
287
184
|
atLeast: ""
|
|
288
185
|
},
|
|
289
186
|
{
|
|
290
187
|
id: "llmReview",
|
|
291
188
|
group: "curator",
|
|
189
|
+
groupLabel: "技能整理",
|
|
190
|
+
groupLabelEn: "Skill tidy-up",
|
|
292
191
|
doc: "Enable the LLM nomination pass on top of the deterministic lifecycle.",
|
|
293
192
|
label: "用模型挑选",
|
|
193
|
+
labelEn: "Let the model nominate",
|
|
294
194
|
hint: "在固定规则之外,再用模型挑一遍候选项。",
|
|
195
|
+
hintEn: "Enable the LLM nomination pass on top of the deterministic lifecycle.",
|
|
295
196
|
control: "switch",
|
|
296
197
|
unit: "",
|
|
198
|
+
unitEn: "",
|
|
297
199
|
values: [],
|
|
298
200
|
valueLabels: [],
|
|
201
|
+
valueLabelsEn: [],
|
|
299
202
|
atLeast: ""
|
|
300
203
|
},
|
|
301
204
|
{
|
|
302
205
|
id: "curatorReviewMaxTokens",
|
|
303
206
|
group: "curator",
|
|
207
|
+
groupLabel: "技能整理",
|
|
208
|
+
groupLabelEn: "Skill tidy-up",
|
|
304
209
|
doc: "Token budget of the curator LLM review.",
|
|
305
210
|
label: "模型处理上限",
|
|
211
|
+
labelEn: "Model token budget",
|
|
306
212
|
hint: "每次用模型挑选时最多消耗多少 token。",
|
|
213
|
+
hintEn: "Token budget of the curator LLM review.",
|
|
307
214
|
control: "number",
|
|
308
215
|
unit: "token",
|
|
216
|
+
unitEn: "tokens",
|
|
309
217
|
values: [],
|
|
310
218
|
valueLabels: [],
|
|
219
|
+
valueLabelsEn: [],
|
|
311
220
|
atLeast: ""
|
|
312
221
|
},
|
|
313
222
|
{
|
|
314
223
|
id: "curatorReviewTimeoutMs",
|
|
315
224
|
group: "curator",
|
|
225
|
+
groupLabel: "技能整理",
|
|
226
|
+
groupLabelEn: "Skill tidy-up",
|
|
316
227
|
doc: "Wall-clock bound of the curator LLM review.",
|
|
317
228
|
label: "模型处理超时",
|
|
229
|
+
labelEn: "Model review timeout",
|
|
318
230
|
hint: "一次模型挑选最长允许运行多久。",
|
|
231
|
+
hintEn: "Wall-clock bound of the curator LLM review.",
|
|
319
232
|
control: "number",
|
|
320
233
|
unit: "毫秒",
|
|
234
|
+
unitEn: "ms",
|
|
321
235
|
values: [],
|
|
322
236
|
valueLabels: [],
|
|
237
|
+
valueLabelsEn: [],
|
|
323
238
|
atLeast: ""
|
|
324
239
|
},
|
|
325
240
|
{
|
|
326
241
|
id: "healthSoftBodyChars",
|
|
327
242
|
group: "curator",
|
|
243
|
+
groupLabel: "技能整理",
|
|
244
|
+
groupLabelEn: "Skill tidy-up",
|
|
328
245
|
doc: "Body character line the health view judges against.",
|
|
329
246
|
label: "正文长度参考值",
|
|
247
|
+
labelEn: "Body length reference",
|
|
330
248
|
hint: "技能正文超过多少字符算偏长(只用于健康提示)。",
|
|
249
|
+
hintEn: "Body character line the health view judges against.",
|
|
331
250
|
control: "number",
|
|
332
251
|
unit: "字符",
|
|
252
|
+
unitEn: "chars",
|
|
333
253
|
values: [],
|
|
334
254
|
valueLabels: [],
|
|
255
|
+
valueLabelsEn: [],
|
|
335
256
|
atLeast: ""
|
|
336
257
|
},
|
|
337
258
|
{
|
|
338
259
|
id: "healthStampDensityPerKb",
|
|
339
260
|
group: "curator",
|
|
261
|
+
groupLabel: "技能整理",
|
|
262
|
+
groupLabelEn: "Skill tidy-up",
|
|
340
263
|
doc: "Stamp density per KB that flags log-like content in a body.",
|
|
341
264
|
label: "日志化判定阈值",
|
|
265
|
+
labelEn: "Log-like density threshold",
|
|
342
266
|
hint: "每 KB 出现多少个时间标记,就判为「像日志」。",
|
|
267
|
+
hintEn: "Stamp density per KB that flags log-like content in a body.",
|
|
343
268
|
control: "number",
|
|
344
269
|
unit: "处/KB",
|
|
270
|
+
unitEn: "per KB",
|
|
345
271
|
values: [],
|
|
346
272
|
valueLabels: [],
|
|
273
|
+
valueLabelsEn: [],
|
|
347
274
|
atLeast: ""
|
|
348
275
|
},
|
|
349
276
|
{
|
|
350
277
|
id: "healthChurnMinPatches",
|
|
351
278
|
group: "curator",
|
|
279
|
+
groupLabel: "技能整理",
|
|
280
|
+
groupLabelEn: "Skill tidy-up",
|
|
352
281
|
doc: "Patches without a read that flag a write-ghost skill.",
|
|
353
282
|
label: "只写不读判定次数",
|
|
283
|
+
labelEn: "Write-only threshold",
|
|
354
284
|
hint: "连续多少次改动都没有被读取,就判为「只写不读」。",
|
|
285
|
+
hintEn: "Patches without a read that flag a write-ghost skill.",
|
|
355
286
|
control: "number",
|
|
356
287
|
unit: "处",
|
|
288
|
+
unitEn: "patches",
|
|
357
289
|
values: [],
|
|
358
290
|
valueLabels: [],
|
|
291
|
+
valueLabelsEn: [],
|
|
359
292
|
atLeast: ""
|
|
360
293
|
}
|
|
361
294
|
]
|
|
@@ -367,37 +300,55 @@ window.__ModuleLoader__.load({
|
|
|
367
300
|
{
|
|
368
301
|
id: "mergeApproval",
|
|
369
302
|
group: "merge",
|
|
303
|
+
groupLabel: "合并",
|
|
304
|
+
groupLabelEn: "Merge",
|
|
370
305
|
doc: "Pause after round 1 and put the pair in the pending queue instead of authoring round 2 automatically.",
|
|
371
306
|
label: "合并审批",
|
|
307
|
+
labelEn: "Merge approval",
|
|
372
308
|
hint: "开启后,第一轮判定完先停下、进入待批准清单;关闭则直接进入第二轮合并。",
|
|
309
|
+
hintEn: "Pause after round 1 and put the pair in the pending queue instead of authoring round 2 automatically.",
|
|
373
310
|
control: "switch",
|
|
374
311
|
unit: "",
|
|
312
|
+
unitEn: "",
|
|
375
313
|
values: [],
|
|
376
314
|
valueLabels: [],
|
|
315
|
+
valueLabelsEn: [],
|
|
377
316
|
atLeast: ""
|
|
378
317
|
},
|
|
379
318
|
{
|
|
380
319
|
id: "mergeSkippedTtlDays",
|
|
381
320
|
group: "merge",
|
|
321
|
+
groupLabel: "合并",
|
|
322
|
+
groupLabelEn: "Merge",
|
|
382
323
|
doc: "Days a skipped pair stays out of screening; 0 means it never expires.",
|
|
383
324
|
label: "无需合并保留天数",
|
|
325
|
+
labelEn: "Keep skipped pairs",
|
|
384
326
|
hint: "判定无需合并的对,多少天内不再参与筛选;0 表示一直保留。",
|
|
327
|
+
hintEn: "Days a skipped pair stays out of screening; 0 means it never expires.",
|
|
385
328
|
control: "number",
|
|
386
329
|
unit: "天",
|
|
330
|
+
unitEn: "days",
|
|
387
331
|
values: [],
|
|
388
332
|
valueLabels: [],
|
|
333
|
+
valueLabelsEn: [],
|
|
389
334
|
atLeast: ""
|
|
390
335
|
},
|
|
391
336
|
{
|
|
392
337
|
id: "mergeRunTimeoutMinutes",
|
|
393
338
|
group: "merge",
|
|
339
|
+
groupLabel: "合并",
|
|
340
|
+
groupLabelEn: "Merge",
|
|
394
341
|
doc: "Wall-clock budget of one merge run; 0 means no limit.",
|
|
395
342
|
label: "单次运行上限",
|
|
343
|
+
labelEn: "Run time limit",
|
|
396
344
|
hint: "一次合并运行最多持续多少分钟;0 表示不设上限。",
|
|
345
|
+
hintEn: "Wall-clock budget of one merge run; 0 means no limit.",
|
|
397
346
|
control: "number",
|
|
398
347
|
unit: "分钟",
|
|
348
|
+
unitEn: "min",
|
|
399
349
|
values: [],
|
|
400
350
|
valueLabels: [],
|
|
351
|
+
valueLabelsEn: [],
|
|
401
352
|
atLeast: ""
|
|
402
353
|
}
|
|
403
354
|
]
|
|
@@ -409,35 +360,52 @@ window.__ModuleLoader__.load({
|
|
|
409
360
|
{
|
|
410
361
|
id: "reviewSkillInterval",
|
|
411
362
|
group: "review",
|
|
363
|
+
groupLabel: "审查",
|
|
364
|
+
groupLabelEn: "Review",
|
|
412
365
|
doc: "Tool calls between skill-review injections: a turn advances by its tool-call count (a turn with none counts as one), or by 1 when the turn itself used a skill.",
|
|
413
366
|
label: "技能检查间隔",
|
|
367
|
+
labelEn: "Skill review interval",
|
|
414
368
|
hint: "每累计多少次工具调用检查一次技能:一轮按该轮工具调用数累加(一轮没有工具调用也算 1 次),本轮本身用到技能则只算 1 次。",
|
|
369
|
+
hintEn: "Tool calls between skill-review injections: a turn advances by its tool-call count (a turn with none counts as one), or by 1 when the turn itself used a skill.",
|
|
415
370
|
control: "number",
|
|
416
371
|
unit: "次",
|
|
372
|
+
unitEn: "calls",
|
|
417
373
|
values: [],
|
|
418
374
|
valueLabels: [],
|
|
375
|
+
valueLabelsEn: [],
|
|
419
376
|
atLeast: ""
|
|
420
377
|
},
|
|
421
378
|
{
|
|
422
379
|
id: "reviewMemoryInterval",
|
|
423
380
|
group: "review",
|
|
381
|
+
groupLabel: "审查",
|
|
382
|
+
groupLabelEn: "Review",
|
|
424
383
|
doc: "Tool calls between memory-review injections: a turn advances by its tool-call count (a turn with none counts as one), or by 1 when the turn itself touched memory.",
|
|
425
384
|
label: "记忆检查间隔",
|
|
385
|
+
labelEn: "Memory review interval",
|
|
426
386
|
hint: "每累计多少次工具调用检查一次记忆:一轮按该轮工具调用数累加(一轮没有工具调用也算 1 次),本轮本身用到记忆则只算 1 次。",
|
|
387
|
+
hintEn: "Tool calls between memory-review injections: a turn advances by its tool-call count (a turn with none counts as one), or by 1 when the turn itself touched memory.",
|
|
427
388
|
control: "number",
|
|
428
389
|
unit: "次",
|
|
390
|
+
unitEn: "calls",
|
|
429
391
|
values: [],
|
|
430
392
|
valueLabels: [],
|
|
393
|
+
valueLabelsEn: [],
|
|
431
394
|
atLeast: ""
|
|
432
395
|
},
|
|
433
396
|
{
|
|
434
397
|
id: "skillReviewTrigger",
|
|
435
398
|
group: "review",
|
|
399
|
+
groupLabel: "审查",
|
|
400
|
+
groupLabelEn: "Review",
|
|
436
401
|
doc: "Which channel may inject a skill review (cadence, completion, both).",
|
|
437
402
|
label: "技能检查时机",
|
|
403
|
+
labelEn: "Skill review trigger",
|
|
438
404
|
hint: "什么时候检查技能:按间隔/按任务完成/两者。",
|
|
405
|
+
hintEn: "Which channel may inject a skill review (cadence, completion, both).",
|
|
439
406
|
control: "select",
|
|
440
407
|
unit: "",
|
|
408
|
+
unitEn: "",
|
|
441
409
|
values: [
|
|
442
410
|
"cadence",
|
|
443
411
|
"completion",
|
|
@@ -448,66 +416,101 @@ window.__ModuleLoader__.load({
|
|
|
448
416
|
"按任务完成",
|
|
449
417
|
"两者"
|
|
450
418
|
],
|
|
419
|
+
valueLabelsEn: [
|
|
420
|
+
"By interval",
|
|
421
|
+
"On completion",
|
|
422
|
+
"Both"
|
|
423
|
+
],
|
|
451
424
|
atLeast: ""
|
|
452
425
|
},
|
|
453
426
|
{
|
|
454
427
|
id: "skillReviewCompletionMinToolCalls",
|
|
455
428
|
group: "review",
|
|
429
|
+
groupLabel: "审查",
|
|
430
|
+
groupLabelEn: "Review",
|
|
456
431
|
doc: "Tool calls a task needs before the completion channel injects.",
|
|
457
432
|
label: "完成时的最少工具调用",
|
|
433
|
+
labelEn: "Minimum tool calls on completion",
|
|
458
434
|
hint: "选「按任务完成」时,任务至少用了几次工具才检查。",
|
|
435
|
+
hintEn: "Tool calls a task needs before the completion channel injects.",
|
|
459
436
|
control: "number",
|
|
460
437
|
unit: "次",
|
|
438
|
+
unitEn: "calls",
|
|
461
439
|
values: [],
|
|
462
440
|
valueLabels: [],
|
|
441
|
+
valueLabelsEn: [],
|
|
463
442
|
atLeast: ""
|
|
464
443
|
},
|
|
465
444
|
{
|
|
466
445
|
id: "reviewEnabled",
|
|
467
446
|
group: "review",
|
|
447
|
+
groupLabel: "审查",
|
|
448
|
+
groupLabelEn: "Review",
|
|
468
449
|
doc: "Master switch for the review plugin.",
|
|
469
450
|
label: "启用自动检查",
|
|
451
|
+
labelEn: "Enable automatic review",
|
|
470
452
|
hint: "关闭后不再自动回顾会话。",
|
|
453
|
+
hintEn: "Master switch for the review plugin.",
|
|
471
454
|
control: "switch",
|
|
472
455
|
unit: "",
|
|
456
|
+
unitEn: "",
|
|
473
457
|
values: [],
|
|
474
458
|
valueLabels: [],
|
|
459
|
+
valueLabelsEn: [],
|
|
475
460
|
atLeast: ""
|
|
476
461
|
},
|
|
477
462
|
{
|
|
478
463
|
id: "reviewMode",
|
|
479
464
|
group: "review",
|
|
465
|
+
groupLabel: "审查",
|
|
466
|
+
groupLabelEn: "Review",
|
|
480
467
|
doc: "Run the review in the parent session (inject), on a subagent, or in a background branch.",
|
|
481
468
|
label: "运行位置",
|
|
469
|
+
labelEn: "Review location",
|
|
482
470
|
hint: "在哪里运行检查:在当前会话里/交给子代理/后台分支。",
|
|
471
|
+
hintEn: "Run the review in the parent session (inject), on a subagent, or in a background branch.",
|
|
483
472
|
control: "select",
|
|
484
473
|
unit: "",
|
|
474
|
+
unitEn: "",
|
|
485
475
|
values: ["inject", "branch"],
|
|
486
476
|
valueLabels: ["在当前会话里", "后台分支"],
|
|
477
|
+
valueLabelsEn: ["In this session", "Background branch"],
|
|
487
478
|
atLeast: ""
|
|
488
479
|
},
|
|
489
480
|
{
|
|
490
481
|
id: "reviewWakeInject",
|
|
491
482
|
group: "review",
|
|
483
|
+
groupLabel: "审查",
|
|
484
|
+
groupLabelEn: "Review",
|
|
492
485
|
doc: "Deliver the deferred review as a waking follow-up message.",
|
|
493
486
|
label: "延后结果发提醒",
|
|
487
|
+
labelEn: "Notify on deferred result",
|
|
494
488
|
hint: "会话空闲时,把延后的检查结果作为一条提醒发给你。",
|
|
489
|
+
hintEn: "Deliver the deferred review as a waking follow-up message.",
|
|
495
490
|
control: "switch",
|
|
496
491
|
unit: "",
|
|
492
|
+
unitEn: "",
|
|
497
493
|
values: [],
|
|
498
494
|
valueLabels: [],
|
|
495
|
+
valueLabelsEn: [],
|
|
499
496
|
atLeast: ""
|
|
500
497
|
},
|
|
501
498
|
{
|
|
502
499
|
id: "reviewNotice",
|
|
503
500
|
group: "review",
|
|
501
|
+
groupLabel: "审查",
|
|
502
|
+
groupLabelEn: "Review",
|
|
504
503
|
doc: "Deliver result notices into the parent conversation.",
|
|
505
504
|
label: "结果通知",
|
|
505
|
+
labelEn: "Result notice",
|
|
506
506
|
hint: "关闭后检查结果不再写进对话;检查本身照常运行,结果留在审计记录里。",
|
|
507
|
+
hintEn: "Deliver result notices into the parent conversation.",
|
|
507
508
|
control: "switch",
|
|
508
509
|
unit: "",
|
|
510
|
+
unitEn: "",
|
|
509
511
|
values: [],
|
|
510
512
|
valueLabels: [],
|
|
513
|
+
valueLabelsEn: [],
|
|
511
514
|
atLeast: ""
|
|
512
515
|
}
|
|
513
516
|
]
|
|
@@ -519,49 +522,73 @@ window.__ModuleLoader__.load({
|
|
|
519
522
|
{
|
|
520
523
|
id: "memoryChars",
|
|
521
524
|
group: "memory",
|
|
525
|
+
groupLabel: "记忆",
|
|
526
|
+
groupLabelEn: "Memory",
|
|
522
527
|
doc: "Character budget the memory store enforces for MEMORY.md.",
|
|
523
528
|
label: "记忆库上限",
|
|
529
|
+
labelEn: "Memory store limit",
|
|
524
530
|
hint: "长期记忆文件(MEMORY.md)最多多少字符。",
|
|
531
|
+
hintEn: "Character budget the memory store enforces for MEMORY.md.",
|
|
525
532
|
control: "number",
|
|
526
533
|
unit: "字符",
|
|
534
|
+
unitEn: "chars",
|
|
527
535
|
values: [],
|
|
528
536
|
valueLabels: [],
|
|
537
|
+
valueLabelsEn: [],
|
|
529
538
|
atLeast: ""
|
|
530
539
|
},
|
|
531
540
|
{
|
|
532
541
|
id: "userChars",
|
|
533
542
|
group: "memory",
|
|
543
|
+
groupLabel: "记忆",
|
|
544
|
+
groupLabelEn: "Memory",
|
|
534
545
|
doc: "Character budget the memory store enforces for USER.md.",
|
|
535
546
|
label: "用户画像上限",
|
|
547
|
+
labelEn: "User profile limit",
|
|
536
548
|
hint: "用户画像文件(USER.md)最多多少字符。",
|
|
549
|
+
hintEn: "Character budget the memory store enforces for USER.md.",
|
|
537
550
|
control: "number",
|
|
538
551
|
unit: "字符",
|
|
552
|
+
unitEn: "chars",
|
|
539
553
|
values: [],
|
|
540
554
|
valueLabels: [],
|
|
555
|
+
valueLabelsEn: [],
|
|
541
556
|
atLeast: ""
|
|
542
557
|
},
|
|
543
558
|
{
|
|
544
559
|
id: "addDatePrefix",
|
|
545
560
|
group: "memory",
|
|
561
|
+
groupLabel: "记忆",
|
|
562
|
+
groupLabelEn: "Memory",
|
|
546
563
|
doc: "Prefix stored memory entries with their date heading.",
|
|
547
564
|
label: "记忆条目加日期",
|
|
565
|
+
labelEn: "Prefix memory entries with date",
|
|
548
566
|
hint: "写入时在条目开头加上日期。",
|
|
567
|
+
hintEn: "Prefix stored memory entries with their date heading.",
|
|
549
568
|
control: "switch",
|
|
550
569
|
unit: "",
|
|
570
|
+
unitEn: "",
|
|
551
571
|
values: [],
|
|
552
572
|
valueLabels: [],
|
|
573
|
+
valueLabelsEn: [],
|
|
553
574
|
atLeast: ""
|
|
554
575
|
},
|
|
555
576
|
{
|
|
556
577
|
id: "maxConsolidationFailures",
|
|
557
578
|
group: "memory",
|
|
579
|
+
groupLabel: "记忆",
|
|
580
|
+
groupLabelEn: "Memory",
|
|
558
581
|
doc: "Consolidation failures one turn tolerates before the tool gives up.",
|
|
559
582
|
label: "合并失败重试",
|
|
583
|
+
labelEn: "Consolidation retries",
|
|
560
584
|
hint: "合并记忆连续失败多少次后停止。",
|
|
585
|
+
hintEn: "Consolidation failures one turn tolerates before the tool gives up.",
|
|
561
586
|
control: "number",
|
|
562
587
|
unit: "次",
|
|
588
|
+
unitEn: "calls",
|
|
563
589
|
values: [],
|
|
564
590
|
valueLabels: [],
|
|
591
|
+
valueLabelsEn: [],
|
|
565
592
|
atLeast: ""
|
|
566
593
|
}
|
|
567
594
|
]
|
|
@@ -572,13 +599,19 @@ window.__ModuleLoader__.load({
|
|
|
572
599
|
fields: [{
|
|
573
600
|
id: "entryPreviewChars",
|
|
574
601
|
group: "memory",
|
|
602
|
+
groupLabel: "记忆",
|
|
603
|
+
groupLabelEn: "Memory",
|
|
575
604
|
doc: "Characters of one memory entry shown in a tool result preview.",
|
|
576
605
|
label: "记忆预览长度",
|
|
606
|
+
labelEn: "Memory preview length",
|
|
577
607
|
hint: "列表里每条记忆最多显示多少字符。",
|
|
608
|
+
hintEn: "Characters of one memory entry shown in a tool result preview.",
|
|
578
609
|
control: "number",
|
|
579
610
|
unit: "字符",
|
|
611
|
+
unitEn: "chars",
|
|
580
612
|
values: [],
|
|
581
613
|
valueLabels: [],
|
|
614
|
+
valueLabelsEn: [],
|
|
582
615
|
atLeast: ""
|
|
583
616
|
}]
|
|
584
617
|
},
|
|
@@ -589,107 +622,160 @@ window.__ModuleLoader__.load({
|
|
|
589
622
|
{
|
|
590
623
|
id: "skillContentChars",
|
|
591
624
|
group: "write-caps",
|
|
625
|
+
groupLabel: "写入上限",
|
|
626
|
+
groupLabelEn: "Write limits",
|
|
592
627
|
doc: "Character cap on a SKILL.md body (tighten-only).",
|
|
593
628
|
label: "技能文件正文上限",
|
|
629
|
+
labelEn: "Skill body limit",
|
|
594
630
|
hint: "技能 Markdown 正文最多多少字符(只能调小)。",
|
|
631
|
+
hintEn: "Character cap on a SKILL.md body (tighten-only).",
|
|
595
632
|
control: "number",
|
|
596
633
|
unit: "字符",
|
|
634
|
+
unitEn: "chars",
|
|
597
635
|
values: [],
|
|
598
636
|
valueLabels: [],
|
|
637
|
+
valueLabelsEn: [],
|
|
599
638
|
atLeast: ""
|
|
600
639
|
},
|
|
601
640
|
{
|
|
602
641
|
id: "maxSkillFileBytes",
|
|
603
642
|
group: "write-caps",
|
|
643
|
+
groupLabel: "写入上限",
|
|
644
|
+
groupLabelEn: "Write limits",
|
|
604
645
|
doc: "Byte cap on one support file (tighten-only).",
|
|
605
646
|
label: "附带文件大小上限",
|
|
647
|
+
labelEn: "Support file size limit",
|
|
606
648
|
hint: "技能附带文件单个最大多少字节(只能调小)。",
|
|
649
|
+
hintEn: "Byte cap on one support file (tighten-only).",
|
|
607
650
|
control: "number",
|
|
608
651
|
unit: "字节",
|
|
652
|
+
unitEn: "bytes",
|
|
609
653
|
values: [],
|
|
610
654
|
valueLabels: [],
|
|
655
|
+
valueLabelsEn: [],
|
|
611
656
|
atLeast: ""
|
|
612
657
|
},
|
|
613
658
|
{
|
|
614
659
|
id: "maxSkillNameLength",
|
|
615
660
|
group: "write-caps",
|
|
661
|
+
groupLabel: "写入上限",
|
|
662
|
+
groupLabelEn: "Write limits",
|
|
616
663
|
doc: "Character cap on a skill name (tighten-only).",
|
|
617
664
|
label: "技能名长度上限",
|
|
665
|
+
labelEn: "Skill name length limit",
|
|
618
666
|
hint: "技能名最多多少字符(只能调小)。",
|
|
667
|
+
hintEn: "Character cap on a skill name (tighten-only).",
|
|
619
668
|
control: "number",
|
|
620
669
|
unit: "字符",
|
|
670
|
+
unitEn: "chars",
|
|
621
671
|
values: [],
|
|
622
672
|
valueLabels: [],
|
|
673
|
+
valueLabelsEn: [],
|
|
623
674
|
atLeast: ""
|
|
624
675
|
},
|
|
625
676
|
{
|
|
626
677
|
id: "maxDescriptionLength",
|
|
627
678
|
group: "write-caps",
|
|
679
|
+
groupLabel: "写入上限",
|
|
680
|
+
groupLabelEn: "Write limits",
|
|
628
681
|
doc: "Character cap on a skill description (tighten-only).",
|
|
629
682
|
label: "技能描述长度上限",
|
|
683
|
+
labelEn: "Description length limit",
|
|
630
684
|
hint: "技能描述最多多少字符(只能调小)。",
|
|
685
|
+
hintEn: "Character cap on a skill description (tighten-only).",
|
|
631
686
|
control: "number",
|
|
632
687
|
unit: "字符",
|
|
688
|
+
unitEn: "chars",
|
|
633
689
|
values: [],
|
|
634
690
|
valueLabels: [],
|
|
691
|
+
valueLabelsEn: [],
|
|
635
692
|
atLeast: ""
|
|
636
693
|
},
|
|
637
694
|
{
|
|
638
695
|
id: "descriptionStrict",
|
|
639
696
|
group: "write-caps",
|
|
697
|
+
groupLabel: "写入上限",
|
|
698
|
+
groupLabelEn: "Write limits",
|
|
640
699
|
doc: "Refuse a description over the authoring bar instead of advising.",
|
|
641
700
|
label: "描述不合规即拒绝",
|
|
701
|
+
labelEn: "Reject invalid descriptions",
|
|
642
702
|
hint: "描述不符合要求时直接拒绝写入(默认只提醒)。",
|
|
703
|
+
hintEn: "Refuse a description over the authoring bar instead of advising.",
|
|
643
704
|
control: "switch",
|
|
644
705
|
unit: "",
|
|
706
|
+
unitEn: "",
|
|
645
707
|
values: [],
|
|
646
708
|
valueLabels: [],
|
|
709
|
+
valueLabelsEn: [],
|
|
647
710
|
atLeast: ""
|
|
648
711
|
},
|
|
649
712
|
{
|
|
650
713
|
id: "strictCrossSource",
|
|
651
714
|
group: "write-caps",
|
|
715
|
+
groupLabel: "写入上限",
|
|
716
|
+
groupLabelEn: "Write limits",
|
|
652
717
|
doc: "Refuse writes whose catalog entry resolves outside the family.",
|
|
653
718
|
label: "引用外部内容即拒绝",
|
|
719
|
+
labelEn: "Reject external references",
|
|
654
720
|
hint: "技能引用了本插件之外的文件时拒绝写入。",
|
|
721
|
+
hintEn: "Refuse writes whose catalog entry resolves outside the family.",
|
|
655
722
|
control: "switch",
|
|
656
723
|
unit: "",
|
|
724
|
+
unitEn: "",
|
|
657
725
|
values: [],
|
|
658
726
|
valueLabels: [],
|
|
727
|
+
valueLabelsEn: [],
|
|
659
728
|
atLeast: ""
|
|
660
729
|
},
|
|
661
730
|
{
|
|
662
731
|
id: "citationPolicy",
|
|
663
732
|
group: "write-caps",
|
|
733
|
+
groupLabel: "写入上限",
|
|
734
|
+
groupLabelEn: "Write limits",
|
|
664
735
|
doc: "Refuse a move that would leave a dangling reference, or verify it.",
|
|
665
736
|
label: "引用检查方式",
|
|
737
|
+
labelEn: "Citation check",
|
|
666
738
|
hint: "移动或合并技能时怎么处理引用:先检查/直接拒绝。",
|
|
739
|
+
hintEn: "Refuse a move that would leave a dangling reference, or verify it.",
|
|
667
740
|
control: "select",
|
|
668
741
|
unit: "",
|
|
742
|
+
unitEn: "",
|
|
669
743
|
values: ["verify", "refuse"],
|
|
670
744
|
valueLabels: ["先检查", "直接拒绝"],
|
|
745
|
+
valueLabelsEn: ["Verify first", "Refuse"],
|
|
671
746
|
atLeast: ""
|
|
672
747
|
},
|
|
673
748
|
{
|
|
674
749
|
id: "supportFileCharPolicy",
|
|
675
750
|
group: "write-caps",
|
|
751
|
+
groupLabel: "写入上限",
|
|
752
|
+
groupLabelEn: "Write limits",
|
|
676
753
|
doc: "Warn about an oversize support file, or refuse the write.",
|
|
677
754
|
label: "附带文件超限时",
|
|
755
|
+
labelEn: "Oversize support file",
|
|
678
756
|
hint: "附带文件超出上限时:只提醒/拒绝写入。",
|
|
757
|
+
hintEn: "Warn about an oversize support file, or refuse the write.",
|
|
679
758
|
control: "select",
|
|
680
759
|
unit: "",
|
|
760
|
+
unitEn: "",
|
|
681
761
|
values: ["report", "enforce"],
|
|
682
762
|
valueLabels: ["只提醒", "拒绝写入"],
|
|
763
|
+
valueLabelsEn: ["Warn only", "Refuse write"],
|
|
683
764
|
atLeast: ""
|
|
684
765
|
},
|
|
685
766
|
{
|
|
686
767
|
id: "skillWriteConfirm",
|
|
687
768
|
group: "write-caps",
|
|
769
|
+
groupLabel: "写入上限",
|
|
770
|
+
groupLabelEn: "Write limits",
|
|
688
771
|
doc: "What the one confirmation before a create or a bare delete does: write straight through, wait for an answer, or cancel the write when nobody answers within the timeout.",
|
|
689
772
|
label: "写入前是否确认",
|
|
773
|
+
labelEn: "Confirm before writing",
|
|
690
774
|
hint: "新建或删除技能前要不要问你:直接写入/一直等你回答/超时未答就取消这次写入。",
|
|
775
|
+
hintEn: "What the one confirmation before a create or a bare delete does: write straight through, wait for an answer, or cancel the write when nobody answers within the timeout.",
|
|
691
776
|
control: "select",
|
|
692
777
|
unit: "",
|
|
778
|
+
unitEn: "",
|
|
693
779
|
values: [
|
|
694
780
|
"auto",
|
|
695
781
|
"ask",
|
|
@@ -700,63 +786,34 @@ window.__ModuleLoader__.load({
|
|
|
700
786
|
"一直等待回答",
|
|
701
787
|
"超时自动取消"
|
|
702
788
|
],
|
|
789
|
+
valueLabelsEn: [
|
|
790
|
+
"Write directly",
|
|
791
|
+
"Wait for answer",
|
|
792
|
+
"Cancel on timeout"
|
|
793
|
+
],
|
|
703
794
|
atLeast: ""
|
|
704
795
|
},
|
|
705
796
|
{
|
|
706
797
|
id: "skillWriteConfirmTimeoutSeconds",
|
|
707
798
|
group: "write-caps",
|
|
799
|
+
groupLabel: "写入上限",
|
|
800
|
+
groupLabelEn: "Write limits",
|
|
708
801
|
doc: "Seconds the confirmation waits for an answer in timeout mode before the write is cancelled.",
|
|
709
802
|
label: "确认等待秒数",
|
|
803
|
+
labelEn: "Confirmation timeout",
|
|
710
804
|
hint: "选「超时自动取消」时,等多少秒没人回答就取消这次写入。",
|
|
805
|
+
hintEn: "Seconds the confirmation waits for an answer in timeout mode before the write is cancelled.",
|
|
711
806
|
control: "number",
|
|
712
807
|
unit: "秒",
|
|
808
|
+
unitEn: "s",
|
|
713
809
|
values: [],
|
|
714
810
|
valueLabels: [],
|
|
811
|
+
valueLabelsEn: [],
|
|
715
812
|
atLeast: ""
|
|
716
813
|
}
|
|
717
814
|
]
|
|
718
815
|
}
|
|
719
816
|
];
|
|
720
|
-
/** Every (bundle, row) pair the Plugins page can open a configuration page for. */
|
|
721
|
-
const CLIENT_ROW_SEATS = [
|
|
722
|
-
{
|
|
723
|
-
bundle: "@lmzhen/dsh-evolution-all",
|
|
724
|
-
rows: [
|
|
725
|
-
"evolution-approval",
|
|
726
|
-
"evolution-commands",
|
|
727
|
-
"evolution-curator",
|
|
728
|
-
"evolution-merge",
|
|
729
|
-
"evolution-review",
|
|
730
|
-
"memory-files",
|
|
731
|
-
"tool-memory",
|
|
732
|
-
"tool-skill-manage"
|
|
733
|
-
]
|
|
734
|
-
},
|
|
735
|
-
{
|
|
736
|
-
bundle: "@lmzhen/dsh-evolution-host",
|
|
737
|
-
rows: [
|
|
738
|
-
"evolution-approval",
|
|
739
|
-
"evolution-commands",
|
|
740
|
-
"evolution-curator",
|
|
741
|
-
"evolution-merge",
|
|
742
|
-
"evolution-review",
|
|
743
|
-
"memory-files"
|
|
744
|
-
]
|
|
745
|
-
},
|
|
746
|
-
{
|
|
747
|
-
bundle: "@lmzhen/dsh-evolution-preset",
|
|
748
|
-
rows: [
|
|
749
|
-
"evolution-approval",
|
|
750
|
-
"evolution-commands",
|
|
751
|
-
"evolution-curator",
|
|
752
|
-
"evolution-merge",
|
|
753
|
-
"evolution-review",
|
|
754
|
-
"memory-files",
|
|
755
|
-
"tool-memory",
|
|
756
|
-
"tool-skill-manage"
|
|
757
|
-
]
|
|
758
|
-
}
|
|
759
|
-
];
|
|
760
817
|
//#endregion
|
|
761
818
|
//#region ../evolution-client-kit/src/generated/states.generated.ts
|
|
762
819
|
/** A neutral/ warn/ fail block; a fail block is role=alert so assistive tech announces it. */
|
|
@@ -787,521 +844,295 @@ window.__ModuleLoader__.load({
|
|
|
787
844
|
});
|
|
788
845
|
}
|
|
789
846
|
//#endregion
|
|
790
|
-
//#region src/panels/settings/
|
|
791
|
-
/** The state a card mounts in: still loading, nothing in flight, nothing said yet. */
|
|
792
|
-
const INITIAL_PENDING_STATE = {
|
|
793
|
-
view: { kind: "loading" },
|
|
794
|
-
busy: [],
|
|
795
|
-
notice: null,
|
|
796
|
-
open: null,
|
|
797
|
-
preview: null
|
|
798
|
-
};
|
|
799
|
-
/** Open one row's preview: the row is now the open one and its answer is on the way. */
|
|
800
|
-
function expanded(state, id) {
|
|
801
|
-
return {
|
|
802
|
-
...state,
|
|
803
|
-
open: id,
|
|
804
|
-
preview: { kind: "loading" }
|
|
805
|
-
};
|
|
806
|
-
}
|
|
807
|
-
/** Close whatever preview is open. */
|
|
808
|
-
function collapsed(state) {
|
|
809
|
-
return {
|
|
810
|
-
...state,
|
|
811
|
-
open: null,
|
|
812
|
-
preview: null
|
|
813
|
-
};
|
|
814
|
-
}
|
|
847
|
+
//#region src/panels/settings/DiagnosticsCard.ts
|
|
815
848
|
/**
|
|
816
|
-
*
|
|
817
|
-
*
|
|
849
|
+
* The diagnostics card (W7 S7): the settings face of the SAME DiagnosticsView the CLI and the
|
|
850
|
+
* `/api/dsh-evolution/diagnostics` route answer.
|
|
851
|
+
*
|
|
852
|
+
* Read-only by construction: it fetches once on mount and only refetches on an explicit Refresh; a
|
|
853
|
+
* failed read renders the shared FailureBlock with the command-face fallback. The client owns no
|
|
854
|
+
* key list and no status vocabulary: it renders the host view's frozen runtime rows and leads with
|
|
855
|
+
* a plain-language summary. The view's documentation facts are a CLI/JSON concern and are
|
|
856
|
+
* deliberately NOT rendered here (0.27.2 real-machine feedback: a settings page is not a spec
|
|
857
|
+
* sheet; the runtime window is the whole reading).
|
|
858
|
+
* @module @lmzhen/dsh-evolution-ui/panels/settings/client/DiagnosticsCard
|
|
818
859
|
*/
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
860
|
+
/** Which word one status gets (the frozen W1 status set). */
|
|
861
|
+
const STATUS_KEYS = Object.freeze({
|
|
862
|
+
ok: "diagStatusOk",
|
|
863
|
+
warn: "diagStatusWarn",
|
|
864
|
+
fail: "diagStatusFail",
|
|
865
|
+
unknown: "diagStatusUnknown"
|
|
866
|
+
});
|
|
867
|
+
/** The summary word for a badge status (the same frozen set; ok has no count). */
|
|
868
|
+
const SUMMARY_KEYS = Object.freeze({
|
|
869
|
+
ok: "diagSummaryOk",
|
|
870
|
+
warn: "diagSummaryWarn",
|
|
871
|
+
fail: "diagSummaryFail",
|
|
872
|
+
unknown: "diagSummaryUnknown"
|
|
873
|
+
});
|
|
874
|
+
/** The one status the card leads with: fail > warn > unknown > ok. */
|
|
875
|
+
function summaryOf(rows) {
|
|
876
|
+
const count = (status) => rows.filter((row) => row.status === status).length;
|
|
877
|
+
for (const status of [
|
|
878
|
+
"fail",
|
|
879
|
+
"warn",
|
|
880
|
+
"unknown"
|
|
881
|
+
]) {
|
|
882
|
+
const n = count(status);
|
|
883
|
+
if (n > 0) return {
|
|
884
|
+
status,
|
|
885
|
+
count: n
|
|
886
|
+
};
|
|
887
|
+
}
|
|
827
888
|
return {
|
|
828
|
-
|
|
829
|
-
|
|
889
|
+
status: "ok",
|
|
890
|
+
count: 0
|
|
830
891
|
};
|
|
831
892
|
}
|
|
832
893
|
/**
|
|
833
|
-
*
|
|
834
|
-
*
|
|
894
|
+
* Render the card.
|
|
895
|
+
* @param props - the copy seat, the host call and the command fallback.
|
|
896
|
+
* @returns the card.
|
|
835
897
|
*/
|
|
836
|
-
function
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
898
|
+
function DiagnosticsCard(props) {
|
|
899
|
+
const { t, api } = props;
|
|
900
|
+
const translate = (key, values) => t(key, values);
|
|
901
|
+
const [state, setState] = (0, react.useState)({ phase: "loading" });
|
|
902
|
+
const [refreshing, setRefreshing] = (0, react.useState)(false);
|
|
903
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
904
|
+
const ticket = (0, react.useRef)(0);
|
|
905
|
+
const read = (keepView) => {
|
|
906
|
+
const current = ticket.current + 1;
|
|
907
|
+
ticket.current = current;
|
|
908
|
+
if (keepView) setRefreshing(true);
|
|
909
|
+
else setState({ phase: "loading" });
|
|
910
|
+
api.view().then((answer) => {
|
|
911
|
+
if (ticket.current !== current) return;
|
|
912
|
+
setState(answer.ok ? {
|
|
913
|
+
phase: "ready",
|
|
914
|
+
view: answer.data
|
|
915
|
+
} : {
|
|
916
|
+
phase: "failed",
|
|
917
|
+
failure: answer.failure
|
|
918
|
+
});
|
|
919
|
+
}).finally(() => {
|
|
920
|
+
if (ticket.current === current) setRefreshing(false);
|
|
921
|
+
});
|
|
843
922
|
};
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
923
|
+
(0, react.useEffect)(() => {
|
|
924
|
+
read(false);
|
|
925
|
+
}, []);
|
|
926
|
+
const refresh = () => {
|
|
927
|
+
read(true);
|
|
928
|
+
};
|
|
929
|
+
const summary = state.phase === "ready" ? summaryOf(state.view.runtime) : void 0;
|
|
930
|
+
const header = (0, react.createElement)("button", {
|
|
931
|
+
type: "button",
|
|
932
|
+
className: "evolution-param-head",
|
|
933
|
+
"aria-expanded": open ? "true" : "false",
|
|
934
|
+
onClick: () => {
|
|
935
|
+
setOpen(!open);
|
|
852
936
|
}
|
|
937
|
+
}, (0, react.createElement)("span", { className: "evolution-param-card-title" }, t("diagTitle")), summary === void 0 ? null : (0, react.createElement)("span", {
|
|
938
|
+
className: "evo-diag-badge",
|
|
939
|
+
"data-status": summary.status
|
|
940
|
+
}, t(SUMMARY_KEYS[summary.status], summary.count === 0 ? void 0 : { n: summary.count })), (0, react.createElement)("span", { className: "evolution-param-chevron" }, open ? "▲" : "▼"));
|
|
941
|
+
const runtime = (view) => view.runtime.length === 0 ? null : (0, react.createElement)("div", {}, (0, react.createElement)("h3", { className: "evolution-param-group" }, t("diagRuntimeTitle")), (0, react.createElement)("div", { className: "evo-diag-list" }, ...view.runtime.map((row) => (0, react.createElement)("div", {
|
|
942
|
+
key: "runtime-" + row.key,
|
|
943
|
+
className: "evo-diag-item",
|
|
944
|
+
"data-kind": "runtime",
|
|
945
|
+
"data-status": row.status
|
|
946
|
+
}, (0, react.createElement)("div", { className: "evo-diag-row-head" }, (0, react.createElement)("span", { className: "evo-diag-dot" }), (0, react.createElement)("span", { className: "evo-diag-label" }, row.label), (0, react.createElement)("span", { className: "evo-diag-status" }, t(STATUS_KEYS[row.status]))), row.detail === void 0 ? null : (0, react.createElement)("span", { className: "evo-diag-detail" }, row.detail), row.remedy === void 0 ? null : (0, react.createElement)("span", { className: "evo-diag-remedy" }, t("diagRemedy") + row.remedy)))));
|
|
947
|
+
const body = () => {
|
|
948
|
+
if (state.phase === "loading") return (0, react.createElement)("p", { className: "evolution-param-loading" }, t("stateLoading"));
|
|
949
|
+
if (state.phase === "failed") return FailureBlock({
|
|
950
|
+
failure: state.failure,
|
|
951
|
+
t: translate,
|
|
952
|
+
fallback: props.fallback
|
|
953
|
+
});
|
|
954
|
+
const view = state.view;
|
|
955
|
+
return (0, react.createElement)("div", {}, (0, react.createElement)("p", { className: "evolution-param-note" }, t("diagSubtitle")), (0, react.createElement)("div", { className: "evolution-param-actions" }, (0, react.createElement)("button", {
|
|
956
|
+
type: "button",
|
|
957
|
+
className: "evolution-param-button",
|
|
958
|
+
onClick: refresh,
|
|
959
|
+
disabled: refreshing,
|
|
960
|
+
"data-action": "diag-refresh"
|
|
961
|
+
}, t(refreshing ? "diagRefreshing" : "diagRefresh"))), runtime(view), view.runtime.length === 0 ? (0, react.createElement)("p", { className: "evolution-param-note" }, t("diagNoRows")) : null);
|
|
853
962
|
};
|
|
963
|
+
return (0, react.createElement)("section", {
|
|
964
|
+
className: "evolution-param-card",
|
|
965
|
+
"data-card": "diagnostics"
|
|
966
|
+
}, header, open ? (0, react.createElement)("div", { className: "evolution-param-body" }, body()) : null);
|
|
854
967
|
}
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
968
|
+
//#endregion
|
|
969
|
+
//#region ../evolution-client-kit/src/generated/routes.generated.ts
|
|
970
|
+
/**
|
|
971
|
+
* Every family host path, generated from evolution-core's HOST_ROUTES (the ONE declaration).
|
|
972
|
+
* A browser half cannot import the host package at runtime (client bundles keep @deepseek-ai/*
|
|
973
|
+
* external), so the table is mirrored here mechanically; a rename in core regenerates all three
|
|
974
|
+
* client copies, and the generator's --check makes a stale copy red.
|
|
975
|
+
*/
|
|
976
|
+
const ROUTES = {
|
|
977
|
+
approval: {
|
|
978
|
+
pending: "/api/dsh-evolution/approval/pending",
|
|
979
|
+
preview: "/api/dsh-evolution/approval/preview",
|
|
980
|
+
approve: "/api/dsh-evolution/approval/approve",
|
|
981
|
+
reject: "/api/dsh-evolution/approval/reject",
|
|
982
|
+
release: "/api/dsh-evolution/approval/release"
|
|
983
|
+
},
|
|
984
|
+
"skill-history": {
|
|
985
|
+
skills: "/api/dsh-evolution/skill-history/skills",
|
|
986
|
+
versions: "/api/dsh-evolution/skill-history/versions",
|
|
987
|
+
diff: "/api/dsh-evolution/skill-history/versions/diff",
|
|
988
|
+
body: "/api/dsh-evolution/skill-history/versions/body",
|
|
989
|
+
undo: "/api/dsh-evolution/skill-history/undo",
|
|
990
|
+
health: "/api/dsh-evolution/skill-history/health"
|
|
991
|
+
},
|
|
992
|
+
"skill-health": { rows: "/api/dsh-evolution/skill-health/rows" },
|
|
993
|
+
merge: {
|
|
994
|
+
view: "/api/dsh-evolution/merge/view",
|
|
995
|
+
rows: "/api/dsh-evolution/merge/rows",
|
|
996
|
+
run: "/api/dsh-evolution/merge/run",
|
|
997
|
+
runs: "/api/dsh-evolution/merge/runs",
|
|
998
|
+
body: "/api/dsh-evolution/merge/body",
|
|
999
|
+
history: "/api/dsh-evolution/merge/history"
|
|
1000
|
+
},
|
|
1001
|
+
diagnostics: { view: "/api/dsh-evolution/diagnostics" },
|
|
1002
|
+
settings: {
|
|
1003
|
+
describe: "/api/dsh-evolution/settings/describe",
|
|
1004
|
+
mutate: "/api/dsh-evolution/settings/mutate"
|
|
1005
|
+
}
|
|
1006
|
+
};
|
|
1007
|
+
//#endregion
|
|
1008
|
+
//#region ../evolution-client-kit/src/generated/route-client.generated.ts
|
|
1009
|
+
/** Classify a non-2xx status: 401/403/404 keep their own kind, everything else is a server answer. */
|
|
1010
|
+
function classify(status) {
|
|
1011
|
+
if (status === 401) return {
|
|
1012
|
+
kind: "unauthenticated",
|
|
1013
|
+
status: 401
|
|
1014
|
+
};
|
|
1015
|
+
if (status === 403) return {
|
|
1016
|
+
kind: "forbidden",
|
|
1017
|
+
status: 403
|
|
1018
|
+
};
|
|
1019
|
+
if (status === 404) return {
|
|
1020
|
+
kind: "unmounted",
|
|
1021
|
+
status: 404
|
|
860
1022
|
};
|
|
861
|
-
}
|
|
862
|
-
/** The action ended, whatever it answered. */
|
|
863
|
-
function endAction(state, id) {
|
|
864
1023
|
return {
|
|
865
|
-
|
|
866
|
-
|
|
1024
|
+
kind: "server",
|
|
1025
|
+
status
|
|
867
1026
|
};
|
|
868
1027
|
}
|
|
869
|
-
/**
|
|
870
|
-
|
|
1028
|
+
/**
|
|
1029
|
+
* One request, no retries and no body parsing for the platform's admission answers.
|
|
1030
|
+
* @param doFetch - the fetch in use (the browser's, or a spec's stub).
|
|
1031
|
+
* @param path - the exact route path from the generated ROUTES table.
|
|
1032
|
+
* @param init - standard RequestInit.
|
|
1033
|
+
* @returns the data, or the classified failure (network/parse failures read as 'server').
|
|
1034
|
+
*/
|
|
1035
|
+
async function requestJson(doFetch, path, init) {
|
|
1036
|
+
let response;
|
|
1037
|
+
try {
|
|
1038
|
+
response = await doFetch(path, init);
|
|
1039
|
+
} catch (error) {
|
|
1040
|
+
return {
|
|
1041
|
+
ok: false,
|
|
1042
|
+
failure: {
|
|
1043
|
+
kind: "server",
|
|
1044
|
+
status: 0,
|
|
1045
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
1046
|
+
}
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
if (!response.ok) return {
|
|
1050
|
+
ok: false,
|
|
1051
|
+
failure: classify(response.status)
|
|
1052
|
+
};
|
|
1053
|
+
let body;
|
|
1054
|
+
try {
|
|
1055
|
+
body = await response.json();
|
|
1056
|
+
} catch {
|
|
1057
|
+
return {
|
|
1058
|
+
ok: false,
|
|
1059
|
+
failure: {
|
|
1060
|
+
kind: "server",
|
|
1061
|
+
status: response.status
|
|
1062
|
+
}
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
if (typeof body === "object" && body !== null) {
|
|
1066
|
+
const record = body;
|
|
1067
|
+
if (record.ok === true) return {
|
|
1068
|
+
ok: true,
|
|
1069
|
+
data: record.data
|
|
1070
|
+
};
|
|
1071
|
+
const nested = record.error;
|
|
1072
|
+
const errorRecord = typeof nested === "object" && nested !== null ? nested : void 0;
|
|
1073
|
+
const code = typeof record.code === "string" && record.code !== "" ? record.code : typeof errorRecord?.code === "string" ? errorRecord.code : void 0;
|
|
1074
|
+
const message = typeof record.message === "string" && record.message !== "" ? record.message : typeof errorRecord?.message === "string" ? errorRecord.message : void 0;
|
|
1075
|
+
if (record.ok === false && code !== void 0 && message !== void 0 && message !== "") return {
|
|
1076
|
+
ok: false,
|
|
1077
|
+
failure: {
|
|
1078
|
+
kind: "refused",
|
|
1079
|
+
code,
|
|
1080
|
+
message
|
|
1081
|
+
}
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
871
1084
|
return {
|
|
872
|
-
|
|
873
|
-
|
|
1085
|
+
ok: false,
|
|
1086
|
+
failure: {
|
|
1087
|
+
kind: "server",
|
|
1088
|
+
status: response.status
|
|
1089
|
+
}
|
|
874
1090
|
};
|
|
875
1091
|
}
|
|
1092
|
+
/** The one-sentence rendering legacy callers attach to a classified failure. */
|
|
1093
|
+
function failureMessage(failure) {
|
|
1094
|
+
switch (failure.kind) {
|
|
1095
|
+
case "refused": return failure.message;
|
|
1096
|
+
case "server": return failure.status === 0 ? failure.detail === void 0 || failure.detail === "" ? "网络错误:无法连接宿主" : failure.detail : "HTTP " + String(failure.status);
|
|
1097
|
+
case "unauthenticated": return "HTTP 401";
|
|
1098
|
+
case "forbidden": return "HTTP 403";
|
|
1099
|
+
case "unmounted": return "HTTP 404";
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
876
1102
|
//#endregion
|
|
877
|
-
//#region src/panels/settings/
|
|
1103
|
+
//#region src/panels/settings/diagnostics-state.ts
|
|
878
1104
|
/**
|
|
879
|
-
* The
|
|
880
|
-
*
|
|
881
|
-
* It is a card in the SAME section and wears the SAME shell as the parameter cards (header with title,
|
|
882
|
-
* count and chevron; body of field blocks; a button row per block), so nothing here invents a second
|
|
883
|
-
* visual language. Two things differ on purpose: the data comes from the host's approval routes rather
|
|
884
|
-
* than the settings transport, and its three states are distinct — "reading", "nothing staged" and "the
|
|
885
|
-
* read failed" each say so in their own words (E7: merging the first two is what made an empty panel
|
|
886
|
-
* look like lost data).
|
|
1105
|
+
* The diagnostics card's data face (W7 S7): ONE request to `HOST_ROUTES.diagnostics.view` through the
|
|
1106
|
+
* GENERATED route client, and the four-state answer the card renders.
|
|
887
1107
|
*
|
|
888
|
-
*
|
|
889
|
-
*
|
|
890
|
-
* @module @lmzhen/dsh-evolution-ui/panels/settings/client/
|
|
1108
|
+
* The Host route is the SAME `buildDoctorView` the CLI prints (W6/W8), so this client owns no facts
|
|
1109
|
+
* and no computation — it only renders.
|
|
1110
|
+
* @module @lmzhen/dsh-evolution-ui/panels/settings/client/diagnostics-state
|
|
891
1111
|
*/
|
|
892
1112
|
/**
|
|
893
|
-
*
|
|
894
|
-
*
|
|
895
|
-
* one of the two explicit words falls back to the shipped default (`auto`) — a document written by a
|
|
896
|
-
* future version must not silently turn the confirmation ON, and a typo must not turn it off. The
|
|
897
|
-
* independent review flagged that this reader had no case at all (P2-11).
|
|
1113
|
+
* Build the diagnostics face over one fetch implementation.
|
|
1114
|
+
* @param doFetch - the browser fetch, or a spec stub.
|
|
898
1115
|
*/
|
|
899
|
-
function
|
|
900
|
-
|
|
901
|
-
return value === "ask" || value === "timeout" ? "ask" : "auto";
|
|
1116
|
+
function createDiagnosticsApi(doFetch = fetch) {
|
|
1117
|
+
return { view: async () => await requestJson(doFetch, ROUTES.diagnostics.view, { headers: { accept: "application/json" } }) };
|
|
902
1118
|
}
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
now: "approvalAgeNow",
|
|
906
|
-
minutes: "approvalAgeMinutes",
|
|
907
|
-
hours: "approvalAgeHours",
|
|
908
|
-
days: "approvalAgeDays",
|
|
909
|
-
months: "approvalAgeMonths"
|
|
910
|
-
});
|
|
911
|
-
/** Which word one staged kind gets. */
|
|
912
|
-
const KIND_KEYS = Object.freeze({
|
|
913
|
-
memory: "approvalKindMemory",
|
|
914
|
-
skill: "approvalKindSkill",
|
|
915
|
-
capability: "approvalKindCapability"
|
|
916
|
-
});
|
|
1119
|
+
//#endregion
|
|
1120
|
+
//#region ../evolution-client-kit/src/generated/fallbacks.generated.ts
|
|
917
1121
|
/**
|
|
918
|
-
*
|
|
919
|
-
*
|
|
920
|
-
*
|
|
921
|
-
*/
|
|
922
|
-
function PendingCard(props) {
|
|
923
|
-
const { t, api } = props;
|
|
924
|
-
const translate = (key, values) => t(key, values);
|
|
925
|
-
const confirmMode = props.confirmMode?.() ?? "auto";
|
|
926
|
-
const [armed, setArmed] = (0, react.useState)(null);
|
|
927
|
-
const [state, setState] = (0, react.useState)(INITIAL_PENDING_STATE);
|
|
928
|
-
const [open, setOpen] = (0, react.useState)(true);
|
|
929
|
-
const latest = (0, react.useRef)(state);
|
|
930
|
-
const apply = (next) => {
|
|
931
|
-
const value = next(latest.current);
|
|
932
|
-
latest.current = value;
|
|
933
|
-
setState(value);
|
|
934
|
-
};
|
|
935
|
-
/**
|
|
936
|
-
* Read the window the card is about: the writes WAITING and the writes RUNNING.
|
|
937
|
-
*
|
|
938
|
-
* T4-05/A47: the route has always taken `?status=` — the CLI asks for both lists — but this card
|
|
939
|
-
* asked for neither, so a record whose approve died between the claim and the decision showed as
|
|
940
|
-
* "nothing is waiting for a decision" while `/evolution pending` and doctor listed it. The two
|
|
941
|
-
* answers are ordered the way the CLI orders them (waiting first); either one failing fails the
|
|
942
|
-
* read, because a half-list would read as a complete window.
|
|
943
|
-
*/
|
|
944
|
-
const readTicket = (0, react.useRef)(0);
|
|
945
|
-
const load = async () => {
|
|
946
|
-
apply(loading);
|
|
947
|
-
const ticket = readTicket.current + 1;
|
|
948
|
-
readTicket.current = ticket;
|
|
949
|
-
const [waiting, running] = await Promise.all([api.pending(), api.pending("executing")]);
|
|
950
|
-
if (readTicket.current !== ticket) return;
|
|
951
|
-
if (!waiting.ok) {
|
|
952
|
-
apply((current) => failed(current, waiting.failure));
|
|
953
|
-
return;
|
|
954
|
-
}
|
|
955
|
-
if (!running.ok) {
|
|
956
|
-
apply((current) => failed(current, running.failure));
|
|
957
|
-
return;
|
|
958
|
-
}
|
|
959
|
-
apply((current) => loaded(current, [...waiting.data, ...running.data]));
|
|
960
|
-
};
|
|
961
|
-
(0, react.useEffect)(() => {
|
|
962
|
-
load();
|
|
963
|
-
const onFocus = () => {
|
|
964
|
-
load();
|
|
965
|
-
};
|
|
966
|
-
window.addEventListener("focus", onFocus);
|
|
967
|
-
return () => {
|
|
968
|
-
window.removeEventListener("focus", onFocus);
|
|
969
|
-
};
|
|
970
|
-
}, []);
|
|
971
|
-
const decide = (id, action) => {
|
|
972
|
-
apply((current) => beginAction(current, id));
|
|
973
|
-
(async () => {
|
|
974
|
-
const answer = action === "approve" ? await api.approve(id) : await api.reject(id);
|
|
975
|
-
if (!answer.ok) {
|
|
976
|
-
apply((current) => noticed(endAction(current, id), failureText(answer.failure, translate)));
|
|
977
|
-
return;
|
|
978
|
-
}
|
|
979
|
-
apply((current) => noticed(endAction(current, id), null));
|
|
980
|
-
await load();
|
|
981
|
-
})();
|
|
982
|
-
};
|
|
983
|
-
const age = (row) => {
|
|
984
|
-
const key = AGE_KEYS[row.age.unit];
|
|
985
|
-
if (key === void 0) return String(row.age.n);
|
|
986
|
-
return t(key, { n: row.age.n });
|
|
987
|
-
};
|
|
988
|
-
/** Ask the host what this record would store; the answer lands only if the row is still open. */
|
|
989
|
-
const loadPreview = async (id) => {
|
|
990
|
-
const answer = await api.preview(id);
|
|
991
|
-
const view = answer.ok ? answer.data.available ? {
|
|
992
|
-
kind: "ready",
|
|
993
|
-
answer: answer.data
|
|
994
|
-
} : {
|
|
995
|
-
kind: "unavailable",
|
|
996
|
-
reason: answer.data.reason
|
|
997
|
-
} : {
|
|
998
|
-
kind: "failed",
|
|
999
|
-
failure: answer.failure
|
|
1000
|
-
};
|
|
1001
|
-
apply((current) => previewArrived(current, id, view));
|
|
1002
|
-
};
|
|
1003
|
-
const togglePreview = (id) => {
|
|
1004
|
-
if (state.open === id) {
|
|
1005
|
-
apply(collapsed);
|
|
1006
|
-
return;
|
|
1007
|
-
}
|
|
1008
|
-
apply((current) => expanded(current, id));
|
|
1009
|
-
loadPreview(id);
|
|
1010
|
-
};
|
|
1011
|
-
/** The facts line, then the source view: the same shape the history panel's diff uses. */
|
|
1012
|
-
const preview = (view) => {
|
|
1013
|
-
if (view.kind === "loading") return (0, react.createElement)("p", {
|
|
1014
|
-
className: "evolution-param-note",
|
|
1015
|
-
role: "status"
|
|
1016
|
-
}, t("approvalPreviewReading"));
|
|
1017
|
-
if (view.kind === "failed") return (0, react.createElement)("p", {
|
|
1018
|
-
className: "evolution-param-error",
|
|
1019
|
-
role: "status"
|
|
1020
|
-
}, failureText(view.failure, translate));
|
|
1021
|
-
if (view.kind === "unavailable") return (0, react.createElement)("p", {
|
|
1022
|
-
className: "evolution-param-error",
|
|
1023
|
-
role: "status"
|
|
1024
|
-
}, t("approvalPreviewNone") + view.reason);
|
|
1025
|
-
const facts = [
|
|
1026
|
-
(0, react.createElement)("span", {
|
|
1027
|
-
className: "evolution-param-fact",
|
|
1028
|
-
key: "added"
|
|
1029
|
-
}, t("approvalDiffAdded", { n: view.answer.linesAdded })),
|
|
1030
|
-
(0, react.createElement)("span", {
|
|
1031
|
-
className: "evolution-param-fact",
|
|
1032
|
-
key: "removed"
|
|
1033
|
-
}, t("approvalDiffRemoved", { n: view.answer.linesRemoved })),
|
|
1034
|
-
...view.answer.truncated ? [(0, react.createElement)("span", {
|
|
1035
|
-
className: "evolution-param-fact",
|
|
1036
|
-
key: "cut"
|
|
1037
|
-
}, t("approvalDiffCut"))] : []
|
|
1038
|
-
];
|
|
1039
|
-
const lines = [];
|
|
1040
|
-
for (const [hunkIndex, hunk] of view.answer.hunks.entries()) {
|
|
1041
|
-
for (const [index, line] of hunk.oldText.split("\n").entries()) {
|
|
1042
|
-
if (hunk.oldText === "" && line === "") continue;
|
|
1043
|
-
lines.push((0, react.createElement)("div", {
|
|
1044
|
-
key: "old-" + String(hunkIndex) + "-" + String(index),
|
|
1045
|
-
className: "evolution-param-line-del"
|
|
1046
|
-
}, "- " + line));
|
|
1047
|
-
}
|
|
1048
|
-
for (const [index, line] of hunk.newText.split("\n").entries()) {
|
|
1049
|
-
if (hunk.newText === "" && line === "") continue;
|
|
1050
|
-
lines.push((0, react.createElement)("div", {
|
|
1051
|
-
key: "new-" + String(hunkIndex) + "-" + String(index),
|
|
1052
|
-
className: "evolution-param-line-add"
|
|
1053
|
-
}, "+ " + line));
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
return (0, react.createElement)("div", null, (0, react.createElement)("p", { className: "evolution-param-facts" }, ...facts), (0, react.createElement)("pre", { className: "evolution-param-diff" }, ...lines.length === 0 ? ["±"] : lines));
|
|
1057
|
-
};
|
|
1058
|
-
const row = (entry) => {
|
|
1059
|
-
if (entry.status === "executing") return (0, react.createElement)("div", {
|
|
1060
|
-
className: "evolution-param-field",
|
|
1061
|
-
key: entry.id
|
|
1062
|
-
}, (0, react.createElement)("div", { className: "evolution-param-label" }, (0, react.createElement)("span", { className: "evolution-param-source" }, t(KIND_KEYS[entry.kind])), (0, react.createElement)("span", { className: "evolution-param-value" }, entry.summary)), (0, react.createElement)("p", { className: "evolution-param-hint" }, age(entry)), (0, react.createElement)("p", {
|
|
1063
|
-
className: "evolution-param-note",
|
|
1064
|
-
role: "status"
|
|
1065
|
-
}, t("approvalExecuting")), entry.claimedBy === void 0 ? null : (0, react.createElement)("p", { className: "evolution-param-hint" }, entry.claimedBy));
|
|
1066
|
-
const busy = state.busy.includes(entry.id);
|
|
1067
|
-
const button = (label, action, tone) => (0, react.createElement)("button", {
|
|
1068
|
-
className: "evolution-param-button",
|
|
1069
|
-
type: "button",
|
|
1070
|
-
disabled: busy,
|
|
1071
|
-
...tone === "primary" ? { "data-primary": "true" } : { "data-tone": tone },
|
|
1072
|
-
onClick: () => {
|
|
1073
|
-
if (action === "approve" && confirmMode === "ask" && armed !== entry.id) {
|
|
1074
|
-
setArmed(entry.id);
|
|
1075
|
-
return;
|
|
1076
|
-
}
|
|
1077
|
-
setArmed(null);
|
|
1078
|
-
decide(entry.id, action);
|
|
1079
|
-
}
|
|
1080
|
-
}, busy ? t("approvalLoading") : t(label));
|
|
1081
|
-
return (0, react.createElement)("div", {
|
|
1082
|
-
className: "evolution-param-field",
|
|
1083
|
-
key: entry.id
|
|
1084
|
-
}, (0, react.createElement)("div", { className: "evolution-param-label" }, (0, react.createElement)("span", { className: "evolution-param-source" }, t(KIND_KEYS[entry.kind])), (0, react.createElement)("span", { className: "evolution-param-value" }, entry.summary)), (0, react.createElement)("p", { className: "evolution-param-hint" }, age(entry)), armed === entry.id ? (0, react.createElement)("p", {
|
|
1085
|
-
className: "evolution-param-note",
|
|
1086
|
-
role: "status"
|
|
1087
|
-
}, (0, react.createElement)("span", { className: "evolution-param-value" }, t("approvalConfirmNamed", { n: entry.summary })), (0, react.createElement)("button", {
|
|
1088
|
-
className: "evolution-param-button",
|
|
1089
|
-
type: "button",
|
|
1090
|
-
"data-confirm": "yes",
|
|
1091
|
-
"data-primary": "true",
|
|
1092
|
-
onClick: () => {
|
|
1093
|
-
setArmed(null);
|
|
1094
|
-
decide(entry.id, "approve");
|
|
1095
|
-
}
|
|
1096
|
-
}, t("approvalConfirmYes")), (0, react.createElement)("button", {
|
|
1097
|
-
className: "evolution-param-button",
|
|
1098
|
-
type: "button",
|
|
1099
|
-
"data-tone": "ghost",
|
|
1100
|
-
onClick: () => {
|
|
1101
|
-
setArmed(null);
|
|
1102
|
-
}
|
|
1103
|
-
}, t("approvalConfirmNo"))) : null, (0, react.createElement)("div", { className: "evolution-param-actions" }, button("approvalApprove", "approve", "primary"), button("approvalReject", "reject", "outline"), (0, react.createElement)("button", {
|
|
1104
|
-
className: "evolution-param-button",
|
|
1105
|
-
type: "button",
|
|
1106
|
-
"data-tone": "ghost",
|
|
1107
|
-
onClick: () => {
|
|
1108
|
-
togglePreview(entry.id);
|
|
1109
|
-
}
|
|
1110
|
-
}, state.open === entry.id ? t("approvalPreviewClose") : t("approvalPreview"))), state.open === entry.id && state.preview !== null ? preview(state.preview) : null);
|
|
1111
|
-
};
|
|
1112
|
-
const body = () => {
|
|
1113
|
-
if (state.view.kind === "loading") return (0, react.createElement)("p", {
|
|
1114
|
-
className: "evolution-param-note",
|
|
1115
|
-
role: "status"
|
|
1116
|
-
}, t("approvalLoading"));
|
|
1117
|
-
if (state.view.kind === "empty") return (0, react.createElement)("p", {
|
|
1118
|
-
className: "evolution-param-note",
|
|
1119
|
-
role: "status"
|
|
1120
|
-
}, t("approvalEmpty"));
|
|
1121
|
-
if (state.view.kind === "failed") return (0, react.createElement)("div", { className: "evolution-param-actions" }, FailureBlock({
|
|
1122
|
-
failure: state.view.failure,
|
|
1123
|
-
t: translate,
|
|
1124
|
-
fallback: props.fallback
|
|
1125
|
-
}), (0, react.createElement)("button", {
|
|
1126
|
-
className: "evolution-param-button",
|
|
1127
|
-
type: "button",
|
|
1128
|
-
onClick: () => {
|
|
1129
|
-
load();
|
|
1130
|
-
}
|
|
1131
|
-
}, t("approvalRetry")));
|
|
1132
|
-
return (0, react.createElement)("div", null, ...state.view.rows.map(row), state.notice === null ? null : (0, react.createElement)("p", {
|
|
1133
|
-
className: "evolution-param-error",
|
|
1134
|
-
role: "status"
|
|
1135
|
-
}, state.notice));
|
|
1136
|
-
};
|
|
1137
|
-
const rows = state.view.kind === "ready" ? state.view.rows : [];
|
|
1138
|
-
const waiting = rows.filter((entry) => entry.status !== "executing").length;
|
|
1139
|
-
const running = rows.length - waiting;
|
|
1140
|
-
return (0, react.createElement)("section", { className: "evolution-param-card" }, (0, react.createElement)("button", {
|
|
1141
|
-
className: "evolution-param-head",
|
|
1142
|
-
type: "button",
|
|
1143
|
-
onClick: () => {
|
|
1144
|
-
setOpen(!open);
|
|
1145
|
-
},
|
|
1146
|
-
"aria-expanded": open
|
|
1147
|
-
}, (0, react.createElement)("span", { className: "evolution-param-card-title" }, t("approvalTitle")), waiting === 0 ? null : (0, react.createElement)("span", { className: "evolution-param-count" }, t("approvalCount", { n: waiting })), running === 0 ? null : (0, react.createElement)("span", { className: "evolution-param-count" }, t("approvalRunningCount", { n: running })), (0, react.createElement)("span", { className: "evolution-param-chevron" }, open ? "▲" : "▼")), open ? (0, react.createElement)("div", { className: "evolution-param-body" }, (0, react.createElement)("div", { className: "evolution-param-actions" }, (0, react.createElement)("button", {
|
|
1148
|
-
className: "evolution-param-button",
|
|
1149
|
-
type: "button",
|
|
1150
|
-
onClick: () => {
|
|
1151
|
-
load();
|
|
1152
|
-
}
|
|
1153
|
-
}, t("approvalRefresh"))), body()) : null);
|
|
1154
|
-
}
|
|
1155
|
-
//#endregion
|
|
1156
|
-
//#region src/panels/settings/DiagnosticsCard.ts
|
|
1157
|
-
/**
|
|
1158
|
-
* The diagnostics card (W7 S7): the settings face of the SAME DiagnosticsView the CLI and the
|
|
1159
|
-
* `/api/dsh-evolution/diagnostics` route answer.
|
|
1160
|
-
*
|
|
1161
|
-
* Read-only by construction: it fetches once on mount and only refetches on an explicit Refresh; a
|
|
1162
|
-
* failed read renders the shared FailureBlock with the command-face fallback. The client owns no
|
|
1163
|
-
* key list and no status vocabulary: it renders the host view's frozen runtime rows and leads with
|
|
1164
|
-
* a plain-language summary. The view's documentation facts are a CLI/JSON concern and are
|
|
1165
|
-
* deliberately NOT rendered here (0.27.2 real-machine feedback: a settings page is not a spec
|
|
1166
|
-
* sheet; the runtime window is the whole reading).
|
|
1167
|
-
* @module @lmzhen/dsh-evolution-ui/panels/settings/client/DiagnosticsCard
|
|
1168
|
-
*/
|
|
1169
|
-
/** Which word one status gets (the frozen W1 status set). */
|
|
1170
|
-
const STATUS_KEYS = Object.freeze({
|
|
1171
|
-
ok: "diagStatusOk",
|
|
1172
|
-
warn: "diagStatusWarn",
|
|
1173
|
-
fail: "diagStatusFail",
|
|
1174
|
-
unknown: "diagStatusUnknown"
|
|
1175
|
-
});
|
|
1176
|
-
/** The summary word for a badge status (the same frozen set; ok has no count). */
|
|
1177
|
-
const SUMMARY_KEYS = Object.freeze({
|
|
1178
|
-
ok: "diagSummaryOk",
|
|
1179
|
-
warn: "diagSummaryWarn",
|
|
1180
|
-
fail: "diagSummaryFail",
|
|
1181
|
-
unknown: "diagSummaryUnknown"
|
|
1182
|
-
});
|
|
1183
|
-
/** The one status the card leads with: fail > warn > unknown > ok. */
|
|
1184
|
-
function summaryOf(rows) {
|
|
1185
|
-
const count = (status) => rows.filter((row) => row.status === status).length;
|
|
1186
|
-
for (const status of [
|
|
1187
|
-
"fail",
|
|
1188
|
-
"warn",
|
|
1189
|
-
"unknown"
|
|
1190
|
-
]) {
|
|
1191
|
-
const n = count(status);
|
|
1192
|
-
if (n > 0) return {
|
|
1193
|
-
status,
|
|
1194
|
-
count: n
|
|
1195
|
-
};
|
|
1196
|
-
}
|
|
1197
|
-
return {
|
|
1198
|
-
status: "ok",
|
|
1199
|
-
count: 0
|
|
1200
|
-
};
|
|
1201
|
-
}
|
|
1202
|
-
/**
|
|
1203
|
-
* Render the card.
|
|
1204
|
-
* @param props - the copy seat, the host call and the command fallback.
|
|
1205
|
-
* @returns the card.
|
|
1206
|
-
*/
|
|
1207
|
-
function DiagnosticsCard(props) {
|
|
1208
|
-
const { t, api } = props;
|
|
1209
|
-
const translate = (key, values) => t(key, values);
|
|
1210
|
-
const [state, setState] = (0, react.useState)({ phase: "loading" });
|
|
1211
|
-
const [open, setOpen] = (0, react.useState)(true);
|
|
1212
|
-
const ticket = (0, react.useRef)(0);
|
|
1213
|
-
const read = (keepView) => {
|
|
1214
|
-
const current = ticket.current + 1;
|
|
1215
|
-
ticket.current = current;
|
|
1216
|
-
if (!keepView) setState({ phase: "loading" });
|
|
1217
|
-
api.view().then((answer) => {
|
|
1218
|
-
if (ticket.current !== current) return;
|
|
1219
|
-
setState(answer.ok ? {
|
|
1220
|
-
phase: "ready",
|
|
1221
|
-
view: answer.data
|
|
1222
|
-
} : {
|
|
1223
|
-
phase: "failed",
|
|
1224
|
-
failure: answer.failure
|
|
1225
|
-
});
|
|
1226
|
-
});
|
|
1227
|
-
};
|
|
1228
|
-
(0, react.useEffect)(() => {
|
|
1229
|
-
read(false);
|
|
1230
|
-
}, []);
|
|
1231
|
-
const refresh = () => {
|
|
1232
|
-
read(true);
|
|
1233
|
-
};
|
|
1234
|
-
const summary = state.phase === "ready" ? summaryOf(state.view.runtime) : void 0;
|
|
1235
|
-
const header = (0, react.createElement)("button", {
|
|
1236
|
-
type: "button",
|
|
1237
|
-
className: "evolution-param-head",
|
|
1238
|
-
"aria-expanded": open ? "true" : "false",
|
|
1239
|
-
onClick: () => {
|
|
1240
|
-
setOpen(!open);
|
|
1241
|
-
}
|
|
1242
|
-
}, (0, react.createElement)("span", { className: "evolution-param-card-title" }, t("diagTitle")), summary === void 0 ? null : (0, react.createElement)("span", {
|
|
1243
|
-
className: "evo-diag-badge",
|
|
1244
|
-
"data-status": summary.status
|
|
1245
|
-
}, t(SUMMARY_KEYS[summary.status], summary.count === 0 ? void 0 : { n: summary.count })), (0, react.createElement)("span", { className: "evolution-param-chevron" }, open ? "▲" : "▼"));
|
|
1246
|
-
const runtime = (view) => view.runtime.length === 0 ? null : (0, react.createElement)("div", {}, (0, react.createElement)("h3", { className: "evolution-param-group" }, t("diagRuntimeTitle")), (0, react.createElement)("div", { className: "evo-diag-list" }, ...view.runtime.map((row) => (0, react.createElement)("div", {
|
|
1247
|
-
key: "runtime-" + row.key,
|
|
1248
|
-
className: "evo-diag-item",
|
|
1249
|
-
"data-kind": "runtime",
|
|
1250
|
-
"data-status": row.status
|
|
1251
|
-
}, (0, react.createElement)("div", { className: "evo-diag-row-head" }, (0, react.createElement)("span", { className: "evo-diag-dot" }), (0, react.createElement)("span", { className: "evo-diag-label" }, row.label), (0, react.createElement)("span", { className: "evo-diag-status" }, t(STATUS_KEYS[row.status]))), row.detail === void 0 ? null : (0, react.createElement)("span", { className: "evo-diag-detail" }, row.detail), row.remedy === void 0 ? null : (0, react.createElement)("span", { className: "evo-diag-remedy" }, t("diagRemedy") + row.remedy)))));
|
|
1252
|
-
const body = () => {
|
|
1253
|
-
if (state.phase === "loading") return (0, react.createElement)("p", { className: "evolution-param-loading" }, t("stateLoading"));
|
|
1254
|
-
if (state.phase === "failed") return FailureBlock({
|
|
1255
|
-
failure: state.failure,
|
|
1256
|
-
t: translate,
|
|
1257
|
-
fallback: props.fallback
|
|
1258
|
-
});
|
|
1259
|
-
const view = state.view;
|
|
1260
|
-
return (0, react.createElement)("div", {}, (0, react.createElement)("p", { className: "evolution-param-note" }, t("diagSubtitle")), (0, react.createElement)("div", { className: "evolution-param-actions" }, (0, react.createElement)("button", {
|
|
1261
|
-
type: "button",
|
|
1262
|
-
className: "evolution-param-button",
|
|
1263
|
-
onClick: refresh,
|
|
1264
|
-
"data-action": "diag-refresh"
|
|
1265
|
-
}, t("diagRefresh"))), runtime(view), view.runtime.length === 0 ? (0, react.createElement)("p", { className: "evolution-param-note" }, t("diagNoRows")) : null);
|
|
1266
|
-
};
|
|
1267
|
-
return (0, react.createElement)("section", {
|
|
1268
|
-
className: "evolution-param-card",
|
|
1269
|
-
"data-card": "diagnostics"
|
|
1270
|
-
}, header, open ? (0, react.createElement)("div", { className: "evolution-param-body" }, body()) : null);
|
|
1271
|
-
}
|
|
1272
|
-
//#endregion
|
|
1273
|
-
//#region src/panels/settings/diagnostics-state.ts
|
|
1274
|
-
/**
|
|
1275
|
-
* The diagnostics card's data face (W7 S7): ONE request to `HOST_ROUTES.diagnostics.view` through the
|
|
1276
|
-
* GENERATED route client, and the four-state answer the card renders.
|
|
1277
|
-
*
|
|
1278
|
-
* The Host route is the SAME `buildDoctorView` the CLI prints (W6/W8), so this client owns no facts
|
|
1279
|
-
* and no computation — it only renders.
|
|
1280
|
-
* @module @lmzhen/dsh-evolution-ui/panels/settings/client/diagnostics-state
|
|
1281
|
-
*/
|
|
1282
|
-
/**
|
|
1283
|
-
* Build the diagnostics face over one fetch implementation.
|
|
1284
|
-
* @param doFetch - the browser fetch, or a spec stub.
|
|
1285
|
-
*/
|
|
1286
|
-
function createDiagnosticsApi(doFetch = fetch) {
|
|
1287
|
-
return { view: async () => await requestJson(doFetch, ROUTES.diagnostics.view, { headers: { accept: "application/json" } }) };
|
|
1288
|
-
}
|
|
1289
|
-
//#endregion
|
|
1290
|
-
//#region ../evolution-client-kit/src/generated/fallbacks.generated.ts
|
|
1291
|
-
/**
|
|
1292
|
-
* The command-face fallback for every Host surface, generated from evolution-commands'
|
|
1293
|
-
* COMMAND_FALLBACKS (the ONE home). When the platform's connection carrier is absent (E-325) the
|
|
1294
|
-
* panels render this hint instead of a bare HTTP failure — same use case, command face.
|
|
1122
|
+
* The command-face fallback for every Host surface, generated from evolution-commands'
|
|
1123
|
+
* COMMAND_FALLBACKS (the ONE home). When the platform's connection carrier is absent (E-325) the
|
|
1124
|
+
* panels render this hint instead of a bare HTTP failure — same use case, command face.
|
|
1295
1125
|
*/
|
|
1296
1126
|
const FALLBACKS = {
|
|
1297
1127
|
approval: { command: "/evolution pending … /evolution approve <id> … /evolution reject <id> … /evolution release <id>" },
|
|
1298
|
-
"skill-history": { command: "/evolution
|
|
1128
|
+
"skill-history": { command: "/evolution skill undo <name> [--to v<N>](查看:/evolution skill history <name>)" },
|
|
1299
1129
|
"skill-health": { command: "/evolution skills refresh(健康行:/evolution doctor)" },
|
|
1300
1130
|
merge: { command: "/evolution merge status / pending / approve <id> / report" },
|
|
1301
1131
|
diagnostics: {
|
|
1302
1132
|
command: "/evolution doctor",
|
|
1303
1133
|
platformCli: "dsh --json /evolution doctor"
|
|
1304
|
-
}
|
|
1134
|
+
},
|
|
1135
|
+
settings: { command: "/evolution policy set <id> <value>(查看:/evolution policy)" }
|
|
1305
1136
|
};
|
|
1306
1137
|
//#endregion
|
|
1307
1138
|
//#region src/panels/settings/messages.ts
|
|
@@ -1321,6 +1152,7 @@ window.__ModuleLoader__.load({
|
|
|
1321
1152
|
const zh$1 = {
|
|
1322
1153
|
title: "自进化",
|
|
1323
1154
|
subtitle: "待你决定的写入在最上面;下面按功能列出你能改的参数,没有列出的由安装时的配置决定。",
|
|
1155
|
+
aboutPurpose: "自进化设置与技能历史",
|
|
1324
1156
|
overridden: "已覆盖",
|
|
1325
1157
|
deployment: "默认",
|
|
1326
1158
|
loading: "正在读取设置…",
|
|
@@ -1343,6 +1175,8 @@ window.__ModuleLoader__.load({
|
|
|
1343
1175
|
approvalConfirmYes: "确认批准",
|
|
1344
1176
|
approvalConfirmNo: "取消",
|
|
1345
1177
|
resetTo: "复位为 {value}",
|
|
1178
|
+
saved: "已保存。",
|
|
1179
|
+
resetDone: "已恢复默认值。",
|
|
1346
1180
|
refused: "这个值没有被接受(界面上保持原样)。想看原因:在对话里让它说明。",
|
|
1347
1181
|
resetRefused: "恢复默认值没有生效(这一行还是你的值)。想看原因:在对话里让它说明。",
|
|
1348
1182
|
cardReview: "会话回顾",
|
|
@@ -1365,10 +1199,22 @@ window.__ModuleLoader__.load({
|
|
|
1365
1199
|
approvalCount: "{n} 项待批",
|
|
1366
1200
|
approvalRunningCount: "{n} 项执行中",
|
|
1367
1201
|
approvalRefresh: "刷新",
|
|
1368
|
-
|
|
1202
|
+
approvalApproving: "批准中…",
|
|
1203
|
+
approvalRejecting: "拒绝中…",
|
|
1204
|
+
approvalExecuting: "执行中:写效果可能已经落地,不要重复批准。先核验效果,再用「放回待批」释放占用;释放不会停止或撤销在途写入。",
|
|
1205
|
+
approvalRelease: "放回待批",
|
|
1206
|
+
approvalReleasing: "释放中…",
|
|
1369
1207
|
approvalKindMemory: "记忆",
|
|
1370
1208
|
approvalKindSkill: "技能",
|
|
1371
1209
|
approvalKindCapability: "能力",
|
|
1210
|
+
approvalVerbAdd: "新增",
|
|
1211
|
+
approvalVerbCreate: "创建",
|
|
1212
|
+
approvalVerbUpdate: "更新",
|
|
1213
|
+
approvalVerbRemove: "移除",
|
|
1214
|
+
approvalVerbDelete: "删除",
|
|
1215
|
+
approvalVerbArchive: "归档",
|
|
1216
|
+
approvalVerbRestore: "恢复",
|
|
1217
|
+
approvalVerbMerge: "合并",
|
|
1372
1218
|
approvalAgeNow: "刚刚",
|
|
1373
1219
|
approvalAgeMinutes: "{n} 分钟前",
|
|
1374
1220
|
approvalAgeHours: "{n} 小时前",
|
|
@@ -1378,6 +1224,7 @@ window.__ModuleLoader__.load({
|
|
|
1378
1224
|
approvalPreviewClose: "收起",
|
|
1379
1225
|
approvalPreviewReading: "正在读取预览…",
|
|
1380
1226
|
approvalPreviewNone: "看不了预览:",
|
|
1227
|
+
approvalPreviewPath: "文件:{path}",
|
|
1381
1228
|
approvalDiffAdded: "{n} 行新增",
|
|
1382
1229
|
approvalDiffRemoved: "{n} 行删除",
|
|
1383
1230
|
approvalDiffCut: "改动区域已截断",
|
|
@@ -1407,6 +1254,7 @@ window.__ModuleLoader__.load({
|
|
|
1407
1254
|
const en$1 = {
|
|
1408
1255
|
title: "Self-evolution",
|
|
1409
1256
|
subtitle: "Writes waiting for your decision come first; the changeable parameters are listed by feature below, and anything not listed comes from the deployment configuration.",
|
|
1257
|
+
aboutPurpose: "Self-evolution settings and skill history",
|
|
1410
1258
|
overridden: "Overridden",
|
|
1411
1259
|
deployment: "default",
|
|
1412
1260
|
loading: "reading settings…",
|
|
@@ -1429,6 +1277,8 @@ window.__ModuleLoader__.load({
|
|
|
1429
1277
|
approvalConfirmYes: "Approve",
|
|
1430
1278
|
approvalConfirmNo: "Cancel",
|
|
1431
1279
|
resetTo: "Restores {value}",
|
|
1280
|
+
saved: "Saved.",
|
|
1281
|
+
resetDone: "Default restored.",
|
|
1432
1282
|
refused: "The value was not accepted (the page keeps what you see). For the reason, ask in the conversation.",
|
|
1433
1283
|
resetRefused: "The reset did not take (this row still holds your value). For the reason, ask in the conversation.",
|
|
1434
1284
|
cardReview: "Session review",
|
|
@@ -1451,10 +1301,22 @@ window.__ModuleLoader__.load({
|
|
|
1451
1301
|
approvalCount: "{n} waiting",
|
|
1452
1302
|
approvalRunningCount: "{n} running",
|
|
1453
1303
|
approvalRefresh: "Refresh",
|
|
1454
|
-
|
|
1304
|
+
approvalApproving: "approving…",
|
|
1305
|
+
approvalRejecting: "rejecting…",
|
|
1306
|
+
approvalExecuting: "Running: the effect may already have landed; do not approve it twice. Verify it, then use Release to put the claim back; release does not stop or undo the in-flight write.",
|
|
1307
|
+
approvalRelease: "Release",
|
|
1308
|
+
approvalReleasing: "releasing…",
|
|
1455
1309
|
approvalKindMemory: "memory",
|
|
1456
1310
|
approvalKindSkill: "skill",
|
|
1457
1311
|
approvalKindCapability: "capability",
|
|
1312
|
+
approvalVerbAdd: "add",
|
|
1313
|
+
approvalVerbCreate: "create",
|
|
1314
|
+
approvalVerbUpdate: "update",
|
|
1315
|
+
approvalVerbRemove: "remove",
|
|
1316
|
+
approvalVerbDelete: "delete",
|
|
1317
|
+
approvalVerbArchive: "archive",
|
|
1318
|
+
approvalVerbRestore: "restore",
|
|
1319
|
+
approvalVerbMerge: "merge",
|
|
1458
1320
|
approvalAgeNow: "just now",
|
|
1459
1321
|
approvalAgeMinutes: "{n} min ago",
|
|
1460
1322
|
approvalAgeHours: "{n} h ago",
|
|
@@ -1464,6 +1326,7 @@ window.__ModuleLoader__.load({
|
|
|
1464
1326
|
approvalPreviewClose: "Hide",
|
|
1465
1327
|
approvalPreviewReading: "reading the preview…",
|
|
1466
1328
|
approvalPreviewNone: "no preview: ",
|
|
1329
|
+
approvalPreviewPath: "File: {path}",
|
|
1467
1330
|
approvalDiffAdded: "{n} lines added",
|
|
1468
1331
|
approvalDiffRemoved: "{n} lines removed",
|
|
1469
1332
|
approvalDiffCut: "the changed region is truncated",
|
|
@@ -1507,10 +1370,10 @@ window.__ModuleLoader__.load({
|
|
|
1507
1370
|
/**
|
|
1508
1371
|
* Resolve one message without the locale seat (tests and the fallback path).
|
|
1509
1372
|
* @param key - the message key.
|
|
1510
|
-
* @param language - which dictionary to read; defaults to
|
|
1373
|
+
* @param language - which dictionary to read; defaults to zh, matching the platform FALLBACK_LOCALE.
|
|
1511
1374
|
* @returns the copy this bundle carries.
|
|
1512
1375
|
*/
|
|
1513
|
-
function message(key, language = "
|
|
1376
|
+
function message(key, language = "zh") {
|
|
1514
1377
|
return language === "zh" ? zh$1[key] : en$1[key];
|
|
1515
1378
|
}
|
|
1516
1379
|
/**
|
|
@@ -1522,7 +1385,7 @@ window.__ModuleLoader__.load({
|
|
|
1522
1385
|
* @param values - substitutions keyed by the slot name inside the braces.
|
|
1523
1386
|
* @returns the filled copy.
|
|
1524
1387
|
*/
|
|
1525
|
-
function fill
|
|
1388
|
+
function fill(template, values) {
|
|
1526
1389
|
return template.replace(/\{(\w+)\}/g, (match, slot) => {
|
|
1527
1390
|
const value = values[slot];
|
|
1528
1391
|
return value === void 0 ? match : String(value);
|
|
@@ -1555,6 +1418,18 @@ window.__ModuleLoader__.load({
|
|
|
1555
1418
|
* typed from the registry (\`control\`), so booleans are switches, enums are
|
|
1556
1419
|
* selects and numbers are numeric inputs with the unit shown beside the label.
|
|
1557
1420
|
*/
|
|
1421
|
+
/** Resolve one field's user-language label. */
|
|
1422
|
+
function labelOf(field, lang) {
|
|
1423
|
+
return lang === "en" ? field.labelEn ?? field.label : field.label;
|
|
1424
|
+
}
|
|
1425
|
+
/** Resolve one field's user-language hint. */
|
|
1426
|
+
function hintOf(field, lang) {
|
|
1427
|
+
return lang === "en" ? field.hintEn ?? field.doc : field.hint;
|
|
1428
|
+
}
|
|
1429
|
+
/** Resolve one field's user-language group heading. */
|
|
1430
|
+
function groupOf(field, lang) {
|
|
1431
|
+
return lang === "en" ? field.groupLabelEn ?? field.group : field.groupLabel ?? field.group;
|
|
1432
|
+
}
|
|
1558
1433
|
/**
|
|
1559
1434
|
* The NAMESPACE-RELATIVE key of one registry field.
|
|
1560
1435
|
*
|
|
@@ -1644,7 +1519,7 @@ window.__ModuleLoader__.load({
|
|
|
1644
1519
|
}
|
|
1645
1520
|
/** One field's control, typed from the registry. */
|
|
1646
1521
|
function FieldControl(props) {
|
|
1647
|
-
const { field, text } = props;
|
|
1522
|
+
const { field, text, lang } = props;
|
|
1648
1523
|
const id = controlId(field);
|
|
1649
1524
|
const common = {
|
|
1650
1525
|
id,
|
|
@@ -1677,7 +1552,7 @@ window.__ModuleLoader__.load({
|
|
|
1677
1552
|
}, text), ...field.values.map((value, index) => (0, react.createElement)("option", {
|
|
1678
1553
|
key: value,
|
|
1679
1554
|
value
|
|
1680
|
-
}, field.valueLabels[index] ?? value))]);
|
|
1555
|
+
}, (lang === "en" ? field.valueLabelsEn ?? field.valueLabels : field.valueLabels)[index] ?? value))]);
|
|
1681
1556
|
return (0, react.createElement)("input", {
|
|
1682
1557
|
...common,
|
|
1683
1558
|
type: field.control === "number" ? "number" : "text",
|
|
@@ -1689,16 +1564,17 @@ window.__ModuleLoader__.load({
|
|
|
1689
1564
|
}
|
|
1690
1565
|
/** One parameter field: label, control, hint, and its reset-to-deployment action. */
|
|
1691
1566
|
function FieldBlock(props) {
|
|
1692
|
-
const { field, t } = props;
|
|
1693
|
-
const
|
|
1694
|
-
|
|
1567
|
+
const { field, t, lang } = props;
|
|
1568
|
+
const unitText = lang === "en" ? field.unitEn ?? field.unit : field.unit;
|
|
1569
|
+
const unit = unitText === "" ? null : (0, react.createElement)("span", { className: "evolution-param-unit" }, (lang === "en" ? " (" : "(") + unitText + (lang === "en" ? ")" : ")"));
|
|
1570
|
+
return (0, react.createElement)("div", { className: "evolution-param-field" }, (0, react.createElement)("div", { className: "evolution-param-label" }, (0, react.createElement)("label", { htmlFor: controlId(field) }, labelOf(field, lang) === "" ? field.id : labelOf(field, lang)), unit, props.overridden ? (0, react.createElement)("span", {
|
|
1695
1571
|
className: "evolution-param-source",
|
|
1696
1572
|
"data-user": "true"
|
|
1697
|
-
}, t("overridden")) : null), FieldControl(props), (0, react.createElement)("p", { className: "evolution-param-hint" }, field
|
|
1573
|
+
}, t("overridden")) : null), FieldControl(props), (0, react.createElement)("p", { className: "evolution-param-hint" }, hintOf(field, lang) === "" ? field.doc : hintOf(field, lang)), props.invalid === null ? null : (0, react.createElement)("p", {
|
|
1698
1574
|
id: controlId(field) + "-invalid",
|
|
1699
1575
|
className: "evolution-param-error",
|
|
1700
1576
|
role: "status"
|
|
1701
|
-
},
|
|
1577
|
+
}, props.invalid), props.overridden ? (0, react.createElement)("div", { className: "evolution-param-actions" }, (0, react.createElement)("button", {
|
|
1702
1578
|
type: "button",
|
|
1703
1579
|
className: "evolution-param-button",
|
|
1704
1580
|
disabled: props.disabled || props.clearing,
|
|
@@ -1714,10 +1590,12 @@ window.__ModuleLoader__.load({
|
|
|
1714
1590
|
*/
|
|
1715
1591
|
function ParamCardView(props) {
|
|
1716
1592
|
const { t, fields, mutateOnce, clear, namespace, snapshot } = props;
|
|
1593
|
+
const lang = props.lang?.() ?? "zh";
|
|
1717
1594
|
const [open, setOpen] = (0, react.useState)(false);
|
|
1718
1595
|
const [draft, setDraft] = (0, react.useState)({});
|
|
1719
1596
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
1720
1597
|
const [error, setError] = (0, react.useState)("");
|
|
1598
|
+
const [note, setNote] = (0, react.useState)("");
|
|
1721
1599
|
const [pending, setPending] = (0, react.useState)(null);
|
|
1722
1600
|
const [clearing, setClearing] = (0, react.useState)(null);
|
|
1723
1601
|
/**
|
|
@@ -1742,7 +1620,19 @@ window.__ModuleLoader__.load({
|
|
|
1742
1620
|
setError(t(pending.resets ? "resetRefused" : "refused"));
|
|
1743
1621
|
return;
|
|
1744
1622
|
}
|
|
1745
|
-
if (
|
|
1623
|
+
if (pending.accepted) if (pending.resets) {
|
|
1624
|
+
const id = pending.field;
|
|
1625
|
+
setDraft((current) => {
|
|
1626
|
+
if (id === void 0) return current;
|
|
1627
|
+
const next = { ...current };
|
|
1628
|
+
delete next[id];
|
|
1629
|
+
return next;
|
|
1630
|
+
});
|
|
1631
|
+
setNote(t("resetDone"));
|
|
1632
|
+
} else {
|
|
1633
|
+
setDraft({});
|
|
1634
|
+
setNote(t("saved"));
|
|
1635
|
+
}
|
|
1746
1636
|
}, [
|
|
1747
1637
|
pending,
|
|
1748
1638
|
snapshot.user,
|
|
@@ -1772,7 +1662,12 @@ window.__ModuleLoader__.load({
|
|
|
1772
1662
|
const related = fields.find((candidate) => candidate.id === field.atLeast);
|
|
1773
1663
|
return related === void 0 ? void 0 : textOf(related);
|
|
1774
1664
|
};
|
|
1775
|
-
const
|
|
1665
|
+
const invalidText = (field) => {
|
|
1666
|
+
const reason = invalidReason(field, textOf(field), peerTextOf(field));
|
|
1667
|
+
if (reason === null) return null;
|
|
1668
|
+
return reason === "invalidAtLeast" ? t(reason, { n: peerTextOf(field) ?? "" }) : t(reason);
|
|
1669
|
+
};
|
|
1670
|
+
const blocked = fields.some((field) => invalidText(field) !== null);
|
|
1776
1671
|
const change = (id, next) => {
|
|
1777
1672
|
setDraft({
|
|
1778
1673
|
...draft,
|
|
@@ -1790,6 +1685,7 @@ window.__ModuleLoader__.load({
|
|
|
1790
1685
|
const reset = (field) => {
|
|
1791
1686
|
setClearing(field);
|
|
1792
1687
|
setError("");
|
|
1688
|
+
setNote("");
|
|
1793
1689
|
(async () => {
|
|
1794
1690
|
let accepted;
|
|
1795
1691
|
try {
|
|
@@ -1801,13 +1697,15 @@ window.__ModuleLoader__.load({
|
|
|
1801
1697
|
}
|
|
1802
1698
|
setPending({
|
|
1803
1699
|
accepted,
|
|
1804
|
-
resets: true
|
|
1700
|
+
resets: true,
|
|
1701
|
+
field
|
|
1805
1702
|
});
|
|
1806
1703
|
})();
|
|
1807
1704
|
};
|
|
1808
1705
|
const save = () => {
|
|
1809
1706
|
setBusy(true);
|
|
1810
1707
|
setError("");
|
|
1708
|
+
setNote("");
|
|
1811
1709
|
(async () => {
|
|
1812
1710
|
try {
|
|
1813
1711
|
setPending({
|
|
@@ -1831,7 +1729,7 @@ window.__ModuleLoader__.load({
|
|
|
1831
1729
|
onClick: () => {
|
|
1832
1730
|
setOpen(!open);
|
|
1833
1731
|
}
|
|
1834
|
-
}, (0, react.createElement)("span", { className: "evolution-param-card-title" }, title), changed === 0 ? null : (0, react.createElement)("span", { className: "evolution-param-count" }, t("changed", { n: changed })), (0, react.createElement)("span", { className: "evolution-param-
|
|
1732
|
+
}, (0, react.createElement)("span", { className: "evolution-param-card-title" }, title), changed === 0 ? null : (0, react.createElement)("span", { className: "evolution-param-count" }, t("changed", { n: changed })), (0, react.createElement)("span", { className: "evolution-param-chevron" }, open ? "▲" : "▼")), open ? (0, react.createElement)("div", { className: "evolution-param-body" }, hintKey === void 0 ? null : (0, react.createElement)("p", { className: "evolution-param-note" }, t(hintKey)), disabled ? (0, react.createElement)("p", {
|
|
1835
1733
|
className: "evolution-param-note",
|
|
1836
1734
|
role: "status"
|
|
1837
1735
|
}, t("readonly")) : null, fields.length === 0 ? (0, react.createElement)("p", {
|
|
@@ -1840,25 +1738,29 @@ window.__ModuleLoader__.load({
|
|
|
1840
1738
|
}, t("empty")) : fields.flatMap((field, index) => [index === 0 || fields[index - 1]?.group !== field.group ? (0, react.createElement)("h3", {
|
|
1841
1739
|
key: "group-" + field.group,
|
|
1842
1740
|
className: "evolution-param-group"
|
|
1843
|
-
}, field
|
|
1741
|
+
}, groupOf(field, lang)) : null, (0, react.createElement)(FieldBlock, {
|
|
1844
1742
|
key: field.id,
|
|
1845
1743
|
field,
|
|
1744
|
+
lang,
|
|
1846
1745
|
text: textOf(field),
|
|
1847
1746
|
overridden: Object.hasOwn(user, keyOf(field)),
|
|
1848
1747
|
disabled: locked,
|
|
1849
1748
|
clearing: clearing === field.id,
|
|
1850
1749
|
resetTo: resetToOf(snapshot.base?.[keyOf(field)]),
|
|
1851
|
-
invalid:
|
|
1750
|
+
invalid: invalidText(field),
|
|
1852
1751
|
t,
|
|
1853
1752
|
onChange: change,
|
|
1854
1753
|
onClear: reset
|
|
1855
1754
|
})]), error === "" ? null : (0, react.createElement)("p", {
|
|
1856
1755
|
className: "evolution-param-error",
|
|
1857
1756
|
role: "status"
|
|
1858
|
-
}, error),
|
|
1757
|
+
}, error), note === "" ? null : (0, react.createElement)("p", {
|
|
1758
|
+
className: "evolution-param-note",
|
|
1759
|
+
role: "status"
|
|
1760
|
+
}, note), fields.length === 0 ? null : (0, react.createElement)("div", { className: "evolution-param-footer" }, (0, react.createElement)("button", {
|
|
1859
1761
|
type: "button",
|
|
1860
1762
|
className: "evolution-param-button",
|
|
1861
|
-
disabled: busy || dirty.length === 0
|
|
1763
|
+
disabled: busy || dirty.length === 0,
|
|
1862
1764
|
onClick: () => {
|
|
1863
1765
|
setDraft({});
|
|
1864
1766
|
setError("");
|
|
@@ -1872,21 +1774,235 @@ window.__ModuleLoader__.load({
|
|
|
1872
1774
|
}, busy ? t("saving") : t("apply")))) : null);
|
|
1873
1775
|
}
|
|
1874
1776
|
/**
|
|
1875
|
-
* The card as our own section renders it: the row state comes from the hook seat the
|
|
1876
|
-
* renderer binds out of the inject face's `hooks` compartment (components never hold a
|
|
1877
|
-
* subscription of their own).
|
|
1878
|
-
* @param props - the card data, its callbacks, and the bound row hook.
|
|
1879
|
-
* @returns the card.
|
|
1777
|
+
* The card as our own section renders it: the row state comes from the hook seat the
|
|
1778
|
+
* renderer binds out of the inject face's `hooks` compartment (components never hold a
|
|
1779
|
+
* subscription of their own).
|
|
1780
|
+
* @param props - the card data, its callbacks, and the bound row hook.
|
|
1781
|
+
* @returns the card.
|
|
1782
|
+
*/
|
|
1783
|
+
/**
|
|
1784
|
+
* Platform card-face wrapper: `settings.plugin.item` injects the face but does NOT bind
|
|
1785
|
+
* the `use<Name>` hook compartment, so this component owns the one stable external-store
|
|
1786
|
+
* subscription the section card gets from its renderer.
|
|
1787
|
+
* @param props - the injected card face.
|
|
1788
|
+
* @returns the card with its live row snapshot.
|
|
1789
|
+
*/
|
|
1790
|
+
const pluginSources = /* @__PURE__ */ new Map();
|
|
1791
|
+
/**
|
|
1792
|
+
* Register a plugin-surface form source by namespace. The platform's
|
|
1793
|
+
* `settings.plugin.item` slot does not bind the card's `hooks` compartment, so
|
|
1794
|
+
* the owning settings apply hands the stable source here instead.
|
|
1795
|
+
* @param namespace - settings namespace.
|
|
1796
|
+
* @param source - the stable external-store source.
|
|
1797
|
+
*/
|
|
1798
|
+
function registerPluginParamSource(namespace, source) {
|
|
1799
|
+
pluginSources.set(namespace, source);
|
|
1800
|
+
}
|
|
1801
|
+
const MISSING_SNAPSHOT = {
|
|
1802
|
+
status: "unavailable",
|
|
1803
|
+
value: void 0,
|
|
1804
|
+
user: void 0,
|
|
1805
|
+
writable: false,
|
|
1806
|
+
base: void 0,
|
|
1807
|
+
reason: "seat-missing"
|
|
1808
|
+
};
|
|
1809
|
+
const MISSING_SOURCE = {
|
|
1810
|
+
getSnapshot: () => MISSING_SNAPSHOT,
|
|
1811
|
+
subscribe: () => () => {},
|
|
1812
|
+
revive: () => {}
|
|
1813
|
+
};
|
|
1814
|
+
function PluginParamCard(props) {
|
|
1815
|
+
const source = props.hooks?.paramSection ?? pluginSources.get(props.namespace) ?? MISSING_SOURCE;
|
|
1816
|
+
const snapshot = (0, react.useSyncExternalStore)(source.subscribe, source.getSnapshot, source.getSnapshot);
|
|
1817
|
+
return ParamCardView({
|
|
1818
|
+
namespace: props.namespace,
|
|
1819
|
+
fields: props.fields,
|
|
1820
|
+
t: props.t,
|
|
1821
|
+
...props.lang === void 0 ? {} : { lang: props.lang },
|
|
1822
|
+
mutateOnce: props.mutateOnce,
|
|
1823
|
+
clear: props.clear,
|
|
1824
|
+
snapshot
|
|
1825
|
+
});
|
|
1826
|
+
}
|
|
1827
|
+
//#endregion
|
|
1828
|
+
//#region src/panels/settings/seam.ts
|
|
1829
|
+
/** Our card slot, hosted by the section this bundle registers. */
|
|
1830
|
+
const CARD_SLOT = "evolution.namespace.card";
|
|
1831
|
+
/** The diagnostics card's slot key: read-only, no settings namespace (its data comes from HOST_ROUTES.diagnostics). */
|
|
1832
|
+
const DIAGNOSTICS_CARD_KEY = "diagnostics";
|
|
1833
|
+
//#endregion
|
|
1834
|
+
//#region ../evolution-client-kit/src/settings-seat.ts
|
|
1835
|
+
const SETTINGS_DESCRIBE = ROUTES.settings.describe;
|
|
1836
|
+
const SETTINGS_MUTATE = ROUTES.settings.mutate;
|
|
1837
|
+
const EMPTY = Object.freeze({
|
|
1838
|
+
status: "unavailable",
|
|
1839
|
+
value: void 0,
|
|
1840
|
+
user: void 0,
|
|
1841
|
+
writable: false,
|
|
1842
|
+
base: void 0,
|
|
1843
|
+
revision: void 0,
|
|
1844
|
+
reason: "transport"
|
|
1845
|
+
});
|
|
1846
|
+
function snapshotFrom(value, ns) {
|
|
1847
|
+
if (typeof value !== "object" || value === null) return EMPTY;
|
|
1848
|
+
const list = value.namespaces;
|
|
1849
|
+
if (!Array.isArray(list)) return EMPTY;
|
|
1850
|
+
const item = list.find((candidate) => typeof candidate === "object" && candidate !== null && candidate.ns === ns);
|
|
1851
|
+
return item === void 0 ? {
|
|
1852
|
+
...EMPTY,
|
|
1853
|
+
reason: "not-served"
|
|
1854
|
+
} : item;
|
|
1855
|
+
}
|
|
1856
|
+
async function describe(namespace) {
|
|
1857
|
+
try {
|
|
1858
|
+
const response = await fetch(SETTINGS_DESCRIBE, { headers: { accept: "application/json" } });
|
|
1859
|
+
if (!response.ok) return EMPTY;
|
|
1860
|
+
const body = await response.json();
|
|
1861
|
+
if (body.ok !== true) return EMPTY;
|
|
1862
|
+
return snapshotFrom(body.data, namespace);
|
|
1863
|
+
} catch {
|
|
1864
|
+
return EMPTY;
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
async function write(namespace, ops, expectedRevision) {
|
|
1868
|
+
try {
|
|
1869
|
+
const response = await fetch(SETTINGS_MUTATE, {
|
|
1870
|
+
method: "POST",
|
|
1871
|
+
headers: {
|
|
1872
|
+
"content-type": "application/json",
|
|
1873
|
+
accept: "application/json"
|
|
1874
|
+
},
|
|
1875
|
+
body: JSON.stringify({
|
|
1876
|
+
ns: namespace,
|
|
1877
|
+
ops,
|
|
1878
|
+
...expectedRevision === void 0 ? {} : { expectedRevision }
|
|
1879
|
+
})
|
|
1880
|
+
});
|
|
1881
|
+
if (!response.ok) return { ok: false };
|
|
1882
|
+
const body = await response.json();
|
|
1883
|
+
return {
|
|
1884
|
+
ok: body.ok === true,
|
|
1885
|
+
snapshot: snapshotFrom(body.data, namespace)
|
|
1886
|
+
};
|
|
1887
|
+
} catch {
|
|
1888
|
+
return { ok: false };
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
function createForm(namespace, ctx) {
|
|
1892
|
+
let snapshot = {
|
|
1893
|
+
...EMPTY,
|
|
1894
|
+
reason: "seat-missing"
|
|
1895
|
+
};
|
|
1896
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1897
|
+
let started = false;
|
|
1898
|
+
let listening = false;
|
|
1899
|
+
const notify = () => {
|
|
1900
|
+
for (const listener of listeners) listener();
|
|
1901
|
+
};
|
|
1902
|
+
const refresh = async () => {
|
|
1903
|
+
snapshot = await describe(namespace);
|
|
1904
|
+
notify();
|
|
1905
|
+
};
|
|
1906
|
+
const start = () => {
|
|
1907
|
+
if (started) return;
|
|
1908
|
+
started = true;
|
|
1909
|
+
if (!listening) {
|
|
1910
|
+
listening = true;
|
|
1911
|
+
(ctx.get?.("remote"))?.$on?.("settings/document-updated", (changed) => {
|
|
1912
|
+
if (changed === namespace) refresh();
|
|
1913
|
+
});
|
|
1914
|
+
}
|
|
1915
|
+
refresh();
|
|
1916
|
+
};
|
|
1917
|
+
const form = {
|
|
1918
|
+
getSnapshot: () => snapshot,
|
|
1919
|
+
subscribe: (listener) => {
|
|
1920
|
+
listeners.add(listener);
|
|
1921
|
+
start();
|
|
1922
|
+
return () => {
|
|
1923
|
+
listeners.delete(listener);
|
|
1924
|
+
};
|
|
1925
|
+
},
|
|
1926
|
+
set: async (field, value) => await form.mutate([{
|
|
1927
|
+
op: "set",
|
|
1928
|
+
path: [field],
|
|
1929
|
+
value
|
|
1930
|
+
}]),
|
|
1931
|
+
unset: async (field) => await form.mutate([{
|
|
1932
|
+
op: "unset",
|
|
1933
|
+
path: [field]
|
|
1934
|
+
}]),
|
|
1935
|
+
mutate: async (ops, expectedRevision) => {
|
|
1936
|
+
const outcome = await write(namespace, ops, expectedRevision ?? snapshot.revision);
|
|
1937
|
+
if (outcome.snapshot !== void 0) {
|
|
1938
|
+
snapshot = outcome.snapshot;
|
|
1939
|
+
notify();
|
|
1940
|
+
} else await refresh();
|
|
1941
|
+
return outcome.ok;
|
|
1942
|
+
}
|
|
1943
|
+
};
|
|
1944
|
+
return form;
|
|
1945
|
+
}
|
|
1946
|
+
/**
|
|
1947
|
+
* Build one cached form per family namespace over the family settings routes.
|
|
1948
|
+
* @param ctx - the client plugin context (remote subscription is optional).
|
|
1949
|
+
* @returns the seat; each namespace is served by /api/dsh-evolution/settings/*.
|
|
1950
|
+
*/
|
|
1951
|
+
const seats = /* @__PURE__ */ new WeakMap();
|
|
1952
|
+
function createSettingsSeat(ctx) {
|
|
1953
|
+
const cached = seats.get(ctx);
|
|
1954
|
+
if (cached !== void 0) return cached;
|
|
1955
|
+
const forms = /* @__PURE__ */ new Map();
|
|
1956
|
+
const seat = { get(namespace) {
|
|
1957
|
+
let form = forms.get(namespace);
|
|
1958
|
+
if (form === void 0) {
|
|
1959
|
+
form = createForm(namespace, ctx);
|
|
1960
|
+
forms.set(namespace, form);
|
|
1961
|
+
}
|
|
1962
|
+
return form;
|
|
1963
|
+
} };
|
|
1964
|
+
seats.set(ctx, seat);
|
|
1965
|
+
return seat;
|
|
1966
|
+
}
|
|
1967
|
+
//#endregion
|
|
1968
|
+
//#region ../evolution-client-kit/src/action.ts
|
|
1969
|
+
/**
|
|
1970
|
+
* Command-only actions. Approval decisions are a separate host API and must never
|
|
1971
|
+
* be represented as a copyable command here.
|
|
1972
|
+
* @module @lmzhen/dsh-evolution-client-kit
|
|
1973
|
+
*/
|
|
1974
|
+
//#endregion
|
|
1975
|
+
//#region ../evolution-client-kit/src/tokens.ts
|
|
1976
|
+
/**
|
|
1977
|
+
* The scale, declared on one element so it never leaks into the host shell.
|
|
1978
|
+
* @param scope - the selector the custom properties are declared on.
|
|
1979
|
+
* @returns the declaration block to put in the stylesheet.
|
|
1980
|
+
*/
|
|
1981
|
+
function tokenVars(scope) {
|
|
1982
|
+
return scope + "{--evo-type-lg:calc(1em + 2px);--evo-type-body:1em;--evo-type-sm:calc(1em - 1px);--evo-type-xs:calc(1em - 2px);--evo-space-2:2px;--evo-space-4:4px;--evo-space-6:6px;--evo-space-8:8px;--evo-space-10:10px;--evo-space-12:12px;--evo-space-14:14px;--evo-space-16:16px;--evo-space-20:20px;--evo-radius-chip:4px;--evo-radius-control:8px;--evo-radius-surface:12px;--evo-radius-pill:999px;--evo-hairline-width:.5px;--evo-hairline-marker:2px;--evo-measure-read:80ch;--evo-leading-base:1.5;--evo-tone-quiet:var(--dsw-alias-label-secondary);--evo-tone-primary:var(--dsw-alias-brand-primary);--evo-tone-danger:var(--dsw-alias-state-error-primary);--evo-tone-success:var(--dsw-alias-state-success-primary);--evo-focus-ring:2px solid var(--dsw-alias-label-primary);--evo-focus-offset:-2px;--evo-cap-aside:280px;--evo-cap-block:260px}";
|
|
1983
|
+
}
|
|
1984
|
+
//#endregion
|
|
1985
|
+
//#region src/panels/settings/SettingsSection.ts
|
|
1986
|
+
/**
|
|
1987
|
+
* The section shell: title, one-line explanation, then one card per namespace — with the staged-write
|
|
1988
|
+
* window's card ahead of them all, because it is the only card here that waits on a decision.
|
|
1989
|
+
*
|
|
1990
|
+
* This is the shape the platform's own settings sections use — a heading plus the
|
|
1991
|
+
* child slots it declared in its registration. The cards themselves are separate
|
|
1992
|
+
* registrations keyed by namespace, so a namespace the Host does not serve simply
|
|
1993
|
+
* renders nothing and a deployment that drops this row loses the whole section.
|
|
1994
|
+
* @module @lmzhen/dsh-evolution-ui/panels/settings/client
|
|
1880
1995
|
*/
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1996
|
+
/**
|
|
1997
|
+
* Render the section.
|
|
1998
|
+
* @param face - the shell's inject face: translator and child-slot renderer.
|
|
1999
|
+
* @returns the section element.
|
|
2000
|
+
*/
|
|
2001
|
+
function SettingsSection(face) {
|
|
2002
|
+
return (0, react.createElement)("div", { className: "evolution-params" }, (0, react.createElement)("h2", { className: "evolution-params-title" }, face.t("title")), (0, react.createElement)("p", { className: "evolution-params-subtitle" }, face.t("subtitle")), (0, react.createElement)("p", { className: "evolution-params-about" }, "0.32.6 · " + face.t("aboutPurpose")), (0, react.createElement)("div", {
|
|
2003
|
+
key: DIAGNOSTICS_CARD_KEY,
|
|
2004
|
+
className: "evolution-params-group"
|
|
2005
|
+
}, face.renderSlot(CARD_SLOT, {}, { entryKey: DIAGNOSTICS_CARD_KEY })));
|
|
1890
2006
|
}
|
|
1891
2007
|
//#endregion
|
|
1892
2008
|
//#region src/panels/settings/source.ts
|
|
@@ -1922,7 +2038,7 @@ window.__ModuleLoader__.load({
|
|
|
1922
2038
|
user: snapshot.user,
|
|
1923
2039
|
writable: snapshot.writable,
|
|
1924
2040
|
base: isRecord(snapshot.base) ? snapshot.base : void 0,
|
|
1925
|
-
...snapshot.status === "unavailable" ? { reason: "not-served" } : {}
|
|
2041
|
+
...snapshot.status === "unavailable" ? { reason: snapshot.reason ?? "not-served" } : {}
|
|
1926
2042
|
};
|
|
1927
2043
|
}
|
|
1928
2044
|
/**
|
|
@@ -1981,7 +2097,7 @@ window.__ModuleLoader__.load({
|
|
|
1981
2097
|
const seat = probe();
|
|
1982
2098
|
if (seat === void 0) return;
|
|
1983
2099
|
const next = seat.get(namespace);
|
|
1984
|
-
if (next === form) return;
|
|
2100
|
+
if (next === void 0 || next === form) return;
|
|
1985
2101
|
offForm?.();
|
|
1986
2102
|
offForm = void 0;
|
|
1987
2103
|
form = next;
|
|
@@ -2066,122 +2182,6 @@ window.__ModuleLoader__.load({
|
|
|
2066
2182
|
};
|
|
2067
2183
|
}
|
|
2068
2184
|
//#endregion
|
|
2069
|
-
//#region src/panels/settings/RowConfigCard.ts
|
|
2070
|
-
/**
|
|
2071
|
-
* The Plugins page's per-row configuration entry for a family row.
|
|
2072
|
-
*
|
|
2073
|
-
* The page owns the settings subscription — it re-renders the entry — and passes the row's
|
|
2074
|
-
* form in, so this component renders the SAME card body as our own section, fed by the
|
|
2075
|
-
* page's state instead of the bound hook and therefore holding no subscription of its own.
|
|
2076
|
-
* `summary` answers the page's one-line slot: a row's description fallback, which stays
|
|
2077
|
-
* empty until the row actually overrides something, exactly as the page expects.
|
|
2078
|
-
* @module @lmzhen/dsh-evolution-ui/panels/settings/client
|
|
2079
|
-
*/
|
|
2080
|
-
/**
|
|
2081
|
-
* Count the keys this row's user layer holds.
|
|
2082
|
-
* @param user - the raw user section, as the page reports it.
|
|
2083
|
-
* @param fields - the row's fields.
|
|
2084
|
-
* @returns how many of them the user overrode.
|
|
2085
|
-
*/
|
|
2086
|
-
function overridden(user, fields, namespace) {
|
|
2087
|
-
const layer = isRecord(user) ? user : {};
|
|
2088
|
-
return fields.filter((field) => Object.hasOwn(layer, fieldKeyOf(namespace, field))).length;
|
|
2089
|
-
}
|
|
2090
|
-
/**
|
|
2091
|
-
* Render one row's configuration entry.
|
|
2092
|
-
* @param props - the page's view/form seat plus this row's face.
|
|
2093
|
-
* @returns the entry; `summary` renders nothing for an untouched row (the page then keeps the row's own description).
|
|
2094
|
-
*/
|
|
2095
|
-
function RowConfigCard(props) {
|
|
2096
|
-
const { t, fields, namespace, view, form } = props;
|
|
2097
|
-
if (form === void 0) return view === "summary" ? null : (0, react.createElement)("p", {
|
|
2098
|
-
className: "evolution-param-note",
|
|
2099
|
-
role: "status"
|
|
2100
|
-
}, t("seatMissing"));
|
|
2101
|
-
const changed = overridden(form.state.user, fields, namespace);
|
|
2102
|
-
if (view === "summary") return changed === 0 ? null : (0, react.createElement)("span", null, t("changed", { n: changed }));
|
|
2103
|
-
return ParamCardView({
|
|
2104
|
-
namespace,
|
|
2105
|
-
fields,
|
|
2106
|
-
t,
|
|
2107
|
-
snapshot: sectionSnapshotOf(form.state),
|
|
2108
|
-
mutateOnce: async (ops) => form.mutate(ops, form.state.revision),
|
|
2109
|
-
clear: async (field) => form.mutate([{
|
|
2110
|
-
op: "unset",
|
|
2111
|
-
path: [field]
|
|
2112
|
-
}], form.state.revision)
|
|
2113
|
-
});
|
|
2114
|
-
}
|
|
2115
|
-
//#endregion
|
|
2116
|
-
//#region src/panels/settings/seam.ts
|
|
2117
|
-
/** Our card slot, hosted by the section this bundle registers. */
|
|
2118
|
-
const CARD_SLOT = "evolution.namespace.card";
|
|
2119
|
-
/** The Plugins page's per-row configuration seat (declared by `ui-plugin-manager`). */
|
|
2120
|
-
const ROW_CONFIG_SLOT = "plugins.row.config";
|
|
2121
|
-
/**
|
|
2122
|
-
* The key the Plugins page files a row's configuration under.
|
|
2123
|
-
*
|
|
2124
|
-
* Spelled here because the page's own helper cannot be imported (a value import from
|
|
2125
|
-
* another plugin's client half is forbidden):
|
|
2126
|
-
* `ui-plugin-manager`'s config-ledger module is the definition this mirrors.
|
|
2127
|
-
* @param bundle - the bundle package name that declares the row.
|
|
2128
|
-
* @param rowId - the row id that bundle's patch declares.
|
|
2129
|
-
* @returns the `plugins.row.config` key.
|
|
2130
|
-
*/
|
|
2131
|
-
function rowConfigKey(bundle, rowId) {
|
|
2132
|
-
return bundle + "#" + rowId;
|
|
2133
|
-
}
|
|
2134
|
-
/**
|
|
2135
|
-
* The pending-window card's slot key.
|
|
2136
|
-
*
|
|
2137
|
-
* The section pairs cards by key and the parameter cards use their settings namespace; this card has no
|
|
2138
|
-
* namespace (its data comes from the host's approval routes), so it carries a key of its own. The constant
|
|
2139
|
-
* lives HERE and not in `index.ts` because the SECTION has to name it too, and `index.ts` already imports
|
|
2140
|
-
* the section — one definition in the module both sides already share is what keeps that pair acyclic.
|
|
2141
|
-
*/
|
|
2142
|
-
const PENDING_CARD_KEY = "approval-pending";
|
|
2143
|
-
/** The diagnostics card's slot key: read-only, no settings namespace (its data comes from HOST_ROUTES.diagnostics). */
|
|
2144
|
-
const DIAGNOSTICS_CARD_KEY = "diagnostics";
|
|
2145
|
-
//#endregion
|
|
2146
|
-
//#region src/panels/settings/SettingsSection.ts
|
|
2147
|
-
/**
|
|
2148
|
-
* The section shell: title, one-line explanation, then one card per namespace — with the staged-write
|
|
2149
|
-
* window's card ahead of them all, because it is the only card here that waits on a decision.
|
|
2150
|
-
*
|
|
2151
|
-
* This is the shape the platform's own settings sections use — a heading plus the
|
|
2152
|
-
* child slots it declared in its registration. The cards themselves are separate
|
|
2153
|
-
* registrations keyed by namespace, so a namespace the Host does not serve simply
|
|
2154
|
-
* renders nothing and a deployment that drops this row loses the whole section.
|
|
2155
|
-
* @module @lmzhen/dsh-evolution-ui/panels/settings/client
|
|
2156
|
-
*/
|
|
2157
|
-
/**
|
|
2158
|
-
* Render the section.
|
|
2159
|
-
* @param face - the shell's inject face: translator and child-slot renderer.
|
|
2160
|
-
* @returns the section element.
|
|
2161
|
-
*/
|
|
2162
|
-
function SettingsSection(face) {
|
|
2163
|
-
return (0, react.createElement)("div", { className: "evolution-params" }, (0, react.createElement)("h2", { className: "evolution-params-title" }, face.t("title")), (0, react.createElement)("p", { className: "evolution-params-subtitle" }, face.t("subtitle")), (0, react.createElement)("p", { className: "evolution-params-about" }, "0.32.4 · Web UI umbrella for the evolution family: settings and skill-history panels"), (0, react.createElement)("div", {
|
|
2164
|
-
key: PENDING_CARD_KEY,
|
|
2165
|
-
className: "evolution-params-group"
|
|
2166
|
-
}, face.renderSlot(CARD_SLOT, {}, { entryKey: PENDING_CARD_KEY })), (0, react.createElement)("div", {
|
|
2167
|
-
key: DIAGNOSTICS_CARD_KEY,
|
|
2168
|
-
className: "evolution-params-group"
|
|
2169
|
-
}, face.renderSlot(CARD_SLOT, {}, { entryKey: DIAGNOSTICS_CARD_KEY })), ...CLIENT_PARAM_SECTIONS.map((section_) => (0, react.createElement)("div", {
|
|
2170
|
-
key: section_.namespace,
|
|
2171
|
-
className: "evolution-params-group"
|
|
2172
|
-
}, face.renderSlot(CARD_SLOT, {}, { entryKey: section_.namespace }))));
|
|
2173
|
-
}
|
|
2174
|
-
//#endregion
|
|
2175
|
-
//#region ../evolution-client-kit/src/tokens.ts
|
|
2176
|
-
/**
|
|
2177
|
-
* The scale, declared on one element so it never leaks into the host shell.
|
|
2178
|
-
* @param scope - the selector the custom properties are declared on.
|
|
2179
|
-
* @returns the declaration block to put in the stylesheet.
|
|
2180
|
-
*/
|
|
2181
|
-
function tokenVars(scope) {
|
|
2182
|
-
return scope + "{--evo-type-lg:calc(1em + 2px);--evo-type-body:1em;--evo-type-sm:calc(1em - 1px);--evo-type-xs:calc(1em - 2px);--evo-space-2:2px;--evo-space-4:4px;--evo-space-6:6px;--evo-space-8:8px;--evo-space-10:10px;--evo-space-12:12px;--evo-space-14:14px;--evo-space-16:16px;--evo-space-20:20px;--evo-radius-chip:4px;--evo-radius-control:8px;--evo-radius-surface:12px;--evo-radius-pill:999px;--evo-hairline-width:.5px;--evo-hairline-marker:2px;--evo-measure-read:80ch;--evo-leading-base:1.5;--evo-tone-quiet:var(--dsw-alias-label-secondary);--evo-tone-primary:var(--dsw-alias-brand-primary);--evo-tone-danger:var(--dsw-alias-state-error-primary);--evo-tone-success:var(--dsw-alias-state-success-primary);--evo-focus-ring:2px solid var(--dsw-alias-label-primary);--evo-focus-offset:-2px;--evo-cap-aside:280px;--evo-cap-block:260px}";
|
|
2183
|
-
}
|
|
2184
|
-
//#endregion
|
|
2185
2185
|
//#region src/panels/settings/styles.ts
|
|
2186
2186
|
/**
|
|
2187
2187
|
* The section's stylesheet.
|
|
@@ -2258,18 +2258,18 @@ window.__ModuleLoader__.load({
|
|
|
2258
2258
|
".evo-diag-badge[data-status=\"ok\"]{color:var(--dsw-alias-state-success-primary)}",
|
|
2259
2259
|
".evo-diag-badge[data-status=\"warn\"]{color:var(--dsw-alias-state-warn-primary)}",
|
|
2260
2260
|
".evo-diag-badge[data-status=\"fail\"]{color:var(--dsw-alias-state-error-primary)}",
|
|
2261
|
-
".evo-diag-badge[data-status=\"unknown\"]{color:var(--dsw-alias-
|
|
2261
|
+
".evo-diag-badge[data-status=\"unknown\"]{color:var(--dsw-alias-label-tertiary)}",
|
|
2262
2262
|
".evo-diag-list{display:flex;flex-direction:column}",
|
|
2263
2263
|
".evo-diag-item{padding:var(--evo-space-12) 0}",
|
|
2264
2264
|
".evo-diag-item+.evo-diag-item{border-top:var(--evo-hairline-width) solid var(--dsw-alias-border-l2)}",
|
|
2265
2265
|
".evo-diag-label{display:block;color:var(--dsw-alias-label-tertiary);font-size:var(--evo-type-xs);line-height:var(--evo-leading-base)}",
|
|
2266
2266
|
".evo-diag-row-head{display:flex;align-items:baseline;gap:var(--evo-space-6);flex-wrap:wrap}",
|
|
2267
|
-
".evo-diag-dot{width:var(--evo-space-6);height:var(--evo-space-6);border-radius:var(--evo-radius-pill);background:var(--evo-diag-tone,var(--dsw-alias-
|
|
2268
|
-
".evo-diag-status{color:var(--evo-diag-tone,var(--dsw-alias-
|
|
2267
|
+
".evo-diag-dot{width:var(--evo-space-6);height:var(--evo-space-6);border-radius:var(--evo-radius-pill);background:var(--evo-diag-tone,var(--dsw-alias-label-tertiary));flex:none}",
|
|
2268
|
+
".evo-diag-status{color:var(--evo-diag-tone,var(--dsw-alias-label-tertiary));font-size:var(--evo-type-xs);line-height:var(--evo-leading-base)}",
|
|
2269
2269
|
".evo-diag-item[data-status=\"ok\"]{--evo-diag-tone:var(--dsw-alias-state-success-primary)}",
|
|
2270
2270
|
".evo-diag-item[data-status=\"warn\"]{--evo-diag-tone:var(--dsw-alias-state-warn-primary)}",
|
|
2271
2271
|
".evo-diag-item[data-status=\"fail\"]{--evo-diag-tone:var(--dsw-alias-state-error-primary)}",
|
|
2272
|
-
".evo-diag-item[data-status=\"unknown\"]{--evo-diag-tone:var(--dsw-alias-
|
|
2272
|
+
".evo-diag-item[data-status=\"unknown\"]{--evo-diag-tone:var(--dsw-alias-label-tertiary)}",
|
|
2273
2273
|
".evo-diag-detail{display:block;margin:var(--evo-space-2) 0 0;color:var(--dsw-alias-label-secondary);font-size:var(--evo-type-xs);line-height:var(--evo-leading-base);overflow-wrap:anywhere}",
|
|
2274
2274
|
".evo-diag-remedy{display:block;margin:var(--evo-space-4) 0 0;color:var(--dsw-alias-label-tertiary);font-size:var(--evo-type-xs);line-height:var(--evo-leading-base)}"
|
|
2275
2275
|
].join("\n");
|
|
@@ -2331,15 +2331,21 @@ window.__ModuleLoader__.load({
|
|
|
2331
2331
|
}
|
|
2332
2332
|
return bound(key);
|
|
2333
2333
|
};
|
|
2334
|
-
const t = (key, values) => values === void 0 ? translate(key) : fill
|
|
2335
|
-
const
|
|
2334
|
+
const t = (key, values) => values === void 0 ? translate(key) : fill(translate(key), values);
|
|
2335
|
+
const language = () => {
|
|
2336
|
+
try {
|
|
2337
|
+
return ctx.locale?.getLocale?.().active === "en" ? "en" : "zh";
|
|
2338
|
+
} catch {
|
|
2339
|
+
return "zh";
|
|
2340
|
+
}
|
|
2341
|
+
};
|
|
2342
|
+
const seat = createSettingsSeat(ctx);
|
|
2343
|
+
const probe = () => seat;
|
|
2336
2344
|
const sources = createSourceCache(probe);
|
|
2337
2345
|
ctx.effect(() => () => {
|
|
2338
2346
|
sources.dispose();
|
|
2339
2347
|
}, "evolution-settings: sources");
|
|
2340
|
-
|
|
2341
|
-
const confirmMode = () => confirmModeFrom(approvalSource.getSnapshot().user);
|
|
2342
|
-
ctx.inject?.(["configForms"], () => {
|
|
2348
|
+
ctx.inject?.(["remote"], () => {
|
|
2343
2349
|
sources.revive();
|
|
2344
2350
|
});
|
|
2345
2351
|
seam.slots.inject("settings.section", function* () {
|
|
@@ -2355,179 +2361,543 @@ window.__ModuleLoader__.load({
|
|
|
2355
2361
|
} }
|
|
2356
2362
|
}, SettingsSection);
|
|
2357
2363
|
});
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
}
|
|
2364
|
+
for (const [index, section] of CLIENT_PARAM_SECTIONS.entries()) {
|
|
2365
|
+
const source = sources.sourceFor(section.seatKey);
|
|
2366
|
+
registerPluginParamSource(section.namespace, source);
|
|
2367
|
+
const { mutateOnce, clear } = writeFaceFor(probe, section.namespace);
|
|
2368
|
+
const face = () => ({
|
|
2369
|
+
namespace: section.namespace,
|
|
2370
|
+
fields: section.fields,
|
|
2371
|
+
t,
|
|
2372
|
+
lang: language,
|
|
2373
|
+
mutateOnce,
|
|
2374
|
+
clear,
|
|
2375
|
+
hooks: { paramSection: source }
|
|
2376
|
+
});
|
|
2377
|
+
seam.slots.inject("settings.plugin.item", function* () {
|
|
2370
2378
|
yield seam.slots.register({
|
|
2371
|
-
name:
|
|
2372
|
-
|
|
2379
|
+
name: "settings.plugin.item",
|
|
2380
|
+
id: section.namespace,
|
|
2381
|
+
order: index * 10,
|
|
2382
|
+
locale: NS$1,
|
|
2373
2383
|
inject: face
|
|
2374
|
-
},
|
|
2384
|
+
}, PluginParamCard);
|
|
2385
|
+
});
|
|
2386
|
+
}
|
|
2387
|
+
seam.slots.inject(CARD_SLOT, function* () {
|
|
2388
|
+
const diagApi = createDiagnosticsApi((input, init) => fetch(input, init));
|
|
2389
|
+
yield seam.slots.register({
|
|
2390
|
+
name: CARD_SLOT,
|
|
2391
|
+
key: DIAGNOSTICS_CARD_KEY,
|
|
2392
|
+
inject: () => ({
|
|
2393
|
+
t,
|
|
2394
|
+
api: diagApi,
|
|
2395
|
+
fallback: FALLBACKS.diagnostics
|
|
2396
|
+
})
|
|
2397
|
+
}, DiagnosticsCard);
|
|
2398
|
+
});
|
|
2399
|
+
}
|
|
2400
|
+
//#endregion
|
|
2401
|
+
//#region src/panels/history/client/api.ts
|
|
2402
|
+
/**
|
|
2403
|
+
* The host routes this bundle calls, and the ONE place a request is made.
|
|
2404
|
+
*
|
|
2405
|
+
* G5/W7: the paths come from the generated client route table (rendered from evolution-core
|
|
2406
|
+
* HOST_ROUTES), and the transport classifier is the shared generated route-client — the browser half
|
|
2407
|
+
* can no longer drift from the host table or grow a second HTTP vocabulary.
|
|
2408
|
+
* @module @lmzhen/dsh-evolution-ui/panels/history/client
|
|
2409
|
+
*/
|
|
2410
|
+
/** Route paths: the generated mirror of the host's HOST_ROUTES (one declaration in core). */
|
|
2411
|
+
const HOST_ROUTES = ROUTES["skill-history"];
|
|
2412
|
+
/**
|
|
2413
|
+
* A refusal the host reported, carrying its own sentence (the curator's, not ours) and the classified
|
|
2414
|
+
* four-state failure so the panel can render the shared failure block (G5/W7 S6b).
|
|
2415
|
+
*/
|
|
2416
|
+
var SkillHistoryRefusal = class extends Error {
|
|
2417
|
+
failure;
|
|
2418
|
+
constructor(failure) {
|
|
2419
|
+
super(failureMessage(failure));
|
|
2420
|
+
this.name = "SkillHistoryRefusal";
|
|
2421
|
+
this.failure = failure;
|
|
2422
|
+
}
|
|
2423
|
+
};
|
|
2424
|
+
/**
|
|
2425
|
+
* Build the panel's face over one fetch implementation.
|
|
2426
|
+
* @param fetchImpl - the fetch to use; a spec passes a stub.
|
|
2427
|
+
* @returns the three callbacks the component receives through its inject face.
|
|
2428
|
+
*/
|
|
2429
|
+
function createSkillHistoryApi(fetchImpl = fetch) {
|
|
2430
|
+
const request = async (path, init) => {
|
|
2431
|
+
const answer = await requestJson(fetchImpl, path, init);
|
|
2432
|
+
if (!answer.ok) throw new SkillHistoryRefusal(answer.failure);
|
|
2433
|
+
return answer.data;
|
|
2434
|
+
};
|
|
2435
|
+
return {
|
|
2436
|
+
skills: async () => await request(HOST_ROUTES.skills),
|
|
2437
|
+
versions: async (name) => await request(HOST_ROUTES.versions + "?name=" + encodeURIComponent(name)),
|
|
2438
|
+
diff: async (name, v) => await request(HOST_ROUTES.diff + "?name=" + encodeURIComponent(name) + "&v=" + String(v)),
|
|
2439
|
+
body: async (name, v) => await request(HOST_ROUTES.body + "?name=" + encodeURIComponent(name) + "&v=" + String(v)),
|
|
2440
|
+
undo: async (name, v, unitId) => {
|
|
2441
|
+
return (await request(HOST_ROUTES.undo, {
|
|
2442
|
+
method: "POST",
|
|
2443
|
+
headers: { "content-type": "application/json" },
|
|
2444
|
+
body: JSON.stringify(unitId !== void 0 && unitId !== "" ? {
|
|
2445
|
+
name,
|
|
2446
|
+
unitId
|
|
2447
|
+
} : v === void 0 ? { name } : {
|
|
2448
|
+
name,
|
|
2449
|
+
v
|
|
2450
|
+
})
|
|
2451
|
+
})).message;
|
|
2452
|
+
}
|
|
2453
|
+
};
|
|
2454
|
+
}
|
|
2455
|
+
//#endregion
|
|
2456
|
+
//#region src/panels/settings/api.ts
|
|
2457
|
+
/**
|
|
2458
|
+
* The approval routes this bundle calls, and the ONE place a request is made.
|
|
2459
|
+
*
|
|
2460
|
+
* Structural types on purpose: the browser half cannot import the host package at runtime, so the row
|
|
2461
|
+
* shape is declared by what the route hands over. Every answer — including a refusal — is parsed the
|
|
2462
|
+
* same way, because a refusal is a business answer the card shows as a sentence, not a transport error.
|
|
2463
|
+
* @module @lmzhen/dsh-evolution-ui/panels/settings/client/api
|
|
2464
|
+
*/
|
|
2465
|
+
/**
|
|
2466
|
+
* Build the approval face over one fetch implementation.
|
|
2467
|
+
* @param doFetch - the fetch to use (the platform's global in the browser, a fake in a spec).
|
|
2468
|
+
* @returns the three calls the pending card makes.
|
|
2469
|
+
*/
|
|
2470
|
+
function createApprovalApi(doFetch) {
|
|
2471
|
+
const post = async (path, id) => await requestJson(doFetch, path, {
|
|
2472
|
+
method: "POST",
|
|
2473
|
+
headers: { "content-type": "application/json" },
|
|
2474
|
+
body: JSON.stringify({ id })
|
|
2475
|
+
});
|
|
2476
|
+
return {
|
|
2477
|
+
pending: async (status) => await requestJson(doFetch, status === void 0 ? ROUTES.approval.pending : ROUTES.approval.pending + "?status=" + status),
|
|
2478
|
+
approve: async (id) => await post(ROUTES.approval.approve, id),
|
|
2479
|
+
reject: async (id) => await post(ROUTES.approval.reject, id),
|
|
2480
|
+
release: async (id) => await post(ROUTES.approval.release, id),
|
|
2481
|
+
preview: async (id) => await requestJson(doFetch, ROUTES.approval.preview + "?id=" + encodeURIComponent(id))
|
|
2482
|
+
};
|
|
2483
|
+
}
|
|
2484
|
+
//#endregion
|
|
2485
|
+
//#region src/panels/settings/pending-state.ts
|
|
2486
|
+
/** The state a card mounts in: still loading, nothing in flight, nothing said yet. */
|
|
2487
|
+
const INITIAL_PENDING_STATE = {
|
|
2488
|
+
view: { kind: "loading" },
|
|
2489
|
+
busy: [],
|
|
2490
|
+
notice: null,
|
|
2491
|
+
refreshing: false,
|
|
2492
|
+
open: null,
|
|
2493
|
+
preview: null
|
|
2494
|
+
};
|
|
2495
|
+
/** Open one row's preview: the row is now the open one and its answer is on the way. */
|
|
2496
|
+
function expanded(state, id) {
|
|
2497
|
+
return {
|
|
2498
|
+
...state,
|
|
2499
|
+
open: id,
|
|
2500
|
+
preview: { kind: "loading" }
|
|
2501
|
+
};
|
|
2502
|
+
}
|
|
2503
|
+
/** Close whatever preview is open. */
|
|
2504
|
+
function collapsed(state) {
|
|
2505
|
+
return {
|
|
2506
|
+
...state,
|
|
2507
|
+
open: null,
|
|
2508
|
+
preview: null
|
|
2509
|
+
};
|
|
2510
|
+
}
|
|
2511
|
+
/**
|
|
2512
|
+
* A preview answer arrived. An answer for a row the reader has already left is DROPPED: it would
|
|
2513
|
+
* otherwise paint one row's diff under another row's heading.
|
|
2514
|
+
*/
|
|
2515
|
+
function previewArrived(state, id, view) {
|
|
2516
|
+
return state.open === id ? {
|
|
2517
|
+
...state,
|
|
2518
|
+
preview: view
|
|
2519
|
+
} : state;
|
|
2520
|
+
}
|
|
2521
|
+
/** The first load, or a refresh after a decision: back to loading, keeping no stale rows. */
|
|
2522
|
+
function loading(state) {
|
|
2523
|
+
if (state.view.kind === "ready" || state.view.kind === "empty") return {
|
|
2524
|
+
...state,
|
|
2525
|
+
refreshing: true
|
|
2526
|
+
};
|
|
2527
|
+
return {
|
|
2528
|
+
...state,
|
|
2529
|
+
view: { kind: "loading" },
|
|
2530
|
+
refreshing: true
|
|
2531
|
+
};
|
|
2532
|
+
}
|
|
2533
|
+
/**
|
|
2534
|
+
* A list arrived. An empty list is its OWN state — the reader is told the window is clear rather than
|
|
2535
|
+
* being shown the same line a slow answer shows.
|
|
2536
|
+
*/
|
|
2537
|
+
function loaded(state, rows) {
|
|
2538
|
+
return {
|
|
2539
|
+
...state,
|
|
2540
|
+
refreshing: false,
|
|
2541
|
+
view: rows.length === 0 ? { kind: "empty" } : {
|
|
2542
|
+
kind: "ready",
|
|
2543
|
+
rows
|
|
2375
2544
|
}
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
}, PendingCard);
|
|
2387
|
-
const diagApi = createDiagnosticsApi((input, init) => fetch(input, init));
|
|
2388
|
-
yield seam.slots.register({
|
|
2389
|
-
name: CARD_SLOT,
|
|
2390
|
-
key: DIAGNOSTICS_CARD_KEY,
|
|
2391
|
-
inject: () => ({
|
|
2392
|
-
t,
|
|
2393
|
-
api: diagApi,
|
|
2394
|
-
fallback: FALLBACKS.diagnostics
|
|
2395
|
-
})
|
|
2396
|
-
}, DiagnosticsCard);
|
|
2397
|
-
});
|
|
2398
|
-
seam.slots.inject(ROW_CONFIG_SLOT, function* () {
|
|
2399
|
-
for (const seat of CLIENT_ROW_SEATS) for (const rowId of seat.rows) {
|
|
2400
|
-
const section = CLIENT_PARAM_SECTIONS.find((candidate) => candidate.namespace === rowId);
|
|
2401
|
-
if (section === void 0) continue;
|
|
2402
|
-
const face = () => ({
|
|
2403
|
-
namespace: rowId,
|
|
2404
|
-
fields: section.fields,
|
|
2405
|
-
t
|
|
2406
|
-
});
|
|
2407
|
-
yield seam.slots.register({
|
|
2408
|
-
name: ROW_CONFIG_SLOT,
|
|
2409
|
-
key: rowConfigKey(seat.bundle, rowId),
|
|
2410
|
-
inject: face
|
|
2411
|
-
}, RowConfigCard);
|
|
2545
|
+
};
|
|
2546
|
+
}
|
|
2547
|
+
/** The list could not be read: the classified failure stays on screen next to a retry control. */
|
|
2548
|
+
function failed(state, failure) {
|
|
2549
|
+
return {
|
|
2550
|
+
...state,
|
|
2551
|
+
refreshing: false,
|
|
2552
|
+
view: {
|
|
2553
|
+
kind: "failed",
|
|
2554
|
+
failure
|
|
2412
2555
|
}
|
|
2413
|
-
}
|
|
2556
|
+
};
|
|
2414
2557
|
}
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2558
|
+
/** Mark one record as having an action in flight (its buttons are disabled until it ends). */
|
|
2559
|
+
function beginAction(state, id) {
|
|
2560
|
+
return state.busy.includes(id) ? state : {
|
|
2561
|
+
...state,
|
|
2562
|
+
busy: [...state.busy, id]
|
|
2563
|
+
};
|
|
2420
2564
|
}
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2565
|
+
/** The action ended, whatever it answered. */
|
|
2566
|
+
function endAction(state, id) {
|
|
2567
|
+
return {
|
|
2568
|
+
...state,
|
|
2569
|
+
busy: state.busy.filter((candidate) => candidate !== id)
|
|
2570
|
+
};
|
|
2571
|
+
}
|
|
2572
|
+
/** One refusal or failure line under the list; `null` clears it (a later success does). */
|
|
2573
|
+
function noticed(state, message) {
|
|
2574
|
+
return {
|
|
2575
|
+
...state,
|
|
2576
|
+
notice: message
|
|
2430
2577
|
};
|
|
2431
2578
|
}
|
|
2579
|
+
//#endregion
|
|
2580
|
+
//#region src/panels/settings/PendingCard.ts
|
|
2581
|
+
/**
|
|
2582
|
+
* The "writes awaiting approval" card: the staged write window, with the two decisions on each row.
|
|
2583
|
+
*
|
|
2584
|
+
* It is a card in the SAME section and wears the SAME shell as the parameter cards (header with title,
|
|
2585
|
+
* count and chevron; body of field blocks; a button row per block), so nothing here invents a second
|
|
2586
|
+
* visual language. Two things differ on purpose: the data comes from the host's approval routes rather
|
|
2587
|
+
* than the settings transport, and its three states are distinct — "reading", "nothing staged" and "the
|
|
2588
|
+
* read failed" each say so in their own words (E7: merging the first two is what made an empty panel
|
|
2589
|
+
* look like lost data).
|
|
2590
|
+
*
|
|
2591
|
+
* Every decision re-reads the window instead of predicting the outcome: the host owns the state, and a
|
|
2592
|
+
* refusal is shown with the host's own sentence.
|
|
2593
|
+
* @module @lmzhen/dsh-evolution-ui/panels/settings/client/PendingCard
|
|
2594
|
+
*/
|
|
2595
|
+
/**
|
|
2596
|
+
* U-6: the registry's gate word, read out of a section's user layer. ONE definition, exported so the
|
|
2597
|
+
* mapping itself is judgeable: the entry does the wiring, this does the reading. Anything that is not
|
|
2598
|
+
* one of the two explicit words falls back to the shipped default (`auto`) — a document written by a
|
|
2599
|
+
* future version must not silently turn the confirmation ON, and a typo must not turn it off. The
|
|
2600
|
+
* independent review flagged that this reader had no case at all (P2-11).
|
|
2601
|
+
*/
|
|
2602
|
+
function confirmModeFrom(user) {
|
|
2603
|
+
const value = typeof user === "object" && user !== null ? user.approvalConfirm : void 0;
|
|
2604
|
+
return value === "ask" || value === "timeout" ? "ask" : "auto";
|
|
2605
|
+
}
|
|
2606
|
+
/** Which word one age bucket gets; an unknown bucket falls back to the count form. */
|
|
2607
|
+
const AGE_KEYS = Object.freeze({
|
|
2608
|
+
now: "approvalAgeNow",
|
|
2609
|
+
minutes: "approvalAgeMinutes",
|
|
2610
|
+
hours: "approvalAgeHours",
|
|
2611
|
+
days: "approvalAgeDays",
|
|
2612
|
+
months: "approvalAgeMonths"
|
|
2613
|
+
});
|
|
2614
|
+
/** Which word one staged kind gets. */
|
|
2615
|
+
/** Verbs the host can report; unknown verbs fall back to the raw word. */
|
|
2616
|
+
const VERB_KEYS = Object.freeze({
|
|
2617
|
+
add: "approvalVerbAdd",
|
|
2618
|
+
create: "approvalVerbCreate",
|
|
2619
|
+
update: "approvalVerbUpdate",
|
|
2620
|
+
replace: "approvalVerbUpdate",
|
|
2621
|
+
patch: "approvalVerbUpdate",
|
|
2622
|
+
remove: "approvalVerbRemove",
|
|
2623
|
+
delete: "approvalVerbDelete",
|
|
2624
|
+
archive: "approvalVerbArchive",
|
|
2625
|
+
restore: "approvalVerbRestore",
|
|
2626
|
+
merge: "approvalVerbMerge"
|
|
2627
|
+
});
|
|
2628
|
+
const KIND_KEYS = Object.freeze({
|
|
2629
|
+
memory: "approvalKindMemory",
|
|
2630
|
+
skill: "approvalKindSkill",
|
|
2631
|
+
capability: "approvalKindCapability"
|
|
2632
|
+
});
|
|
2432
2633
|
/**
|
|
2433
|
-
*
|
|
2434
|
-
* @param
|
|
2435
|
-
* @
|
|
2634
|
+
* Render the pending window.
|
|
2635
|
+
* @param props - the copy seat and the host calls.
|
|
2636
|
+
* @returns the card.
|
|
2436
2637
|
*/
|
|
2437
|
-
function
|
|
2438
|
-
const
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2638
|
+
function PendingCard(props) {
|
|
2639
|
+
const { t, api } = props;
|
|
2640
|
+
const translate = (key, values) => t(key, values);
|
|
2641
|
+
const confirmMode = props.confirmMode?.() ?? "auto";
|
|
2642
|
+
const [armed, setArmed] = (0, react.useState)(null);
|
|
2643
|
+
const [state, setState] = (0, react.useState)(INITIAL_PENDING_STATE);
|
|
2644
|
+
const [open, setOpen] = (0, react.useState)(true);
|
|
2645
|
+
const viewKind = state.view.kind;
|
|
2646
|
+
const rowCount = state.view.kind === "ready" ? state.view.rows.length : 0;
|
|
2647
|
+
(0, react.useEffect)(() => {
|
|
2648
|
+
if (viewKind === "empty") setOpen(false);
|
|
2649
|
+
else if (viewKind === "ready" && rowCount > 0) setOpen(true);
|
|
2650
|
+
}, [viewKind, rowCount]);
|
|
2651
|
+
const latest = (0, react.useRef)(state);
|
|
2652
|
+
const apply = (next) => {
|
|
2653
|
+
const value = next(latest.current);
|
|
2654
|
+
latest.current = value;
|
|
2655
|
+
setState(value);
|
|
2656
|
+
};
|
|
2657
|
+
/**
|
|
2658
|
+
* Read the window the card is about: the writes WAITING and the writes RUNNING.
|
|
2659
|
+
*
|
|
2660
|
+
* T4-05/A47: the route has always taken `?status=` — the CLI asks for both lists — but this card
|
|
2661
|
+
* asked for neither, so a record whose approve died between the claim and the decision showed as
|
|
2662
|
+
* "nothing is waiting for a decision" while `/evolution pending` and doctor listed it. The two
|
|
2663
|
+
* answers are ordered the way the CLI orders them (waiting first); either one failing fails the
|
|
2664
|
+
* read, because a half-list would read as a complete window.
|
|
2665
|
+
*/
|
|
2666
|
+
const readTicket = (0, react.useRef)(0);
|
|
2667
|
+
const load = async () => {
|
|
2668
|
+
apply(loading);
|
|
2669
|
+
const ticket = readTicket.current + 1;
|
|
2670
|
+
readTicket.current = ticket;
|
|
2671
|
+
const [waiting, running] = await Promise.all([api.pending(), api.pending("executing")]);
|
|
2672
|
+
if (readTicket.current !== ticket) return;
|
|
2673
|
+
if (!waiting.ok) {
|
|
2674
|
+
apply((current) => failed(current, waiting.failure));
|
|
2675
|
+
return;
|
|
2448
2676
|
}
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2677
|
+
if (!running.ok) {
|
|
2678
|
+
apply((current) => failed(current, running.failure));
|
|
2679
|
+
return;
|
|
2680
|
+
}
|
|
2681
|
+
apply((current) => loaded(current, [...waiting.data, ...running.data]));
|
|
2682
|
+
};
|
|
2683
|
+
(0, react.useEffect)(() => {
|
|
2684
|
+
load();
|
|
2685
|
+
const onFocus = () => {
|
|
2686
|
+
load();
|
|
2687
|
+
};
|
|
2688
|
+
window.addEventListener("focus", onFocus);
|
|
2689
|
+
return () => {
|
|
2690
|
+
window.removeEventListener("focus", onFocus);
|
|
2691
|
+
};
|
|
2692
|
+
}, []);
|
|
2693
|
+
const decide = (id, action) => {
|
|
2694
|
+
apply((current) => beginAction(current, id));
|
|
2695
|
+
(async () => {
|
|
2696
|
+
const answer = action === "approve" ? await api.approve(id) : action === "reject" ? await api.reject(id) : await api.release(id);
|
|
2697
|
+
if (!answer.ok) {
|
|
2698
|
+
apply((current) => noticed(endAction(current, id), failureText(answer.failure, translate)));
|
|
2699
|
+
return;
|
|
2700
|
+
}
|
|
2701
|
+
apply((current) => noticed(endAction(current, id), null));
|
|
2702
|
+
await load();
|
|
2703
|
+
})();
|
|
2704
|
+
};
|
|
2705
|
+
const age = (row) => {
|
|
2706
|
+
const key = AGE_KEYS[row.age.unit];
|
|
2707
|
+
if (key === void 0) return String(row.age.n);
|
|
2708
|
+
return t(key, { n: row.age.n });
|
|
2709
|
+
};
|
|
2710
|
+
/** Ask the host what this record would store; the answer lands only if the row is still open. */
|
|
2711
|
+
const loadPreview = async (id) => {
|
|
2712
|
+
const answer = await api.preview(id);
|
|
2713
|
+
const view = answer.ok ? answer.data.available ? {
|
|
2714
|
+
kind: "ready",
|
|
2715
|
+
answer: answer.data
|
|
2716
|
+
} : {
|
|
2717
|
+
kind: "unavailable",
|
|
2718
|
+
reason: answer.data.reason
|
|
2719
|
+
} : {
|
|
2720
|
+
kind: "failed",
|
|
2721
|
+
failure: answer.failure
|
|
2722
|
+
};
|
|
2723
|
+
apply((current) => previewArrived(current, id, view));
|
|
2724
|
+
};
|
|
2725
|
+
const togglePreview = (id) => {
|
|
2726
|
+
if (state.open === id) {
|
|
2727
|
+
apply(collapsed);
|
|
2728
|
+
return;
|
|
2729
|
+
}
|
|
2730
|
+
apply((current) => expanded(current, id));
|
|
2731
|
+
loadPreview(id);
|
|
2732
|
+
};
|
|
2733
|
+
/** The facts line, then the source view: the same shape the history panel's diff uses. */
|
|
2734
|
+
const preview = (view) => {
|
|
2735
|
+
if (view.kind === "loading") return (0, react.createElement)("p", {
|
|
2736
|
+
className: "evolution-param-note",
|
|
2737
|
+
role: "status"
|
|
2738
|
+
}, t("approvalPreviewReading"));
|
|
2739
|
+
if (view.kind === "failed") return (0, react.createElement)("p", {
|
|
2740
|
+
className: "evolution-param-error",
|
|
2741
|
+
role: "status"
|
|
2742
|
+
}, failureText(view.failure, translate));
|
|
2743
|
+
if (view.kind === "unavailable") return (0, react.createElement)("p", {
|
|
2744
|
+
className: "evolution-param-error",
|
|
2745
|
+
role: "status"
|
|
2746
|
+
}, t("approvalPreviewNone") + view.reason);
|
|
2747
|
+
const facts = [
|
|
2748
|
+
(0, react.createElement)("span", {
|
|
2749
|
+
className: "evolution-param-fact",
|
|
2750
|
+
key: "path"
|
|
2751
|
+
}, t("approvalPreviewPath", { path: view.answer.path })),
|
|
2752
|
+
(0, react.createElement)("span", {
|
|
2753
|
+
className: "evolution-param-fact",
|
|
2754
|
+
key: "added"
|
|
2755
|
+
}, t("approvalDiffAdded", { n: view.answer.linesAdded })),
|
|
2756
|
+
(0, react.createElement)("span", {
|
|
2757
|
+
className: "evolution-param-fact",
|
|
2758
|
+
key: "removed"
|
|
2759
|
+
}, t("approvalDiffRemoved", { n: view.answer.linesRemoved })),
|
|
2760
|
+
...view.answer.truncated ? [(0, react.createElement)("span", {
|
|
2761
|
+
className: "evolution-param-fact",
|
|
2762
|
+
key: "cut"
|
|
2763
|
+
}, t("approvalDiffCut"))] : []
|
|
2764
|
+
];
|
|
2765
|
+
const lines = [];
|
|
2766
|
+
for (const [hunkIndex, hunk] of view.answer.hunks.entries()) {
|
|
2767
|
+
for (const [index, line] of hunk.oldText.split("\n").entries()) {
|
|
2768
|
+
if (hunk.oldText === "" && line === "") continue;
|
|
2769
|
+
lines.push((0, react.createElement)("div", {
|
|
2770
|
+
key: "old-" + String(hunkIndex) + "-" + String(index),
|
|
2771
|
+
className: "evolution-param-line-del"
|
|
2772
|
+
}, "- " + line));
|
|
2773
|
+
}
|
|
2774
|
+
for (const [index, line] of hunk.newText.split("\n").entries()) {
|
|
2775
|
+
if (hunk.newText === "" && line === "") continue;
|
|
2776
|
+
lines.push((0, react.createElement)("div", {
|
|
2777
|
+
key: "new-" + String(hunkIndex) + "-" + String(index),
|
|
2778
|
+
className: "evolution-param-line-add"
|
|
2779
|
+
}, "+ " + line));
|
|
2780
|
+
}
|
|
2459
2781
|
}
|
|
2782
|
+
return (0, react.createElement)("div", null, (0, react.createElement)("p", { className: "evolution-param-facts" }, ...facts), (0, react.createElement)("pre", { className: "evolution-param-diff" }, ...lines.length === 0 ? ["±"] : lines));
|
|
2783
|
+
};
|
|
2784
|
+
const summaryLine = (entry) => entry.verb === void 0 || entry.target === void 0 ? entry.summary : t(VERB_KEYS[entry.verb] ?? "approvalVerbUpdate") + " " + entry.target + (entry.operationCount !== void 0 && entry.operationCount > 1 ? " ×" + String(entry.operationCount) : "");
|
|
2785
|
+
const row = (entry) => {
|
|
2786
|
+
if (entry.status === "executing") return (0, react.createElement)("div", {
|
|
2787
|
+
className: "evolution-param-field",
|
|
2788
|
+
key: entry.id
|
|
2789
|
+
}, (0, react.createElement)("div", { className: "evolution-param-label" }, (0, react.createElement)("span", { className: "evolution-param-source" }, t(KIND_KEYS[entry.kind])), (0, react.createElement)("span", {
|
|
2790
|
+
className: "evolution-param-value",
|
|
2791
|
+
title: entry.summary
|
|
2792
|
+
}, summaryLine(entry))), (0, react.createElement)("p", { className: "evolution-param-hint" }, age(entry)), (0, react.createElement)("p", {
|
|
2793
|
+
className: "evolution-param-note",
|
|
2794
|
+
role: "status"
|
|
2795
|
+
}, t("approvalExecuting")), entry.claimedBy === void 0 ? null : (0, react.createElement)("p", { className: "evolution-param-hint" }, entry.claimedBy), (0, react.createElement)("div", { className: "evolution-param-actions" }, (0, react.createElement)("button", {
|
|
2796
|
+
className: "evolution-param-button",
|
|
2797
|
+
type: "button",
|
|
2798
|
+
disabled: state.busy.includes(entry.id),
|
|
2799
|
+
onClick: () => {
|
|
2800
|
+
decide(entry.id, "release");
|
|
2801
|
+
}
|
|
2802
|
+
}, state.busy.includes(entry.id) ? t("approvalReleasing") : t("approvalRelease"))));
|
|
2803
|
+
const busy = state.busy.includes(entry.id);
|
|
2804
|
+
const button = (label, action, tone) => (0, react.createElement)("button", {
|
|
2805
|
+
className: "evolution-param-button",
|
|
2806
|
+
type: "button",
|
|
2807
|
+
disabled: busy,
|
|
2808
|
+
...tone === "primary" ? { "data-primary": "true" } : { "data-tone": tone },
|
|
2809
|
+
onClick: () => {
|
|
2810
|
+
if (action === "approve" && confirmMode === "ask" && armed !== entry.id) {
|
|
2811
|
+
setArmed(entry.id);
|
|
2812
|
+
return;
|
|
2813
|
+
}
|
|
2814
|
+
setArmed(null);
|
|
2815
|
+
decide(entry.id, action);
|
|
2816
|
+
}
|
|
2817
|
+
}, busy ? t(action === "approve" ? "approvalApproving" : "approvalRejecting") : t(label));
|
|
2818
|
+
return (0, react.createElement)("div", {
|
|
2819
|
+
className: "evolution-param-field",
|
|
2820
|
+
key: entry.id
|
|
2821
|
+
}, (0, react.createElement)("div", { className: "evolution-param-label" }, (0, react.createElement)("span", { className: "evolution-param-source" }, t(KIND_KEYS[entry.kind])), (0, react.createElement)("span", {
|
|
2822
|
+
className: "evolution-param-value",
|
|
2823
|
+
title: entry.summary
|
|
2824
|
+
}, summaryLine(entry))), (0, react.createElement)("p", { className: "evolution-param-hint" }, age(entry)), armed === entry.id ? (0, react.createElement)("p", {
|
|
2825
|
+
className: "evolution-param-note",
|
|
2826
|
+
role: "status"
|
|
2827
|
+
}, (0, react.createElement)("span", { className: "evolution-param-value" }, t("approvalConfirmNamed", { n: summaryLine(entry) })), (0, react.createElement)("button", {
|
|
2828
|
+
className: "evolution-param-button",
|
|
2829
|
+
type: "button",
|
|
2830
|
+
"data-confirm": "yes",
|
|
2831
|
+
"data-primary": "true",
|
|
2832
|
+
onClick: () => {
|
|
2833
|
+
setArmed(null);
|
|
2834
|
+
decide(entry.id, "approve");
|
|
2835
|
+
}
|
|
2836
|
+
}, t("approvalConfirmYes")), (0, react.createElement)("button", {
|
|
2837
|
+
className: "evolution-param-button",
|
|
2838
|
+
type: "button",
|
|
2839
|
+
"data-tone": "ghost",
|
|
2840
|
+
onClick: () => {
|
|
2841
|
+
setArmed(null);
|
|
2842
|
+
}
|
|
2843
|
+
}, t("approvalConfirmNo"))) : null, (0, react.createElement)("div", { className: "evolution-param-actions" }, button("approvalApprove", "approve", "primary"), button("approvalReject", "reject", "outline"), (0, react.createElement)("button", {
|
|
2844
|
+
className: "evolution-param-button",
|
|
2845
|
+
type: "button",
|
|
2846
|
+
"data-tone": "ghost",
|
|
2847
|
+
onClick: () => {
|
|
2848
|
+
togglePreview(entry.id);
|
|
2849
|
+
}
|
|
2850
|
+
}, state.open === entry.id ? t("approvalPreviewClose") : t("approvalPreview"))), state.open === entry.id && state.preview !== null ? preview(state.preview) : null);
|
|
2460
2851
|
};
|
|
2461
|
-
const
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
},
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
},
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
* @module @lmzhen/dsh-evolution-ui/panels/history/client
|
|
2486
|
-
*/
|
|
2487
|
-
/** Route paths: the generated mirror of the host's HOST_ROUTES (one declaration in core). */
|
|
2488
|
-
const HOST_ROUTES = ROUTES["skill-history"];
|
|
2489
|
-
/**
|
|
2490
|
-
* A refusal the host reported, carrying its own sentence (the curator's, not ours) and the classified
|
|
2491
|
-
* four-state failure so the panel can render the shared failure block (G5/W7 S6b).
|
|
2492
|
-
*/
|
|
2493
|
-
var SkillHistoryRefusal = class extends Error {
|
|
2494
|
-
failure;
|
|
2495
|
-
constructor(failure) {
|
|
2496
|
-
super(failureMessage(failure));
|
|
2497
|
-
this.name = "SkillHistoryRefusal";
|
|
2498
|
-
this.failure = failure;
|
|
2499
|
-
}
|
|
2500
|
-
};
|
|
2501
|
-
/**
|
|
2502
|
-
* Build the panel's face over one fetch implementation.
|
|
2503
|
-
* @param fetchImpl - the fetch to use; a spec passes a stub.
|
|
2504
|
-
* @returns the three callbacks the component receives through its inject face.
|
|
2505
|
-
*/
|
|
2506
|
-
function createSkillHistoryApi(fetchImpl = fetch) {
|
|
2507
|
-
const request = async (path, init) => {
|
|
2508
|
-
const answer = await requestJson(fetchImpl, path, init);
|
|
2509
|
-
if (!answer.ok) throw new SkillHistoryRefusal(answer.failure);
|
|
2510
|
-
return answer.data;
|
|
2852
|
+
const body = () => {
|
|
2853
|
+
if (state.view.kind === "loading") return (0, react.createElement)("p", {
|
|
2854
|
+
className: "evolution-param-note",
|
|
2855
|
+
role: "status"
|
|
2856
|
+
}, t("approvalLoading"));
|
|
2857
|
+
if (state.view.kind === "empty") return (0, react.createElement)("p", {
|
|
2858
|
+
className: "evolution-param-note",
|
|
2859
|
+
role: "status"
|
|
2860
|
+
}, t("approvalEmpty"));
|
|
2861
|
+
if (state.view.kind === "failed") return (0, react.createElement)("div", { className: "evolution-param-actions" }, FailureBlock({
|
|
2862
|
+
failure: state.view.failure,
|
|
2863
|
+
t: translate,
|
|
2864
|
+
fallback: props.fallback
|
|
2865
|
+
}), (0, react.createElement)("button", {
|
|
2866
|
+
className: "evolution-param-button",
|
|
2867
|
+
type: "button",
|
|
2868
|
+
onClick: () => {
|
|
2869
|
+
load();
|
|
2870
|
+
}
|
|
2871
|
+
}, t("approvalRetry")));
|
|
2872
|
+
return (0, react.createElement)("div", null, ...state.view.rows.map(row), state.notice === null ? null : (0, react.createElement)("p", {
|
|
2873
|
+
className: "evolution-param-error",
|
|
2874
|
+
role: "status"
|
|
2875
|
+
}, state.notice));
|
|
2511
2876
|
};
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2877
|
+
const rows = state.view.kind === "ready" ? state.view.rows : [];
|
|
2878
|
+
const waiting = rows.filter((entry) => entry.status !== "executing").length;
|
|
2879
|
+
const running = rows.length - waiting;
|
|
2880
|
+
return (0, react.createElement)("section", {
|
|
2881
|
+
className: "evolution-param-card",
|
|
2882
|
+
"data-confirm-mode": confirmMode
|
|
2883
|
+
}, (0, react.createElement)("button", {
|
|
2884
|
+
className: "evolution-param-head",
|
|
2885
|
+
type: "button",
|
|
2886
|
+
onClick: () => {
|
|
2887
|
+
setOpen(!open);
|
|
2888
|
+
},
|
|
2889
|
+
"aria-expanded": open
|
|
2890
|
+
}, (0, react.createElement)("span", { className: "evolution-param-card-title" }, t("approvalTitle")), state.view.kind === "empty" ? (0, react.createElement)("span", {
|
|
2891
|
+
className: "evolution-param-count",
|
|
2892
|
+
role: "status"
|
|
2893
|
+
}, t("approvalEmpty")) : null, waiting === 0 ? null : (0, react.createElement)("span", { className: "evolution-param-count" }, t("approvalCount", { n: waiting })), running === 0 ? null : (0, react.createElement)("span", { className: "evolution-param-count" }, t("approvalRunningCount", { n: running })), (0, react.createElement)("span", { className: "evolution-param-chevron" }, open ? "▲" : "▼")), (0, react.createElement)("div", { className: "evolution-param-actions" }, (0, react.createElement)("button", {
|
|
2894
|
+
className: "evolution-param-button",
|
|
2895
|
+
type: "button",
|
|
2896
|
+
disabled: state.refreshing,
|
|
2897
|
+
onClick: () => {
|
|
2898
|
+
load();
|
|
2529
2899
|
}
|
|
2530
|
-
};
|
|
2900
|
+
}, state.refreshing ? t("diagRefreshing") : t("approvalRefresh"))), open ? (0, react.createElement)("div", { className: "evolution-param-body" }, body()) : null);
|
|
2531
2901
|
}
|
|
2532
2902
|
//#endregion
|
|
2533
2903
|
//#region src/panels/history/client/messages.ts
|
|
@@ -2545,6 +2915,10 @@ window.__ModuleLoader__.load({
|
|
|
2545
2915
|
const zh = {
|
|
2546
2916
|
"entry.label": "技能历史",
|
|
2547
2917
|
title: "技能历史",
|
|
2918
|
+
"workbench.history": "技能历史",
|
|
2919
|
+
"workbench.pending": "待批",
|
|
2920
|
+
"workbench.merge": "合并",
|
|
2921
|
+
"workbench.health": "健康",
|
|
2548
2922
|
hint: "每次技能写入留下的内容版本。回退只改 SKILL.md 正文,标记、使用计数与策展状态不变。内容相同的版本会同时标「当前」。",
|
|
2549
2923
|
search: "按名字或描述筛选",
|
|
2550
2924
|
"search.none": "没有匹配的技能。",
|
|
@@ -2568,7 +2942,7 @@ window.__ModuleLoader__.load({
|
|
|
2568
2942
|
"failure.unmounted": "该功能没有挂载。",
|
|
2569
2943
|
"failure.server": "读取失败,可以重试。",
|
|
2570
2944
|
"fallback.hint": "可以改用命令:{command}",
|
|
2571
|
-
"undo.confirm": "
|
|
2945
|
+
"undo.confirm": "确认回退(会写入一版,可再回退)",
|
|
2572
2946
|
"undo.no": "取消",
|
|
2573
2947
|
current: "当前",
|
|
2574
2948
|
"versions.count": "{n} 个版本",
|
|
@@ -2589,6 +2963,7 @@ window.__ModuleLoader__.load({
|
|
|
2589
2963
|
"markdown.footnotes": "脚注",
|
|
2590
2964
|
"diff.hide": "收起差异",
|
|
2591
2965
|
"diff.loading": "读取差异…",
|
|
2966
|
+
"preview.loading": "读取预览…",
|
|
2592
2967
|
"diff.against": "与 v{n} 比较",
|
|
2593
2968
|
"diff.first": "这是第一版,整篇都是新增",
|
|
2594
2969
|
"diff.truncated": "差异过长,只显示前一部分",
|
|
@@ -2631,6 +3006,10 @@ window.__ModuleLoader__.load({
|
|
|
2631
3006
|
const en = {
|
|
2632
3007
|
"entry.label": "Skill history",
|
|
2633
3008
|
title: "Skill history",
|
|
3009
|
+
"workbench.history": "Skill history",
|
|
3010
|
+
"workbench.pending": "Pending",
|
|
3011
|
+
"workbench.merge": "Merge",
|
|
3012
|
+
"workbench.health": "Health",
|
|
2634
3013
|
hint: "The content versions every skill write leaves behind. Restoring changes the SKILL.md body only: markers, usage counts and curation state stay as they are. Versions holding the same content are all marked “current”.",
|
|
2635
3014
|
search: "Filter by name or description",
|
|
2636
3015
|
"search.none": "No skill matches.",
|
|
@@ -2654,7 +3033,7 @@ window.__ModuleLoader__.load({
|
|
|
2654
3033
|
"failure.unmounted": "This feature is not mounted.",
|
|
2655
3034
|
"failure.server": "The read failed; you can retry.",
|
|
2656
3035
|
"fallback.hint": "Command fallback: {command}",
|
|
2657
|
-
"undo.confirm": "Confirm restore",
|
|
3036
|
+
"undo.confirm": "Confirm restore (writes a new version; can be undone)",
|
|
2658
3037
|
"undo.no": "Cancel",
|
|
2659
3038
|
current: "Current",
|
|
2660
3039
|
"versions.count": "{n} versions",
|
|
@@ -2675,6 +3054,7 @@ window.__ModuleLoader__.load({
|
|
|
2675
3054
|
"markdown.footnotes": "Footnotes",
|
|
2676
3055
|
"diff.hide": "Hide diff",
|
|
2677
3056
|
"diff.loading": "Loading the diff…",
|
|
3057
|
+
"preview.loading": "Loading the preview…",
|
|
2678
3058
|
"diff.against": "compared with v{n}",
|
|
2679
3059
|
"diff.first": "first version: everything is new",
|
|
2680
3060
|
"diff.truncated": "the change is long; only its beginning is shown",
|
|
@@ -2713,19 +3093,6 @@ window.__ModuleLoader__.load({
|
|
|
2713
3093
|
error: "Could not load",
|
|
2714
3094
|
"error.hint": "The panel could not reach the host routes — usually a plugin/host version mismatch. Restart DeepSeek Harness and retry; if it persists, ask in the conversation."
|
|
2715
3095
|
};
|
|
2716
|
-
/**
|
|
2717
|
-
* Fill one TEMPLATE’s `{n}` slots. The seat resolves the key, this fills it, so the panel needs
|
|
2718
|
-
* only the seat plus this rule and never learns which language it is rendering.
|
|
2719
|
-
* @param template - the copy, with `{slot}` placeholders.
|
|
2720
|
-
* @param values - the substitutions, keyed by the slot name inside the braces.
|
|
2721
|
-
* @returns the filled copy.
|
|
2722
|
-
*/
|
|
2723
|
-
function fill(template, values) {
|
|
2724
|
-
return template.replace(/\{(\w+)\}/g, (match, slot) => {
|
|
2725
|
-
const value = values[slot];
|
|
2726
|
-
return value === void 0 ? match : String(value);
|
|
2727
|
-
});
|
|
2728
|
-
}
|
|
2729
3096
|
//#endregion
|
|
2730
3097
|
//#region src/panels/history/client/styles.ts
|
|
2731
3098
|
/**
|
|
@@ -2815,7 +3182,8 @@ window.__ModuleLoader__.load({
|
|
|
2815
3182
|
".evo-doc-add{border-left-color:var(--evo-tone-success)}",
|
|
2816
3183
|
".evo-doc-tag{position:absolute;left:var(--evo-space-8);top:var(--evo-space-8);color:var(--dsw-alias-label-tertiary)}",
|
|
2817
3184
|
".evo-doc-line-add{color:var(--evo-tone-success)}",
|
|
2818
|
-
".evo-doc-line-del{color:var(--evo-tone-danger)}"
|
|
3185
|
+
".evo-doc-line-del{color:var(--evo-tone-danger)}",
|
|
3186
|
+
"@media (max-width:720px){.evo-hist-root{flex-direction:column}.evo-hist-aside{flex:0 0 auto;width:100%;max-height:42%;border-right:0;border-bottom:var(--evo-hairline-width) solid var(--dsw-alias-border-l2)}.evo-hist-row{max-width:none}}"
|
|
2819
3187
|
].join("\n");
|
|
2820
3188
|
//#endregion
|
|
2821
3189
|
//#region src/panels/history/client/atoms.ts
|
|
@@ -3261,7 +3629,7 @@ window.__ModuleLoader__.load({
|
|
|
3261
3629
|
*/
|
|
3262
3630
|
function PreviewBody(props) {
|
|
3263
3631
|
const { face, body } = props;
|
|
3264
|
-
if (body.kind === "loading") return note({ children: face.t("
|
|
3632
|
+
if (body.kind === "loading") return note({ children: face.t("preview.loading") });
|
|
3265
3633
|
if (body.kind === "failed") return note({
|
|
3266
3634
|
error: true,
|
|
3267
3635
|
children: body.message
|
|
@@ -3377,7 +3745,9 @@ window.__ModuleLoader__.load({
|
|
|
3377
3745
|
const [diffs, setDiffs] = (0, react.useState)(/* @__PURE__ */ new Map());
|
|
3378
3746
|
const [bodies, setBodies] = (0, react.useState)(/* @__PURE__ */ new Map());
|
|
3379
3747
|
const readTicket = (0, react.useRef)(0);
|
|
3380
|
-
const
|
|
3748
|
+
const failureSource = (0, react.useRef)(void 0);
|
|
3749
|
+
const failed = (error, source = "read") => {
|
|
3750
|
+
failureSource.current = source;
|
|
3381
3751
|
setNoteError(true);
|
|
3382
3752
|
setNoteText(face.t("error.hint") + "\n" + face.t("error") + ": " + (error instanceof Error ? error.message : String(error)));
|
|
3383
3753
|
setFailure(error instanceof SkillHistoryRefusal ? error.failure : {
|
|
@@ -3389,11 +3759,22 @@ window.__ModuleLoader__.load({
|
|
|
3389
3759
|
setNoteError(false);
|
|
3390
3760
|
setNoteText(message);
|
|
3391
3761
|
};
|
|
3762
|
+
/** A successful read clears only the read failure; a write result is not erased by a later read. */
|
|
3763
|
+
const clearReadFailure = () => {
|
|
3764
|
+
if (failureSource.current !== "read") return;
|
|
3765
|
+
failureSource.current = void 0;
|
|
3766
|
+
setFailure(void 0);
|
|
3767
|
+
setNoteError(false);
|
|
3768
|
+
setNoteText(void 0);
|
|
3769
|
+
};
|
|
3392
3770
|
/** The sentence one failed read carries, in ONE place: every reader below reports the same way. */
|
|
3393
3771
|
const failedMessage = (error) => face.t("error") + ": " + (error instanceof Error ? error.message : String(error));
|
|
3394
3772
|
/** Read the skills again, keeping the sentence: a re-read is not an answer to anything. */
|
|
3395
3773
|
const reloadSkills = () => {
|
|
3396
|
-
face.loadSkills().then(
|
|
3774
|
+
face.loadSkills().then((next) => {
|
|
3775
|
+
setSkills(next);
|
|
3776
|
+
clearReadFailure();
|
|
3777
|
+
}).catch(failed).finally(() => {
|
|
3397
3778
|
setSkillsLoaded(true);
|
|
3398
3779
|
});
|
|
3399
3780
|
};
|
|
@@ -3414,7 +3795,10 @@ window.__ModuleLoader__.load({
|
|
|
3414
3795
|
setDiffs(/* @__PURE__ */ new Map());
|
|
3415
3796
|
setBodies(/* @__PURE__ */ new Map());
|
|
3416
3797
|
face.loadVersions(name).then((next) => {
|
|
3417
|
-
if (readTicket.current === ticket)
|
|
3798
|
+
if (readTicket.current === ticket) {
|
|
3799
|
+
setPayload(next);
|
|
3800
|
+
clearReadFailure();
|
|
3801
|
+
}
|
|
3418
3802
|
}).catch((error) => {
|
|
3419
3803
|
if (readTicket.current !== ticket) return;
|
|
3420
3804
|
setVersionsFailed(failedMessage(error));
|
|
@@ -3434,6 +3818,10 @@ window.__ModuleLoader__.load({
|
|
|
3434
3818
|
reloadSkills();
|
|
3435
3819
|
if (selected !== void 0) showVersions(selected);
|
|
3436
3820
|
};
|
|
3821
|
+
(0, react.useEffect)(() => {
|
|
3822
|
+
if (face.focusSkill === void 0 || face.focusSkill === "") return;
|
|
3823
|
+
showVersions(face.focusSkill);
|
|
3824
|
+
}, [face.focusSkill, face.focusRevision]);
|
|
3437
3825
|
const restore = (v, unitId) => {
|
|
3438
3826
|
if (selected === void 0) return;
|
|
3439
3827
|
if (pending !== v) {
|
|
@@ -3448,7 +3836,9 @@ window.__ModuleLoader__.load({
|
|
|
3448
3836
|
setNoteText(message + "\n" + face.t("error") + ": " + (error instanceof Error ? error.message : String(error)));
|
|
3449
3837
|
});
|
|
3450
3838
|
succeeded(message);
|
|
3451
|
-
}).catch(
|
|
3839
|
+
}).catch((error) => {
|
|
3840
|
+
failed(error, "write");
|
|
3841
|
+
});
|
|
3452
3842
|
};
|
|
3453
3843
|
/**
|
|
3454
3844
|
* Expand one row, or collapse it: one row is open at a time, and each kind keeps its own lazy read.
|
|
@@ -3591,61 +3981,289 @@ window.__ModuleLoader__.load({
|
|
|
3591
3981
|
return next;
|
|
3592
3982
|
}
|
|
3593
3983
|
//#endregion
|
|
3594
|
-
//#region src/panels/history/client/
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
strokeLinecap: "round",
|
|
3607
|
-
strokeLinejoin: "round"
|
|
3608
|
-
}, (0, react.createElement)("circle", {
|
|
3609
|
-
cx: "8",
|
|
3610
|
-
cy: "8",
|
|
3611
|
-
r: "5.75"
|
|
3612
|
-
}), (0, react.createElement)("path", { d: "M8 4.75V8l2.25 1.5" }));
|
|
3984
|
+
//#region src/panels/history/client/Workbench.ts
|
|
3985
|
+
/**
|
|
3986
|
+
* The family's one task page. G3 owns the history view; later groups add health
|
|
3987
|
+
* without creating another page or a second navigation registry.
|
|
3988
|
+
* @module @lmzhen/dsh-evolution-ui/panels/history/client
|
|
3989
|
+
*/
|
|
3990
|
+
/** Stable page id used by the sidebar row, the page slot key, and navigation params. */
|
|
3991
|
+
const WORKBENCH_ID = "evolution.workbench";
|
|
3992
|
+
/** Convert a navigation params value to a skill name only when it is a non-empty string. */
|
|
3993
|
+
function skillOf(params) {
|
|
3994
|
+
const value = params?.["skillName"];
|
|
3995
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
3613
3996
|
}
|
|
3614
3997
|
/**
|
|
3615
|
-
*
|
|
3616
|
-
*
|
|
3998
|
+
* Render the workbench page. The platform keeps it mounted; this component only
|
|
3999
|
+
* consumes a new explicit revision exactly once.
|
|
4000
|
+
* @param props - page owner props plus the injected history face.
|
|
4001
|
+
* @returns the page surface.
|
|
3617
4002
|
*/
|
|
3618
|
-
function
|
|
3619
|
-
const
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
4003
|
+
function WorkbenchPage(props) {
|
|
4004
|
+
const [selectedSkill, setSelectedSkill] = (0, react.useState)(void 0);
|
|
4005
|
+
const [view, setView] = (0, react.useState)("history");
|
|
4006
|
+
const lastRevision = (0, react.useRef)(void 0);
|
|
4007
|
+
(0, react.useEffect)(() => {
|
|
4008
|
+
if (props.revision === void 0 || props.revision === lastRevision.current) return;
|
|
4009
|
+
lastRevision.current = props.revision;
|
|
4010
|
+
const requested = props.params?.["view"];
|
|
4011
|
+
if (requested === "pending" || requested === "merge" || requested === "health") {
|
|
4012
|
+
setView(requested);
|
|
4013
|
+
return;
|
|
4014
|
+
}
|
|
4015
|
+
setView("history");
|
|
4016
|
+
const skill = skillOf(props.params);
|
|
4017
|
+
if (skill !== void 0) setSelectedSkill(skill);
|
|
4018
|
+
}, [props.params, props.revision]);
|
|
4019
|
+
const viewOrder = [
|
|
4020
|
+
"history",
|
|
4021
|
+
"pending",
|
|
4022
|
+
"merge",
|
|
4023
|
+
"health"
|
|
4024
|
+
];
|
|
4025
|
+
const onTabKey = (event, current) => {
|
|
4026
|
+
const delta = event.key === "ArrowRight" ? 1 : event.key === "ArrowLeft" ? -1 : 0;
|
|
4027
|
+
if (delta === 0 && event.key !== "Home" && event.key !== "End") return;
|
|
4028
|
+
event.preventDefault();
|
|
4029
|
+
const index = viewOrder.indexOf(current);
|
|
4030
|
+
const next = event.key === "Home" ? viewOrder[0] : event.key === "End" ? viewOrder[viewOrder.length - 1] : viewOrder[(index + delta + viewOrder.length) % viewOrder.length];
|
|
4031
|
+
setView(next);
|
|
4032
|
+
requestAnimationFrame(() => {
|
|
4033
|
+
document.getElementById("evo-workbench-tab-" + next)?.focus();
|
|
4034
|
+
});
|
|
4035
|
+
};
|
|
4036
|
+
const hasMerge = props.hasView?.("merge") ?? true;
|
|
4037
|
+
const hasHealth = props.hasView?.("health") ?? true;
|
|
4038
|
+
const focusRevision = props.revision === lastRevision.current ? props.revision : void 0;
|
|
4039
|
+
return (0, react.createElement)("section", {
|
|
4040
|
+
className: "evolution-page-surface evo-workbench",
|
|
4041
|
+
"data-page": view
|
|
4042
|
+
}, (0, react.createElement)("div", { className: "evo-workbench-head" }, (0, react.createElement)("h1", { className: "evo-workbench-title" }, props.t("entry.label")), (0, react.createElement)("div", {
|
|
4043
|
+
className: "evo-workbench-tabs",
|
|
4044
|
+
role: "tablist",
|
|
4045
|
+
"aria-label": props.t("entry.label")
|
|
4046
|
+
}, (0, react.createElement)("button", {
|
|
4047
|
+
id: "evo-workbench-tab-history",
|
|
4048
|
+
type: "button",
|
|
4049
|
+
role: "tab",
|
|
4050
|
+
tabIndex: view === "history" ? 0 : -1,
|
|
4051
|
+
"aria-selected": view === "history" ? "true" : "false",
|
|
4052
|
+
"aria-controls": "evo-workbench-panel",
|
|
4053
|
+
"data-active": view === "history" ? "true" : void 0,
|
|
4054
|
+
onClick: () => {
|
|
4055
|
+
setView("history");
|
|
3627
4056
|
},
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
4057
|
+
onKeyDown: (event) => {
|
|
4058
|
+
onTabKey(event, "history");
|
|
4059
|
+
}
|
|
4060
|
+
}, props.t("workbench.history")), (0, react.createElement)("button", {
|
|
4061
|
+
id: "evo-workbench-tab-pending",
|
|
4062
|
+
type: "button",
|
|
4063
|
+
role: "tab",
|
|
4064
|
+
tabIndex: view === "pending" ? 0 : -1,
|
|
4065
|
+
"aria-selected": view === "pending" ? "true" : "false",
|
|
4066
|
+
"aria-controls": "evo-workbench-panel",
|
|
4067
|
+
"data-active": view === "pending" ? "true" : void 0,
|
|
4068
|
+
onClick: () => {
|
|
4069
|
+
setView("pending");
|
|
3632
4070
|
},
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
4071
|
+
onKeyDown: (event) => {
|
|
4072
|
+
onTabKey(event, "pending");
|
|
4073
|
+
}
|
|
4074
|
+
}, props.t("workbench.pending")), !hasMerge ? null : (0, react.createElement)("button", {
|
|
4075
|
+
id: "evo-workbench-tab-merge",
|
|
4076
|
+
type: "button",
|
|
4077
|
+
role: "tab",
|
|
4078
|
+
tabIndex: view === "merge" ? 0 : -1,
|
|
4079
|
+
"aria-selected": view === "merge" ? "true" : "false",
|
|
4080
|
+
"aria-controls": "evo-workbench-panel",
|
|
4081
|
+
"data-active": view === "merge" ? "true" : void 0,
|
|
4082
|
+
onClick: () => {
|
|
4083
|
+
setView("merge");
|
|
4084
|
+
},
|
|
4085
|
+
onKeyDown: (event) => {
|
|
4086
|
+
onTabKey(event, "merge");
|
|
4087
|
+
}
|
|
4088
|
+
}, props.t("workbench.merge")), !hasHealth ? null : (0, react.createElement)("button", {
|
|
4089
|
+
id: "evo-workbench-tab-health",
|
|
4090
|
+
type: "button",
|
|
4091
|
+
role: "tab",
|
|
4092
|
+
tabIndex: view === "health" ? 0 : -1,
|
|
4093
|
+
"aria-selected": view === "health" ? "true" : "false",
|
|
4094
|
+
"aria-controls": "evo-workbench-panel",
|
|
4095
|
+
"data-active": view === "health" ? "true" : void 0,
|
|
4096
|
+
onClick: () => {
|
|
4097
|
+
setView("health");
|
|
4098
|
+
},
|
|
4099
|
+
onKeyDown: (event) => {
|
|
4100
|
+
onTabKey(event, "health");
|
|
4101
|
+
}
|
|
4102
|
+
}, props.t("workbench.health")))), (0, react.createElement)("div", {
|
|
4103
|
+
className: "evo-workbench-body",
|
|
4104
|
+
id: "evo-workbench-panel",
|
|
4105
|
+
role: "tabpanel"
|
|
4106
|
+
}, view === "history" ? (0, react.createElement)(SkillHistoryPanel, {
|
|
4107
|
+
...props.history,
|
|
4108
|
+
focusSkill: selectedSkill,
|
|
4109
|
+
focusRevision
|
|
4110
|
+
}) : view === "pending" ? (0, react.createElement)(PendingCard, props.pending) : null, (0, react.createElement)("div", {
|
|
4111
|
+
hidden: view !== "merge" && view !== "health",
|
|
4112
|
+
className: "evo-workbench-feature"
|
|
4113
|
+
}, props.renderSlot?.("evolution.workbench.view", { activeView: view }) ?? null)));
|
|
4114
|
+
}
|
|
4115
|
+
/**
|
|
4116
|
+
* Render the sidebar entry that opens the workbench.
|
|
4117
|
+
* @param props - active page id, opener, and copy.
|
|
4118
|
+
* @returns the row button.
|
|
4119
|
+
*/
|
|
4120
|
+
function WorkbenchNav(props) {
|
|
4121
|
+
const active = props.activePageId === WORKBENCH_ID;
|
|
4122
|
+
return (0, react.createElement)("button", {
|
|
4123
|
+
type: "button",
|
|
4124
|
+
className: "evo-workbench-nav",
|
|
4125
|
+
"data-active": active ? "true" : void 0,
|
|
4126
|
+
"aria-current": active ? "page" : void 0,
|
|
4127
|
+
onClick: () => {
|
|
4128
|
+
props.openPage(WORKBENCH_ID);
|
|
4129
|
+
}
|
|
4130
|
+
}, props.t("entry.label"));
|
|
4131
|
+
}
|
|
4132
|
+
/** Workbench shell and sidebar row; colours come from the platform tokens. */
|
|
4133
|
+
const WORKBENCH_CSS = [
|
|
4134
|
+
tokenVars(".evo-workbench"),
|
|
4135
|
+
".evo-workbench{display:flex;flex-direction:column;height:100%;min-height:0;min-width:0}",
|
|
4136
|
+
".evo-workbench-head{display:flex;align-items:center;justify-content:space-between;gap:var(--evo-space-16);padding:var(--evo-space-12) var(--evo-space-16);border-bottom:var(--evo-hairline-width) solid var(--dsw-alias-border-l2)}",
|
|
4137
|
+
".evo-workbench-tabs{display:flex;gap:var(--evo-space-4)}",
|
|
4138
|
+
".evo-workbench-tabs button{padding:var(--evo-space-4) var(--evo-space-8);border:0;border-radius:var(--evo-radius-control);background:transparent;color:var(--dsw-alias-label-tertiary);font:inherit;font-size:var(--evo-type-sm);cursor:pointer}",
|
|
4139
|
+
".evo-workbench-tabs button[data-active=\"true\"]{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}",
|
|
4140
|
+
".evo-workbench-tabs button:focus-visible{outline:var(--evo-focus-ring);outline-offset:var(--evo-focus-offset)}",
|
|
4141
|
+
".evo-workbench-title{margin:0;font-size:var(--evo-type-lg);font-weight:600;line-height:var(--evo-leading-base)}",
|
|
4142
|
+
".evo-workbench-body{display:flex;flex:1;min-height:0;min-width:0;overflow:hidden}",
|
|
4143
|
+
".evo-workbench-nav{display:flex;width:100%;align-items:center;gap:var(--evo-space-8);padding:var(--evo-space-6) var(--evo-space-12);border:0;border-radius:var(--evo-radius-control);background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:var(--evo-type-sm);text-align:left;cursor:pointer}",
|
|
4144
|
+
".evo-workbench-nav:hover,.evo-workbench-nav[data-active=\"true\"]{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}",
|
|
4145
|
+
".evo-workbench-nav:focus-visible{outline:var(--evo-focus-ring);outline-offset:var(--evo-focus-offset)}",
|
|
4146
|
+
"@media (max-width:720px){.evo-workbench-head{flex-wrap:wrap}.evo-workbench-tabs{flex-wrap:wrap}}"
|
|
4147
|
+
].join("");
|
|
4148
|
+
//#endregion
|
|
4149
|
+
//#region src/panels/history/client/index.ts
|
|
4150
|
+
function injectStyles(documentRef, css) {
|
|
4151
|
+
if (documentRef === void 0) return () => {};
|
|
4152
|
+
documentRef.querySelector("style[data-plugin-css=\"@lmzhen/dsh-evolution-ui/panel.css\"]")?.remove();
|
|
4153
|
+
const tag = documentRef.createElement("style");
|
|
4154
|
+
tag.setAttribute("data-plugin-css", CSS_TAG_ID);
|
|
4155
|
+
tag.textContent = css;
|
|
4156
|
+
documentRef.head.appendChild(tag);
|
|
4157
|
+
return () => {
|
|
4158
|
+
tag.remove();
|
|
4159
|
+
};
|
|
4160
|
+
}
|
|
4161
|
+
/**
|
|
4162
|
+
* Register the locale namespace, the workbench page, and its sidebar entry.
|
|
4163
|
+
* @param ctx - client cordis context.
|
|
4164
|
+
*/
|
|
4165
|
+
function apply$1(ctx) {
|
|
4166
|
+
const seam = ctx;
|
|
4167
|
+
const api = createSkillHistoryApi();
|
|
4168
|
+
let registered = false;
|
|
4169
|
+
ctx.effect(() => {
|
|
4170
|
+
try {
|
|
4171
|
+
const dispose = seam.locale.register(NS, {
|
|
4172
|
+
zh,
|
|
4173
|
+
en
|
|
4174
|
+
});
|
|
4175
|
+
registered = true;
|
|
4176
|
+
return dispose;
|
|
4177
|
+
} catch (error) {
|
|
4178
|
+
ctx.logger.warn("evolution-skill-history: the locale seat refused the dictionary (" + String(error) + "); the workbench falls back to the bundle copy");
|
|
4179
|
+
return () => {};
|
|
4180
|
+
}
|
|
4181
|
+
}, "evolution-skill-history: locale");
|
|
4182
|
+
ctx.effect(() => injectStyles(typeof document === "undefined" ? void 0 : document, CSS + WORKBENCH_CSS), "evolution-skill-history: styles");
|
|
4183
|
+
const fallback = zh;
|
|
4184
|
+
const lookup = (key) => {
|
|
4185
|
+
const own = fallback[key] ?? key;
|
|
4186
|
+
if (!registered) return own;
|
|
4187
|
+
try {
|
|
4188
|
+
return seam.locale.bind(NS)(key);
|
|
4189
|
+
} catch {
|
|
4190
|
+
return own;
|
|
4191
|
+
}
|
|
4192
|
+
};
|
|
4193
|
+
const t = (key, values) => {
|
|
4194
|
+
const template = lookup(key);
|
|
4195
|
+
if (values === void 0) return template;
|
|
4196
|
+
return template.replace(/\{(\w+)\}/g, (match, slot) => values[slot] === void 0 ? match : String(values[slot]));
|
|
4197
|
+
};
|
|
4198
|
+
const historyFace = () => ({
|
|
4199
|
+
t,
|
|
4200
|
+
format: (key, values) => t(key, values),
|
|
4201
|
+
loadSkills: api.skills,
|
|
4202
|
+
loadVersions: api.versions,
|
|
4203
|
+
loadDiff: api.diff,
|
|
4204
|
+
loadBody: api.body,
|
|
4205
|
+
undo: api.undo,
|
|
4206
|
+
markdownWords: {
|
|
4207
|
+
copy: t("markdown.copy"),
|
|
4208
|
+
copied: t("markdown.copied"),
|
|
4209
|
+
footnotes: t("markdown.footnotes")
|
|
4210
|
+
}
|
|
4211
|
+
});
|
|
4212
|
+
const pendingT = (key, values) => {
|
|
4213
|
+
let template;
|
|
4214
|
+
try {
|
|
4215
|
+
template = seam.locale.bind(NS$1)(key);
|
|
4216
|
+
} catch {
|
|
4217
|
+
template = message(key);
|
|
4218
|
+
}
|
|
4219
|
+
return values === void 0 ? template : fill(template, values);
|
|
4220
|
+
};
|
|
4221
|
+
const settingsSeat = createSettingsSeat(ctx);
|
|
4222
|
+
let approvalConfirmMode = "ask";
|
|
4223
|
+
const approvalForm = settingsSeat.get("evolution-approval");
|
|
4224
|
+
const stopApprovalWatch = approvalForm?.subscribe(() => {
|
|
4225
|
+
approvalConfirmMode = confirmModeFrom(approvalForm.getSnapshot().user);
|
|
4226
|
+
});
|
|
4227
|
+
if (stopApprovalWatch !== void 0) ctx.effect(() => stopApprovalWatch, "evolution-history: approval confirm mode");
|
|
4228
|
+
const confirmMode = () => approvalConfirmMode;
|
|
4229
|
+
const hasView = (view) => {
|
|
4230
|
+
try {
|
|
4231
|
+
return (seam.slots.entries?.("evolution.workbench.view") ?? []).some((entry) => entry.options?.id === (view === "merge" ? "merge" : "skill-health"));
|
|
4232
|
+
} catch {
|
|
4233
|
+
return false;
|
|
4234
|
+
}
|
|
4235
|
+
};
|
|
4236
|
+
const pendingFace = () => ({
|
|
4237
|
+
t: pendingT,
|
|
4238
|
+
api: createApprovalApi((input, init) => fetch(input, init)),
|
|
4239
|
+
confirmMode,
|
|
4240
|
+
fallback: FALLBACKS.approval
|
|
4241
|
+
});
|
|
4242
|
+
const label = () => t("entry.label");
|
|
4243
|
+
seam.slots.inject("sidebar.pages", function* () {
|
|
4244
|
+
yield seam.slots.register({
|
|
4245
|
+
name: "sidebar.pages",
|
|
4246
|
+
id: WORKBENCH_ID,
|
|
4247
|
+
order: 35,
|
|
4248
|
+
label,
|
|
4249
|
+
inject: () => ({ t })
|
|
4250
|
+
}, WorkbenchNav);
|
|
4251
|
+
});
|
|
4252
|
+
seam.slots.inject("layout.page", function* () {
|
|
4253
|
+
yield seam.slots.register({
|
|
4254
|
+
name: "layout.page",
|
|
4255
|
+
key: WORKBENCH_ID,
|
|
4256
|
+
children: { "evolution.workbench.view": {
|
|
4257
|
+
kind: "list",
|
|
4258
|
+
scope: "root"
|
|
4259
|
+
} },
|
|
4260
|
+
inject: () => ({
|
|
4261
|
+
t,
|
|
4262
|
+
history: historyFace(),
|
|
4263
|
+
pending: pendingFace(),
|
|
4264
|
+
hasView
|
|
4265
|
+
})
|
|
4266
|
+
}, WorkbenchPage);
|
|
3649
4267
|
});
|
|
3650
4268
|
}
|
|
3651
4269
|
//#endregion
|