@lmzhen/dsh-evolution-skill-history 0.26.26 → 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/client.js CHANGED
@@ -5,25 +5,146 @@ 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 src/client/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
+ diagnostics: { view: "/api/dsh-evolution/diagnostics" }
32
+ };
33
+ //#endregion
34
+ //#region src/client/route-client.generated.ts
35
+ /** Classify a non-2xx status: 401/403/404 keep their own kind, everything else is a server answer. */
36
+ function classify(status) {
37
+ if (status === 401) return {
38
+ kind: "unauthenticated",
39
+ status: 401
40
+ };
41
+ if (status === 403) return {
42
+ kind: "forbidden",
43
+ status: 403
44
+ };
45
+ if (status === 404) return {
46
+ kind: "unmounted",
47
+ status: 404
48
+ };
49
+ return {
50
+ kind: "server",
51
+ status
52
+ };
53
+ }
54
+ /**
55
+ * One request, no retries and no body parsing for the platform's admission answers.
56
+ * @param doFetch - the fetch in use (the browser's, or a spec's stub).
57
+ * @param path - the exact route path from the generated ROUTES table.
58
+ * @param init - standard RequestInit.
59
+ * @returns the data, or the classified failure (network/parse failures read as 'server').
60
+ */
61
+ async function requestJson(doFetch, path, init) {
62
+ let response;
63
+ try {
64
+ response = await doFetch(path, init);
65
+ } catch (error) {
66
+ return {
67
+ ok: false,
68
+ failure: {
69
+ kind: "server",
70
+ status: 0,
71
+ detail: error instanceof Error ? error.message : String(error)
72
+ }
73
+ };
74
+ }
75
+ if (!response.ok) return {
76
+ ok: false,
77
+ failure: classify(response.status)
78
+ };
79
+ let body;
80
+ try {
81
+ body = await response.json();
82
+ } catch {
83
+ return {
84
+ ok: false,
85
+ failure: {
86
+ kind: "server",
87
+ status: response.status
88
+ }
89
+ };
90
+ }
91
+ if (typeof body === "object" && body !== null) {
92
+ const record = body;
93
+ if (record.ok === true) return {
94
+ ok: true,
95
+ data: record.data
96
+ };
97
+ if (record.ok === false && typeof record.message === "string" && record.message !== "") return {
98
+ ok: false,
99
+ failure: {
100
+ kind: "refused",
101
+ code: typeof record.code === "string" && record.code !== "" ? record.code : "refused",
102
+ message: record.message
103
+ }
104
+ };
105
+ }
106
+ return {
107
+ ok: false,
108
+ failure: {
109
+ kind: "server",
110
+ status: response.status
111
+ }
112
+ };
113
+ }
114
+ /** The one-sentence rendering legacy callers attach to a classified failure. */
115
+ function failureMessage(failure) {
116
+ switch (failure.kind) {
117
+ case "refused": return failure.message;
118
+ case "server": return failure.status === 0 ? failure.detail === void 0 || failure.detail === "" ? "网络错误:无法连接宿主" : failure.detail : "HTTP " + String(failure.status);
119
+ case "unauthenticated": return "HTTP 401";
120
+ case "forbidden": return "HTTP 403";
121
+ case "unmounted": return "HTTP 404";
122
+ }
123
+ }
124
+ //#endregion
8
125
  //#region src/client/api.ts
9
126
  /**
10
127
  * The host routes this bundle calls, and the ONE place a request is made.
11
128
  *
12
- * The paths mirror the host half's `SKILL_HISTORY_ROUTES` (a spec asserts the two sides agree). Every
13
- * refusal arrives as `ok:false` plus the curator's own sentence, so the panel shows the same words the
14
- * slash command prints instead of inventing a second vocabulary.
129
+ * G5/W7: the paths come from the generated client route table (rendered from evolution-core
130
+ * HOST_ROUTES), and the transport classifier is the shared generated route-client — the browser half
131
+ * can no longer drift from the host table or grow a second HTTP vocabulary.
15
132
  * @module @lmzhen/dsh-evolution-skill-history/client
16
133
  */
17
- /** Route paths, mirrored from the host half. */
18
- const HOST_ROUTES = {
19
- skills: "/api/dsh-evolution/skill-history/skills",
20
- versions: "/api/dsh-evolution/skill-history/versions",
21
- diff: "/api/dsh-evolution/skill-history/versions/diff",
22
- body: "/api/dsh-evolution/skill-history/versions/body",
23
- undo: "/api/dsh-evolution/skill-history/undo"
134
+ /** Route paths: the generated mirror of the host's HOST_ROUTES (one declaration in core). */
135
+ const HOST_ROUTES = ROUTES["skill-history"];
136
+ /**
137
+ * A refusal the host reported, carrying its own sentence (the curator's, not ours) and the classified
138
+ * four-state failure so the panel can render the shared failure block (G5/W7 S6b).
139
+ */
140
+ var SkillHistoryRefusal = class extends Error {
141
+ failure;
142
+ constructor(failure) {
143
+ super(failureMessage(failure));
144
+ this.name = "SkillHistoryRefusal";
145
+ this.failure = failure;
146
+ }
24
147
  };
25
- /** A refusal the host reported, carrying its own sentence (the curator's, not ours). */
26
- var SkillHistoryRefusal = class extends Error {};
27
148
  /**
28
149
  * Build the panel's face over one fetch implementation.
29
150
  * @param fetchImpl - the fetch to use; a spec passes a stub.
@@ -31,11 +152,9 @@ window.__ModuleLoader__.load({
31
152
  */
32
153
  function createSkillHistoryApi(fetchImpl = fetch) {
33
154
  const request = async (path, init) => {
34
- const response = await fetchImpl(path, init);
35
- if (!response.ok) throw new SkillHistoryRefusal("HTTP " + String(response.status));
36
- const body = await response.json();
37
- if (body.ok !== true) throw new SkillHistoryRefusal(body.message ?? "request refused");
38
- return body.data;
155
+ const answer = await requestJson(fetchImpl, path, init);
156
+ if (!answer.ok) throw new SkillHistoryRefusal(answer.failure);
157
+ return answer.data;
39
158
  };
40
159
  return {
41
160
  skills: async () => await request(HOST_ROUTES.skills),
@@ -83,6 +202,11 @@ window.__ModuleLoader__.load({
83
202
  loading: "读取中…",
84
203
  refresh: "刷新",
85
204
  undo: "回退到此版",
205
+ "failure.unauthorized": "需要登录:重新打开这个页面完成登录后再试。",
206
+ "failure.forbidden": "平台拒绝了这个请求(身份不是 owner)。",
207
+ "failure.unmounted": "该功能没有挂载。",
208
+ "failure.server": "读取失败,可以重试。",
209
+ "fallback.hint": "可以改用命令:{command}",
86
210
  "undo.confirm": "确认回退",
87
211
  "undo.no": "取消",
88
212
  current: "当前",
@@ -158,6 +282,11 @@ window.__ModuleLoader__.load({
158
282
  loading: "Loading…",
159
283
  refresh: "Refresh",
160
284
  undo: "Restore this version",
285
+ "failure.unauthorized": "Sign-in is required: reopen this page and sign in, then retry.",
286
+ "failure.forbidden": "The platform refused this request (not the owner principal).",
287
+ "failure.unmounted": "This feature is not mounted.",
288
+ "failure.server": "The read failed; you can retry.",
289
+ "fallback.hint": "Command fallback: {command}",
161
290
  "undo.confirm": "Confirm restore",
162
291
  "undo.no": "Cancel",
163
292
  current: "Current",
@@ -848,6 +977,51 @@ window.__ModuleLoader__.load({
848
977
  ] }), toggle), diffView(face, diff.diff, mode));
849
978
  }
850
979
  //#endregion
980
+ //#region src/client/states.generated.ts
981
+ /** A neutral/ warn/ fail block; a fail block is role=alert so assistive tech announces it. */
982
+ function Block(props) {
983
+ const tone = props.tone ?? "neutral";
984
+ return (0, react.createElement)("div", {
985
+ "data-tone": tone,
986
+ ...tone === "fail" ? { role: "alert" } : {}
987
+ }, props.title === void 0 ? null : (0, react.createElement)("div", { "data-tone-title": tone }, props.title), props.children);
988
+ }
989
+ /** One classified failure as a reader-facing sentence. */
990
+ function failureText(failure, t) {
991
+ switch (failure.kind) {
992
+ case "unauthenticated": return t("failureUnauthorized");
993
+ case "forbidden": return t("failureForbidden");
994
+ case "unmounted": return t("failureUnmounted");
995
+ case "server": return failure.status === 0 ? t("failureServer") : t("failureServer") + " (HTTP " + String(failure.status) + ")";
996
+ case "refused": return failure.message;
997
+ }
998
+ }
999
+ /** The failed-state block: sentence + (when the surface exists but is unreachable) the command fallback. */
1000
+ function FailureBlock(props) {
1001
+ const hint = props.fallback === void 0 ? null : (0, react.createElement)("div", { "data-tone-hint": "true" }, props.t("fallbackHint", { command: props.fallback.command }));
1002
+ return Block({
1003
+ tone: "fail",
1004
+ title: failureText(props.failure, props.t),
1005
+ children: hint
1006
+ });
1007
+ }
1008
+ //#endregion
1009
+ //#region src/client/fallbacks.generated.ts
1010
+ /**
1011
+ * The command-face fallback for every Host surface, generated from evolution-commands'
1012
+ * COMMAND_FALLBACKS (the ONE home). When the platform's connection carrier is absent (E-325) the
1013
+ * panels render this hint instead of a bare HTTP failure — same use case, command face.
1014
+ */
1015
+ const FALLBACKS = {
1016
+ approval: { command: "/evolution pending … /evolution approve <id> … /evolution reject <id> … /evolution release <id>" },
1017
+ "skill-history": { command: "/evolution restore <name> [v](查看:/evolution skills history <name>)" },
1018
+ "skill-health": { command: "/evolution skills refresh(健康行:/evolution doctor)" },
1019
+ diagnostics: {
1020
+ command: "/evolution doctor",
1021
+ platformCli: "dsh --json /evolution doctor"
1022
+ }
1023
+ };
1024
+ //#endregion
851
1025
  //#region src/client/Panel.ts
852
1026
  /**
853
1027
  * The panel: one read, one open skill, one open row — and the faces that draw them.
@@ -865,9 +1039,29 @@ window.__ModuleLoader__.load({
865
1039
  * @param face - copy plus the data callbacks.
866
1040
  * @returns the panel element.
867
1041
  */
1042
+ /** Fill the generated block's `{slot}` copies with this bundle's dictionary. */
1043
+ function fillCopy(template, values) {
1044
+ return template.replace(/\{(\w+)\}/g, (match, slot) => {
1045
+ const value = values[slot];
1046
+ return value === void 0 ? match : String(value);
1047
+ });
1048
+ }
1049
+ /** The shared four-state failure block, spoken through this bundle's keys. */
1050
+ function failureBlock(t, failure) {
1051
+ const translate = (key, values) => {
1052
+ const text = t(key);
1053
+ return values === void 0 ? text : fillCopy(text, values);
1054
+ };
1055
+ return FailureBlock({
1056
+ failure,
1057
+ t: translate,
1058
+ fallback: FALLBACKS["skill-history"]
1059
+ });
1060
+ }
868
1061
  function SkillHistoryPanel(face) {
869
1062
  const [skills, setSkills] = (0, react.useState)([]);
870
1063
  const [skillsLoaded, setSkillsLoaded] = (0, react.useState)(false);
1064
+ const [failure, setFailure] = (0, react.useState)(void 0);
871
1065
  const [selected, setSelected] = (0, react.useState)(void 0);
872
1066
  const [payload, setPayload] = (0, react.useState)(void 0);
873
1067
  const [versionsFailed, setVersionsFailed] = (0, react.useState)(void 0);
@@ -882,6 +1076,10 @@ window.__ModuleLoader__.load({
882
1076
  const failed = (error) => {
883
1077
  setNoteError(true);
884
1078
  setNoteText(face.t("error.hint") + "\n" + face.t("error") + ": " + (error instanceof Error ? error.message : String(error)));
1079
+ setFailure(error instanceof SkillHistoryRefusal ? error.failure : {
1080
+ kind: "server",
1081
+ status: 0
1082
+ });
885
1083
  };
886
1084
  const succeeded = (message) => {
887
1085
  setNoteError(false);
@@ -1055,7 +1253,7 @@ window.__ModuleLoader__.load({
1055
1253
  };
1056
1254
  return (0, react.createElement)("div", { className: "evo-hist-root" }, (0, react.createElement)("aside", { className: "evo-hist-aside" }, searchField(face, query, setQuery), (0, react.createElement)("div", { className: "evo-hist-list" }, skills.length === 0 ? emptyState(face.t(skillsLoaded ? "empty.skills" : "loading")) : visible.length === 0 ? emptyState(face.t("search.none")) : visible.map((skill) => skillLine(face, skill, skill.name === selected, () => {
1057
1255
  open(skill.name);
1058
- })))), (0, react.createElement)("div", { className: "evo-hist-main" }, panelHead(face, refresh), notice(noteText, noteError), selected === void 0 ? [emptyState(face.t(skills.length === 0 ? skillsLoaded ? "empty.skills" : "loading" : "empty.pick"), "empty"), (0, react.createElement)("p", {
1256
+ })))), (0, react.createElement)("div", { className: "evo-hist-main" }, panelHead(face, refresh), notice(noteText, noteError), failure === void 0 ? null : (0, react.createElement)("div", { className: "evo-hist-failure" }, failureBlock(face.t, failure)), selected === void 0 ? [emptyState(face.t(skills.length === 0 ? skillsLoaded ? "empty.skills" : "loading" : "empty.pick"), "empty"), (0, react.createElement)("p", {
1059
1257
  key: "hint",
1060
1258
  className: "evo-hist-hint"
1061
1259
  }, face.t("hint"))] : loaded ? null : emptyState(versionsFailed === void 0 ? face.t("loading") : `${face.t("empty.versions.failed")} — ${versionsFailed}`), loaded ? (0, react.createElement)("div", null, group(face.t("group.content"), payload.content, void 0), group(face.t("group.support"), payload.support, face.t("group.support.note")), payload.content.length === 0 && payload.support.length === 0 ? (0, react.createElement)("p", { className: "evo-hist-hint" }, face.t("empty.versions")) : null) : null));
package/lib/index.js CHANGED
@@ -1,4 +1,5 @@
1
- import { contentHash, displayBodyOf, elapsedSince, errorText, isLandmark, isLoopbackRequest, latestVersionAt, partitionVersions, readJsonObject, skillRowCells, textDiffFacts, versionActionKind, versionRowCells, writeJson } from "@lmzhen/dsh-evolution-core";
1
+ import { HOST_ROUTES, json, methodNotAllowed, readJsonObject, registerHostRoutes } from "@lmzhen/dsh-evolution-core/http-routes";
2
+ import { contentHash, displayBodyOf, elapsedSince, errorText, isLandmark, latestVersionAt, partitionVersions, skillRowCells, textDiffFacts, versionActionKind, versionRowCells } from "@lmzhen/dsh-evolution-core";
2
3
  //#region lib/types/unit-undoable.js
3
4
  /**
4
5
  * G3b: undoability is a property of the UNIT, not of one entry.
@@ -36,22 +37,20 @@ function undoableFor(entry, flags, liveHash) {
36
37
  //#endregion
37
38
  //#region lib/types/routes.js
38
39
  /**
39
- * The skill-history HTTP surface: six loopback routes over the curator's read/write seam (five reads plus the one write).
40
+ * The skill-history HTTP surface: six exact Fetch routes over the curator's read/write seam (five
41
+ * reads plus the one write).
42
+ *
43
+ * W6/G4: the carrier is the platform's Host connection data plane — the fence, browser auth and
44
+ * owner-only admission are applied by the platform BEFORE `fetch(request)` runs, so this module has
45
+ * no 403/401 branch. The wire envelope the browser half reads is unchanged.
40
46
  *
41
47
  * Layering: this module maps a route to a USE CASE and does nothing else. "Which artifact does this
42
48
  * version hold" and "how do the two chains split" live in evolution-core; the only write is
43
49
  * curator.undo. The panel never writes the skill tree itself.
44
50
  * @module @lmzhen/dsh-evolution-skill-history/routes
45
51
  */
46
- /** Route paths. The client bundle mirrors these literals; a spec asserts the two sides agree. */
47
- const SKILL_HISTORY_ROUTES = {
48
- skills: "/api/dsh-evolution/skill-history/skills",
49
- versions: "/api/dsh-evolution/skill-history/versions",
50
- diff: "/api/dsh-evolution/skill-history/versions/diff",
51
- body: "/api/dsh-evolution/skill-history/versions/body",
52
- undo: "/api/dsh-evolution/skill-history/undo",
53
- health: "/api/dsh-evolution/skill-history/health"
54
- };
52
+ /** Route paths: the ONE declaration lives in core (W1); the client constants are generated from it. */
53
+ const SKILL_HISTORY_ROUTES = HOST_ROUTES.skillHistory;
55
54
  /**
56
55
  * How much of one version's body the read route hands back. A bound on the ANSWER, not on what the
57
56
  * deployment may store: a long skill arrives as its head plus `truncated`, so the reader is told.
@@ -60,49 +59,29 @@ const MAX_BODY_CHARS = 2e4;
60
59
  /**
61
60
  * Build the six routes over the curator seam.
62
61
  * @param services - the host services the handlers read.
63
- * @returns the route list for ctx.webServer.register.
62
+ * @param _audit - the registration audit (read routes record nothing; undo's decision stays the
63
+ * curator's own — W6 keeps the ledger to the approval surface).
64
+ * @returns the route list for `registerHostRoutes`.
64
65
  */
65
- function makeSkillHistoryRoutes(services) {
66
+ function makeSkillHistoryRoutes(services, _audit) {
66
67
  /** Run one handler body with the curator, or answer the family's E-302 sentence when it is absent. */
67
- const withCurator = async (res, run) => {
68
+ const withCurator = async (run) => {
68
69
  const curator = services.getCurator();
69
- if (curator === void 0) {
70
- writeJson(res, 200, {
71
- ok: false,
72
- code: "curator-not-mounted",
73
- message: errorText("e-302-curator-service-not-mounted")
74
- });
75
- return;
76
- }
77
- await run(curator);
78
- };
79
- /** Guard: trust fence, then method. Every refusal is a JSON body, never an empty response. */
80
- const guard = (req, res, method) => {
81
- if (!isLoopbackRequest(req)) {
82
- writeJson(res, 403, {
83
- ok: false,
84
- code: "forbidden",
85
- message: "禁止访问:仅限回环地址"
86
- });
87
- return false;
88
- }
89
- if (req.method !== method) {
90
- writeJson(res, 405, {
91
- ok: false,
92
- code: "method-not-allowed",
93
- message: "方法不允许:" + (req.method ?? "")
94
- });
95
- return false;
96
- }
97
- return true;
70
+ if (curator === void 0) return json(200, {
71
+ ok: false,
72
+ code: "curator-not-mounted",
73
+ message: errorText("e-302-curator-service-not-mounted")
74
+ });
75
+ return await run(curator);
98
76
  };
99
77
  return [
100
78
  {
101
- kind: "exact",
102
79
  path: SKILL_HISTORY_ROUTES.skills,
103
- handler: async (req, res) => {
104
- if (!guard(req, res, "GET")) return;
105
- await withCurator(res, async (curator) => {
80
+ methods: ["GET"],
81
+ requestBody: "buffered",
82
+ fetch: (request) => {
83
+ if (request.method !== "GET") return Promise.resolve(methodNotAllowed(["GET"]));
84
+ return withCurator(async (curator) => {
106
85
  const listed = await curator.skills.list();
107
86
  const withHistory = [];
108
87
  const now = Date.now();
@@ -125,7 +104,7 @@ function makeSkillHistoryRoutes(services) {
125
104
  cells: skillRowCells(row)
126
105
  });
127
106
  }
128
- writeJson(res, 200, {
107
+ return json(200, {
129
108
  ok: true,
130
109
  data: withHistory
131
110
  });
@@ -133,20 +112,18 @@ function makeSkillHistoryRoutes(services) {
133
112
  }
134
113
  },
135
114
  {
136
- kind: "exact",
137
115
  path: SKILL_HISTORY_ROUTES.versions,
138
- handler: async (req, res) => {
139
- if (!guard(req, res, "GET")) return;
140
- const name = new URL(req.url ?? "/", "http://loopback").searchParams.get("name")?.trim() ?? "";
141
- if (name === "") {
142
- writeJson(res, 400, {
143
- ok: false,
144
- code: "bad-request",
145
- message: "缺少 name"
146
- });
147
- return;
148
- }
149
- await withCurator(res, async (curator) => {
116
+ methods: ["GET"],
117
+ requestBody: "buffered",
118
+ fetch: (request) => {
119
+ if (request.method !== "GET") return Promise.resolve(methodNotAllowed(["GET"]));
120
+ const name = new URL(request.url).searchParams.get("name")?.trim() ?? "";
121
+ if (name === "") return Promise.resolve(json(400, {
122
+ ok: false,
123
+ code: "bad-request",
124
+ message: "缺少 name"
125
+ }));
126
+ return withCurator(async (curator) => {
150
127
  const groups = partitionVersions(await curator.history(name));
151
128
  const live = await curator.skills.read(name);
152
129
  const liveHash = live === null ? null : contentHash(live);
@@ -161,7 +138,7 @@ function makeSkillHistoryRoutes(services) {
161
138
  };
162
139
  };
163
140
  const unitFlags = unitUndoableFlags([...groups.content, ...groups.support], liveHash);
164
- writeJson(res, 200, {
141
+ return json(200, {
165
142
  ok: true,
166
143
  data: {
167
144
  content: groups.content.map((entry, index) => {
@@ -189,47 +166,39 @@ function makeSkillHistoryRoutes(services) {
189
166
  }
190
167
  },
191
168
  {
192
- kind: "exact",
193
169
  path: SKILL_HISTORY_ROUTES.diff,
194
- handler: async (req, res) => {
195
- if (!guard(req, res, "GET")) return;
196
- const params = new URL(req.url ?? "/", "http://loopback").searchParams;
170
+ methods: ["GET"],
171
+ requestBody: "buffered",
172
+ fetch: (request) => {
173
+ if (request.method !== "GET") return Promise.resolve(methodNotAllowed(["GET"]));
174
+ const params = new URL(request.url).searchParams;
197
175
  const name = params.get("name")?.trim() ?? "";
198
176
  const rawV = params.get("v")?.trim() ?? "";
199
177
  const v = Number.parseInt(rawV, 10);
200
- if (name === "" || !/^\d+$/.test(rawV) || !Number.isInteger(v) || v < 1) {
201
- writeJson(res, 400, {
202
- ok: false,
203
- code: "bad-request",
204
- message: "需要 name 与正整数 v"
205
- });
206
- return;
207
- }
208
- await withCurator(res, async (curator) => {
178
+ if (name === "" || !/^\d+$/.test(rawV) || !Number.isInteger(v) || v < 1) return Promise.resolve(json(400, {
179
+ ok: false,
180
+ code: "bad-request",
181
+ message: "需要 name 与正整数 v"
182
+ }));
183
+ return withCurator(async (curator) => {
209
184
  const groups = partitionVersions(await curator.history(name));
210
185
  const index = groups.content.findIndex((entry) => entry.v === v);
211
- if (index === -1) {
212
- writeJson(res, 404, {
213
- ok: false,
214
- code: "not-found",
215
- message: "该版本不在「" + name + "」的记录历史里"
216
- });
217
- return;
218
- }
186
+ if (index === -1) return json(404, {
187
+ ok: false,
188
+ code: "not-found",
189
+ message: "该版本不在「" + name + "」的记录历史里"
190
+ });
219
191
  const entry = groups.content[index];
220
192
  const previous = (entry === void 0 || entry.beforeHash === void 0 ? void 0 : groups.content.find((candidate) => candidate.hash === entry.beforeHash)) ?? (index === 0 ? void 0 : groups.content[index - 1]);
221
193
  const after = entry === void 0 ? null : await curator.skills.readVersion(name, v);
222
194
  const before = previous === void 0 ? "" : await curator.skills.readVersion(name, previous.v);
223
- if (after === null || before === null) {
224
- writeJson(res, 404, {
225
- ok: false,
226
- code: "not-found",
227
- message: "该版本的存储内容读不出来"
228
- });
229
- return;
230
- }
195
+ if (after === null || before === null) return json(404, {
196
+ ok: false,
197
+ code: "not-found",
198
+ message: "该版本的存储内容读不出来"
199
+ });
231
200
  const facts = textDiffFacts(before, after, "SKILL.md");
232
- writeJson(res, 200, {
201
+ return json(200, {
233
202
  ok: true,
234
203
  data: {
235
204
  v,
@@ -241,34 +210,29 @@ function makeSkillHistoryRoutes(services) {
241
210
  }
242
211
  },
243
212
  {
244
- kind: "exact",
245
213
  path: SKILL_HISTORY_ROUTES.body,
246
- handler: async (req, res) => {
247
- if (!guard(req, res, "GET")) return;
248
- const params = new URL(req.url ?? "/", "http://loopback").searchParams;
214
+ methods: ["GET"],
215
+ requestBody: "buffered",
216
+ fetch: (request) => {
217
+ if (request.method !== "GET") return Promise.resolve(methodNotAllowed(["GET"]));
218
+ const params = new URL(request.url).searchParams;
249
219
  const name = params.get("name")?.trim() ?? "";
250
220
  const rawV = params.get("v")?.trim() ?? "";
251
221
  const v = Number.parseInt(rawV, 10);
252
- if (name === "" || !/^\d+$/.test(rawV) || !Number.isInteger(v) || v < 1) {
253
- writeJson(res, 400, {
222
+ if (name === "" || !/^\d+$/.test(rawV) || !Number.isInteger(v) || v < 1) return Promise.resolve(json(400, {
223
+ ok: false,
224
+ code: "bad-request",
225
+ message: "需要 name 与正整数 v"
226
+ }));
227
+ return withCurator(async (curator) => {
228
+ const text = await curator.skills.readVersion(name, v);
229
+ if (text === null) return json(404, {
254
230
  ok: false,
255
- code: "bad-request",
256
- message: "需要 name 与正整数 v"
231
+ code: "not-found",
232
+ message: "该版本的存储内容读不出来"
257
233
  });
258
- return;
259
- }
260
- await withCurator(res, async (curator) => {
261
- const text = await curator.skills.readVersion(name, v);
262
- if (text === null) {
263
- writeJson(res, 404, {
264
- ok: false,
265
- code: "not-found",
266
- message: "该版本的存储内容读不出来"
267
- });
268
- return;
269
- }
270
234
  const truncated = text.length > MAX_BODY_CHARS;
271
- writeJson(res, 200, {
235
+ return json(200, {
272
236
  ok: true,
273
237
  data: {
274
238
  v,
@@ -281,89 +245,66 @@ function makeSkillHistoryRoutes(services) {
281
245
  }
282
246
  },
283
247
  {
284
- kind: "exact",
285
248
  path: SKILL_HISTORY_ROUTES.undo,
286
- handler: async (req, res) => {
287
- if (!guard(req, res, "POST")) return;
288
- const payload = await readJsonObject(req);
289
- const name = typeof payload?.name === "string" ? payload.name.trim() : "";
290
- if (name === "") {
291
- writeJson(res, 400, {
249
+ methods: ["POST"],
250
+ requestBody: "buffered",
251
+ fetch: (request) => {
252
+ if (request.method !== "POST") return Promise.resolve(methodNotAllowed(["POST"]));
253
+ return (async () => {
254
+ const payload = await readJsonObject(request);
255
+ const name = typeof payload?.name === "string" ? payload.name.trim() : "";
256
+ if (name === "") return json(400, {
292
257
  ok: false,
293
258
  code: "bad-request",
294
259
  message: "缺少 name"
295
260
  });
296
- return;
297
- }
298
- const raw = payload?.v;
299
- if (raw !== void 0 && (typeof raw !== "number" || !Number.isInteger(raw) || raw < 1)) {
300
- writeJson(res, 400, {
261
+ const raw = payload?.v;
262
+ if (raw !== void 0 && (typeof raw !== "number" || !Number.isInteger(raw) || raw < 1)) return json(400, {
301
263
  ok: false,
302
264
  code: "bad-request",
303
265
  message: "v 必须是正整数"
304
266
  });
305
- return;
306
- }
307
- await withCurator(res, async (curator) => {
308
- const result = raw === void 0 ? await curator.undo(name) : await curator.undo(name, raw);
309
- writeJson(res, 200, result.ok ? {
310
- ok: true,
311
- data: result
312
- } : {
313
- ok: false,
314
- code: "undo-refused",
315
- message: result.message
267
+ return withCurator(async (curator) => {
268
+ const result = raw === void 0 ? await curator.undo(name) : await curator.undo(name, raw);
269
+ return json(200, result.ok ? {
270
+ ok: true,
271
+ data: result
272
+ } : {
273
+ ok: false,
274
+ code: "undo-refused",
275
+ message: result.message
276
+ });
316
277
  });
317
- });
278
+ })();
318
279
  }
319
280
  },
320
281
  {
321
- kind: "exact",
322
282
  path: SKILL_HISTORY_ROUTES.health,
323
- handler: (req, res) => {
324
- if (!guard(req, res, "GET")) return;
325
- writeJson(res, 200, {
283
+ methods: ["GET"],
284
+ requestBody: "buffered",
285
+ fetch: (request) => {
286
+ if (request.method !== "GET") return Promise.resolve(methodNotAllowed(["GET"]));
287
+ return Promise.resolve(json(200, {
326
288
  ok: true,
327
289
  data: { surface: "skill-history" }
328
- });
290
+ }));
329
291
  }
330
292
  }
331
293
  ];
332
294
  }
333
295
  //#endregion
334
296
  //#region lib/types/index.js
335
- /**
336
- * Host half of the skill-history surface: it mounts the four loopback routes the sidebar panel reads
337
- * and writes, and it owns nothing else.
338
- *
339
- * The version FACTS come from evolution-core (which artifact a version holds, how the body chain and
340
- * the support-file chain split) and the only write is the curator's undo, so this row carries no state
341
- * of its own: unloading it removes the routes and leaves the feature's data untouched.
342
- * @module @lmzhen/dsh-evolution-skill-history
343
- */
344
297
  const name = "evolution-skill-history";
345
298
  /** No declared service: a headless profile must not leave a pending row, and the curator is resolved
346
- * per request. The web server is waited on INSIDE apply (see below), not declared here. */
299
+ * per request; `connection` is waited on inside registerHostRoutes, not demanded here. */
347
300
  const inject = [];
348
301
  /**
349
- * Wait for the web server, then mount the routes as ONE effect (disposers run on unload and on HMR).
350
- *
351
- * **Why `ctx.inject` and not `ctx.get`**: profile rows apply in file order, so at this row's apply time
352
- * the web server may not exist yet — `ctx.get('webServer')` returning undefined is a TIMING fact, not a
353
- * verdict, and acting on it (0.11.1 shipped that way) left the routes permanently unregistered while the
354
- * panel rendered and every call answered HTTP 404. Waiting on the service mounts the routes whenever it
355
- * arrives, and a profile that never gets one simply never runs this callback — inert, never pending.
356
- * @param ctx - the host context; the web server and curator arrive through it.
302
+ * Mount the routes as ONE owner effect (disposers run on unload and on HMR). A profile whose host has
303
+ * no connection service simply never runs the callback — inert, never pending.
304
+ * @param ctx - the host context; the connection and curator arrive through it.
357
305
  */
358
306
  function apply(ctx) {
359
- ctx.inject(["webServer"], (scope) => {
360
- scope.effect(() => {
361
- const disposers = makeSkillHistoryRoutes({ getCurator: () => scope.get("evolutionCurator") }).map((route) => scope.webServer.register(route));
362
- return () => {
363
- for (const dispose of disposers) dispose();
364
- };
365
- }, "evolution-skill-history: routes");
366
- });
307
+ registerHostRoutes(ctx, (audit) => makeSkillHistoryRoutes({ getCurator: () => ctx.get("evolutionCurator") }, audit));
367
308
  }
368
309
  //#endregion
369
310
  export { apply, inject, name };
@@ -11,7 +11,7 @@
11
11
  */
12
12
  import { type ReactNode } from 'react';
13
13
  import { type Copy } from './chrome.ts';
14
- import type { SkillRow, VersionBodyRow, VersionDiffRow, VersionsPayload } from './api.ts';
14
+ import { type SkillRow, type VersionBodyRow, type VersionDiffRow, type VersionsPayload } from './api.ts';
15
15
  import type { MarkdownWords } from './markdown.ts';
16
16
  /** What the panel is handed: the dictionary seat, the renderer's words, and the host's callbacks. */
17
17
  export interface PanelFace extends Copy {
@@ -23,10 +23,5 @@ export interface PanelFace extends Copy {
23
23
  readonly loadBody: (name: string, v: number) => Promise<VersionBodyRow>;
24
24
  readonly undo: (name: string, v?: number) => Promise<string>;
25
25
  }
26
- /**
27
- * The panel body.
28
- * @param face - copy plus the data callbacks.
29
- * @returns the panel element.
30
- */
31
26
  export declare function SkillHistoryPanel(face: PanelFace): ReactNode;
32
27
  //# sourceMappingURL=Panel.d.ts.map
@@ -1,18 +1,12 @@
1
- /**
2
- * The host routes this bundle calls, and the ONE place a request is made.
3
- *
4
- * The paths mirror the host half's `SKILL_HISTORY_ROUTES` (a spec asserts the two sides agree). Every
5
- * refusal arrives as `ok:false` plus the curator's own sentence, so the panel shows the same words the
6
- * slash command prints instead of inventing a second vocabulary.
7
- * @module @lmzhen/dsh-evolution-skill-history/client
8
- */
9
- /** Route paths, mirrored from the host half. */
1
+ import type { RouteFailure } from './route-client.generated.ts';
2
+ /** Route paths: the generated mirror of the host's HOST_ROUTES (one declaration in core). */
10
3
  export declare const HOST_ROUTES: {
11
4
  readonly skills: "/api/dsh-evolution/skill-history/skills";
12
5
  readonly versions: "/api/dsh-evolution/skill-history/versions";
13
6
  readonly diff: "/api/dsh-evolution/skill-history/versions/diff";
14
7
  readonly body: "/api/dsh-evolution/skill-history/versions/body";
15
8
  readonly undo: "/api/dsh-evolution/skill-history/undo";
9
+ readonly health: "/api/dsh-evolution/skill-history/health";
16
10
  };
17
11
  /** One changed region, as the host reports it (the platform diff card's own shape). */
18
12
  export interface DiffHunkRow {
@@ -129,8 +123,13 @@ export interface SkillHistoryApi {
129
123
  readonly diff: (name: string, v: number) => Promise<VersionDiffRow>;
130
124
  readonly body: (name: string, v: number) => Promise<VersionBodyRow>;
131
125
  }
132
- /** A refusal the host reported, carrying its own sentence (the curator's, not ours). */
126
+ /**
127
+ * A refusal the host reported, carrying its own sentence (the curator's, not ours) and the classified
128
+ * four-state failure so the panel can render the shared failure block (G5/W7 S6b).
129
+ */
133
130
  export declare class SkillHistoryRefusal extends Error {
131
+ readonly failure: RouteFailure;
132
+ constructor(failure: RouteFailure);
134
133
  }
135
134
  /**
136
135
  * Build the panel's face over one fetch implementation.
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Literal mirror of the frozen Host diagnostics contract (W1 §2.8 / core diagnostics.ts). The browser
3
+ * build keeps every @deepseek-ai/* specifier external, so the client CANNOT import the host type — the
4
+ * shape is mirrored mechanically here instead. The key unions are rendered from core diagnostics.ts by
5
+ * the generator, so a key change regenerates all three copies (a stale copy fails --check).
6
+ */
7
+ export type DiagnosticsSchemaVersion = 2;
8
+ export type DiagnosticsFactKey = 'platform-line' | 'family-version' | 'install-form' | 'preset-ids' | 'readonly-contract' | 'upgrade-order' | 'fallback-map';
9
+ export type DiagnosticsRuntimeKey = 'subagent-channel' | 'restricted-preset' | 'host-surface' | 'session-fold' | 'platform-services' | 'runs' | 'platform-pairing' | 'install-form-detected';
10
+ export type DiagnosticStatus = 'ok' | 'warn' | 'fail' | 'unknown';
11
+ export interface DocFactRow {
12
+ readonly key: DiagnosticsFactKey;
13
+ readonly label: string;
14
+ readonly value: string;
15
+ readonly home: string;
16
+ }
17
+ export interface DiagnosticRow {
18
+ readonly key: DiagnosticsRuntimeKey;
19
+ readonly label: string;
20
+ readonly status: DiagnosticStatus;
21
+ readonly detail?: string;
22
+ readonly remedy?: string;
23
+ }
24
+ export interface DiagnosticsView {
25
+ readonly schemaVersion: DiagnosticsSchemaVersion;
26
+ readonly generatedAt: string;
27
+ readonly facts: readonly DocFactRow[];
28
+ readonly runtime: readonly DiagnosticRow[];
29
+ }
30
+ /** The installation form the doctor reports (W8 install-form-detected row). */
31
+ export type InstallFormId = 'M1' | 'M2' | 'M3' | 'M4';
32
+ /** One surface's command-face path(s) — the client `fallbacks.generated.ts` table's row type. */
33
+ export interface FallbackHint {
34
+ readonly command: string;
35
+ readonly platformCli?: string;
36
+ }
37
+ //# sourceMappingURL=contracts.generated.d.ts.map
@@ -0,0 +1,8 @@
1
+ import type { FallbackHint } from './contracts.generated.ts';
2
+ /**
3
+ * The command-face fallback for every Host surface, generated from evolution-commands'
4
+ * COMMAND_FALLBACKS (the ONE home). When the platform's connection carrier is absent (E-325) the
5
+ * panels render this hint instead of a bare HTTP failure — same use case, command face.
6
+ */
7
+ export declare const FALLBACKS: Readonly<Record<'approval' | 'skill-history' | 'skill-health' | 'diagnostics', FallbackHint>>;
8
+ //# sourceMappingURL=fallbacks.generated.d.ts.map
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The four-state transport classifier every family client shares (W7 §2). The platform owns
3
+ * admission (401/403/404 are its answers, not ours), so the client reads the STATUS only for those
4
+ * three and never tries to interpret a platform body. Everything the family route itself answers
5
+ * keeps its business envelope: 2xx + {ok:true,data} or 2xx + {ok:false,code,message}.
6
+ */
7
+ export type RouteFailure = {
8
+ readonly kind: 'unauthenticated';
9
+ readonly status: 401;
10
+ } | {
11
+ readonly kind: 'forbidden';
12
+ readonly status: 403;
13
+ } | {
14
+ readonly kind: 'unmounted';
15
+ readonly status: 404;
16
+ } | {
17
+ readonly kind: 'server';
18
+ readonly status: number;
19
+ readonly detail?: string;
20
+ } | {
21
+ readonly kind: 'refused';
22
+ readonly code: string;
23
+ readonly message: string;
24
+ };
25
+ /** One route answer: the data, or the classified failure. */
26
+ export type RouteAnswer<T> = {
27
+ readonly ok: true;
28
+ readonly data: T;
29
+ } | {
30
+ readonly ok: false;
31
+ readonly failure: RouteFailure;
32
+ };
33
+ /** Classify a non-2xx status: 401/403/404 keep their own kind, everything else is a server answer. */
34
+ export declare function classify(status: number): RouteFailure;
35
+ /**
36
+ * One request, no retries and no body parsing for the platform's admission answers.
37
+ * @param doFetch - the fetch in use (the browser's, or a spec's stub).
38
+ * @param path - the exact route path from the generated ROUTES table.
39
+ * @param init - standard RequestInit.
40
+ * @returns the data, or the classified failure (network/parse failures read as 'server').
41
+ */
42
+ export declare function requestJson<T>(doFetch: typeof fetch, path: string, init?: RequestInit): Promise<RouteAnswer<T>>;
43
+ /** The one-sentence rendering legacy callers attach to a classified failure. */
44
+ export declare function failureMessage(failure: RouteFailure): string;
45
+ //# sourceMappingURL=route-client.generated.d.ts.map
@@ -0,0 +1,31 @@
1
+ /** One host data-plane surface the browser half can call. */
2
+ export type SurfaceId = 'approval' | 'skill-history' | 'skill-health' | 'diagnostics';
3
+ /**
4
+ * Every family host path, generated from evolution-core's HOST_ROUTES (the ONE declaration).
5
+ * A browser half cannot import the host package at runtime (client bundles keep @deepseek-ai/*
6
+ * external), so the table is mirrored here mechanically; a rename in core regenerates all three
7
+ * client copies, and the generator's --check makes a stale copy red.
8
+ */
9
+ export declare const ROUTES: {
10
+ readonly approval: {
11
+ readonly pending: "/api/dsh-evolution/approval/pending";
12
+ readonly preview: "/api/dsh-evolution/approval/preview";
13
+ readonly approve: "/api/dsh-evolution/approval/approve";
14
+ readonly reject: "/api/dsh-evolution/approval/reject";
15
+ };
16
+ readonly 'skill-history': {
17
+ readonly skills: "/api/dsh-evolution/skill-history/skills";
18
+ readonly versions: "/api/dsh-evolution/skill-history/versions";
19
+ readonly diff: "/api/dsh-evolution/skill-history/versions/diff";
20
+ readonly body: "/api/dsh-evolution/skill-history/versions/body";
21
+ readonly undo: "/api/dsh-evolution/skill-history/undo";
22
+ readonly health: "/api/dsh-evolution/skill-history/health";
23
+ };
24
+ readonly 'skill-health': {
25
+ readonly rows: "/api/dsh-evolution/skill-health/rows";
26
+ };
27
+ readonly diagnostics: {
28
+ readonly view: "/api/dsh-evolution/diagnostics";
29
+ };
30
+ };
31
+ //# sourceMappingURL=routes.generated.d.ts.map
@@ -0,0 +1,43 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { RouteAnswer, RouteFailure } from './route-client.generated.ts';
3
+ import type { FallbackHint } from './contracts.generated.ts';
4
+ /**
5
+ * The four-state base of every family card (W7 §2). Load states are read from the generated
6
+ * route-client's answer — a card owns no HTTP vocabulary and no second state machine.
7
+ */
8
+ export type LoadState<T> = {
9
+ readonly phase: 'loading';
10
+ } | {
11
+ readonly phase: 'ready';
12
+ readonly data: T;
13
+ } | {
14
+ readonly phase: 'empty';
15
+ } | {
16
+ readonly phase: 'failed';
17
+ readonly failure: RouteFailure;
18
+ };
19
+ /** Map one classified answer onto the card's four states. */
20
+ export declare function loadStateOf<T>(answer: RouteAnswer<T>, isEmpty: (data: T) => boolean): LoadState<T>;
21
+ /** The locale lookup every block calls (the platform's own `t`). */
22
+ export interface Translate {
23
+ (key: string, values?: Readonly<Record<string, string | number>>): string;
24
+ }
25
+ export type BlockTone = 'neutral' | 'warn' | 'fail';
26
+ export interface BlockProps {
27
+ readonly tone?: BlockTone;
28
+ readonly title?: string;
29
+ readonly children?: ReactNode;
30
+ }
31
+ /** A neutral/ warn/ fail block; a fail block is role=alert so assistive tech announces it. */
32
+ export declare function Block(props: BlockProps): ReactNode;
33
+ export interface FailureBlockProps {
34
+ readonly failure: RouteFailure;
35
+ readonly t: Translate;
36
+ /** The command-face fallback for this surface (rendered on unmounted/failed answers). */
37
+ readonly fallback?: FallbackHint | undefined;
38
+ }
39
+ /** One classified failure as a reader-facing sentence. */
40
+ export declare function failureText(failure: RouteFailure, t: Translate): string;
41
+ /** The failed-state block: sentence + (when the surface exists but is unreachable) the command fallback. */
42
+ export declare function FailureBlock(props: FailureBlockProps): ReactNode;
43
+ //# sourceMappingURL=states.generated.d.ts.map
@@ -1,6 +1,10 @@
1
1
  /**
2
- * Host half of the skill-history surface: it mounts the four loopback routes the sidebar panel reads
3
- * and writes, and it owns nothing else.
2
+ * Host half of the skill-history surface: it mounts the six routes the sidebar panel reads and
3
+ * writes, and it owns nothing else.
4
+ *
5
+ * W6/G4: the carrier is the platform's Host connection data plane. `registerHostRoutes` waits on the
6
+ * `connection` service (it may apply after this row), registers via one owner effect and captures the
7
+ * DecisionAudit there; the old webServer + loopback fence path is gone.
4
8
  *
5
9
  * The version FACTS come from evolution-core (which artifact a version holds, how the body chain and
6
10
  * the support-file chain split) and the only write is the curator's undo, so this row carries no state
@@ -10,17 +14,12 @@
10
14
  import type { Context } from '@deepseek-ai/cordis';
11
15
  export declare const name = "evolution-skill-history";
12
16
  /** No declared service: a headless profile must not leave a pending row, and the curator is resolved
13
- * per request. The web server is waited on INSIDE apply (see below), not declared here. */
17
+ * per request; `connection` is waited on inside registerHostRoutes, not demanded here. */
14
18
  export declare const inject: string[];
15
19
  /**
16
- * Wait for the web server, then mount the routes as ONE effect (disposers run on unload and on HMR).
17
- *
18
- * **Why `ctx.inject` and not `ctx.get`**: profile rows apply in file order, so at this row's apply time
19
- * the web server may not exist yet — `ctx.get('webServer')` returning undefined is a TIMING fact, not a
20
- * verdict, and acting on it (0.11.1 shipped that way) left the routes permanently unregistered while the
21
- * panel rendered and every call answered HTTP 404. Waiting on the service mounts the routes whenever it
22
- * arrives, and a profile that never gets one simply never runs this callback — inert, never pending.
23
- * @param ctx - the host context; the web server and curator arrive through it.
20
+ * Mount the routes as ONE owner effect (disposers run on unload and on HMR). A profile whose host has
21
+ * no connection service simply never runs the callback — inert, never pending.
22
+ * @param ctx - the host context; the connection and curator arrive through it.
24
23
  */
25
24
  export declare function apply(ctx: Context): void;
26
25
  //# sourceMappingURL=index.d.ts.map
@@ -1,5 +1,10 @@
1
1
  /**
2
- * The skill-history HTTP surface: six loopback routes over the curator's read/write seam (five reads plus the one write).
2
+ * The skill-history HTTP surface: six exact Fetch routes over the curator's read/write seam (five
3
+ * reads plus the one write).
4
+ *
5
+ * W6/G4: the carrier is the platform's Host connection data plane — the fence, browser auth and
6
+ * owner-only admission are applied by the platform BEFORE `fetch(request)` runs, so this module has
7
+ * no 403/401 branch. The wire envelope the browser half reads is unchanged.
3
8
  *
4
9
  * Layering: this module maps a route to a USE CASE and does nothing else. "Which artifact does this
5
10
  * version hold" and "how do the two chains split" live in evolution-core; the only write is
@@ -7,8 +12,8 @@
7
12
  * @module @lmzhen/dsh-evolution-skill-history/routes
8
13
  */
9
14
  import type { Context } from '@deepseek-ai/cordis';
10
- import type { WebRoute } from '@deepseek-ai/dsh-host-webserver';
11
- /** Route paths. The client bundle mirrors these literals; a spec asserts the two sides agree. */
15
+ import type { ConnectionFetchRouteLike, DecisionAudit } from '@lmzhen/dsh-evolution-core/http-routes';
16
+ /** Route paths: the ONE declaration lives in core (W1); the client constants are generated from it. */
12
17
  export declare const SKILL_HISTORY_ROUTES: {
13
18
  readonly skills: "/api/dsh-evolution/skill-history/skills";
14
19
  readonly versions: "/api/dsh-evolution/skill-history/versions";
@@ -37,7 +42,9 @@ export interface RouteServices {
37
42
  /**
38
43
  * Build the six routes over the curator seam.
39
44
  * @param services - the host services the handlers read.
40
- * @returns the route list for ctx.webServer.register.
45
+ * @param _audit - the registration audit (read routes record nothing; undo's decision stays the
46
+ * curator's own — W6 keeps the ledger to the approval surface).
47
+ * @returns the route list for `registerHostRoutes`.
41
48
  */
42
- export declare function makeSkillHistoryRoutes(services: RouteServices): WebRoute[];
49
+ export declare function makeSkillHistoryRoutes(services: RouteServices, _audit: DecisionAudit): ConnectionFetchRouteLike[];
43
50
  //# sourceMappingURL=routes.d.ts.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-skill-history",
3
3
  "description": "Web surface for skill content history: loopback host routes plus the sidebar panel (community build)",
4
- "version": "0.26.26",
4
+ "version": "0.27.0",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -30,23 +30,22 @@
30
30
  ],
31
31
  "license": "MIT",
32
32
  "dependencies": {
33
- "@lmzhen/dsh-evolution-core": "^0.26.26"
33
+ "@lmzhen/dsh-evolution-core": "^0.27.0"
34
34
  },
35
35
  "peerDependencies": {
36
- "@deepseek-ai/cordis": "^4.0.1",
37
- "@deepseek-ai/dsh-host-webserver": "^0.2.0-rc.1",
36
+ "@deepseek-ai/cordis": "^4.0.5",
38
37
  "react": "^18.2.0",
39
- "@lmzhen/dsh-evolution-curator": "^0.26.26"
38
+ "@lmzhen/dsh-evolution-core": "^0.27.0",
39
+ "@lmzhen/dsh-evolution-curator": "^0.27.0"
40
40
  },
41
41
  "devDependencies": {
42
- "@deepseek-ai/cordis": "^4.0.1",
43
- "@deepseek-ai/dsh-host-webserver": "^0.2.0-rc.1",
42
+ "@deepseek-ai/cordis": "^4.0.5",
44
43
  "@types/react": "~18.3.1",
45
44
  "react": "^18.2.0",
46
- "@lmzhen/dsh-evolution-core": "^0.26.26",
47
- "@lmzhen/dsh-evolution-curator": "^0.26.26",
48
- "@lmzhen/dsh-evolution-io": "^0.26.26",
49
- "@lmzhen/dsh-evolution-io-node": "^0.26.26"
45
+ "@lmzhen/dsh-evolution-core": "^0.27.0",
46
+ "@lmzhen/dsh-evolution-curator": "^0.27.0",
47
+ "@lmzhen/dsh-evolution-io": "^0.27.0",
48
+ "@lmzhen/dsh-evolution-io-node": "^0.27.0"
50
49
  },
51
50
  "dsh": {
52
51
  "client": {
@@ -57,5 +56,8 @@
57
56
  "@deepseek-ai/dsh-client-locale"
58
57
  ]
59
58
  }
59
+ },
60
+ "engines": {
61
+ "dsh": ">=0.2.1-rc.1 <0.2.2-0"
60
62
  }
61
63
  }