dsh-continual-evolve 0.11.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -115
- package/README.zh.md +33 -93
- package/client/client.js +253 -0
- package/cordis.patch.yml +2 -2
- package/lib/card-routes.d.ts +32 -0
- package/lib/card-routes.js +92 -0
- package/lib/index.d.ts +65 -217
- package/lib/index.js +74 -234
- package/lib/known-workspaces.d.ts +22 -0
- package/lib/known-workspaces.js +42 -0
- package/lib/memory-guide.d.ts +13 -14
- package/lib/memory-guide.js +34 -21
- package/lib/memory-section.d.ts +77 -0
- package/lib/memory-section.js +189 -0
- package/lib/memory-snapshot.d.ts +54 -0
- package/lib/memory-snapshot.js +125 -0
- package/package.json +8 -17
- package/lib/apply.d.ts +0 -22
- package/lib/apply.js +0 -157
- package/lib/approval.d.ts +0 -73
- package/lib/approval.js +0 -127
- package/lib/audit-log.d.ts +0 -19
- package/lib/audit-log.js +0 -52
- package/lib/auto.d.ts +0 -236
- package/lib/auto.js +0 -773
- package/lib/autocase.d.ts +0 -38
- package/lib/autocase.js +0 -76
- package/lib/benchmark-command.d.ts +0 -9
- package/lib/benchmark-command.js +0 -357
- package/lib/benchmark.d.ts +0 -151
- package/lib/benchmark.js +0 -250
- package/lib/command-util.d.ts +0 -70
- package/lib/command-util.js +0 -123
- package/lib/command.d.ts +0 -13
- package/lib/command.js +0 -134
- package/lib/consolidate.d.ts +0 -90
- package/lib/consolidate.js +0 -180
- package/lib/copy.d.ts +0 -37
- package/lib/copy.js +0 -58
- package/lib/declines.d.ts +0 -103
- package/lib/declines.js +0 -179
- package/lib/evaluate.d.ts +0 -74
- package/lib/evaluate.js +0 -271
- package/lib/evolve-event.d.ts +0 -40
- package/lib/evolve-event.js +0 -60
- package/lib/extraction-phase.d.ts +0 -132
- package/lib/extraction-phase.js +0 -236
- package/lib/failures.d.ts +0 -42
- package/lib/failures.js +0 -171
- package/lib/fate.d.ts +0 -130
- package/lib/fate.js +0 -350
- package/lib/goal-command.d.ts +0 -7
- package/lib/goal-command.js +0 -37
- package/lib/goal.d.ts +0 -72
- package/lib/goal.js +0 -72
- package/lib/inject.d.ts +0 -90
- package/lib/inject.js +0 -164
- package/lib/injection-caps.d.ts +0 -4
- package/lib/injection-caps.js +0 -4
- package/lib/injection-directory.d.ts +0 -29
- package/lib/injection-directory.js +0 -78
- package/lib/injection-rank.d.ts +0 -85
- package/lib/injection-rank.js +0 -161
- package/lib/listener.d.ts +0 -45
- package/lib/listener.js +0 -199
- package/lib/llm-text.d.ts +0 -104
- package/lib/llm-text.js +0 -140
- package/lib/logfile.d.ts +0 -71
- package/lib/logfile.js +0 -159
- package/lib/maintenance-commands.d.ts +0 -23
- package/lib/maintenance-commands.js +0 -136
- package/lib/memory-agent.d.ts +0 -93
- package/lib/memory-agent.js +0 -300
- package/lib/memory-apply.d.ts +0 -44
- package/lib/memory-apply.js +0 -202
- package/lib/memory-benchmark.d.ts +0 -76
- package/lib/memory-benchmark.js +0 -170
- package/lib/memory-index.d.ts +0 -114
- package/lib/memory-index.js +0 -197
- package/lib/memory-manifest.d.ts +0 -16
- package/lib/memory-manifest.js +0 -70
- package/lib/message-source.d.ts +0 -23
- package/lib/message-source.js +0 -12
- package/lib/mount-command.d.ts +0 -10
- package/lib/mount-command.js +0 -48
- package/lib/mount.d.ts +0 -42
- package/lib/mount.js +0 -212
- package/lib/notify.d.ts +0 -48
- package/lib/notify.js +0 -57
- package/lib/plan-command.d.ts +0 -12
- package/lib/plan-command.js +0 -38
- package/lib/plan.d.ts +0 -16
- package/lib/plan.js +0 -126
- package/lib/planner.d.ts +0 -61
- package/lib/planner.js +0 -144
- package/lib/pool.d.ts +0 -7
- package/lib/pool.js +0 -25
- package/lib/prefix-cache.d.ts +0 -79
- package/lib/prefix-cache.js +0 -168
- package/lib/project.d.ts +0 -28
- package/lib/project.js +0 -66
- package/lib/projection.d.ts +0 -22
- package/lib/projection.js +0 -112
- package/lib/promotion.d.ts +0 -94
- package/lib/promotion.js +0 -171
- package/lib/recall.d.ts +0 -85
- package/lib/recall.js +0 -142
- package/lib/record-language.d.ts +0 -85
- package/lib/record-language.js +0 -152
- package/lib/render.d.ts +0 -13
- package/lib/render.js +0 -80
- package/lib/review-scheduler.d.ts +0 -38
- package/lib/review-scheduler.js +0 -183
- package/lib/review.d.ts +0 -60
- package/lib/review.js +0 -145
- package/lib/rollback.d.ts +0 -9
- package/lib/rollback.js +0 -68
- package/lib/rubric.d.ts +0 -29
- package/lib/rubric.js +0 -119
- package/lib/runtime-commands.d.ts +0 -14
- package/lib/runtime-commands.js +0 -115
- package/lib/runtime.d.ts +0 -24
- package/lib/runtime.js +0 -87
- package/lib/score.d.ts +0 -64
- package/lib/score.js +0 -191
- package/lib/search.d.ts +0 -83
- package/lib/search.js +0 -136
- package/lib/service.d.ts +0 -63
- package/lib/service.js +0 -253
- package/lib/skill-render.d.ts +0 -23
- package/lib/skill-render.js +0 -69
- package/lib/skill.d.ts +0 -15
- package/lib/skill.js +0 -80
- package/lib/skillquality.d.ts +0 -85
- package/lib/skillquality.js +0 -320
- package/lib/source.d.ts +0 -30
- package/lib/source.js +0 -43
- package/lib/state.d.ts +0 -26
- package/lib/state.js +0 -151
- package/lib/store.d.ts +0 -61
- package/lib/store.js +0 -164
- package/lib/token-usage.d.ts +0 -68
- package/lib/token-usage.js +0 -211
- package/lib/tool.d.ts +0 -34
- package/lib/tool.js +0 -320
- package/lib/turn-snapshot.d.ts +0 -78
- package/lib/turn-snapshot.js +0 -252
- package/lib/types.d.ts +0 -218
- package/lib/types.js +0 -133
- package/lib/usage.d.ts +0 -36
- package/lib/usage.js +0 -98
- package/lib/validate.d.ts +0 -28
- package/lib/validate.js +0 -181
- package/lib/wrapup-candidates.d.ts +0 -127
- package/lib/wrapup-candidates.js +0 -161
- package/lib/wrapup-command.d.ts +0 -10
- package/lib/wrapup-command.js +0 -243
- package/lib/wrapup-proposals.d.ts +0 -101
- package/lib/wrapup-proposals.js +0 -281
- package/lib/wrapup.d.ts +0 -56
- package/lib/wrapup.js +0 -105
package/client/client.js
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-continual-evolve — client bundle for the official plugin manager card.
|
|
3
|
+
*
|
|
4
|
+
* Hand-written (no bundler: docs/coding-standard.md forbids a second one) in
|
|
5
|
+
* the host loader's factory shape, verified against dshmarket's built
|
|
6
|
+
* bundle: `window.__ModuleLoader__.load({ id, factory })`, where the factory
|
|
7
|
+
* resolves host-provided modules through the passed `require` (here: only
|
|
8
|
+
* react) and returns its `module.exports`. The host mounts the bundle via
|
|
9
|
+
* package.json `dsh.client` + the `./client` export.
|
|
10
|
+
*
|
|
11
|
+
* The card registers one slot, `plugins.bundle.config` — the seat the 0.1.7+
|
|
12
|
+
* plugin manager declares for a bundle's own page. Slot detection is the
|
|
13
|
+
* compatibility contract: a host that never declares the slot never runs the
|
|
14
|
+
* registration, so the card is silently absent while the memory section is
|
|
15
|
+
* unaffected. The render function answers the host's `view === "summary"`
|
|
16
|
+
* call with null (the bundle list row shows the package description; the
|
|
17
|
+
* card is the page).
|
|
18
|
+
*
|
|
19
|
+
* The card is a read-only projection of the workspace memory store, fetched
|
|
20
|
+
* at render time from this plugin's host routes (same-origin). No editor, no
|
|
21
|
+
* write path, no cached state — refresh refetches everything.
|
|
22
|
+
*/
|
|
23
|
+
window.__ModuleLoader__.load({
|
|
24
|
+
id: "dsh-continual-evolve",
|
|
25
|
+
factory: function (require) {
|
|
26
|
+
var module = { exports: {} };
|
|
27
|
+
var exports = module.exports;
|
|
28
|
+
var react = undefined;
|
|
29
|
+
try {
|
|
30
|
+
react = require("react");
|
|
31
|
+
} catch (error) {
|
|
32
|
+
// Browser side: this runs before any cordis logger exporter exists,
|
|
33
|
+
// so console is the only outlet (same escape hatch dshmarket uses).
|
|
34
|
+
console.warn(
|
|
35
|
+
"[dsh-continual-evolve] host react module unavailable — card disabled: " +
|
|
36
|
+
(error instanceof Error ? error.message : String(error)),
|
|
37
|
+
);
|
|
38
|
+
return module.exports;
|
|
39
|
+
}
|
|
40
|
+
if (react === undefined || typeof react.createElement !== "function") {
|
|
41
|
+
// A module table that resolves react to nothing is the same downgrade:
|
|
42
|
+
// render would throw and blank the whole settings dialog (dshmarket #671 shape).
|
|
43
|
+
console.warn("[dsh-continual-evolve] host react module has no createElement — card disabled");
|
|
44
|
+
return module.exports;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
var createElement = react.createElement;
|
|
48
|
+
var useState = react.useState;
|
|
49
|
+
var useEffect = react.useEffect;
|
|
50
|
+
|
|
51
|
+
var NS = "dsh-continual-evolve";
|
|
52
|
+
var SLOT = "plugins.bundle.config";
|
|
53
|
+
var API_ROOT = "/dsh-continual-evolve/api/v1";
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Assemble the card's view model: the workspace allowlist, then one
|
|
57
|
+
* snapshot per workspace. Network or API failure per workspace degrades
|
|
58
|
+
* to an `error` string on that workspace's entry — one bad store never
|
|
59
|
+
* blanks the whole card.
|
|
60
|
+
*
|
|
61
|
+
* @param {typeof fetch} fetchImpl - injectable for tests.
|
|
62
|
+
* @returns {Promise<{workspaces: Array<object>}>}
|
|
63
|
+
*/
|
|
64
|
+
async function loadCardModel(fetchImpl) {
|
|
65
|
+
var listResponse = await fetchImpl(API_ROOT + "/workspaces");
|
|
66
|
+
if (!listResponse.ok) {
|
|
67
|
+
throw new Error("workspaces API answered HTTP " + listResponse.status);
|
|
68
|
+
}
|
|
69
|
+
var listBody = await listResponse.json();
|
|
70
|
+
var workspaces = Array.isArray(listBody.workspaces) ? listBody.workspaces : [];
|
|
71
|
+
var entries = await Promise.all(
|
|
72
|
+
workspaces.map(async function (workspace) {
|
|
73
|
+
var root = typeof workspace.root === "string" ? workspace.root : "";
|
|
74
|
+
var lastSeen = typeof workspace.lastSeen === "string" ? workspace.lastSeen : "";
|
|
75
|
+
try {
|
|
76
|
+
var snapshotResponse = await fetchImpl(API_ROOT + "/memory?root=" + encodeURIComponent(root));
|
|
77
|
+
if (!snapshotResponse.ok) {
|
|
78
|
+
return { root: root, lastSeen: lastSeen, snapshot: null, error: "HTTP " + snapshotResponse.status };
|
|
79
|
+
}
|
|
80
|
+
return { root: root, lastSeen: lastSeen, snapshot: await snapshotResponse.json(), error: null };
|
|
81
|
+
} catch (error) {
|
|
82
|
+
return {
|
|
83
|
+
root: root,
|
|
84
|
+
lastSeen: lastSeen,
|
|
85
|
+
snapshot: null,
|
|
86
|
+
error: error instanceof Error ? error.message : String(error),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}),
|
|
90
|
+
);
|
|
91
|
+
return { workspaces: entries };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** One workspace block: root, stats, drift warnings, per-file lines. */
|
|
95
|
+
function WorkspaceBlock(props) {
|
|
96
|
+
var entry = props.entry;
|
|
97
|
+
var snapshot = entry.snapshot;
|
|
98
|
+
var children = [
|
|
99
|
+
createElement("div", { key: "root", style: { fontFamily: "monospace", wordBreak: "break-all" } }, entry.root),
|
|
100
|
+
];
|
|
101
|
+
if (entry.error !== null) {
|
|
102
|
+
children.push(createElement("div", { key: "error" }, "读取失败:" + entry.error));
|
|
103
|
+
return createElement("div", { style: { marginBottom: "12px" } }, children);
|
|
104
|
+
}
|
|
105
|
+
if (snapshot === null || !snapshot.exists) {
|
|
106
|
+
children.push(createElement("div", { key: "absent" }, "该工作区尚无记忆库(开一次会话自动创建)。"));
|
|
107
|
+
return createElement("div", { style: { marginBottom: "12px" } }, children);
|
|
108
|
+
}
|
|
109
|
+
if (snapshot.readError !== null) {
|
|
110
|
+
children.push(createElement("div", { key: "readError" }, "记忆库读取失败:" + snapshot.readError));
|
|
111
|
+
return createElement("div", { style: { marginBottom: "12px" } }, children);
|
|
112
|
+
}
|
|
113
|
+
children.push(
|
|
114
|
+
createElement(
|
|
115
|
+
"div",
|
|
116
|
+
{ key: "stats" },
|
|
117
|
+
snapshot.fileCount + " 条记忆 · 索引 " + snapshot.indexEntryCount + " 行",
|
|
118
|
+
),
|
|
119
|
+
);
|
|
120
|
+
if (snapshot.missingFiles.length > 0) {
|
|
121
|
+
children.push(
|
|
122
|
+
createElement(
|
|
123
|
+
"div",
|
|
124
|
+
{ key: "missing" },
|
|
125
|
+
"索引失联(索引引用但文件已删):" + snapshot.missingFiles.join("、"),
|
|
126
|
+
),
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
if (snapshot.unindexedFiles.length > 0) {
|
|
130
|
+
children.push(
|
|
131
|
+
createElement(
|
|
132
|
+
"div",
|
|
133
|
+
{ key: "unindexed" },
|
|
134
|
+
"未入索引(文件存在但索引没有行):" + snapshot.unindexedFiles.join("、"),
|
|
135
|
+
),
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
children.push(
|
|
139
|
+
createElement(
|
|
140
|
+
"ul",
|
|
141
|
+
{ key: "files", style: { margin: "4px 0 0", paddingLeft: "18px" } },
|
|
142
|
+
snapshot.files.map(function (file) {
|
|
143
|
+
return createElement(
|
|
144
|
+
"li",
|
|
145
|
+
{ key: file.file },
|
|
146
|
+
createElement("strong", null, file.file) +
|
|
147
|
+
(file.description !== "" ? " — " + file.description : ""),
|
|
148
|
+
);
|
|
149
|
+
}),
|
|
150
|
+
),
|
|
151
|
+
);
|
|
152
|
+
return createElement("div", { style: { marginBottom: "12px" } }, children);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** The card page: refresh control + one block per known workspace. */
|
|
156
|
+
function MemoryCard() {
|
|
157
|
+
var state = useState({ status: "loading", model: null, message: "" });
|
|
158
|
+
var model = state[0];
|
|
159
|
+
var setModel = state[1];
|
|
160
|
+
var reloadState = useState(0);
|
|
161
|
+
var reload = reloadState[1];
|
|
162
|
+
useEffect(
|
|
163
|
+
function () {
|
|
164
|
+
var alive = true;
|
|
165
|
+
loadCardModel(fetch).then(
|
|
166
|
+
function (data) {
|
|
167
|
+
if (alive) setModel({ status: "ready", model: data, message: "" });
|
|
168
|
+
},
|
|
169
|
+
function (error) {
|
|
170
|
+
if (alive) {
|
|
171
|
+
setModel({ status: "error", model: null, message: error instanceof Error ? error.message : String(error) });
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
);
|
|
175
|
+
return function () {
|
|
176
|
+
alive = false;
|
|
177
|
+
};
|
|
178
|
+
},
|
|
179
|
+
[reloadState[0]],
|
|
180
|
+
);
|
|
181
|
+
var children = [
|
|
182
|
+
createElement(
|
|
183
|
+
"div",
|
|
184
|
+
{ key: "head", style: { display: "flex", alignItems: "baseline", gap: "12px" } },
|
|
185
|
+
createElement("strong", null, "工作区记忆(只读投影)"),
|
|
186
|
+
createElement(
|
|
187
|
+
"button",
|
|
188
|
+
{
|
|
189
|
+
type: "button",
|
|
190
|
+
onClick: function () {
|
|
191
|
+
setModel({ status: "loading", model: null, message: "" });
|
|
192
|
+
reload(function (n) {
|
|
193
|
+
return n + 1;
|
|
194
|
+
});
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
"刷新",
|
|
198
|
+
),
|
|
199
|
+
),
|
|
200
|
+
createElement(
|
|
201
|
+
"div",
|
|
202
|
+
{ key: "hint" },
|
|
203
|
+
"事实源是各工作区 .evolve/memory/ 下的 markdown 文件——模型原生读写;本卡片只展示,编辑请直接改文件。",
|
|
204
|
+
),
|
|
205
|
+
];
|
|
206
|
+
if (model.status === "loading") {
|
|
207
|
+
children.push(createElement("div", { key: "body" }, "读取中…"));
|
|
208
|
+
} else if (model.status === "error") {
|
|
209
|
+
children.push(createElement("div", { key: "body" }, "卡片数据加载失败:" + model.message));
|
|
210
|
+
} else if (model.model.workspaces.length === 0) {
|
|
211
|
+
children.push(
|
|
212
|
+
createElement(
|
|
213
|
+
"div",
|
|
214
|
+
{ key: "body" },
|
|
215
|
+
"本进程还没有服务过任何工作区会话——在某个工作区开一次会话后回来刷新。",
|
|
216
|
+
),
|
|
217
|
+
);
|
|
218
|
+
} else {
|
|
219
|
+
children.push(
|
|
220
|
+
createElement(
|
|
221
|
+
"div",
|
|
222
|
+
{ key: "body" },
|
|
223
|
+
model.model.workspaces.map(function (entry) {
|
|
224
|
+
return createElement(WorkspaceBlock, { key: entry.root, entry: entry });
|
|
225
|
+
}),
|
|
226
|
+
),
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
return createElement("div", null, children);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
exports.name = NS;
|
|
233
|
+
exports.inject = ["slots"];
|
|
234
|
+
exports.loadCardModel = loadCardModel;
|
|
235
|
+
exports.apply = function apply(ctx) {
|
|
236
|
+
ctx.slots.inject(SLOT, function () {
|
|
237
|
+
return ctx.slots.register(
|
|
238
|
+
{
|
|
239
|
+
name: SLOT,
|
|
240
|
+
key: NS,
|
|
241
|
+
inject: function () {
|
|
242
|
+
return {};
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
function (ownerProps) {
|
|
246
|
+
return ownerProps && ownerProps.view === "summary" ? null : createElement(MemoryCard);
|
|
247
|
+
},
|
|
248
|
+
);
|
|
249
|
+
});
|
|
250
|
+
};
|
|
251
|
+
return module.exports;
|
|
252
|
+
},
|
|
253
|
+
});
|
package/cordis.patch.yml
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
#
|
|
3
3
|
# Installed via `dsh plugin --profile <name> add dsh-continual-evolve`, this
|
|
4
4
|
# patch inserts the plugin row so it activates without manual profile editing.
|
|
5
|
-
#
|
|
6
|
-
#
|
|
5
|
+
# The plugin only registers a session-start memory section (workspace
|
|
6
|
+
# .evolve/memory, native file read/write); no gate is ever ON by default.
|
|
7
7
|
- insert:
|
|
8
8
|
- id: continual-evolve
|
|
9
9
|
name: 'dsh-continual-evolve'
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { KnownWorkspaces } from "./known-workspaces.js";
|
|
2
|
+
/** URL prefix of the card API (kind-exact routes registered under it). */
|
|
3
|
+
export declare const CARD_API_WORKSPACES_PATH = "/dsh-continual-evolve/api/v1/workspaces";
|
|
4
|
+
export declare const CARD_API_MEMORY_PATH = "/dsh-continual-evolve/api/v1/memory";
|
|
5
|
+
/** Minimal request shape the handlers touch (Node IncomingMessage subset). */
|
|
6
|
+
export interface CardRequest {
|
|
7
|
+
method?: string;
|
|
8
|
+
url?: string;
|
|
9
|
+
}
|
|
10
|
+
/** Minimal response shape the handlers touch (Node ServerResponse subset). */
|
|
11
|
+
export interface CardResponse {
|
|
12
|
+
writeHead(status: number, headers?: Record<string, string>): unknown;
|
|
13
|
+
end(body?: string): unknown;
|
|
14
|
+
}
|
|
15
|
+
/** One `kind: "exact"` route registration on the host web server. */
|
|
16
|
+
export interface CardRoute {
|
|
17
|
+
kind: "exact";
|
|
18
|
+
path: string;
|
|
19
|
+
handler(request: CardRequest, response: CardResponse): void;
|
|
20
|
+
}
|
|
21
|
+
/** Minimal webServer surface the card mounts on. */
|
|
22
|
+
export interface CardWebServer {
|
|
23
|
+
register(route: CardRoute): unknown;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Register the two card routes and return a disposer that removes them.
|
|
27
|
+
* Registration return values are passed through untouched: whatever disposer
|
|
28
|
+
* shape the host hands back (or none, as on some host versions), the
|
|
29
|
+
* disposer calls only the functions among them.
|
|
30
|
+
*/
|
|
31
|
+
export declare function mountCardRoutes(webServer: CardWebServer, workspaces: KnownWorkspaces): () => void;
|
|
32
|
+
//# sourceMappingURL=card-routes.d.ts.map
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The read-only HTTP surface the plugin management card fetches from.
|
|
3
|
+
*
|
|
4
|
+
* Mounted through the host's `webServer` service (same registration shape
|
|
5
|
+
* dshmarket uses: `kind: "exact"` routes on the profile's web server), the
|
|
6
|
+
* two endpoints are GET-only projections: the workspace allowlist and, for a
|
|
7
|
+
* known root, the memory snapshot. The allowlist IS the security fence — the
|
|
8
|
+
* memory endpoint resolves its `root` parameter only against roots the
|
|
9
|
+
* running process has actually served, so the API cannot read arbitrary
|
|
10
|
+
* paths. Handlers are synchronous and never throw past the response.
|
|
11
|
+
*/
|
|
12
|
+
import { resolve } from "node:path";
|
|
13
|
+
import { memorySnapshot } from "./memory-snapshot.js";
|
|
14
|
+
/** URL prefix of the card API (kind-exact routes registered under it). */
|
|
15
|
+
export const CARD_API_WORKSPACES_PATH = "/dsh-continual-evolve/api/v1/workspaces";
|
|
16
|
+
export const CARD_API_MEMORY_PATH = "/dsh-continual-evolve/api/v1/memory";
|
|
17
|
+
function sendJson(response, status, body) {
|
|
18
|
+
response.writeHead(status, { "content-type": "application/json; charset=utf-8" });
|
|
19
|
+
response.end(JSON.stringify(body));
|
|
20
|
+
}
|
|
21
|
+
function isGet(request) {
|
|
22
|
+
return (request.method ?? "GET") === "GET";
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Register the two card routes and return a disposer that removes them.
|
|
26
|
+
* Registration return values are passed through untouched: whatever disposer
|
|
27
|
+
* shape the host hands back (or none, as on some host versions), the
|
|
28
|
+
* disposer calls only the functions among them.
|
|
29
|
+
*/
|
|
30
|
+
export function mountCardRoutes(webServer, workspaces) {
|
|
31
|
+
const disposers = [];
|
|
32
|
+
disposers.push(webServer.register({
|
|
33
|
+
kind: "exact",
|
|
34
|
+
path: CARD_API_WORKSPACES_PATH,
|
|
35
|
+
handler(request, response) {
|
|
36
|
+
if (!isGet(request)) {
|
|
37
|
+
response.writeHead(405, { allow: "GET" });
|
|
38
|
+
response.end();
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
sendJson(response, 200, { workspaces: workspaces.list() });
|
|
42
|
+
},
|
|
43
|
+
}));
|
|
44
|
+
disposers.push(webServer.register({
|
|
45
|
+
kind: "exact",
|
|
46
|
+
path: CARD_API_MEMORY_PATH,
|
|
47
|
+
handler(request, response) {
|
|
48
|
+
if (!isGet(request)) {
|
|
49
|
+
response.writeHead(405, { allow: "GET" });
|
|
50
|
+
response.end();
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const root = rootParamOf(request);
|
|
54
|
+
if (root === null) {
|
|
55
|
+
sendJson(response, 400, { error: "root query parameter is required and must be an absolute path" });
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const known = workspaces.list().find((workspace) => workspace.root === resolve(root));
|
|
59
|
+
if (known === undefined) {
|
|
60
|
+
// The allowlist answers 404 without distinguishing "never
|
|
61
|
+
// served" from "wrong path": no filesystem oracle here.
|
|
62
|
+
sendJson(response, 404, { error: "unknown workspace root" });
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
sendJson(response, 200, memorySnapshot(known.root));
|
|
66
|
+
},
|
|
67
|
+
}));
|
|
68
|
+
return () => {
|
|
69
|
+
for (const disposer of disposers) {
|
|
70
|
+
if (typeof disposer === "function") {
|
|
71
|
+
disposer();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/** The decoded `root` query parameter, or null when absent/relative. */
|
|
77
|
+
function rootParamOf(request) {
|
|
78
|
+
try {
|
|
79
|
+
const url = new URL(request.url ?? "/", "http://dsh-card.invalid");
|
|
80
|
+
const root = url.searchParams.get("root");
|
|
81
|
+
if (root === null || root.trim().length === 0) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
return root;
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
// A malformed URL is a bad request, not a crash: null maps to 400.
|
|
88
|
+
void error;
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=card-routes.js.map
|