hippo-memory 1.56.0 → 1.57.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/dist/api.d.ts +16 -9
- package/dist/api.js +57 -24
- package/dist/card-detail.d.ts +1 -1
- package/dist/card-detail.js +1 -1
- package/dist/cli/shared.d.ts +137 -0
- package/dist/cli/shared.js +830 -0
- package/dist/cli/sleep.d.ts +10 -0
- package/dist/cli/sleep.js +171 -0
- package/dist/cli.d.ts +0 -7
- package/dist/cli.js +223 -1789
- package/dist/connectors/github/webhook.d.ts +19 -0
- package/dist/connectors/github/webhook.js +313 -0
- package/dist/connectors/slack/webhook.d.ts +22 -0
- package/dist/connectors/slack/webhook.js +203 -0
- package/dist/consolidate.js +3 -2
- package/dist/context-auto.d.ts +3 -0
- package/dist/context-auto.js +34 -0
- package/dist/customer-notes.js +2 -1
- package/dist/dashboard.js +2 -1
- package/dist/decisions.js +2 -1
- package/dist/eval-stats.d.ts +58 -0
- package/dist/eval-stats.js +111 -0
- package/dist/goals.d.ts +49 -25
- package/dist/goals.js +39 -22
- package/dist/graph-extract.js +1 -1
- package/dist/graph-recall.d.ts +1 -1
- package/dist/graph-recall.js +1 -1
- package/dist/graph.js +1 -1
- package/dist/hooks.d.ts +1 -3
- package/dist/hooks.js +2 -4
- package/dist/http-util.d.ts +31 -0
- package/dist/http-util.js +46 -0
- package/dist/incidents.js +2 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +5 -2
- package/dist/mcp/server.js +173 -285
- package/dist/memory.d.ts +19 -0
- package/dist/memory.js +38 -0
- package/dist/policies.js +2 -1
- package/dist/predictions.js +2 -1
- package/dist/processes.js +2 -1
- package/dist/project-briefs.js +3 -1
- package/dist/prompt-recall.js +1 -1
- package/dist/recall-history.d.ts +5 -0
- package/dist/recall-history.js +9 -0
- package/dist/recall-pipeline.d.ts +101 -0
- package/dist/recall-pipeline.js +313 -0
- package/dist/recall-scope.d.ts +22 -0
- package/dist/recall-scope.js +27 -1
- package/dist/search.d.ts +0 -20
- package/dist/search.js +2 -49
- package/dist/server.js +1901 -2384
- package/dist/skills.js +2 -1
- package/dist/store-cards.d.ts +53 -0
- package/dist/store-cards.js +512 -0
- package/dist/store.d.ts +2 -89
- package/dist/store.js +6 -562
- package/dist/tenant.d.ts +22 -0
- package/dist/tenant.js +26 -0
- package/dist/tokenize.d.ts +2 -0
- package/dist/tokenize.js +8 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
- package/extensions/openclaw-plugin/package.json +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/dist/eval-stats.d.ts
CHANGED
|
@@ -120,4 +120,62 @@ export declare function passAtK(runsByTask: boolean[][], k: number): number;
|
|
|
120
120
|
* NaN when no task has k runs.
|
|
121
121
|
*/
|
|
122
122
|
export declare function passHatK(runsByTask: boolean[][], k: number): number;
|
|
123
|
+
/** All units of one family (tasks by seeds), resampled as one block so seeds stay together. */
|
|
124
|
+
export type Family<T> = readonly T[];
|
|
125
|
+
/** One repository's families; a task with no lesson is a family of one. */
|
|
126
|
+
export type Repository<T> = readonly Family<T>[];
|
|
127
|
+
/** An {@link Estimate} with a two-sided p-value from the same resamples. */
|
|
128
|
+
export interface TestedEstimate extends Estimate {
|
|
129
|
+
/** 2 x min(share <= null, share >= null), capped at 1; NaN with fewer than two repositories. */
|
|
130
|
+
readonly p: number;
|
|
131
|
+
/** Non-finite resamples, dropped; `iterations + dropped` is the number requested. */
|
|
132
|
+
readonly dropped: number;
|
|
133
|
+
/** The null the p-value tested against; {@link verdict} reads its sides from here. */
|
|
134
|
+
readonly nullValue: number;
|
|
135
|
+
}
|
|
136
|
+
/** Options for {@link twoLevelBootstrap}. */
|
|
137
|
+
export interface TwoLevelOpts extends BootstrapOpts {
|
|
138
|
+
/** Resamples. Default 10,000. */
|
|
139
|
+
readonly iterations?: number;
|
|
140
|
+
/** Value the p-value tests against: 0 for differences, 1 for ratios. Default 0. */
|
|
141
|
+
readonly nullValue?: number;
|
|
142
|
+
}
|
|
143
|
+
/** Resamples repositories, then families inside each; only the repository draw carries a shared-store fault.
|
|
144
|
+
* The statistic never uses the PRNG, so a second call on one seed draws the same resamples. */
|
|
145
|
+
export declare function twoLevelBootstrap<T>(repos: readonly Repository<T>[], statistic: (units: readonly T[]) => number, opts?: TwoLevelOpts): TestedEstimate;
|
|
146
|
+
/** Holm step-down in input order; the family size is `ps.length`, as preregistered.
|
|
147
|
+
* A NaN stays NaN but ranks as 1, so it never loosens the others; a finite p outside [0, 1] throws. */
|
|
148
|
+
export declare function holmAdjust(ps: readonly number[]): number[];
|
|
149
|
+
/** One of the four mutually exclusive outcomes the preregistration allows per hypothesis. */
|
|
150
|
+
export type Verdict = 'loss' | 'win' | 'tie' | 'inconclusive';
|
|
151
|
+
/** What a hypothesis needs to be read; see {@link verdict}. */
|
|
152
|
+
export interface VerdictSpec {
|
|
153
|
+
/** Direction that favours the treatment arm. */
|
|
154
|
+
readonly helpful: 'lower' | 'higher';
|
|
155
|
+
/** Inclusive band the interval must sit inside for a tie. */
|
|
156
|
+
readonly tieBand: readonly [number, number];
|
|
157
|
+
/** An estimate on this value or beyond it, on the helpful side, reaches the minimum effect. */
|
|
158
|
+
readonly minimumEffectAt: number;
|
|
159
|
+
/** Default 0.05. */
|
|
160
|
+
readonly alpha?: number;
|
|
161
|
+
}
|
|
162
|
+
/** A verdict, plus whether a win reaches the minimum effect (a win below it is a small win). */
|
|
163
|
+
export interface VerdictResult {
|
|
164
|
+
readonly verdict: Verdict;
|
|
165
|
+
readonly reachesMinimum: boolean;
|
|
166
|
+
}
|
|
167
|
+
/** Checks in the preregistered order; the null comes from `e.nullValue`, so p and sides cannot disagree.
|
|
168
|
+
* A NaN estimate, p or bound is inconclusive, since a win or loss cannot be ruled out. */
|
|
169
|
+
export declare function verdict(e: TestedEstimate, adjustedP: number, spec: VerdictSpec): VerdictResult;
|
|
170
|
+
/** A win must hold under both codings of not-applicable, while a loss under either is reported. */
|
|
171
|
+
export declare function combineCodings(a: VerdictResult, b: VerdictResult): VerdictResult;
|
|
172
|
+
/** Outcome of the harm gate; see {@link harmGate}. */
|
|
173
|
+
export interface HarmGate {
|
|
174
|
+
readonly pass: boolean;
|
|
175
|
+
readonly costOk: boolean;
|
|
176
|
+
readonly resolveOk: boolean;
|
|
177
|
+
}
|
|
178
|
+
/** Cost ratio's upper bound below 1.10, resolve difference's lower bound (a fraction) above -0.05.
|
|
179
|
+
* Both estimates must use alpha 0.05, the conservative reading of "upper 95% bound"; a NaN bound fails. */
|
|
180
|
+
export declare function harmGate(costRatio: Estimate, resolveDiff: Estimate): HarmGate;
|
|
123
181
|
//# sourceMappingURL=eval-stats.d.ts.map
|
package/dist/eval-stats.js
CHANGED
|
@@ -184,4 +184,115 @@ export function passHatK(runsByTask, k) {
|
|
|
184
184
|
return Number.NaN;
|
|
185
185
|
return eligible.filter((r) => r.slice(0, k).every(Boolean)).length / eligible.length;
|
|
186
186
|
}
|
|
187
|
+
function notANumber(nullValue, dropped) {
|
|
188
|
+
const nan = Number.NaN;
|
|
189
|
+
return { estimate: nan, low: nan, high: nan, p: nan, iterations: 0, dropped, nullValue };
|
|
190
|
+
}
|
|
191
|
+
function resampleUnits(repos, rand) {
|
|
192
|
+
const units = [];
|
|
193
|
+
for (let i = 0; i < repos.length; i++) {
|
|
194
|
+
const repo = repos[Math.floor(rand() * repos.length)];
|
|
195
|
+
for (let j = 0; j < repo.length; j++) {
|
|
196
|
+
for (const unit of repo[Math.floor(rand() * repo.length)])
|
|
197
|
+
units.push(unit);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return units;
|
|
201
|
+
}
|
|
202
|
+
// Inclusive on both sides, as the p-value is worded; float noise around the null breaks a tie.
|
|
203
|
+
function twoSidedP(samples, nullValue) {
|
|
204
|
+
let below = 0;
|
|
205
|
+
let above = 0;
|
|
206
|
+
for (const s of samples) {
|
|
207
|
+
if (s <= nullValue)
|
|
208
|
+
below++;
|
|
209
|
+
if (s >= nullValue)
|
|
210
|
+
above++;
|
|
211
|
+
}
|
|
212
|
+
return Math.min(1, (2 * Math.min(below, above)) / samples.length);
|
|
213
|
+
}
|
|
214
|
+
/** Resamples repositories, then families inside each; only the repository draw carries a shared-store fault.
|
|
215
|
+
* The statistic never uses the PRNG, so a second call on one seed draws the same resamples. */
|
|
216
|
+
export function twoLevelBootstrap(repos, statistic, opts = {}) {
|
|
217
|
+
const requested = opts.iterations ?? 10_000;
|
|
218
|
+
if (!Number.isInteger(requested) || requested <= 0) {
|
|
219
|
+
throw new RangeError(`iterations must be a positive integer, got ${requested}`);
|
|
220
|
+
}
|
|
221
|
+
const nullValue = opts.nullValue ?? 0;
|
|
222
|
+
const kept = repos.map((r) => r.filter((f) => f.length > 0)).filter((r) => r.length > 0);
|
|
223
|
+
if (kept.length === 0)
|
|
224
|
+
return notANumber(nullValue, 0);
|
|
225
|
+
const rand = seededRandom(opts.seed ?? 1);
|
|
226
|
+
const samples = [];
|
|
227
|
+
for (let b = 0; b < requested; b++) {
|
|
228
|
+
const value = statistic(resampleUnits(kept, rand));
|
|
229
|
+
if (Number.isFinite(value))
|
|
230
|
+
samples.push(value);
|
|
231
|
+
}
|
|
232
|
+
const dropped = requested - samples.length;
|
|
233
|
+
if (samples.length === 0)
|
|
234
|
+
return notANumber(nullValue, dropped);
|
|
235
|
+
const estimate = statistic(kept.flatMap((r) => r.flat()));
|
|
236
|
+
const p = kept.length < 2 ? Number.NaN : twoSidedP(samples, nullValue);
|
|
237
|
+
const interval = percentileInterval(samples, opts.alpha ?? 0.05);
|
|
238
|
+
return { estimate, ...interval, p, iterations: samples.length, dropped, nullValue };
|
|
239
|
+
}
|
|
240
|
+
/** Holm step-down in input order; the family size is `ps.length`, as preregistered.
|
|
241
|
+
* A NaN stays NaN but ranks as 1, so it never loosens the others; a finite p outside [0, 1] throws. */
|
|
242
|
+
export function holmAdjust(ps) {
|
|
243
|
+
for (const p of ps) {
|
|
244
|
+
if (!Number.isNaN(p) && !(p >= 0 && p <= 1))
|
|
245
|
+
throw new RangeError(`p-value out of range: ${p}`);
|
|
246
|
+
}
|
|
247
|
+
const ranked = ps
|
|
248
|
+
.map((p, index) => ({ index, key: Number.isNaN(p) ? 1 : p }))
|
|
249
|
+
.sort((a, b) => a.key - b.key || a.index - b.index);
|
|
250
|
+
const adjusted = Array.from({ length: ps.length }, () => Number.NaN);
|
|
251
|
+
let running = 0;
|
|
252
|
+
ranked.forEach(({ index, key }, rank) => {
|
|
253
|
+
running = Math.max(running, (ps.length - rank) * key);
|
|
254
|
+
if (!Number.isNaN(ps[index]))
|
|
255
|
+
adjusted[index] = Math.min(1, running);
|
|
256
|
+
});
|
|
257
|
+
return adjusted;
|
|
258
|
+
}
|
|
259
|
+
/** Checks in the preregistered order; the null comes from `e.nullValue`, so p and sides cannot disagree.
|
|
260
|
+
* A NaN estimate, p or bound is inconclusive, since a win or loss cannot be ruled out. */
|
|
261
|
+
export function verdict(e, adjustedP, spec) {
|
|
262
|
+
const inconclusive = { verdict: 'inconclusive', reachesMinimum: false };
|
|
263
|
+
if ([adjustedP, e.estimate, e.low, e.high].some(Number.isNaN))
|
|
264
|
+
return inconclusive;
|
|
265
|
+
const nullValue = e.nullValue;
|
|
266
|
+
const lowerIsHelpful = spec.helpful === 'lower';
|
|
267
|
+
if (adjustedP < (spec.alpha ?? 0.05)) {
|
|
268
|
+
const helpfulSide = lowerIsHelpful ? e.estimate < nullValue : e.estimate > nullValue;
|
|
269
|
+
const harmfulSide = lowerIsHelpful ? e.estimate > nullValue : e.estimate < nullValue;
|
|
270
|
+
if (harmfulSide)
|
|
271
|
+
return { verdict: 'loss', reachesMinimum: false };
|
|
272
|
+
if (helpfulSide) {
|
|
273
|
+
const reaches = lowerIsHelpful ? e.estimate <= spec.minimumEffectAt : e.estimate >= spec.minimumEffectAt;
|
|
274
|
+
return { verdict: 'win', reachesMinimum: reaches };
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
const insideBand = e.low >= spec.tieBand[0] && e.high <= spec.tieBand[1];
|
|
278
|
+
return insideBand ? { verdict: 'tie', reachesMinimum: false } : inconclusive;
|
|
279
|
+
}
|
|
280
|
+
/** A win must hold under both codings of not-applicable, while a loss under either is reported. */
|
|
281
|
+
export function combineCodings(a, b) {
|
|
282
|
+
if (a.verdict === 'loss' || b.verdict === 'loss')
|
|
283
|
+
return { verdict: 'loss', reachesMinimum: false };
|
|
284
|
+
if (a.verdict === 'win' && b.verdict === 'win') {
|
|
285
|
+
return { verdict: 'win', reachesMinimum: a.reachesMinimum && b.reachesMinimum };
|
|
286
|
+
}
|
|
287
|
+
if (a.verdict === 'tie' && b.verdict === 'tie')
|
|
288
|
+
return { verdict: 'tie', reachesMinimum: false };
|
|
289
|
+
return { verdict: 'inconclusive', reachesMinimum: false };
|
|
290
|
+
}
|
|
291
|
+
/** Cost ratio's upper bound below 1.10, resolve difference's lower bound (a fraction) above -0.05.
|
|
292
|
+
* Both estimates must use alpha 0.05, the conservative reading of "upper 95% bound"; a NaN bound fails. */
|
|
293
|
+
export function harmGate(costRatio, resolveDiff) {
|
|
294
|
+
const costOk = costRatio.high < 1.1;
|
|
295
|
+
const resolveOk = resolveDiff.low > -0.05;
|
|
296
|
+
return { pass: costOk && resolveOk, costOk, resolveOk };
|
|
297
|
+
}
|
|
187
298
|
//# sourceMappingURL=eval-stats.js.map
|
package/dist/goals.d.ts
CHANGED
|
@@ -79,12 +79,36 @@ export interface GetActiveGoalsOpts {
|
|
|
79
79
|
}
|
|
80
80
|
export declare function getActiveGoals(hippoRoot: string, opts: GetActiveGoalsOpts): Goal[];
|
|
81
81
|
export declare function getActiveGoalsWithDb(db: DatabaseSyncLike, opts: GetActiveGoalsOpts): Goal[];
|
|
82
|
+
/** One `goal_recall_log` row: a boosted local memory recalled while its goal was active. */
|
|
83
|
+
export interface GoalRecallLogRow {
|
|
84
|
+
goalId: string;
|
|
85
|
+
memoryId: string;
|
|
86
|
+
tenantId: string;
|
|
87
|
+
sessionId: string;
|
|
88
|
+
recalledAt: string;
|
|
89
|
+
score: number;
|
|
90
|
+
}
|
|
91
|
+
/** Options shared by {@link computeGoalStackBoost} and {@link applyGoalStackBoost}. */
|
|
92
|
+
export interface GoalStackBoostOpts {
|
|
93
|
+
sessionId: string;
|
|
94
|
+
tenantId: string;
|
|
95
|
+
limit: number;
|
|
96
|
+
/**
|
|
97
|
+
* Optional side-channel: one goal-boost `RerankStep` per boosted row, keyed by
|
|
98
|
+
* `entry.id`. A map rather than a row field because the helper re-spreads rows.
|
|
99
|
+
* Only populated when passed, so the default path allocates nothing.
|
|
100
|
+
*/
|
|
101
|
+
trace?: Map<string, RerankStep>;
|
|
102
|
+
}
|
|
103
|
+
/** The boosted, re-sorted rows and the `goal_recall_log` rows they earn. */
|
|
104
|
+
export interface GoalStackBoost<R> {
|
|
105
|
+
results: R[];
|
|
106
|
+
log: GoalRecallLogRow[];
|
|
107
|
+
}
|
|
82
108
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
* lifting here lets api.recall (primary band only) AND MCP physics/hybrid
|
|
87
|
-
* call it.
|
|
109
|
+
* dlPFC goal-stack boost. Applies the multi-goal boost to entry-backed scored
|
|
110
|
+
* rows when (tenant, session) has active goals and returns the log rows to
|
|
111
|
+
* write, without writing them; {@link writeGoalRecallLog} persists them.
|
|
88
112
|
*
|
|
89
113
|
* Caller responsibilities:
|
|
90
114
|
* - Do NOT call when an explicit `goalTag` is set (caller's gate)
|
|
@@ -94,31 +118,31 @@ export declare function getActiveGoalsWithDb(db: DatabaseSyncLike, opts: GetActi
|
|
|
94
118
|
* - Recompute `tokens` after if returned rows are projected to a budgeted
|
|
95
119
|
* shape
|
|
96
120
|
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
* to preserve FK invariant on goal_recall_log.memory_id)
|
|
121
|
+
* Log rows cover the top `limit` boosted rows that live in this store's
|
|
122
|
+
* `memories` table (global-only ids are skipped to keep the FK on
|
|
123
|
+
* goal_recall_log.memory_id valid).
|
|
101
124
|
*
|
|
102
|
-
* @internal
|
|
125
|
+
* @internal Recall ranking helper. Subject to change.
|
|
126
|
+
*/
|
|
127
|
+
export declare function computeGoalStackBoost<R extends {
|
|
128
|
+
entry: MemoryEntry;
|
|
129
|
+
score: number;
|
|
130
|
+
}>(db: DatabaseSyncLike, results: R[], opts: GoalStackBoostOpts): GoalStackBoost<R>;
|
|
131
|
+
/**
|
|
132
|
+
* Writes goal-boost log rows. INSERT OR IGNORE because UNIQUE(memory_id, goal_id)
|
|
133
|
+
* makes a re-recall during the same goal life a no-op for outcome attribution.
|
|
134
|
+
*/
|
|
135
|
+
export declare function writeGoalRecallLog(db: DatabaseSyncLike, rows: readonly GoalRecallLogRow[]): void;
|
|
136
|
+
/**
|
|
137
|
+
* {@link computeGoalStackBoost} plus {@link writeGoalRecallLog} in one call, for
|
|
138
|
+
* pipelines that boost and log on the same handle.
|
|
139
|
+
*
|
|
140
|
+
* @internal Recall ranking helper. Subject to change.
|
|
103
141
|
*/
|
|
104
142
|
export declare function applyGoalStackBoost<R extends {
|
|
105
143
|
entry: MemoryEntry;
|
|
106
144
|
score: number;
|
|
107
|
-
}>(db: DatabaseSyncLike, results: R[], opts:
|
|
108
|
-
sessionId: string;
|
|
109
|
-
tenantId: string;
|
|
110
|
-
limit: number;
|
|
111
|
-
/**
|
|
112
|
-
* A7 recall-trace (optional side-channel). When supplied, the helper
|
|
113
|
-
* records one goal-boost `RerankStep` per ACTUALLY-boosted row, keyed by
|
|
114
|
-
* `entry.id`. This is a SEPARATE accumulator, NOT a field on the result
|
|
115
|
-
* row — the helper re-spreads rows and strips internal markers
|
|
116
|
-
* (`_goalMatches` below), so a row field would be dropped. The score-mul
|
|
117
|
-
* + re-sort math is untouched; the trace is only populated when this map
|
|
118
|
-
* is passed (default path never allocates → byte-identical).
|
|
119
|
-
*/
|
|
120
|
-
trace?: Map<string, RerankStep>;
|
|
121
|
-
}): R[];
|
|
145
|
+
}>(db: DatabaseSyncLike, results: R[], opts: GoalStackBoostOpts): R[];
|
|
122
146
|
export interface CompleteGoalOpts {
|
|
123
147
|
outcomeScore?: number;
|
|
124
148
|
/**
|
package/dist/goals.js
CHANGED
|
@@ -142,11 +142,9 @@ export function getActiveGoalsWithDb(db, opts) {
|
|
|
142
142
|
return rows.map(rowToGoal);
|
|
143
143
|
}
|
|
144
144
|
/**
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
* lifting here lets api.recall (primary band only) AND MCP physics/hybrid
|
|
149
|
-
* call it.
|
|
145
|
+
* dlPFC goal-stack boost. Applies the multi-goal boost to entry-backed scored
|
|
146
|
+
* rows when (tenant, session) has active goals and returns the log rows to
|
|
147
|
+
* write, without writing them; {@link writeGoalRecallLog} persists them.
|
|
150
148
|
*
|
|
151
149
|
* Caller responsibilities:
|
|
152
150
|
* - Do NOT call when an explicit `goalTag` is set (caller's gate)
|
|
@@ -156,18 +154,17 @@ export function getActiveGoalsWithDb(db, opts) {
|
|
|
156
154
|
* - Recompute `tokens` after if returned rows are projected to a budgeted
|
|
157
155
|
* shape
|
|
158
156
|
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
* to preserve FK invariant on goal_recall_log.memory_id)
|
|
157
|
+
* Log rows cover the top `limit` boosted rows that live in this store's
|
|
158
|
+
* `memories` table (global-only ids are skipped to keep the FK on
|
|
159
|
+
* goal_recall_log.memory_id valid).
|
|
163
160
|
*
|
|
164
|
-
* @internal
|
|
161
|
+
* @internal Recall ranking helper. Subject to change.
|
|
165
162
|
*/
|
|
166
|
-
export function
|
|
163
|
+
export function computeGoalStackBoost(db, results, opts) {
|
|
167
164
|
const { sessionId, tenantId, limit, trace } = opts;
|
|
168
165
|
const active = getActiveGoalsWithDb(db, { sessionId, tenantId });
|
|
169
166
|
if (active.length === 0)
|
|
170
|
-
return results;
|
|
167
|
+
return { results, log: [] };
|
|
171
168
|
const goalsByTag = new Map(active.map((g) => [g.goalName, g]));
|
|
172
169
|
// Load retrieval_policy rows for active goals so per-policy multipliers
|
|
173
170
|
// can compose onto the base goal-tag boost. Composed result is hard-capped
|
|
@@ -271,15 +268,8 @@ export function applyGoalStackBoost(db, results, opts) {
|
|
|
271
268
|
for (const row of localRows)
|
|
272
269
|
localIds.add(row.id);
|
|
273
270
|
}
|
|
274
|
-
// Log top-K boosted recalls. INSERT OR IGNORE because
|
|
275
|
-
// UNIQUE(memory_id, goal_id) means a re-recall during the same goal life
|
|
276
|
-
// is a no-op for outcome attribution.
|
|
277
271
|
const recalledAt = new Date().toISOString();
|
|
278
|
-
const
|
|
279
|
-
INSERT OR IGNORE INTO goal_recall_log
|
|
280
|
-
(goal_id, memory_id, tenant_id, session_id, recalled_at, score)
|
|
281
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
282
|
-
`);
|
|
272
|
+
const log = [];
|
|
283
273
|
for (const r of boosted.slice(0, limit)) {
|
|
284
274
|
if (!localIds.has(r.entry.id))
|
|
285
275
|
continue; // global -> skip log insert
|
|
@@ -290,10 +280,37 @@ export function applyGoalStackBoost(db, results, opts) {
|
|
|
290
280
|
const goal = goalsByTag.get(tag);
|
|
291
281
|
if (!goal)
|
|
292
282
|
continue;
|
|
293
|
-
|
|
283
|
+
log.push({ goalId: goal.id, memoryId: r.entry.id, tenantId, sessionId, recalledAt, score: r.score });
|
|
294
284
|
}
|
|
295
285
|
}
|
|
296
|
-
return boosted;
|
|
286
|
+
return { results: boosted, log };
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Writes goal-boost log rows. INSERT OR IGNORE because UNIQUE(memory_id, goal_id)
|
|
290
|
+
* makes a re-recall during the same goal life a no-op for outcome attribution.
|
|
291
|
+
*/
|
|
292
|
+
export function writeGoalRecallLog(db, rows) {
|
|
293
|
+
if (rows.length === 0)
|
|
294
|
+
return;
|
|
295
|
+
const insertLog = db.prepare(`
|
|
296
|
+
INSERT OR IGNORE INTO goal_recall_log
|
|
297
|
+
(goal_id, memory_id, tenant_id, session_id, recalled_at, score)
|
|
298
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
299
|
+
`);
|
|
300
|
+
for (const row of rows) {
|
|
301
|
+
insertLog.run(row.goalId, row.memoryId, row.tenantId, row.sessionId, row.recalledAt, row.score);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* {@link computeGoalStackBoost} plus {@link writeGoalRecallLog} in one call, for
|
|
306
|
+
* pipelines that boost and log on the same handle.
|
|
307
|
+
*
|
|
308
|
+
* @internal Recall ranking helper. Subject to change.
|
|
309
|
+
*/
|
|
310
|
+
export function applyGoalStackBoost(db, results, opts) {
|
|
311
|
+
const boost = computeGoalStackBoost(db, results, opts);
|
|
312
|
+
writeGoalRecallLog(db, boost.log);
|
|
313
|
+
return boost.results;
|
|
297
314
|
}
|
|
298
315
|
const POSITIVE_OUTCOME_THRESHOLD = 0.7;
|
|
299
316
|
const NEGATIVE_OUTCOME_THRESHOLD = 0.3;
|
package/dist/graph-extract.js
CHANGED
|
@@ -25,7 +25,7 @@ import { loadDecisions } from './decisions.js';
|
|
|
25
25
|
import { loadPolicies } from './policies.js';
|
|
26
26
|
import { loadCustomerNotes } from './customer-notes.js';
|
|
27
27
|
import { loadProjectBriefs } from './project-briefs.js';
|
|
28
|
-
import { assertTenantId } from './
|
|
28
|
+
import { assertTenantId } from './tenant.js';
|
|
29
29
|
/** Per-type load cap (the loaders default to 100). A type whose active or superseded
|
|
30
30
|
* set exceeds this is truncated; `ExtractResult.truncated` records it so the
|
|
31
31
|
* incompleteness is observable rather than silent. */
|
package/dist/graph-recall.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ResultCost, SearchResult } from './search.js';
|
|
2
2
|
/** Hard cap on `--hops` (a higher value just walks more of a finite graph; this bounds
|
|
3
3
|
* worst-case work and keeps the flag honest). */
|
|
4
4
|
export declare const MAX_HOPS = 3;
|
package/dist/graph-recall.js
CHANGED
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
* check-graph-writes lint permits this module living outside graph.ts.
|
|
38
38
|
*/
|
|
39
39
|
import { loadEntriesByIds } from './store.js';
|
|
40
|
-
import { estimateTokens } from './
|
|
40
|
+
import { estimateTokens } from './token-ledger.js';
|
|
41
41
|
import { compareEntryIdentity } from './compare.js';
|
|
42
42
|
import { loadEntitiesByMemoryId, loadEntitiesByIds, loadNeighborRelations, } from './graph.js';
|
|
43
43
|
import { passesCliRecallScopeFilter, passesScopeFilterForRecall } from './recall-scope.js';
|
package/dist/graph.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* until E3.2 multi-hop recall.
|
|
19
19
|
*/
|
|
20
20
|
import { openHippoDb, closeHippoDb } from './db.js';
|
|
21
|
-
import { assertTenantId } from './
|
|
21
|
+
import { assertTenantId } from './tenant.js';
|
|
22
22
|
const SOURCE_OBJECT_TABLE = {
|
|
23
23
|
decision: 'decisions',
|
|
24
24
|
policy: 'policies',
|
package/dist/hooks.d.ts
CHANGED
|
@@ -45,8 +45,8 @@ export interface CodexWrapperPaths {
|
|
|
45
45
|
wrapperShPath: string;
|
|
46
46
|
logFile: string;
|
|
47
47
|
runsDir: string;
|
|
48
|
+
codexHome: string;
|
|
48
49
|
historyPath: string;
|
|
49
|
-
sessionsDir: string;
|
|
50
50
|
}
|
|
51
51
|
export interface CodexWrapperInstallResult {
|
|
52
52
|
installed: boolean;
|
|
@@ -63,8 +63,6 @@ export interface CodexWrapperMetadata {
|
|
|
63
63
|
backupPath: string;
|
|
64
64
|
installMode: 'same-path' | 'cmd-shim';
|
|
65
65
|
logFile: string;
|
|
66
|
-
historyPath: string;
|
|
67
|
-
sessionsDir: string;
|
|
68
66
|
installedAt: string;
|
|
69
67
|
}
|
|
70
68
|
export interface EnsureCodexWrapperResult {
|
package/dist/hooks.js
CHANGED
|
@@ -154,7 +154,7 @@ export function defaultPreCompactLogPath() {
|
|
|
154
154
|
}
|
|
155
155
|
export function resolveCodexWrapperPaths() {
|
|
156
156
|
const home = homeDir();
|
|
157
|
-
const codexHome =
|
|
157
|
+
const codexHome = codexHomeDir(home);
|
|
158
158
|
const wrapperDir = path.join(home, '.hippo', 'bin');
|
|
159
159
|
return {
|
|
160
160
|
wrapperDir,
|
|
@@ -164,8 +164,8 @@ export function resolveCodexWrapperPaths() {
|
|
|
164
164
|
wrapperShPath: path.join(wrapperDir, 'codex'),
|
|
165
165
|
logFile: path.join(home, '.hippo', 'logs', 'codex-sleep.log'),
|
|
166
166
|
runsDir: path.join(home, '.hippo', 'runs', 'codex'),
|
|
167
|
+
codexHome,
|
|
167
168
|
historyPath: path.join(codexHome, 'history.jsonl'),
|
|
168
|
-
sessionsDir: path.join(codexHome, 'sessions'),
|
|
169
169
|
};
|
|
170
170
|
}
|
|
171
171
|
function pathEquals(a, b) {
|
|
@@ -348,8 +348,6 @@ export function installCodexWrapper(realCodexPath) {
|
|
|
348
348
|
backupPath: plan.backupPath,
|
|
349
349
|
installMode: plan.installMode,
|
|
350
350
|
logFile: paths.logFile,
|
|
351
|
-
historyPath: paths.historyPath,
|
|
352
|
-
sessionsDir: paths.sessionsDir,
|
|
353
351
|
installedAt: new Date().toISOString(),
|
|
354
352
|
};
|
|
355
353
|
fs.writeFileSync(paths.metadataPath, JSON.stringify(metadata, null, 2) + '\n', 'utf8');
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
export type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
3
|
+
[key: string]: JsonValue;
|
|
4
|
+
};
|
|
5
|
+
export declare function isJsonObjectRecord(value: JsonValue | undefined): value is Record<string, JsonValue>;
|
|
6
|
+
export declare function isHeaderString(value: string | string[] | undefined): value is string;
|
|
7
|
+
export declare const JSON_HEADERS: {
|
|
8
|
+
readonly 'content-type': "application/json";
|
|
9
|
+
};
|
|
10
|
+
export declare class HttpError extends Error {
|
|
11
|
+
status: number;
|
|
12
|
+
constructor(status: number, message: string);
|
|
13
|
+
}
|
|
14
|
+
export declare class BodyTooLargeError extends Error {
|
|
15
|
+
}
|
|
16
|
+
export declare function sendJson<T>(res: ServerResponse, status: number, body: T): void;
|
|
17
|
+
/**
|
|
18
|
+
* Read the entire request body into a Buffer. Caps at MAX_BODY_BYTES to keep
|
|
19
|
+
* a malicious or buggy client from exhausting memory. The cap is enforced
|
|
20
|
+
* mid-stream so we don't wait for an attacker to finish before erroring out.
|
|
21
|
+
*/
|
|
22
|
+
export declare function readBody(req: IncomingMessage): Promise<string>;
|
|
23
|
+
/** Per-request values a connector webhook receiver reads; ServeOpts and the /v1 RouteRequest both satisfy it. */
|
|
24
|
+
export interface WebhookRequest {
|
|
25
|
+
req: IncomingMessage;
|
|
26
|
+
res: ServerResponse;
|
|
27
|
+
opts: {
|
|
28
|
+
hippoRoot: string;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=http-util.d.ts.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export function isJsonObjectRecord(value) {
|
|
2
|
+
return value !== undefined && value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
// node:http header values are `string | string[] | undefined` (never a bare
|
|
5
|
+
// unknown), so this gets its own predicate rather than reusing isJsonString.
|
|
6
|
+
export function isHeaderString(value) {
|
|
7
|
+
return typeof value === 'string';
|
|
8
|
+
}
|
|
9
|
+
// 1 MB body cap. The CLI never sends payloads near this; anything bigger is
|
|
10
|
+
// almost certainly a misconfigured client or a deliberate memory-blowup attempt.
|
|
11
|
+
const MAX_BODY_BYTES = 1024 * 1024;
|
|
12
|
+
export const JSON_HEADERS = { 'content-type': 'application/json' };
|
|
13
|
+
export class HttpError extends Error {
|
|
14
|
+
status;
|
|
15
|
+
constructor(status, message) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.status = status;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export class BodyTooLargeError extends Error {
|
|
21
|
+
}
|
|
22
|
+
export function sendJson(res, status, body) {
|
|
23
|
+
res.writeHead(status, JSON_HEADERS);
|
|
24
|
+
res.end(JSON.stringify(body));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Read the entire request body into a Buffer. Caps at MAX_BODY_BYTES to keep
|
|
28
|
+
* a malicious or buggy client from exhausting memory. The cap is enforced
|
|
29
|
+
* mid-stream so we don't wait for an attacker to finish before erroring out.
|
|
30
|
+
*/
|
|
31
|
+
export async function readBody(req) {
|
|
32
|
+
const chunks = [];
|
|
33
|
+
let total = 0;
|
|
34
|
+
for await (const chunk of req) {
|
|
35
|
+
// SAFETY: IncomingMessage never runs setEncoding() here, so every
|
|
36
|
+
// streamed chunk is a Buffer, not a decoded string.
|
|
37
|
+
const buf = chunk;
|
|
38
|
+
total += buf.length;
|
|
39
|
+
if (total > MAX_BODY_BYTES) {
|
|
40
|
+
throw new BodyTooLargeError('request body exceeds 1MB');
|
|
41
|
+
}
|
|
42
|
+
chunks.push(buf);
|
|
43
|
+
}
|
|
44
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=http-util.js.map
|
package/dist/incidents.js
CHANGED
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
* cross-tenant or nonexistent id is rejected (throw) before the insert.
|
|
27
27
|
*/
|
|
28
28
|
import { openHippoDb, closeHippoDb } from './db.js';
|
|
29
|
-
import { writeEntry
|
|
29
|
+
import { writeEntry } from './store.js';
|
|
30
|
+
import { assertTenantId } from './tenant.js';
|
|
30
31
|
import { createMemory, Layer } from './memory.js';
|
|
31
32
|
import { appendAuditEvent } from './audit.js';
|
|
32
33
|
import { objectHalfLifeDays } from './half-life-migration.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -5,10 +5,13 @@ import { type CreateMemoryOptions, type MemoryEntry } from './memory.js';
|
|
|
5
5
|
export { MemoryEntry, Layer, EmotionalValence, ConfidenceLevel, DecayOptions, calculateStrength, resolveConfidence, confidenceFacets, type ConfidenceFacets, applyOutcome, generateId, computeSchemaFit } from './memory.js';
|
|
6
6
|
/** Published signature, so `baseHalfLifeDays` stays optional here; hippo's own writers use the strict one in memory.ts. */
|
|
7
7
|
export declare function createMemory(content: string, options?: Partial<CreateMemoryOptions>): MemoryEntry;
|
|
8
|
-
export { search, hybridSearch, physicsSearch,
|
|
8
|
+
export { search, hybridSearch, physicsSearch, estimateTokens, textOverlap, explainMatch, detectTemporalDirection, temporalBoost, computeTemporalRange, SearchResult, MatchExplanation } from './search.js';
|
|
9
|
+
export { tokenize } from './tokenize.js';
|
|
10
|
+
export { markRetrieved } from './memory.js';
|
|
9
11
|
export { multihopSearch } from './multihop.js';
|
|
10
12
|
export { graphExpandRecall, MAX_HOPS, DEFAULT_MAX_NEIGHBORS, type GraphExpandOpts } from './graph-recall.js';
|
|
11
|
-
export { initStore, loadAllEntries, loadSearchEntries, loadRecallSearchEntries, writeEntry, readEntry, deleteEntry, loadIndex, rebuildIndex, saveActiveTaskSnapshot, loadActiveTaskSnapshot, loadFreshActiveTaskSnapshot, SNAPSHOT_AMBIENT_MAX_AGE_MS, closeTaskSnapshotsForSession, clearActiveTaskSnapshot, appendSessionEvent, listSessionEvents, listMemoryConflicts, replaceDetectedConflicts, resolveConflict, saveSessionHandoff, loadLatestHandoff, loadHandoffById, stampHandoffOutcome, writeSessionEndHandoff, loadSessionDecayContext, SessionDecayContext,
|
|
13
|
+
export { initStore, loadAllEntries, loadSearchEntries, loadRecallSearchEntries, writeEntry, readEntry, deleteEntry, loadIndex, rebuildIndex, saveActiveTaskSnapshot, loadActiveTaskSnapshot, loadFreshActiveTaskSnapshot, SNAPSHOT_AMBIENT_MAX_AGE_MS, closeTaskSnapshotsForSession, clearActiveTaskSnapshot, appendSessionEvent, listSessionEvents, listMemoryConflicts, replaceDetectedConflicts, resolveConflict, saveSessionHandoff, loadLatestHandoff, loadHandoffById, stampHandoffOutcome, writeSessionEndHandoff, loadSessionDecayContext, SessionDecayContext, } from './store.js';
|
|
14
|
+
export { createCard, loadCard, listCards, loadCardDeps, loadCardRuns, loadCardComments, claimCard, heartbeatCard, blockCard, reviewCard, completeCard, reclaimExpiredCards, addCardComment, loadLatestHandoffForCard, } from './store-cards.js';
|
|
12
15
|
export { SessionHandoff, HandoffOutcome, HandoffEvidence, isHandoffOutcome } from './handoff.js';
|
|
13
16
|
export { Card, CardStatus, CardRun, CardComment, CardTransitions, isCardStatus, CARD_TRANSITIONS, CARD_LEASE_MS } from './card.js';
|
|
14
17
|
export { consolidate, ConsolidationResult } from './consolidate.js';
|
package/dist/index.js
CHANGED
|
@@ -7,10 +7,13 @@ export { Layer, calculateStrength, resolveConfidence, confidenceFacets, applyOut
|
|
|
7
7
|
export function createMemory(content, options = {}) {
|
|
8
8
|
return createStoreMemory(content, { ...options, baseHalfLifeDays: options.baseHalfLifeDays ?? DEFAULT_HALF_LIFE_DAYS });
|
|
9
9
|
}
|
|
10
|
-
export { search, hybridSearch, physicsSearch,
|
|
10
|
+
export { search, hybridSearch, physicsSearch, estimateTokens, textOverlap, explainMatch, detectTemporalDirection, temporalBoost, computeTemporalRange } from './search.js';
|
|
11
|
+
export { tokenize } from './tokenize.js';
|
|
12
|
+
export { markRetrieved } from './memory.js';
|
|
11
13
|
export { multihopSearch } from './multihop.js';
|
|
12
14
|
export { graphExpandRecall, MAX_HOPS, DEFAULT_MAX_NEIGHBORS } from './graph-recall.js';
|
|
13
|
-
export { initStore, loadAllEntries, loadSearchEntries, loadRecallSearchEntries, writeEntry, readEntry, deleteEntry, loadIndex, rebuildIndex, saveActiveTaskSnapshot, loadActiveTaskSnapshot, loadFreshActiveTaskSnapshot, SNAPSHOT_AMBIENT_MAX_AGE_MS, closeTaskSnapshotsForSession, clearActiveTaskSnapshot, appendSessionEvent, listSessionEvents, listMemoryConflicts, replaceDetectedConflicts, resolveConflict, saveSessionHandoff, loadLatestHandoff, loadHandoffById, stampHandoffOutcome, writeSessionEndHandoff, loadSessionDecayContext,
|
|
15
|
+
export { initStore, loadAllEntries, loadSearchEntries, loadRecallSearchEntries, writeEntry, readEntry, deleteEntry, loadIndex, rebuildIndex, saveActiveTaskSnapshot, loadActiveTaskSnapshot, loadFreshActiveTaskSnapshot, SNAPSHOT_AMBIENT_MAX_AGE_MS, closeTaskSnapshotsForSession, clearActiveTaskSnapshot, appendSessionEvent, listSessionEvents, listMemoryConflicts, replaceDetectedConflicts, resolveConflict, saveSessionHandoff, loadLatestHandoff, loadHandoffById, stampHandoffOutcome, writeSessionEndHandoff, loadSessionDecayContext, } from './store.js';
|
|
16
|
+
export { createCard, loadCard, listCards, loadCardDeps, loadCardRuns, loadCardComments, claimCard, heartbeatCard, blockCard, reviewCard, completeCard, reclaimExpiredCards, addCardComment, loadLatestHandoffForCard, } from './store-cards.js';
|
|
14
17
|
// Feature 5: Session handoff
|
|
15
18
|
export { isHandoffOutcome } from './handoff.js';
|
|
16
19
|
// W2a: work-queue cards
|