dsh-engram 0.7.1 → 0.7.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/README.md +42 -5
- package/README.zh.md +30 -5
- package/docs/CONTEXT-GC-GUIDE.zh.md +3 -1
- package/docs/RELEASE-NOTES-v0.7.2.md +48 -0
- package/docs/RELEASE-NOTES-v0.7.3.md +30 -0
- package/docs/RELEASE-NOTES-v0.7.4.md +71 -0
- package/lib/boundary-prune.js +13 -0
- package/lib/capture.js +2 -1
- package/lib/client.js +1760 -1758
- package/lib/episode.js +10 -5
- package/lib/index-block.js +10 -4
- package/lib/index.js +42 -11
- package/lib/preference.js +2 -1
- package/lib/session-events.js +126 -0
- package/lib/todo-sink.js +3 -2
- package/lib/tools.js +2 -1
- package/package.json +1 -1
package/lib/episode.js
CHANGED
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
* later succeeded), the written commit subjects, and what is still open.
|
|
7
7
|
*
|
|
8
8
|
* Design constraints:
|
|
9
|
-
* - zero LLM, pure function over
|
|
9
|
+
* - zero LLM, pure function over the session event log (read through
|
|
10
|
+
* `readSessionEvents`, so it survives restarts and DSH ≥0.2.x, where the
|
|
11
|
+
* old `session.events` getter no longer exists);
|
|
10
12
|
* - never enters the [ENGRAM] index (long background context, not a symbol);
|
|
11
13
|
* - bounded by construction (≤4 files / ≤4 commands / ≤2 decisions / ≤3 open);
|
|
12
14
|
* - stored only when there is substance (a bare `ls` session produces none);
|
|
@@ -17,6 +19,7 @@
|
|
|
17
19
|
|
|
18
20
|
import { cmdTag } from "./capture.js";
|
|
19
21
|
import { pendingTodosFromSession } from "./todo-sink.js";
|
|
22
|
+
import { readSessionEvents } from "./session-events.js";
|
|
20
23
|
|
|
21
24
|
const EDIT_TOOLS = new Set(["write", "edit", "str_replace_editor"]);
|
|
22
25
|
const RUN_TOOLS = new Set(["bash", "shell", "exec"]);
|
|
@@ -59,13 +62,15 @@ function commitSubject(command) {
|
|
|
59
62
|
/**
|
|
60
63
|
* Build the episode record from a disposed session.
|
|
61
64
|
*
|
|
62
|
-
* @param {object} session session
|
|
65
|
+
* @param {object} session host session (`.header.cwd` + a readable event log)
|
|
63
66
|
* @param {object} [opts]
|
|
67
|
+
* @param {number} [opts.now] clock injection for the header stamp
|
|
68
|
+
* @param {object} [opts.sessionQuery] `ctx.sessionQuery` — how ≥0.2.x logs are read
|
|
64
69
|
* @returns {{text: string, tags: string[], signal: number, summary: object}|null}
|
|
65
70
|
*/
|
|
66
|
-
export function buildEpisode(session, { now = Date.now() } = {}) {
|
|
71
|
+
export function buildEpisode(session, { now = Date.now(), sessionQuery = void 0 } = {}) {
|
|
67
72
|
if (session === null || typeof session !== "object") return null;
|
|
68
|
-
const events =
|
|
73
|
+
const events = readSessionEvents(session, sessionQuery);
|
|
69
74
|
if (events.length === 0) return null;
|
|
70
75
|
const cwd = session?.header?.cwd;
|
|
71
76
|
|
|
@@ -119,7 +124,7 @@ export function buildEpisode(session, { now = Date.now() } = {}) {
|
|
|
119
124
|
}
|
|
120
125
|
}
|
|
121
126
|
|
|
122
|
-
const open = pendingTodosFromSession(session).slice(0, 3);
|
|
127
|
+
const open = pendingTodosFromSession(session, sessionQuery).slice(0, 3);
|
|
123
128
|
const hasSubstance = files.size > 0 || decisions.length > 0 || failedRuns > 0 || open.length > 0;
|
|
124
129
|
if (!hasSubstance) return null;
|
|
125
130
|
|
package/lib/index-block.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { label, shortId, fmtDate, escapeLt, truncate, byRecency } from "./util.js";
|
|
13
13
|
import { buildEntityPage, renderEntityCard } from "./entity-page.js";
|
|
14
14
|
import { memoryIsStale } from "./anchor.js";
|
|
15
|
+
import { readSessionEvents } from "./session-events.js";
|
|
15
16
|
|
|
16
17
|
/** Deterministic index line prefix per memory kind. */
|
|
17
18
|
export function prefixFor(kind) {
|
|
@@ -190,13 +191,18 @@ function enforceCharCap(lines, maxChars) {
|
|
|
190
191
|
}
|
|
191
192
|
|
|
192
193
|
/** Text of the session's first user message (the opening instruction of the
|
|
193
|
-
* conversation). Walk
|
|
194
|
+
* conversation). Walk the session log for the first `user/message` event and
|
|
194
195
|
* join its `data.content` text blocks; fall back to the first text-bearing
|
|
195
196
|
* event ("goal"/reminder start) guardedly. Returns "" when there is none.
|
|
196
|
-
* Pure, deterministic — the auto-recall (B) keys its query off this.
|
|
197
|
-
|
|
197
|
+
* Pure, deterministic — the auto-recall (B) keys its query off this.
|
|
198
|
+
*
|
|
199
|
+
* The log is read through `readSessionEvents` because DSH 0.2.x dropped the
|
|
200
|
+
* `session.events` getter: reading it directly returned `[]` on every real
|
|
201
|
+
* session, which silently killed the whole [RECALL] push leg (and, via
|
|
202
|
+
* `pendingRecall`, kept the index block from ever being cached). */
|
|
203
|
+
export function firstUserMessageText(session, sessionQuery = void 0) {
|
|
198
204
|
if (session === null || typeof session !== "object") return "";
|
|
199
|
-
const events =
|
|
205
|
+
const events = readSessionEvents(session, sessionQuery);
|
|
200
206
|
if (events.length === 0) return "";
|
|
201
207
|
const textOf = (data) => {
|
|
202
208
|
if (data === null || typeof data !== "object") return "";
|
package/lib/index.js
CHANGED
|
@@ -32,6 +32,7 @@ import { createJit } from "./jit.js";
|
|
|
32
32
|
import { buildEpisode } from "./episode.js";
|
|
33
33
|
import { collectStaleAnchors } from "./anchor.js";
|
|
34
34
|
import { renderIndex, renderStartRecall, renderResumeBrief, firstUserMessageText, renderEsr, esrMethodology, buildEsrSnapshot, buildFrozenEsrBlock } from "./index-block.js";
|
|
35
|
+
import { sessionEventsSource, sessionSeq } from "./session-events.js";
|
|
35
36
|
import { makeTriggerRecorder } from "./trigger.js";
|
|
36
37
|
import { pendingTodosFromSession, sinkPendingTodos } from "./todo-sink.js";
|
|
37
38
|
import { installEngramSettings, makeEngineConfigSchema, SETTINGS_KEYS } from "./settings.js";
|
|
@@ -522,6 +523,8 @@ export function apply(ctx, config) {
|
|
|
522
523
|
// [ESR] block below follows the same frozen-snapshot rule but additionally
|
|
523
524
|
// accumulates this-session actionables (see esrBlockFor).
|
|
524
525
|
const sessionCache = new WeakMap();
|
|
526
|
+
/** One-time-per-process notice: the host's session log is unreadable. */
|
|
527
|
+
let warnedUnreadableLog = false;
|
|
525
528
|
const blockFor = (agent) => {
|
|
526
529
|
if (agent === void 0) return "";
|
|
527
530
|
const session = agent.session;
|
|
@@ -560,9 +563,20 @@ export function apply(ctx, config) {
|
|
|
560
563
|
// next assembly retry, so a new conversation never permanently misses its
|
|
561
564
|
// recalled memory.
|
|
562
565
|
if (resolved.autoRecallOnStart !== false) {
|
|
563
|
-
const query = firstUserMessageText(session);
|
|
566
|
+
const query = firstUserMessageText(session, sessionQuery);
|
|
564
567
|
if (query.length === 0) {
|
|
565
|
-
|
|
568
|
+
// Retry on the next assembly ONLY when the log is readable and the
|
|
569
|
+
// opening message simply has not been appended yet. An unreadable
|
|
570
|
+
// log (host API drift — see lib/session-events.js) will never gain
|
|
571
|
+
// one, and retrying forever would also keep the [ENGRAM] block
|
|
572
|
+
// uncached, churning the prefix every turn.
|
|
573
|
+
pendingRecall = sessionEventsSource(session, sessionQuery) !== "none";
|
|
574
|
+
if (!pendingRecall && !warnedUnreadableLog) {
|
|
575
|
+
warnedUnreadableLog = true;
|
|
576
|
+
log?.warn?.(
|
|
577
|
+
"engram: this host exposes no readable session event log (no session.events, no ctx.sessionQuery, no snapshotEvents) — [RECALL] / episode / todo-sink stay dormant. Reporting once per session.",
|
|
578
|
+
);
|
|
579
|
+
}
|
|
566
580
|
} else {
|
|
567
581
|
const recall = renderStartRecall(opened, workspace, resolved, { query, allowRecencyFallback: false });
|
|
568
582
|
if (recall.length > 0) {
|
|
@@ -901,7 +915,7 @@ export function apply(ctx, config) {
|
|
|
901
915
|
const sessionId = session?.id;
|
|
902
916
|
if (typeof cwd !== "string" || cwd.length === 0) return;
|
|
903
917
|
if (typeof sessionId !== "string" || sessionId.length === 0) return;
|
|
904
|
-
const todos = pendingTodosFromSession(session);
|
|
918
|
+
const todos = pendingTodosFromSession(session, sessionQuery);
|
|
905
919
|
if (todos.length === 0) return;
|
|
906
920
|
const workspace = wk(cwd);
|
|
907
921
|
service
|
|
@@ -932,7 +946,7 @@ export function apply(ctx, config) {
|
|
|
932
946
|
const sessionId = session?.id;
|
|
933
947
|
if (typeof cwd !== "string" || cwd.length === 0) return;
|
|
934
948
|
if (typeof sessionId !== "string" || sessionId.length === 0) return;
|
|
935
|
-
const episode = buildEpisode(session);
|
|
949
|
+
const episode = buildEpisode(session, { sessionQuery });
|
|
936
950
|
if (episode === null) return;
|
|
937
951
|
const workspace = wk(cwd);
|
|
938
952
|
const maxEpisodes = Number.isFinite(resolved.episodeMaxPerWorkspace)
|
|
@@ -950,7 +964,7 @@ export function apply(ctx, config) {
|
|
|
950
964
|
signal: episode.signal,
|
|
951
965
|
filePath: null,
|
|
952
966
|
sessionId,
|
|
953
|
-
seq:
|
|
967
|
+
seq: sessionSeq(session, sessionQuery),
|
|
954
968
|
},
|
|
955
969
|
resolved,
|
|
956
970
|
);
|
|
@@ -1019,7 +1033,7 @@ export function apply(ctx, config) {
|
|
|
1019
1033
|
// over-threshold tool results at todo/goal subtask boundaries through
|
|
1020
1034
|
// DSH's own deterministic toolResultPruner, gated by a repayment clock.
|
|
1021
1035
|
const l2Dir = resultPackDir();
|
|
1022
|
-
const
|
|
1036
|
+
const boundaryHooks = {
|
|
1023
1037
|
// ResultPack L2 hook: archive the pruned middles (redacted) + append
|
|
1024
1038
|
// a handle tail on each pruned node. Fail-contained in result-pack.js.
|
|
1025
1039
|
// Also records durable activation telemetry (SoL-Pi M15/C13) —
|
|
@@ -1051,11 +1065,28 @@ export function apply(ctx, config) {
|
|
|
1051
1065
|
log,
|
|
1052
1066
|
});
|
|
1053
1067
|
},
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
//
|
|
1057
|
-
// (
|
|
1058
|
-
|
|
1068
|
+
};
|
|
1069
|
+
// Arm L1 through `ctx.inject`, never a bare `ctx.get`: on a full web
|
|
1070
|
+
// profile the host pruner's fiber activates a tick or two after this
|
|
1071
|
+
// plugin (measured ~100 ms with dsh-context + dshmarket mounted), and a
|
|
1072
|
+
// strict lookup at install time is then empty — the old direct install
|
|
1073
|
+
// gave up and L1 stayed dormant for the whole process, silently killing
|
|
1074
|
+
// the only ledger credit path. `ctx.inject` runs the callback as soon as
|
|
1075
|
+
// the service is active (and re-runs it after a reload).
|
|
1076
|
+
if (resolved.boundaryPrune !== false) {
|
|
1077
|
+
ctx.inject(["toolResultPruner"], (prunerCtx) => {
|
|
1078
|
+
const dispose = installBoundaryPrune(prunerCtx, resolved, log, boundaryHooks, {
|
|
1079
|
+
allowFire: boundaryGate,
|
|
1080
|
+
});
|
|
1081
|
+
// L1 provably can (or cannot) fire from here on: record it for the
|
|
1082
|
+
// durable snapshot, then rewrite it — the first flush may have landed
|
|
1083
|
+
// before the pruner activated, and this status line is what users
|
|
1084
|
+
// read when "节省" stays at 0.
|
|
1085
|
+
boundaryPruneState = boundaryPruneAvailability(prunerCtx, resolved);
|
|
1086
|
+
flushStatus();
|
|
1087
|
+
return dispose;
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1059
1090
|
|
|
1060
1091
|
// ResultPack L2 tool (default ON with L1 since v0.6): `result_recall(callId,
|
|
1061
1092
|
// offset?)` pages back an archived middle. Independent of the memory
|
package/lib/preference.js
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
30
|
import { workspaceKey as wk } from "./util.js";
|
|
31
|
+
import { sessionSeq } from "./session-events.js";
|
|
31
32
|
|
|
32
33
|
/** Direction of the preference statement, used in the captured text. */
|
|
33
34
|
export const PREFERENCE_DIRECTION = {
|
|
@@ -244,7 +245,7 @@ export function installPreferenceCapture(ctx, storeAdapter, config, log, stats)
|
|
|
244
245
|
signal: PREFERENCE_SIGNAL,
|
|
245
246
|
filePath: null,
|
|
246
247
|
sessionId,
|
|
247
|
-
seq:
|
|
248
|
+
seq: sessionSeq(session),
|
|
248
249
|
},
|
|
249
250
|
config,
|
|
250
251
|
)
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session event-log access across DSH generations.
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS EXISTS (2026-10-20, the second "silent zero"):
|
|
5
|
+
* DSH ≤0.1.x exposed the conversation log as `session.events`, a plain frozen
|
|
6
|
+
* array. DSH 0.2.x removed that getter — `Session.prototype` now carries only
|
|
7
|
+
* `eventAt` / `snapshotEvents` / `ownEvents` / `deriveEventMessage`, and the
|
|
8
|
+
* synchronous full-log read is deprecated host-internally in favour of
|
|
9
|
+
* `ctx.sessionQuery.live(session)` leases. Every
|
|
10
|
+
* `Array.isArray(session.events)` read in this plugin therefore yielded `[]`
|
|
11
|
+
* on the running host, silently disabling three whole features while their
|
|
12
|
+
* unit tests (which mock `{ events: [...] }`) stayed green:
|
|
13
|
+
*
|
|
14
|
+
* - `[RECALL]` session-start auto-recall never materialized (no first user
|
|
15
|
+
* message → no query), and worse, `index.js` refused to cache the
|
|
16
|
+
* `[ENGRAM]` block ("pendingRecall") on every assembly, so the index was
|
|
17
|
+
* re-rendered per turn and never metered into the ledger;
|
|
18
|
+
* - session episodes were never written → `[RESUME]` briefs never fired;
|
|
19
|
+
* - the session-end todo sink never found a pending todo → no ESR drafts.
|
|
20
|
+
*
|
|
21
|
+
* The lesson is the same one as the session-log rename (memory #8): a
|
|
22
|
+
* host-API read must feature-detect and REPORT, never silently degrade to an
|
|
23
|
+
* empty value. `sessionEventsSource()` names the path taken so the status
|
|
24
|
+
* snapshot can surface `none` instead of pretending the log was empty.
|
|
25
|
+
*
|
|
26
|
+
* Resolution order (first working wins):
|
|
27
|
+
* 1. `session.events` — legacy hosts (≤0.1.x), and every unit test;
|
|
28
|
+
* 2. `sessionQuery.live(session)`— sanctioned ≥0.2.x lease (lazy snapshot);
|
|
29
|
+
* 3. `session.snapshotEvents()` — context-free fallback (deprecated host-side,
|
|
30
|
+
* still the only sync full-log read a plugin has);
|
|
31
|
+
* 4. `[]` — nothing readable; callers degrade and
|
|
32
|
+
* `sessionEventsSource` reports "none".
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/** Shared empty array: callers only read, so one frozen instance is enough. */
|
|
36
|
+
const NO_EVENTS = Object.freeze([]);
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Which read path a session exposes, without materializing the log.
|
|
40
|
+
* @param {object} session
|
|
41
|
+
* @param {object} [sessionQuery] the `ctx.sessionQuery` service, if mounted
|
|
42
|
+
* @returns {"legacy"|"sessionQuery"|"snapshotEvents"|"none"}
|
|
43
|
+
*/
|
|
44
|
+
export function sessionEventsSource(session, sessionQuery = void 0) {
|
|
45
|
+
if (session === null || typeof session !== "object") return "none";
|
|
46
|
+
if (Array.isArray(session.events)) return "legacy";
|
|
47
|
+
if (hasLive(sessionQuery)) return "sessionQuery";
|
|
48
|
+
if (typeof session.snapshotEvents === "function") return "snapshotEvents";
|
|
49
|
+
return "none";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function hasLive(sessionQuery) {
|
|
53
|
+
return (
|
|
54
|
+
sessionQuery !== null &&
|
|
55
|
+
typeof sessionQuery === "object" &&
|
|
56
|
+
typeof sessionQuery.live === "function"
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Read a session's event log across DSH generations (see module header).
|
|
62
|
+
*
|
|
63
|
+
* Returns a frozen-or-plain array of events; never throws. A session that
|
|
64
|
+
* genuinely has no events and a session whose log is unreadable both return
|
|
65
|
+
* `[]` — use {@link sessionEventsSource} when the difference matters.
|
|
66
|
+
*
|
|
67
|
+
* @param {object} session host session (`agent.session`, or the arg of
|
|
68
|
+
* `session/disposed`)
|
|
69
|
+
* @param {object} [sessionQuery] the `ctx.sessionQuery` service, if mounted
|
|
70
|
+
* @returns {ReadonlyArray<object>}
|
|
71
|
+
*/
|
|
72
|
+
export function readSessionEvents(session, sessionQuery = void 0) {
|
|
73
|
+
if (session === null || typeof session !== "object") return NO_EVENTS;
|
|
74
|
+
|
|
75
|
+
// 1. Legacy: DSH ≤0.1.x (and every unit-test double).
|
|
76
|
+
if (Array.isArray(session.events)) return session.events;
|
|
77
|
+
|
|
78
|
+
// 2. Sanctioned ≥0.2.x: a live lease exposes a lazily materialized snapshot
|
|
79
|
+
// up to the session's current seq. Dispose it immediately — we only need
|
|
80
|
+
// the array, not the retention or the projection payload.
|
|
81
|
+
if (hasLive(sessionQuery)) {
|
|
82
|
+
let lease;
|
|
83
|
+
try {
|
|
84
|
+
lease = sessionQuery.live(session);
|
|
85
|
+
const events = lease?.events;
|
|
86
|
+
if (Array.isArray(events)) return events;
|
|
87
|
+
} catch {
|
|
88
|
+
// fall through to snapshotEvents
|
|
89
|
+
} finally {
|
|
90
|
+
try {
|
|
91
|
+
lease?.[Symbol.dispose]?.();
|
|
92
|
+
} catch {
|
|
93
|
+
// best-effort lease release
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 3. Context-free fallback.
|
|
99
|
+
if (typeof session.snapshotEvents === "function") {
|
|
100
|
+
try {
|
|
101
|
+
const end = typeof session.seq === "number" ? session.seq : void 0;
|
|
102
|
+
const events = end === void 0 ? session.snapshotEvents() : session.snapshotEvents(0, end);
|
|
103
|
+
if (Array.isArray(events)) return events;
|
|
104
|
+
} catch {
|
|
105
|
+
// unreadable — degrade
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 4. Nothing.
|
|
110
|
+
return NO_EVENTS;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The session's current event count, for provenance `seq` stamps.
|
|
115
|
+
*
|
|
116
|
+
* `seq` is the next free seq on ≥0.2.x (`session.seq`), so it equals the log
|
|
117
|
+
* length; on legacy hosts the array length is the same number. Kept here so
|
|
118
|
+
* the four write sites (capture / tools / preference / episode) agree.
|
|
119
|
+
*/
|
|
120
|
+
export function sessionSeq(session, sessionQuery = void 0) {
|
|
121
|
+
if (session === null || typeof session !== "object") return 0;
|
|
122
|
+
const seq = session.seq;
|
|
123
|
+
if (typeof seq === "number" && Number.isFinite(seq) && seq >= 0) return Math.trunc(seq);
|
|
124
|
+
const events = readSessionEvents(session, sessionQuery);
|
|
125
|
+
return events.length;
|
|
126
|
+
}
|
package/lib/todo-sink.js
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
import { pendingTodos } from "./trigger.js";
|
|
26
26
|
import { shortId, uuid } from "./util.js";
|
|
27
|
+
import { readSessionEvents } from "./session-events.js";
|
|
27
28
|
|
|
28
29
|
/** Description stamped on every auto-sunk draft (mirrors the GUI button's note). */
|
|
29
30
|
export const SINK_DESCRIPTION = "源自会话计划(会话结束自动沉淀)";
|
|
@@ -35,9 +36,9 @@ export const SINK_DESCRIPTION = "源自会话计划(会话结束自动沉淀
|
|
|
35
36
|
* Deterministic — this is a pure function of the session log, so it survives
|
|
36
37
|
* process restarts (no reliance on the process-local recorder snapshot).
|
|
37
38
|
*/
|
|
38
|
-
export function pendingTodosFromSession(session) {
|
|
39
|
+
export function pendingTodosFromSession(session, sessionQuery = void 0) {
|
|
39
40
|
if (session === null || typeof session !== "object") return [];
|
|
40
|
-
const events =
|
|
41
|
+
const events = readSessionEvents(session, sessionQuery);
|
|
41
42
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
42
43
|
const event = events[i];
|
|
43
44
|
if (event === null || typeof event !== "object" || event.type !== "todo/write") continue;
|
package/lib/tools.js
CHANGED
|
@@ -22,6 +22,7 @@ import { buildEsrStatusView } from "./index-block.js";
|
|
|
22
22
|
import { buildEntityPage, renderEntityPage } from "./entity-page.js";
|
|
23
23
|
import { truncate, shortId, slugId, fmtDate, escapeLt, workspaceKey as wk, uuid } from "./util.js";
|
|
24
24
|
import { hasCJK, searchSessionsCJK } from "./session-cjk.js";
|
|
25
|
+
import { sessionSeq } from "./session-events.js";
|
|
25
26
|
|
|
26
27
|
const KINDS = [
|
|
27
28
|
"preference",
|
|
@@ -51,7 +52,7 @@ function callerOf(exec) {
|
|
|
51
52
|
if (agent === void 0) throw new EngramError("ENGRAM_MISSING_AGENT", "engram tools require an agent-bound caller");
|
|
52
53
|
const cwd = agent.session?.header?.cwd;
|
|
53
54
|
if (cwd === void 0 || cwd.length === 0) throw new EngramError("ENGRAM_UNAUTHORIZED", "this session has no workspace cwd; engram memory is workspace-scoped");
|
|
54
|
-
return { agent, cwd: wk(cwd), sessionId: agent.session.id, seq: agent.session
|
|
55
|
+
return { agent, cwd: wk(cwd), sessionId: agent.session.id, seq: sessionSeq(agent.session) };
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
function parse(zodSchema, raw, tool) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-engram",
|
|
3
3
|
"description": "Minimalist long-term memory for DeepSeek Harness, distilling symbolic-index + pi-esr ideas: zero-LLM auto-capture, a symbolic [ENGRAM] index with progressive disclosure, and an ESR-lite evidence-closure protocol (esr_task / esr_node / esr_close / esr_link). Real agent-behaviour telemetry (usage rollup + /stats) and a deterministic offline recall benchmark (npm run eval). Hot path is model-free; storage on ctx.storageDomain; web memory viewer, observability panel and config card through DSH's native settings slots.",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.4",
|
|
5
5
|
"packageManager": "pnpm@11.22.0",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|