@alanzhao/dsh-memory-lite 0.1.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.
Files changed (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +112 -0
  3. package/cordis.patch.yml +9 -0
  4. package/lib/catalog.d.ts +35 -0
  5. package/lib/catalog.js +145 -0
  6. package/lib/catalog.js.map +1 -0
  7. package/lib/client.js +631 -0
  8. package/lib/config.d.ts +133 -0
  9. package/lib/config.js +173 -0
  10. package/lib/config.js.map +1 -0
  11. package/lib/extract/checkpoint.d.ts +60 -0
  12. package/lib/extract/checkpoint.js +42 -0
  13. package/lib/extract/checkpoint.js.map +1 -0
  14. package/lib/extract/decision.d.ts +36 -0
  15. package/lib/extract/decision.js +118 -0
  16. package/lib/extract/decision.js.map +1 -0
  17. package/lib/extract/digest.d.ts +12 -0
  18. package/lib/extract/digest.js +25 -0
  19. package/lib/extract/digest.js.map +1 -0
  20. package/lib/extract/index.d.ts +52 -0
  21. package/lib/extract/index.js +489 -0
  22. package/lib/extract/index.js.map +1 -0
  23. package/lib/extract/prompt.d.ts +20 -0
  24. package/lib/extract/prompt.js +63 -0
  25. package/lib/extract/prompt.js.map +1 -0
  26. package/lib/extract/triggers.d.ts +15 -0
  27. package/lib/extract/triggers.js +26 -0
  28. package/lib/extract/triggers.js.map +1 -0
  29. package/lib/extract/window.d.ts +48 -0
  30. package/lib/extract/window.js +110 -0
  31. package/lib/extract/window.js.map +1 -0
  32. package/lib/index.d.ts +29 -0
  33. package/lib/index.js +92 -0
  34. package/lib/index.js.map +1 -0
  35. package/lib/inject.d.ts +45 -0
  36. package/lib/inject.js +102 -0
  37. package/lib/inject.js.map +1 -0
  38. package/lib/memory-store.d.ts +147 -0
  39. package/lib/memory-store.js +494 -0
  40. package/lib/memory-store.js.map +1 -0
  41. package/lib/path.d.ts +24 -0
  42. package/lib/path.js +54 -0
  43. package/lib/path.js.map +1 -0
  44. package/lib/peer.d.ts +16 -0
  45. package/lib/peer.js +38 -0
  46. package/lib/peer.js.map +1 -0
  47. package/lib/status.d.ts +44 -0
  48. package/lib/status.js +42 -0
  49. package/lib/status.js.map +1 -0
  50. package/lib/tool-utils.d.ts +25 -0
  51. package/lib/tool-utils.js +16 -0
  52. package/lib/tool-utils.js.map +1 -0
  53. package/lib/tools/forget-memory.d.ts +9 -0
  54. package/lib/tools/forget-memory.js +39 -0
  55. package/lib/tools/forget-memory.js.map +1 -0
  56. package/lib/tools/read-memory.d.ts +8 -0
  57. package/lib/tools/read-memory.js +41 -0
  58. package/lib/tools/read-memory.js.map +1 -0
  59. package/lib/tools/remember.d.ts +9 -0
  60. package/lib/tools/remember.js +65 -0
  61. package/lib/tools/remember.js.map +1 -0
  62. package/lib/tools/search-memory.d.ts +8 -0
  63. package/lib/tools/search-memory.js +49 -0
  64. package/lib/tools/search-memory.js.map +1 -0
  65. package/lib/tools/update-memory.d.ts +9 -0
  66. package/lib/tools/update-memory.js +46 -0
  67. package/lib/tools/update-memory.js.map +1 -0
  68. package/lib/types.d.ts +35 -0
  69. package/lib/types.js +7 -0
  70. package/lib/types.js.map +1 -0
  71. package/package.json +81 -0
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Configuration contracts for dsh-memory-lite. The schemastery `Config`
3
+ * validates the shape a user writes in cordis.yml (every field optional);
4
+ * `resolveConfig` performs explicit defaulting — the only place defaults
5
+ * are applied.
6
+ * @module dsh-memory-lite/src/config
7
+ */
8
+ import z from '@deepseek-ai/schemastery';
9
+ /** Default memory root; `~` expands against the user's home. */
10
+ export declare const DEFAULT_MEMORY_ROOT = "~/.agent-memory";
11
+ /** Default peer used for sessions without a usable cwd. */
12
+ export declare const DEFAULT_PEER = "dsh-web";
13
+ /** Default cap on catalog size, in approximate tokens. */
14
+ export declare const DEFAULT_INDEX_MAX_TOKENS = 1200;
15
+ /** Default session-header indicator slot order (ascending; -1 sits left of the built-in "Session log" capsule). */
16
+ export declare const DEFAULT_HEADER_ORDER = -1;
17
+ /** Per-session peer derivation from `SessionHeader.cwd`. */
18
+ export interface WorkspacePeersConfig {
19
+ /** Derive the peer from each session's cwd. Defaults to true. */
20
+ enabled: boolean;
21
+ /** Reserved for Phase 2 extraction; peer derivation ignores it. Defaults to true. */
22
+ excludeSubagents: boolean;
23
+ /** Peer name used when a session has no cwd; the literal `default_peer` selects the default. Defaults to `default_peer`. */
24
+ cwdFallback: string;
25
+ }
26
+ /** L0 catalog sizing. */
27
+ export interface IndexConfig {
28
+ /** Approximate token cap for the catalog; entries past the cap are truncated. Defaults to 1200. */
29
+ maxTokens: number;
30
+ /** Index sort order; `mtime` newest-first is the only supported value. Defaults to `mtime`. */
31
+ sortBy: 'mtime';
32
+ }
33
+ /** Tool-side switches. */
34
+ export interface ToolsConfig {
35
+ /** Reserved; the tool schemas are minimal by construction. Defaults to true. */
36
+ schemaMinimal: boolean;
37
+ }
38
+ /** Browser-side indicator options. */
39
+ export interface UiConfig {
40
+ /** Render order of the memory status indicator in the session-header utilities slot (ascending; smaller = further left, -1 sits left of the built-in "Session log" capsule). Defaults to -1. */
41
+ headerOrder: number;
42
+ }
43
+ /** One explicit cross-peer share: expose a target peer's memories at shared/<name>/... */
44
+ export interface SharingMountConfig {
45
+ /** Path prefix exposed to this peer's sessions: shared/<name>/<file>.md. Must be a plain segment. */
46
+ name: string;
47
+ /** Target peer whose memories root is shared. */
48
+ peer: string;
49
+ /** Subpath inside the target peer's memories root; '' = the whole root. */
50
+ subpath: string;
51
+ /** Read-only mirror; write operations are rejected. Defaults to true. */
52
+ readonly: boolean;
53
+ }
54
+ /** Cross-peer sharing (Phase 3): explicit, not default. */
55
+ export interface SharingConfig {
56
+ /** Master switch; when off, shared/ paths are rejected. Defaults to false. */
57
+ enabled: boolean;
58
+ /** Ordered mount declarations, visible to every peer except the target itself. */
59
+ mounts: SharingMountConfig[];
60
+ }
61
+ /** Extraction model selection: an explicit route wins; empty = the global default model (agent-default-model). */
62
+ export interface ExtractionLlmConfig {
63
+ /** Combined "provider/model" route, e.g. 'deepseek-official/deepseek-v4-flash'. Empty = global default. */
64
+ route?: string;
65
+ /** Adapter-owned reasoning effort id (e.g. 'low'); empty = follow the global default selection. */
66
+ reasoningEffort?: string;
67
+ /** Legacy provider key (kept for compatibility with pre-§17 configs); superseded by route. */
68
+ provider?: string;
69
+ /** Legacy model id (kept for compatibility with pre-§17 configs); superseded by route. */
70
+ model?: string;
71
+ }
72
+ /** Background implicit extraction (Phase 2, channel B). */
73
+ export interface ExtractionConfig {
74
+ /** incremental | explicit_only | off. Defaults to incremental. */
75
+ mode: 'incremental' | 'explicit_only' | 'off';
76
+ /** New surface messages >= this count triggers an extraction. Defaults to 20. */
77
+ windowTurns: number;
78
+ /** Idle minutes before the remaining window is extracted. Defaults to 30. */
79
+ idleTimeoutMin: number;
80
+ /** Max messages fed to the extraction LLM per run. Defaults to 20. */
81
+ maxMessages: number;
82
+ /** Which event kinds count and feed the window (fixed to the three surface types). */
83
+ messageScope: ('user' | 'assistant' | 'tool_result')[];
84
+ /** Per tool/result content truncation before feeding the LLM. Defaults to 2048. */
85
+ toolResultMaxBytes: number;
86
+ /** Carry the rolling session digest. Defaults to true. */
87
+ includeDigest: boolean;
88
+ /** Grep existing memories for dedup/contradiction. Defaults to true. */
89
+ dedup: boolean;
90
+ /** Extraction model selection; empty route = the global default model (agent-default-model). */
91
+ llm: ExtractionLlmConfig;
92
+ /** agent/turn-stopping backstop trigger. Defaults to true. */
93
+ turnStoppingTrigger: boolean;
94
+ /** session/flush last-resort trigger. Defaults to true. */
95
+ flushTrigger: boolean;
96
+ /** Minimum unextracted messages for a turn-boundary run. Defaults to 5. */
97
+ minTurnExtract: number;
98
+ /** Debounce window for turn-boundary extraction, ms. Defaults to 30000. */
99
+ turnDebounceMs: number;
100
+ /** Write audit entries to peers/{peer}/sessions/{id}.json. Defaults to true. */
101
+ auditLog: boolean;
102
+ /** Global cap on concurrent extraction LLM requests. Defaults to 1. */
103
+ maxConcurrentRequests: number;
104
+ /** On a failed JSON parse, one cheap repair call re-asks the model for valid JSON. Defaults to true. */
105
+ parseRetry: boolean;
106
+ }
107
+ /** Raw plugin configuration as written in cordis.yml; every field optional. */
108
+ export interface MemoryConfig {
109
+ root?: string;
110
+ defaultPeer?: string;
111
+ workspacePeers?: Partial<WorkspacePeersConfig>;
112
+ index?: Partial<IndexConfig>;
113
+ tools?: Partial<ToolsConfig>;
114
+ extraction?: Partial<ExtractionConfig>;
115
+ sharing?: Partial<SharingConfig>;
116
+ ui?: Partial<UiConfig>;
117
+ }
118
+ /** Fully defaulted configuration after {@link resolveConfig}. */
119
+ export interface ResolvedConfig {
120
+ /** Absolute, `~`-expanded memory root. */
121
+ root: string;
122
+ defaultPeer: string;
123
+ workspacePeers: WorkspacePeersConfig;
124
+ index: IndexConfig;
125
+ tools: ToolsConfig;
126
+ extraction: ExtractionConfig;
127
+ sharing: SharingConfig;
128
+ ui: UiConfig;
129
+ }
130
+ /** Validate the plugin configuration shape. Unknown keys are tolerated for forward compatibility. */
131
+ export declare const Config: z<MemoryConfig>;
132
+ /** Normalize and default a raw configuration; throws on invalid values. */
133
+ export declare function resolveConfig(config?: MemoryConfig): ResolvedConfig;
package/lib/config.js ADDED
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Configuration contracts for dsh-memory-lite. The schemastery `Config`
3
+ * validates the shape a user writes in cordis.yml (every field optional);
4
+ * `resolveConfig` performs explicit defaulting — the only place defaults
5
+ * are applied.
6
+ * @module dsh-memory-lite/src/config
7
+ */
8
+ import { resolve } from 'node:path';
9
+ import z from '@deepseek-ai/schemastery';
10
+ import { expandHomePath } from '@deepseek-ai/dsh-home-paths';
11
+ /** Default memory root; `~` expands against the user's home. */
12
+ export const DEFAULT_MEMORY_ROOT = '~/.agent-memory';
13
+ /** Default peer used for sessions without a usable cwd. */
14
+ export const DEFAULT_PEER = 'dsh-web';
15
+ /** Default cap on catalog size, in approximate tokens. */
16
+ export const DEFAULT_INDEX_MAX_TOKENS = 1200;
17
+ /** Default session-header indicator slot order (ascending; -1 sits left of the built-in "Session log" capsule). */
18
+ export const DEFAULT_HEADER_ORDER = -1;
19
+ /** Validate the plugin configuration shape. Unknown keys are tolerated for forward compatibility. */
20
+ export const Config = z.object({
21
+ root: z.string(),
22
+ defaultPeer: z.string(),
23
+ workspacePeers: z.object({
24
+ enabled: z.boolean(),
25
+ excludeSubagents: z.boolean(),
26
+ cwdFallback: z.string(),
27
+ }),
28
+ index: z.object({
29
+ maxTokens: z.number(),
30
+ sortBy: z.union([z.const('mtime')]),
31
+ }),
32
+ tools: z.object({
33
+ schemaMinimal: z.boolean(),
34
+ }),
35
+ extraction: z.object({
36
+ mode: z.union([z.const('incremental'), z.const('explicit_only'), z.const('off')]),
37
+ windowTurns: z.number(),
38
+ idleTimeoutMin: z.number(),
39
+ maxMessages: z.number(),
40
+ messageScope: z.array(z.union([z.const('user'), z.const('assistant'), z.const('tool_result')])),
41
+ toolResultMaxBytes: z.number(),
42
+ includeDigest: z.boolean(),
43
+ dedup: z.boolean(),
44
+ llm: z.object({
45
+ route: z.string(),
46
+ reasoningEffort: z.string(),
47
+ provider: z.string(),
48
+ model: z.string(),
49
+ }),
50
+ turnStoppingTrigger: z.boolean(),
51
+ flushTrigger: z.boolean(),
52
+ minTurnExtract: z.number(),
53
+ turnDebounceMs: z.number(),
54
+ auditLog: z.boolean(),
55
+ maxConcurrentRequests: z.number(),
56
+ parseRetry: z.boolean(),
57
+ }),
58
+ sharing: z.object({
59
+ enabled: z.boolean(),
60
+ mounts: z.array(z.object({
61
+ name: z.string(),
62
+ peer: z.string(),
63
+ subpath: z.string(),
64
+ readonly: z.boolean(),
65
+ })),
66
+ }),
67
+ ui: z.object({
68
+ headerOrder: z.number(),
69
+ }),
70
+ });
71
+ /** Normalize and default a raw configuration; throws on invalid values. */
72
+ export function resolveConfig(config = {}) {
73
+ const root = resolve(expandHomePath(config.root ?? DEFAULT_MEMORY_ROOT));
74
+ const defaultPeer = config.defaultPeer ?? DEFAULT_PEER;
75
+ if (defaultPeer === '' || defaultPeer.includes('/') || defaultPeer.includes('\\') || defaultPeer === '.' || defaultPeer === '..') {
76
+ throw new Error(`dsh-memory-lite: defaultPeer must be a plain directory name, got ${JSON.stringify(defaultPeer)}`);
77
+ }
78
+ const workspacePeers = {
79
+ enabled: config.workspacePeers?.enabled ?? true,
80
+ excludeSubagents: config.workspacePeers?.excludeSubagents ?? true,
81
+ cwdFallback: config.workspacePeers?.cwdFallback ?? 'default_peer',
82
+ };
83
+ const index = {
84
+ maxTokens: config.index?.maxTokens ?? DEFAULT_INDEX_MAX_TOKENS,
85
+ sortBy: 'mtime',
86
+ };
87
+ if (!Number.isInteger(index.maxTokens) || index.maxTokens < 50) {
88
+ throw new Error(`dsh-memory-lite: index.maxTokens must be an integer >= 50, got ${index.maxTokens}`);
89
+ }
90
+ const extraction = {
91
+ mode: config.extraction?.mode ?? 'incremental',
92
+ windowTurns: config.extraction?.windowTurns ?? 20,
93
+ idleTimeoutMin: config.extraction?.idleTimeoutMin ?? 30,
94
+ maxMessages: config.extraction?.maxMessages ?? 20,
95
+ messageScope: config.extraction?.messageScope ?? ['user', 'assistant', 'tool_result'],
96
+ toolResultMaxBytes: config.extraction?.toolResultMaxBytes ?? 2048,
97
+ includeDigest: config.extraction?.includeDigest ?? true,
98
+ dedup: config.extraction?.dedup ?? true,
99
+ llm: {
100
+ route: config.extraction?.llm?.route ?? '',
101
+ reasoningEffort: config.extraction?.llm?.reasoningEffort ?? '',
102
+ provider: config.extraction?.llm?.provider,
103
+ model: config.extraction?.llm?.model,
104
+ },
105
+ turnStoppingTrigger: config.extraction?.turnStoppingTrigger ?? true,
106
+ flushTrigger: config.extraction?.flushTrigger ?? true,
107
+ minTurnExtract: config.extraction?.minTurnExtract ?? 5,
108
+ turnDebounceMs: config.extraction?.turnDebounceMs ?? 30000,
109
+ auditLog: config.extraction?.auditLog ?? true,
110
+ maxConcurrentRequests: config.extraction?.maxConcurrentRequests ?? 1,
111
+ parseRetry: config.extraction?.parseRetry ?? true,
112
+ };
113
+ for (const [key, value] of [
114
+ ['windowTurns', extraction.windowTurns],
115
+ ['maxMessages', extraction.maxMessages],
116
+ ['toolResultMaxBytes', extraction.toolResultMaxBytes],
117
+ ['minTurnExtract', extraction.minTurnExtract],
118
+ ['turnDebounceMs', extraction.turnDebounceMs],
119
+ ['maxConcurrentRequests', extraction.maxConcurrentRequests],
120
+ ['idleTimeoutMin', extraction.idleTimeoutMin],
121
+ ]) {
122
+ if (!Number.isInteger(value) || value < 0) {
123
+ throw new Error(`dsh-memory-lite: extraction.${key} must be a non-negative integer, got ${value}`);
124
+ }
125
+ }
126
+ const llmRoute = extraction.llm.route;
127
+ if (llmRoute !== '') {
128
+ // "provider/model-id": the model id itself may contain a slash (pi-ai
129
+ // routes like commandcode/deepseek/deepseek-v4-flash), so only the first
130
+ // segment is the provider and the rest is the model id.
131
+ const slash = llmRoute.indexOf('/');
132
+ if (slash <= 0 || slash === llmRoute.length - 1 || llmRoute.includes('\\')) {
133
+ throw new Error(`dsh-memory-lite: extraction.llm.route must be "provider/model", got ${JSON.stringify(llmRoute)}`);
134
+ }
135
+ }
136
+ const sharing = {
137
+ enabled: config.sharing?.enabled ?? false,
138
+ mounts: (config.sharing?.mounts ?? []).map(mount => ({
139
+ name: mount.name,
140
+ peer: mount.peer,
141
+ subpath: mount.subpath ?? '',
142
+ readonly: mount.readonly ?? true,
143
+ })),
144
+ };
145
+ for (const mount of sharing.mounts) {
146
+ if (mount.name === '' || /[\\/]/.test(mount.name) || mount.name === '.' || mount.name === '..') {
147
+ throw new Error('dsh-memory-lite: sharing mount name must be a plain segment, got ' + JSON.stringify(mount.name));
148
+ }
149
+ if (mount.peer === '' || mount.peer.includes('/') || mount.peer.includes('\\') || mount.peer === '.' || mount.peer === '..') {
150
+ throw new Error('dsh-memory-lite: sharing mount peer must be a plain directory name, got ' + JSON.stringify(mount.peer));
151
+ }
152
+ if (mount.subpath.startsWith('/') || mount.subpath.split('/').includes('..')) {
153
+ throw new Error('dsh-memory-lite: sharing mount subpath must not escape the memories root, got ' + JSON.stringify(mount.subpath));
154
+ }
155
+ }
156
+ const ui = {
157
+ headerOrder: config.ui?.headerOrder ?? DEFAULT_HEADER_ORDER,
158
+ };
159
+ if (!Number.isInteger(ui.headerOrder)) {
160
+ throw new Error(`dsh-memory-lite: ui.headerOrder must be an integer, got ${ui.headerOrder}`);
161
+ }
162
+ return {
163
+ root,
164
+ defaultPeer,
165
+ workspacePeers,
166
+ index,
167
+ tools: { schemaMinimal: config.tools?.schemaMinimal ?? true },
168
+ extraction,
169
+ sharing,
170
+ ui,
171
+ };
172
+ }
173
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Checkpoint + audit document for one peer/session, persisted at
3
+ * peers/{peer}/sessions/{session-id}.json. The checkpoint records the last
4
+ * extracted log seq (resume gap-filling); the audit records each run's inputs
5
+ * and outcome so extraction is reproducible from the session log.
6
+ * @module dsh-memory-lite/src/extract/checkpoint
7
+ */
8
+ export interface AuditEntry {
9
+ /** ISO timestamp of the run. */
10
+ readonly at: string;
11
+ /** Included window seq range [start, end]. */
12
+ readonly windowSeq: readonly [number, number];
13
+ /** Grep hits fed as existing memory context. */
14
+ readonly grepHits: readonly string[];
15
+ /** Provider/model route used, when resolvable. */
16
+ readonly route?: {
17
+ readonly provider: string;
18
+ readonly model: string;
19
+ };
20
+ /** maxTokens sent on the extraction request. */
21
+ readonly maxTokens?: number;
22
+ /** The parsed decision kind, or the failure note. */
23
+ readonly decision: string;
24
+ /** Target memory path when the decision addressed one. */
25
+ readonly path?: string;
26
+ /** The log seq the checkpoint advanced to after this run. */
27
+ readonly seq: number;
28
+ /** Failure/retry context (successful runs omit it). */
29
+ readonly note?: string;
30
+ /** Number of LLM stream calls made for this run (1, or 2 when a repair ran). */
31
+ readonly llmCalls?: number;
32
+ /** Total input tokens (real stream usage when available, else bytes/3 estimate). */
33
+ readonly inputTokens?: number;
34
+ /** Total output tokens (real stream usage when available, else bytes/3 estimate). */
35
+ readonly outputTokens?: number;
36
+ /** Cache-read tokens reported by the provider. */
37
+ readonly cacheReadTokens?: number;
38
+ /** Cache-write tokens reported by the provider. */
39
+ readonly cacheWriteTokens?: number;
40
+ /** Run wall-clock duration in ms. */
41
+ readonly durationMs?: number;
42
+ }
43
+ export interface CheckpointFile {
44
+ readonly version: 1;
45
+ readonly checkpoint: {
46
+ readonly seq: number;
47
+ };
48
+ readonly digest: string;
49
+ readonly audit: readonly AuditEntry[];
50
+ }
51
+ export declare const CHECKPOINT_VERSION = 1;
52
+ /** Cap on retained audit entries per session, bounding file size. */
53
+ export declare const MAX_AUDIT_ENTRIES = 50;
54
+ export declare function emptyCheckpoint(): CheckpointFile;
55
+ /** Append an audit entry and advance the checkpoint; returns a new document. */
56
+ export declare function withRun(state: CheckpointFile, seq: number, entry: Omit<AuditEntry, 'seq'>): CheckpointFile;
57
+ /** Serialize the document for atomic write. */
58
+ export declare function renderCheckpoint(state: CheckpointFile): string;
59
+ /** Parse a persisted document; tolerant of malformed input (degrades to empty). */
60
+ export declare function parseCheckpoint(text: string): CheckpointFile;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Checkpoint + audit document for one peer/session, persisted at
3
+ * peers/{peer}/sessions/{session-id}.json. The checkpoint records the last
4
+ * extracted log seq (resume gap-filling); the audit records each run's inputs
5
+ * and outcome so extraction is reproducible from the session log.
6
+ * @module dsh-memory-lite/src/extract/checkpoint
7
+ */
8
+ export const CHECKPOINT_VERSION = 1;
9
+ /** Cap on retained audit entries per session, bounding file size. */
10
+ export const MAX_AUDIT_ENTRIES = 50;
11
+ export function emptyCheckpoint() {
12
+ return { version: CHECKPOINT_VERSION, checkpoint: { seq: 0 }, digest: '', audit: [] };
13
+ }
14
+ /** Append an audit entry and advance the checkpoint; returns a new document. */
15
+ export function withRun(state, seq, entry) {
16
+ const audit = [...state.audit, { ...entry, seq }];
17
+ if (audit.length > MAX_AUDIT_ENTRIES)
18
+ audit.splice(0, audit.length - MAX_AUDIT_ENTRIES);
19
+ return { version: CHECKPOINT_VERSION, checkpoint: { seq }, digest: state.digest, audit };
20
+ }
21
+ /** Serialize the document for atomic write. */
22
+ export function renderCheckpoint(state) {
23
+ return JSON.stringify(state, null, 2) + '\n';
24
+ }
25
+ /** Parse a persisted document; tolerant of malformed input (degrades to empty). */
26
+ export function parseCheckpoint(text) {
27
+ try {
28
+ const value = JSON.parse(text);
29
+ if (typeof value !== 'object' || value === null)
30
+ return emptyCheckpoint();
31
+ const record = value;
32
+ const checkpoint = record.checkpoint;
33
+ const seq = typeof checkpoint?.seq === 'number' && Number.isSafeInteger(checkpoint.seq) ? checkpoint.seq : 0;
34
+ const digest = typeof record.digest === 'string' ? record.digest : '';
35
+ const audit = Array.isArray(record.audit) ? record.audit : [];
36
+ return { version: CHECKPOINT_VERSION, checkpoint: { seq }, digest, audit: audit };
37
+ }
38
+ catch {
39
+ return emptyCheckpoint();
40
+ }
41
+ }
42
+ //# sourceMappingURL=checkpoint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkpoint.js","sourceRoot":"","sources":["../../src/extract/checkpoint.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA0CH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAA;AACnC,qEAAqE;AACrE,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,CAAA;AAEnC,MAAM,UAAU,eAAe;IAC7B,OAAO,EAAE,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAA;AACvF,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,OAAO,CAAC,KAAqB,EAAE,GAAW,EAAE,KAA8B;IACxF,MAAM,KAAK,GAAG,CAAC,GAAG,KAAK,CAAC,KAAK,EAAE,EAAE,GAAG,KAAK,EAAE,GAAG,EAAE,CAAC,CAAA;IACjD,IAAI,KAAK,CAAC,MAAM,GAAG,iBAAiB;QAAE,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,iBAAiB,CAAC,CAAA;IACvF,OAAO,EAAE,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,CAAA;AAC1F,CAAC;AAED,+CAA+C;AAC/C,MAAM,UAAU,gBAAgB,CAAC,KAAqB;IACpD,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAA;AAC9C,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,IAAI,CAAC;QACH,MAAM,KAAK,GAAY,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QACvC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;YAAE,OAAO,eAAe,EAAE,CAAA;QACzE,MAAM,MAAM,GAAG,KAAgC,CAAA;QAC/C,MAAM,UAAU,GAAG,MAAM,CAAC,UAAiD,CAAA;QAC3E,MAAM,GAAG,GAAG,OAAO,UAAU,EAAE,GAAG,KAAK,QAAQ,IAAI,MAAM,CAAC,aAAa,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;QAC5G,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAA;QACrE,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;QAC7D,OAAO,EAAE,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,KAAqB,EAAE,CAAA;IACnG,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,eAAe,EAAE,CAAA;IAC1B,CAAC;AACH,CAAC"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Parse the extraction LLM's structured output into a concrete memory
3
+ * mutation decision. The model answers in JSON; the parser is tolerant of
4
+ * code fences and prose, normalizes full-width braces/quotes, salvages
5
+ * broken-brace JSON from key:value pairs, and rejects anything outside the
6
+ * closed vocabulary.
7
+ * @module dsh-memory-lite/src/extract/decision
8
+ */
9
+ export type ExtractionDecisionKind = 'create' | 'merge' | 'update' | 'skip';
10
+ export interface CreateDecision {
11
+ readonly kind: 'create';
12
+ readonly category: string;
13
+ readonly title: string;
14
+ readonly content: string;
15
+ }
16
+ export interface PathDecision {
17
+ readonly kind: 'merge' | 'update';
18
+ readonly path: string;
19
+ readonly content: string;
20
+ }
21
+ export interface SkipDecision {
22
+ readonly kind: 'skip';
23
+ readonly reason?: string;
24
+ }
25
+ export type ExtractionDecision = CreateDecision | PathDecision | SkipDecision;
26
+ export declare const DECISION_KINDS: readonly ExtractionDecisionKind[];
27
+ /** Extraction decisions never delete; `forget` stays a user-only tool. */
28
+ export declare const DECISION_ALLOWED: Set<string>;
29
+ /** Locate and parse the JSON object in the model's answer. */
30
+ export declare function parseDecision(text: string): ExtractionDecision;
31
+ /**
32
+ * The first balanced `{...}` region in the answer (fence-free). When the
33
+ * braces are broken (truncated or unbalanced output), falls back to rebuilding
34
+ * a JSON object from `"key": "value"` pairs; throws only when neither works.
35
+ */
36
+ export declare function extractJsonObject(text: string): string;
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Parse the extraction LLM's structured output into a concrete memory
3
+ * mutation decision. The model answers in JSON; the parser is tolerant of
4
+ * code fences and prose, normalizes full-width braces/quotes, salvages
5
+ * broken-brace JSON from key:value pairs, and rejects anything outside the
6
+ * closed vocabulary.
7
+ * @module dsh-memory-lite/src/extract/decision
8
+ */
9
+ export const DECISION_KINDS = ['create', 'merge', 'update', 'skip'];
10
+ /** Extraction decisions never delete; `forget` stays a user-only tool. */
11
+ export const DECISION_ALLOWED = new Set(['create', 'merge', 'update', 'skip']);
12
+ /** Locate and parse the JSON object in the model's answer. */
13
+ export function parseDecision(text) {
14
+ const json = extractJsonObject(text);
15
+ const parsed = JSON.parse(json);
16
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
17
+ throw new Error(`extraction decision is not an object: ${json.slice(0, 200)}`);
18
+ }
19
+ const record = parsed;
20
+ const kind = typeof record.decision === 'string' ? record.decision : '';
21
+ if (!DECISION_ALLOWED.has(kind)) {
22
+ throw new Error(`extraction decision must be one of ${DECISION_KINDS.join('/')}, got ${JSON.stringify(kind)}`);
23
+ }
24
+ switch (kind) {
25
+ case 'create': {
26
+ const category = str(record.category);
27
+ const title = str(record.title);
28
+ const content = str(record.content);
29
+ if (category === '' || title === '' || content === '') {
30
+ throw new Error('create decision requires category, title, and content');
31
+ }
32
+ return { kind, category, title, content };
33
+ }
34
+ case 'merge':
35
+ case 'update': {
36
+ const path = str(record.path);
37
+ const content = str(record.content);
38
+ if (path === '' || content === '') {
39
+ throw new Error(`${kind} decision requires path and content`);
40
+ }
41
+ return { kind, path, content };
42
+ }
43
+ default: {
44
+ const reason = str(record.reason);
45
+ return { kind: 'skip', ...(reason === '' ? {} : { reason }) };
46
+ }
47
+ }
48
+ }
49
+ /** Normalize full-width brace/quote characters the model may emit. */
50
+ function normalizeFullWidth(text) {
51
+ return text
52
+ .replaceAll('{', '{')
53
+ .replaceAll('}', '}')
54
+ .replaceAll('\uFF02', '"');
55
+ }
56
+ /**
57
+ * The first balanced `{...}` region in the answer (fence-free). When the
58
+ * braces are broken (truncated or unbalanced output), falls back to rebuilding
59
+ * a JSON object from `"key": "value"` pairs; throws only when neither works.
60
+ */
61
+ export function extractJsonObject(text) {
62
+ const normalized = normalizeFullWidth(text);
63
+ const balanced = scanBalanced(normalized);
64
+ if (balanced !== undefined)
65
+ return balanced;
66
+ const rebuilt = rebuildFromPairs(normalized);
67
+ if (rebuilt !== undefined)
68
+ return rebuilt;
69
+ throw new Error('extraction answer contains no JSON object');
70
+ }
71
+ /** The first balanced `{...}` region, or undefined when unbalanced. */
72
+ function scanBalanced(text) {
73
+ const start = text.indexOf('{');
74
+ if (start === -1)
75
+ return undefined;
76
+ let depth = 0;
77
+ let inString = false;
78
+ let escaped = false;
79
+ for (let i = start; i < text.length; i += 1) {
80
+ const ch = text[i];
81
+ if (inString) {
82
+ if (escaped)
83
+ escaped = false;
84
+ else if (ch === '\\')
85
+ escaped = true;
86
+ else if (ch === '"')
87
+ inString = false;
88
+ continue;
89
+ }
90
+ if (ch === '"')
91
+ inString = true;
92
+ else if (ch === '{')
93
+ depth += 1;
94
+ else if (ch === '}') {
95
+ depth -= 1;
96
+ if (depth === 0)
97
+ return text.slice(start, i + 1);
98
+ }
99
+ }
100
+ return undefined;
101
+ }
102
+ /** Rebuild a JSON object from complete `"key": "value"` pairs (no braces). */
103
+ function rebuildFromPairs(text) {
104
+ const pairRe = /"([A-Za-z_][A-Za-z0-9_]*)":\s*("(?:\\.|[^"\\])*"|true|false|null|-?\d+(?:\.\d+)?)/g;
105
+ const pairs = [];
106
+ let match;
107
+ while ((match = pairRe.exec(text)) !== null) {
108
+ pairs.push([match[1], match[2]]);
109
+ pairRe.lastIndex = match.index + match[0].length;
110
+ }
111
+ if (pairs.length === 0)
112
+ return undefined;
113
+ return '{' + pairs.map(([key, value]) => JSON.stringify(key) + ':' + value).join(',') + '}';
114
+ }
115
+ function str(value) {
116
+ return typeof value === 'string' ? value.trim() : '';
117
+ }
118
+ //# sourceMappingURL=decision.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decision.js","sourceRoot":"","sources":["../../src/extract/decision.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Rolling session digest: a bounded ~100-token summary carried between
3
+ * extraction runs so later windows can resolve cross-window references.
4
+ * Token length is approximated as bytes/3 (CJK 1 token, ASCII ~4 chars).
5
+ * @module dsh-memory-lite/src/extract/digest
6
+ */
7
+ /** Rough token-length estimate for mixed CJK/ASCII text. */
8
+ export declare function digestApproxTokens(text: string): number;
9
+ /** Shrink the digest until it fits `maxTokens` (progressive halving). */
10
+ export declare function truncateDigest(text: string, maxTokens: number): string;
11
+ /** Roll the digest forward: previous digest + this window, re-truncated. */
12
+ export declare function rollDigest(prev: string, windowText: string, maxTokens: number): string;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Rolling session digest: a bounded ~100-token summary carried between
3
+ * extraction runs so later windows can resolve cross-window references.
4
+ * Token length is approximated as bytes/3 (CJK 1 token, ASCII ~4 chars).
5
+ * @module dsh-memory-lite/src/extract/digest
6
+ */
7
+ /** Rough token-length estimate for mixed CJK/ASCII text. */
8
+ export function digestApproxTokens(text) {
9
+ const bytes = Buffer.byteLength(text, 'utf8');
10
+ return Math.ceil(bytes / 3);
11
+ }
12
+ /** Shrink the digest until it fits `maxTokens` (progressive halving). */
13
+ export function truncateDigest(text, maxTokens) {
14
+ let current = text.trim();
15
+ while (current.length > 0 && digestApproxTokens(current) > maxTokens) {
16
+ current = current.slice(0, Math.floor(current.length * 0.8)).trim();
17
+ }
18
+ return current;
19
+ }
20
+ /** Roll the digest forward: previous digest + this window, re-truncated. */
21
+ export function rollDigest(prev, windowText, maxTokens) {
22
+ const combined = prev === '' ? windowText : `${prev}\n${windowText}`;
23
+ return truncateDigest(combined, maxTokens);
24
+ }
25
+ //# sourceMappingURL=digest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"digest.js","sourceRoot":"","sources":["../../src/extract/digest.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,4DAA4D;AAC5D,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IAC7C,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAA;AAC7B,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,SAAiB;IAC5D,IAAI,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAA;IACzB,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,kBAAkB,CAAC,OAAO,CAAC,GAAG,SAAS,EAAE,CAAC;QACrE,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;IACrE,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,UAAkB,EAAE,SAAiB;IAC5E,MAAM,QAAQ,GAAG,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,KAAK,UAAU,EAAE,CAAA;IACpE,OAAO,cAAc,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAA;AAC5C,CAAC"}