hippo-memory 1.56.0 → 1.58.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/README.md +11 -0
- package/dist/agent-memories/claude-code.js +1 -1
- package/dist/agent-memories/gemini.js +1 -1
- package/dist/api-errors.d.ts +27 -0
- package/dist/api-errors.js +37 -0
- package/dist/api.d.ts +21 -14
- package/dist/api.js +97 -71
- package/dist/audit.d.ts +4 -0
- package/dist/audit.js +11 -0
- package/dist/autolearn.d.ts +1 -1
- package/dist/autolearn.js +7 -5
- package/dist/capture-contract.d.ts +47 -0
- package/dist/capture-contract.js +49 -0
- package/dist/capture-error.js +2 -1
- package/dist/capture.d.ts +0 -13
- package/dist/capture.js +5 -66
- 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 +834 -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 +322 -1827
- package/dist/client.js +9 -0
- package/dist/codex-patch.js +1 -1
- package/dist/compaction-record.d.ts +1 -1
- package/dist/compaction-record.js +3 -2
- package/dist/config.d.ts +5 -0
- package/dist/config.js +17 -0
- package/dist/connectors/github/dlq.js +5 -2
- package/dist/connectors/github/octokit-client.js +4 -2
- package/dist/connectors/github/webhook.d.ts +19 -0
- package/dist/connectors/github/webhook.js +313 -0
- package/dist/connectors/slack/dlq.js +6 -2
- package/dist/connectors/slack/web-client.js +7 -5
- package/dist/connectors/slack/webhook.d.ts +22 -0
- package/dist/connectors/slack/webhook.js +203 -0
- package/dist/consolidate.d.ts +10 -0
- package/dist/consolidate.js +38 -35
- package/dist/context-auto.d.ts +3 -0
- package/dist/context-auto.js +34 -0
- package/dist/customer-notes.js +16 -14
- package/dist/dag.js +3 -2
- package/dist/dashboard.js +3 -2
- package/dist/db.d.ts +12 -0
- package/dist/db.js +62 -1
- package/dist/decisions.js +11 -9
- package/dist/doctor.js +5 -0
- package/dist/embedding-provider.js +3 -3
- package/dist/embeddings.d.ts +4 -4
- package/dist/embeddings.js +72 -16
- package/dist/eval-stats.d.ts +58 -0
- package/dist/eval-stats.js +111 -0
- package/dist/extract.js +3 -2
- 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-retry.d.ts +21 -0
- package/dist/http-retry.js +50 -0
- package/dist/http-util.d.ts +39 -0
- package/dist/http-util.js +56 -0
- package/dist/importers.d.ts +2 -0
- package/dist/importers.js +16 -5
- package/dist/incidents.js +13 -11
- package/dist/index.d.ts +5 -2
- package/dist/index.js +5 -2
- package/dist/judgment.js +10 -17
- package/dist/log.d.ts +25 -0
- package/dist/log.js +48 -0
- package/dist/mcp/server.js +224 -308
- package/dist/mcp/tool-args.d.ts +21 -0
- package/dist/mcp/tool-args.js +80 -0
- package/dist/memory.d.ts +19 -0
- package/dist/memory.js +41 -2
- package/dist/overlap-index.d.ts +7 -0
- package/dist/overlap-index.js +38 -0
- package/dist/pilot-arm.d.ts +9 -0
- package/dist/pilot-arm.js +47 -0
- package/dist/policies.js +14 -12
- package/dist/predictions.js +11 -9
- package/dist/processes.js +16 -14
- package/dist/project-briefs.js +19 -16
- package/dist/project-identity.d.ts +1 -1
- package/dist/project-identity.js +25 -1
- package/dist/prompt-recall.js +1 -1
- package/dist/raw-archive.js +7 -6
- 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 +24 -1
- package/dist/recall-scope.js +29 -2
- package/dist/refine-llm.js +3 -2
- package/dist/reject-flow.js +6 -9
- package/dist/rejection.d.ts +2 -1
- package/dist/rejection.js +2 -1
- package/dist/search.d.ts +0 -20
- package/dist/search.js +16 -51
- package/dist/secret-detect.d.ts +13 -1
- package/dist/secret-detect.js +33 -1
- package/dist/server.d.ts +3 -1
- package/dist/server.js +1854 -2566
- package/dist/session-digest.js +2 -1
- package/dist/shared.js +7 -6
- package/dist/skills.js +17 -15
- 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 +10 -566
- package/dist/tenant.d.ts +22 -0
- package/dist/tenant.js +26 -0
- package/dist/token-ledger.d.ts +4 -2
- package/dist/token-ledger.js +2 -2
- 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/connectors/slack/ratelimit.d.ts +0 -9
- package/dist/connectors/slack/ratelimit.js +0 -18
package/dist/session-digest.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import { createHash } from 'crypto';
|
|
3
3
|
import * as fs from 'fs';
|
|
4
4
|
import * as path from 'path';
|
|
5
|
-
import { collectSessionTurns, errorMessage,
|
|
5
|
+
import { collectSessionTurns, errorMessage, readTranscriptTail, splitSentences, } from './capture.js';
|
|
6
|
+
import { isObjectLike, isStringValue } from './capture-contract.js';
|
|
6
7
|
import { PATCH_SUCCESS_LINE, patchPaths, shellPatch } from './codex-patch.js';
|
|
7
8
|
import { loadConfig } from './config.js';
|
|
8
9
|
import { createMemory, Layer } from './memory.js';
|
package/dist/shared.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Resolution: $HIPPO_HOME > $XDG_DATA_HOME/hippo > ~/.hippo/
|
|
5
5
|
* Local .hippo/ stores are per-project.
|
|
6
6
|
*/
|
|
7
|
+
import { BadRequestError, NotFoundError } from './api-errors.js';
|
|
7
8
|
import * as fs from 'fs';
|
|
8
9
|
import * as path from 'path';
|
|
9
10
|
import { generateId, COMPACTION_MEMORY_TAG } from './memory.js';
|
|
@@ -48,16 +49,16 @@ export function initGlobal() {
|
|
|
48
49
|
export function promoteToGlobal(localRoot, id, opts) {
|
|
49
50
|
const entry = readEntry(localRoot, id, opts?.tenantId);
|
|
50
51
|
if (!entry)
|
|
51
|
-
throw new
|
|
52
|
+
throw new NotFoundError(`Memory not found: ${id}`);
|
|
52
53
|
// CD5: same veto as shareMemory; a promoted copy would have no quarantine record to review.
|
|
53
54
|
if (isQuarantineScope(entry.scope)) {
|
|
54
|
-
throw new
|
|
55
|
+
throw new BadRequestError(`Refusing to promote ${id}: it is quarantined pending review. Approve it first via 'hippo quarantine approve ${id}'.`);
|
|
55
56
|
}
|
|
56
57
|
// v39 S4 producer veto: promote is a producer path to the global store
|
|
57
58
|
// exactly like shareMemory - same hard rule (codex gating review P2).
|
|
58
59
|
const promoteSecret = detectSecret(entry);
|
|
59
60
|
if (promoteSecret.flagged) {
|
|
60
|
-
throw new
|
|
61
|
+
throw new BadRequestError(`Refusing to promote ${id} to the global store: content matches secret material (${promoteSecret.reason}). ` +
|
|
61
62
|
`Secrets stay in their owning project's store.`);
|
|
62
63
|
}
|
|
63
64
|
initGlobal();
|
|
@@ -273,18 +274,18 @@ export function shareMemory(localRoot, id, options = {}) {
|
|
|
273
274
|
// when tenantId is provided.
|
|
274
275
|
const entry = readEntry(localRoot, id, options.tenantId);
|
|
275
276
|
if (!entry)
|
|
276
|
-
throw new
|
|
277
|
+
throw new NotFoundError(`Memory not found: ${id}`);
|
|
277
278
|
// v39 S4 producer veto: secrets never go to the global store, not even
|
|
278
279
|
// with --force. Explicit and loud - a silent null would read as "low
|
|
279
280
|
// transfer score" and invite retries.
|
|
280
281
|
const secret = detectSecret(entry);
|
|
281
282
|
if (secret.flagged) {
|
|
282
|
-
throw new
|
|
283
|
+
throw new BadRequestError(`Refusing to share ${id} to the global store: content matches secret material (${secret.reason}). ` +
|
|
283
284
|
`Secrets stay in their owning project's store.`);
|
|
284
285
|
}
|
|
285
286
|
// CD5: a quarantined row is unreviewed input, not a lesson; sharing it would spread poison globally.
|
|
286
287
|
if (isQuarantineScope(entry.scope)) {
|
|
287
|
-
throw new
|
|
288
|
+
throw new BadRequestError(`Refusing to share ${id}: it is quarantined pending review. Approve it first via 'hippo quarantine approve ${id}'.`);
|
|
288
289
|
}
|
|
289
290
|
const score = transferScore(entry);
|
|
290
291
|
if (score < 0.3 && !options.force)
|
package/dist/skills.js
CHANGED
|
@@ -21,8 +21,10 @@
|
|
|
21
21
|
* Lifecycle: active -> superseded (a newer version replaces it) or active ->
|
|
22
22
|
* closed (retired). Export renders ACTIVE skills only.
|
|
23
23
|
*/
|
|
24
|
+
import { BadRequestError, ConflictError, NotFoundError } from './api-errors.js';
|
|
24
25
|
import { openHippoDb, closeHippoDb } from './db.js';
|
|
25
|
-
import { writeEntry
|
|
26
|
+
import { writeEntry } from './store.js';
|
|
27
|
+
import { assertTenantId } from './tenant.js';
|
|
26
28
|
import { createMemory, Layer } from './memory.js';
|
|
27
29
|
import { appendAuditEvent } from './audit.js';
|
|
28
30
|
import { objectHalfLifeDays } from './half-life-migration.js';
|
|
@@ -48,28 +50,28 @@ export const MAX_EXPORT_SKILLS = 1000;
|
|
|
48
50
|
function validateSkillFields(skillName, instructions, trigger) {
|
|
49
51
|
const name = (skillName ?? '').trim();
|
|
50
52
|
if (name.length === 0)
|
|
51
|
-
throw new
|
|
53
|
+
throw new BadRequestError('saveSkill: skillName is required');
|
|
52
54
|
if (/[\r\n]/.test(name))
|
|
53
|
-
throw new
|
|
55
|
+
throw new BadRequestError('saveSkill: skillName must be a single line (no newlines)');
|
|
54
56
|
if (name.length > MAX_SKILL_NAME_LEN) {
|
|
55
|
-
throw new
|
|
57
|
+
throw new BadRequestError(`saveSkill: skillName exceeds the ${MAX_SKILL_NAME_LEN}-char cap`);
|
|
56
58
|
}
|
|
57
59
|
if (!instructions || instructions.trim().length === 0) {
|
|
58
|
-
throw new
|
|
60
|
+
throw new BadRequestError('saveSkill: instructions are required');
|
|
59
61
|
}
|
|
60
62
|
if (instructions.length > MAX_SKILL_INSTRUCTIONS_LEN) {
|
|
61
|
-
throw new
|
|
63
|
+
throw new BadRequestError(`saveSkill: instructions exceed the ${MAX_SKILL_INSTRUCTIONS_LEN}-char cap`);
|
|
62
64
|
}
|
|
63
65
|
let triggerVal = null;
|
|
64
66
|
if (trigger !== undefined && trigger !== null && trigger.trim().length > 0) {
|
|
65
67
|
if (trigger.length > MAX_SKILL_TRIGGER_LEN) {
|
|
66
|
-
throw new
|
|
68
|
+
throw new BadRequestError(`saveSkill: trigger exceeds the ${MAX_SKILL_TRIGGER_LEN}-char cap`);
|
|
67
69
|
}
|
|
68
70
|
// Single-line, like skill_name: a trigger is a short "when to apply" phrase,
|
|
69
71
|
// and a newline would let it forge a heading inside the export **When:** line
|
|
70
72
|
// (independent-review 2026-05-30). Reject rather than emit a multi-line trigger.
|
|
71
73
|
if (/[\r\n]/.test(trigger)) {
|
|
72
|
-
throw new
|
|
74
|
+
throw new BadRequestError('saveSkill: trigger must be a single line (no newlines)');
|
|
73
75
|
}
|
|
74
76
|
triggerVal = trigger;
|
|
75
77
|
}
|
|
@@ -147,10 +149,10 @@ export function saveSkill(hippoRoot, tenantId, opts, actor = 'cli') {
|
|
|
147
149
|
// SAFETY: row shape matches the `status, version` columns named in the SELECT below.
|
|
148
150
|
const pred = db.prepare(`SELECT status, version FROM skills WHERE id = ? AND tenant_id = ?`).get(opts.supersedesSkillId, tenantId);
|
|
149
151
|
if (!pred) {
|
|
150
|
-
throw new
|
|
152
|
+
throw new NotFoundError(`saveSkill: skill ${opts.supersedesSkillId} to supersede not found for tenant ${tenantId}`);
|
|
151
153
|
}
|
|
152
154
|
if (pred.status !== 'active') {
|
|
153
|
-
throw new
|
|
155
|
+
throw new ConflictError(`saveSkill: skill ${opts.supersedesSkillId} is not active (status='${pred.status}'); only active skills can be superseded.`);
|
|
154
156
|
}
|
|
155
157
|
version = pred.version + 1;
|
|
156
158
|
}
|
|
@@ -168,7 +170,7 @@ export function saveSkill(hippoRoot, tenantId, opts, actor = 'cli') {
|
|
|
168
170
|
WHERE id = ? AND tenant_id = ? AND status = 'active' AND id != ?
|
|
169
171
|
`).run(skillId, now, opts.supersedesSkillId, tenantId, skillId);
|
|
170
172
|
if (sup.changes === 0) {
|
|
171
|
-
throw new
|
|
173
|
+
throw new ConflictError(`saveSkill: skill ${opts.supersedesSkillId} could not be superseded (no longer active or self-reference).`);
|
|
172
174
|
}
|
|
173
175
|
appendAuditEvent(db, {
|
|
174
176
|
tenantId,
|
|
@@ -227,15 +229,15 @@ export function closeSkill(hippoRoot, tenantId, id, actor = 'cli') {
|
|
|
227
229
|
// SAFETY: row shape matches the single `status` column named in the SELECT above.
|
|
228
230
|
const existing = db.prepare(`SELECT status FROM skills WHERE id = ? AND tenant_id = ?`).get(id, tenantId);
|
|
229
231
|
if (!existing) {
|
|
230
|
-
throw new
|
|
232
|
+
throw new NotFoundError(`closeSkill: skill ${id} not found for tenant ${tenantId}`);
|
|
231
233
|
}
|
|
232
|
-
throw new
|
|
234
|
+
throw new ConflictError(`closeSkill: skill ${id} is not active (status='${existing.status}'); only active skills can be closed.`);
|
|
233
235
|
}
|
|
234
236
|
// SAFETY: row's shape matches the columns named in SKILL_COLS above.
|
|
235
237
|
const row = db.prepare(`SELECT ${SKILL_COLS} FROM skills WHERE id = ? AND tenant_id = ?`)
|
|
236
238
|
.get(id, tenantId);
|
|
237
239
|
if (!row)
|
|
238
|
-
throw new
|
|
240
|
+
throw new NotFoundError(`closeSkill: skill ${id} not found after UPDATE`);
|
|
239
241
|
appendAuditEvent(db, {
|
|
240
242
|
tenantId,
|
|
241
243
|
actor,
|
|
@@ -281,7 +283,7 @@ export function loadSkills(hippoRoot, tenantId, opts = {}) {
|
|
|
281
283
|
let rows;
|
|
282
284
|
if (opts.status) {
|
|
283
285
|
if (!VALID_SKILL_STATES.has(opts.status)) {
|
|
284
|
-
throw new
|
|
286
|
+
throw new BadRequestError(`loadSkills: status must be one of ${Array.from(VALID_SKILL_STATES).join('|')}; got ${opts.status}`);
|
|
285
287
|
}
|
|
286
288
|
// SAFETY: rows' shape matches the columns named in SKILL_COLS above.
|
|
287
289
|
rows = db.prepare(`
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type DatabaseSyncLike } from './db.js';
|
|
2
|
+
import { SessionHandoff, HandoffOutcome } from './handoff.js';
|
|
3
|
+
import { Card, CardStatus, CardRun, CardComment } from './card.js';
|
|
4
|
+
export declare function transitionCard(db: DatabaseSyncLike, tenantId: string, cardId: string, from: CardStatus[], to: CardStatus, extra?: {
|
|
5
|
+
setSql?: string;
|
|
6
|
+
whereSql?: string;
|
|
7
|
+
params?: unknown[];
|
|
8
|
+
}): number;
|
|
9
|
+
/** Creates a card; status is ready with no deps or once every dependsOn id is done, else backlog. An unknown dependsOn id throws and commits nothing. A repeated dependsOn id is recorded once. */
|
|
10
|
+
export declare function createCard(hippoRoot: string, tenantId: string, input: {
|
|
11
|
+
title: string;
|
|
12
|
+
repo?: string;
|
|
13
|
+
contract?: string;
|
|
14
|
+
budget?: number;
|
|
15
|
+
dependsOn?: string[];
|
|
16
|
+
}): Card;
|
|
17
|
+
/** Returns the card row for id, or null if it does not exist under this tenant. */
|
|
18
|
+
export declare function loadCard(hippoRoot: string, tenantId: string, id: string): Card | null;
|
|
19
|
+
/** Lists cards for this tenant, optionally filtered to one status, newest-updated first. */
|
|
20
|
+
export declare function listCards(hippoRoot: string, tenantId: string, opts?: {
|
|
21
|
+
status?: CardStatus;
|
|
22
|
+
}): Card[];
|
|
23
|
+
/** Returns this card's parent and child ids from card_deps. */
|
|
24
|
+
export declare function loadCardDeps(hippoRoot: string, tenantId: string, id: string): {
|
|
25
|
+
parents: string[];
|
|
26
|
+
children: string[];
|
|
27
|
+
};
|
|
28
|
+
/** Returns this card's run history, most recent first. */
|
|
29
|
+
export declare function loadCardRuns(hippoRoot: string, tenantId: string, id: string): CardRun[];
|
|
30
|
+
/** Returns this card's comments, most recent first. */
|
|
31
|
+
export declare function loadCardComments(hippoRoot: string, tenantId: string, id: string): CardComment[];
|
|
32
|
+
/** Read side of the card <-> handoff round trip: the newest handoff filed against this card. */
|
|
33
|
+
export declare function loadLatestHandoffForCard(hippoRoot: string, tenantId: string, cardId: string): SessionHandoff | null;
|
|
34
|
+
/** Atomic claim: WHERE status IN (ready, blocked) AND assignee_runtime IS NULL decides the race. Throws on an unknown card id; returns null for a card not ready/blocked or already claimed. Sets a CARD_LEASE_MS lease and returns the new run's id as runId. */
|
|
35
|
+
export declare function claimCard(hippoRoot: string, tenantId: string, id: string, runtime: string, sessionId?: string): (Card & {
|
|
36
|
+
runId: number;
|
|
37
|
+
}) | null;
|
|
38
|
+
/** Moves a running card's lease to CARD_LEASE_MS from now and records the heartbeat; updated_at is left alone. Throws on an unknown card id or a run id that is not a positive integer; returns null unless the card is running and runId is its live run. */
|
|
39
|
+
export declare function heartbeatCard(hippoRoot: string, tenantId: string, id: string, runId: number): Card | null;
|
|
40
|
+
/** Requires the card be running; closes the live run as blocked and files reason as a comment. Throws on an unknown card id; returns null for a card not running. When runId is given, returns null unless it is the card's live run. */
|
|
41
|
+
export declare function blockCard(hippoRoot: string, tenantId: string, id: string, reason: string, runId?: number): Card | null;
|
|
42
|
+
/** Requires the card be running; moves it to review, clearing its lease and heartbeat and keeping its live run. When runId is given, returns null unless it is the card's live run. Throws on an unknown card id; returns null for a card not running. */
|
|
43
|
+
export declare function reviewCard(hippoRoot: string, tenantId: string, id: string, runId?: number): Card | null;
|
|
44
|
+
/** Requires the card be in review; closes the live run with outcome. Outcome 'success' moves the card to done and, in the same transaction, promotes any child whose parents are now all done; 'failure' or 'partial' moves it to shelved and promotes nothing. Throws on an unknown card id; returns null for a card not in review. When runId is given, returns null unless it is the card's live run. */
|
|
45
|
+
export declare function completeCard(hippoRoot: string, tenantId: string, id: string, outcome: HandoffOutcome, runId?: number): {
|
|
46
|
+
card: Card;
|
|
47
|
+
promotedChildren: string[];
|
|
48
|
+
} | null;
|
|
49
|
+
/** Returns to ready every running card of the tenant whose lease has expired or is missing: clears its assignee, closes its live run as 'reclaimed' and leaves its handoffs alone, all in one write transaction. Returns the reclaimed card ids in id order. */
|
|
50
|
+
export declare function reclaimExpiredCards(hippoRoot: string, tenantId: string): string[];
|
|
51
|
+
/** Appends a comment to cardId in any card status; throws if cardId is not a card of this tenant. */
|
|
52
|
+
export declare function addCardComment(hippoRoot: string, tenantId: string, cardId: string, author: string, body: string): CardComment;
|
|
53
|
+
//# sourceMappingURL=store-cards.d.ts.map
|