dsh-engram 0.3.2 → 0.3.4

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
@@ -4,6 +4,7 @@ window.__ModuleLoader__.load({
4
4
  var module = { exports: {} };
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ "use strict";
7
8
  var __defProp = Object.defineProperty;
8
9
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
9
10
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -217,6 +218,44 @@ function useEngramTheme() {
217
218
  return { dark, vars };
218
219
  }
219
220
 
221
+ // client/src/esrModel.ts
222
+ function taskGaps(t) {
223
+ const gaps = [];
224
+ if (!t.artifact) gaps.push("artifact");
225
+ if (!t.evaluation) gaps.push("evaluation");
226
+ if (!t.memoryRefs || t.memoryRefs.length === 0) gaps.push("memory_ref");
227
+ return gaps;
228
+ }
229
+ function shortId(id) {
230
+ return id.length > 12 ? `${id.slice(0, 6)}\u2026${id.slice(-4)}` : id;
231
+ }
232
+ function blockedBy(t, all) {
233
+ if (t.state === "stable") return 0;
234
+ const open = new Set(all.filter((x) => x.state !== "stable").map((x) => x.id));
235
+ return (t.deps ?? []).filter((d) => d.kind !== "relates-to" && open.has(d.id)).length;
236
+ }
237
+ function shortAgent(a) {
238
+ if (!a) return "";
239
+ const bare = a.split(/[@#/:\\]+/).pop();
240
+ return bare.replace(/^session[-_]/i, "").replace(/^agent[-_]/i, "").slice(0, 14);
241
+ }
242
+ function fmtDateShort(ts) {
243
+ return ts ? new Date(ts).toLocaleDateString("zh-CN", { month: "short", day: "numeric" }) : "\u2013";
244
+ }
245
+ function buildTasksMarkdown(tasks) {
246
+ const esc = (s4) => String(s4 ?? "").replace(/\|/g, "\\|").replace(/\n/g, " ");
247
+ const rows = tasks.slice().sort((a, b) => a.state.localeCompare(b.state) || (a.createdAt ?? 0) - (b.createdAt ?? 0)).map((t) => {
248
+ const ev = `${t.artifact ? "artifact\u2713" : "artifact\u2717"} \xB7 ${t.evaluation ? "eval\u2713" : "eval\u2717"} \xB7 ${(t.memoryRefs?.length ?? 0) > 0 ? "ref\u2713" : "ref\u2717"}`;
249
+ return `| ${t.state} | ${esc(t.name)} | ${esc(t.workspace.replace(/^.*[\\/]/, ""))} | ${esc(taskGaps(t).join(", ") || "\u2014")} | ${ev} | ${fmtDateShort(t.createdAt ?? 0)} |`;
250
+ });
251
+ return `# ESR \u4EFB\u52A1\u5BFC\u51FA \xB7 ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}
252
+
253
+ | \u72B6\u6001 | \u4EFB\u52A1 | \u5DE5\u4F5C\u533A | \u8BC1\u636E\u7F3A\u53E3 | \u8BC1\u636E | \u521B\u5EFA |
254
+ |---|---|---|---|---|---|
255
+ ${rows.join("\n") || "(\u65E0\u4EFB\u52A1)"}`;
256
+ }
257
+ var POLL_MS = 2e4;
258
+
220
259
  // client/src/EngramTaskDock.tsx
221
260
  var import_jsx_runtime = require("react/jsx-runtime");
222
261
  var EMPTY = { tasks: [], links: [], nodes: [], denied: false, error: null, loading: true };
@@ -233,13 +272,6 @@ function fmtD(ts) {
233
272
  const p = (n) => String(n).padStart(2, "0");
234
273
  return `${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;
235
274
  }
236
- var taskGaps = (t) => {
237
- const gaps = [];
238
- if (!t.artifact) gaps.push("artifact");
239
- if (!t.evaluation) gaps.push("evaluation");
240
- if (!t.memoryRefs || t.memoryRefs.length === 0) gaps.push("memory_ref");
241
- return gaps;
242
- };
243
275
  var STATUS = {
244
276
  gap: { bg: "var(--dsw-alias-state-warn-secondary, rgba(245,158,11,.14))", fg: "var(--dsw-alias-state-warn-label, #b45309)" },
245
277
  ready: { bg: "rgba(59,130,246,.13)", fg: "var(--dsw-alias-label-primary-bluish, #1d4ed8)" },
@@ -329,6 +361,7 @@ function EngramTaskDock({ sessionId, useSessions, useWorkspaces, useProjection,
329
361
  const [expandedId, setExpandedId] = (0, import_react2.useState)(null);
330
362
  const [taskPage, setTaskPage] = (0, import_react2.useState)(0);
331
363
  const [promoted, setPromoted] = (0, import_react2.useState)(/* @__PURE__ */ new Set());
364
+ const [promoteNote, setPromoteNote] = (0, import_react2.useState)(null);
332
365
  const [promoteBusy, setPromoteBusy] = (0, import_react2.useState)(false);
333
366
  const [toolStats, setToolStats] = (0, import_react2.useState)(null);
334
367
  const [closeArtifact, setCloseArtifact] = (0, import_react2.useState)("");
@@ -397,7 +430,7 @@ function EngramTaskDock({ sessionId, useSessions, useWorkspaces, useProjection,
397
430
  (0, import_react2.useEffect)(() => {
398
431
  if (!effWs || data.denied) return;
399
432
  if (pollRef.current !== null) window.clearInterval(pollRef.current);
400
- pollRef.current = window.setInterval(() => void load(), 15e3);
433
+ pollRef.current = window.setInterval(() => void load(), POLL_MS);
401
434
  return () => {
402
435
  if (pollRef.current !== null) window.clearInterval(pollRef.current);
403
436
  pollRef.current = null;
@@ -456,18 +489,26 @@ function EngramTaskDock({ sessionId, useSessions, useWorkspaces, useProjection,
456
489
  const promoteAll = async () => {
457
490
  if (!effWs || promoteBusy || toPromote.length === 0) return;
458
491
  setPromoteBusy(true);
492
+ setPromoteNote(null);
459
493
  const done = [];
494
+ const failed = [];
460
495
  for (const it of toPromote) {
461
496
  try {
462
497
  await api.createTask(effWs, it.content, "\u6E90\u81EA\u4F1A\u8BDD\u8BA1\u5212\uFF08todo_write \u4E00\u952E\u6C89\u6DC0\uFF09");
463
498
  done.push(it.content);
464
499
  } catch {
500
+ failed.push(it.content);
465
501
  }
466
502
  }
467
503
  if (done.length > 0) {
468
504
  setPromoted((prev) => /* @__PURE__ */ new Set([...prev, ...done]));
469
505
  void load();
470
506
  }
507
+ if (failed.length > 0) {
508
+ setPromoteNote(
509
+ failed.length === done.length ? `\u6C89\u6DC0\u5931\u8D25\uFF1A${failed.length} \u9879\u672A\u80FD\u521B\u5EFA\uFF08\u5DE5\u4F5C\u533A\u5DF2\u6EE1\u6216\u670D\u52A1\u4E0D\u53EF\u8FBE\uFF09\uFF0C\u8BF7\u5237\u65B0\u540E\u91CD\u8BD5` : `\u90E8\u5206\u5931\u8D25\uFF1A${failed.length}/${toPromote.length} \u9879\u672A\u80FD\u521B\u5EFA`
510
+ );
511
+ }
471
512
  setPromoteBusy(false);
472
513
  };
473
514
  const submitClose = async (task) => {
@@ -726,6 +767,10 @@ function EngramTaskDock({ sessionId, useSessions, useWorkspaces, useProjection,
726
767
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "ed-btn", style: { ...btn, ...btnSolid, padding: "3px 9px", fontSize: 11.5 }, disabled: promoteBusy || toPromote.length === 0, onClick: () => void promoteAll(), title: "\u628A\u672C\u8F6E\u5F85\u529E\u8F6C\u6210 ESR \u4EFB\u52A1\u8349\u7A3F\uFF08\u8DE8\u4F1A\u8BDD\u4FDD\u7559\uFF09", children: promoteBusy ? "\u2026" : promoted.size > 0 ? toPromote.length > 0 ? `\u5DF2\u6C89\u6DC0 ${promoted.size} \u9879 \xB7 \u518D\u6C89\u6DC0 ${toPromote.length} \u9879` : `\u5DF2\u6C89\u6DC0 ${promoted.size} \u9879` : `\u6C89\u6DC0\u5230 ESR\uFF08${toPromote.length} \u9879\uFF09` }),
727
768
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 10.5, color: "var(--dsw-alias-label-dimmed, var(--dsh-color-muted-weak, #9ca3af))" }, children: "\u8F6C\u8349\u7A3F \xB7 \u8DE8\u4F1A\u8BDD\u95ED\u73AF" })
728
769
  ] }),
770
+ promoteNote && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { fontSize: 11, color: "#dc2626", lineHeight: 1.4 }, role: "alert", children: [
771
+ "\u26A0 ",
772
+ promoteNote
773
+ ] }),
729
774
  activeTasks.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 6, alignItems: "flex-start", fontSize: 11.5, lineHeight: 1.5, color: "var(--dsw-alias-label-dimmed, var(--dsh-color-muted, #6b7280))", paddingTop: 2 }, children: [
730
775
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": true, style: { color: badgeEsr.color }, children: "\u21B3" }),
731
776
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
@@ -1327,7 +1372,7 @@ function EngramGraph({ workspace, entities, tasks, links, nameOf }) {
1327
1372
  setView({ x: pan.current.vx + dx, y: pan.current.vy + dy, k: view.k });
1328
1373
  }
1329
1374
  };
1330
- const endPointer = (e) => {
1375
+ const endPointer = () => {
1331
1376
  if (dragging.current) {
1332
1377
  const p = posRef.current.get(dragging.current.id);
1333
1378
  if (p) {
@@ -1639,7 +1684,7 @@ function EngramPreview({ api, workspace, defaultWorkspace, workspaces }) {
1639
1684
  );
1640
1685
  (0, import_react4.useEffect)(() => {
1641
1686
  void refresh();
1642
- const id = setInterval(() => void refresh(), 2e4);
1687
+ const id = setInterval(() => void refresh(), POLL_MS);
1643
1688
  return () => clearInterval(id);
1644
1689
  }, [refresh]);
1645
1690
  const copy = (which) => {
@@ -1950,13 +1995,12 @@ function EngramTelemetry({ api, workspace }) {
1950
1995
  );
1951
1996
  (0, import_react5.useEffect)(() => {
1952
1997
  void refresh();
1953
- const id = setInterval(() => void refresh(), 2e4);
1998
+ const id = setInterval(() => void refresh(), POLL_MS);
1954
1999
  return () => clearInterval(id);
1955
2000
  }, [refresh]);
1956
2001
  const toolRows = (0, import_react5.useMemo)(() => {
1957
2002
  if (!stats) return [];
1958
2003
  const entries = Object.entries(stats.totals.counts ?? {}).sort((a, b) => b[1] - a[1]);
1959
- const max = entries.length > 0 ? entries[0][1] : 0;
1960
2004
  return entries.slice(0, 8).map(([name, count]) => ({
1961
2005
  name,
1962
2006
  count,
@@ -2180,7 +2224,7 @@ function gapsOf(t) {
2180
2224
  if (!t.memoryRefs || t.memoryRefs.length === 0) gaps.push("memory_ref");
2181
2225
  return gaps;
2182
2226
  }
2183
- function EngramDetail({ target, api, memories, tasks, nodes, links, onClose, onNavigateMemory, onChanged }) {
2227
+ function EngramDetail({ target, api, tasks, nodes, links, onClose, onNavigateMemory, onChanged }) {
2184
2228
  const { dark, vars } = useEngramTheme();
2185
2229
  const [gArt, setGArt] = (0, import_react6.useState)("");
2186
2230
  const [gEval, setGEval] = (0, import_react6.useState)("");
@@ -2674,13 +2718,6 @@ function StatCard({ num, label }) {
2674
2718
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: s2.cardLabel, children: label })
2675
2719
  ] });
2676
2720
  }
2677
- function taskGaps2(t) {
2678
- const gaps = [];
2679
- if (!t.artifact) gaps.push("artifact");
2680
- if (!t.evaluation) gaps.push("evaluation");
2681
- if (!t.memoryRefs || t.memoryRefs.length === 0) gaps.push("memory_ref");
2682
- return gaps;
2683
- }
2684
2721
  function groupByWorkspace(items) {
2685
2722
  const groups = /* @__PURE__ */ new Map();
2686
2723
  for (const m of items) {
@@ -2729,7 +2766,7 @@ function EngramSection({ api, t }) {
2729
2766
  const ov = await api.overview();
2730
2767
  setOverview(ov);
2731
2768
  const wsList = workspace ? [workspace] : Object.keys(ov.workspaces);
2732
- const [mem, taskGroups2, linkGroups2, nodeGroups2, st] = await Promise.all([
2769
+ const [mem, taskGroups2, linkGroups2, nodeGroups2] = await Promise.all([
2733
2770
  api.memories({
2734
2771
  workspace: workspace || void 0,
2735
2772
  q: q || void 0,
@@ -2752,6 +2789,8 @@ function EngramSection({ api, t }) {
2752
2789
  }, [api, workspace, q, kind, status]);
2753
2790
  (0, import_react7.useEffect)(() => {
2754
2791
  void refresh();
2792
+ const id = setInterval(() => void refresh(), POLL_MS);
2793
+ return () => clearInterval(id);
2755
2794
  }, [refresh]);
2756
2795
  const workspaces = (0, import_react7.useMemo)(() => overview ? Object.keys(overview.workspaces) : [], [overview]);
2757
2796
  const kindsPresent = (0, import_react7.useMemo)(() => overview ? Object.keys(overview.kinds) : [], [overview]);
@@ -2989,11 +3028,11 @@ function EngramSection({ api, t }) {
2989
3028
  ] }, t2.id)),
2990
3029
  gcReport.removedLinks.slice(0, 3).map((l) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: s2.mono, children: [
2991
3030
  "- link ",
2992
- l.source.slice(0, 8),
3031
+ (l.source ?? "?").slice(0, 8),
2993
3032
  " --",
2994
- l.relation,
3033
+ l.relation ?? "",
2995
3034
  "--> ",
2996
- l.target.slice(0, 8)
3035
+ (l.target ?? "?").slice(0, 8)
2997
3036
  ] }, l.id))
2998
3037
  ] })
2999
3038
  ] }),
@@ -3075,7 +3114,7 @@ function EngramSection({ api, t }) {
3075
3114
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("td", { style: { ...s2.td, minWidth: 0 }, children: [
3076
3115
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { title: r.m.text, style: expandedRows.has(r.m.id) ? s2.expanded : s2.clamp3, children: r.m.text }),
3077
3116
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "center", marginTop: 2, flexWrap: "wrap" }, children: [
3078
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { ...s2.mono, ...s2.ellipsis, flex: "1 1 160px" }, title: `${fmtDate2(r.m.createdAt)} \xB7 ${r.m.id} \xB7 ${r.m.entity ?? ""} \xB7 signal ${r.m.signal.toFixed(2)} \xB7 hits ${r.m.hits} \xB7 TTL ${daysLeft2(r.m.expiresAt)}`, children: [
3117
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { ...s2.mono, flex: "1 1 160px" }, title: `${fmtDate2(r.m.createdAt)} \xB7 ${r.m.id} \xB7 ${r.m.entity ?? ""} \xB7 signal ${r.m.signal.toFixed(2)} \xB7 hits ${r.m.hits} \xB7 TTL ${daysLeft2(r.m.expiresAt)}`, children: [
3079
3118
  fmtDate2(r.m.createdAt),
3080
3119
  " \xB7 ",
3081
3120
  r.m.id.slice(0, 8),
@@ -3171,7 +3210,7 @@ ${r.m.text.slice(0, 60)}`)) void act(() => api.remove(r.m.id, r.m.workspace));
3171
3210
  " \u4E2A\u4EFB\u52A1"
3172
3211
  ] }),
3173
3212
  items.map((task) => {
3174
- const gaps = taskGaps2(task);
3213
+ const gaps = taskGaps(task);
3175
3214
  const isStable = task.state === "stable";
3176
3215
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: s2.subPanel, children: [
3177
3216
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { fontWeight: 600, fontSize: 13 }, children: [
@@ -3284,7 +3323,6 @@ ${r.m.text.slice(0, 60)}`)) void act(() => api.remove(r.m.id, r.m.workspace));
3284
3323
  {
3285
3324
  target: detail,
3286
3325
  api,
3287
- memories,
3288
3326
  tasks,
3289
3327
  nodes,
3290
3328
  links,
@@ -3330,10 +3368,11 @@ var ADVANCED_GROUPS = [
3330
3368
  {
3331
3369
  id: "retention-adv",
3332
3370
  title: "\u751F\u547D\u5468\u671F\u4E0E GC\uFF08\u9AD8\u7EA7\uFF09",
3333
- description: "\u5BB9\u91CF\u4E0A\u9650\u4E0E\u5B9A\u671F\u56DE\u6536",
3371
+ description: "\u5BB9\u91CF\u4E0A\u9650\u4E0E\u5B9A\u671F\u56DE\u6536\uFF08\u81EA\u52A8 GC = \u66FF\u4EE3 DSH \u81EA\u52A8 compact\uFF09",
3334
3372
  fields: [
3335
- { key: "maxMemoriesPerWorkspace", label: "\u5DE5\u4F5C\u533A\u8BB0\u5FC6\u4E0A\u9650", kind: "num", min: 0, max: 1e4 },
3373
+ { key: "maxMemoriesPerWorkspace", label: "\u5DE5\u4F5C\u533A\u8BB0\u5FC6\u4E0A\u9650", hint: "\u5355\u5DE5\u4F5C\u533A\u5BB9\u91CF\u4E0A\u9650\uFF08GC \u5F52\u6863\u8D85\u9650\u6761\u76EE\uFF09", kind: "num", min: 0, max: 1e4 },
3336
3374
  { key: "gcEnabled", label: "\u8BB0\u5FC6 GC", hint: "\u5B9A\u65F6\u56DE\u6536\uFF08\u8FC7\u671F/\u8D85\u5BB9\u91CF/stable \u8D85\u7A97/\u60AC\u7A7A\u94FE\u63A5\uFF09", kind: "bool" },
3375
+ { key: "gcNarrative", label: "Context GC \u53D9\u4E8B\u515C\u5E95", hint: "\u81EA\u52A8 compact \u65F6\u65E0\u951A\u8F6E\u6B21\u7528 LLM \u6458\u8981\uFF1B\u5173\u6389=\u7EAF\u673A\u68B0\uFF08\u96F6 LLM\uFF09", kind: "bool" },
3337
3376
  { key: "gcStableRetentionDays", label: "stable \u4EFB\u52A1\u4FDD\u7559\uFF08\u5929\uFF09", hint: "\u8D85\u7A97\u540E\u7531 GC \u5F52\u6863\u3001\u79BB\u5F00 [ESR] \u8868\u9762", kind: "num", min: 0, max: 3650 },
3338
3377
  { key: "verifyArtifact", label: "\u6821\u9A8C artifact \u5B58\u5728", hint: "esr_close \u7684 artifact \u987B\u5728\u5DE5\u4F5C\u533A\u78C1\u76D8\u4E0A\u771F\u5B9E\u5B58\u5728\uFF08URL \u8C41\u514D\uFF09\uFF1Bforce:true/\u5173\u6389\u53EF\u8DF3\u8FC7", kind: "bool" }
3339
3378
  ]
@@ -3822,20 +3861,12 @@ var import_client = require("react-dom/client");
3822
3861
  // client/src/EngramBoard.tsx
3823
3862
  var import_react9 = require("react");
3824
3863
  var import_jsx_runtime9 = require("react/jsx-runtime");
3825
- function taskGaps3(t) {
3826
- const gaps = [];
3827
- if (!t.artifact) gaps.push("artifact");
3828
- if (!t.evaluation) gaps.push("evaluation");
3829
- if (!t.memoryRefs || t.memoryRefs.length === 0) gaps.push("memory_ref");
3830
- return gaps;
3831
- }
3832
3864
  var COLUMNS = [
3833
3865
  { key: "draft", title: "\u8349\u7A3F", sub: "\u672A\u88AB\u6FC0\u6D3B", color: "#94a3b8", match: (t) => t.state === "draft" },
3834
- { key: "gapped", title: "\u8FDB\u884C\u4E2D", sub: "\u8BC1\u636E\u6709\u7F3A\u53E3", color: "#f59e0b", match: (t) => t.state === "active" && taskGaps3(t).length > 0 },
3835
- { key: "ready", title: "\u5C31\u7EEA", sub: "\u8BC1\u636E\u9F50\uFF0C\u53EF\u95ED\u73AF", color: "#10b981", match: (t) => t.state === "active" && taskGaps3(t).length === 0 },
3866
+ { key: "gapped", title: "\u8FDB\u884C\u4E2D", sub: "\u8BC1\u636E\u6709\u7F3A\u53E3", color: "#f59e0b", match: (t) => t.state === "active" && taskGaps(t).length > 0 },
3867
+ { key: "ready", title: "\u5C31\u7EEA", sub: "\u8BC1\u636E\u9F50\uFF0C\u53EF\u95ED\u73AF", color: "#10b981", match: (t) => t.state === "active" && taskGaps(t).length === 0 },
3836
3868
  { key: "stable", title: "\u5DF2\u95ED\u73AF", sub: "\u51ED\u636E\u9F50\u5907", color: "#6366f1", match: (t) => t.state === "stable" }
3837
3869
  ];
3838
- var fmtDate3 = (ts) => ts ? new Date(ts).toLocaleDateString("zh-CN", { month: "short", day: "numeric" }) : "\u2013";
3839
3870
  function buildCloseEvidence(bulkArtifact, bulkEval, bulkRefs, task) {
3840
3871
  const refs = bulkRefs.split(/[,\s]+/).map((x) => x.trim()).filter(Boolean);
3841
3872
  return {
@@ -3844,31 +3875,6 @@ function buildCloseEvidence(bulkArtifact, bulkEval, bulkRefs, task) {
3844
3875
  memoryRefs: refs.length > 0 ? refs : task.memoryRefs
3845
3876
  };
3846
3877
  }
3847
- function buildTasksMarkdown(tasks) {
3848
- const esc = (s4) => String(s4 ?? "").replace(/\|/g, "\\|").replace(/\n/g, " ");
3849
- const rows = tasks.slice().sort((a, b) => a.state.localeCompare(b.state) || (a.createdAt ?? 0) - (b.createdAt ?? 0)).map((t) => {
3850
- const ev = `${t.artifact ? "artifact\u2713" : "artifact\u2717"} \xB7 ${t.evaluation ? "eval\u2713" : "eval\u2717"} \xB7 ${(t.memoryRefs?.length ?? 0) > 0 ? "ref\u2713" : "ref\u2717"}`;
3851
- return `| ${t.state} | ${esc(t.name)} | ${esc(t.workspace.replace(/^.*[\\/]/, ""))} | ${esc(taskGaps3(t).join(", ") || "\u2014")} | ${ev} | ${fmtDate3(t.createdAt ?? 0)} |`;
3852
- });
3853
- return `# ESR \u4EFB\u52A1\u5BFC\u51FA \xB7 ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}
3854
-
3855
- | \u72B6\u6001 | \u4EFB\u52A1 | \u5DE5\u4F5C\u533A | \u8BC1\u636E\u7F3A\u53E3 | \u8BC1\u636E | \u521B\u5EFA |
3856
- |---|---|---|---|---|---|
3857
- ${rows.join("\n") || "(\u65E0\u4EFB\u52A1)"}`;
3858
- }
3859
- function shortId(id) {
3860
- return id.length > 12 ? `${id.slice(0, 6)}\u2026${id.slice(-4)}` : id;
3861
- }
3862
- function blockedBy(t, all) {
3863
- if (t.state === "stable") return 0;
3864
- const open = new Set(all.filter((x) => x.state !== "stable").map((x) => x.id));
3865
- return (t.deps ?? []).filter((d) => d.kind !== "relates-to" && open.has(d.id)).length;
3866
- }
3867
- function shortAgent(a) {
3868
- if (!a) return "";
3869
- const bare = a.split(/[@#/:\\]+/).pop();
3870
- return bare.replace(/^session[-_]/i, "").replace(/^agent[-_]/i, "").slice(0, 14);
3871
- }
3872
3878
  var TREND_LABEL = {
3873
3879
  new: "\u65B0",
3874
3880
  strengthening: "\u589E\u5F3A",
@@ -3904,7 +3910,6 @@ function EngramBoard({ api, onRequestClose }) {
3904
3910
  const [bulkRefs, setBulkRefs] = (0, import_react9.useState)("");
3905
3911
  const [bulkBusy, setBulkBusy] = (0, import_react9.useState)(false);
3906
3912
  const [bulkMsg, setBulkMsg] = (0, import_react9.useState)(null);
3907
- const loadedWs = (0, import_react9.useRef)(/* @__PURE__ */ new Set());
3908
3913
  const [obsItems, setObsItems] = (0, import_react9.useState)(null);
3909
3914
  const [obsOpen, setObsOpen] = (0, import_react9.useState)(false);
3910
3915
  const [model, setModel] = (0, import_react9.useState)(null);
@@ -3917,10 +3922,8 @@ function EngramBoard({ api, onRequestClose }) {
3917
3922
  const wsList = Object.keys(ov.workspaces);
3918
3923
  const entries = await Promise.all(
3919
3924
  wsList.map(async (w) => {
3920
- const shouldLoad = !loadedWs.current.has(w);
3921
- if (shouldLoad) loadedWs.current.add(w);
3922
- const res = shouldLoad ? await Promise.all([api.tasks(w, true), api.nodes(w), api.links(w)]) : null;
3923
- return [w, res ? { tasks: res[0].items, nodes: res[1].items, links: res[2].items } : null];
3925
+ const res = await Promise.all([api.tasks(w, true), api.nodes(w), api.links(w)]);
3926
+ return [w, { tasks: res[0].items, nodes: res[1].items, links: res[2].items }];
3924
3927
  })
3925
3928
  );
3926
3929
  setTasksByWs((prev) => {
@@ -3953,25 +3956,9 @@ function EngramBoard({ api, onRequestClose }) {
3953
3956
  }, [api]);
3954
3957
  (0, import_react9.useEffect)(() => {
3955
3958
  void refresh();
3956
- const id = setInterval(() => void refresh(), 2e4);
3959
+ const id = setInterval(() => void refresh(), POLL_MS);
3957
3960
  return () => clearInterval(id);
3958
3961
  }, [refresh]);
3959
- (0, import_react9.useEffect)(() => {
3960
- if (!ws || denied) return;
3961
- const id = setInterval(() => {
3962
- const fetchWs = async () => {
3963
- try {
3964
- const [res, nodeRes, linkRes] = await Promise.all([api.tasks(ws, true), api.nodes(ws), api.links(ws)]);
3965
- setTasksByWs((prev) => ({ ...prev, [ws]: res.items }));
3966
- setNodesByWs((prev) => ({ ...prev, [ws]: nodeRes.items }));
3967
- setLinksByWs((prev) => ({ ...prev, [ws]: linkRes.items }));
3968
- } catch {
3969
- }
3970
- };
3971
- void fetchWs();
3972
- }, 2e4);
3973
- return () => clearInterval(id);
3974
- }, [ws, denied, api]);
3975
3962
  const workspaces = (0, import_react9.useMemo)(
3976
3963
  () => overview ? Object.entries(overview.workspaces).sort((a, b) => b[1].tasks - a[1].tasks) : [],
3977
3964
  [overview]
@@ -4004,7 +3991,7 @@ function EngramBoard({ api, onRequestClose }) {
4004
3991
  }
4005
3992
  if (t.state === "draft") continue;
4006
3993
  active += 1;
4007
- const gates = taskGaps3(t);
3994
+ const gates = taskGaps(t);
4008
3995
  gateTotal += 3;
4009
3996
  gateFilled += 3 - gates.length;
4010
3997
  if (gates.length === 0) ready += 1;
@@ -4033,7 +4020,6 @@ function EngramBoard({ api, onRequestClose }) {
4033
4020
  setBusy(true);
4034
4021
  try {
4035
4022
  await api.createTask(targetWs, name, newDesc);
4036
- loadedWs.current.add(targetWs);
4037
4023
  setNewName("");
4038
4024
  setNewDesc("");
4039
4025
  setCreating(false);
@@ -4322,7 +4308,7 @@ function EngramBoard({ api, onRequestClose }) {
4322
4308
  "@",
4323
4309
  shortAgent(t.assignee)
4324
4310
  ] }),
4325
- (col.key === "gapped" || col.key === "ready") && taskGaps3(t).map((g) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { style: hb.gap, children: [
4311
+ (col.key === "gapped" || col.key === "ready") && taskGaps(t).map((g) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { style: hb.gap, children: [
4326
4312
  g,
4327
4313
  " \u2717"
4328
4314
  ] }, g)),
@@ -4334,7 +4320,7 @@ function EngramBoard({ api, onRequestClose }) {
4334
4320
  "\xB7ref",
4335
4321
  (t.memoryRefs?.length ?? 0) > 0 ? "\u2713" : "\u2717"
4336
4322
  ] }),
4337
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: hb.meta, children: fmtDate3(t.createdAt) })
4323
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: hb.meta, children: fmtDateShort(t.createdAt) })
4338
4324
  ] })
4339
4325
  ] })
4340
4326
  ] }),
@@ -0,0 +1,85 @@
1
+ /**
2
+ * dsh-engram — Context GC as a mountable HOST plugin entry (agent-preset row).
3
+ *
4
+ * Web/agent-preset compositions keep compaction on the AGENT plane: the
5
+ * `standard` preset (and friends) mount `@deepseek-ai/dsh-compaction-basic`
6
+ * inside a `compaction` group carrying its own isolate realm. A host-plane row
7
+ * cannot reach that realm, so replacing the default summarizer per-session
8
+ * needs a row that can be listed IN the preset's `compaction` group:
9
+ *
10
+ * ```yaml
11
+ * - id: compaction
12
+ * name: cordis:group
13
+ * group: true
14
+ * isolate:
15
+ * compaction: true
16
+ * toolResultPruner: true
17
+ * config:
18
+ * - id: engram-compaction
19
+ * name: dsh-engram/compaction # this entry
20
+ * - id: command-compact
21
+ * name: '@deepseek-ai/dsh-command-compact'
22
+ * ...
23
+ * ```
24
+ *
25
+ * Config (all optional): `gcReplacesCompaction` (true → Context GC; false →
26
+ * plain BasicCompactionEngine), `gcNarrative`, `gcNarrativeMaxTokens`,
27
+ * `gcNarrativeMaxChars`. Values default to the plugin DEFAULTS.
28
+ *
29
+ * The engine reads engram/ESR state through the storage-domain service shared
30
+ * with the host plane (best-effort reads for pointer classification — the
31
+ * entry never writes).
32
+ */
33
+ import { openEngramDomain } from "./store.js";
34
+ import { loadCompactionEngine, collectPointerContext } from "./context-gc.js";
35
+ import { workspaceKey as wk } from "./util.js";
36
+
37
+ export const name = "dsh-engram-compaction";
38
+ export const inject = ["llm", "tokenMeter", "sessions"];
39
+
40
+ /** One cached domain per storage-domain service (read-only for compaction). */
41
+ const domains = new WeakMap();
42
+
43
+ async function domainFor(ctx) {
44
+ const storageDomain = ctx.get("storageDomain");
45
+ if (!storageDomain) return null;
46
+ let domain = domains.get(storageDomain);
47
+ if (!domain) {
48
+ domain = await openEngramDomain(storageDomain).catch(() => null);
49
+ if (domain) domains.set(storageDomain, domain);
50
+ }
51
+ return domain;
52
+ }
53
+
54
+ export function apply(ctx, config = {}) {
55
+ const mode = config.gcReplacesCompaction === false ? "default" : "context-gc";
56
+ const deps =
57
+ mode === "context-gc"
58
+ ? {
59
+ narrativeEnabled: config.gcNarrative !== false,
60
+ narrativeMaxTokens: config.gcNarrativeMaxTokens ?? 1024,
61
+ narrativeMaxChars: config.gcNarrativeMaxChars ?? 4000,
62
+ readWorkspace: async (workspace, report) => {
63
+ if (!workspace) return null;
64
+ const domain = await domainFor(ctx);
65
+ if (!domain) return null;
66
+ return collectPointerContext(domain, wk(workspace), report);
67
+ },
68
+ }
69
+ : {};
70
+
71
+ return loadCompactionEngine(ctx, { auto: true }, deps, { mode })
72
+ .then((Engine) => {
73
+ if (!Engine) {
74
+ ctx.logger?.warn?.("engram context-gc: dsh-compaction-basic unavailable — no compaction engine");
75
+ return;
76
+ }
77
+ new Engine();
78
+ ctx.logger?.info?.(
79
+ `engram context-gc: compaction = ${mode === "context-gc" ? "Context GC (mechanical eviction + re-fetch pointers)" : "default LLM summarizer (gcReplacesCompaction:false)"}`,
80
+ );
81
+ })
82
+ .catch((error) => {
83
+ ctx.logger?.warn?.(`engram context-gc mount failed: ${String(error)}`);
84
+ });
85
+ }