dsh-continual-evolve 0.14.1 → 0.15.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/README.md +33 -21
- package/README.zh.md +33 -21
- package/client/client.js +140 -91
- package/lib/card-routes.d.ts +3 -3
- package/lib/card-routes.js +38 -50
- package/lib/extraction-prompt.d.ts +52 -0
- package/lib/extraction-prompt.js +139 -0
- package/lib/extraction-surface.d.ts +66 -0
- package/lib/extraction-surface.js +186 -0
- package/lib/extraction.d.ts +60 -0
- package/lib/extraction.js +269 -0
- package/lib/import-md.d.ts +15 -0
- package/lib/import-md.js +140 -0
- package/lib/index.d.ts +63 -27
- package/lib/index.js +121 -65
- package/lib/memory-guide.d.ts +22 -14
- package/lib/memory-guide.js +33 -76
- package/lib/memory-rules.d.ts +56 -0
- package/lib/memory-rules.js +152 -0
- package/lib/memory-section.d.ts +26 -26
- package/lib/memory-section.js +48 -82
- package/lib/memory-snapshot.d.ts +43 -59
- package/lib/memory-snapshot.js +101 -141
- package/lib/memory-tools.d.ts +34 -0
- package/lib/memory-tools.js +142 -0
- package/lib/store-apply.d.ts +46 -0
- package/lib/store-apply.js +177 -0
- package/lib/store.d.ts +95 -0
- package/lib/store.js +207 -0
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +2 -2
package/client/client.js
CHANGED
|
@@ -76,6 +76,7 @@ window.__ModuleLoader__.load({
|
|
|
76
76
|
var createElement = react.createElement;
|
|
77
77
|
var useState = react.useState;
|
|
78
78
|
var useEffect = react.useEffect;
|
|
79
|
+
var useRef = react.useRef;
|
|
79
80
|
|
|
80
81
|
var NS = "dsh-continual-evolve";
|
|
81
82
|
var SLOT = "plugins.bundle.config";
|
|
@@ -84,25 +85,24 @@ window.__ModuleLoader__.load({
|
|
|
84
85
|
|
|
85
86
|
var zh = {
|
|
86
87
|
title: "工作区记忆",
|
|
87
|
-
desc: "
|
|
88
|
+
desc: "中央记忆库(~/.dsh/evolve/memory.db)的只读投影。",
|
|
88
89
|
refresh: "刷新",
|
|
89
|
-
hint: "
|
|
90
|
+
hint: "事实源是中央记忆库——模型经 memory_write 提案、代码校验后落库(代码是唯一写者);本卡片只展示。",
|
|
90
91
|
loading: "读取中…",
|
|
91
92
|
loadFailed: "卡片数据加载失败:",
|
|
92
|
-
empty: "
|
|
93
|
-
absent: "
|
|
93
|
+
empty: "还没有任何工作区写过记忆——在某个工作区正常使用后回来刷新。",
|
|
94
|
+
absent: "该工作区还没有记忆。",
|
|
94
95
|
readFailed: "记忆库读取失败:",
|
|
95
|
-
stats: "{files} 条记忆
|
|
96
|
+
stats: "{files} 条记忆",
|
|
96
97
|
filesHeading: "文件",
|
|
97
98
|
scopeLabel: "选择工作区",
|
|
98
|
-
searchPlaceholder: "
|
|
99
|
+
searchPlaceholder: "搜索记忆…",
|
|
99
100
|
searchEmpty: "没有匹配的记忆文件。",
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
previewLoading: "正在加载文件…",
|
|
101
|
+
quarantined: "巡检隔离(疑似密钥泄漏等异常,已隐藏):",
|
|
102
|
+
readError: "读取失败:",
|
|
103
|
+
previewLoading: "正在加载记忆…",
|
|
104
104
|
previewTooLarge: "该记忆文件超过 5 MiB 预览上限。",
|
|
105
|
-
previewDeleted: "
|
|
105
|
+
previewDeleted: "该记忆已被删除,请刷新列表。",
|
|
106
106
|
previewFailed: "内容读取失败:",
|
|
107
107
|
justNow: "刚刚",
|
|
108
108
|
minutesAgo: "{count} 分钟前",
|
|
@@ -112,25 +112,24 @@ window.__ModuleLoader__.load({
|
|
|
112
112
|
};
|
|
113
113
|
var en = {
|
|
114
114
|
title: "Workspace Memory",
|
|
115
|
-
desc: "Read-only projection of
|
|
115
|
+
desc: "Read-only projection of the central memory store (~/.dsh/evolve/memory.db).",
|
|
116
116
|
refresh: "Refresh",
|
|
117
|
-
hint: "The source of truth is the
|
|
117
|
+
hint: "The source of truth is the central memory store — the model proposes through memory_write, code validates and lands it (code is the sole writer); this card only displays it.",
|
|
118
118
|
loading: "Loading…",
|
|
119
119
|
loadFailed: "Failed to load card data: ",
|
|
120
|
-
empty: "No workspace has
|
|
121
|
-
absent: "No
|
|
120
|
+
empty: "No workspace has memories yet — use a workspace normally, then come back and refresh.",
|
|
121
|
+
absent: "No memories in this workspace yet.",
|
|
122
122
|
readFailed: "Failed to read the memory store: ",
|
|
123
|
-
stats: "{files} memories
|
|
123
|
+
stats: "{files} memories",
|
|
124
124
|
filesHeading: "Files",
|
|
125
125
|
scopeLabel: "Choose workspace",
|
|
126
126
|
searchPlaceholder: "Search memory files…",
|
|
127
127
|
searchEmpty: "No matching memory files.",
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
previewLoading: "Loading file…",
|
|
128
|
+
quarantined: "Quarantined by patrol (suspected secrets, hidden): ",
|
|
129
|
+
readError: "Read failed: ",
|
|
130
|
+
previewLoading: "Loading memory…",
|
|
132
131
|
previewTooLarge: "This memory file exceeds the 5 MiB preview limit.",
|
|
133
|
-
previewDeleted: "This memory
|
|
132
|
+
previewDeleted: "This memory has been deleted — refresh the list.",
|
|
134
133
|
previewFailed: "Failed to read the content: ",
|
|
135
134
|
justNow: "Just now",
|
|
136
135
|
minutesAgo: "{count} min ago",
|
|
@@ -230,11 +229,11 @@ window.__ModuleLoader__.load({
|
|
|
230
229
|
*
|
|
231
230
|
* @param {typeof fetch} fetchImpl - injectable for tests.
|
|
232
231
|
* @param {string} root - known workspace root.
|
|
233
|
-
* @param {string}
|
|
232
|
+
* @param {string} id - the memory id inside that workspace's partition.
|
|
234
233
|
* @returns {Promise<{status: "ready"|"deleted"|"tooLarge"|"failed", content: string, message: string}>}
|
|
235
234
|
*/
|
|
236
|
-
async function loadFileContent(fetchImpl, root,
|
|
237
|
-
var url = API_ROOT + "/memory/file?root=" + encodeURIComponent(root) + "&
|
|
235
|
+
async function loadFileContent(fetchImpl, root, id) {
|
|
236
|
+
var url = API_ROOT + "/memory/file?root=" + encodeURIComponent(root) + "&id=" + encodeURIComponent(id);
|
|
238
237
|
try {
|
|
239
238
|
var response = await fetchImpl(url);
|
|
240
239
|
if (response.ok) {
|
|
@@ -298,6 +297,7 @@ window.__ModuleLoader__.load({
|
|
|
298
297
|
".dce-hint{color:var(--dsw-alias-label-tertiary,#8b93a1);font-size:12px;line-height:18px}",
|
|
299
298
|
".dce-empty{border:1px dashed var(--dsw-alias-border-l3,#d1d9e0);border-radius:10px;padding:16px;text-align:center;color:var(--dsw-alias-label-tertiary,#8b93a1);font-size:13px;line-height:20px}",
|
|
300
299
|
".dce-scope-wrap{position:relative;display:inline-flex;min-width:0;max-width:100%;align-self:flex-start}",
|
|
300
|
+
".dce-scope-pill{border-radius:999px}",
|
|
301
301
|
".dce-scope{display:inline-flex;align-items:center;gap:8px;max-width:100%;padding:4px 10px;border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:999px;background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-primary,#1f2328);font:inherit;font-size:12px;line-height:18px;cursor:pointer}",
|
|
302
302
|
".dce-scope:hover{border-color:var(--dsw-alias-label-dimmed,#c8ccd4)}",
|
|
303
303
|
".dce-scope:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#4f6ef7);outline-offset:1px}",
|
|
@@ -307,8 +307,6 @@ window.__ModuleLoader__.load({
|
|
|
307
307
|
".dce-menu-item{display:flex;flex-direction:column;gap:2px;width:100%;padding:6px 10px;border:0;border-radius:8px;background:0 0;font:inherit;color:inherit;text-align:left;cursor:pointer}",
|
|
308
308
|
".dce-menu-item:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(0,0,0,.03))}",
|
|
309
309
|
'.dce-menu-item[data-active="true"]{background:var(--dsw-alias-bg-module-platform,#eef1f4)}',
|
|
310
|
-
".dce-menu-label{color:var(--dsw-alias-label-primary,#1f2328);font-size:13px;line-height:18px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
|
|
311
|
-
".dce-menu-path{color:var(--dsw-alias-label-tertiary,#8b93a1);font-size:11px;line-height:16px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
|
|
312
310
|
".dce-search{width:100%;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l4,#d8dee4);border-radius:var(--dsw-radius-md,8px);background:var(--dsw-alias-bg-layer-1,#fff);height:32px;color:var(--dsw-alias-label-primary,#1f2328);font:inherit;font-size:12px;padding:0 10px;outline:none}",
|
|
313
311
|
".dce-search::placeholder{color:var(--dsw-alias-label-tertiary,#8b93a1)}",
|
|
314
312
|
".dce-search:focus-visible{border-color:var(--dsw-alias-brand-primary,#4f6ef7)}",
|
|
@@ -321,6 +319,7 @@ window.__ModuleLoader__.load({
|
|
|
321
319
|
".dce-row{display:flex;width:100%;align-items:center;gap:10px;padding:8px 12px;background:0 0;border:0;font:inherit;color:inherit;text-align:left;cursor:pointer}",
|
|
322
320
|
".dce-row:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(0,0,0,.03))}",
|
|
323
321
|
".dce-row:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#4f6ef7);outline-offset:-2px}",
|
|
322
|
+
".dce-file-icon{display:inline-flex;flex:none;align-items:center;justify-content:center;width:28px;height:28px;border-radius:8px;background:var(--dsw-alias-bg-module-platform,#eef1f4)}",
|
|
324
323
|
".dce-file-name{min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-primary,#1f2328);font-size:13px;font-weight:500;line-height:18px}",
|
|
325
324
|
".dce-file-time{flex:none;color:var(--dsw-alias-label-tertiary,#8b93a1);font-size:11px;line-height:16px}",
|
|
326
325
|
".dce-preview{margin:0;padding:8px 12px 10px;background:var(--dsw-alias-bg-module-platform,#eef1f4);border-top:1px solid var(--dsw-alias-border-l2,#e5e7eb)}",
|
|
@@ -369,9 +368,20 @@ window.__ModuleLoader__.load({
|
|
|
369
368
|
var expanded = props.expanded;
|
|
370
369
|
var preview = props.preview;
|
|
371
370
|
var onToggle = props.onToggle;
|
|
372
|
-
var
|
|
373
|
-
|
|
374
|
-
|
|
371
|
+
var UI = props.UI;
|
|
372
|
+
var rowChildren = [];
|
|
373
|
+
if (UI !== null && UI !== undefined && UI.FileIcon !== undefined && UI.FileIcon !== null) {
|
|
374
|
+
// ZCode's memory rows open with a category tile; the host classifies
|
|
375
|
+
// the file itself, so a memory .md gets the markdown glyph.
|
|
376
|
+
rowChildren.push(
|
|
377
|
+
createElement(
|
|
378
|
+
"span",
|
|
379
|
+
{ key: "icon", className: "dce-file-icon" },
|
|
380
|
+
createElement(UI.FileIcon, { path: file.id + ".md", size: 16 }),
|
|
381
|
+
),
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
rowChildren.push(createElement("span", { key: "name", className: "dce-file-name" }, file.title));
|
|
375
385
|
var time = formatRelative(file.updatedAt, now, t);
|
|
376
386
|
if (time !== "") {
|
|
377
387
|
rowChildren.push(createElement("span", { key: "time", className: "dce-file-time" }, time));
|
|
@@ -391,7 +401,7 @@ window.__ModuleLoader__.load({
|
|
|
391
401
|
];
|
|
392
402
|
if (expanded) {
|
|
393
403
|
var body = null;
|
|
394
|
-
if (preview === null || preview.file !== file.
|
|
404
|
+
if (preview === null || preview.file !== file.id) {
|
|
395
405
|
body = createElement("div", { className: "dce-hint" }, t("previewLoading"));
|
|
396
406
|
} else if (preview.status === "loading") {
|
|
397
407
|
body = createElement("div", { className: "dce-hint" }, t("previewLoading"));
|
|
@@ -409,26 +419,31 @@ window.__ModuleLoader__.load({
|
|
|
409
419
|
return createElement("li", { className: "dce-item" }, children);
|
|
410
420
|
}
|
|
411
421
|
|
|
412
|
-
/**
|
|
413
|
-
function
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
typeof primitives.IconChevronsUpDownOutlineRegular !== "undefined" &&
|
|
418
|
-
primitives.IconChevronsUpDownOutlineRegular !== null
|
|
419
|
-
) {
|
|
420
|
-
return createElement(primitives.IconChevronsUpDownOutlineRegular, { size: 14, "aria-hidden": "true" });
|
|
422
|
+
/** The host's folder marker for a workspace, or nothing on an old host. */
|
|
423
|
+
function FolderMark(props) {
|
|
424
|
+
var UI = props.UI;
|
|
425
|
+
if (UI !== null && UI !== undefined && UI.Folder !== undefined && UI.Folder !== null) {
|
|
426
|
+
return createElement(UI.Folder, { size: 16, "aria-hidden": "true" });
|
|
421
427
|
}
|
|
422
428
|
return null;
|
|
423
429
|
}
|
|
424
430
|
|
|
425
431
|
/**
|
|
426
432
|
* The workspace scope selector, ZCode's Settings → Memory shape: one
|
|
427
|
-
* scope at a time behind a dropdown (
|
|
428
|
-
* label + chevron), never a row of chips
|
|
433
|
+
* scope at a time behind a dropdown (folder marker + current workspace
|
|
434
|
+
* label + chevron), never a row of chips — a chip row can only render
|
|
429
435
|
* when several workspaces are known, so in the common single-workspace
|
|
430
436
|
* case it renders nothing at all and the capability reads as missing.
|
|
431
|
-
*
|
|
437
|
+
*
|
|
438
|
+
* The list is the host's own `Menu` primitive whenever the host provides
|
|
439
|
+
* it: the same surface the rest of the settings UI uses, and it owns
|
|
440
|
+
* outside-click/Escape handling. A hand-rolled document listener gets
|
|
441
|
+
* that wrong — React flushes this component's effect synchronously for
|
|
442
|
+
* the discrete click that opens the list, so a listener attached there
|
|
443
|
+
* catches that very click and closes the list again (the button then
|
|
444
|
+
* looks dead). The hand-rolled list stays as the old-host fallback and
|
|
445
|
+
* closes on a pointerdown outside its wrapper, which cannot be the click
|
|
446
|
+
* that opened it.
|
|
432
447
|
*/
|
|
433
448
|
function WorkspaceScopeMenu(props) {
|
|
434
449
|
var workspaces = props.workspaces;
|
|
@@ -439,27 +454,41 @@ window.__ModuleLoader__.load({
|
|
|
439
454
|
var openState = useState(false);
|
|
440
455
|
var open = openState[0];
|
|
441
456
|
var setOpen = openState[1];
|
|
457
|
+
var wrapRef = useRef(null);
|
|
458
|
+
var hostMenu =
|
|
459
|
+
UI !== null && UI !== undefined && UI.Menu !== undefined && UI.Menu !== null ? UI.Menu : null;
|
|
442
460
|
useEffect(
|
|
443
461
|
function () {
|
|
444
|
-
if (!open || typeof document === "undefined") {
|
|
462
|
+
if (hostMenu !== null || !open || typeof document === "undefined") {
|
|
445
463
|
return undefined;
|
|
446
464
|
}
|
|
447
|
-
var
|
|
448
|
-
|
|
465
|
+
var onPointerDown = function (event) {
|
|
466
|
+
var node = wrapRef.current;
|
|
467
|
+
var target = event.target;
|
|
468
|
+
var inside =
|
|
469
|
+
node !== null &&
|
|
470
|
+
node !== undefined &&
|
|
471
|
+
typeof node.contains === "function" &&
|
|
472
|
+
target !== null &&
|
|
473
|
+
target !== undefined &&
|
|
474
|
+
node.contains(target);
|
|
475
|
+
if (!inside) {
|
|
476
|
+
setOpen(false);
|
|
477
|
+
}
|
|
449
478
|
};
|
|
450
479
|
var onKeyDown = function (event) {
|
|
451
480
|
if (event.key === "Escape") {
|
|
452
|
-
|
|
481
|
+
setOpen(false);
|
|
453
482
|
}
|
|
454
483
|
};
|
|
455
|
-
document.addEventListener("
|
|
484
|
+
document.addEventListener("pointerdown", onPointerDown);
|
|
456
485
|
document.addEventListener("keydown", onKeyDown);
|
|
457
486
|
return function () {
|
|
458
|
-
document.removeEventListener("
|
|
487
|
+
document.removeEventListener("pointerdown", onPointerDown);
|
|
459
488
|
document.removeEventListener("keydown", onKeyDown);
|
|
460
489
|
};
|
|
461
490
|
},
|
|
462
|
-
[open],
|
|
491
|
+
[open, hostMenu],
|
|
463
492
|
);
|
|
464
493
|
var current = null;
|
|
465
494
|
for (var index = 0; index < workspaces.length; index++) {
|
|
@@ -473,18 +502,11 @@ window.__ModuleLoader__.load({
|
|
|
473
502
|
}
|
|
474
503
|
var label = current === null ? "" : current.label || workspaceLabel(current.root);
|
|
475
504
|
var scopeChildren = [
|
|
476
|
-
createElement(ScopeMark, { key: "mark" }),
|
|
477
505
|
createElement("span", { key: "label", className: "dce-scope-label" }, label),
|
|
478
506
|
createElement("span", { key: "caret", className: "dce-scope-caret" }, createElement(Chevron, null)),
|
|
479
507
|
];
|
|
480
|
-
var
|
|
481
|
-
|
|
482
|
-
"aria-haspopup": "menu",
|
|
483
|
-
"aria-expanded": open ? "true" : "false",
|
|
484
|
-
"aria-label": t("scopeLabel"),
|
|
485
|
-
onClick: function () {
|
|
486
|
-
setOpen(!open);
|
|
487
|
-
},
|
|
508
|
+
var toggle = function () {
|
|
509
|
+
setOpen(!open);
|
|
488
510
|
};
|
|
489
511
|
var trigger = null;
|
|
490
512
|
if (UI !== null && UI !== undefined && typeof UI.Button !== "undefined" && UI.Button !== null) {
|
|
@@ -493,12 +515,14 @@ window.__ModuleLoader__.load({
|
|
|
493
515
|
{
|
|
494
516
|
key: "trigger",
|
|
495
517
|
variant: "outline",
|
|
496
|
-
size: "
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
"aria-
|
|
501
|
-
|
|
518
|
+
size: "md",
|
|
519
|
+
icon: createElement(FolderMark, { UI: UI }),
|
|
520
|
+
className: "dce-scope-pill",
|
|
521
|
+
type: "button",
|
|
522
|
+
"aria-haspopup": "menu",
|
|
523
|
+
"aria-expanded": open ? "true" : "false",
|
|
524
|
+
"aria-label": t("scopeLabel"),
|
|
525
|
+
onClick: toggle,
|
|
502
526
|
},
|
|
503
527
|
scopeChildren,
|
|
504
528
|
);
|
|
@@ -507,16 +531,44 @@ window.__ModuleLoader__.load({
|
|
|
507
531
|
"button",
|
|
508
532
|
{
|
|
509
533
|
key: "trigger",
|
|
510
|
-
type:
|
|
534
|
+
type: "button",
|
|
511
535
|
className: "dce-scope",
|
|
512
|
-
"aria-haspopup":
|
|
513
|
-
"aria-expanded":
|
|
514
|
-
"aria-label":
|
|
515
|
-
onClick:
|
|
536
|
+
"aria-haspopup": "menu",
|
|
537
|
+
"aria-expanded": open ? "true" : "false",
|
|
538
|
+
"aria-label": t("scopeLabel"),
|
|
539
|
+
onClick: toggle,
|
|
516
540
|
},
|
|
517
541
|
scopeChildren,
|
|
518
542
|
);
|
|
519
543
|
}
|
|
544
|
+
if (hostMenu !== null) {
|
|
545
|
+
return createElement(hostMenu, {
|
|
546
|
+
key: "scope",
|
|
547
|
+
open: open,
|
|
548
|
+
anchor: trigger,
|
|
549
|
+
items: workspaces.map(function (workspace) {
|
|
550
|
+
return {
|
|
551
|
+
id: workspace.root,
|
|
552
|
+
label: workspace.label || workspaceLabel(workspace.root),
|
|
553
|
+
icon: createElement(FolderMark, { UI: UI }),
|
|
554
|
+
};
|
|
555
|
+
}),
|
|
556
|
+
selectedId: selected,
|
|
557
|
+
onSelect: function (id) {
|
|
558
|
+
setOpen(false);
|
|
559
|
+
if (id !== selected) {
|
|
560
|
+
onSelect(id);
|
|
561
|
+
}
|
|
562
|
+
},
|
|
563
|
+
onClose: function () {
|
|
564
|
+
setOpen(false);
|
|
565
|
+
},
|
|
566
|
+
align: "start",
|
|
567
|
+
side: "bottom",
|
|
568
|
+
portal: true,
|
|
569
|
+
selection: "check",
|
|
570
|
+
});
|
|
571
|
+
}
|
|
520
572
|
var menu = null;
|
|
521
573
|
if (open) {
|
|
522
574
|
menu = createElement(
|
|
@@ -539,15 +591,12 @@ window.__ModuleLoader__.load({
|
|
|
539
591
|
}
|
|
540
592
|
},
|
|
541
593
|
},
|
|
542
|
-
|
|
543
|
-
// disambiguates two workspaces that read the same.
|
|
544
|
-
createElement("span", { key: "label", className: "dce-menu-label" }, workspace.label || workspaceLabel(workspace.root)),
|
|
545
|
-
createElement("span", { key: "root", className: "dce-menu-path" }, workspace.root),
|
|
594
|
+
workspace.label || workspaceLabel(workspace.root),
|
|
546
595
|
);
|
|
547
596
|
}),
|
|
548
597
|
);
|
|
549
598
|
}
|
|
550
|
-
return createElement("div", { key: "scope", className: "dce-scope-wrap" }, trigger, menu);
|
|
599
|
+
return createElement("div", { key: "scope", className: "dce-scope-wrap", ref: wrapRef }, trigger, menu);
|
|
551
600
|
}
|
|
552
601
|
|
|
553
602
|
/** One workspace's memory store: stats, drift warnings, searchable file list with previews. */
|
|
@@ -600,7 +649,7 @@ window.__ModuleLoader__.load({
|
|
|
600
649
|
if (normalizedQuery.length === 0) {
|
|
601
650
|
return true;
|
|
602
651
|
}
|
|
603
|
-
return [file.
|
|
652
|
+
return [file.id, file.title, file.description, file.type]
|
|
604
653
|
.filter(function (value) {
|
|
605
654
|
return typeof value === "string";
|
|
606
655
|
})
|
|
@@ -612,21 +661,12 @@ window.__ModuleLoader__.load({
|
|
|
612
661
|
createElement(
|
|
613
662
|
"div",
|
|
614
663
|
{ key: "stats", className: "dce-stats" },
|
|
615
|
-
t("stats", { files: snapshot.fileCount
|
|
664
|
+
t("stats", { files: snapshot.fileCount }),
|
|
616
665
|
),
|
|
617
666
|
];
|
|
618
|
-
if (snapshot.
|
|
619
|
-
parts.push(
|
|
620
|
-
createElement("div", { key: "missing", className: "dce-warn" }, t("missing") + snapshot.missingFiles.join("、")),
|
|
621
|
-
);
|
|
622
|
-
}
|
|
623
|
-
if (snapshot.unindexedFiles.length > 0) {
|
|
667
|
+
if (snapshot.quarantined.length > 0) {
|
|
624
668
|
parts.push(
|
|
625
|
-
createElement(
|
|
626
|
-
"div",
|
|
627
|
-
{ key: "unindexed", className: "dce-warn" },
|
|
628
|
-
t("unindexed") + snapshot.unindexedFiles.join("、"),
|
|
629
|
-
),
|
|
669
|
+
createElement("div", { key: "quarantined", className: "dce-warn" }, t("quarantined") + snapshot.quarantined.map(function (entry) { return entry.id; }).join("、")),
|
|
630
670
|
);
|
|
631
671
|
}
|
|
632
672
|
parts.push(
|
|
@@ -665,14 +705,15 @@ window.__ModuleLoader__.load({
|
|
|
665
705
|
{ key: "files", className: "dce-list" },
|
|
666
706
|
files.map(function (file) {
|
|
667
707
|
return createElement(FileRow, {
|
|
668
|
-
key: file.
|
|
708
|
+
key: file.id,
|
|
669
709
|
file: file,
|
|
670
710
|
now: now,
|
|
671
711
|
t: t,
|
|
672
|
-
|
|
712
|
+
UI: UI,
|
|
713
|
+
expanded: expandedFile === file.id,
|
|
673
714
|
preview: preview,
|
|
674
715
|
onToggle: function () {
|
|
675
|
-
onToggleFile(file.
|
|
716
|
+
onToggleFile(file.id);
|
|
676
717
|
},
|
|
677
718
|
});
|
|
678
719
|
}),
|
|
@@ -896,7 +937,15 @@ window.__ModuleLoader__.load({
|
|
|
896
937
|
}
|
|
897
938
|
var UI = null;
|
|
898
939
|
if (primitives !== undefined && primitives !== null && typeof primitives.Button !== "undefined") {
|
|
899
|
-
|
|
940
|
+
// The scope selector prefers the host's own Menu and folder icon;
|
|
941
|
+
// each stays optional so a host with only Button still gets the
|
|
942
|
+
// hand-rolled list and a plain trigger.
|
|
943
|
+
UI = {
|
|
944
|
+
Button: primitives.Button,
|
|
945
|
+
Menu: primitives.Menu,
|
|
946
|
+
Folder: primitives.IconFolderOpenOutlineRegular,
|
|
947
|
+
FileIcon: primitives.FileTypeIcon,
|
|
948
|
+
};
|
|
900
949
|
}
|
|
901
950
|
ctx.slots.inject(SLOT, function () {
|
|
902
951
|
return ctx.slots.register(
|
package/lib/card-routes.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { MemoryStore } from "./store.js";
|
|
2
2
|
/** URL prefix of the card API (kind-exact routes registered under it). */
|
|
3
3
|
export declare const CARD_API_WORKSPACES_PATH = "/dsh-continual-evolve/api/v1/workspaces";
|
|
4
4
|
export declare const CARD_API_MEMORY_PATH = "/dsh-continual-evolve/api/v1/memory";
|
|
@@ -24,10 +24,10 @@ export interface CardWebServer {
|
|
|
24
24
|
register(route: CardRoute): unknown;
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
|
-
* Register the
|
|
27
|
+
* Register the card routes and return a disposer that removes them.
|
|
28
28
|
* Registration return values are passed through untouched: whatever disposer
|
|
29
29
|
* shape the host hands back (or none, as on some host versions), the
|
|
30
30
|
* disposer calls only the functions among them.
|
|
31
31
|
*/
|
|
32
|
-
export declare function mountCardRoutes(webServer: CardWebServer,
|
|
32
|
+
export declare function mountCardRoutes(webServer: CardWebServer, store: MemoryStore): () => void;
|
|
33
33
|
//# sourceMappingURL=card-routes.d.ts.map
|
package/lib/card-routes.js
CHANGED
|
@@ -3,17 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Mounted through the host's `webServer` service (same registration shape
|
|
5
5
|
* dshmarket uses: `kind: "exact"` routes on the profile's web server), the
|
|
6
|
-
* three endpoints are GET-only projections
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* the
|
|
12
|
-
*
|
|
13
|
-
* Handlers are synchronous and never throw past the response.
|
|
6
|
+
* three endpoints are GET-only projections of the central SQLite store: the
|
|
7
|
+
* workspace list (active partitions whose directory still exists), the
|
|
8
|
+
* memory snapshot for a known workspace, and one record's body by id. The
|
|
9
|
+
* store itself is the security fence — a `root` parameter only resolves
|
|
10
|
+
* when the database has rows for it, so the API cannot probe the
|
|
11
|
+
* filesystem; the content endpoint takes an id, never a path. Handlers are
|
|
12
|
+
* synchronous and never throw past the response.
|
|
14
13
|
*/
|
|
15
|
-
import {
|
|
16
|
-
import { memoryFileContent, memorySnapshot } from "./memory-snapshot.js";
|
|
14
|
+
import { memoryEntryContent, listCardWorkspaces, memorySnapshot } from "./memory-snapshot.js";
|
|
17
15
|
/** URL prefix of the card API (kind-exact routes registered under it). */
|
|
18
16
|
export const CARD_API_WORKSPACES_PATH = "/dsh-continual-evolve/api/v1/workspaces";
|
|
19
17
|
export const CARD_API_MEMORY_PATH = "/dsh-continual-evolve/api/v1/memory";
|
|
@@ -25,13 +23,17 @@ function sendJson(response, status, body) {
|
|
|
25
23
|
function isGet(request) {
|
|
26
24
|
return (request.method ?? "GET") === "GET";
|
|
27
25
|
}
|
|
26
|
+
/** The card's read fence: a root only resolves when the store has rows for it. */
|
|
27
|
+
function isKnownRoot(store, root) {
|
|
28
|
+
return listCardWorkspaces(store).some((workspace) => workspace.root === root);
|
|
29
|
+
}
|
|
28
30
|
/**
|
|
29
|
-
* Register the
|
|
31
|
+
* Register the card routes and return a disposer that removes them.
|
|
30
32
|
* Registration return values are passed through untouched: whatever disposer
|
|
31
33
|
* shape the host hands back (or none, as on some host versions), the
|
|
32
34
|
* disposer calls only the functions among them.
|
|
33
35
|
*/
|
|
34
|
-
export function mountCardRoutes(webServer,
|
|
36
|
+
export function mountCardRoutes(webServer, store) {
|
|
35
37
|
const disposers = [];
|
|
36
38
|
disposers.push(webServer.register({
|
|
37
39
|
kind: "exact",
|
|
@@ -42,9 +44,7 @@ export function mountCardRoutes(webServer, catalog) {
|
|
|
42
44
|
response.end();
|
|
43
45
|
return;
|
|
44
46
|
}
|
|
45
|
-
|
|
46
|
-
// memory directory); the read fence is `known` below.
|
|
47
|
-
sendJson(response, 200, { workspaces: catalog.list() });
|
|
47
|
+
sendJson(response, 200, { workspaces: listCardWorkspaces(store) });
|
|
48
48
|
},
|
|
49
49
|
}));
|
|
50
50
|
disposers.push(webServer.register({
|
|
@@ -61,14 +61,13 @@ export function mountCardRoutes(webServer, catalog) {
|
|
|
61
61
|
sendJson(response, 400, { error: "root query parameter is required and must be an absolute path" });
|
|
62
62
|
return;
|
|
63
63
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
//
|
|
67
|
-
// workspace here" from "wrong path": no filesystem oracle.
|
|
64
|
+
if (!isKnownRoot(store, root)) {
|
|
65
|
+
// The store answers 404 without distinguishing "no rows" from
|
|
66
|
+
// "wrong path": no filesystem oracle.
|
|
68
67
|
sendJson(response, 404, { error: "unknown workspace root" });
|
|
69
68
|
return;
|
|
70
69
|
}
|
|
71
|
-
sendJson(response, 200, memorySnapshot(
|
|
70
|
+
sendJson(response, 200, memorySnapshot(store, root));
|
|
72
71
|
},
|
|
73
72
|
}));
|
|
74
73
|
disposers.push(webServer.register({
|
|
@@ -85,39 +84,33 @@ export function mountCardRoutes(webServer, catalog) {
|
|
|
85
84
|
sendJson(response, 400, { error: "root query parameter is required and must be an absolute path" });
|
|
86
85
|
return;
|
|
87
86
|
}
|
|
88
|
-
const
|
|
89
|
-
if (
|
|
90
|
-
sendJson(response, 400, { error: "
|
|
87
|
+
const id = idParamOf(request);
|
|
88
|
+
if (id === null) {
|
|
89
|
+
sendJson(response, 400, { error: "id query parameter is required" });
|
|
91
90
|
return;
|
|
92
91
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
// Same fence as the snapshot route: 404 without distinguishing
|
|
96
|
-
// "not a workspace here" from "wrong path".
|
|
92
|
+
if (!isKnownRoot(store, root)) {
|
|
93
|
+
// Same fence as the snapshot route.
|
|
97
94
|
sendJson(response, 404, { error: "unknown workspace root" });
|
|
98
95
|
return;
|
|
99
96
|
}
|
|
100
|
-
const content =
|
|
97
|
+
const content = memoryEntryContent(store, root, id);
|
|
101
98
|
if (!content.ok) {
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
if (content.reason === "
|
|
106
|
-
sendJson(response,
|
|
107
|
-
}
|
|
108
|
-
else if (content.reason === "too-large") {
|
|
109
|
-
sendJson(response, 413, { error: "memory file exceeds the 5 MiB preview limit" });
|
|
99
|
+
// Absence answers 404; the oversized case is theoretically
|
|
100
|
+
// unreachable (bodies are schema-capped at 64KB) but keeps its
|
|
101
|
+
// own status for the day the preview limit shrinks.
|
|
102
|
+
if (content.reason === "too-large") {
|
|
103
|
+
sendJson(response, 413, { error: "memory body exceeds the 5 MiB preview limit" });
|
|
110
104
|
}
|
|
111
105
|
else {
|
|
112
|
-
sendJson(response, 404, { error: "memory
|
|
106
|
+
sendJson(response, 404, { error: "memory not found" });
|
|
113
107
|
}
|
|
114
108
|
return;
|
|
115
109
|
}
|
|
116
110
|
sendJson(response, 200, {
|
|
117
|
-
|
|
111
|
+
id: content.id,
|
|
118
112
|
content: content.content,
|
|
119
|
-
|
|
120
|
-
changed: content.changed,
|
|
113
|
+
updatedAtMs: content.updatedAtMs,
|
|
121
114
|
});
|
|
122
115
|
},
|
|
123
116
|
}));
|
|
@@ -129,11 +122,6 @@ export function mountCardRoutes(webServer, catalog) {
|
|
|
129
122
|
}
|
|
130
123
|
};
|
|
131
124
|
}
|
|
132
|
-
/** The catalogued workspace a `root` query parameter resolves to, if any. */
|
|
133
|
-
function knownEntryOf(catalog, root) {
|
|
134
|
-
const target = resolve(root);
|
|
135
|
-
return catalog.known().find((workspace) => workspace.root === target);
|
|
136
|
-
}
|
|
137
125
|
/** The decoded `root` query parameter, or null when absent/relative. */
|
|
138
126
|
function rootParamOf(request) {
|
|
139
127
|
try {
|
|
@@ -150,15 +138,15 @@ function rootParamOf(request) {
|
|
|
150
138
|
return null;
|
|
151
139
|
}
|
|
152
140
|
}
|
|
153
|
-
/** The decoded `
|
|
154
|
-
function
|
|
141
|
+
/** The decoded `id` query parameter, or null when absent. */
|
|
142
|
+
function idParamOf(request) {
|
|
155
143
|
try {
|
|
156
144
|
const url = new URL(request.url ?? "/", "http://dsh-card.invalid");
|
|
157
|
-
const
|
|
158
|
-
if (
|
|
145
|
+
const id = url.searchParams.get("id");
|
|
146
|
+
if (id === null || id.trim().length === 0) {
|
|
159
147
|
return null;
|
|
160
148
|
}
|
|
161
|
-
return
|
|
149
|
+
return id;
|
|
162
150
|
}
|
|
163
151
|
catch (error) {
|
|
164
152
|
void error;
|