dsh-hypatia-auto-memory 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +454 -0
- package/cordis.patch.yml +20 -0
- package/lib/client.js +1081 -0
- package/lib/client.js.map +1 -0
- package/package.json +90 -0
- package/skills/hypatia/SKILL.md +585 -0
- package/skills/hypatia-dream/SKILL.md +246 -0
- package/skills/hypatia-dream/evals/evals.json +92 -0
- package/skills/hypatia-memory/SKILL.md +175 -0
- package/src/auto-approve.js +133 -0
- package/src/cascade.js +190 -0
- package/src/client/SettingsCard.module.css +420 -0
- package/src/client/SettingsCard.tsx +700 -0
- package/src/client/card-form.ts +450 -0
- package/src/client/consolidation-models.ts +69 -0
- package/src/client/css-modules.d.ts +4 -0
- package/src/client/index.tsx +106 -0
- package/src/client/locales.ts +111 -0
- package/src/client/shelves.ts +88 -0
- package/src/client/slot-contract.ts +17 -0
- package/src/collector.js +596 -0
- package/src/config.js +225 -0
- package/src/consolidator.js +592 -0
- package/src/content-policy.js +301 -0
- package/src/housekeeping.js +197 -0
- package/src/hypatia-cli.js +294 -0
- package/src/hypatia-client.js +72 -0
- package/src/hypatia-mcp.js +477 -0
- package/src/index.js +418 -0
- package/src/persisted-session.js +99 -0
- package/src/progress.js +56 -0
- package/src/queue.js +466 -0
- package/src/recall.js +148 -0
- package/src/shelf.js +180 -0
- package/src/skills.js +166 -0
- package/src/state.js +87 -0
- package/src/status.js +53 -0
- package/src/writer.js +320 -0
package/src/config.js
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings namespace and live configuration for dsh-hypatia-auto-memory.
|
|
3
|
+
*
|
|
4
|
+
* All user-facing behavior knobs live in the `hypatia-auto-memory` settings
|
|
5
|
+
* namespace so they are editable through settings.yaml / Web settings and
|
|
6
|
+
* hot-updatable through the settings provider. The cordis `config` block on
|
|
7
|
+
* the bundle row only carries skill-packaging knobs (see index.js).
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-hypatia-auto-memory/config
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import z from '@deepseek-ai/schemastery'
|
|
13
|
+
|
|
14
|
+
/** Settings namespace carrying the auto-memory policy. */
|
|
15
|
+
export const SETTINGS_NAMESPACE = 'hypatia-auto-memory'
|
|
16
|
+
|
|
17
|
+
/** Composition defaults; every schema field mirrors one of these. */
|
|
18
|
+
export const DEFAULTS = {
|
|
19
|
+
enabled: true,
|
|
20
|
+
binaries: ['hypatia'],
|
|
21
|
+
// How the plugin itself talks to hypatia: a private `hypatia mcp` process,
|
|
22
|
+
// or one CLI command per call. A binary without `mcp` falls back to the CLI
|
|
23
|
+
// on its own (see hypatia-client.js). Applies from the next call.
|
|
24
|
+
transport: 'mcp',
|
|
25
|
+
// Where every entry this plugin writes — and every lookup it makes — goes.
|
|
26
|
+
// Read at startup, like `enabled`: see shelf.js for what a switch does.
|
|
27
|
+
shelf: 'default',
|
|
28
|
+
// Approve the AGENT's own `hypatia …` bash calls (retrieval, explicit
|
|
29
|
+
// remember/forget). This plugin's own writes never need it — they go over
|
|
30
|
+
// its own connection, never through a tool call. Read at startup, like `enabled`.
|
|
31
|
+
autoApprove: true,
|
|
32
|
+
collector: {
|
|
33
|
+
enabled: true,
|
|
34
|
+
maxAssistantChars: 8000,
|
|
35
|
+
// User messages were never capped, so a pasted log went into hypatia whole.
|
|
36
|
+
// Generous, because what the user typed is the part most worth keeping.
|
|
37
|
+
maxUserChars: 32000,
|
|
38
|
+
toolLedger: true,
|
|
39
|
+
},
|
|
40
|
+
consolidation: {
|
|
41
|
+
enabled: true,
|
|
42
|
+
models: [],
|
|
43
|
+
maxInputTokens: 16000,
|
|
44
|
+
maxOutputTokens: 2000,
|
|
45
|
+
timeoutMs: 120_000,
|
|
46
|
+
checkEveryTurns: 5,
|
|
47
|
+
minNewTokens: 3000,
|
|
48
|
+
maxWorkUnitsPerRun: 3,
|
|
49
|
+
// Work-unit relationship adjudication. `dedupMaxDistance` is a cosine
|
|
50
|
+
// distance ceiling on candidates worth judging at all; anything further
|
|
51
|
+
// away is not a relative, and asking about it only invites a wrong edge.
|
|
52
|
+
adjudicate: true,
|
|
53
|
+
dedupMaxDistance: 0.45,
|
|
54
|
+
dedupCandidates: 5,
|
|
55
|
+
// Hierarchical archive. 16:1 per tier is the protocol's constant; it is what
|
|
56
|
+
// makes the summary layer log₁₆(n) deep instead of growing with the
|
|
57
|
+
// conversation.
|
|
58
|
+
cascade: {
|
|
59
|
+
enabled: true,
|
|
60
|
+
batchSize: 16,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
queue: {
|
|
64
|
+
concurrency: 1,
|
|
65
|
+
maxAttempts: 3,
|
|
66
|
+
retryDelayMs: 5000,
|
|
67
|
+
flushWindowMs: 120_000,
|
|
68
|
+
},
|
|
69
|
+
// Recall is a session seed, not a per-turn injector: the agent pulls the rest
|
|
70
|
+
// through the bundled skill. `maxEntries` / `maxCharsPerEntry` belonged to the
|
|
71
|
+
// removed per-turn path and would now be knobs that control nothing.
|
|
72
|
+
recall: {
|
|
73
|
+
enabled: true,
|
|
74
|
+
preloadRulesTaboos: true,
|
|
75
|
+
},
|
|
76
|
+
// Startup housekeeping of the progress table (see housekeeping.js). Both act
|
|
77
|
+
// only on positive evidence and skip on any doubt.
|
|
78
|
+
housekeeping: {
|
|
79
|
+
// Reset a row whose session has no msg-* entry left in the shelf (a wiped
|
|
80
|
+
// or replaced shelf). A session whose messages were all deleted on purpose
|
|
81
|
+
// looks the same and would be logged again — switch off if that matters.
|
|
82
|
+
reconcileOnStartup: true,
|
|
83
|
+
// Drop the row and tasks of a session DSH no longer has.
|
|
84
|
+
pruneVanishedSessions: true,
|
|
85
|
+
// Consolidate a logged tail the in-session trigger never reached. A process
|
|
86
|
+
// restart takes the session-end path with it, so a short session would
|
|
87
|
+
// otherwise stay logged-but-never-distilled. Gated by the same
|
|
88
|
+
// `consolidation.minNewTokens` floor, read from the progress row.
|
|
89
|
+
backfillConsolidation: true,
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Exact LLM route eligible for one consolidation attempt. */
|
|
94
|
+
const ConsolidationModelSchema = z.object({
|
|
95
|
+
provider: z.string(),
|
|
96
|
+
model: z.string(),
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
/** Schema of the auto-memory settings section. */
|
|
100
|
+
export const SettingsSchema = z.object({
|
|
101
|
+
enabled: z.boolean().default(DEFAULTS.enabled),
|
|
102
|
+
binaries: z.array(z.string()).default(DEFAULTS.binaries),
|
|
103
|
+
transport: z.union(['mcp', 'cli']).default(DEFAULTS.transport),
|
|
104
|
+
shelf: z.string().default(DEFAULTS.shelf),
|
|
105
|
+
autoApprove: z.boolean().default(DEFAULTS.autoApprove),
|
|
106
|
+
collector: z.object({
|
|
107
|
+
enabled: z.boolean().default(DEFAULTS.collector.enabled),
|
|
108
|
+
maxAssistantChars: z.number().step(1).min(500).default(DEFAULTS.collector.maxAssistantChars),
|
|
109
|
+
maxUserChars: z.number().step(1).min(500).default(DEFAULTS.collector.maxUserChars),
|
|
110
|
+
toolLedger: z.boolean().default(DEFAULTS.collector.toolLedger),
|
|
111
|
+
}).default(DEFAULTS.collector),
|
|
112
|
+
consolidation: z.object({
|
|
113
|
+
enabled: z.boolean().default(DEFAULTS.consolidation.enabled),
|
|
114
|
+
models: z.array(ConsolidationModelSchema).default(DEFAULTS.consolidation.models),
|
|
115
|
+
maxInputTokens: z.number().step(1).min(1000).default(DEFAULTS.consolidation.maxInputTokens),
|
|
116
|
+
maxOutputTokens: z.number().step(1).min(200).default(DEFAULTS.consolidation.maxOutputTokens),
|
|
117
|
+
timeoutMs: z.number().step(1).min(1000).max(3_600_000).default(DEFAULTS.consolidation.timeoutMs),
|
|
118
|
+
checkEveryTurns: z.number().step(1).min(1).default(DEFAULTS.consolidation.checkEveryTurns),
|
|
119
|
+
minNewTokens: z.number().step(1).min(500).default(DEFAULTS.consolidation.minNewTokens),
|
|
120
|
+
maxWorkUnitsPerRun: z.number().step(1).min(1).max(10).default(DEFAULTS.consolidation.maxWorkUnitsPerRun),
|
|
121
|
+
adjudicate: z.boolean().default(DEFAULTS.consolidation.adjudicate),
|
|
122
|
+
dedupMaxDistance: z.number().min(0).max(2).default(DEFAULTS.consolidation.dedupMaxDistance),
|
|
123
|
+
dedupCandidates: z.number().step(1).min(1).max(20).default(DEFAULTS.consolidation.dedupCandidates),
|
|
124
|
+
cascade: z.object({
|
|
125
|
+
enabled: z.boolean().default(DEFAULTS.consolidation.cascade.enabled),
|
|
126
|
+
batchSize: z.number().step(1).min(2).max(64).default(DEFAULTS.consolidation.cascade.batchSize),
|
|
127
|
+
}).default(DEFAULTS.consolidation.cascade),
|
|
128
|
+
}).default(DEFAULTS.consolidation),
|
|
129
|
+
queue: z.object({
|
|
130
|
+
concurrency: z.number().step(1).min(1).max(8).default(DEFAULTS.queue.concurrency),
|
|
131
|
+
maxAttempts: z.number().step(1).min(1).max(10).default(DEFAULTS.queue.maxAttempts),
|
|
132
|
+
retryDelayMs: z.number().step(1).min(100).default(DEFAULTS.queue.retryDelayMs),
|
|
133
|
+
flushWindowMs: z.number().step(1).min(0).max(600_000).default(DEFAULTS.queue.flushWindowMs),
|
|
134
|
+
}).default(DEFAULTS.queue),
|
|
135
|
+
recall: z.object({
|
|
136
|
+
enabled: z.boolean().default(DEFAULTS.recall.enabled),
|
|
137
|
+
preloadRulesTaboos: z.boolean().default(DEFAULTS.recall.preloadRulesTaboos),
|
|
138
|
+
}).default(DEFAULTS.recall),
|
|
139
|
+
housekeeping: z.object({
|
|
140
|
+
reconcileOnStartup: z.boolean().default(DEFAULTS.housekeeping.reconcileOnStartup),
|
|
141
|
+
pruneVanishedSessions: z.boolean().default(DEFAULTS.housekeeping.pruneVanishedSessions),
|
|
142
|
+
backfillConsolidation: z.boolean().default(DEFAULTS.housekeeping.backfillConsolidation),
|
|
143
|
+
}).default(DEFAULTS.housekeeping),
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Read-only namespace publishing the shelves `hypatia list` reports, so the
|
|
148
|
+
* settings card can offer them as choices. The browser cannot run the CLI, and
|
|
149
|
+
* a settings descriptor is the one Host-owned value this plugin can put on the
|
|
150
|
+
* wire. Nothing is ever written to it: the Host re-registers it with a fresh
|
|
151
|
+
* composition layer (`base`) whenever the listing changes, and no card claims
|
|
152
|
+
* the namespace, so it renders nowhere.
|
|
153
|
+
*/
|
|
154
|
+
export const INVENTORY_NAMESPACE = 'hypatia-auto-memory-shelves'
|
|
155
|
+
|
|
156
|
+
/** Schema of the shelf inventory; `error` is set when the listing failed. */
|
|
157
|
+
export const InventorySchema = z.object({
|
|
158
|
+
shelves: z.array(z.object({
|
|
159
|
+
name: z.string(),
|
|
160
|
+
path: z.string(),
|
|
161
|
+
connected: z.boolean(),
|
|
162
|
+
})).default([]),
|
|
163
|
+
error: z.string().default(''),
|
|
164
|
+
listedAt: z.number().default(0),
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Validate the route list before its consumer sees it. An empty list is valid:
|
|
169
|
+
* logging continues while consolidation stays idle until the user selects one
|
|
170
|
+
* or more catalog routes.
|
|
171
|
+
*/
|
|
172
|
+
function validateSettings(value) {
|
|
173
|
+
// One argv element to hypatia, and the name `hypatia list` prints: a blank
|
|
174
|
+
// or padded name would silently address a shelf that does not exist.
|
|
175
|
+
if (value.shelf === '' || /\s/.test(value.shelf)) {
|
|
176
|
+
throw new Error('hypatia-auto-memory: shelf must be a non-empty name without whitespace')
|
|
177
|
+
}
|
|
178
|
+
const seen = new Set()
|
|
179
|
+
for (const route of value.consolidation.models) {
|
|
180
|
+
if (route.provider.trim() === '' || route.model.trim() === '') {
|
|
181
|
+
throw new Error('hypatia-auto-memory: every consolidation model route needs a provider and model')
|
|
182
|
+
}
|
|
183
|
+
const key = `${route.provider}\0${route.model}`
|
|
184
|
+
if (seen.has(key)) {
|
|
185
|
+
throw new Error(`hypatia-auto-memory: duplicate consolidation model route ${route.provider}/${route.model}`)
|
|
186
|
+
}
|
|
187
|
+
seen.add(key)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Attach this plugin's settings section to the provider (when present) and
|
|
193
|
+
* return a live config handle. Reads through the returned `get()` always see
|
|
194
|
+
* the currently authoritative value; `onChange` fires on attach, detach, and
|
|
195
|
+
* every committed change.
|
|
196
|
+
*
|
|
197
|
+
* @param {import('@deepseek-ai/cordis').Context} ctx - settings-consuming context.
|
|
198
|
+
* @returns {{get: () => any, onChange: (cb: (value: any) => void) => () => void}}
|
|
199
|
+
*/
|
|
200
|
+
export function installConfig(ctx) {
|
|
201
|
+
let source = () => DEFAULTS
|
|
202
|
+
const listeners = new Set()
|
|
203
|
+
ctx.settings.installSection(ctx, SETTINGS_NAMESPACE, SettingsSchema, DEFAULTS, {
|
|
204
|
+
validate: validateSettings,
|
|
205
|
+
setSource: (current) => {
|
|
206
|
+
source = current
|
|
207
|
+
},
|
|
208
|
+
onChange: () => {
|
|
209
|
+
for (const cb of listeners) {
|
|
210
|
+
try {
|
|
211
|
+
cb(source())
|
|
212
|
+
} catch {
|
|
213
|
+
// A status listener must never break settings application.
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
})
|
|
218
|
+
return {
|
|
219
|
+
get: () => source(),
|
|
220
|
+
onChange(cb) {
|
|
221
|
+
listeners.add(cb)
|
|
222
|
+
return () => listeners.delete(cb)
|
|
223
|
+
},
|
|
224
|
+
}
|
|
225
|
+
}
|