hippo-memory 1.63.1 → 1.64.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 +13 -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/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/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/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
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Sleep and the daily runner clean a store's old automatic memories once, so an upgrade needs no command.
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { errorMessage, log } from '../log.js';
|
|
4
|
+
import { repairQualityOnce } from '../quality-repair.js';
|
|
5
|
+
import { getGlobalRoot } from '../shared.js';
|
|
6
|
+
import { resolveTenantId } from '../tenant.js';
|
|
7
|
+
/** Fault-isolated like the project tag repair: a failure warns, leaves the store unmarked and runs again next time. */
|
|
8
|
+
export function repairQualityOnceAt(root) {
|
|
9
|
+
try {
|
|
10
|
+
const result = repairQualityOnce(root, resolveTenantId({}));
|
|
11
|
+
if (result === null)
|
|
12
|
+
return;
|
|
13
|
+
if (!result.supported) {
|
|
14
|
+
log.warn(`memory quality repair skipped, tried again next time: ${result.blockers.join('; ')}`);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
// A sleep run from a folder with no project store can resolve to the global store too.
|
|
18
|
+
const scope = path.resolve(root) === path.resolve(getGlobalRoot()) ? ' --global' : '';
|
|
19
|
+
const moved = result.appliedIds.length;
|
|
20
|
+
if (moved > 0) {
|
|
21
|
+
console.log(`Set aside ${moved} automatic ${moved === 1 ? 'memory' : 'memories'} with a certain defect, once after the upgrade (backup: ${result.backup}).`);
|
|
22
|
+
console.log(` List them: hippo dormant${scope} Bring one back: hippo dormant restore <id>${scope}`);
|
|
23
|
+
}
|
|
24
|
+
const review = result.issues.filter((issue) => issue.disposition === 'review').length;
|
|
25
|
+
if (review > 0) {
|
|
26
|
+
const lead = moved > 0 ? ` ${review} more` : `Checked old automatic memories once after the upgrade: ${review}`;
|
|
27
|
+
console.log(`${lead} ${review === 1 ? 'looks' : 'look'} doubtful and ${review === 1 ? 'was' : 'were'} kept; hippo audit repair${scope} lists them.`);
|
|
28
|
+
}
|
|
29
|
+
for (const warning of result.warnings)
|
|
30
|
+
log.warn(warning);
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
log.warn(`memory quality repair skipped, tried again next time: ${errorMessage(err)}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=quality-repair-once.js.map
|
package/dist/cli/recall.js
CHANGED
|
@@ -32,8 +32,9 @@ import { rankRecall, } from '../recall-pipeline.js';
|
|
|
32
32
|
import { JEV_DEFAULT_TOP_K } from '../rerankers/jev.js';
|
|
33
33
|
import { isClefModel } from '../rerankers/clef.js';
|
|
34
34
|
import { handoffText, printedTokens, sessionTrailText, settleTokens, snapshotText } from '../context-render.js';
|
|
35
|
+
import { withLedgerDb } from '../ledger-db.js';
|
|
35
36
|
import { printError } from './output.js';
|
|
36
|
-
import { parseLimitFlag, parseBudgetFlag, emitCliAudit, requireInit, recallEntryText, recallHeading, parseAsOfFlag, engineFlags, printActiveTaskSnapshot, printSessionEvents, printHandoff, hostSessionId, captureConsole, hookStoreRoot,
|
|
37
|
+
import { parseLimitFlag, parseBudgetFlag, emitCliAudit, requireInit, recallEntryText, recallHeading, parseAsOfFlag, engineFlags, printActiveTaskSnapshot, printSessionEvents, printHandoff, hostSessionId, captureConsole, hookStoreRoot, } from './shared.js';
|
|
37
38
|
// Per-process rings: a single-shot `hippo recall` starts empty, so anchoring only accumulates in long-lived
|
|
38
39
|
// hosts (in-process loops, `hippo serve`, the MCP server).
|
|
39
40
|
const sessionRecallHistoryCli = new Map();
|
package/dist/cli/remember.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { type CommandContext } from './shared.js';
|
|
2
|
+
/** @internal Exported so tests/remember-origin-parity.test.ts runs the real direct write; not a stable public API. */
|
|
3
|
+
export declare function cmdRemember(hippoRoot: string, text: string, flags: Record<string, string | boolean | string[]>): Promise<void>;
|
|
2
4
|
export declare function handleRemember({ hippoRoot, args, flags }: CommandContext): Promise<void>;
|
|
3
5
|
export declare function handleSupersede({ hippoRoot, args, flags }: CommandContext): void;
|
|
4
6
|
export declare function handleTrace({ hippoRoot, args, flags }: CommandContext): void;
|
package/dist/cli/remember.js
CHANGED
|
@@ -14,6 +14,7 @@ import { embedMemory } from '../embeddings.js';
|
|
|
14
14
|
import { loadConfig } from '../config.js';
|
|
15
15
|
import { extractPathTags } from '../path-context.js';
|
|
16
16
|
import { detectScope } from '../scope.js';
|
|
17
|
+
import { assertClientScope } from '../recall-scope.js';
|
|
17
18
|
import { getGlobalRoot, initGlobal } from '../shared.js';
|
|
18
19
|
import { vetSecrets } from '../secret-detect.js';
|
|
19
20
|
import * as client from '../client.js';
|
|
@@ -81,9 +82,11 @@ function parseRememberEnvelope(flags) {
|
|
|
81
82
|
const owner = ownerCheck.value ?? null;
|
|
82
83
|
const artifactRef = typeof flags['artifact-ref'] === 'string' ? flags['artifact-ref'] : null;
|
|
83
84
|
const scope = typeof flags['scope'] === 'string' ? flags['scope'].trim() || null : null;
|
|
85
|
+
assertClientScope(scope);
|
|
84
86
|
return { kind, owner, artifactRef, scope };
|
|
85
87
|
}
|
|
86
|
-
|
|
88
|
+
/** @internal Exported so tests/remember-origin-parity.test.ts runs the real direct write; not a stable public API. */
|
|
89
|
+
export async function cmdRemember(hippoRoot, text, flags) {
|
|
87
90
|
const useGlobal = Boolean(flags['global']);
|
|
88
91
|
const targetRoot = useGlobal ? getGlobalRoot() : hippoRoot;
|
|
89
92
|
if (useGlobal) {
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
import { type CommandContext } from './shared.js';
|
|
2
|
-
/** Prints the SessionEnd sleep log, then clears it. Stderr, because Claude Code adds
|
|
3
|
-
* SessionStart stdout to the model's context and this log is for the user. */
|
|
4
|
-
export declare function cmdLastSleep(flags: Record<string, string | boolean | string[]>): void;
|
|
5
2
|
/**
|
|
6
3
|
* SessionEnd entry point. Claude Code / OpenCode fire this on /exit while
|
|
7
4
|
* tearing down the TUI, which kills any child that is still running when
|