dsh-engram 0.4.0 → 0.5.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 +142 -17
- package/README.zh.md +110 -15
- package/docs/PROPOSAL-resultpack.zh.md +7 -3
- package/docs/RELEASE-NOTES-v0.5.0.md +58 -0
- package/docs/RELEASE-NOTES-v0.5.1.md +54 -0
- package/lib/api.js +19 -6
- package/lib/boundary-prune.js +115 -14
- package/lib/client.js +253 -3
- package/lib/index-block.js +27 -5
- package/lib/index.js +181 -5
- package/lib/pack-telemetry.js +115 -0
- package/lib/preference.js +276 -0
- package/lib/result-pack.js +276 -0
- package/lib/session-cjk.js +2 -2
- package/lib/settings.js +115 -31
- package/lib/store.js +1 -0
- package/lib/tools.js +2 -1
- package/lib/usage.js +47 -4
- package/package.json +8 -7
- package/scripts/prepublish-check.mjs +7 -4
package/lib/api.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { uuid, shortId } from "./util.js";
|
|
14
14
|
import { collectToolCounts, collectUsageStats } from "./usage.js";
|
|
15
|
-
import { SETTINGS_NS } from "./settings.js";
|
|
15
|
+
import { SETTINGS_NS, WIRE_SETTINGS_NS, LEGACY_SETTINGS_NS } from "./settings.js";
|
|
16
16
|
|
|
17
17
|
/** Route family prefix (kept in sync with the client half). */
|
|
18
18
|
export const API_PREFIX = "/api/dsh-engram";
|
|
@@ -160,8 +160,9 @@ export function makeEngramRoutes(service) {
|
|
|
160
160
|
writeJson(res, 200, {
|
|
161
161
|
...summary,
|
|
162
162
|
indexes,
|
|
163
|
-
captures: service.captureStats ?? { total: 0, git: 0, file: 0, error: 0 },
|
|
163
|
+
captures: service.captureStats ?? { total: 0, git: 0, file: 0, error: 0, preference: 0 },
|
|
164
164
|
gc: service.gcStats ?? { lastRun: 0, archivedMemories: 0, archivedTasks: 0, removedLinks: 0 },
|
|
165
|
+
activation: service.packTelemetry ? service.packTelemetry() : { boundaryPrune: null, recall: null },
|
|
165
166
|
compaction: service.compactionStatus ?? { host: "unknown", web: { action: "unknown", presets: [] } },
|
|
166
167
|
config: service.config ?? {},
|
|
167
168
|
anonymous: {
|
|
@@ -512,6 +513,10 @@ export function makeEngramRoutes(service) {
|
|
|
512
513
|
recallHitsPerQuery: queries > 0 ? +(hitsTotal / queries).toFixed(3) : null,
|
|
513
514
|
detailFollowRate: queries > 0 ? +(detailFollows / queries).toFixed(4) : null,
|
|
514
515
|
},
|
|
516
|
+
// SoL-Pi activation telemetry: the durable BoundaryPrune / ResultPack
|
|
517
|
+
// activation record (fires actually landed on this profile) + the
|
|
518
|
+
// [RECALL] injection-funnel leg (blocks actually injected into sessions).
|
|
519
|
+
activation: service.packTelemetry ? service.packTelemetry() : { boundaryPrune: null, recall: null },
|
|
515
520
|
});
|
|
516
521
|
};
|
|
517
522
|
|
|
@@ -561,8 +566,13 @@ export function makeEngramRoutes(service) {
|
|
|
561
566
|
// Normalize both onto the HTTP contract {namespaces:[...]} the client uses.
|
|
562
567
|
const payload = settings.describe();
|
|
563
568
|
const namespaces = Array.isArray(payload) ? payload : (payload?.namespaces ?? []);
|
|
564
|
-
|
|
565
|
-
|
|
569
|
+
// The seam carries the ACTIVE namespace — entry id `engram` on DSH
|
|
570
|
+
// ≥0.1.7 / 0.2.x, legacy `dsh-engram` on ≤0.1.5. The view is re-labelled
|
|
571
|
+
// onto the stable WIRE_SETTINGS_NS the client card filters on, so the
|
|
572
|
+
// shipped client works unchanged across generations.
|
|
573
|
+
const activeNs = settings.ns ?? SETTINGS_NS;
|
|
574
|
+
const view = namespaces.find((n) => n && (n.ns === activeNs || n.ns === LEGACY_SETTINGS_NS));
|
|
575
|
+
writeJson(res, 200, view ? { namespaces: [{ ...view, ns: WIRE_SETTINGS_NS }] } : { namespaces: [] });
|
|
566
576
|
} catch (error) {
|
|
567
577
|
writeError(res, 500, String(error?.message ?? error));
|
|
568
578
|
}
|
|
@@ -586,10 +596,13 @@ export function makeEngramRoutes(service) {
|
|
|
586
596
|
return writeError(res, 400, "body must provide ops (array of {op,path[,value]}) or patch (object)");
|
|
587
597
|
}
|
|
588
598
|
try {
|
|
599
|
+
// Drive the seam's ACTIVE namespace (entry id `engram` on DSH ≥0.1.7 /
|
|
600
|
+
// 0.2.x, legacy `dsh-engram` on ≤0.1.5), never the wire label.
|
|
601
|
+
const activeNs = settings.ns ?? SETTINGS_NS;
|
|
589
602
|
if (Array.isArray(ops)) {
|
|
590
|
-
await settings.mutate(
|
|
603
|
+
await settings.mutate(activeNs, ops, expectedRevision);
|
|
591
604
|
} else {
|
|
592
|
-
await settings.update(
|
|
605
|
+
await settings.update(activeNs, patch, expectedRevision);
|
|
593
606
|
}
|
|
594
607
|
writeJson(res, 200, { ok: true });
|
|
595
608
|
} catch (error) {
|
package/lib/boundary-prune.js
CHANGED
|
@@ -40,6 +40,7 @@ import fs from "node:fs";
|
|
|
40
40
|
import os from "node:os";
|
|
41
41
|
import path from "node:path";
|
|
42
42
|
|
|
43
|
+
import { sessionLogFile } from "./usage.js";
|
|
43
44
|
import { workspaceKey as wk } from "./util.js";
|
|
44
45
|
|
|
45
46
|
/** Fixed fallback for the repayment gate when no history exists yet. */
|
|
@@ -93,7 +94,7 @@ export function workspaceHistory(root = path.join(os.homedir(), ".dsh", "session
|
|
|
93
94
|
let sessions = 0;
|
|
94
95
|
const bdir = path.join(root, bucket);
|
|
95
96
|
for (const sid of fs.readdirSync(bdir)) {
|
|
96
|
-
const file = path.join(bdir, sid
|
|
97
|
+
const file = sessionLogFile(path.join(bdir, sid));
|
|
97
98
|
try {
|
|
98
99
|
const st = fs.statSync(file);
|
|
99
100
|
if (st.size > 0) sessions += 1;
|
|
@@ -128,7 +129,7 @@ export function expectedSessionRequests(requestsSoFar, history, workspace, floor
|
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
/** Install the boundary-prune listener. Returns a disposer. */
|
|
131
|
-
export function installBoundaryPrune(ctx, config, log) {
|
|
132
|
+
export function installBoundaryPrune(ctx, config, log, hooks = {}, opts = {}) {
|
|
132
133
|
const enabled = config?.boundaryPrune === true;
|
|
133
134
|
if (!enabled) return () => {};
|
|
134
135
|
const pruner = ctx.get?.("toolResultPruner");
|
|
@@ -139,7 +140,44 @@ export function installBoundaryPrune(ctx, config, log) {
|
|
|
139
140
|
const minRemaining = Number.isFinite(config?.boundaryPruneMinRemaining)
|
|
140
141
|
? config.boundaryPruneMinRemaining
|
|
141
142
|
: DEFAULT_MIN_REMAINING;
|
|
142
|
-
|
|
143
|
+
// History is injectable for tests; production reads the real session corpus.
|
|
144
|
+
const history = opts?.history ?? workspaceHistory();
|
|
145
|
+
|
|
146
|
+
// Optional threshold override (ResultPack L0, T2048 landing): the host pruner
|
|
147
|
+
// ships a fixed threshold (8192) that the plugin cannot change in place. When
|
|
148
|
+
// `boundaryPruneThresholdChars` is set, we instantiate our own pruner at that
|
|
149
|
+
// threshold — same deterministic head/middle/tail engine, lower budget — via
|
|
150
|
+
// the exported `ToolResultPruner` prototype (bypassing the cordis Service
|
|
151
|
+
// constructor, which needs a full context; the instance only uses its own
|
|
152
|
+
// `config` + `ctx.tokenMeter`). Falls back to the host pruner when the package
|
|
153
|
+
// or the token meter is unavailable. `boundaryPruneHeadChars` /
|
|
154
|
+
// `boundaryPruneTailChars` tune the retained edges (defaults stay at the
|
|
155
|
+
// host's 4096/1024).
|
|
156
|
+
let activePruner = pruner;
|
|
157
|
+
const thresholdOverride = Number.isFinite(config?.boundaryPruneThresholdChars)
|
|
158
|
+
? Math.trunc(config.boundaryPruneThresholdChars)
|
|
159
|
+
: null;
|
|
160
|
+
if (thresholdOverride !== null && thresholdOverride > 0) {
|
|
161
|
+
activePruner = makeThresholdPruner(pruner, {
|
|
162
|
+
thresholdChars: thresholdOverride,
|
|
163
|
+
headChars: Number.isFinite(config?.boundaryPruneHeadChars) && config.boundaryPruneHeadChars > 0
|
|
164
|
+
? Math.trunc(config.boundaryPruneHeadChars)
|
|
165
|
+
: 4096,
|
|
166
|
+
tailChars: Number.isFinite(config?.boundaryPruneTailChars) && config.boundaryPruneTailChars > 0
|
|
167
|
+
? Math.trunc(config.boundaryPruneTailChars)
|
|
168
|
+
: 1024,
|
|
169
|
+
});
|
|
170
|
+
if (activePruner === null) {
|
|
171
|
+
log?.warn?.(
|
|
172
|
+
`engram boundary-prune: threshold override ${thresholdOverride} unavailable (pruner package/tokenMeter) — using host pruner ${pruner.config?.thresholdChars ?? "default"}`,
|
|
173
|
+
);
|
|
174
|
+
activePruner = pruner;
|
|
175
|
+
} else {
|
|
176
|
+
log?.info?.(
|
|
177
|
+
`engram boundary-prune: using threshold ${activePruner.config?.thresholdChars} (head ${activePruner.config?.headChars} / tail ${activePruner.config?.tailChars})`,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
143
181
|
|
|
144
182
|
/** Per-session boundary state (todo high-water mark, request count, total prunes). */
|
|
145
183
|
const stateBySession = new Map();
|
|
@@ -150,12 +188,32 @@ export function installBoundaryPrune(ctx, config, log) {
|
|
|
150
188
|
if (typeof sid !== "string" || sid.length === 0) return;
|
|
151
189
|
let state = stateBySession.get(sid);
|
|
152
190
|
if (!state) {
|
|
153
|
-
state = { todoCompleted: 0, requests: 0, prunes: 0, prunedResults: 0 };
|
|
191
|
+
state = { todoCompleted: 0, requests: 0, prunes: 0, prunedResults: 0, lastFireRequest: void 0 };
|
|
154
192
|
stateBySession.set(sid, state);
|
|
155
193
|
}
|
|
156
194
|
const type = event?.type;
|
|
157
195
|
if (type === "assistant/message") {
|
|
158
196
|
state.requests += 1;
|
|
197
|
+
// Request-count boundary (SoL-Pi "step idle", default OFF): sessions
|
|
198
|
+
// with no todo progress / no goal never fire the event boundaries (L0
|
|
199
|
+
// corpus: ~62% of sessions), leaving their over-threshold replay
|
|
200
|
+
// exposure permanently untouched. When `boundaryPruneIdleCadence` is
|
|
201
|
+
// set, the counter itself is a boundary source: fire at most once per
|
|
202
|
+
// cadence requests, still under the repayment gate (so cadence ≠
|
|
203
|
+
// aggressive — the gate keeps per-session reuse healthy). The implied
|
|
204
|
+
// boundary kind is `step` so telemetry can attribute it.
|
|
205
|
+
const cadence = config?.boundaryPruneIdleCadence;
|
|
206
|
+
if (Number.isFinite(cadence) && cadence > 0 && state.requests % Math.trunc(cadence) === 0) {
|
|
207
|
+
const sinceLastPrune = state.lastFireRequest === void 0 ? state.requests : state.requests - state.lastFireRequest;
|
|
208
|
+
if (sinceLastPrune >= Math.trunc(cadence)) {
|
|
209
|
+
const workspace = wk(session?.header?.cwd);
|
|
210
|
+
const expectedTotal = expectedSessionRequests(state.requests, history, workspace, minRemaining);
|
|
211
|
+
if (state.requests + minRemaining <= expectedTotal) {
|
|
212
|
+
// reuse the same fire path via a synthetic boundary
|
|
213
|
+
return fireBoundary(session, { kind: "step" }, state, expectedTotal);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
159
217
|
return;
|
|
160
218
|
}
|
|
161
219
|
const boundary = isBoundary(event, state);
|
|
@@ -168,21 +226,43 @@ export function installBoundaryPrune(ctx, config, log) {
|
|
|
168
226
|
const workspace = wk(session?.header?.cwd);
|
|
169
227
|
const expectedTotal = expectedSessionRequests(state.requests, history, workspace, minRemaining);
|
|
170
228
|
if (state.requests + minRemaining > expectedTotal) return;
|
|
171
|
-
|
|
172
|
-
const result = pruner.pruneSession(session);
|
|
173
|
-
const prunedNow = result?.pruned?.length ?? 0;
|
|
174
|
-
if (prunedNow > 0) {
|
|
175
|
-
state.prunes += 1;
|
|
176
|
-
state.prunedResults += prunedNow;
|
|
177
|
-
log?.info?.(
|
|
178
|
-
`engram boundary-prune (${boundary.kind} @${state.requests} reqs): pruned ${prunedNow} results, saved ${result.charsRemoved} chars`,
|
|
179
|
-
);
|
|
180
|
-
}
|
|
229
|
+
return fireBoundary(session, boundary, state, expectedTotal);
|
|
181
230
|
} catch (error) {
|
|
182
231
|
// fully contained — a prune bug must never disturb the session pipeline
|
|
183
232
|
log?.warn?.(`engram boundary-prune threw: ${String(error)}`);
|
|
184
233
|
}
|
|
185
234
|
};
|
|
235
|
+
|
|
236
|
+
/** Shared fire path for every boundary source (event + step-cadence). */
|
|
237
|
+
const fireBoundary = (session, boundary, state, expectedTotal) => {
|
|
238
|
+
const result = activePruner.pruneSession(session);
|
|
239
|
+
const prunedNow = result?.pruned?.length ?? 0;
|
|
240
|
+
if (prunedNow > 0) {
|
|
241
|
+
state.prunes += 1;
|
|
242
|
+
state.prunedResults += prunedNow;
|
|
243
|
+
state.lastFireRequest = state.requests;
|
|
244
|
+
log?.info?.(
|
|
245
|
+
`engram boundary-prune (${boundary.kind} @${state.requests} reqs): pruned ${prunedNow} results, saved ${result.charsRemoved} chars`,
|
|
246
|
+
);
|
|
247
|
+
// L2 hook (ResultPack): archiving + handle tail is wired by index.js
|
|
248
|
+
// when `resultPack` is enabled. Contained there — never throws here.
|
|
249
|
+
// Attach the live pruner budgets so the archiver extracts the exact
|
|
250
|
+
// removed span (archives must match what the surface now displays).
|
|
251
|
+
try {
|
|
252
|
+
const budgets = {
|
|
253
|
+
headChars: activePruner.config?.headChars ?? 4096,
|
|
254
|
+
tailChars: activePruner.config?.tailChars ?? 1024,
|
|
255
|
+
};
|
|
256
|
+
for (const item of result?.pruned ?? []) {
|
|
257
|
+
if (item && typeof item === "object") Object.assign(item, budgets);
|
|
258
|
+
}
|
|
259
|
+
hooks?.onPruned?.(session, result, boundary);
|
|
260
|
+
} catch (error) {
|
|
261
|
+
log?.warn?.(`engram boundary-prune: L2 hook threw: ${String(error)}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
void expectedTotal;
|
|
265
|
+
};
|
|
186
266
|
const disposeListener = ctx.on("session/event", onEvent);
|
|
187
267
|
const disposeSink = ctx.on("session/disposed", (session) => {
|
|
188
268
|
stateBySession.delete(session?.id);
|
|
@@ -192,3 +272,24 @@ export function installBoundaryPrune(ctx, config, log) {
|
|
|
192
272
|
disposeSink?.();
|
|
193
273
|
};
|
|
194
274
|
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Build a pruner at an override threshold by prototyping off the injected
|
|
278
|
+
* host service instance.
|
|
279
|
+
*
|
|
280
|
+
* The host `toolResultPruner` ships a fixed budget (8192/4096/1024) that
|
|
281
|
+
* cannot be mutated in place. DSH's pruner is a plain prototype-owning class
|
|
282
|
+
* whose methods only read `this.config` and `this.ctx.tokenMeter`, so we
|
|
283
|
+
* create a child object inheriting the host instance (methods + ctx shared,
|
|
284
|
+
* config overridden on the child). Zero LLM, deterministic, contained — the
|
|
285
|
+
* host instance and its config stay untouched.
|
|
286
|
+
*/
|
|
287
|
+
function makeThresholdPruner(pruner, cfg) {
|
|
288
|
+
try {
|
|
289
|
+
const clone = Object.create(pruner);
|
|
290
|
+
clone.config = cfg;
|
|
291
|
+
return clone;
|
|
292
|
+
} catch {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
}
|
package/lib/client.js
CHANGED
|
@@ -153,6 +153,28 @@ var EngramApi = class {
|
|
|
153
153
|
})
|
|
154
154
|
);
|
|
155
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* Data contract (issue #3) — dump the selected workspace, or every
|
|
158
|
+
* workspace when omitted, as a `dsh-engram/export` v1 payload. The same
|
|
159
|
+
* format `POST /import` restores, so the dump is a portable backup.
|
|
160
|
+
*/
|
|
161
|
+
async exportData(workspace) {
|
|
162
|
+
return readJson(await fetch(`${API_PREFIX}/export${query({ workspace })}`));
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Data contract (issue #3) — restore a dump. `merge` adds missing rows
|
|
166
|
+
* (idempotent), `replace` wipes the target workspace first (the host then
|
|
167
|
+
* requires `confirm: "restore"`). `dryRun` plans without writing.
|
|
168
|
+
*/
|
|
169
|
+
async importData(body) {
|
|
170
|
+
return readJson(
|
|
171
|
+
await fetch(`${API_PREFIX}/import`, {
|
|
172
|
+
method: "POST",
|
|
173
|
+
headers: { "content-type": "application/json" },
|
|
174
|
+
body: JSON.stringify(body)
|
|
175
|
+
})
|
|
176
|
+
);
|
|
177
|
+
}
|
|
156
178
|
/** Settings card transport — read the `dsh-engram` namespace through our
|
|
157
179
|
* own HTTP API instead of the host connection/remote RPC. */
|
|
158
180
|
async getSettings() {
|
|
@@ -2209,6 +2231,7 @@ function EngramTelemetry({ api, workspace }) {
|
|
|
2209
2231
|
var import_react6 = require("react");
|
|
2210
2232
|
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
2211
2233
|
var KIND_COLORS = {
|
|
2234
|
+
preference: "#b45309",
|
|
2212
2235
|
decision: "#2563eb",
|
|
2213
2236
|
error: "#dc2626",
|
|
2214
2237
|
procedure: "#7c3aed",
|
|
@@ -2218,6 +2241,7 @@ var KIND_COLORS = {
|
|
|
2218
2241
|
task: "#4f46e5"
|
|
2219
2242
|
};
|
|
2220
2243
|
var KIND_LABEL = {
|
|
2244
|
+
preference: "\u504F\u597D",
|
|
2221
2245
|
decision: "\u51B3\u5B9A",
|
|
2222
2246
|
error: "\u9519\u8BEF",
|
|
2223
2247
|
procedure: "\u6D41\u7A0B",
|
|
@@ -2252,7 +2276,7 @@ function gapsOf(t) {
|
|
|
2252
2276
|
if (!t.memoryRefs || t.memoryRefs.length === 0) gaps.push("memory_ref");
|
|
2253
2277
|
return gaps;
|
|
2254
2278
|
}
|
|
2255
|
-
function EngramDetail({ target, api, tasks, nodes, links, onClose, onNavigateMemory, onChanged }) {
|
|
2279
|
+
function EngramDetail({ target, api, tasks, nodes, links, memories = [], onClose, onNavigateMemory, onChanged }) {
|
|
2256
2280
|
const { dark, vars } = useEngramTheme();
|
|
2257
2281
|
const [gArt, setGArt] = (0, import_react6.useState)("");
|
|
2258
2282
|
const [gEval, setGEval] = (0, import_react6.useState)("");
|
|
@@ -2272,6 +2296,15 @@ function EngramDetail({ target, api, tasks, nodes, links, onClose, onNavigateMem
|
|
|
2272
2296
|
if (target.kind !== "node" || !node) return [];
|
|
2273
2297
|
return links.filter((l) => l.source === node.id || l.target === node.id).map((l) => ({ l, outgoing: l.source === node.id }));
|
|
2274
2298
|
}, [links, node, target.kind]);
|
|
2299
|
+
const memoryLinks = (0, import_react6.useMemo)(() => {
|
|
2300
|
+
if (target.kind !== "memory") return null;
|
|
2301
|
+
const m = target.memory;
|
|
2302
|
+
return {
|
|
2303
|
+
m,
|
|
2304
|
+
replacedBy: memories.filter((x) => x.status === "active" && x.supersedes === m.id),
|
|
2305
|
+
contradictedBy: memories.filter((x) => x.status === "active" && x.contradicts === m.id)
|
|
2306
|
+
};
|
|
2307
|
+
}, [target, memories]);
|
|
2275
2308
|
const hb2 = {
|
|
2276
2309
|
root: {
|
|
2277
2310
|
...vars,
|
|
@@ -2302,6 +2335,8 @@ function EngramDetail({ target, api, tasks, nodes, links, onClose, onNavigateMem
|
|
|
2302
2335
|
badge: { fontSize: 10.5, fontWeight: 700, padding: "2px 8px", borderRadius: 999, color: "#fff" },
|
|
2303
2336
|
tag: { fontSize: 11, padding: "1px 8px", borderRadius: 999, background: "var(--dsh-color-hover-bg, #f3f4f6)", color: "var(--dsh-color-muted, #6b7280)", border: "1px solid var(--dsh-color-border, #e5e7eb)" },
|
|
2304
2337
|
chip: { fontSize: 11, padding: "2px 9px", borderRadius: 999, background: "rgba(37,99,235,.1)", color: "#2563eb", border: "1px solid rgba(37,99,235,.25)", cursor: "pointer" },
|
|
2338
|
+
chipSup: { fontSize: 11, padding: "2px 9px", borderRadius: 999, background: "rgba(124,58,237,.1)", color: "#7c3aed", border: "1px solid rgba(124,58,237,.25)", cursor: "pointer" },
|
|
2339
|
+
chipDanger: { fontSize: 11, padding: "2px 9px", borderRadius: 999, background: "rgba(220,38,38,.1)", color: "#dc2626", border: "1px solid rgba(220,38,38,.25)", cursor: "pointer" },
|
|
2305
2340
|
input: { fontSize: 12, padding: "5px 8px", border: "1px solid var(--dsh-color-border, #d1d5db)", borderRadius: 8, background: "var(--dsh-color-surface, #ffffff)", color: "var(--dsh-color-muted-strong, #374151)", width: "100%", boxSizing: "border-box" },
|
|
2306
2341
|
btn: { fontSize: 11.5, fontWeight: 600, padding: "5px 10px", borderRadius: 8, cursor: "pointer", border: "1px solid var(--dsh-color-border, #d1d5db)", background: "transparent", color: "var(--dsh-color-muted-strong, #374151)" },
|
|
2307
2342
|
btnPrimary: { fontSize: 11.5, fontWeight: 700, padding: "5px 12px", borderRadius: 8, cursor: "pointer", border: "none", background: "#2563eb", color: "#fff" },
|
|
@@ -2440,6 +2475,29 @@ function EngramDetail({ target, api, tasks, nodes, links, onClose, onNavigateMem
|
|
|
2440
2475
|
t
|
|
2441
2476
|
] }, t)) })
|
|
2442
2477
|
] }),
|
|
2478
|
+
memoryLinks && (memoryLinks.m.supersedes || memoryLinks.m.contradicts || memoryLinks.replacedBy.length > 0 || memoryLinks.contradictedBy.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: hb2.section, children: [
|
|
2479
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: hb2.k, children: "\u66FF\u6362 / \u51B2\u7A81" }),
|
|
2480
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", gap: 5, flexWrap: "wrap", marginTop: 3 }, children: [
|
|
2481
|
+
memoryLinks.m.supersedes && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { style: hb2.chipSup, title: `\u672C\u6761\u66FF\u4EE3 ${memoryLinks.m.supersedes}\uFF08\u65E7\u884C\u4FDD\u7559\u3001\u53EC\u56DE\u964D\u7EA7\uFF09`, onClick: () => onNavigateMemory(memoryLinks.m.supersedes), children: [
|
|
2482
|
+
"\u66FF\u4EE3 #",
|
|
2483
|
+
memoryLinks.m.supersedes.slice(0, 8)
|
|
2484
|
+
] }),
|
|
2485
|
+
memoryLinks.m.contradicts && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { style: hb2.chipDanger, title: `\u672C\u6761\u4E0E ${memoryLinks.m.contradicts} \u51B2\u7A81\uFF08\u4E24\u6761\u90FD\u4FDD\u7559\uFF09`, onClick: () => onNavigateMemory(memoryLinks.m.contradicts), children: [
|
|
2486
|
+
"\u51B2\u7A81 #",
|
|
2487
|
+
memoryLinks.m.contradicts.slice(0, 8)
|
|
2488
|
+
] }),
|
|
2489
|
+
memoryLinks.replacedBy.map((x) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { style: hb2.chipSup, title: `\u5DF2\u88AB ${x.text.slice(0, 40)}\u2026 \u66FF\u4EE3`, onClick: () => onNavigateMemory(x.id), children: [
|
|
2490
|
+
"\u2190 \u88AB #",
|
|
2491
|
+
x.id.slice(0, 8),
|
|
2492
|
+
" \u66FF\u4EE3"
|
|
2493
|
+
] }, x.id)),
|
|
2494
|
+
memoryLinks.contradictedBy.map((x) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { style: hb2.chipDanger, title: `${x.text.slice(0, 40)}\u2026 \u4E0E\u672C\u6761\u51B2\u7A81`, onClick: () => onNavigateMemory(x.id), children: [
|
|
2495
|
+
"\u21C4 #",
|
|
2496
|
+
x.id.slice(0, 8),
|
|
2497
|
+
" \u51B2\u7A81"
|
|
2498
|
+
] }, x.id))
|
|
2499
|
+
] })
|
|
2500
|
+
] }),
|
|
2443
2501
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: hb2.section, children: [
|
|
2444
2502
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: hb2.k, children: "\u5143\u6570\u636E" }),
|
|
2445
2503
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 2, marginTop: 3, fontSize: 11.5, fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", color: "var(--dsh-color-muted, #6b7280)" }, children: [
|
|
@@ -2467,6 +2525,18 @@ function EngramDetail({ target, api, tasks, nodes, links, onClose, onNavigateMem
|
|
|
2467
2525
|
" \xB7 updated ",
|
|
2468
2526
|
fmtDate(target.memory.updatedAt)
|
|
2469
2527
|
] }),
|
|
2528
|
+
target.memory.source && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { children: [
|
|
2529
|
+
"source ",
|
|
2530
|
+
target.memory.source
|
|
2531
|
+
] }),
|
|
2532
|
+
target.memory.conditions && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { children: [
|
|
2533
|
+
"conditions ",
|
|
2534
|
+
target.memory.conditions
|
|
2535
|
+
] }),
|
|
2536
|
+
target.memory.filePath && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { children: [
|
|
2537
|
+
"file ",
|
|
2538
|
+
target.memory.filePath
|
|
2539
|
+
] }),
|
|
2470
2540
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { children: [
|
|
2471
2541
|
"TTL ",
|
|
2472
2542
|
daysLeft(target.memory.expiresAt)
|
|
@@ -2567,6 +2637,7 @@ function EngramDetail({ target, api, tasks, nodes, links, onClose, onNavigateMem
|
|
|
2567
2637
|
// client/src/EngramSection.tsx
|
|
2568
2638
|
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
2569
2639
|
var KIND_COLORS2 = {
|
|
2640
|
+
preference: "#b45309",
|
|
2570
2641
|
decision: "#2563eb",
|
|
2571
2642
|
error: "#dc2626",
|
|
2572
2643
|
procedure: "#7c3aed",
|
|
@@ -2576,6 +2647,7 @@ var KIND_COLORS2 = {
|
|
|
2576
2647
|
task: "#4f46e5"
|
|
2577
2648
|
};
|
|
2578
2649
|
var KIND_LABEL2 = {
|
|
2650
|
+
preference: "\u504F\u597D",
|
|
2579
2651
|
decision: "\u51B3\u5B9A",
|
|
2580
2652
|
error: "\u9519\u8BEF",
|
|
2581
2653
|
procedure: "\u6D41\u7A0B",
|
|
@@ -2787,6 +2859,14 @@ function EngramSection({ api, t }) {
|
|
|
2787
2859
|
const [closeEval, setCloseEval] = (0, import_react7.useState)("");
|
|
2788
2860
|
const [closeRefs, setCloseRefs] = (0, import_react7.useState)("");
|
|
2789
2861
|
const [closeBusy, setCloseBusy] = (0, import_react7.useState)(false);
|
|
2862
|
+
const [importDump, setImportDump] = (0, import_react7.useState)(null);
|
|
2863
|
+
const [importName, setImportName] = (0, import_react7.useState)("");
|
|
2864
|
+
const [importMode, setImportMode] = (0, import_react7.useState)("merge");
|
|
2865
|
+
const [importDryRun, setImportDryRun] = (0, import_react7.useState)(true);
|
|
2866
|
+
const [importReplaceOk, setImportReplaceOk] = (0, import_react7.useState)(false);
|
|
2867
|
+
const [importBusy, setImportBusy] = (0, import_react7.useState)(false);
|
|
2868
|
+
const [importReport, setImportReport] = (0, import_react7.useState)(null);
|
|
2869
|
+
const [dataError, setDataError] = (0, import_react7.useState)(null);
|
|
2790
2870
|
const refresh = (0, import_react7.useCallback)(async () => {
|
|
2791
2871
|
setBusy(true);
|
|
2792
2872
|
setError(null);
|
|
@@ -2843,6 +2923,24 @@ function EngramSection({ api, t }) {
|
|
|
2843
2923
|
for (const t2 of tasks) map.set(t2.id, t2.name);
|
|
2844
2924
|
return (id) => map.get(id) ?? id;
|
|
2845
2925
|
}, [nodes, tasks]);
|
|
2926
|
+
const supersedeIndex = (0, import_react7.useMemo)(() => {
|
|
2927
|
+
const replacedBy = /* @__PURE__ */ new Map();
|
|
2928
|
+
const contradictedBy = /* @__PURE__ */ new Map();
|
|
2929
|
+
for (const m of memories) {
|
|
2930
|
+
if (m.status !== "active") continue;
|
|
2931
|
+
if (m.supersedes) {
|
|
2932
|
+
const list = replacedBy.get(m.supersedes) ?? [];
|
|
2933
|
+
list.push(m);
|
|
2934
|
+
replacedBy.set(m.supersedes, list);
|
|
2935
|
+
}
|
|
2936
|
+
if (m.contradicts) {
|
|
2937
|
+
const list = contradictedBy.get(m.contradicts) ?? [];
|
|
2938
|
+
list.push(m);
|
|
2939
|
+
contradictedBy.set(m.contradicts, list);
|
|
2940
|
+
}
|
|
2941
|
+
}
|
|
2942
|
+
return { replacedBy, contradictedBy };
|
|
2943
|
+
}, [memories]);
|
|
2846
2944
|
(0, import_react7.useEffect)(() => {
|
|
2847
2945
|
if (!newTaskWs && workspaces.length > 0 && !workspace) setNewTaskWs(workspaces[0]);
|
|
2848
2946
|
}, [newTaskWs, workspaces, workspace]);
|
|
@@ -2876,6 +2974,60 @@ function EngramSection({ api, t }) {
|
|
|
2876
2974
|
setGcRunning(false);
|
|
2877
2975
|
}
|
|
2878
2976
|
};
|
|
2977
|
+
const downloadExport = async () => {
|
|
2978
|
+
setDataError(null);
|
|
2979
|
+
try {
|
|
2980
|
+
const dump = await api.exportData(workspace || void 0);
|
|
2981
|
+
const blob = new Blob([JSON.stringify(dump, null, 2)], { type: "application/json" });
|
|
2982
|
+
const url = URL.createObjectURL(blob);
|
|
2983
|
+
const a = document.createElement("a");
|
|
2984
|
+
const safe = (workspace || "all").replace(/[^a-zA-Z0-9._-]+/g, "_");
|
|
2985
|
+
a.href = url;
|
|
2986
|
+
a.download = `dsh-engram-export-${safe}-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.json`;
|
|
2987
|
+
a.click();
|
|
2988
|
+
URL.revokeObjectURL(url);
|
|
2989
|
+
} catch (e) {
|
|
2990
|
+
setDataError(e instanceof Error ? e.message : String(e));
|
|
2991
|
+
}
|
|
2992
|
+
};
|
|
2993
|
+
const pickImportFile = async (file) => {
|
|
2994
|
+
setDataError(null);
|
|
2995
|
+
setImportReport(null);
|
|
2996
|
+
setImportDump(null);
|
|
2997
|
+
setImportName("");
|
|
2998
|
+
if (!file) return;
|
|
2999
|
+
try {
|
|
3000
|
+
const text = await file.text();
|
|
3001
|
+
setImportDump(JSON.parse(text));
|
|
3002
|
+
setImportName(file.name);
|
|
3003
|
+
} catch (e) {
|
|
3004
|
+
setDataError(`\u65E0\u6CD5\u89E3\u6790 ${file.name}\uFF1A${e instanceof Error ? e.message : String(e)}`);
|
|
3005
|
+
}
|
|
3006
|
+
};
|
|
3007
|
+
const runImport = async () => {
|
|
3008
|
+
if (importDump === null) return;
|
|
3009
|
+
if (importMode === "replace" && !importReplaceOk) {
|
|
3010
|
+
setDataError("\u66FF\u6362\u6A21\u5F0F\u4F1A\u5148\u6E05\u7A7A\u76EE\u6807\u5DE5\u4F5C\u533A \u2014 \u8BF7\u5148\u52FE\u9009\u786E\u8BA4");
|
|
3011
|
+
return;
|
|
3012
|
+
}
|
|
3013
|
+
setImportBusy(true);
|
|
3014
|
+
setDataError(null);
|
|
3015
|
+
try {
|
|
3016
|
+
const report = await api.importData({
|
|
3017
|
+
payload: importDump,
|
|
3018
|
+
mode: importMode,
|
|
3019
|
+
dryRun: importDryRun,
|
|
3020
|
+
workspace: workspace || void 0,
|
|
3021
|
+
confirm: importMode === "replace" ? "restore" : void 0
|
|
3022
|
+
});
|
|
3023
|
+
setImportReport(report);
|
|
3024
|
+
if (!report.dryRun) await refresh();
|
|
3025
|
+
} catch (e) {
|
|
3026
|
+
setDataError(e instanceof Error ? e.message : String(e));
|
|
3027
|
+
} finally {
|
|
3028
|
+
setImportBusy(false);
|
|
3029
|
+
}
|
|
3030
|
+
};
|
|
2879
3031
|
const groupedRows = workspace === "" ? groupByWorkspace(memories) : [[workspace, memories]];
|
|
2880
3032
|
const taskGroups = workspace === "" ? groupByWorkspace(tasks) : [[workspace, tasks]];
|
|
2881
3033
|
const linkGroups = workspace === "" ? groupByWorkspace(links) : [[workspace, links]];
|
|
@@ -2995,6 +3147,7 @@ function EngramSection({ api, t }) {
|
|
|
2995
3147
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(StatCard, { num: String(wsCounts ? wsCounts.nodes ?? 0 : overview.totals.nodes ?? 0), label: wsCounts ? "\u8282\u70B9" : "\u8282\u70B9 (\u5168\u5C40)" }),
|
|
2996
3148
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(StatCard, { num: String(workspaces.length), label: "\u5DE5\u4F5C\u533A" }),
|
|
2997
3149
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(StatCard, { num: String(overview.captures.total), label: "\u81EA\u52A8\u6355\u83B7" }),
|
|
3150
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(StatCard, { num: String(overview.captures.preference ?? 0), label: "\u504F\u597D\u6355\u83B7" }),
|
|
2998
3151
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2999
3152
|
StatCard,
|
|
3000
3153
|
{
|
|
@@ -3064,6 +3217,90 @@ function EngramSection({ api, t }) {
|
|
|
3064
3217
|
] }, l.id))
|
|
3065
3218
|
] })
|
|
3066
3219
|
] }),
|
|
3220
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: s2.subPanel, children: [
|
|
3221
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" }, children: [
|
|
3222
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: { fontWeight: 700, fontSize: 13 }, children: "\u6570\u636E\u5907\u4EFD\uFF08export / import\uFF09" }),
|
|
3223
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { style: s2.btn, title: "\u4E0B\u8F7D\u5F53\u524D\u5DE5\u4F5C\u533A\uFF08\u6216\u5168\u90E8\uFF09\u7684 dsh-engram/export v1 \u8F6C\u50A8", onClick: () => void downloadExport(), children: "\u5BFC\u51FA JSON" }),
|
|
3224
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { style: { ...s2.btn, display: "inline-flex", alignItems: "center", gap: 6, cursor: "pointer" }, children: [
|
|
3225
|
+
"\u9009\u62E9\u5BFC\u5165\u6587\u4EF6\u2026",
|
|
3226
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3227
|
+
"input",
|
|
3228
|
+
{
|
|
3229
|
+
type: "file",
|
|
3230
|
+
accept: "application/json,.json",
|
|
3231
|
+
style: { display: "none" },
|
|
3232
|
+
onChange: (e) => {
|
|
3233
|
+
void pickImportFile(e.target.files?.[0] ?? null);
|
|
3234
|
+
e.currentTarget.value = "";
|
|
3235
|
+
}
|
|
3236
|
+
}
|
|
3237
|
+
)
|
|
3238
|
+
] }),
|
|
3239
|
+
importName && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: s2.mono, children: importName })
|
|
3240
|
+
] }),
|
|
3241
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { fontSize: 11.5, color: "var(--dsh-color-muted, #6b7280)", marginTop: 4 }, children: [
|
|
3242
|
+
"\u5BFC\u51FA\u5373\u53EF\u79FB\u690D\u5907\u4EFD\uFF08\u8BB0\u5FC6/\u4EFB\u52A1/\u5173\u7CFB/\u8282\u70B9\uFF0C\u542B\u5F52\u6863\uFF09\uFF1B",
|
|
3243
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("b", { children: "merge" }),
|
|
3244
|
+
" \u53EA\u8865\u7F3A\u5931\uFF08\u5E42\u7B49\uFF09\uFF0C",
|
|
3245
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("b", { children: "replace" }),
|
|
3246
|
+
" \u5148\u6E05\u7A7A\u76EE\u6807\u5DE5\u4F5C\u533A\u518D\u6062\u590D\u3002\u8BB0\u5FC6\u6B63\u6587\u4E0E\u6240\u6709\u5199\u5165\u8DEF\u5F84\u4E00\u6837\u8FC7\u8131\u654F\u3002"
|
|
3247
|
+
] }),
|
|
3248
|
+
importDump !== null && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { marginTop: 8, display: "flex", flexDirection: "column", gap: 6 }, children: [
|
|
3249
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { display: "flex", gap: 10, alignItems: "center", flexWrap: "wrap", fontSize: 12.5 }, children: [
|
|
3250
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { style: { display: "inline-flex", gap: 4, alignItems: "center" }, children: [
|
|
3251
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("input", { type: "checkbox", checked: importDryRun, onChange: (e) => setImportDryRun(e.target.checked) }),
|
|
3252
|
+
"\u4EC5\u9884\u89C8\uFF08dry run\uFF09"
|
|
3253
|
+
] }),
|
|
3254
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
3255
|
+
"select",
|
|
3256
|
+
{
|
|
3257
|
+
style: s2.input,
|
|
3258
|
+
value: importMode,
|
|
3259
|
+
onChange: (e) => {
|
|
3260
|
+
const mode = e.target.value;
|
|
3261
|
+
setImportMode(mode);
|
|
3262
|
+
if (mode !== "replace") setImportReplaceOk(false);
|
|
3263
|
+
},
|
|
3264
|
+
children: [
|
|
3265
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: "merge", children: "\u5408\u5E76\uFF08merge\uFF0C\u5E42\u7B49\uFF09" }),
|
|
3266
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: "replace", children: "\u66FF\u6362\uFF08replace\uFF0C\u5148\u6E05\u7A7A\u5DE5\u4F5C\u533A\uFF09" })
|
|
3267
|
+
]
|
|
3268
|
+
}
|
|
3269
|
+
),
|
|
3270
|
+
importMode === "replace" && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { style: { display: "inline-flex", gap: 4, alignItems: "center", color: "#b45309" }, children: [
|
|
3271
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("input", { type: "checkbox", checked: importReplaceOk, onChange: (e) => setImportReplaceOk(e.target.checked) }),
|
|
3272
|
+
"\u6211\u786E\u8BA4\u66FF\u6362",
|
|
3273
|
+
workspace ? `\uFF08${workspace}\uFF09` : "\uFF08\u9700\u5148\u9009\u5DE5\u4F5C\u533A\uFF09"
|
|
3274
|
+
] }),
|
|
3275
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3276
|
+
"button",
|
|
3277
|
+
{
|
|
3278
|
+
style: s2.btnPrimary,
|
|
3279
|
+
disabled: importBusy || importMode === "replace" && (!importReplaceOk || !workspace),
|
|
3280
|
+
onClick: () => void runImport(),
|
|
3281
|
+
children: importBusy ? "\u2026" : importDryRun ? "\u9884\u89C8\u5BFC\u5165" : "\u6267\u884C\u5BFC\u5165"
|
|
3282
|
+
}
|
|
3283
|
+
)
|
|
3284
|
+
] }),
|
|
3285
|
+
importReport && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { fontSize: 12.5 }, children: [
|
|
3286
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
|
|
3287
|
+
importReport.dryRun ? "dry-run \u9884\u89C8\uFF1A" : "\u5DF2\u5BFC\u5165\uFF1A",
|
|
3288
|
+
" ",
|
|
3289
|
+
Object.entries(importReport.written).map(([k, v]) => `${k} ${v}`).join(" \xB7 ") || "\u2014",
|
|
3290
|
+
importReport.skipped.length > 0 ? ` \xB7 \u8DF3\u8FC7 ${importReport.skipped.length}` : ""
|
|
3291
|
+
] }),
|
|
3292
|
+
importReport.skipped.slice(0, 5).map((x, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: s2.mono, children: [
|
|
3293
|
+
"- ",
|
|
3294
|
+
x.table,
|
|
3295
|
+
" ",
|
|
3296
|
+
x.id ? x.id.slice(0, 8) : "?",
|
|
3297
|
+
": ",
|
|
3298
|
+
x.reason
|
|
3299
|
+
] }, `${x.table}-${x.id ?? i}`))
|
|
3300
|
+
] }),
|
|
3301
|
+
dataError && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { fontSize: 12, color: "#dc2626", fontWeight: 600 }, children: dataError })
|
|
3302
|
+
] })
|
|
3303
|
+
] }),
|
|
3067
3304
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: s2.row, children: [
|
|
3068
3305
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("select", { style: s2.input, value: kind, onChange: (e) => setKind(e.target.value), children: [
|
|
3069
3306
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: "", children: "\u5168\u90E8\u7C7B\u578B" }),
|
|
@@ -3137,7 +3374,8 @@ function EngramSection({ api, t }) {
|
|
|
3137
3374
|
] }) }, `h-${r.ws}`) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("tr", { children: [
|
|
3138
3375
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("td", { style: { ...s2.td, whiteSpace: "nowrap" }, children: [
|
|
3139
3376
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: { ...s2.badge, background: KIND_COLORS2[r.m.kind] ?? "#6b7280" }, children: KIND_LABEL2[r.m.kind] ?? r.m.kind }),
|
|
3140
|
-
r.m.status === "archived" && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: { ...s2.tag, color: "#b45309", background: "#fef3c7" }, children: "archived" })
|
|
3377
|
+
r.m.status === "archived" && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: { ...s2.tag, color: "#b45309", background: "#fef3c7" }, children: "archived" }),
|
|
3378
|
+
supersedeIndex.replacedBy.has(r.m.id) && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: { ...s2.tag, color: "#b45309", background: "#fef3c7" }, title: `\u5DF2\u88AB ${(supersedeIndex.replacedBy.get(r.m.id) ?? []).map((x) => `#${x.id.slice(0, 8)}`).join(", ")} \u66FF\u4EE3\uFF08\u65E7\u884C\u4FDD\u7559\u3001\u53EC\u56DE\u964D\u7EA7\uFF09`, children: "\u5DF2\u66FF\u6362" })
|
|
3141
3379
|
] }),
|
|
3142
3380
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("td", { style: { ...s2.td, minWidth: 0 }, children: [
|
|
3143
3381
|
/* @__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 }),
|
|
@@ -3154,7 +3392,11 @@ function EngramSection({ api, t }) {
|
|
|
3154
3392
|
" \xB7 ",
|
|
3155
3393
|
daysLeft2(r.m.expiresAt)
|
|
3156
3394
|
] }),
|
|
3157
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { style: s2.linkBtn, onClick: () => toggleExpand(r.m.id), children: expandedRows.has(r.m.id) ? "\u6536\u8D77" : "\u5C55\u5F00\u5168\u6587" })
|
|
3395
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { style: s2.linkBtn, onClick: () => toggleExpand(r.m.id), children: expandedRows.has(r.m.id) ? "\u6536\u8D77" : "\u5C55\u5F00\u5168\u6587" }),
|
|
3396
|
+
r.m.supersedes && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("button", { style: s2.linkBtn, title: `\u6253\u5F00\u88AB\u66FF\u4EE3\u7684\u65E7\u8BB0\u5FC6 ${r.m.supersedes}`, onClick: () => openMemory(r.m.supersedes), children: [
|
|
3397
|
+
"\u66FF\u4EE3 #",
|
|
3398
|
+
r.m.supersedes.slice(0, 8)
|
|
3399
|
+
] })
|
|
3158
3400
|
] }),
|
|
3159
3401
|
r.m.tags.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { marginTop: 3, display: "flex", flexWrap: "wrap", gap: 4 }, children: r.m.tags.map((tag) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: s2.tag, children: tag }, tag)) })
|
|
3160
3402
|
] }),
|
|
@@ -3354,6 +3596,7 @@ ${r.m.text.slice(0, 60)}`)) void act(() => api.remove(r.m.id, r.m.workspace));
|
|
|
3354
3596
|
tasks,
|
|
3355
3597
|
nodes,
|
|
3356
3598
|
links,
|
|
3599
|
+
memories,
|
|
3357
3600
|
onClose: () => setDetail(null),
|
|
3358
3601
|
onNavigateMemory: openMemory,
|
|
3359
3602
|
onChanged: () => void refresh()
|
|
@@ -3369,6 +3612,10 @@ var import_react8 = require("react");
|
|
|
3369
3612
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
3370
3613
|
var COMMON_FIELDS = [
|
|
3371
3614
|
{ key: "autoCapture", label: "\u81EA\u52A8\u6355\u83B7", hint: "\u96F6 LLM \u4ECE\u5DE5\u5177\u7ED3\u679C\u63D0\u53D6\u8BB0\u5FC6\uFF08git/\u5173\u952E\u6587\u4EF6/\u9519\u8BEF\uFF09", kind: "bool" },
|
|
3615
|
+
{ key: "autoCapturePreferences", label: "\u504F\u597D\u6355\u83B7", hint: "\u7528\u6237\u7EA0\u6B63/\u504F\u597D\u5B66\u4E60\uFF1A\u68C0\u6D4B\u6D88\u606F\u91CC\u7684\u300C\u4E0D\u8981\u7528X/\u6539\u7528Y/prefer/never use\u300D\u53E5\u5F0F\uFF0C\u81EA\u52A8\u6C89\u6DC0\u4E3A preference \u8BB0\u5FC6\u5E76\u7F6E\u9876 [ENGRAM]\uFF08correx/PAHF \u5F0F\uFF0C\u96F6 LLM\uFF09", kind: "bool" },
|
|
3616
|
+
{ key: "boundaryPrune", label: "\u8FB9\u754C\u526A\u679D", hint: "ResultPack L1\uFF1Atodo \u5B8C\u6210/goal \u7EC8\u6001\u8FB9\u754C\u4E3B\u52A8\u526A\u6389\u8D85\u9608\u503C\u65E7\u5DE5\u5177\u7ED3\u679C\uFF08DSH \u786E\u5B9A\u6027 surface-replace\u3001\u507F\u8FD8\u95E8\u3001\u96F6 LLM\uFF1B\u9ED8\u8BA4\u5173\uFF09", kind: "bool" },
|
|
3617
|
+
{ key: "boundaryPruneThresholdChars", label: "\u526A\u679D\u9608\u503C(\u5B57\u7B26)", hint: "\u8986\u76D6\u5BBF\u4E3B 8192\uFF1A\u8BBE 2048 \u53EF\u66F4\u65E9\u5F00\u526A\uFF0C\u5E76\u914D L2 \u5F52\u6863\u627E\u56DE\u88AB\u526A\u4E2D\u6BB5\uFF1B\u7559\u7A7A=\u5BBF\u4E3B\u9ED8\u8BA4\u3002\u5EFA\u8BAE\u540C\u65F6\u628A\u4FDD\u7559\u8FB9\u7F18\u8C03\u5C0F\uFF08\u5982 1024/256\uFF09\u4EE5\u83B7\u5F97\u6700\u5927\u8282\u7701", kind: "num", min: 256, max: 16384, step: 256 },
|
|
3618
|
+
{ key: "resultPack", label: "\u7ED3\u679C\u5F52\u6863\u53EC\u56DE", hint: "ResultPack L2\uFF1A\u526A\u6389\u7684\u4E2D\u6BB5\u8131\u654F\u5F52\u6863\uFF0C\u8282\u70B9\u5C3E\u90E8\u9644 handle\uFF0C\u6A21\u578B\u53EF\u7528 result_recall(callId) \u6309\u9875\u53EC\u56DE\uFF08\u5B8C\u6574 ObservationPack\uFF1B\u9700\u4E0E\u8FB9\u754C\u526A\u679D\u4E00\u8D77\u5F00\uFF09", kind: "bool" },
|
|
3372
3619
|
{ key: "autoRecallOnStart", label: "\u4F1A\u8BDD\u542F\u52A8\u81EA\u52A8\u53EC\u56DE", hint: "\u65B0\u4F1A\u8BDD\u6309\u9996\u6761\u6D88\u606F\u81EA\u52A8\u628A\u6700\u76F8\u5173\u8BB0\u5FC6\u6CE8\u5165 [RECALL] \u5757\uFF08\u65E0\u9700\u6A21\u578B\u60F3\u8D77\u8C03 engram_recall\uFF09\uFF1B\u5173\u6389=\u56DE\u5230\u7EAF\u62C9\u53D6", kind: "bool" },
|
|
3373
3620
|
{ key: "maxRecallPerSession", label: "\u6BCF\u4F1A\u8BDD\u53BB\u91CD\u4E0A\u9650", hint: "recall \u6BCF\u4F1A\u8BDD\u6700\u591A\u4FDD\u7559 N \u6761\uFF08\u8DE8\u4F1A\u8BDD\u53BB\u91CD\uFF09", kind: "num", min: 1, max: 10 },
|
|
3374
3621
|
{ key: "expireDays", label: "TTL\uFF08\u5929\uFF09", hint: "0 = \u4E0D\u8FC7\u671F", kind: "num", min: 0, max: 3650 },
|
|
@@ -3381,6 +3628,9 @@ var ADVANCED_GROUPS = [
|
|
|
3381
3628
|
description: "\u5176\u4F59\u6355\u83B7/\u641C\u7D22\u65CB\u94AE",
|
|
3382
3629
|
fields: [
|
|
3383
3630
|
{ key: "autoCapturePerSession", label: "\u6BCF\u4F1A\u8BDD\u6355\u83B7\u4E0A\u9650", hint: "\u5355\u4F1A\u8BDD\u81EA\u52A8\u6355\u83B7\u6761\u6570\u4E0A\u9650", kind: "num", min: 0, max: 1e3 },
|
|
3631
|
+
{ key: "boundaryPruneHeadChars", label: "\u526A\u679D\u4FDD\u7559\u5934\u90E8(\u5B57\u7B26)", hint: "\u9608\u503C\u8986\u76D6\u751F\u6548\u65F6\u7ED3\u679C\u5F00\u5934\u4FDD\u7559\u957F\u5EA6\uFF08\u9ED8\u8BA4 4096\uFF09\uFF1B\u8C03\u5C0F\uFF08\u5982 1024\uFF09\u914D\u5408 T2048 \u66F4\u7701", kind: "num", min: 0, max: 16384, step: 128 },
|
|
3632
|
+
{ key: "boundaryPruneTailChars", label: "\u526A\u679D\u4FDD\u7559\u5C3E\u90E8(\u5B57\u7B26)", hint: "\u9608\u503C\u8986\u76D6\u751F\u6548\u65F6\u7ED3\u679C\u7ED3\u5C3E\u4FDD\u7559\u957F\u5EA6\uFF08\u9ED8\u8BA4 1024\uFF09\uFF1B\u8C03\u5C0F\uFF08\u5982 256\uFF09\u914D\u5408 T2048 \u66F4\u7701", kind: "num", min: 0, max: 16384, step: 128 },
|
|
3633
|
+
{ key: "boundaryPruneIdleCadence", label: "\u7A7A\u95F2\u6B65\u6570\u8FB9\u754C", hint: "\u6700\u591A\u6BCF N \u8BF7\u6C42\u89E6\u53D1\u4E00\u6B21\u526A\u679D\uFF0C\u8986\u76D6\u65E0 todo/goal \u7684\u4F1A\u8BDD\uFF08\u7EA6 62% \u4F1A\u8BDD\uFF09\uFF1B\u4ECD\u53D7\u507F\u8FD8\u95E8\u7EA6\u675F\uFF1B\u7559\u7A7A=\u4EC5\u4E8B\u4EF6\u8FB9\u754C", kind: "num", min: 10, max: 1e3, step: 10 },
|
|
3384
3634
|
{ key: "autoRecallLimit", label: "\u81EA\u52A8\u53EC\u56DE\u6761\u6570", hint: "[RECALL] \u5757\u6700\u591A\u6CE8\u5165\u7684\u8BB0\u5FC6\u6761\u6570\uFF08\u5C11\u800C\u51C6\uFF09", kind: "num", min: 1, max: 8 },
|
|
3385
3635
|
{ key: "sessionSearch", label: "\u4F1A\u8BDD\u5386\u53F2\u641C\u7D22", hint: "engram_recall \u652F\u6301\u8DE8\u4F1A\u8BDD FTS \u515C\u5E95", kind: "bool" },
|
|
3386
3636
|
{ key: "recallScope", label: "\u53EC\u56DE\u8303\u56F4", hint: "workspace=\u4E25\u683C\u5F53\u524D\u5DE5\u4F5C\u533A\u9694\u79BB\uFF08\u9ED8\u8BA4\uFF0Ctoken \u7EAA\u5F8B\uFF09\uFF1Bglobal=\u672C\u5DE5\u4F5C\u533A\u547D\u4E2D\u5F31/\u7A7A\u65F6\u4ECE\u5176\u5B83\u5DE5\u4F5C\u533A\u6C60\u53EC\u56DE\u5E76\u6807 [W:<workspace>] \u6765\u6E90\uFF08\u4F1A\u6253\u7834\u9694\u79BB\uFF09", kind: "select", options: ["workspace", "global"] }
|