@lmzhen/dsh-evolution-skill-history 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/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",
@@ -222,7 +351,7 @@ window.__ModuleLoader__.load({
222
351
  * @param key - the message key.
223
352
  * @returns the copy, or the key itself when neither dictionary carries it.
224
353
  */
225
- function message(language, key) {
354
+ function message(key, language = "en") {
226
355
  return (language === "zh" ? zh : en)[key] ?? key;
227
356
  }
228
357
  /**
@@ -567,9 +696,15 @@ window.__ModuleLoader__.load({
567
696
  className: "evo-hist-hint"
568
697
  }, noteText)];
569
698
  }
570
- /** What the list shows when it has nothing to show. */
571
- function emptyState(text) {
572
- return (0, react.createElement)("p", { className: "evo-hist-empty" }, text);
699
+ /** What the list shows when it has nothing to show.
700
+ * S5-07: the sites that render it as a SIBLING in an array must pass a key - a keyless sibling made
701
+ * React warn on every render of the default view (measured: one warning per render, captured by spy).
702
+ */
703
+ function emptyState(text, key) {
704
+ return (0, react.createElement)("p", {
705
+ key,
706
+ className: "evo-hist-empty"
707
+ }, text);
573
708
  }
574
709
  /** One notice about what just happened. */
575
710
  function notice(text, error) {
@@ -620,7 +755,7 @@ window.__ModuleLoader__.load({
620
755
  function rendererOf(row) {
621
756
  const candidate = row.MarkdownText;
622
757
  if (typeof candidate === "function") return candidate;
623
- if (typeof candidate === "object" && candidate !== null) return candidate;
758
+ if (typeof candidate === "object" && candidate !== null && candidate.$$typeof !== void 0) return candidate;
624
759
  }
625
760
  /**
626
761
  * The platform component, when this deployment's module table carries one.
@@ -842,6 +977,51 @@ window.__ModuleLoader__.load({
842
977
  ] }), toggle), diffView(face, diff.diff, mode));
843
978
  }
844
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
845
1025
  //#region src/client/Panel.ts
846
1026
  /**
847
1027
  * The panel: one read, one open skill, one open row — and the faces that draw them.
@@ -859,9 +1039,29 @@ window.__ModuleLoader__.load({
859
1039
  * @param face - copy plus the data callbacks.
860
1040
  * @returns the panel element.
861
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
+ }
862
1061
  function SkillHistoryPanel(face) {
863
1062
  const [skills, setSkills] = (0, react.useState)([]);
864
1063
  const [skillsLoaded, setSkillsLoaded] = (0, react.useState)(false);
1064
+ const [failure, setFailure] = (0, react.useState)(void 0);
865
1065
  const [selected, setSelected] = (0, react.useState)(void 0);
866
1066
  const [payload, setPayload] = (0, react.useState)(void 0);
867
1067
  const [versionsFailed, setVersionsFailed] = (0, react.useState)(void 0);
@@ -876,6 +1076,10 @@ window.__ModuleLoader__.load({
876
1076
  const failed = (error) => {
877
1077
  setNoteError(true);
878
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
+ });
879
1083
  };
880
1084
  const succeeded = (message) => {
881
1085
  setNoteError(false);
@@ -1049,7 +1253,7 @@ window.__ModuleLoader__.load({
1049
1253
  };
1050
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, () => {
1051
1255
  open(skill.name);
1052
- })))), (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")), (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", {
1053
1257
  key: "hint",
1054
1258
  className: "evo-hist-hint"
1055
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));
@@ -1146,7 +1350,7 @@ window.__ModuleLoader__.load({
1146
1350
  }
1147
1351
  }, "evolution-skill-history: locale");
1148
1352
  const seatTranslator = registered ? seam.locale.bind(NS) : null;
1149
- const t = (key) => seatTranslator === null ? message("en", key) : seatTranslator(key);
1353
+ const t = (key) => seatTranslator === null ? message(key) : seatTranslator(key);
1150
1354
  ctx.effect(() => {
1151
1355
  if (document.querySelector("style[data-plugin-css=\"@lmzhen/dsh-evolution-skill-history/panel.css\"]") === null) {
1152
1356
  const tag = document.createElement("style");
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.
@@ -64,8 +64,11 @@ export declare function versionRow(props: VersionRowProps): ReactNode;
64
64
  * @returns the heading and the note, for the caller to place in its own section.
65
65
  */
66
66
  export declare function groupHeading(title: string, noteText: string | undefined): ReactNode[];
67
- /** What the list shows when it has nothing to show. */
68
- export declare function emptyState(text: string): ReactNode;
67
+ /** What the list shows when it has nothing to show.
68
+ * S5-07: the sites that render it as a SIBLING in an array must pass a key - a keyless sibling made
69
+ * React warn on every render of the default view (measured: one warning per render, captured by spy).
70
+ */
71
+ export declare function emptyState(text: string, key?: string): ReactNode;
69
72
  /** One notice about what just happened. */
70
73
  export declare function notice(text: string | undefined, error: boolean): ReactNode;
71
74
  /** The pane's heading and its one control. */
@@ -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
@@ -18,7 +18,7 @@ export declare const en: Record<string, string>;
18
18
  * @param key - the message key.
19
19
  * @returns the copy, or the key itself when neither dictionary carries it.
20
20
  */
21
- export declare function message(language: 'zh' | 'en', key: string): string;
21
+ export declare function message(key: string, language?: 'zh' | 'en'): string;
22
22
  /**
23
23
  * Fill one TEMPLATE’s `{n}` slots. The seat resolves the key, this fills it, so the panel needs
24
24
  * only the seat plus this rule and never learns which language it is rendering.
@@ -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.15",
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.15"
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.15"
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.15",
47
- "@lmzhen/dsh-evolution-curator": "^0.26.15",
48
- "@lmzhen/dsh-evolution-io": "^0.26.15",
49
- "@lmzhen/dsh-evolution-io-node": "^0.26.15"
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
  }