hippo-memory 1.63.2 → 1.64.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.
- package/README.md +11 -5
- package/dist/agent-memories/claude-code.js +3 -3
- package/dist/ambient-store.d.ts +2 -0
- package/dist/ambient-store.js +4 -3
- package/dist/api/assemble.d.ts +2 -0
- package/dist/api/assemble.js +11 -7
- package/dist/api/auth.d.ts +15 -11
- package/dist/api/auth.js +116 -22
- package/dist/api/context-select.d.ts +5 -3
- package/dist/api/context-select.js +6 -11
- package/dist/api/context-types.d.ts +2 -1
- package/dist/api/context.d.ts +1 -1
- package/dist/api/context.js +79 -55
- package/dist/api/dormant.d.ts +5 -5
- package/dist/api/dormant.js +13 -9
- package/dist/api/drill-down.d.ts +2 -0
- package/dist/api/drill-down.js +12 -7
- package/dist/api/forget.d.ts +1 -1
- package/dist/api/forget.js +8 -4
- package/dist/api/learn.js +5 -3
- package/dist/api/outcome.d.ts +1 -1
- package/dist/api/outcome.js +2 -1
- package/dist/api/promote.js +8 -5
- package/dist/api/recall-types.d.ts +4 -2
- package/dist/api/recall.js +51 -36
- package/dist/api/remember.d.ts +7 -0
- package/dist/api/remember.js +26 -2
- package/dist/api/sleep-run.d.ts +28 -0
- package/dist/api/sleep-run.js +267 -0
- package/dist/api/sleep.d.ts +5 -45
- package/dist/api/sleep.js +5 -252
- package/dist/api/types.d.ts +5 -0
- package/dist/api/types.js +4 -0
- package/dist/audit-prune.js +1 -1
- package/dist/audit.d.ts +1 -3
- package/dist/audit.js +7 -103
- package/dist/auth.d.ts +48 -4
- package/dist/auth.js +111 -42
- package/dist/autolearn.js +2 -2
- package/dist/capture/caller-session.d.ts +16 -0
- package/dist/capture/caller-session.js +58 -0
- package/dist/capture/command.d.ts +19 -1
- package/dist/capture/command.js +28 -21
- package/dist/capture/compact-resume-caller.d.ts +11 -0
- package/dist/capture/compact-resume-caller.js +35 -0
- package/dist/capture/compact.d.ts +0 -14
- package/dist/capture/compact.js +7 -160
- package/dist/capture/compaction-items-caller.d.ts +16 -0
- package/dist/capture/compaction-items-caller.js +32 -0
- package/dist/capture/extract.d.ts +2 -14
- package/dist/capture/extract.js +135 -346
- package/dist/capture/failure-caller.d.ts +21 -0
- package/dist/capture/failure-caller.js +74 -0
- package/dist/capture/failure-reading.d.ts +44 -0
- package/dist/capture/failure-reading.js +78 -0
- package/dist/capture/pre-compact-caller.d.ts +16 -0
- package/dist/capture/pre-compact-caller.js +39 -0
- package/dist/capture/session-end-caller.d.ts +21 -0
- package/dist/capture/session-end-caller.js +30 -0
- package/dist/capture/session-texts.d.ts +14 -0
- package/dist/capture/session-texts.js +42 -0
- package/dist/capture/transcript.d.ts +6 -1
- package/dist/capture/transcript.js +20 -16
- package/dist/capture/working-state.d.ts +21 -0
- package/dist/capture/working-state.js +154 -0
- package/dist/capture-error.d.ts +8 -22
- package/dist/capture-error.js +9 -61
- package/dist/cli/audit.js +25 -0
- package/dist/cli/auth.js +4 -3
- package/dist/cli/context.js +13 -158
- package/dist/cli/continuity.js +2 -1
- package/dist/cli/init.js +5 -3
- package/dist/cli/last-sleep.d.ts +6 -0
- package/dist/cli/last-sleep.js +68 -0
- package/dist/cli/maintenance.d.ts +2 -1
- package/dist/cli/maintenance.js +17 -11
- package/dist/cli/quality-repair-once.d.ts +3 -0
- package/dist/cli/quality-repair-once.js +36 -0
- package/dist/cli/recall.js +2 -1
- package/dist/cli/remember.d.ts +2 -0
- package/dist/cli/remember.js +4 -1
- package/dist/cli/session-hooks.d.ts +0 -3
- package/dist/cli/session-hooks.js +12 -49
- package/dist/cli/setup.js +10 -2
- package/dist/cli/shared.d.ts +7 -14
- package/dist/cli/shared.js +19 -78
- package/dist/cli/sleep.js +6 -3
- package/dist/cli/usage.js +18 -9
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +3 -2
- package/dist/client.js +1 -1
- package/dist/compaction-record.d.ts +19 -1
- package/dist/compaction-record.js +61 -132
- package/dist/compaction-spool.d.ts +45 -0
- package/dist/compaction-spool.js +454 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.js +49 -0
- package/dist/connectors/github/deletion.d.ts +1 -1
- package/dist/connectors/github/deletion.js +5 -17
- package/dist/consolidate/conflicts.js +4 -0
- package/dist/consolidate/flush-units.d.ts +9 -0
- package/dist/consolidate/flush-units.js +63 -0
- package/dist/consolidate/llm-passes.js +5 -4
- package/dist/consolidate/merge.js +5 -1
- package/dist/consolidate/run.d.ts +2 -1
- package/dist/consolidate/sleep.d.ts +2 -0
- package/dist/consolidate/sleep.js +82 -15
- package/dist/context-render.d.ts +14 -0
- package/dist/context-render.js +28 -0
- package/dist/dag.js +6 -0
- package/dist/dashboard-actions.js +3 -2
- package/dist/dashboard.js +4 -3
- package/dist/db/backup.d.ts +6 -0
- package/dist/db/backup.js +29 -0
- package/dist/db/busy.d.ts +9 -0
- package/dist/db/busy.js +35 -0
- package/dist/db/continuity.d.ts +1 -0
- package/dist/db/continuity.js +31 -4
- package/dist/db/meta.d.ts +2 -0
- package/dist/db/meta.js +7 -0
- package/dist/db/migrate.js +2 -3
- package/dist/db/migrations/index.d.ts +1 -1
- package/dist/db/migrations/index.js +5 -1
- package/dist/db/migrations/v39.js +2 -7
- package/dist/db/migrations/v53.d.ts +3 -0
- package/dist/db/migrations/v53.js +16 -0
- package/dist/db/migrations/v54.d.ts +3 -0
- package/dist/db/migrations/v54.js +25 -0
- package/dist/db/open.d.ts +11 -0
- package/dist/db/open.js +27 -0
- package/dist/db/tables.d.ts +2 -0
- package/dist/db/tables.js +6 -2
- package/dist/db.d.ts +2 -2
- package/dist/db.js +2 -2
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +31 -15
- package/dist/dormant.d.ts +16 -20
- package/dist/dormant.js +24 -30
- package/dist/embedding-provider.js +4 -3
- package/dist/entry/json-hooks.d.ts +3 -0
- package/dist/entry/json-hooks.js +4 -0
- package/dist/entry/project-identity.d.ts +2 -0
- package/dist/entry/project-identity.js +3 -0
- package/dist/entry/session-text.d.ts +7 -0
- package/dist/entry/session-text.js +8 -0
- package/dist/extract.js +6 -0
- package/dist/failure-log.d.ts +10 -2
- package/dist/failure-log.js +19 -2
- package/dist/gated-write.js +1 -1
- package/dist/graph/delta.d.ts +44 -0
- package/dist/graph/delta.js +109 -0
- package/dist/graph/types.d.ts +5 -0
- package/dist/graph/write.d.ts +20 -20
- package/dist/graph/write.js +103 -36
- package/dist/graph-extract.d.ts +43 -9
- package/dist/graph-extract.js +140 -157
- package/dist/graph-recall.d.ts +1 -0
- package/dist/graph-recall.js +2 -2
- package/dist/graph-view.d.ts +1 -0
- package/dist/graph-view.js +25 -2
- package/dist/half-life-migration.js +5 -1
- package/dist/handoff-evidence.d.ts +5 -0
- package/dist/handoff-evidence.js +30 -0
- package/dist/home-path.d.ts +5 -0
- package/dist/home-path.js +24 -0
- package/dist/hooks/json-hooks.d.ts +13 -2
- package/dist/hooks/json-hooks.js +246 -117
- package/dist/hooks/opencode.js +1 -1
- package/dist/hooks/shared.d.ts +2 -0
- package/dist/hooks/shared.js +6 -1
- package/dist/http-util.d.ts +16 -7
- package/dist/http-util.js +65 -20
- package/dist/importers/vault.js +2 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/json.d.ts +2 -0
- package/dist/json.js +7 -0
- package/dist/ledger-db.d.ts +10 -0
- package/dist/ledger-db.js +42 -0
- package/dist/local-embedding.d.ts +2 -0
- package/dist/local-embedding.js +20 -4
- package/dist/mcp/admin-tools.d.ts +2 -2
- package/dist/mcp/admin-tools.js +47 -5
- package/dist/mcp/memory-tools.js +7 -2
- package/dist/mcp/protocol.d.ts +6 -0
- package/dist/mcp/protocol.js +4 -2
- package/dist/mcp/recall-tools.js +19 -6
- package/dist/mcp/request.js +4 -0
- package/dist/mcp/session-state.d.ts +4 -0
- package/dist/mcp/session-state.js +15 -6
- package/dist/mcp/shared-gate.d.ts +4 -0
- package/dist/mcp/shared-gate.js +22 -0
- package/dist/mcp/tools.js +1 -0
- package/dist/memory-quality.d.ts +35 -0
- package/dist/memory-quality.js +191 -0
- package/dist/memory.d.ts +4 -4
- package/dist/physics-state.js +1 -1
- package/dist/pilot-arm.d.ts +14 -3
- package/dist/pilot-arm.js +32 -10
- package/dist/postinstall.js +2 -2
- package/dist/project-briefs.js +4 -4
- package/dist/project-identity.d.ts +14 -10
- package/dist/project-identity.js +29 -11
- package/dist/project-merge.d.ts +0 -2
- package/dist/project-merge.js +9 -13
- package/dist/prompt-hook.d.ts +65 -0
- package/dist/prompt-hook.js +213 -0
- package/dist/prompt-recall.js +1 -1
- package/dist/quality-repair.d.ts +27 -0
- package/dist/quality-repair.js +220 -0
- package/dist/rate-limit.d.ts +1 -0
- package/dist/rate-limit.js +5 -3
- package/dist/raw-archive.d.ts +1 -1
- package/dist/raw-archive.js +6 -16
- package/dist/recall-scope.d.ts +28 -5
- package/dist/recall-scope.js +52 -7
- package/dist/recall-trace.js +1 -1
- package/dist/reject-flow.d.ts +2 -0
- package/dist/reject-flow.js +20 -6
- package/dist/secret-detect.js +7 -8
- package/dist/server/auth.d.ts +8 -6
- package/dist/server/auth.js +85 -38
- package/dist/server/client-ip.d.ts +2 -0
- package/dist/server/client-ip.js +16 -1
- package/dist/server/mcp-http.d.ts +9 -3
- package/dist/server/mcp-http.js +56 -13
- package/dist/server/request.d.ts +1 -1
- package/dist/server/request.js +5 -5
- package/dist/server/routes/admin.js +12 -6
- package/dist/server/routes/memories.js +9 -1
- package/dist/server/routes/recall.js +14 -2
- package/dist/server/types.d.ts +42 -5
- package/dist/server/validation.d.ts +5 -2
- package/dist/server/validation.js +19 -2
- package/dist/server.d.ts +21 -4
- package/dist/server.js +207 -49
- package/dist/session-digest.d.ts +0 -1
- package/dist/session-digest.js +3 -12
- package/dist/session-owners.d.ts +7 -0
- package/dist/session-owners.js +54 -0
- package/dist/share-scrub.d.ts +3 -0
- package/dist/share-scrub.js +8 -0
- package/dist/shared.d.ts +1 -0
- package/dist/shared.js +26 -18
- package/dist/store/candidates.d.ts +14 -3
- package/dist/store/candidates.js +48 -26
- package/dist/store/conflicts.d.ts +15 -0
- package/dist/store/conflicts.js +68 -44
- package/dist/store/delete-and-batch.d.ts +25 -1
- package/dist/store/delete-and-batch.js +121 -69
- package/dist/store/entry-reads.d.ts +5 -6
- package/dist/store/entry-reads.js +24 -12
- package/dist/store/entry-row.d.ts +7 -10
- package/dist/store/entry-row.js +40 -18
- package/dist/store/entry-writes.d.ts +3 -3
- package/dist/store/entry-writes.js +9 -23
- package/dist/store/handoffs.d.ts +9 -6
- package/dist/store/handoffs.js +72 -59
- package/dist/store/index-and-stats.js +2 -2
- package/dist/store/open.d.ts +3 -1
- package/dist/store/open.js +3 -3
- package/dist/store/search-rows.d.ts +8 -5
- package/dist/store/search-rows.js +15 -18
- package/dist/store/sessions.d.ts +25 -6
- package/dist/store/sessions.js +61 -19
- package/dist/store/summaries.d.ts +1 -1
- package/dist/store/summaries.js +3 -18
- package/dist/store/tenant-lookup.d.ts +7 -3
- package/dist/store/tenant-lookup.js +9 -9
- package/dist/store-port.d.ts +13 -0
- package/dist/store-port.js +20 -0
- package/dist/version.d.ts +5 -1
- package/dist/version.js +5 -1
- package/dist/working-memory.js +1 -1
- package/dist/write-budget.d.ts +15 -0
- package/dist/write-budget.js +15 -0
- 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 +8 -4
package/dist/capture-error.js
CHANGED
|
@@ -7,63 +7,7 @@ import { loadContentsWithTag } from './store/entry-reads.js';
|
|
|
7
7
|
import { loadConfig } from './config.js';
|
|
8
8
|
import { closeHippoDb, openHippoDb } from './db.js';
|
|
9
9
|
import { recordFailure } from './failure-log.js';
|
|
10
|
-
import {
|
|
11
|
-
import { redactSecretsStrict } from './secret-detect.js';
|
|
12
|
-
const MAX_LEN = 200;
|
|
13
|
-
/** The user, a permission prompt or a hook said no: routine, not a lesson. */
|
|
14
|
-
const DECLINED = /user (?:doesn't|does not) want|denied by (?:the )?user|user (?:rejected|declined|denied)|permission to use|was blocked by (?:a )?hook/i;
|
|
15
|
-
/** The OS or a remote host refused access (EACCES, SSH publickey): routine too, but nobody declined anything. */
|
|
16
|
-
const OS_PERMISSION = /permission denied/i;
|
|
17
|
-
/** Leading `cd` or `pushd` steps say where a command ran, not what it ran. */
|
|
18
|
-
const LEADING_CD = /^\s*(?:(?:cd|pushd)\b[^;&|]*(?:&&|\|\||;)\s*)+/;
|
|
19
|
-
const NO_MATCH = /\bno (?:matches|files|results) found\b/i;
|
|
20
|
-
/** Shell commands whose exit code 1 means "nothing found" or "differs", not an error. */
|
|
21
|
-
const QUIET_EXIT_1 = /^\s*(?:grep|rg|egrep|fgrep|find|test|\[|diff|cmp|git diff|git grep)\b/;
|
|
22
|
-
function isString(v) {
|
|
23
|
-
return v !== undefined && v !== null && v.constructor === String;
|
|
24
|
-
}
|
|
25
|
-
function isObject(v) {
|
|
26
|
-
return v !== undefined && v !== null && !Array.isArray(v) && v.constructor === Object;
|
|
27
|
-
}
|
|
28
|
-
function payloadString(payload, key) {
|
|
29
|
-
if (!isObject(payload))
|
|
30
|
-
return null;
|
|
31
|
-
const value = payload[key];
|
|
32
|
-
return isString(value) && value.trim() !== '' ? value : null;
|
|
33
|
-
}
|
|
34
|
-
/** Normalised form used to spot repeats. The failure log keeps only its hash, so changing it breaks repeat counts. */
|
|
35
|
-
export function failureSignature(text) {
|
|
36
|
-
return text.toLowerCase().replace(/[0-9a-f]{7,}/g, '#').replace(/\d+/g, '#').replace(/\s+/g, ' ').trim();
|
|
37
|
-
}
|
|
38
|
-
/** The memory text for a failure payload, or why it is not stored; a routine skip keeps its text for the log. Pure. */
|
|
39
|
-
export function lessonFromFailure(payload) {
|
|
40
|
-
if (!isObject(payload))
|
|
41
|
-
return { skip: 'skipped-invalid', text: null, detail: null };
|
|
42
|
-
// SAFETY: isObject narrowed payload to a plain JSON object; the fields read are all optional.
|
|
43
|
-
const p = payload;
|
|
44
|
-
if (p.is_interrupt === true)
|
|
45
|
-
return { skip: 'skipped-interrupt', text: null, detail: null };
|
|
46
|
-
if (!isString(p.error) || p.error.trim().length < 12)
|
|
47
|
-
return { skip: 'skipped-invalid', text: null, detail: null };
|
|
48
|
-
const tool = isString(p.tool_name) ? p.tool_name : 'tool';
|
|
49
|
-
const error = redactSecretsStrict(p.error.replace(/\s+/g, ' ').trim());
|
|
50
|
-
const text = `${tool}: ${error}`.slice(0, MAX_LEN);
|
|
51
|
-
const command = isObject(p.tool_input) && isString(p.tool_input['command']) ? p.tool_input['command'].replace(LEADING_CD, '') : '';
|
|
52
|
-
const head = command.trim().split(/\s+/).slice(0, 2).join(' ');
|
|
53
|
-
const detail = `${tool}${head ? ` ${head}` : ''}: ${error}`;
|
|
54
|
-
const routine = (rule) => ({ skip: 'skipped-routine', rule, text, detail });
|
|
55
|
-
if (DECLINED.test(error))
|
|
56
|
-
return routine('declined');
|
|
57
|
-
if (OS_PERMISSION.test(error))
|
|
58
|
-
return routine('os-permission');
|
|
59
|
-
if (NO_MATCH.test(error))
|
|
60
|
-
return routine('no-match');
|
|
61
|
-
if (tool === 'Grep' || tool === 'Glob')
|
|
62
|
-
return routine('search-tool');
|
|
63
|
-
if (tool === 'Bash' && QUIET_EXIT_1.test(command) && /exit code 1\b/i.test(error))
|
|
64
|
-
return routine('quiet-exit');
|
|
65
|
-
return { text, detail };
|
|
66
|
-
}
|
|
10
|
+
import { failureHash, failureSignature, lessonFromFailure, payloadString, } from './capture/failure-reading.js';
|
|
67
11
|
/** Store a failure as an error memory unless it is routine or a repeat, and log it either way, even when storing throws. */
|
|
68
12
|
export function captureToolFailure(hippoRoot, tenantId, payload) {
|
|
69
13
|
const lesson = lessonFromFailure(payload);
|
|
@@ -77,7 +21,7 @@ export function captureToolFailure(hippoRoot, tenantId, payload) {
|
|
|
77
21
|
}
|
|
78
22
|
}
|
|
79
23
|
function logFailure(hippoRoot, tenantId, payload, lesson, outcome) {
|
|
80
|
-
const hash = (s) => (s === null ? null :
|
|
24
|
+
const hash = (s) => (s === null ? null : failureHash(s));
|
|
81
25
|
const db = openHippoDb(hippoRoot);
|
|
82
26
|
try {
|
|
83
27
|
recordFailure(db, {
|
|
@@ -94,9 +38,9 @@ function logFailure(hippoRoot, tenantId, payload, lesson, outcome) {
|
|
|
94
38
|
closeHippoDb(db);
|
|
95
39
|
}
|
|
96
40
|
}
|
|
97
|
-
function storeLesson(hippoRoot, tenantId, text) {
|
|
41
|
+
export function storeLesson(hippoRoot, tenantId, text, caller) {
|
|
98
42
|
const sig = failureSignature(text);
|
|
99
|
-
const repeat = loadContentsWithTag(hippoRoot, tenantId, 'auto-captured').some((content) => failureSignature(content) === sig);
|
|
43
|
+
const repeat = loadContentsWithTag(hippoRoot, tenantId, 'auto-captured', caller?.origins).some((content) => failureSignature(content) === sig);
|
|
100
44
|
if (repeat)
|
|
101
45
|
return 'duplicate';
|
|
102
46
|
const entry = createMemory(text, {
|
|
@@ -106,7 +50,11 @@ function storeLesson(hippoRoot, tenantId, text) {
|
|
|
106
50
|
tenantId,
|
|
107
51
|
baseHalfLifeDays: loadConfig(hippoRoot).defaultHalfLifeDays,
|
|
108
52
|
});
|
|
109
|
-
|
|
53
|
+
// A caller's project is its checked one; the store's own folder would name the server.
|
|
54
|
+
if (caller === undefined)
|
|
55
|
+
writeEntry(hippoRoot, entry);
|
|
56
|
+
else
|
|
57
|
+
writeEntry(hippoRoot, { ...entry, origin_project: caller.originProject }, { actor: caller.actor });
|
|
110
58
|
return 'stored';
|
|
111
59
|
}
|
|
112
60
|
//# sourceMappingURL=capture-error.js.map
|
package/dist/cli/audit.js
CHANGED
|
@@ -8,6 +8,8 @@ import { resolveTenantId } from '../tenant.js';
|
|
|
8
8
|
import { pruneAuditLog, parseOlderThanFlag } from '../audit-prune.js';
|
|
9
9
|
import { printError } from './output.js';
|
|
10
10
|
import { requireInit, resolveAuthRoot } from './shared.js';
|
|
11
|
+
import { repairAutomaticMemories } from '../quality-repair.js';
|
|
12
|
+
import { getGlobalRoot } from '../shared.js';
|
|
11
13
|
// ---------------------------------------------------------------------------
|
|
12
14
|
// Audit log subcommands (`hippo audit list`)
|
|
13
15
|
// ---------------------------------------------------------------------------
|
|
@@ -114,6 +116,29 @@ function cmdAuditLog(hippoRoot, args, flags) {
|
|
|
114
116
|
process.exit(1);
|
|
115
117
|
}
|
|
116
118
|
export function handleAudit({ hippoRoot, args, flags }) {
|
|
119
|
+
if (args[0] === 'repair') {
|
|
120
|
+
const apply = flags['apply'] === true && flags['dry-run'] !== true;
|
|
121
|
+
const result = repairAutomaticMemories(flags['global'] ? getGlobalRoot() : hippoRoot, { tenantId: resolveTenantId({}), apply });
|
|
122
|
+
if (flags['json']) {
|
|
123
|
+
console.log(JSON.stringify(result));
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
console.log(`Quality repair ${apply ? 'apply' : 'preview'}: ${result.issues.length} issue(s) across ${result.total} memories.`);
|
|
127
|
+
for (const issue of result.issues)
|
|
128
|
+
console.log(` [${issue.disposition}] ${issue.id}: ${issue.reason}${issue.protection ? ` (${issue.protection})` : ''}`);
|
|
129
|
+
for (const blocker of result.blockers)
|
|
130
|
+
console.log(` Blocked: ${blocker}. Repair never upgrades a store; any other hippo command does, then run repair again.`);
|
|
131
|
+
const restore = `hippo dormant restore <id>${flags['global'] ? ' --global' : ''}`;
|
|
132
|
+
if (result.backup)
|
|
133
|
+
console.log(`Backup: ${result.backup}\nMoved ${result.appliedIds.length} memories to dormant storage. Recovery: ${restore}.`);
|
|
134
|
+
else if (apply && result.supported)
|
|
135
|
+
console.log('Nothing moved: no unprotected memory has a certain defect.');
|
|
136
|
+
for (const warning of result.warnings)
|
|
137
|
+
console.log(`Warning: ${warning}`);
|
|
138
|
+
if (!apply)
|
|
139
|
+
console.log('Preview only. Add --apply to move set-aside memories to dormant storage. Pin a review memory to keep it.');
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
117
142
|
// `audit list` and `audit prune` -> audit-log subcommands.
|
|
118
143
|
// Other forms (no sub, --fix) keep the existing memory-quality auditor
|
|
119
144
|
// for backwards compatibility.
|
package/dist/cli/auth.js
CHANGED
|
@@ -54,8 +54,9 @@ function cmdAuthCreate(hippoRoot, flags) {
|
|
|
54
54
|
function formatKeyRow(item) {
|
|
55
55
|
const label = item.label ?? '-';
|
|
56
56
|
const created = item.createdAt;
|
|
57
|
+
const expires = item.expiresAt ?? '-';
|
|
57
58
|
const revoked = item.revokedAt ?? '-';
|
|
58
|
-
return `${item.keyId} ${item.tenantId} ${item.role} ${label} ${created} ${revoked}`;
|
|
59
|
+
return `${item.keyId} ${item.tenantId} ${item.role} ${label} ${created} ${expires} ${revoked}`;
|
|
59
60
|
}
|
|
60
61
|
function cmdAuthList(hippoRoot, flags) {
|
|
61
62
|
const root = resolveAuthRoot(hippoRoot, flags);
|
|
@@ -74,10 +75,10 @@ function cmdAuthList(hippoRoot, flags) {
|
|
|
74
75
|
return;
|
|
75
76
|
}
|
|
76
77
|
if (items.length === 0) {
|
|
77
|
-
console.log(includeRevoked ? 'No API keys.' : 'No active API keys. (Use --all to include revoked.)');
|
|
78
|
+
console.log(includeRevoked ? 'No API keys.' : 'No active API keys. (Use --all to include revoked and expired.)');
|
|
78
79
|
return;
|
|
79
80
|
}
|
|
80
|
-
console.log('key_id tenant role label created revoked');
|
|
81
|
+
console.log('key_id tenant role label created expires revoked');
|
|
81
82
|
for (const item of items) {
|
|
82
83
|
console.log(formatKeyRow(item));
|
|
83
84
|
}
|
package/dist/cli/context.js
CHANGED
|
@@ -1,23 +1,20 @@
|
|
|
1
1
|
// The `hippo context` verb, which the per-prompt hook also runs; main() loads it lazily from the command table.
|
|
2
|
-
import { evalNow } from '../ablation.js';
|
|
3
2
|
import * as path from 'path';
|
|
4
|
-
import { isInitialized } from '../store/open.js';
|
|
5
|
-
import { writeDeliveryEventAtRoot, writeDeliveryEventOnHandle } from '../recall-trace.js';
|
|
6
3
|
import { createDeliveryRecorder } from '../delivery-recorder.js';
|
|
7
4
|
import { loadConfig } from '../config.js';
|
|
8
|
-
import {
|
|
9
|
-
import { blockHash, estimateTokens, isSubagentPayload, lastSentState, recordTokenUse, shouldSkipUnchanged, } from '../token-ledger.js';
|
|
5
|
+
import { blockHash, estimateTokens, isSubagentPayload, recordTokenUse } from '../token-ledger.js';
|
|
10
6
|
import { isGlobalStoreRoot } from '../project-identity.js';
|
|
11
7
|
import { autoDetectContext } from '../context-auto.js';
|
|
12
8
|
import { detectScope } from '../scope.js';
|
|
13
|
-
import {
|
|
9
|
+
import { ledgerRoot, withLedgerDb } from '../ledger-db.js';
|
|
14
10
|
import { readStdinBounded } from '../stdin.js';
|
|
15
11
|
import * as api from '../api.js';
|
|
16
12
|
import { resolveTenantId } from '../tenant.js';
|
|
17
13
|
import { renderAmbientSummary } from '../ambient.js';
|
|
18
|
-
import {
|
|
14
|
+
import { contextBlockLines, contextCost, crossProjectLines, settleTokens } from '../context-render.js';
|
|
15
|
+
import { additionalContextOutput, flushDeliveryRecorder, hasContextData, toRenderItems, } from '../prompt-hook.js';
|
|
19
16
|
import { printError } from './output.js';
|
|
20
|
-
import { parseLimitFlag, parseCountFlag, parseBudgetFlag, requireInit, printActiveTaskSnapshot, printSessionEvents, printHandoff, hostSessionId, captureConsole, hookStoreRoot,
|
|
17
|
+
import { parseLimitFlag, parseCountFlag, parseBudgetFlag, requireInit, printActiveTaskSnapshot, printSessionEvents, printHandoff, hostSessionId, captureConsole, hookStoreRoot, runHookWithStores, inPilotHoldout, } from './shared.js';
|
|
21
18
|
export async function cmdContext(hippoRoot, args, flags, stdinText) {
|
|
22
19
|
const rec = startDeliveryRecorder(hippoRoot, flags, stdinText);
|
|
23
20
|
// No try/finally: a render throw keeps its own exit code and writes no event.
|
|
@@ -30,7 +27,7 @@ function startDeliveryRecorder(hippoRoot, flags, stdinText) {
|
|
|
30
27
|
return null;
|
|
31
28
|
try {
|
|
32
29
|
// The same store withLedgerDb writes the token ledger to, so its config governs both.
|
|
33
|
-
const root =
|
|
30
|
+
const root = ledgerRoot(hippoRoot);
|
|
34
31
|
if (root === null || !loadConfig(root).deliveryLedger.enabled)
|
|
35
32
|
return null;
|
|
36
33
|
return createDeliveryRecorder({
|
|
@@ -48,18 +45,6 @@ function startDeliveryRecorder(hippoRoot, flags, stdinText) {
|
|
|
48
45
|
return null;
|
|
49
46
|
}
|
|
50
47
|
}
|
|
51
|
-
/** With `db`, writes on the token ledger's handle (same store); without it, opens its own. A second flush is a no-op. */
|
|
52
|
-
function flushDeliveryRecorder(rec, db) {
|
|
53
|
-
if (rec === null)
|
|
54
|
-
return;
|
|
55
|
-
try {
|
|
56
|
-
rec.flush((input) => (db ? writeDeliveryEventOnHandle(db, input) : writeDeliveryEventAtRoot(rec.root, input)));
|
|
57
|
-
}
|
|
58
|
-
catch (error) {
|
|
59
|
-
// Pinned stderr text, as in the recorder build above.
|
|
60
|
-
printError(`[hippo] delivery ledger write failed:${error instanceof Error ? error.message : String(error)}`);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
48
|
/** The hook payload's session id and raw prompt; a malformed or empty payload yields neither. */
|
|
64
49
|
function readHookPayload(stdinText) {
|
|
65
50
|
let sessionId;
|
|
@@ -122,7 +107,9 @@ async function renderContext(hippoRoot, args, flags, stdinText, rec) {
|
|
|
122
107
|
renderContextJson(view, query);
|
|
123
108
|
}
|
|
124
109
|
else if (format === 'additional-context') {
|
|
125
|
-
|
|
110
|
+
const stdout = additionalContextOutput(view);
|
|
111
|
+
if (stdout)
|
|
112
|
+
process.stdout.write(stdout);
|
|
126
113
|
}
|
|
127
114
|
else {
|
|
128
115
|
renderContextMarkdown(view);
|
|
@@ -162,15 +149,6 @@ function buildContextOpts(flags, input) {
|
|
|
162
149
|
deliveryObserver: input.rec ?? undefined,
|
|
163
150
|
};
|
|
164
151
|
}
|
|
165
|
-
function hasContextData(result) {
|
|
166
|
-
return Boolean(result.entries.length > 0 ||
|
|
167
|
-
result.activeSnapshot ||
|
|
168
|
-
result.sessionHandoff ||
|
|
169
|
-
(result.recentEvents && result.recentEvents.length > 0));
|
|
170
|
-
}
|
|
171
|
-
function toRenderItems(entries) {
|
|
172
|
-
return entries.map((r) => ({ entry: r.entry, score: r.score, tokens: r.tokens, isGlobal: r.isGlobal ?? false }));
|
|
173
|
-
}
|
|
174
152
|
function renderContextJson(view, query) {
|
|
175
153
|
const { result, rec } = view;
|
|
176
154
|
const output = result.entries.map((r) => ({
|
|
@@ -202,122 +180,6 @@ function renderContextJson(view, query) {
|
|
|
202
180
|
flushDeliveryRecorder(rec, db);
|
|
203
181
|
});
|
|
204
182
|
}
|
|
205
|
-
/** The hook format: a skippable static block (snapshot, handoff, events, pins, recent-N) and a never-skipped recall block. */
|
|
206
|
-
function renderAdditionalContext(view) {
|
|
207
|
-
const { result, rec, framing } = view;
|
|
208
|
-
const staticEntries = result.entries.filter((r) => r.category !== 'cross-project' && !r.promptRecall);
|
|
209
|
-
const staticCrossEntries = result.entries.filter((r) => r.category === 'cross-project' && !r.promptRecall);
|
|
210
|
-
const staticItems = toRenderItems(staticEntries);
|
|
211
|
-
const recallItems = toRenderItems(result.entries.filter((r) => r.promptRecall));
|
|
212
|
-
const staticBlock = settleTokens((t) => captureConsole(() => {
|
|
213
|
-
if (result.activeSnapshot)
|
|
214
|
-
printActiveTaskSnapshot(result.activeSnapshot);
|
|
215
|
-
if (result.sessionHandoff)
|
|
216
|
-
printHandoff(result.sessionHandoff);
|
|
217
|
-
if (result.recentEvents && result.recentEvents.length > 0) {
|
|
218
|
-
printSessionEvents(result.recentEvents);
|
|
219
|
-
}
|
|
220
|
-
// No live strength percentage, so an unchanged set of memories renders byte-identically turn after turn.
|
|
221
|
-
if (staticItems.length > 0)
|
|
222
|
-
printContextMarkdown(staticItems, t, framing, { showStrength: false });
|
|
223
|
-
printCrossProjectSection(staticCrossEntries);
|
|
224
|
-
}));
|
|
225
|
-
const recallBlock = recallItems.length > 0
|
|
226
|
-
? settleTokens((t) => captureConsole(() => printContextMarkdown(recallItems, t, framing, { showStrength: false, heading: 'Prompt-Relevant Memory' })))
|
|
227
|
-
: '';
|
|
228
|
-
if (!staticBlock.trim() && !recallBlock.trim()) {
|
|
229
|
-
rec?.delivered({ state: 'empty' });
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
const surface = view.pinnedOnly ? 'hook' : 'context';
|
|
233
|
-
const sendStatic = staticBlock.trim().length > 0 && !skipUnchangedStatic(view, surface, staticBlock, recallBlock, staticItems.length);
|
|
234
|
-
const finalStatic = sendStatic ? staticBlock : '';
|
|
235
|
-
const additionalContext = finalStatic && recallBlock
|
|
236
|
-
? `${finalStatic}\n\n${recallBlock}`
|
|
237
|
-
: finalStatic || recallBlock;
|
|
238
|
-
const staticReused = !sendStatic && staticBlock.trim().length > 0;
|
|
239
|
-
if (!additionalContext.trim()) {
|
|
240
|
-
rec?.delivered({ state: 'reused', staticHash: blockHash(staticBlock), staticReused });
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
const payload = {
|
|
244
|
-
hookSpecificOutput: {
|
|
245
|
-
hookEventName: 'UserPromptSubmit',
|
|
246
|
-
additionalContext,
|
|
247
|
-
},
|
|
248
|
-
};
|
|
249
|
-
process.stdout.write(JSON.stringify(payload));
|
|
250
|
-
rec?.delivered({
|
|
251
|
-
state: staticReused ? 'reused-recall-sent' : 'sent',
|
|
252
|
-
staticHash: staticBlock.trim() ? blockHash(staticBlock) : null,
|
|
253
|
-
recallHash: recallBlock ? blockHash(recallBlock) : null,
|
|
254
|
-
emittedText: additionalContext,
|
|
255
|
-
staticReused,
|
|
256
|
-
});
|
|
257
|
-
if (finalStatic || recallBlock) {
|
|
258
|
-
recordAdditionalContextRows(view, surface, { text: finalStatic, items: staticItems.length }, { text: recallBlock, items: recallItems.length });
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
/** True, after booking the skip row, when the hook may omit a static block this session already holds. */
|
|
262
|
-
function skipUnchangedStatic(view, surface, staticBlock, recallBlock, staticCount) {
|
|
263
|
-
const { hippoRoot, payloadSessionId, rec } = view;
|
|
264
|
-
if (!view.pinnedOnly || payloadSessionId === undefined)
|
|
265
|
-
return false;
|
|
266
|
-
const injectCfg = loadConfig(hippoRoot).pinnedInject;
|
|
267
|
-
if (injectCfg.skipUnchanged === false)
|
|
268
|
-
return false;
|
|
269
|
-
const refreshTurns = Number.isFinite(injectCfg.refreshTurns) && injectCfg.refreshTurns >= 0
|
|
270
|
-
? injectCfg.refreshTurns
|
|
271
|
-
: 10;
|
|
272
|
-
// Hashed on the static text alone so an unchanged pin set still skips while recall varies.
|
|
273
|
-
const staticHash = blockHash(staticBlock);
|
|
274
|
-
const last = withLedgerDb(hippoRoot, (db) => lastSentState(db, view.tenantId, payloadSessionId, surface));
|
|
275
|
-
if (!shouldSkipUnchanged(last ?? null, staticHash, refreshTurns))
|
|
276
|
-
return false;
|
|
277
|
-
withLedgerDb(hippoRoot, (db) => {
|
|
278
|
-
recordTokenUse(db, {
|
|
279
|
-
tenantId: view.tenantId, sessionId: payloadSessionId, surface, event: 'skip',
|
|
280
|
-
items: staticCount, tokens: estimateTokens(staticBlock), hash: staticHash,
|
|
281
|
-
});
|
|
282
|
-
if (recallBlock.trim())
|
|
283
|
-
return;
|
|
284
|
-
rec?.delivered({ state: 'reused', staticHash, staticReused: true });
|
|
285
|
-
flushDeliveryRecorder(rec, db);
|
|
286
|
-
});
|
|
287
|
-
return true;
|
|
288
|
-
}
|
|
289
|
-
/** One connection for both rows; each insert in its own try so one failing doesn't skip the other. */
|
|
290
|
-
function recordAdditionalContextRows(view, surface, staticPart, recallPart) {
|
|
291
|
-
withLedgerDb(view.hippoRoot, (db) => {
|
|
292
|
-
if (staticPart.text) {
|
|
293
|
-
try {
|
|
294
|
-
recordTokenUse(db, {
|
|
295
|
-
tenantId: view.tenantId, sessionId: view.ledgerSessionId, surface, event: 'inject',
|
|
296
|
-
items: staticPart.items, tokens: estimateTokens(staticPart.text), hash: blockHash(staticPart.text),
|
|
297
|
-
});
|
|
298
|
-
// Best-effort row: only a busy store is actionable, and a ledger failure must not break the hook.
|
|
299
|
-
}
|
|
300
|
-
catch (error) {
|
|
301
|
-
if (isSqliteBusy(error))
|
|
302
|
-
noteStoreBusy('token ledger row skipped');
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
if (recallPart.text) {
|
|
306
|
-
try {
|
|
307
|
-
recordTokenUse(db, {
|
|
308
|
-
tenantId: view.tenantId, sessionId: view.ledgerSessionId, surface: 'hook_recall', event: 'inject',
|
|
309
|
-
items: recallPart.items, tokens: estimateTokens(recallPart.text), hash: blockHash(recallPart.text),
|
|
310
|
-
});
|
|
311
|
-
// Same best-effort rule as the inject row above.
|
|
312
|
-
}
|
|
313
|
-
catch (error) {
|
|
314
|
-
if (isSqliteBusy(error))
|
|
315
|
-
noteStoreBusy('token ledger row skipped');
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
flushDeliveryRecorder(view.rec, db);
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
183
|
/** The default format; the header figure counts the whole block, sections included, as the ledger does. */
|
|
322
184
|
function renderContextMarkdown(view) {
|
|
323
185
|
const { result, rec, framing } = view;
|
|
@@ -352,21 +214,14 @@ function renderContextMarkdown(view) {
|
|
|
352
214
|
flushDeliveryRecorder(rec, db);
|
|
353
215
|
});
|
|
354
216
|
}
|
|
355
|
-
/** An explicit header lets agents and humans tell borrowed context from project memory. */
|
|
356
217
|
function printCrossProjectSection(items) {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
console.log(crossProjectHeading(items.length));
|
|
360
|
-
for (const item of items)
|
|
361
|
-
console.log(crossProjectLine(item));
|
|
218
|
+
for (const line of crossProjectLines(items))
|
|
219
|
+
console.log(line);
|
|
362
220
|
}
|
|
363
221
|
/** @internal Exported for the render snapshot test; not a stable public API. */
|
|
364
222
|
export function printContextMarkdown(items, totalTokens, framing = 'observe', opts = {}) {
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
console.log(contextHeading(opts.heading ?? 'Project Memory', items.length, totalTokens));
|
|
368
|
-
for (const item of items)
|
|
369
|
-
console.log(contextLine(item, framing, showStrength, now));
|
|
223
|
+
for (const line of contextBlockLines(items, totalTokens, framing, opts))
|
|
224
|
+
console.log(line);
|
|
370
225
|
}
|
|
371
226
|
export async function handleContext({ hippoRoot, args, flags }) {
|
|
372
227
|
// Bounded, not a TTY guard: the hot stdin path and a manual run share this one command.
|
package/dist/cli/continuity.js
CHANGED
|
@@ -4,8 +4,9 @@ import { saveSessionHandoff, loadLatestHandoff, loadHandoffById, stampHandoffOut
|
|
|
4
4
|
import { isHandoffOutcome, formatHandoffEvidenceLine } from '../handoff.js';
|
|
5
5
|
import { resolveTenantId } from '../tenant.js';
|
|
6
6
|
import { wmPush, wmRead, wmClear, wmFlush } from '../working-memory.js';
|
|
7
|
+
import { collectHandoffEvidence } from '../handoff-evidence.js';
|
|
7
8
|
import { printError } from './output.js';
|
|
8
|
-
import { requireInit,
|
|
9
|
+
import { requireInit, printActiveTaskSnapshot, printSessionEvents, printHandoff, } from './shared.js';
|
|
9
10
|
export function cmdSnapshot(hippoRoot, args, flags) {
|
|
10
11
|
requireInit(hippoRoot);
|
|
11
12
|
const subcommand = args[0] ?? 'show';
|
package/dist/cli/init.js
CHANGED
|
@@ -14,7 +14,7 @@ import { currentMachine, importForStore, importProjectMemories, importUserMemori
|
|
|
14
14
|
import { emptyReport, mergeReports } from '../agent-memories/report.js';
|
|
15
15
|
import { getGlobalRoot, initGlobal } from '../shared.js';
|
|
16
16
|
import { registerWorkspace } from '../scheduler.js';
|
|
17
|
-
import { printAgentImport, installCodexMemoryHooks, setupDailySchedule, learnFromRepo } from './shared.js';
|
|
17
|
+
import { printAgentImport, installCodexMemoryHooks, setupDailySchedule, learnFromRepo, skipLearnOnSharedStore, warnClaudeSettingsUnusable } from './shared.js';
|
|
18
18
|
import { HOOK_MARKERS, HOOKS, hippoBlock } from './hook-blocks.js';
|
|
19
19
|
function scanForGitRepos(rootDir, maxDepth = 2) {
|
|
20
20
|
const repos = [];
|
|
@@ -150,8 +150,9 @@ export function cmdInit(hippoRoot, flags) {
|
|
|
150
150
|
if (!flags['no-schedule'] && !flags['global']) {
|
|
151
151
|
setupDailySchedule(globalRoot);
|
|
152
152
|
}
|
|
153
|
+
const learn = !flags['no-learn'] && !skipLearnOnSharedStore(hippoRoot);
|
|
153
154
|
// Seed with git history on first init (unless --no-learn)
|
|
154
|
-
if (!alreadyExists &&
|
|
155
|
+
if (!alreadyExists && learn && !flags['global']) {
|
|
155
156
|
if (isGitRepo(process.cwd())) {
|
|
156
157
|
const seedDays = 30;
|
|
157
158
|
console.log(`\n Seeding memories from last ${seedDays} days of git history...`);
|
|
@@ -165,7 +166,7 @@ export function cmdInit(hippoRoot, flags) {
|
|
|
165
166
|
}
|
|
166
167
|
}
|
|
167
168
|
// Every run, not only the first: an agent's notes change between inits.
|
|
168
|
-
if (
|
|
169
|
+
if (learn)
|
|
169
170
|
printAgentImport(importForStore(hippoRoot, { machine: currentMachine() }));
|
|
170
171
|
}
|
|
171
172
|
/** Every write init makes into agent config (instruction blocks, hooks, plugins) is an automatic integration, so one switch skips them all. */
|
|
@@ -254,6 +255,7 @@ function installUserLevelHooks(agents, codexHint) {
|
|
|
254
255
|
// claude-code` and `hippo setup`.
|
|
255
256
|
if (hook === 'claude-code') {
|
|
256
257
|
const result = installJsonHooks(hook);
|
|
258
|
+
warnClaudeSettingsUnusable(result, ' ');
|
|
257
259
|
if (result.installedSessionEnd) {
|
|
258
260
|
console.log(` Auto-installed hippo session-end SessionEnd hook in ${hook} settings`);
|
|
259
261
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** The line the user sees when the last sleep failed, a spool step hit an unexpected error, or the spool holds `.bad` files; null when none did. */
|
|
2
|
+
export declare function sleepProblems(logText: string, bad: number): string | null;
|
|
3
|
+
/** Prints the SessionEnd sleep log on stderr, then clears it. In a hook, stdout carries only `systemMessage`,
|
|
4
|
+
* because Claude Code shows that to the user and adds any other SessionStart stdout to the model's context. */
|
|
5
|
+
export declare function cmdLastSleep(hippoRoot: string, flags: Record<string, string | boolean | string[]>, out?: 'hook' | 'terminal'): void;
|
|
6
|
+
//# sourceMappingURL=last-sleep.d.ts.map
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// `hippo last-sleep`: the previous session's sleep log for the debug log, and one problems line for the user.
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import { isStringValue } from '../capture-contract.js';
|
|
4
|
+
import { REPLAY_AFTER_MS } from '../compaction-record.js';
|
|
5
|
+
import { SPOOL_PROBLEM, spoolCounts } from '../compaction-spool.js';
|
|
6
|
+
import { defaultSleepLogPath } from '../hooks/shared.js';
|
|
7
|
+
import { errorMessage } from '../log.js';
|
|
8
|
+
import { truncateCodePointSafe } from '../transcript-tail.js';
|
|
9
|
+
import { printError } from './output.js';
|
|
10
|
+
import { hookStoreRoot } from './shared.js';
|
|
11
|
+
const SLEEP_FAILED = '[hippo] sleep failed: ';
|
|
12
|
+
const FAILURE_CHARS = 120;
|
|
13
|
+
/** The line the user sees when the last sleep failed, a spool step hit an unexpected error, or the spool holds `.bad` files; null when none did. */
|
|
14
|
+
export function sleepProblems(logText, bad) {
|
|
15
|
+
const lines = logText.split(/\r?\n/);
|
|
16
|
+
const failed = lines.find((line) => line.startsWith(SLEEP_FAILED));
|
|
17
|
+
const errors = lines.filter((line) => line.includes(SPOOL_PROBLEM)).length;
|
|
18
|
+
const parts = [
|
|
19
|
+
failed === undefined ? '' : `the last sleep failed (${truncateCodePointSafe(failed.slice(SLEEP_FAILED.length).trim(), FAILURE_CHARS)})`,
|
|
20
|
+
errors === 0 ? '' : `${errors} error${errors === 1 ? '' : 's'} while saving waiting compaction summaries`,
|
|
21
|
+
bad === 0 ? '' : `${bad} compaction ${bad === 1 ? 'summary' : 'summaries'} set aside as .bad`,
|
|
22
|
+
].filter((part) => part !== '');
|
|
23
|
+
return parts.length === 0 ? null : `Hippo: ${parts.join('; ')}. Run hippo doctor for details.`;
|
|
24
|
+
}
|
|
25
|
+
function badSpoolFiles(hippoRoot) {
|
|
26
|
+
try {
|
|
27
|
+
return spoolCounts(hookStoreRoot(hippoRoot), new Date(), REPLAY_AFTER_MS).bad;
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
printError(`hippo: spool not counted: ${errorMessage(err)}`);
|
|
31
|
+
return 0;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Prints the SessionEnd sleep log on stderr, then clears it. In a hook, stdout carries only `systemMessage`,
|
|
35
|
+
* because Claude Code shows that to the user and adds any other SessionStart stdout to the model's context. */
|
|
36
|
+
export function cmdLastSleep(hippoRoot, flags, out = process.stdout.isTTY ? 'terminal' : 'hook') {
|
|
37
|
+
const pathFlag = flags['path'];
|
|
38
|
+
const logPath = isStringValue(pathFlag) ? pathFlag : defaultSleepLogPath();
|
|
39
|
+
if (!fs.existsSync(logPath))
|
|
40
|
+
return;
|
|
41
|
+
let content;
|
|
42
|
+
try {
|
|
43
|
+
content = fs.readFileSync(logPath, 'utf8');
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// Removed or locked since the exists check: there is nothing to show this session.
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (content.trim().length > 0) {
|
|
50
|
+
printError('=== Previous session hippo consolidation ===');
|
|
51
|
+
process.stderr.write(content);
|
|
52
|
+
if (!content.endsWith('\n'))
|
|
53
|
+
printError();
|
|
54
|
+
printError('===========================================');
|
|
55
|
+
}
|
|
56
|
+
const problems = sleepProblems(content, badSpoolFiles(hippoRoot));
|
|
57
|
+
if (problems !== null && out === 'hook')
|
|
58
|
+
process.stdout.write(`${JSON.stringify({ systemMessage: problems })}\n`);
|
|
59
|
+
if (problems !== null && out === 'terminal')
|
|
60
|
+
printError(problems);
|
|
61
|
+
if (!flags['keep']) {
|
|
62
|
+
try {
|
|
63
|
+
fs.unlinkSync(logPath);
|
|
64
|
+
}
|
|
65
|
+
catch { /* non-fatal */ }
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=last-sleep.js.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
+
import { type EmbeddingProvider } from '../embedding-provider.js';
|
|
1
2
|
export declare function cmdRefine(hippoRoot: string, flags: Record<string, string | boolean | string[]>): Promise<void>;
|
|
2
3
|
export declare function cmdDedup(hippoRoot: string, flags: Record<string, string | boolean | string[]>): void;
|
|
3
|
-
export declare function cmdEmbed(hippoRoot: string, flags: Record<string, string | boolean | string[]
|
|
4
|
+
export declare function cmdEmbed(hippoRoot: string, flags: Record<string, string | boolean | string[]>, given?: EmbeddingProvider): Promise<void>;
|
|
4
5
|
//# sourceMappingURL=maintenance.d.ts.map
|
package/dist/cli/maintenance.js
CHANGED
|
@@ -3,7 +3,6 @@ import { envAnthropicApiKey } from '../env.js';
|
|
|
3
3
|
import { loadAllEntries } from '../store/entry-reads.js';
|
|
4
4
|
import { deduplicateStore } from '../dedupe.js';
|
|
5
5
|
import { embedAll, loadEmbeddingIndex } from '../embeddings.js';
|
|
6
|
-
import { resolveEmbeddingModel } from '../local-embedding.js';
|
|
7
6
|
import { resolveEmbeddingProvider } from '../embedding-provider.js';
|
|
8
7
|
import { resetAllPhysicsState } from '../physics-state.js';
|
|
9
8
|
import { loadConfig } from '../config.js';
|
|
@@ -93,7 +92,7 @@ export function cmdDedup(hippoRoot, flags) {
|
|
|
93
92
|
// ---------------------------------------------------------------------------
|
|
94
93
|
// Embed command
|
|
95
94
|
// ---------------------------------------------------------------------------
|
|
96
|
-
export async function cmdEmbed(hippoRoot, flags) {
|
|
95
|
+
export async function cmdEmbed(hippoRoot, flags, given) {
|
|
97
96
|
// --global mirrors resolveAuthRoot (cli.ts:6900): initGlobal() + the global
|
|
98
97
|
// root, skipping the local requireInit entirely, so this is the healing
|
|
99
98
|
// path for pre-1.27.0 global stores from a directory with no local .hippo.
|
|
@@ -109,12 +108,13 @@ export async function cmdEmbed(hippoRoot, flags) {
|
|
|
109
108
|
printEmbedStatus(root);
|
|
110
109
|
return;
|
|
111
110
|
}
|
|
112
|
-
|
|
111
|
+
const provider = readyEmbedProvider(root, given);
|
|
112
|
+
if (!provider)
|
|
113
113
|
return;
|
|
114
114
|
console.log('Embedding all memories (this may take a moment on first run to download model)...');
|
|
115
115
|
let count;
|
|
116
116
|
try {
|
|
117
|
-
count = await embedAll(root,
|
|
117
|
+
count = await embedAll(root, undefined, provider);
|
|
118
118
|
}
|
|
119
119
|
catch (err) {
|
|
120
120
|
printError(`Embedding failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -126,6 +126,11 @@ export async function cmdEmbed(hippoRoot, flags) {
|
|
|
126
126
|
const entriesAfter = loadAllEntries(root);
|
|
127
127
|
const embIndexAfter = loadEmbeddingIndex(root);
|
|
128
128
|
console.log(`Done. ${count} new embeddings created. ${Object.keys(embIndexAfter).length}/${entriesAfter.length} total.`);
|
|
129
|
+
const unembedded = entriesAfter.filter((e) => !embIndexAfter[e.id]).length;
|
|
130
|
+
if (unembedded > 0) {
|
|
131
|
+
printError(`${unembedded} memories are still not embedded (the warnings above name them). Re-run \`hippo embed\` to retry.`);
|
|
132
|
+
process.exitCode = 1;
|
|
133
|
+
}
|
|
129
134
|
}
|
|
130
135
|
function resetPhysics(root) {
|
|
131
136
|
const entries = loadAllEntries(root);
|
|
@@ -154,10 +159,10 @@ function printEmbedStatus(root) {
|
|
|
154
159
|
console.log(` ${missing.length} memories need embedding (run \`hippo embed\` to embed them)`);
|
|
155
160
|
}
|
|
156
161
|
}
|
|
157
|
-
/**
|
|
158
|
-
function
|
|
162
|
+
/** The provider to embed with, or null after saying why none is usable. */
|
|
163
|
+
function readyEmbedProvider(root, given) {
|
|
159
164
|
// Embedding (unlike status/reset) needs an available provider.
|
|
160
|
-
const embedProvider = (() => {
|
|
165
|
+
const embedProvider = given ?? (() => {
|
|
161
166
|
try {
|
|
162
167
|
return resolveEmbeddingProvider(root);
|
|
163
168
|
}
|
|
@@ -168,23 +173,24 @@ function embedProviderReady(root) {
|
|
|
168
173
|
})();
|
|
169
174
|
if (!embedProvider) {
|
|
170
175
|
process.exitCode = 1;
|
|
171
|
-
return
|
|
176
|
+
return null;
|
|
172
177
|
}
|
|
173
178
|
if (embedProvider.isAvailable())
|
|
174
|
-
return
|
|
179
|
+
return embedProvider;
|
|
175
180
|
if (loadConfig(root).embeddings.enabled === false) {
|
|
176
181
|
console.log('Embeddings are disabled in config (embeddings.enabled = false). Set it to true or "auto" to enable.');
|
|
177
|
-
return
|
|
182
|
+
return null;
|
|
178
183
|
}
|
|
179
184
|
if (embedProvider.kind === 'local') {
|
|
180
185
|
console.log('Embeddings not available. Install @huggingface/transformers to enable:');
|
|
181
186
|
console.log(' npm install @huggingface/transformers');
|
|
187
|
+
process.exitCode = 1;
|
|
182
188
|
}
|
|
183
189
|
else {
|
|
184
190
|
printError(`Embedding provider '${embedProvider.kind}' is configured but ${embedProvider.keyEnv} is not set.`);
|
|
185
191
|
printError(`Export ${embedProvider.keyEnv}, or set config.embeddings.provider back to 'local'.`);
|
|
186
192
|
process.exitCode = 1;
|
|
187
193
|
}
|
|
188
|
-
return
|
|
194
|
+
return null;
|
|
189
195
|
}
|
|
190
196
|
//# sourceMappingURL=maintenance.js.map
|