@lmzhen/dsh-evolution-commands 0.26.15 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.js +577 -66
- package/lib/types/command-fallback.d.ts +27 -0
- package/lib/types/diagnostics-route.d.ts +9 -0
- package/lib/types/doctor-view.d.ts +98 -0
- package/lib/types/doctor.d.ts +14 -6
- package/lib/types/registry.d.ts +10 -1
- package/package.json +13 -11
package/lib/index.js
CHANGED
|
@@ -1,13 +1,470 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
+
import { DEFAULT_SKILL_LIMITS, DIAGNOSTICS_FACT_KEYS, DIAGNOSTICS_RUNTIME_KEYS, DIAGNOSTICS_VIEW_SCHEMA_VERSION, EVOLUTION_PRESET_IDS, MAINTAINER_PRESET_ID, MAX_TIMER_DELAY_MS, PARAM_EXPOSURE, PARAM_GROUP_LABELS, PLATFORM_LINE, REVIEWER_PRESET_ID, absentHostServices, appendEvolutionEvent, applyNamespaceMigration, asRecord, assertSkillsRootAliasRetired, atomicWriteFiles, authorityFrom, buildLearnPrompt, canonicalWriteId, clampedNumber, classifyNamespaceMigration, classifySessionEvent, createSubagentChannel, elapsedSince, errorText, eventsFile, evolutionHome, evolutionRoot, hostSurface, isDeprecatedParamId, isPresent, isRecord, isUnknown, liveBool, newRunRegistry, newSkillLibrary, paramSettingsId, partitionVersions, planNamespaceMigration, policyStageLimits, probeMtime, probeText, reportsSweepLockTarget, resolveParamId, resolveRootConfig, scopedPackageName, scopedProbeReport, sweepReports, transactIo } from "@lmzhen/dsh-evolution-core";
|
|
3
|
+
import { HOST_ROUTES, json, methodNotAllowed, registerHostRoutes } from "@lmzhen/dsh-evolution-core/http-routes";
|
|
4
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
2
7
|
import z from "@deepseek-ai/schemastery";
|
|
3
8
|
import { effectiveSessionPolicy } from "@lmzhen/dsh-evolution-approval";
|
|
4
9
|
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
5
|
-
import {
|
|
10
|
+
import { composePresetEntry, mergePresetRow, presetPatchText, presetRowBlock, presetRowBody, presetRowId, resolveBasePresetPatch } from "@lmzhen/dsh-evolution-core/composition";
|
|
6
11
|
import { buildEnrichment, buildMaintainFacts, enrichmentSnapshotOptions, runMaintain, snapshotFromLibrary } from "@lmzhen/dsh-evolution-maintenance";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
//#region lib/types/command-fallback.js
|
|
13
|
+
/**
|
|
14
|
+
* G6/W8: the command-face fallback for every Host data-plane surface.
|
|
15
|
+
*
|
|
16
|
+
* The browser panels depend on the platform's `connection` carrier (exact Fetch routes). When that
|
|
17
|
+
* carrier is absent (headless host, E-325) or a panel answer is missing, the operator is owed the
|
|
18
|
+
* equivalent command — the SAME use case, not a different implementation. This table is the ONE
|
|
19
|
+
* home of those pairs; the INSTALL runbook and the client `fallbacks.generated.ts` are generated
|
|
20
|
+
* from it (W7), and a spec pins its key set to HOST_ROUTES' four surfaces.
|
|
21
|
+
*
|
|
22
|
+
* Not exported from the package: this is a command-side contract, not a plugin API (W8 §2).
|
|
23
|
+
* @module @lmzhen/dsh-evolution-commands/command-fallback
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* One entry per Host surface. The command column names the same service methods the routes call,
|
|
27
|
+
* so the answers (and refusal sentences) match the panel.
|
|
28
|
+
*/
|
|
29
|
+
const COMMAND_FALLBACKS = {
|
|
30
|
+
approval: { command: "/evolution pending … /evolution approve <id> … /evolution reject <id> … /evolution release <id>" },
|
|
31
|
+
"skill-history": { command: "/evolution restore <name> [v](查看:/evolution skills history <name>)" },
|
|
32
|
+
"skill-health": { command: "/evolution skills refresh(健康行:/evolution doctor)" },
|
|
33
|
+
diagnostics: {
|
|
34
|
+
command: "/evolution doctor",
|
|
35
|
+
platformCli: "dsh --json /evolution doctor"
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region lib/types/doctor-view.js
|
|
40
|
+
const FACT_HOMES = {
|
|
41
|
+
"platform-line": "README.md(兼容线声明)",
|
|
42
|
+
"family-version": "README.md(家族版本)",
|
|
43
|
+
"install-form": "packages/INSTALL.md(安装形态与验证状态)",
|
|
44
|
+
"preset-ids": "evolution-core/README.md(preset id 冻结表)",
|
|
45
|
+
"readonly-contract": "evolution-review/README.md(只读契约)",
|
|
46
|
+
"upgrade-order": "packages/INSTALL.md(升级 runbook)",
|
|
47
|
+
"fallback-map": "packages/INSTALL.md(命令兜底表)"
|
|
48
|
+
};
|
|
49
|
+
const FACT_LABELS = {
|
|
50
|
+
"platform-line": "平台兼容线",
|
|
51
|
+
"family-version": "家族版本",
|
|
52
|
+
"install-form": "安装形态",
|
|
53
|
+
"preset-ids": "受限 preset id",
|
|
54
|
+
"readonly-contract": "只读契约",
|
|
55
|
+
"upgrade-order": "升级顺序",
|
|
56
|
+
"fallback-map": "命令兜底"
|
|
57
|
+
};
|
|
58
|
+
const RUNTIME_LABELS = {
|
|
59
|
+
"subagent-channel": "子代理通道",
|
|
60
|
+
"restricted-preset": "受限 preset",
|
|
61
|
+
"host-surface": "Host 数据面",
|
|
62
|
+
"session-fold": "会话事件 fold 单入口",
|
|
63
|
+
"platform-services": "平台服务探测",
|
|
64
|
+
runs: "运行登记",
|
|
65
|
+
"platform-pairing": "平台/家族配对",
|
|
66
|
+
"install-form-detected": "实际安装形态"
|
|
67
|
+
};
|
|
68
|
+
function factsOf(inputs) {
|
|
69
|
+
const values = {
|
|
70
|
+
"platform-line": inputs.platformLine.version + "(floor " + inputs.platformLine.floor + ")",
|
|
71
|
+
"family-version": inputs.familyVersion,
|
|
72
|
+
"install-form": inputs.form.id + "(" + inputs.form.confidence + ";证据:" + inputs.form.evidence.join("、") + ")",
|
|
73
|
+
"preset-ids": REVIEWER_PRESET_ID + " / " + MAINTAINER_PRESET_ID + "(家族 preset 集合:" + EVOLUTION_PRESET_IDS.join(", ") + ")",
|
|
74
|
+
"readonly-contract": REVIEWER_PRESET_ID + " 的工具集由 restrictor allow 集合决定;维护行同构(" + MAINTAINER_PRESET_ID + ")",
|
|
75
|
+
"upgrade-order": "先升平台 → 再升家族 → 最后发布(见 INSTALL runbook)",
|
|
76
|
+
"fallback-map": Object.keys(COMMAND_FALLBACKS).join("、") + "(每面的命令见 INSTALL 命令兜底表)"
|
|
77
|
+
};
|
|
78
|
+
return DIAGNOSTICS_FACT_KEYS.map((key) => ({
|
|
79
|
+
key,
|
|
80
|
+
label: FACT_LABELS[key] ?? key,
|
|
81
|
+
value: values[key] ?? "unknown",
|
|
82
|
+
home: FACT_HOMES[key] ?? "README.md"
|
|
83
|
+
})).sort((a, b) => a.key.localeCompare(b.key));
|
|
84
|
+
}
|
|
85
|
+
function channelRow(channels) {
|
|
86
|
+
const unavailable = [channels.review, channels.maintenance].filter((state) => state.kind === "unavailable");
|
|
87
|
+
if (unavailable.length === 0) return {
|
|
88
|
+
key: "subagent-channel",
|
|
89
|
+
label: RUNTIME_LABELS["subagent-channel"] ?? "子代理通道",
|
|
90
|
+
status: "ok",
|
|
91
|
+
detail: "reviewer/maintainer 通道均可用(spawn provider + agentPreset 能力)"
|
|
92
|
+
};
|
|
93
|
+
const reasons = unavailable.map((state) => state.code + ": " + state.reason);
|
|
94
|
+
return {
|
|
95
|
+
key: "subagent-channel",
|
|
96
|
+
label: RUNTIME_LABELS["subagent-channel"] ?? "子代理通道",
|
|
97
|
+
status: "warn",
|
|
98
|
+
detail: reasons.join(";"),
|
|
99
|
+
remedy: "显式子代理运行会标记为不可用(不会静默退回 inject);运行 /evolution doctor 查看修复指引。"
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function presetRow(presets) {
|
|
103
|
+
const entries = [presets.reviewer, presets.maintainer];
|
|
104
|
+
if (entries.every((entry) => entry === void 0)) return {
|
|
105
|
+
key: "restricted-preset",
|
|
106
|
+
label: RUNTIME_LABELS["restricted-preset"] ?? "受限 preset",
|
|
107
|
+
status: "unknown",
|
|
108
|
+
detail: "agentPresets 未挂载或未暴露 list(),无法探测受限 preset。",
|
|
109
|
+
remedy: "挂载 agentPresets(随平台 Web 组合)后运行 /evolution doctor。"
|
|
110
|
+
};
|
|
111
|
+
const broken = entries.filter((entry) => entry !== void 0 && entry.broken !== void 0);
|
|
112
|
+
const missing = entries.filter((entry) => entry === void 0 || !entry.registered);
|
|
113
|
+
if (broken.length > 0) return {
|
|
114
|
+
key: "restricted-preset",
|
|
115
|
+
label: RUNTIME_LABELS["restricted-preset"] ?? "受限 preset",
|
|
116
|
+
status: "fail",
|
|
117
|
+
detail: errorText("e-324-restricted-preset-unavailable", { a1: broken.map((entry) => entry.broken ?? "").join(";") }),
|
|
118
|
+
remedy: "重载插件行使 ensureRestrictedPreset 重新注册;运行 /evolution doctor 查看 preset id 与 allow 集合。"
|
|
119
|
+
};
|
|
120
|
+
if (missing.length > 0) return {
|
|
121
|
+
key: "restricted-preset",
|
|
122
|
+
label: RUNTIME_LABELS["restricted-preset"] ?? "受限 preset",
|
|
123
|
+
status: "warn",
|
|
124
|
+
detail: "受限 preset 尚未注册(首次显式子代理运行时创建)。",
|
|
125
|
+
remedy: "运行一次审阅/维护,或在挂载后运行 /evolution doctor。"
|
|
126
|
+
};
|
|
127
|
+
return {
|
|
128
|
+
key: "restricted-preset",
|
|
129
|
+
label: RUNTIME_LABELS["restricted-preset"] ?? "受限 preset",
|
|
130
|
+
status: "ok",
|
|
131
|
+
detail: REVIEWER_PRESET_ID + " / " + MAINTAINER_PRESET_ID + " 均已注册"
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function hostRow(host, count) {
|
|
135
|
+
if (!host.mounted) {
|
|
136
|
+
const fallback = COMMAND_FALLBACKS.approval.command.split(" … ")[0] ?? "";
|
|
137
|
+
return {
|
|
138
|
+
key: "host-surface",
|
|
139
|
+
label: RUNTIME_LABELS["host-surface"] ?? "Host 数据面",
|
|
140
|
+
status: "warn",
|
|
141
|
+
detail: errorText("e-325-host-data-plane-unavailable") + "(" + (host.reason ?? "unknown") + ")",
|
|
142
|
+
remedy: "改用命令面完成同等操作:" + fallback + " 等;运行 /evolution doctor 查看已挂载服务。"
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
const remote = host.deploymentAllowsRemote === true ? "允许远程权限(平台准入策略原样报告)" : "仅本机权限(平台准入策略原样报告)";
|
|
146
|
+
const mounted = count === void 0 ? "已挂载" : "已挂载 " + String(count.mounted) + "/" + String(count.total);
|
|
147
|
+
return {
|
|
148
|
+
key: "host-surface",
|
|
149
|
+
label: RUNTIME_LABELS["host-surface"] ?? "Host 数据面",
|
|
150
|
+
status: "ok",
|
|
151
|
+
detail: mounted + ";" + remote
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function pairingRow(inputs) {
|
|
155
|
+
switch (inputs.platformPairing ?? "unknown") {
|
|
156
|
+
case "in-window": return {
|
|
157
|
+
key: "platform-pairing",
|
|
158
|
+
label: RUNTIME_LABELS["platform-pairing"] ?? "平台/家族配对",
|
|
159
|
+
status: "ok",
|
|
160
|
+
detail: "宿主版本 " + String(inputs.hostVersion) + " 在兼容窗口 " + inputs.platformLine.version + " 内"
|
|
161
|
+
};
|
|
162
|
+
case "host-newer": return {
|
|
163
|
+
key: "platform-pairing",
|
|
164
|
+
label: RUNTIME_LABELS["platform-pairing"] ?? "平台/家族配对",
|
|
165
|
+
status: "warn",
|
|
166
|
+
detail: "宿主 patch 线已更新(" + String(inputs.hostVersion) + ",家族声明 " + inputs.platformLine.version + ")。",
|
|
167
|
+
remedy: "按升级 runbook 先升平台线再升家族。"
|
|
168
|
+
};
|
|
169
|
+
case "out-of-window": return {
|
|
170
|
+
key: "platform-pairing",
|
|
171
|
+
label: RUNTIME_LABELS["platform-pairing"] ?? "平台/家族配对",
|
|
172
|
+
status: "fail",
|
|
173
|
+
detail: "宿主版本 " + String(inputs.hostVersion) + " 落在兼容窗口(floor " + inputs.platformLine.floor + ")外。",
|
|
174
|
+
remedy: "先升/降平台到兼容窗口,再重载家族。"
|
|
175
|
+
};
|
|
176
|
+
case "unknown": return {
|
|
177
|
+
key: "platform-pairing",
|
|
178
|
+
label: RUNTIME_LABELS["platform-pairing"] ?? "平台/家族配对",
|
|
179
|
+
status: "unknown",
|
|
180
|
+
detail: inputs.hostVersion === void 0 ? "读不到宿主版本。" : "无法判定版本窗口。",
|
|
181
|
+
remedy: "在宿主会话里运行 /evolution doctor。"
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function runtimeOf(inputs) {
|
|
186
|
+
const missing = inputs.missingServices ?? [];
|
|
187
|
+
const fold = inputs.sessionFoldClassifierPresent !== false;
|
|
188
|
+
return [
|
|
189
|
+
channelRow(inputs.channels),
|
|
190
|
+
presetRow(inputs.presets),
|
|
191
|
+
hostRow(inputs.host, inputs.hostRouteCount),
|
|
192
|
+
fold ? {
|
|
193
|
+
key: "session-fold",
|
|
194
|
+
label: RUNTIME_LABELS["session-fold"] ?? "会话事件 fold 单入口",
|
|
195
|
+
status: "ok",
|
|
196
|
+
detail: "classifySessionEvent/isIgnorableEvent 存在:model 消费,plugin-record/unknown 忽略。"
|
|
197
|
+
} : {
|
|
198
|
+
key: "session-fold",
|
|
199
|
+
label: RUNTIME_LABELS["session-fold"] ?? "会话事件 fold 单入口",
|
|
200
|
+
status: "fail",
|
|
201
|
+
detail: "分类器缺失,无法证明单一 fold 入口。",
|
|
202
|
+
remedy: "升级家族(classifySessionEvent 是 core 根导出)。"
|
|
203
|
+
},
|
|
204
|
+
missing.length === 0 ? {
|
|
205
|
+
key: "platform-services",
|
|
206
|
+
label: RUNTIME_LABELS["platform-services"] ?? "平台服务探测",
|
|
207
|
+
status: "ok",
|
|
208
|
+
detail: "探测表中的服务均已挂载"
|
|
209
|
+
} : {
|
|
210
|
+
key: "platform-services",
|
|
211
|
+
label: RUNTIME_LABELS["platform-services"] ?? "平台服务探测",
|
|
212
|
+
status: "warn",
|
|
213
|
+
detail: "缺少:" + missing.join("、"),
|
|
214
|
+
remedy: "安装行(evolution-host/evolution-all)或检查 profile 行。"
|
|
215
|
+
},
|
|
216
|
+
inputs.runs.orphaned > 0 ? {
|
|
217
|
+
key: "runs",
|
|
218
|
+
label: RUNTIME_LABELS.runs ?? "运行登记",
|
|
219
|
+
status: "warn",
|
|
220
|
+
detail: "running=" + String(inputs.runs.running) + ",orphaned=" + String(inputs.runs.orphaned),
|
|
221
|
+
remedy: "运行 /evolution runs 收敛遗留运行。"
|
|
222
|
+
} : {
|
|
223
|
+
key: "runs",
|
|
224
|
+
label: RUNTIME_LABELS.runs ?? "运行登记",
|
|
225
|
+
status: "ok",
|
|
226
|
+
detail: "running=" + String(inputs.runs.running) + ",orphaned=0"
|
|
227
|
+
},
|
|
228
|
+
pairingRow(inputs),
|
|
229
|
+
inputs.form.confidence === "declared" ? {
|
|
230
|
+
key: "install-form-detected",
|
|
231
|
+
label: RUNTIME_LABELS["install-form-detected"] ?? "实际安装形态",
|
|
232
|
+
status: "ok",
|
|
233
|
+
detail: inputs.form.id + "(声明)"
|
|
234
|
+
} : {
|
|
235
|
+
key: "install-form-detected",
|
|
236
|
+
label: RUNTIME_LABELS["install-form-detected"] ?? "实际安装形态",
|
|
237
|
+
status: "warn",
|
|
238
|
+
detail: inputs.form.id + "(推断:" + inputs.form.evidence.join("、") + ")",
|
|
239
|
+
remedy: "在 profile 里显式声明安装行,核对 INSTALL 的形态表。"
|
|
240
|
+
}
|
|
241
|
+
].sort((a, b) => a.key.localeCompare(b.key));
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Build the frozen DiagnosticsView. Pure; never throws.
|
|
245
|
+
* @param inputs - everything the 7+8 rows read (assembleDoctorInputs probes the live host).
|
|
246
|
+
*/
|
|
247
|
+
function buildDoctorView(inputs) {
|
|
248
|
+
return {
|
|
249
|
+
schemaVersion: DIAGNOSTICS_VIEW_SCHEMA_VERSION,
|
|
250
|
+
generatedAt: inputs.now.toISOString(),
|
|
251
|
+
facts: factsOf(inputs),
|
|
252
|
+
runtime: runtimeOf(inputs)
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
/** A safe view for a host whose inputs could not be assembled: every row names the failure. */
|
|
256
|
+
function unavailableDoctorView(now, reason) {
|
|
257
|
+
const facts = DIAGNOSTICS_FACT_KEYS.map((key) => ({
|
|
258
|
+
key,
|
|
259
|
+
label: FACT_LABELS[key] ?? key,
|
|
260
|
+
value: "unknown",
|
|
261
|
+
home: FACT_HOMES[key] ?? "README.md"
|
|
262
|
+
}));
|
|
263
|
+
const runtime = DIAGNOSTICS_RUNTIME_KEYS.map((key) => ({
|
|
264
|
+
key,
|
|
265
|
+
label: RUNTIME_LABELS[key] ?? key,
|
|
266
|
+
status: "unknown",
|
|
267
|
+
detail: "无法装配 doctor 输入:" + reason,
|
|
268
|
+
remedy: "运行 /evolution doctor 重试;若持续失败,检查宿主日志。"
|
|
269
|
+
}));
|
|
270
|
+
return {
|
|
271
|
+
schemaVersion: DIAGNOSTICS_VIEW_SCHEMA_VERSION,
|
|
272
|
+
generatedAt: now.toISOString(),
|
|
273
|
+
facts: facts.sort((a, b) => a.key.localeCompare(b.key)),
|
|
274
|
+
runtime: runtime.sort((a, b) => a.key.localeCompare(b.key))
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
/** The family version this build carries (nearest package.json above the running module). */
|
|
278
|
+
function familyVersion() {
|
|
279
|
+
try {
|
|
280
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
281
|
+
for (let hops = 0; hops < 32; hops += 1) {
|
|
282
|
+
const manifest = join(dir, "package.json");
|
|
283
|
+
if (existsSync(manifest)) {
|
|
284
|
+
const parsed = JSON.parse(readFileSync(manifest, "utf8"));
|
|
285
|
+
if (typeof parsed.version === "string") return parsed.version;
|
|
286
|
+
}
|
|
287
|
+
const parent = dirname(dir);
|
|
288
|
+
if (parent === dir) break;
|
|
289
|
+
dir = parent;
|
|
290
|
+
}
|
|
291
|
+
} catch {}
|
|
292
|
+
return "unknown";
|
|
293
|
+
}
|
|
294
|
+
/** The soft service probe list for the platform-services row. */
|
|
295
|
+
const SERVICES_PROBED = [
|
|
296
|
+
"connection",
|
|
297
|
+
"agentPresets",
|
|
298
|
+
"subagents",
|
|
299
|
+
"commands",
|
|
300
|
+
"settings"
|
|
301
|
+
];
|
|
302
|
+
/** The host's own version, read from the installed platform manifest (soft: absent = unknown). */
|
|
303
|
+
function hostRuntimeVersion() {
|
|
304
|
+
for (const packageName of ["@deepseek-ai/dsh-app-boot", "@deepseek-ai/dsh"]) try {
|
|
305
|
+
const manifest = createRequire(import.meta.url).resolve(packageName + "/package.json");
|
|
306
|
+
const parsed = JSON.parse(readFileSync(manifest, "utf8"));
|
|
307
|
+
if (typeof parsed.version === "string" && parsed.version !== "") return parsed.version;
|
|
308
|
+
} catch {}
|
|
309
|
+
}
|
|
310
|
+
/** The family bundles the platform reports (soft probe: a host without the manager answers []). */
|
|
311
|
+
async function installedFamilyBundles(ctx) {
|
|
312
|
+
const listed = await ctx.get("pluginManager")?.listBundles?.();
|
|
313
|
+
if (!Array.isArray(listed)) return [];
|
|
314
|
+
const names = [];
|
|
315
|
+
for (const entry of listed) {
|
|
316
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
317
|
+
const name = entry.name;
|
|
318
|
+
if (typeof name === "string" && name.includes("dsh-evolution-")) names.push(name);
|
|
319
|
+
}
|
|
320
|
+
return names;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Assemble the view inputs through the live Context's soft probes.
|
|
324
|
+
*
|
|
325
|
+
* Every probe is optional: an absent service becomes a row value (unknown/warn), never a throw. The
|
|
326
|
+
* channel probe uses W5's own `createSubagentChannel` state machinery, so the doctor reports the
|
|
327
|
+
* SAME availability the review/maintenance runs would see.
|
|
328
|
+
* @param ctx - the host context.
|
|
329
|
+
* @param extras - call-site facts the context cannot answer (runs, host version, install form).
|
|
330
|
+
*/
|
|
331
|
+
async function assembleDoctorInputs(ctx, extras = {}) {
|
|
332
|
+
const get = (name) => ctx.get(name);
|
|
333
|
+
const channel = createSubagentChannel(ctx);
|
|
334
|
+
const probeChannel = async (kind) => {
|
|
335
|
+
try {
|
|
336
|
+
return await channel.prepare({
|
|
337
|
+
kind,
|
|
338
|
+
allow: ["skill"]
|
|
339
|
+
});
|
|
340
|
+
} catch (error) {
|
|
341
|
+
return {
|
|
342
|
+
kind: "unavailable",
|
|
343
|
+
code: "E-323",
|
|
344
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
const [review, maintenance] = await Promise.all([probeChannel("reviewer"), probeChannel("maintainer")]);
|
|
349
|
+
const presetsService = get("agentPresets");
|
|
350
|
+
let listed = [];
|
|
351
|
+
try {
|
|
352
|
+
const value = await presetsService?.list?.();
|
|
353
|
+
if (Array.isArray(value)) listed = value;
|
|
354
|
+
} catch {
|
|
355
|
+
listed = [];
|
|
356
|
+
}
|
|
357
|
+
const presetOf = (id) => {
|
|
358
|
+
if (listed.length === 0) return void 0;
|
|
359
|
+
const entry = listed.find((candidate) => candidate.id === id);
|
|
360
|
+
if (entry === void 0) return {
|
|
361
|
+
id,
|
|
362
|
+
registered: false
|
|
363
|
+
};
|
|
364
|
+
return {
|
|
365
|
+
id,
|
|
366
|
+
registered: true,
|
|
367
|
+
...typeof entry.broken === "string" ? { broken: entry.broken } : {}
|
|
368
|
+
};
|
|
369
|
+
};
|
|
370
|
+
const bundles = await installedFamilyBundles(ctx);
|
|
371
|
+
const hasAll = bundles.some((name) => name.endsWith("dsh-evolution-all"));
|
|
372
|
+
const hasPreset = bundles.some((name) => name.endsWith("dsh-evolution-preset"));
|
|
373
|
+
const hasHost = bundles.some((name) => name.endsWith("dsh-evolution-host"));
|
|
374
|
+
const hasFamilyPreset = listed.some((entry) => typeof entry.id === "string" && EVOLUTION_PRESET_IDS.includes(entry.id));
|
|
375
|
+
const detectedForm = hasAll || hasPreset ? {
|
|
376
|
+
id: "M1",
|
|
377
|
+
evidence: [hasAll ? "bundle dsh-evolution-all installed" : "bundle dsh-evolution-preset installed"],
|
|
378
|
+
confidence: "declared"
|
|
379
|
+
} : hasHost ? hasFamilyPreset ? {
|
|
380
|
+
id: "M4",
|
|
381
|
+
evidence: ["bundle dsh-evolution-host installed", "family agent preset registered"],
|
|
382
|
+
confidence: "declared"
|
|
383
|
+
} : {
|
|
384
|
+
id: "M3",
|
|
385
|
+
evidence: ["bundle dsh-evolution-host installed"],
|
|
386
|
+
confidence: "declared"
|
|
387
|
+
} : {
|
|
388
|
+
id: "M3",
|
|
389
|
+
evidence: ["no family bundle was reported by the platform manager"],
|
|
390
|
+
confidence: "inferred"
|
|
391
|
+
};
|
|
392
|
+
const platformVersion = hostRuntimeVersion();
|
|
393
|
+
const missingServices = SERVICES_PROBED.filter((name) => get(name) === void 0);
|
|
394
|
+
const host = hostSurface(ctx);
|
|
395
|
+
const hostMounted = host.available;
|
|
396
|
+
return {
|
|
397
|
+
now: extras.now ?? /* @__PURE__ */ new Date(),
|
|
398
|
+
platformLine: {
|
|
399
|
+
version: PLATFORM_LINE.version,
|
|
400
|
+
floor: PLATFORM_LINE.floor
|
|
401
|
+
},
|
|
402
|
+
familyVersion: familyVersion(),
|
|
403
|
+
hostVersion: extras.hostVersion ?? platformVersion,
|
|
404
|
+
form: extras.form ?? detectedForm,
|
|
405
|
+
probes: {
|
|
406
|
+
agentPresets: get("agentPresets") !== void 0,
|
|
407
|
+
connection: get("connection") !== void 0
|
|
408
|
+
},
|
|
409
|
+
channels: {
|
|
410
|
+
review,
|
|
411
|
+
maintenance
|
|
412
|
+
},
|
|
413
|
+
presets: {
|
|
414
|
+
reviewer: presetOf(REVIEWER_PRESET_ID),
|
|
415
|
+
maintainer: presetOf(MAINTAINER_PRESET_ID)
|
|
416
|
+
},
|
|
417
|
+
host: {
|
|
418
|
+
mounted: hostMounted,
|
|
419
|
+
...hostMounted ? {} : { reason: host.reason },
|
|
420
|
+
...host.available ? { deploymentAllowsRemote: host.allowsRemoteAuthorities } : {}
|
|
421
|
+
},
|
|
422
|
+
runs: extras.runs ?? {
|
|
423
|
+
running: 0,
|
|
424
|
+
orphaned: 0
|
|
425
|
+
},
|
|
426
|
+
missingServices,
|
|
427
|
+
sessionFoldClassifierPresent: typeof classifySessionEvent === "function",
|
|
428
|
+
platformPairing: extras.platformPairing ?? (platformVersion === void 0 ? "unknown" : platformVersion === PLATFORM_LINE.version ? "in-window" : "host-newer")
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
//#endregion
|
|
432
|
+
//#region lib/types/diagnostics-route.js
|
|
433
|
+
/**
|
|
434
|
+
* G6/W8: the diagnostics Host route — the browser face of the SAME DiagnosticsView the CLI prints.
|
|
435
|
+
*
|
|
436
|
+
* Registration belongs to W6 (`registerHostRoutes` is called by the commands plugin's apply), so
|
|
437
|
+
* this module only supplies the handler. A failed input assembly is NOT a transport error: the
|
|
438
|
+
* handler answers an all-unknown view naming the failure, because doctor must never throw (W8 §6).
|
|
439
|
+
* @module @lmzhen/dsh-evolution-commands/diagnostics-route
|
|
440
|
+
*/
|
|
441
|
+
/**
|
|
442
|
+
* Build the diagnostics route over one input producer.
|
|
443
|
+
* @param getInputs - assembles the live view inputs (soft probes; may reject).
|
|
444
|
+
* @returns the exact Fetch route for `HOST_ROUTES.diagnostics.view`.
|
|
445
|
+
*/
|
|
446
|
+
function makeDiagnosticsRoutes(getInputs) {
|
|
447
|
+
return [{
|
|
448
|
+
path: HOST_ROUTES.diagnostics.view,
|
|
449
|
+
methods: ["GET"],
|
|
450
|
+
requestBody: "buffered",
|
|
451
|
+
fetch: async (request) => {
|
|
452
|
+
if (request.method !== "GET") return methodNotAllowed(["GET"]);
|
|
453
|
+
try {
|
|
454
|
+
return json(200, {
|
|
455
|
+
ok: true,
|
|
456
|
+
data: buildDoctorView(await getInputs())
|
|
457
|
+
});
|
|
458
|
+
} catch (error) {
|
|
459
|
+
return json(200, {
|
|
460
|
+
ok: true,
|
|
461
|
+
data: unavailableDoctorView(/* @__PURE__ */ new Date(), error instanceof Error ? error.message : String(error))
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}];
|
|
466
|
+
}
|
|
467
|
+
//#endregion
|
|
11
468
|
//#region lib/types/migration.js
|
|
12
469
|
/**
|
|
13
470
|
* G3 (caller half): find the legacy settings document and run the namespace migration.
|
|
@@ -53,15 +510,18 @@ async function runNamespaceMigration(run) {
|
|
|
53
510
|
await settings.update(rowId, patch);
|
|
54
511
|
};
|
|
55
512
|
if (settings.describe === void 0 || settings.update === void 0) return NOTHING;
|
|
513
|
+
let seen;
|
|
56
514
|
for (const source of legacySettingsPaths(run.home)) {
|
|
57
515
|
const text = await run.io.readText(source);
|
|
58
516
|
if (text === null) continue;
|
|
59
517
|
const plan = planNamespaceMigration(text);
|
|
60
|
-
if (plan.entries.length === 0)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
518
|
+
if (plan.entries.length === 0) {
|
|
519
|
+
seen ??= {
|
|
520
|
+
source,
|
|
521
|
+
plan
|
|
522
|
+
};
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
65
525
|
return {
|
|
66
526
|
source,
|
|
67
527
|
plan,
|
|
@@ -72,7 +532,11 @@ async function runNamespaceMigration(run) {
|
|
|
72
532
|
})
|
|
73
533
|
};
|
|
74
534
|
}
|
|
75
|
-
return NOTHING
|
|
535
|
+
return seen === void 0 ? NOTHING : {
|
|
536
|
+
source: seen.source,
|
|
537
|
+
plan: seen.plan,
|
|
538
|
+
report: void 0
|
|
539
|
+
};
|
|
76
540
|
}
|
|
77
541
|
/**
|
|
78
542
|
* Read the legacy document and report what is still unmigrated.
|
|
@@ -86,11 +550,24 @@ async function runNamespaceMigration(run) {
|
|
|
86
550
|
async function readLegacyDocumentState(run) {
|
|
87
551
|
const settings = run.settings;
|
|
88
552
|
if (settings?.describe === void 0) return void 0;
|
|
553
|
+
let seen;
|
|
89
554
|
for (const source of legacySettingsPaths(run.home)) {
|
|
90
555
|
const text = await run.io.readText(source);
|
|
91
556
|
if (text === null) continue;
|
|
92
557
|
const plan = planNamespaceMigration(text);
|
|
93
|
-
|
|
558
|
+
if (plan.entries.length === 0) {
|
|
559
|
+
seen ??= {
|
|
560
|
+
source,
|
|
561
|
+
plan
|
|
562
|
+
};
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
return stateOf(source, plan);
|
|
566
|
+
}
|
|
567
|
+
return seen === void 0 ? void 0 : stateOf(seen.source, seen.plan);
|
|
568
|
+
/** The doctor-facing state of ONE document, judged against the seat. */
|
|
569
|
+
function stateOf(source, plan) {
|
|
570
|
+
const read = (rowId) => settings?.describe?.({ redactSecrets: false }).find((entry) => entry.ns === rowId);
|
|
94
571
|
const decided = classifyNamespaceMigration(plan, read);
|
|
95
572
|
return {
|
|
96
573
|
source,
|
|
@@ -625,39 +1102,12 @@ function envIssues() {
|
|
|
625
1102
|
if (query && !SESSION_QUERY_MODES.has(query)) issues.push(`DSH_EVOLUTION_SESSION_QUERY="${query}" is not one of startup|first-search|never — the patch normalizes it to 'startup' silently; set it to a listed mode.`);
|
|
626
1103
|
return issues;
|
|
627
1104
|
}
|
|
628
|
-
/** S0.2 (v37 P0-1): memory files that still carry the platform's `{{` prompt
|
|
629
|
-
* variable syntax. The platform interpolates EVERY `systemPrompt.context` text
|
|
630
|
-
* once per model step and throws on an unknown/malformed reference, so one such
|
|
631
|
-
* entry used to fail every turn of every session under this home. Current
|
|
632
|
-
* builds neutralize the injected text (`MemoryStore.renderContext`), which is
|
|
633
|
-
* why this is a residual-risk report instead of a hard failure: an older
|
|
634
|
-
* still-running process is still bricked, and an operator may prefer to clean
|
|
635
|
-
* the file. Read-only; an unreadable file is skipped (the doctor never throws).
|
|
636
|
-
* @param home - resolved DSH_HOME.
|
|
637
|
-
* @returns one message per affected memory file.
|
|
638
|
-
*/
|
|
639
|
-
function memoryInterpolationIssues(home) {
|
|
640
|
-
const issues = [];
|
|
641
|
-
for (const file of ["MEMORY.md", "USER.md"]) {
|
|
642
|
-
const path = join(home, "memories", file);
|
|
643
|
-
if (!existsSync(path)) continue;
|
|
644
|
-
let raw;
|
|
645
|
-
try {
|
|
646
|
-
raw = readFileSync(path, "utf8");
|
|
647
|
-
} catch {
|
|
648
|
-
continue;
|
|
649
|
-
}
|
|
650
|
-
const hits = raw.split("{{").length - 1;
|
|
651
|
-
if (hits > 0) issues.push(`${file}: ${hits} "{{" occurrence(s) — the platform interpolates this syntax in prompt context and throws on an unknown reference, so a build without the render-time neutralization fails every later turn; rewrite the affected entries`);
|
|
652
|
-
}
|
|
653
|
-
return issues;
|
|
654
|
-
}
|
|
655
1105
|
/** G3-② (B2): the family delta container. An optionalDependency of this
|
|
656
1106
|
* package, so resolution may legitimately fail; every failure degrades to
|
|
657
1107
|
* "unknown" — the doctor never throws.
|
|
658
1108
|
* Platform anchor: `packages/boot/app-boot/src/profile.ts` — a family preset is a ROW in the
|
|
659
1109
|
* profile's own patch layer there (`PROFILE_PATCH_FILENAME`), never a preset directory. */
|
|
660
|
-
const AGENT_PRESET_PACKAGE = "
|
|
1110
|
+
const AGENT_PRESET_PACKAGE = scopedPackageName("dsh-evolution-agent-preset", import.meta.url);
|
|
661
1111
|
/** Resolve one asset the package declares in its exports map.
|
|
662
1112
|
* @param asset - bases.json or agent.cordis.yml.
|
|
663
1113
|
* @returns the file path, or null when the package is not installed. */
|
|
@@ -995,20 +1445,19 @@ async function diagnose(ctx, options = {}) {
|
|
|
995
1445
|
if (presetIssueRows.length > 0) actions.push("Resolve the agent-preset finding above: a broken preset composes no session, and a row two presets declare is resolved by layer order (keep the declaration in one preset).");
|
|
996
1446
|
const legacy = await legacyMigrationRow(ctx, home);
|
|
997
1447
|
if (legacy.state === "pending") actions.push(`The legacy settings document still holds ${legacy.pending.length} family value(s) — /evolution migrate moves them onto their rows (the document itself is left alone, so nothing is lost)`);
|
|
998
|
-
const memoryIssues = memoryInterpolationIssues(home);
|
|
999
1448
|
const budgetIssues = memoryBudgetIssues(ctx);
|
|
1000
1449
|
const paramIssues = paramDivergences(platform);
|
|
1001
1450
|
const queryIssues = await sessionQueryIssues(ctx);
|
|
1002
1451
|
if (queryIssues.length > 0) actions.push("Session search is degraded: isolate the session named above (or wait for the platform fix described in the family maintenance notes) before retrying the same query");
|
|
1003
1452
|
if (paramIssues.length > 0) actions.push("Review the parameter divergences above: /evolution params shows every row with its source, /evolution policy set writes a user value, and a declared-but-unreachable row needs its owning plugin row mounted in this profile.");
|
|
1004
1453
|
if (budgetIssues.length > 0) actions.push("Align the memory budget: leave memory-files memoryCharLimit/userCharLimit UNSET so the store follows evolution-policy, or set both surfaces to the same value (the review plans against the policy value while the store enforces its own)");
|
|
1005
|
-
if (memoryIssues.length > 0) actions.push("Rewrite the memory entries listed above (or run a build with the render-time neutralization) — they broke prompt assembly on older builds.");
|
|
1006
1454
|
if (scopedProbe.rows !== null && scopedProbe.rows.length > 0 && scopedProbe.verdict === "never-hit") actions.push("The session-scoped rows are mounted but the family-tool probe has never matched in this process — review injection and skill-usage telemetry skipped every session observed. HOST-ONLY install: the model rows (tool-memory / tool-skill-manage) sit in evolution-host devDependencies, so no session carries them; install a bundle that mounts them (see INSTALL.md). VARIANT install: only a session on the Evolution preset matches, so open one — a session on a platform original preset is the intended skip, not a fault. Either way, check that no profile overlay disables those two rows.");
|
|
1007
1455
|
if ((executingCount ?? 0) > 0) actions.push(`${executingCount} staged write(s) are EXECUTING (an approve crashed mid-run — or one is still in flight). Inspect with /evolution pending: if you started the approve, verify the landed write and do not reject it; only reject after verifying no write is intended. For a verified orphan (this build: S2-P2-22), /evolution release <id> returns it to the pending window instead.`);
|
|
1008
1456
|
for (const row of presetFreshness) {
|
|
1009
1457
|
if (row.status !== "differs") continue;
|
|
1010
1458
|
actions.push(`Preset row for base "${row.base}" in ${row.destination} DIFFERS from a fresh generation — it is an install-time snapshot; re-run the installer (/evolution preset install) to regenerate`);
|
|
1011
1459
|
}
|
|
1460
|
+
const view = buildDoctorView(await assembleDoctorInputs(ctx));
|
|
1012
1461
|
return {
|
|
1013
1462
|
installForm,
|
|
1014
1463
|
deploymentForm,
|
|
@@ -1016,7 +1465,6 @@ async function diagnose(ctx, options = {}) {
|
|
|
1016
1465
|
foreignProfiles: homeProfiles(home).filter(({ profile, dir }) => mounted === void 0 || profile !== mounted.profile && dir !== mounted.dir).map(({ profile }) => profile),
|
|
1017
1466
|
conflicts,
|
|
1018
1467
|
envIssues: env,
|
|
1019
|
-
memoryIssues,
|
|
1020
1468
|
budgetIssues,
|
|
1021
1469
|
paramIssues,
|
|
1022
1470
|
queryIssues,
|
|
@@ -1032,7 +1480,8 @@ async function diagnose(ctx, options = {}) {
|
|
|
1032
1480
|
presetIssues: presetIssueRows,
|
|
1033
1481
|
dormantBundles,
|
|
1034
1482
|
serviceSource: reviewRow === void 0 ? "bundles" : "platform",
|
|
1035
|
-
actions
|
|
1483
|
+
actions,
|
|
1484
|
+
view
|
|
1036
1485
|
};
|
|
1037
1486
|
}
|
|
1038
1487
|
/**
|
|
@@ -1175,13 +1624,35 @@ function renderDoctorText(report) {
|
|
|
1175
1624
|
if (report.foreignProfiles.length > 0) lines.push("profiles-foreign: " + report.foreignProfiles.join(", ") + " — not part of this plane: /evolution preset install ignores them, while the bundles (all profiles) line above counts them");
|
|
1176
1625
|
if (report.conflicts.length > 0) lines.push("conflicts:", ...report.conflicts.map((line) => ` ! ${line}`));
|
|
1177
1626
|
if (report.envIssues.length > 0) lines.push("env:", ...report.envIssues.map((line) => ` ! ${line}`));
|
|
1178
|
-
if (report.memoryIssues.length > 0) lines.push("memory:", ...report.memoryIssues.map((line) => ` ! ${line}`));
|
|
1179
1627
|
if (report.budgetIssues.length > 0) lines.push("memory budget:", ...report.budgetIssues.map((line) => ` ! ${line}`));
|
|
1180
1628
|
if (report.paramIssues.length > 0) lines.push("parameters:", ...report.paramIssues.map((line) => ` ! ${line}`));
|
|
1181
1629
|
if (report.queryIssues.length > 0) lines.push("session search:", ...report.queryIssues.map((line) => ` ! ${line}`));
|
|
1182
1630
|
if (report.actions.length > 0) lines.push("next steps:", ...report.actions.map((line) => ` → ${line}`));
|
|
1183
1631
|
return lines.filter((line) => line !== null).join("\n");
|
|
1184
1632
|
}
|
|
1633
|
+
/**
|
|
1634
|
+
* G6/W8 tail: render the frozen DiagnosticsView as the doctor's diagnostics section.
|
|
1635
|
+
*
|
|
1636
|
+
* This is the ONE text face of the 7+8 rows: the same object `--json` and the diagnostics route
|
|
1637
|
+
* answer, so the three readers cannot disagree. Legacy install/conflict sections keep their own
|
|
1638
|
+
* renderer until the remaining W8-S7 rewrite lands.
|
|
1639
|
+
* @param view - the assembled view.
|
|
1640
|
+
* @returns the section text (ends with a newline).
|
|
1641
|
+
*/
|
|
1642
|
+
function renderDiagnosticsText(view) {
|
|
1643
|
+
const lines = [];
|
|
1644
|
+
lines.push("诊断(schema v" + String(view.schemaVersion) + ",生成于 " + view.generatedAt + "):");
|
|
1645
|
+
lines.push(" 事实:");
|
|
1646
|
+
for (const fact of view.facts) lines.push(" - " + fact.label + ":" + fact.value + "(出处:" + fact.home + ")");
|
|
1647
|
+
lines.push(" 运行时:");
|
|
1648
|
+
for (const row of view.runtime) {
|
|
1649
|
+
const status = row.status === "ok" ? "正常" : row.status === "warn" ? "注意" : row.status === "fail" ? "异常" : "未知";
|
|
1650
|
+
const detail = row.detail === void 0 ? "" : ":" + row.detail;
|
|
1651
|
+
const remedy = row.remedy === void 0 ? "" : ";下一步:" + row.remedy;
|
|
1652
|
+
lines.push(" - [" + status + "] " + row.label + detail + remedy);
|
|
1653
|
+
}
|
|
1654
|
+
return lines.join("\n") + "\n";
|
|
1655
|
+
}
|
|
1185
1656
|
//#endregion
|
|
1186
1657
|
//#region lib/types/params.js
|
|
1187
1658
|
/**
|
|
@@ -1368,10 +1839,20 @@ function flagsOf(name) {
|
|
|
1368
1839
|
const entry = COMMAND_ENTRIES.find((candidate) => entryName(candidate) === name);
|
|
1369
1840
|
return entry === void 0 ? [] : entryFlags(entry);
|
|
1370
1841
|
}
|
|
1371
|
-
/**
|
|
1842
|
+
/**
|
|
1843
|
+
* C7 shape, DERIVED: the entry IDENTITY key, and it is HIERARCHICAL (E-08).
|
|
1844
|
+
*
|
|
1845
|
+
* The key used to be the first token alone, and `flagsOf` finds an entry with `.find(...)` - the FIRST
|
|
1846
|
+
* match - so `maintain handoff` / `maintain cancel` and `skill restore` / `skill history` / `skill undo` /
|
|
1847
|
+
* `skill pin` all derived the SAME key as their parent and every flag lookup silently saw the parent entry
|
|
1848
|
+
* only (nine collisions). A bare parent (`maintain [--timeout]`) keeps its one-token key, so parent lookups
|
|
1849
|
+
* still resolve: the one call site that named an entry by a name the key no longer produced was the preset
|
|
1850
|
+
* dispatch branch, and it now asks for the entry own key (`preset install`).
|
|
1851
|
+
*/
|
|
1372
1852
|
function entryName(entry) {
|
|
1373
|
-
const [head = ""] = entry.usage.split(" ");
|
|
1374
|
-
|
|
1853
|
+
const [head = "", second = ""] = entry.usage.split(" ");
|
|
1854
|
+
const name = head.split("|")[0] ?? "";
|
|
1855
|
+
return /^[a-z][a-z-]*$/.test(second) && !second.startsWith("--") ? name + " " + second : name;
|
|
1375
1856
|
}
|
|
1376
1857
|
/** C7 shape, DERIVED: the flags a `usage` declares, in the form `--flag` (value flags keep their name). */
|
|
1377
1858
|
function entryFlags(entry) {
|
|
@@ -1841,7 +2322,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
1841
2322
|
const ioRegistry = ioCtx.get("evolutionIo");
|
|
1842
2323
|
if (!ioRegistry) return;
|
|
1843
2324
|
const registry = newRunRegistry({
|
|
1844
|
-
io:
|
|
2325
|
+
io: ioRegistry.provider(),
|
|
1845
2326
|
home: evolutionHome(),
|
|
1846
2327
|
warn: (message) => {
|
|
1847
2328
|
ctx.logger.warn(message);
|
|
@@ -1859,6 +2340,22 @@ function apply(ctx, rawConfig = {}) {
|
|
|
1859
2340
|
runs = void 0;
|
|
1860
2341
|
}, "evolution-commands.run-registry");
|
|
1861
2342
|
});
|
|
2343
|
+
/** G6/W8: the run-registry snapshot the diagnostics `runs` row reads (one owner: the registry). */
|
|
2344
|
+
const doctorRuns = () => {
|
|
2345
|
+
try {
|
|
2346
|
+
const running = (runs?.runs() ?? []).filter((record) => record.state === "running").length;
|
|
2347
|
+
const active = runs?.inFlight();
|
|
2348
|
+
return {
|
|
2349
|
+
running,
|
|
2350
|
+
orphaned: Math.max(0, running - (active === void 0 ? 0 : 1))
|
|
2351
|
+
};
|
|
2352
|
+
} catch {
|
|
2353
|
+
return {
|
|
2354
|
+
running: 0,
|
|
2355
|
+
orphaned: 0
|
|
2356
|
+
};
|
|
2357
|
+
}
|
|
2358
|
+
};
|
|
1862
2359
|
ctx.inject(["commands"], (commandCtx) => {
|
|
1863
2360
|
const commands = commandCtx.commands;
|
|
1864
2361
|
const commandBody = { run: async (invocation) => {
|
|
@@ -2072,8 +2569,9 @@ function apply(ctx, rawConfig = {}) {
|
|
|
2072
2569
|
const ioRegistry = ctx.get("evolutionIo");
|
|
2073
2570
|
if (!ioRegistry) return err("未挂载 evolution-io 注册表:无法固定或取消固定技能。");
|
|
2074
2571
|
const result = await newSkillLibrary({
|
|
2572
|
+
authority: authorityFrom({ origin: "foreground" }),
|
|
2075
2573
|
config: { root: skillsRootValue },
|
|
2076
|
-
io:
|
|
2574
|
+
io: ioRegistry.provider(),
|
|
2077
2575
|
ctx,
|
|
2078
2576
|
limits: {
|
|
2079
2577
|
...DEFAULT_SKILL_LIMITS,
|
|
@@ -2149,8 +2647,9 @@ function apply(ctx, rawConfig = {}) {
|
|
|
2149
2647
|
const ioRegistry = ctx.get("evolutionIo");
|
|
2150
2648
|
if (!ioRegistry) return err("未挂载 evolution-io 注册表:维护事实不可用。");
|
|
2151
2649
|
const library = newSkillLibrary({
|
|
2650
|
+
authority: authorityFrom({ origin: "foreground" }),
|
|
2152
2651
|
config: { root: skillsRootValue },
|
|
2153
|
-
io:
|
|
2652
|
+
io: ioRegistry.provider()
|
|
2154
2653
|
});
|
|
2155
2654
|
const enrichment = await buildEnrichment(ctx, library);
|
|
2156
2655
|
const readFailures = [];
|
|
@@ -2162,7 +2661,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
2162
2661
|
}
|
|
2163
2662
|
});
|
|
2164
2663
|
if (snapshots.length === 0 && readFailures.length > 0) return err(`维护扫描读不出 ${readFailures.length} 个已登记技能(${readFailures.slice(0, 10).join(", ")}${readFailures.length > 10 ? ", …" : ""}),且一个技能都没读到:技能库未被审计。请修好读不出的技能(或它们的目录名)后重新扫描`);
|
|
2165
|
-
if (snapshots.length === 0 && skillsRootValue !== "" && !await
|
|
2664
|
+
if (snapshots.length === 0 && skillsRootValue !== "" && !await ioRegistry.provider().exists(skillsRootValue)) return err(`维护扫描发现技能库为空,但配置的技能根目录不存在(${skillsRootValue}):请把 skillsRoot 指向真实目录(字面量 \`~\` 不会被展开),然后重新扫描`);
|
|
2166
2665
|
if (snapshots.length === 0) return ok("维护扫描:技能库为空,无需处理。");
|
|
2167
2666
|
const { facts } = buildMaintainFacts(snapshots, enrichment.usageObservedValue, void 0);
|
|
2168
2667
|
return ok(`维护事实(零 token 预览):\n${facts}`);
|
|
@@ -2179,9 +2678,8 @@ function apply(ctx, rawConfig = {}) {
|
|
|
2179
2678
|
if (!synced.ok) ctx.logger.warn(`evolution-commands: ${synced.note ?? "run index unreadable"} — the in-flight guard cannot see other processes' runs`);
|
|
2180
2679
|
else if (synced.note !== void 0) ctx.logger.warn(`evolution-commands: ${synced.note}`);
|
|
2181
2680
|
const ioRegistry = ctx.get("evolutionIo");
|
|
2182
|
-
const subagents = ctx.get("subagents");
|
|
2183
2681
|
if (!ioRegistry) return err("未挂载 evolution-io 注册表:维护扫描不可用。");
|
|
2184
|
-
|
|
2682
|
+
const channel = createSubagentChannel(ctx);
|
|
2185
2683
|
const running = registry.inFlight();
|
|
2186
2684
|
if (running) {
|
|
2187
2685
|
const elapsed = Math.max(1, Math.round((Date.now() - running.startedAt) / 1e3));
|
|
@@ -2202,20 +2700,21 @@ function apply(ctx, rawConfig = {}) {
|
|
|
2202
2700
|
(async () => {
|
|
2203
2701
|
try {
|
|
2204
2702
|
const library = newSkillLibrary({
|
|
2703
|
+
authority: authorityFrom({ origin: "foreground" }),
|
|
2205
2704
|
config: { root: skillsRootValue },
|
|
2206
|
-
io:
|
|
2705
|
+
io: ioRegistry.provider()
|
|
2207
2706
|
});
|
|
2208
2707
|
const enrichment = await buildEnrichment(ctx, library);
|
|
2209
2708
|
const outcome = await runMaintain({
|
|
2210
2709
|
library,
|
|
2211
|
-
|
|
2710
|
+
channel,
|
|
2212
2711
|
parent: invocation.agent,
|
|
2213
2712
|
evolutionPolicy: { get: () => ctx.get("evolutionPolicy")?.get() },
|
|
2214
2713
|
tools: { get: (name) => ctx.get("tools")?.get(name) },
|
|
2215
2714
|
logger: { warn: (message) => {
|
|
2216
2715
|
ctx.logger.warn(message);
|
|
2217
2716
|
} },
|
|
2218
|
-
rootExists: skillsRootValue === "" ? void 0 : () =>
|
|
2717
|
+
rootExists: skillsRootValue === "" ? void 0 : () => ioRegistry.provider().exists(skillsRootValue),
|
|
2219
2718
|
skillRoot: skillsRootValue
|
|
2220
2719
|
}, {
|
|
2221
2720
|
timeoutMs: runTimeoutMs,
|
|
@@ -2224,7 +2723,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
2224
2723
|
enrichment: () => enrichment
|
|
2225
2724
|
});
|
|
2226
2725
|
const endedAt = Date.now();
|
|
2227
|
-
const resultRef = await writeMaintainReport(
|
|
2726
|
+
const resultRef = await writeMaintainReport(ioRegistry.provider(), {
|
|
2228
2727
|
runId: handle.id,
|
|
2229
2728
|
...outcome.ok ? {
|
|
2230
2729
|
verdict: outcome.verdict,
|
|
@@ -2241,6 +2740,15 @@ function apply(ctx, rawConfig = {}) {
|
|
|
2241
2740
|
}, (message) => {
|
|
2242
2741
|
ctx.logger.warn(message);
|
|
2243
2742
|
}, runHome);
|
|
2743
|
+
if (resultRef !== void 0) {
|
|
2744
|
+
if (!isPresent(await probeMtime(ioRegistry.provider(), resultRef))) {
|
|
2745
|
+
await registry.settle(handle.id, {
|
|
2746
|
+
state: "failed",
|
|
2747
|
+
failure: "the maintain report file is missing at settle time (write/io race) — the scan result was not persisted."
|
|
2748
|
+
});
|
|
2749
|
+
return;
|
|
2750
|
+
}
|
|
2751
|
+
}
|
|
2244
2752
|
const state = outcome.ok ? "succeeded" : handle.signal.aborted ? "cancelled" : "failed";
|
|
2245
2753
|
await registry.settle(handle.id, {
|
|
2246
2754
|
state,
|
|
@@ -2248,7 +2756,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
2248
2756
|
...outcome.ok ? {} : { failure: handle.signal.aborted ? "cancelled: the scan was stopped before it settled" : outcome.error ?? "Maintenance scan failed." }
|
|
2249
2757
|
});
|
|
2250
2758
|
if (outcome.ok) {
|
|
2251
|
-
appendEvolutionEvent(
|
|
2759
|
+
appendEvolutionEvent(ioRegistry.provider(), eventsFile(runRoot), {
|
|
2252
2760
|
type: "maintain",
|
|
2253
2761
|
source: "manual",
|
|
2254
2762
|
runId: handle.id,
|
|
@@ -2341,7 +2849,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
2341
2849
|
if (wanted === void 0 && record.state === "running") return err(`最新一次运行 ${record.id} 仍在进行:它结束后才会出报告;用 /evolution maintain status ${record.id} 查看进度。`);
|
|
2342
2850
|
const ioRegistry = ctx.get("evolutionIo");
|
|
2343
2851
|
if (!ioRegistry) return err("未挂载 evolution-io 注册表:无法查看运行结果。");
|
|
2344
|
-
const probed = await readRunReport(
|
|
2852
|
+
const probed = await readRunReport(ioRegistry.provider(), record);
|
|
2345
2853
|
if (probed.kind === "missing") return err(`运行 ${record.id}(${record.state})没有结果:没有写出报告${record.failure === void 0 ? "" : `(${record.failure})`}。`);
|
|
2346
2854
|
if (probed.kind === "unknown") return err(`运行 ${record.id} 有报告但读不出来(${probed.reason}):这「不是」没有结果。`);
|
|
2347
2855
|
return ok(probed.text);
|
|
@@ -2386,7 +2894,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
2386
2894
|
if (wanted === void 0 && record.state === "running") return err(`最新一次运行 ${record.id} 仍在进行:它结束后才会出报告;用 /evolution maintain status ${record.id} 查看进度。`);
|
|
2387
2895
|
const ioRegistry = ctx.get("evolutionIo");
|
|
2388
2896
|
if (!ioRegistry) return err("未挂载 evolution-io 注册表:无法交付报告。");
|
|
2389
|
-
const probed = await readRunReport(
|
|
2897
|
+
const probed = await readRunReport(ioRegistry.provider(), record);
|
|
2390
2898
|
if (probed.kind === "missing") return err(`运行 ${record.id}(${record.state})没有结果:没有写出报告${record.failure === void 0 ? "" : `(${record.failure})`}。`);
|
|
2391
2899
|
if (probed.kind === "unknown") return err(`运行 ${record.id} 有报告但读不出来(${probed.reason}):这「不是」没有结果。`);
|
|
2392
2900
|
const missingAgent = agentMissing("handoff");
|
|
@@ -2413,7 +2921,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
2413
2921
|
}
|
|
2414
2922
|
if (/^maintain\b/.test(input)) return err("未知的 maintain 参数:应为 `maintain`、`maintain --timeout <ms>` / `maintain --timeout=<ms>`、`maintain status [<id>]`、`maintain report [<id>]`、`maintain handoff [<id>]` 或 `maintain cancel [<id>]`。实际收到:" + input);
|
|
2415
2923
|
const presetParsed = parseSubcommand(input);
|
|
2416
|
-
const presetInstall = isParseFailure(presetParsed) || presetParsed.name !== "preset" ? null : presetParsed.positionals[0] === "install" && presetParsed.positionals.length === 1 && [...presetParsed.flags.keys()].every((flag) => flagsOf("preset").includes("--" + flag)) ? presetParsed : false;
|
|
2924
|
+
const presetInstall = isParseFailure(presetParsed) || presetParsed.name !== "preset" ? null : presetParsed.positionals[0] === "install" && presetParsed.positionals.length === 1 && [...presetParsed.flags.keys()].every((flag) => flagsOf("preset install").includes("--" + flag)) ? presetParsed : false;
|
|
2417
2925
|
if (presetInstall !== null && presetInstall !== false) {
|
|
2418
2926
|
{
|
|
2419
2927
|
const mounted = presetProfileTarget(ctx);
|
|
@@ -2525,8 +3033,9 @@ function apply(ctx, rawConfig = {}) {
|
|
|
2525
3033
|
if (decision.action === "staged") return ok(`${decision.message}${planRunId ? `\n[audit] plan=${planRunId}` : ""}`);
|
|
2526
3034
|
}
|
|
2527
3035
|
const result = await newSkillLibrary({
|
|
3036
|
+
authority: authorityFrom({ origin: "foreground" }),
|
|
2528
3037
|
config: { root: skillsRootValue },
|
|
2529
|
-
io:
|
|
3038
|
+
io: ioRegistry.provider(),
|
|
2530
3039
|
ctx,
|
|
2531
3040
|
threatExemptLabels: config.threatExemptLabels,
|
|
2532
3041
|
limits: {
|
|
@@ -2664,8 +3173,8 @@ function apply(ctx, rawConfig = {}) {
|
|
|
2664
3173
|
home: evolutionRoot(),
|
|
2665
3174
|
...mounted
|
|
2666
3175
|
});
|
|
2667
|
-
if (input === "doctor --json") return ok(JSON.stringify(report, null, 2));
|
|
2668
|
-
return ok(renderDoctorText(report));
|
|
3176
|
+
if (input === "doctor --json") return ok(JSON.stringify(report.view, null, 2));
|
|
3177
|
+
return ok(renderDoctorText(report) + "\n" + renderDiagnosticsText(report.view));
|
|
2669
3178
|
}
|
|
2670
3179
|
if (input === "" || input === "help") return ok(`自进化:记忆、技能、会话回顾、技能整理——状态、待批准写入与维护。\n${renderHelpText()}`);
|
|
2671
3180
|
return err(`/evolution ${input} 不是本构建能应答的子命令(已知子命令但缺参数也会落到这里)。\n${renderHint()}`);
|
|
@@ -2727,6 +3236,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
2727
3236
|
ctx.inject(["settings"], () => {
|
|
2728
3237
|
runMigrationOnce();
|
|
2729
3238
|
});
|
|
3239
|
+
registerHostRoutes(ctx, () => makeDiagnosticsRoutes(() => assembleDoctorInputs(ctx, { runs: doctorRuns() })));
|
|
2730
3240
|
(async () => {
|
|
2731
3241
|
const loader = ctx.root.loader;
|
|
2732
3242
|
try {
|
|
@@ -2783,7 +3293,8 @@ function resolveAgentPresetDir(importMetaUrl) {
|
|
|
2783
3293
|
const overlayCandidate = join(dirname(fileURLToPath(importMetaUrl)), "..", "..", "evolution-agent");
|
|
2784
3294
|
if (existsSync(join(overlayCandidate, "agent.cordis.yml")) && existsSync(join(overlayCandidate, "bases.json"))) return overlayCandidate;
|
|
2785
3295
|
try {
|
|
2786
|
-
|
|
3296
|
+
const agentPresetPackage = scopedPackageName("dsh-evolution-agent-preset", importMetaUrl);
|
|
3297
|
+
return dirname(createRequire(importMetaUrl).resolve(agentPresetPackage + "/package.json"));
|
|
2787
3298
|
} catch {
|
|
2788
3299
|
return overlayCandidate;
|
|
2789
3300
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* G6/W8: the command-face fallback for every Host data-plane surface.
|
|
3
|
+
*
|
|
4
|
+
* The browser panels depend on the platform's `connection` carrier (exact Fetch routes). When that
|
|
5
|
+
* carrier is absent (headless host, E-325) or a panel answer is missing, the operator is owed the
|
|
6
|
+
* equivalent command — the SAME use case, not a different implementation. This table is the ONE
|
|
7
|
+
* home of those pairs; the INSTALL runbook and the client `fallbacks.generated.ts` are generated
|
|
8
|
+
* from it (W7), and a spec pins its key set to HOST_ROUTES' four surfaces.
|
|
9
|
+
*
|
|
10
|
+
* Not exported from the package: this is a command-side contract, not a plugin API (W8 §2).
|
|
11
|
+
* @module @lmzhen/dsh-evolution-commands/command-fallback
|
|
12
|
+
*/
|
|
13
|
+
/** One surface's command-face path(s): the family command, plus the platform CLI when one exists. */
|
|
14
|
+
export interface CommandFallbackHint {
|
|
15
|
+
readonly command: string;
|
|
16
|
+
readonly platformCli?: string;
|
|
17
|
+
}
|
|
18
|
+
/** The four Host surfaces (keyed exactly like the frozen `COMMAND_FALLBACK_SURFACES`). */
|
|
19
|
+
export type CommandFallbackSurface = 'approval' | 'skill-history' | 'skill-health' | 'diagnostics';
|
|
20
|
+
/**
|
|
21
|
+
* One entry per Host surface. The command column names the same service methods the routes call,
|
|
22
|
+
* so the answers (and refusal sentences) match the panel.
|
|
23
|
+
*/
|
|
24
|
+
export declare const COMMAND_FALLBACKS: Readonly<Record<CommandFallbackSurface, CommandFallbackHint>>;
|
|
25
|
+
/** The ordered surface ids (the frozen W1 set). */
|
|
26
|
+
export declare const COMMAND_FALLBACK_SURFACES: readonly CommandFallbackSurface[];
|
|
27
|
+
//# sourceMappingURL=command-fallback.d.ts.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ConnectionFetchRouteLike } from '@lmzhen/dsh-evolution-core/http-routes';
|
|
2
|
+
import { type DoctorInputs } from './doctor-view.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Build the diagnostics route over one input producer.
|
|
5
|
+
* @param getInputs - assembles the live view inputs (soft probes; may reject).
|
|
6
|
+
* @returns the exact Fetch route for `HOST_ROUTES.diagnostics.view`.
|
|
7
|
+
*/
|
|
8
|
+
export declare function makeDiagnosticsRoutes(getInputs: () => Promise<DoctorInputs>): ConnectionFetchRouteLike[];
|
|
9
|
+
//# sourceMappingURL=diagnostics-route.d.ts.map
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* G6/W8: `buildDoctorView` — the ONE producer of DiagnosticsView.
|
|
3
|
+
*
|
|
4
|
+
* CLI `--json`, the diagnostics route (W6) and the client diagnostics card (W7) all call this
|
|
5
|
+
* function, so they cannot drift: 7 facts (declared/derived values, homes named) + 8 runtime rows
|
|
6
|
+
* (measured state, never rendered as ok when unreadable). The function is pure and NEVER throws: a
|
|
7
|
+
* missing/undecidable provider becomes an `unknown`/`warn`/`fail` row with a remedy, because a
|
|
8
|
+
* doctor that fails to answer is worse than one that names its blind spots.
|
|
9
|
+
* @module @lmzhen/dsh-evolution-commands/doctor-view
|
|
10
|
+
*/
|
|
11
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
12
|
+
import { type ChannelState, type DiagnosticsView } from '@lmzhen/dsh-evolution-core';
|
|
13
|
+
/** The install form the running profile was classified as (W8 §2 runtime install-form-detected). */
|
|
14
|
+
export type InstallFormId = 'M1' | 'M2' | 'M3' | 'M4';
|
|
15
|
+
/** One preset's probe result (reviewer/maintainer). */
|
|
16
|
+
export interface PresetFacts {
|
|
17
|
+
readonly registered: boolean;
|
|
18
|
+
readonly broken?: string | undefined;
|
|
19
|
+
readonly id?: string | undefined;
|
|
20
|
+
}
|
|
21
|
+
/** The platform/connection pairing verdict. */
|
|
22
|
+
export type PlatformPairing = 'in-window' | 'host-newer' | 'out-of-window' | 'unknown';
|
|
23
|
+
/**
|
|
24
|
+
* Every input the view reads. Callers assemble it (assembleDoctorInputs does the soft probing);
|
|
25
|
+
* tests drive each branch directly.
|
|
26
|
+
*/
|
|
27
|
+
export interface DoctorInputs {
|
|
28
|
+
readonly now: Date;
|
|
29
|
+
readonly platformLine: {
|
|
30
|
+
readonly version: string;
|
|
31
|
+
readonly floor: string;
|
|
32
|
+
};
|
|
33
|
+
readonly familyVersion: string;
|
|
34
|
+
readonly hostVersion?: string | undefined;
|
|
35
|
+
readonly form: {
|
|
36
|
+
readonly id: InstallFormId;
|
|
37
|
+
readonly evidence: readonly string[];
|
|
38
|
+
readonly confidence: 'declared' | 'inferred';
|
|
39
|
+
};
|
|
40
|
+
readonly probes: {
|
|
41
|
+
readonly agentPresets: boolean;
|
|
42
|
+
readonly connection: boolean;
|
|
43
|
+
};
|
|
44
|
+
readonly channels: {
|
|
45
|
+
readonly review: ChannelState;
|
|
46
|
+
readonly maintenance: ChannelState;
|
|
47
|
+
};
|
|
48
|
+
readonly presets: {
|
|
49
|
+
readonly reviewer?: PresetFacts | undefined;
|
|
50
|
+
readonly maintainer?: PresetFacts | undefined;
|
|
51
|
+
};
|
|
52
|
+
readonly host: {
|
|
53
|
+
readonly mounted: boolean;
|
|
54
|
+
readonly reason?: string | undefined;
|
|
55
|
+
readonly deploymentAllowsRemote?: boolean | undefined;
|
|
56
|
+
};
|
|
57
|
+
readonly runs: {
|
|
58
|
+
readonly running: number;
|
|
59
|
+
readonly orphaned: number;
|
|
60
|
+
};
|
|
61
|
+
readonly missingServices?: readonly string[] | undefined;
|
|
62
|
+
readonly sessionFoldClassifierPresent?: boolean | undefined;
|
|
63
|
+
readonly platformPairing?: PlatformPairing | undefined;
|
|
64
|
+
/** Human-facing total of registered host routes, when the carrier reports one (12 today). */
|
|
65
|
+
readonly hostRouteCount?: {
|
|
66
|
+
readonly mounted: number;
|
|
67
|
+
readonly total: number;
|
|
68
|
+
} | undefined;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Build the frozen DiagnosticsView. Pure; never throws.
|
|
72
|
+
* @param inputs - everything the 7+8 rows read (assembleDoctorInputs probes the live host).
|
|
73
|
+
*/
|
|
74
|
+
export declare function buildDoctorView(inputs: DoctorInputs): DiagnosticsView;
|
|
75
|
+
/** A safe view for a host whose inputs could not be assembled: every row names the failure. */
|
|
76
|
+
export declare function unavailableDoctorView(now: Date, reason: string): DiagnosticsView;
|
|
77
|
+
/** The family version this build carries (nearest package.json above the running module). */
|
|
78
|
+
export declare function familyVersion(): string;
|
|
79
|
+
/**
|
|
80
|
+
* Assemble the view inputs through the live Context's soft probes.
|
|
81
|
+
*
|
|
82
|
+
* Every probe is optional: an absent service becomes a row value (unknown/warn), never a throw. The
|
|
83
|
+
* channel probe uses W5's own `createSubagentChannel` state machinery, so the doctor reports the
|
|
84
|
+
* SAME availability the review/maintenance runs would see.
|
|
85
|
+
* @param ctx - the host context.
|
|
86
|
+
* @param extras - call-site facts the context cannot answer (runs, host version, install form).
|
|
87
|
+
*/
|
|
88
|
+
export declare function assembleDoctorInputs(ctx: Context, extras?: {
|
|
89
|
+
readonly runs?: {
|
|
90
|
+
readonly running: number;
|
|
91
|
+
readonly orphaned: number;
|
|
92
|
+
} | undefined;
|
|
93
|
+
readonly hostVersion?: string | undefined;
|
|
94
|
+
readonly form?: DoctorInputs['form'] | undefined;
|
|
95
|
+
readonly platformPairing?: PlatformPairing | undefined;
|
|
96
|
+
readonly now?: Date | undefined;
|
|
97
|
+
}): Promise<DoctorInputs>;
|
|
98
|
+
//# sourceMappingURL=doctor-view.d.ts.map
|
package/lib/types/doctor.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type PlatformServiceProbe, type ScopedProbeReport } from '@lmzhen/dsh-evolution-core';
|
|
2
|
+
import type { DiagnosticsView } from '@lmzhen/dsh-evolution-core';
|
|
2
3
|
import type { MountedProfile } from './profiles.ts';
|
|
3
4
|
/** P2-20 (audit): the TS-side copy of the install-target tails. The script
|
|
4
5
|
* side (install-layered.mjs, verify-profile-bundles.mjs) imports the list
|
|
@@ -87,12 +88,6 @@ export interface DoctorReport {
|
|
|
87
88
|
foreignProfiles: string[];
|
|
88
89
|
conflicts: string[];
|
|
89
90
|
envIssues: string[];
|
|
90
|
-
/** S0.2 (v37 P0-1): memory files carrying the platform's `{{` prompt-variable
|
|
91
|
-
* syntax. Residual-risk detector — current builds neutralize the INJECTED text
|
|
92
|
-
* (`MemoryStore.renderContext`), but a still-running older build fails every
|
|
93
|
-
* pre-step of every session under this home, and an operator may want to clean
|
|
94
|
-
* the file regardless. */
|
|
95
|
-
memoryIssues: string[];
|
|
96
91
|
/** S4/P-1+P-2 (platform gap, family mitigation): the session-query corpus
|
|
97
92
|
* listing failed — reported verbatim, with the isolation recipe. Empty when the
|
|
98
93
|
* service is absent or answering. */
|
|
@@ -146,6 +141,9 @@ export interface DoctorReport {
|
|
|
146
141
|
/** G5: where the review row's presence came from: the live entry, or the aggregate inference. */
|
|
147
142
|
serviceSource: 'platform' | 'bundles';
|
|
148
143
|
actions: string[];
|
|
144
|
+
/** G6/W8 tail: the frozen DiagnosticsView (7 facts + 8 runtime rows) — the SAME object the
|
|
145
|
+
* diagnostics route and the client card render; `--json` emits it and the text renderer prints it. */
|
|
146
|
+
readonly view: DiagnosticsView;
|
|
149
147
|
}
|
|
150
148
|
/** Profile bundle rows for evolution-family packages across all profiles.
|
|
151
149
|
*
|
|
@@ -162,4 +160,14 @@ export declare function diagnose(ctx: {
|
|
|
162
160
|
mounted?: MountedProfile;
|
|
163
161
|
}): Promise<DoctorReport>;
|
|
164
162
|
export declare function renderDoctorText(report: DoctorReport): string;
|
|
163
|
+
/**
|
|
164
|
+
* G6/W8 tail: render the frozen DiagnosticsView as the doctor's diagnostics section.
|
|
165
|
+
*
|
|
166
|
+
* This is the ONE text face of the 7+8 rows: the same object `--json` and the diagnostics route
|
|
167
|
+
* answer, so the three readers cannot disagree. Legacy install/conflict sections keep their own
|
|
168
|
+
* renderer until the remaining W8-S7 rewrite lands.
|
|
169
|
+
* @param view - the assembled view.
|
|
170
|
+
* @returns the section text (ends with a newline).
|
|
171
|
+
*/
|
|
172
|
+
export declare function renderDiagnosticsText(view: DiagnosticsView): string;
|
|
165
173
|
//# sourceMappingURL=doctor.d.ts.map
|
package/lib/types/registry.d.ts
CHANGED
|
@@ -19,7 +19,16 @@ export interface CommandEntry {
|
|
|
19
19
|
}
|
|
20
20
|
/** The flags the TABLE declares for one subcommand (name → `--flag`), DERIVED from its usage. */
|
|
21
21
|
export declare function flagsOf(name: string): string[];
|
|
22
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* C7 shape, DERIVED: the entry IDENTITY key, and it is HIERARCHICAL (E-08).
|
|
24
|
+
*
|
|
25
|
+
* The key used to be the first token alone, and `flagsOf` finds an entry with `.find(...)` - the FIRST
|
|
26
|
+
* match - so `maintain handoff` / `maintain cancel` and `skill restore` / `skill history` / `skill undo` /
|
|
27
|
+
* `skill pin` all derived the SAME key as their parent and every flag lookup silently saw the parent entry
|
|
28
|
+
* only (nine collisions). A bare parent (`maintain [--timeout]`) keeps its one-token key, so parent lookups
|
|
29
|
+
* still resolve: the one call site that named an entry by a name the key no longer produced was the preset
|
|
30
|
+
* dispatch branch, and it now asks for the entry own key (`preset install`).
|
|
31
|
+
*/
|
|
23
32
|
export declare function entryName(entry: CommandEntry): string;
|
|
24
33
|
/** C7 shape, DERIVED: the flags a `usage` declares, in the form `--flag` (value flags keep their name). */
|
|
25
34
|
export declare function entryFlags(entry: CommandEntry): string[];
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lmzhen/dsh-evolution-commands",
|
|
3
3
|
"description": "Human commands for the evolution family (/evolution pending|curator|maintain|doctor) (community build)",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.27.0",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -26,21 +26,23 @@
|
|
|
26
26
|
],
|
|
27
27
|
"license": "MIT",
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
30
|
-
"@lmzhen/dsh-evolution-approval": "^0.
|
|
31
|
-
"@lmzhen/dsh-evolution-core": "^0.
|
|
32
|
-
"@lmzhen/dsh-evolution-maintenance": "^0.
|
|
29
|
+
"@deepseek-ai/schemastery": "^3.18.5",
|
|
30
|
+
"@lmzhen/dsh-evolution-approval": "^0.27.0",
|
|
31
|
+
"@lmzhen/dsh-evolution-core": "^0.27.0",
|
|
32
|
+
"@lmzhen/dsh-evolution-maintenance": "^0.27.0"
|
|
33
33
|
},
|
|
34
34
|
"optionalDependencies": {
|
|
35
|
-
"@lmzhen/dsh-evolution-agent-preset": "^0.
|
|
35
|
+
"@lmzhen/dsh-evolution-agent-preset": "^0.27.0"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
39
|
-
"@deepseek-ai/dsh-commands": "^0.2.
|
|
40
|
-
"@deepseek-ai/dsh-llm": "^0.2.
|
|
41
|
-
"@lmzhen/dsh-evolution-io": "^0.26.15"
|
|
38
|
+
"@deepseek-ai/cordis": "^4.0.5",
|
|
39
|
+
"@deepseek-ai/dsh-commands": "^0.2.1-rc.1",
|
|
40
|
+
"@deepseek-ai/dsh-llm": "^0.2.1-rc.1"
|
|
42
41
|
},
|
|
43
42
|
"devDependencies": {
|
|
44
|
-
"@deepseek-ai/dsh-commands": "^0.2.
|
|
43
|
+
"@deepseek-ai/dsh-commands": "^0.2.1-rc.1"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"dsh": ">=0.2.1-rc.1 <0.2.2-0"
|
|
45
47
|
}
|
|
46
48
|
}
|