hippo-memory 1.65.0 → 1.67.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 +2 -0
- package/dist/api/auth.d.ts +11 -2
- package/dist/api/auth.js +25 -2
- package/dist/api/recall-types.d.ts +1 -0
- package/dist/api/recall.js +5 -5
- package/dist/auth.d.ts +3 -1
- package/dist/auth.js +6 -2
- package/dist/capture/compact.d.ts +2 -0
- package/dist/capture/compact.js +12 -6
- package/dist/capture/copilot-transcript.d.ts +15 -0
- package/dist/capture/copilot-transcript.js +40 -0
- package/dist/capture/failure-reading.js +5 -2
- package/dist/capture/transcript.js +7 -1
- package/dist/capture/working-state.js +14 -3
- package/dist/capture-contract.d.ts +4 -2
- package/dist/capture-contract.js +3 -3
- package/dist/cli/context.js +11 -8
- package/dist/cli/hook-blocks.d.ts +2 -0
- package/dist/cli/hook-blocks.js +6 -0
- package/dist/cli/session-hooks.d.ts +1 -1
- package/dist/cli/session-hooks.js +28 -16
- package/dist/cli/setup.js +66 -7
- package/dist/cli/shared.d.ts +5 -0
- package/dist/cli/shared.js +22 -3
- package/dist/cli/usage.js +20 -9
- package/dist/db/open.d.ts +5 -1
- package/dist/db/open.js +28 -7
- package/dist/db/sqlite-blocked.d.ts +11 -1
- package/dist/db/sqlite-blocked.js +20 -2
- package/dist/db.d.ts +2 -2
- package/dist/db.js +2 -2
- package/dist/delivery-recorder.d.ts +3 -1
- package/dist/delivery-recorder.js +1 -1
- package/dist/embeddings.d.ts +11 -2
- package/dist/embeddings.js +20 -11
- package/dist/hooks/copilot.d.ts +45 -0
- package/dist/hooks/copilot.js +187 -0
- package/dist/hooks/json-hooks.d.ts +1 -1
- package/dist/hooks/json-hooks.js +41 -2
- package/dist/hooks/shared.d.ts +5 -1
- package/dist/hooks/shared.js +9 -0
- package/dist/mcp/request.d.ts +0 -2
- package/dist/mcp/request.js +16 -11
- package/dist/prompt-hook.d.ts +6 -0
- package/dist/prompt-hook.js +15 -7
- package/dist/quality-repair.js +3 -0
- package/dist/search/hybrid.d.ts +3 -0
- package/dist/search/physics-search.d.ts +3 -0
- package/dist/search/physics-search.js +18 -19
- package/dist/search/vector.d.ts +7 -2
- package/dist/search/vector.js +24 -10
- package/dist/server/routes/admin.js +1 -1
- package/dist/server/routes/recall.js +6 -9
- package/dist/server/types.d.ts +5 -4
- package/dist/server.d.ts +11 -6
- package/dist/server.js +18 -14
- package/dist/stdin.d.ts +5 -0
- package/dist/stdin.js +62 -0
- package/dist/store/key-audit.d.ts +6 -0
- package/dist/store/key-audit.js +25 -0
- package/dist/store/open.js +2 -1
- package/dist/store-port.d.ts +42 -4
- package/dist/store-port.js +46 -2
- package/dist/vector-store.d.ts +8 -0
- package/dist/vector-store.js +31 -19
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
- package/extensions/openclaw-plugin/package.json +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@ import * as fs from 'fs';
|
|
|
4
4
|
import { spawn } from 'child_process';
|
|
5
5
|
import { resolveCodexSessionTranscript } from '../hooks/codex-session.js';
|
|
6
6
|
import { resolveCodexWrapperPaths } from '../hooks/codex-wrapper.js';
|
|
7
|
+
import { resolveJsonHookPaths } from '../hooks/json-hooks.js';
|
|
7
8
|
import { isInitialized } from '../store/open.js';
|
|
8
9
|
import { loadActiveTaskSnapshot, loadFreshActiveTaskSnapshot, closeTaskSnapshotsForSession, listSessionEvents, } from '../store/sessions.js';
|
|
9
10
|
import { writeSessionEndHandoff } from '../store/handoffs.js';
|
|
@@ -20,16 +21,17 @@ import { cmdPreCompact, cmdPostCompact } from '../capture/compact.js';
|
|
|
20
21
|
import { transcriptWorkingState } from '../capture/working-state.js';
|
|
21
22
|
import { collectHandoffEvidence } from '../handoff-evidence.js';
|
|
22
23
|
import { resolveLastSessionTranscript } from '../capture/transcript.js';
|
|
24
|
+
import { copilotTranscriptFor } from '../capture/copilot-transcript.js';
|
|
23
25
|
import { truncateCodePointSafe } from '../transcript-tail.js';
|
|
24
26
|
import { COMPACTION_DB_WAIT_MS } from '../compaction-record.js';
|
|
25
27
|
import { COMPACT_RESUME_EVENT_CONTENT_CAP, COMPACT_RESUME_MAX_AGE_MS, compactResumeText } from '../context-render.js';
|
|
26
|
-
import {
|
|
28
|
+
import { readHookStdin } from '../stdin.js';
|
|
27
29
|
import { resolveTenantId } from '../tenant.js';
|
|
28
30
|
import { errorMessage, log } from '../log.js';
|
|
29
31
|
import { withLedgerDb } from '../ledger-db.js';
|
|
30
32
|
import { printError } from './output.js';
|
|
31
33
|
import { cmdLastSleep } from './last-sleep.js';
|
|
32
|
-
import { logSessionEndImport, appendSessionEndCloseLog, resetHookInjection, hookStoreRoot, runHookWithStores, inPilotHoldout, } from './shared.js';
|
|
34
|
+
import { logSessionEndImport, appendSessionEndCloseLog, resetHookInjection, hookStoreRoot, hookRuntime, payloadCwdRoot, runHookWithStores, inPilotHoldout, } from './shared.js';
|
|
33
35
|
/**
|
|
34
36
|
* SessionStart(compact) injector. Prints the active task snapshot + recent
|
|
35
37
|
* session trail so working state that would otherwise be lost to
|
|
@@ -140,10 +142,14 @@ function restoreCompactSnapshot(hippoRoot, payloadSessionId) {
|
|
|
140
142
|
* the next SessionStart reads the log via `hippo last-sleep`.
|
|
141
143
|
*/
|
|
142
144
|
export async function cmdSessionEnd(hippoRoot, flags) {
|
|
143
|
-
const
|
|
145
|
+
const runtime = hookRuntime(flags);
|
|
146
|
+
// Copilot's hook command carries no path, since one quoted into it would need escaping for each shell; the log goes where the hook table used to point.
|
|
147
|
+
const logFile = typeof flags['log-file'] === 'string' ? flags['log-file'] : runtime === 'copilot' ? resolveJsonHookPaths('copilot').logFile : null;
|
|
144
148
|
// Bounded read: extracts transcript_path + session_id for the detached worker's argv.
|
|
145
149
|
let sessionId = null;
|
|
146
|
-
const { text: stdinText } = await
|
|
150
|
+
const { text: stdinText } = await readHookStdin();
|
|
151
|
+
// Before the spawn, since the worker finds its store from the folder it inherits.
|
|
152
|
+
const root = payloadCwdRoot(hippoRoot, stdinText, runtime);
|
|
147
153
|
try {
|
|
148
154
|
if (stdinText && stdinText.trim().startsWith('{')) {
|
|
149
155
|
const payload = JSON.parse(stdinText);
|
|
@@ -156,8 +162,9 @@ export async function cmdSessionEnd(hippoRoot, flags) {
|
|
|
156
162
|
// No stdin, not JSON, or read failure: the snapshot close below will no-op.
|
|
157
163
|
}
|
|
158
164
|
// Resolved here because only this process saw the payload; the worker captures just the path it is handed.
|
|
159
|
-
// Always a hook, so never scan: an empty stdin here is not a manual run.
|
|
160
|
-
const transcriptPath = resolveLastSessionTranscript(undefined, stdinText, { mayScan: false })
|
|
165
|
+
// Always a hook, so never scan: an empty stdin here is not a manual run. The Copilot CLI's sessionEnd names no transcript, so its log is found by id.
|
|
166
|
+
const transcriptPath = resolveLastSessionTranscript(undefined, stdinText, { mayScan: false })
|
|
167
|
+
?? (runtime === 'copilot' && sessionId ? copilotTranscriptFor(sessionId) : null);
|
|
161
168
|
const workerArgs = [process.argv[1], '__session-end-worker'];
|
|
162
169
|
if (logFile)
|
|
163
170
|
workerArgs.push('--log-file', logFile);
|
|
@@ -177,11 +184,13 @@ export async function cmdSessionEnd(hippoRoot, flags) {
|
|
|
177
184
|
}
|
|
178
185
|
catch {
|
|
179
186
|
// If spawn fails, run inline as a last resort, handed what the child's argv would have carried.
|
|
187
|
+
if (logFile)
|
|
188
|
+
flags['log-file'] = logFile;
|
|
180
189
|
if (transcriptPath)
|
|
181
190
|
flags['transcript'] = transcriptPath;
|
|
182
191
|
if (sessionId)
|
|
183
192
|
flags['session-id'] = sessionId;
|
|
184
|
-
await cmdSessionEndWorker(
|
|
193
|
+
await cmdSessionEndWorker(root, flags);
|
|
185
194
|
return;
|
|
186
195
|
}
|
|
187
196
|
}
|
|
@@ -493,19 +502,22 @@ export async function cmdCodexSessionEndWorker(hippoRoot, flags) {
|
|
|
493
502
|
}
|
|
494
503
|
export async function handlePreCompact({ hippoRoot, flags }) {
|
|
495
504
|
// Bounded wait, not a TTY guard: an idle non-TTY pipe must not hang.
|
|
496
|
-
const { text: stdinText, timedOut: stdinTimedOut } = await
|
|
505
|
+
const { text: stdinText, timedOut: stdinTimedOut } = await readHookStdin();
|
|
506
|
+
const runtime = hookRuntime(flags);
|
|
507
|
+
const root = payloadCwdRoot(hippoRoot, stdinText, runtime);
|
|
497
508
|
await runHookWithStores(async () => {
|
|
498
|
-
resetHookInjection(
|
|
499
|
-
await cmdPreCompact(hookStoreRoot(
|
|
509
|
+
resetHookInjection(root, stdinText, null);
|
|
510
|
+
await cmdPreCompact(hookStoreRoot(root), {
|
|
500
511
|
stdinText,
|
|
501
512
|
stdinTimedOut,
|
|
502
513
|
logFile: typeof flags['log-file'] === 'string' ? flags['log-file'] : undefined,
|
|
514
|
+
runtime,
|
|
503
515
|
});
|
|
504
516
|
});
|
|
505
517
|
}
|
|
506
518
|
export async function handlePostCompact({ hippoRoot, flags }) {
|
|
507
519
|
// PostCompact hook: saves the compaction summary and its memories, then prints one plain line, because Claude Code shows this hook's stdout as-is. Always exits 0.
|
|
508
|
-
const { text } = await
|
|
520
|
+
const { text } = await readHookStdin();
|
|
509
521
|
const logFlag = flags['log-file'];
|
|
510
522
|
const store = hookStoreRoot(hippoRoot);
|
|
511
523
|
const line = await runHookWithStores(() => cmdPostCompact(store, {
|
|
@@ -524,12 +536,12 @@ export async function handlePostCompact({ hippoRoot, flags }) {
|
|
|
524
536
|
if (line !== null && line !== undefined)
|
|
525
537
|
console.log(line);
|
|
526
538
|
}
|
|
527
|
-
export async function handleCaptureError({ hippoRoot }) {
|
|
539
|
+
export async function handleCaptureError({ hippoRoot, flags }) {
|
|
528
540
|
// PostToolUseFailure hook: every path exits 0, and nothing is created
|
|
529
541
|
// when no store exists (the hook fires in every directory).
|
|
530
|
-
const { text } = await
|
|
542
|
+
const { text } = await readHookStdin();
|
|
531
543
|
try {
|
|
532
|
-
const root = hookStoreRoot(hippoRoot);
|
|
544
|
+
const root = hookStoreRoot(payloadCwdRoot(hippoRoot, text, hookRuntime(flags)));
|
|
533
545
|
const payload = (text ?? '').trim();
|
|
534
546
|
if (isInitialized(root) && payload) {
|
|
535
547
|
// SAFETY: JSON.parse returns a JSON value by definition.
|
|
@@ -542,7 +554,7 @@ export async function handleCaptureError({ hippoRoot }) {
|
|
|
542
554
|
}
|
|
543
555
|
}
|
|
544
556
|
export async function handleCompactResume({ hippoRoot }) {
|
|
545
|
-
const { text: stdinText, timedOut: stdinTimedOut } = await
|
|
557
|
+
const { text: stdinText, timedOut: stdinTimedOut } = await readHookStdin();
|
|
546
558
|
await runHookWithStores(() => {
|
|
547
559
|
resetHookInjection(hippoRoot, stdinText, 'compact');
|
|
548
560
|
cmdCompactResume(hookStoreRoot(hippoRoot), stdinText, stdinTimedOut);
|
|
@@ -574,7 +586,7 @@ export async function handleCapture({ hippoRoot, flags }) {
|
|
|
574
586
|
// Bounded, and only when last-session has no explicit path: the
|
|
575
587
|
// --stdin source keeps its own blocking read in capture.ts by design.
|
|
576
588
|
const bounded = captureSource === 'last-session' && !transcriptPath
|
|
577
|
-
? await
|
|
589
|
+
? await readHookStdin()
|
|
578
590
|
: { text: undefined, timedOut: false };
|
|
579
591
|
cmdCapture(hippoRoot, {
|
|
580
592
|
source: captureSource,
|
package/dist/cli/setup.js
CHANGED
|
@@ -6,6 +6,7 @@ import { installJsonHooks, uninstallJsonHooks, checkUninstallable, resolveJsonHo
|
|
|
6
6
|
import { detectInstalledTools } from '../hooks/shared.js';
|
|
7
7
|
import { ensureCodexWrapperInstalled, installCodexWrapper, detectRealCodexPath, uninstallCodexWrapper, } from '../hooks/codex-wrapper.js';
|
|
8
8
|
import { installOpencodePlugin, uninstallOpencodePlugin, resolveOpencodePluginPath } from '../hooks/opencode.js';
|
|
9
|
+
import { copilotMcpSnippet, copilotPaths, installCopilot, isMcpFailure, uninstallCopilot } from '../hooks/copilot.js';
|
|
9
10
|
import { isInitialized } from '../store/open.js';
|
|
10
11
|
import { currentMachine, importUserMemories } from '../agent-memories/sync.js';
|
|
11
12
|
import { getGlobalRoot } from '../shared.js';
|
|
@@ -15,7 +16,7 @@ import { log } from '../log.js';
|
|
|
15
16
|
import { printError } from './output.js';
|
|
16
17
|
import { printAgentImport, installCodexMemoryHooks, setupDailySchedule, warnClaudeSettingsUnusable } from './shared.js';
|
|
17
18
|
import { repairQualityOnceAt } from './quality-repair-once.js';
|
|
18
|
-
import { HOOK_MARKERS, HOOKS, hippoBlock } from './hook-blocks.js';
|
|
19
|
+
import { HOOK_MARKERS, HOOKS, hippoBlock, withoutHookBlock } from './hook-blocks.js';
|
|
19
20
|
import { escapeRegex } from '../escape.js';
|
|
20
21
|
// ---------------------------------------------------------------------------
|
|
21
22
|
// Hook install/uninstall
|
|
@@ -41,13 +42,17 @@ function hookList() {
|
|
|
41
42
|
for (const [name, hook] of Object.entries(HOOKS)) {
|
|
42
43
|
console.log(` ${name.padEnd(15)} -> ${hook.file} (${hook.description})`);
|
|
43
44
|
}
|
|
45
|
+
console.log(` ${'copilot'.padEnd(15)} -> $COPILOT_HOME or ~/.copilot (GitHub Copilot: hooks, MCP server and instructions)`);
|
|
44
46
|
console.log('\nUsage: hippo hook install <name>');
|
|
45
47
|
console.log(' hippo hook uninstall <name>');
|
|
46
48
|
}
|
|
47
49
|
function hookInstall(target) {
|
|
50
|
+
// Copilot's instructions live in its home folder, not in a project file, so it has no HOOKS entry.
|
|
51
|
+
if (target === 'copilot')
|
|
52
|
+
return printCopilotInstall(installCopilot(), '');
|
|
48
53
|
if (!target || !HOOKS[target]) {
|
|
49
54
|
printError(`Unknown hook target: ${target ?? '(none)'}`);
|
|
50
|
-
printError(` Available: ${Object.keys(HOOKS).join(', ')}`);
|
|
55
|
+
printError(` Available: ${[...Object.keys(HOOKS), 'copilot'].join(', ')}`);
|
|
51
56
|
process.exit(1);
|
|
52
57
|
}
|
|
53
58
|
const hook = HOOKS[target];
|
|
@@ -150,7 +155,56 @@ function installOpencodeHook() {
|
|
|
150
155
|
console.log(`WARNING: opencode.json is unparseable; legacy hooks block could not be auto-removed. Fix the file manually.`);
|
|
151
156
|
}
|
|
152
157
|
}
|
|
158
|
+
const MCP_MERGE_LINES = {
|
|
159
|
+
added: (file) => `Added the "hippo" MCP server -> ${file}`,
|
|
160
|
+
present: (file) => `The "hippo" MCP server is already in ${file}`,
|
|
161
|
+
'user-owned': (file) => `Left the "hippo" MCP server in ${file} as it is: hippo did not write it`,
|
|
162
|
+
unreadable: (file) => `WARNING: ${file} has comments or is not a JSON object hippo can merge into, so it was left unchanged; add this under "mcpServers" by hand: ${copilotMcpSnippet()}`,
|
|
163
|
+
};
|
|
164
|
+
const mcpFailureLine = (file, failure, fix) => `WARNING: hippo could not use ${file} (${failure.failed}), so it was left unchanged; ${fix}`;
|
|
165
|
+
const unclosedLine = (file) => `WARNING: ${file} has ${HOOK_MARKERS.start} with no ${HOOK_MARKERS.end}, so hippo left it unchanged; fix the markers by hand`;
|
|
166
|
+
const INSTRUCTIONS_INSTALL_LINES = {
|
|
167
|
+
written: (file) => `Wrote the hippo block -> ${file}`,
|
|
168
|
+
present: (file) => `The hippo block is already in ${file}`,
|
|
169
|
+
kept: (file) => `Kept the hippo block in ${file} as it is: it is not the Copilot text hippo wrote, so it was edited or is another agent's`,
|
|
170
|
+
unclosed: unclosedLine,
|
|
171
|
+
};
|
|
172
|
+
const INSTRUCTIONS_REMOVE_LINES = {
|
|
173
|
+
removed: (file) => `Removed the hippo block from ${file}`,
|
|
174
|
+
absent: () => '',
|
|
175
|
+
kept: (file) => `Left the hippo block in ${file}: it is not the Copilot text hippo wrote. Delete it by hand if no agent needs it.`,
|
|
176
|
+
unclosed: unclosedLine,
|
|
177
|
+
};
|
|
178
|
+
function printCopilotInstall(result, indent) {
|
|
179
|
+
const { paths, mcp } = result;
|
|
180
|
+
const hooks = result.hooks ? "Installed hippo's Copilot hooks (sessionStart, postToolUseFailure, preCompact, sessionEnd) ->" : "hippo's Copilot hooks are already in";
|
|
181
|
+
console.log(`${indent}${hooks} ${paths.hooks}`);
|
|
182
|
+
const mcpLine = isMcpFailure(mcp) ? mcpFailureLine(paths.mcpConfig, mcp, `add this under "mcpServers" by hand: ${copilotMcpSnippet()}`) : MCP_MERGE_LINES[mcp](paths.mcpConfig);
|
|
183
|
+
console.log(`${indent}${mcpLine}`);
|
|
184
|
+
console.log(`${indent}${INSTRUCTIONS_INSTALL_LINES[result.instructions](paths.instructions)}`);
|
|
185
|
+
console.log(`${indent}VS Code Copilot runs new hooks in a new chat session.`);
|
|
186
|
+
}
|
|
187
|
+
function printCopilotUninstall(result) {
|
|
188
|
+
const { paths, mcp } = result;
|
|
189
|
+
if (result.hooks)
|
|
190
|
+
console.log(`Removed hippo's Copilot hooks file ${paths.hooks}`);
|
|
191
|
+
if (isMcpFailure(mcp))
|
|
192
|
+
console.log(mcpFailureLine(paths.mcpConfig, mcp, 'remove the "hippo" server by hand if hippo added it'));
|
|
193
|
+
if (mcp === 'removed')
|
|
194
|
+
console.log(`Removed the "hippo" MCP server from ${paths.mcpConfig}`);
|
|
195
|
+
if (mcp === 'user-owned')
|
|
196
|
+
console.log(`Left the "hippo" MCP server in ${paths.mcpConfig}: hippo did not write it`);
|
|
197
|
+
if (mcp === 'unreadable')
|
|
198
|
+
console.log(`WARNING: ${paths.mcpConfig} has comments or is not a JSON object hippo can edit, so it was left unchanged; remove the "hippo" server by hand if hippo added it`);
|
|
199
|
+
const instructions = INSTRUCTIONS_REMOVE_LINES[result.instructions](paths.instructions);
|
|
200
|
+
if (instructions)
|
|
201
|
+
console.log(instructions);
|
|
202
|
+
if (!result.hooks && mcp === 'absent' && result.instructions === 'absent')
|
|
203
|
+
console.log('No hippo Copilot hooks, MCP server or instructions block found.');
|
|
204
|
+
}
|
|
153
205
|
function hookUninstall(target) {
|
|
206
|
+
if (target === 'copilot')
|
|
207
|
+
return printCopilotUninstall(uninstallCopilot());
|
|
154
208
|
if (!target || !HOOKS[target]) {
|
|
155
209
|
printError(`Unknown hook target: ${target ?? '(none)'}`);
|
|
156
210
|
process.exit(1);
|
|
@@ -218,10 +272,6 @@ function removeLegacyCursorRules() {
|
|
|
218
272
|
console.log(left ? 'Removed the old Hippo hook from .cursorrules' : 'Deleted .cursorrules, which held only the old Hippo hook');
|
|
219
273
|
}
|
|
220
274
|
}
|
|
221
|
-
function withoutHookBlock(text) {
|
|
222
|
-
const re = new RegExp(`\\n?${escapeRegex(HOOK_MARKERS.start)}[\\s\\S]*?${escapeRegex(HOOK_MARKERS.end)}\\n?`, 'g');
|
|
223
|
-
return text.replace(re, '\n').replace(/\n{3,}/g, '\n\n').trim();
|
|
224
|
-
}
|
|
225
275
|
// `hippo setup` -- one-shot configuration for every AI coding tool on the box.
|
|
226
276
|
// Detection and install logic live in ./hooks.ts.
|
|
227
277
|
export function cmdSetup(flags) {
|
|
@@ -237,7 +287,7 @@ export function cmdSetup(flags) {
|
|
|
237
287
|
const markdownTools = tools.filter((t) => t.kind === 'markdown-instruction' && t.detected);
|
|
238
288
|
const pluginTools = tools.filter((t) => t.kind === 'plugin' && t.detected);
|
|
239
289
|
if (jsonTools.length === 0 && !forceAll) {
|
|
240
|
-
console.log('No JSON-hook-capable tools detected (checked: claude-code).');
|
|
290
|
+
console.log('No JSON-hook-capable tools detected (checked: claude-code, copilot).');
|
|
241
291
|
console.log('Run with --all to install hooks anyway.');
|
|
242
292
|
}
|
|
243
293
|
for (const tool of jsonTools)
|
|
@@ -276,7 +326,16 @@ export function cmdSetup(flags) {
|
|
|
276
326
|
console.log('');
|
|
277
327
|
console.log('Done. Restart your AI tool to activate the hooks.');
|
|
278
328
|
}
|
|
329
|
+
function setupCopilot(dryRun) {
|
|
330
|
+
const indent = ` ${'copilot'.padEnd(14)} `;
|
|
331
|
+
if (!dryRun)
|
|
332
|
+
return printCopilotInstall(installCopilot(), indent);
|
|
333
|
+
const { hooks, mcpConfig, instructions } = copilotPaths();
|
|
334
|
+
console.log(`[dry-run] would install hooks in ${hooks}, the MCP server in ${mcpConfig} and the hippo block in ${instructions}`);
|
|
335
|
+
}
|
|
279
336
|
function setupJsonTool(tool, dryRun) {
|
|
337
|
+
if (tool.name === 'copilot')
|
|
338
|
+
return setupCopilot(dryRun);
|
|
280
339
|
if (dryRun) {
|
|
281
340
|
// Resolve the real settings path so the filename is right for each tool
|
|
282
341
|
// (claude-code -> settings.json, opencode -> opencode.json).
|
package/dist/cli/shared.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type InstallResult } from '../hooks/json-hooks.js';
|
|
2
2
|
import { TaskSnapshot, SessionEvent } from '../store/rows.js';
|
|
3
|
+
import type { HookRuntime } from '../capture-contract.js';
|
|
3
4
|
import type { SessionHandoff } from '../handoff.js';
|
|
4
5
|
import type { SearchResult } from '../search/types.js';
|
|
5
6
|
import { type HippoConfig } from '../config.js';
|
|
@@ -113,6 +114,10 @@ export declare function captureConsole(fn: () => void): string;
|
|
|
113
114
|
* is looked for in the other.
|
|
114
115
|
*/
|
|
115
116
|
export declare function hookStoreRoot(hippoRoot: string): string;
|
|
117
|
+
/** `--runtime copilot`, or `--format copilot` on `hippo context`, marks a Copilot hook: the flag decides, never the payload. */
|
|
118
|
+
export declare function hookRuntime(flags: CliFlags): HookRuntime;
|
|
119
|
+
/** A Copilot hook's project root, from the payload's `cwd`, since VS Code runs user-level hooks in the home folder; other runtimes keep `hippoRoot`. */
|
|
120
|
+
export declare function payloadCwdRoot(hippoRoot: string, stdinText: string | undefined, runtime: HookRuntime): string;
|
|
116
121
|
export declare function learnFromRepo(hippoRoot: string, repoPath: string, days: number, label?: string): {
|
|
117
122
|
added: number;
|
|
118
123
|
skipped: number;
|
package/dist/cli/shared.js
CHANGED
|
@@ -7,13 +7,13 @@ import { execFileSync, execSync } from 'child_process';
|
|
|
7
7
|
import { installJsonHooks } from '../hooks/json-hooks.js';
|
|
8
8
|
import { CODEX_TRUST_LINE } from '../hooks/shared.js';
|
|
9
9
|
import { confidenceLabel } from '../memory.js';
|
|
10
|
-
import { isInitialized } from '../store/open.js';
|
|
10
|
+
import { getHippoRoot, isInitialized } from '../store/open.js';
|
|
11
11
|
import { explainMatch } from '../search/explain.js';
|
|
12
12
|
import { isSharedStore } from '../config.js';
|
|
13
13
|
import { openHippoDb, closeHippoDb, isSqliteBusy, noteStoreBusy, runWithRequestStores, HOOK_DB_WAIT_MS } from '../db.js';
|
|
14
14
|
import { withLedgerDb } from '../ledger-db.js';
|
|
15
15
|
import { sessionPilotArm } from '../pilot-arm.js';
|
|
16
|
-
import { hookPayloadSessionId, isSubagentPayload, recordTokenUse } from '../token-ledger.js';
|
|
16
|
+
import { hookPayloadSessionId, hookPayloadString, isSubagentPayload, recordTokenUse } from '../token-ledger.js';
|
|
17
17
|
import { importAtSessionEnd, currentMachine } from '../agent-memories/sync.js';
|
|
18
18
|
import { summaryLine } from '../agent-memories/report.js';
|
|
19
19
|
import { detectChurnStale } from '../invalidation.js';
|
|
@@ -27,7 +27,7 @@ import { detectServer, removePidfileIfOwned } from '../server-detect.js';
|
|
|
27
27
|
import { resolveTenantId } from '../tenant.js';
|
|
28
28
|
import { adminActor, learn, CLI_LEARN } from '../api.js';
|
|
29
29
|
import { snapshotText, sessionTrailText, handoffText } from '../context-render.js';
|
|
30
|
-
import { log } from '../log.js';
|
|
30
|
+
import { errorMessage, log } from '../log.js';
|
|
31
31
|
import { printError } from './output.js';
|
|
32
32
|
export function parseLimitFlag(value) {
|
|
33
33
|
if (!value)
|
|
@@ -441,6 +441,25 @@ export function hookStoreRoot(hippoRoot) {
|
|
|
441
441
|
const globalRoot = getGlobalRoot();
|
|
442
442
|
return isInitialized(globalRoot) ? globalRoot : hippoRoot;
|
|
443
443
|
}
|
|
444
|
+
/** `--runtime copilot`, or `--format copilot` on `hippo context`, marks a Copilot hook: the flag decides, never the payload. */
|
|
445
|
+
export function hookRuntime(flags) {
|
|
446
|
+
return flags['runtime'] === 'copilot' || flags['format'] === 'copilot' ? 'copilot' : 'claude-code';
|
|
447
|
+
}
|
|
448
|
+
/** A Copilot hook's project root, from the payload's `cwd`, since VS Code runs user-level hooks in the home folder; other runtimes keep `hippoRoot`. */
|
|
449
|
+
export function payloadCwdRoot(hippoRoot, stdinText, runtime) {
|
|
450
|
+
const cwd = runtime === 'copilot' ? hookPayloadString(stdinText, 'cwd') : null;
|
|
451
|
+
if (cwd === null || cwd.trim() === '')
|
|
452
|
+
return hippoRoot;
|
|
453
|
+
try {
|
|
454
|
+
// Moving, not just re-rooting, keeps project identity, scope, handoff evidence and the session-end worker on that folder too.
|
|
455
|
+
process.chdir(cwd);
|
|
456
|
+
}
|
|
457
|
+
catch (err) {
|
|
458
|
+
log.warn(`hippo: payload cwd ${cwd} is not usable, so the hook stays in ${process.cwd()}: ${errorMessage(err)}`);
|
|
459
|
+
return hippoRoot;
|
|
460
|
+
}
|
|
461
|
+
return getHippoRoot(process.cwd());
|
|
462
|
+
}
|
|
444
463
|
export function learnFromRepo(hippoRoot, repoPath, days, label) {
|
|
445
464
|
const prefix = label ? `[${label}] ` : '';
|
|
446
465
|
const ctx = { hippoRoot, tenantId: resolveTenantId({}), actor: adminActor('cli') };
|
package/dist/cli/usage.js
CHANGED
|
@@ -255,18 +255,24 @@ export const VERB_USAGE = {
|
|
|
255
255
|
session-end SessionEnd hook: count this session's re-read tokens, run sleep, then
|
|
256
256
|
capture from the session's last 20 user and 10 assistant messages,
|
|
257
257
|
in a detached worker
|
|
258
|
-
--log-file <path> Tee the worker's output to a log file (paired with 'hippo last-sleep')
|
|
258
|
+
--log-file <path> Tee the worker's output to a log file (paired with 'hippo last-sleep')
|
|
259
|
+
--runtime copilot The payload came from a Copilot hook: use the store of its cwd and find
|
|
260
|
+
the Copilot CLI session log by session id; with no --log-file, log to
|
|
261
|
+
~/.hippo/logs/copilot-sleep.log`],
|
|
259
262
|
'pre-compact': [`
|
|
260
263
|
pre-compact PreCompact hook: record the compaction, save a working-state snapshot, and
|
|
261
264
|
ask the summariser to end with a "Memories for hippo" list
|
|
262
|
-
--log-file <p> Diagnostic log path (default: ~/.hippo/logs/pre-compact.log)
|
|
265
|
+
--log-file <p> Diagnostic log path (default: ~/.hippo/logs/pre-compact.log)
|
|
266
|
+
--runtime copilot The payload came from a Copilot hook: use the store of its cwd and save
|
|
267
|
+
the snapshot only (Copilot has no PostCompact hook to close a record)`],
|
|
263
268
|
'post-compact': [`
|
|
264
269
|
post-compact PostCompact hook: keep that list as memories (a busy store leaves the save to
|
|
265
270
|
the next hippo sleep) and print one line saying how many
|
|
266
271
|
--log-file <p> Same log path as pre-compact (default: ~/.hippo/logs/pre-compact.log)`],
|
|
267
272
|
'capture-error': [`
|
|
268
273
|
capture-error Store a failed tool call as an error memory (reads the Claude Code
|
|
269
|
-
PostToolUseFailure hook payload on stdin; skips routine failures)
|
|
274
|
+
PostToolUseFailure hook payload on stdin; skips routine failures)
|
|
275
|
+
--runtime copilot The payload came from a Copilot hook: use the store of its cwd`],
|
|
270
276
|
'compact-resume': [`
|
|
271
277
|
compact-resume SessionStart(compact) hook: re-print the snapshot, if under 15 minutes old`],
|
|
272
278
|
'codex-run': [`
|
|
@@ -544,23 +550,28 @@ export const VERB_USAGE = {
|
|
|
544
550
|
--pinned-only Only inject pinned memories (used by UserPromptSubmit hook)
|
|
545
551
|
--include-recent <n> With --pinned-only, also inject the last N writes regardless of pinning
|
|
546
552
|
(the hook payload's "prompt" drives prompt recall instead of --include-recent when pinnedInject.promptRecall is on, the default)
|
|
547
|
-
--format <fmt> Output format: markdown (default), json,
|
|
553
|
+
--format <fmt> Output format: markdown (default), json, additional-context (Claude Code hook JSON),
|
|
554
|
+
or copilot (Copilot sessionStart hook JSON, from the store of the payload's cwd)
|
|
548
555
|
--framing <mode> Framing: observe (default), suggest, assert`],
|
|
549
556
|
hook: [`
|
|
550
557
|
hook <sub> [target] Manage framework integrations
|
|
551
558
|
hook list Show available hooks
|
|
552
|
-
hook install <target> Install hook (claude-code|codex|cursor|openclaw|opencode|pi)
|
|
559
|
+
hook install <target> Install hook (claude-code|codex|copilot|cursor|openclaw|opencode|pi)
|
|
553
560
|
claude-code adds 7 hooks to $CLAUDE_CONFIG_DIR/settings.json
|
|
554
561
|
(~/.claude by default); opencode installs a plugin; codex
|
|
555
562
|
adds 2 hooks to $CODEX_HOME/hooks.json (trust them once in /hooks) and
|
|
556
|
-
wraps the detected launcher in place;
|
|
557
|
-
|
|
558
|
-
|
|
563
|
+
wraps the detected launcher in place; copilot writes
|
|
564
|
+
hooks/hippo.json, the "hippo" MCP server and an instructions
|
|
565
|
+
block under $COPILOT_HOME (~/.copilot by default); all but
|
|
566
|
+
claude-code and copilot also patch an existing AGENTS.md
|
|
567
|
+
hook uninstall <target> Remove hook; for copilot, only what hippo wrote`],
|
|
559
568
|
setup: [`
|
|
560
569
|
setup One-shot: detect installed AI tools and install their hooks:
|
|
561
570
|
claude-code gets 7 hooks in $CLAUDE_CONFIG_DIR/settings.json
|
|
562
571
|
(~/.claude by default), opencode a plugin, codex 2 hooks in
|
|
563
|
-
its hooks.json plus a launcher wrapper
|
|
572
|
+
its hooks.json plus a launcher wrapper, copilot 4 hooks, the
|
|
573
|
+
MCP server and an instructions block under $COPILOT_HOME
|
|
574
|
+
(~/.copilot by default); other tools get a
|
|
564
575
|
hint. Then imports each agent's
|
|
565
576
|
user-level memories into the global store
|
|
566
577
|
--all Install for every JSON-hook tool, even if not detected
|
package/dist/db/open.d.ts
CHANGED
|
@@ -18,10 +18,14 @@ export declare const SLEEP_DB_WAIT_MS = 5000;
|
|
|
18
18
|
export declare function scopedBusyWait(): number | undefined;
|
|
19
19
|
/** Runs `fn` so that every hippo.db open inside it, across awaits, throws; otherwise a missed port would create and write a hippo.db nobody reads. */
|
|
20
20
|
export declare function withSqliteBlocked<T>(storeKind: string, fn: () => T): T;
|
|
21
|
-
/** Runs `fn` with hippo.db opens allowed again, for a store whose own methods are backed by hippo.db. */
|
|
21
|
+
/** Runs `fn` with hippo.db opens allowed again, for a store whose own methods are backed by hippo.db, or a copy that reads the old hippo.db. */
|
|
22
22
|
export declare function withSqliteAllowed<T>(fn: () => T): T;
|
|
23
23
|
/** First line of a best-effort catch around a hippo.db open: an unported path must fail closed, not fall back silently. */
|
|
24
24
|
export declare function rethrowIfSqliteBlocked<E>(err: E): void;
|
|
25
|
+
/** store init --db, store copy --db and serve --db write this file in the hippo root; its text is the store kind. */
|
|
26
|
+
export declare const OTHER_STORE_MARKER = "other-store";
|
|
27
|
+
/** Throws where hippo.db must not open; checked on every open, not cached, since the marker can appear while a process runs. */
|
|
28
|
+
export declare function assertSqliteAllowed(hippoRoot: string): void;
|
|
25
29
|
/** `busyWaitMs` shortens every lock wait of this open, for a hook that must finish inside its own timeout. */
|
|
26
30
|
export declare function openHippoDb(hippoRoot: string, opts?: {
|
|
27
31
|
busyWaitMs?: number;
|
package/dist/db/open.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
-
import {
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { errorMessage, log } from '../log.js';
|
|
3
5
|
import { DatabaseSync } from './sqlite.js';
|
|
4
6
|
import { tableExists } from './tables.js';
|
|
5
7
|
import { assertBinaryCompatible } from './migrate.js';
|
|
6
8
|
import { connectHippoDb, getHippoDbPath } from './connect.js';
|
|
7
9
|
import { currentRequestStores, isScopedHandle, runWithRequestStores } from './request-stores.js';
|
|
8
|
-
import { SqliteBlockedError } from './sqlite-blocked.js';
|
|
10
|
+
import { OtherStoreFolderError, SqliteBlockedError } from './sqlite-blocked.js';
|
|
9
11
|
export { getHippoDbPath };
|
|
10
12
|
/** Lock wait for hook commands: under the 5 s prompt-hook budget even after a few skipped writes, and far above a normal write's hold. */
|
|
11
13
|
export const HOOK_DB_WAIT_MS = 1000;
|
|
@@ -32,29 +34,48 @@ const sqliteBlockedBy = new AsyncLocalStorage();
|
|
|
32
34
|
export function withSqliteBlocked(storeKind, fn) {
|
|
33
35
|
return sqliteBlockedBy.run(storeKind, fn);
|
|
34
36
|
}
|
|
35
|
-
/** Runs `fn` with hippo.db opens allowed again, for a store whose own methods are backed by hippo.db. */
|
|
37
|
+
/** Runs `fn` with hippo.db opens allowed again, for a store whose own methods are backed by hippo.db, or a copy that reads the old hippo.db. */
|
|
36
38
|
export function withSqliteAllowed(fn) {
|
|
37
|
-
return sqliteBlockedBy.exit(fn);
|
|
39
|
+
return sqliteBlockedBy.exit(() => markerWaived.run(true, fn));
|
|
38
40
|
}
|
|
39
41
|
/** First line of a best-effort catch around a hippo.db open: an unported path must fail closed, not fall back silently. */
|
|
40
42
|
export function rethrowIfSqliteBlocked(err) {
|
|
41
43
|
if (err instanceof SqliteBlockedError)
|
|
42
44
|
throw err;
|
|
43
45
|
}
|
|
44
|
-
|
|
46
|
+
/** store init --db, store copy --db and serve --db write this file in the hippo root; its text is the store kind. */
|
|
47
|
+
export const OTHER_STORE_MARKER = 'other-store';
|
|
48
|
+
const markerWaived = new AsyncLocalStorage();
|
|
49
|
+
// An empty or unreadable marker still refuses: falling through would write the hippo.db the marker forbids.
|
|
50
|
+
function markerKind(marker) {
|
|
51
|
+
try {
|
|
52
|
+
return readFileSync(marker, 'utf8').trim() || 'unknown';
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
log.debug(`${marker} is unreadable, so its store kind reads as unknown: ${errorMessage(err)}`);
|
|
56
|
+
return 'unknown';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** Throws where hippo.db must not open; checked on every open, not cached, since the marker can appear while a process runs. */
|
|
60
|
+
export function assertSqliteAllowed(hippoRoot) {
|
|
45
61
|
const storeKind = sqliteBlockedBy.getStore();
|
|
46
62
|
if (storeKind !== undefined)
|
|
47
63
|
throw new SqliteBlockedError(storeKind);
|
|
64
|
+
if (markerWaived.getStore())
|
|
65
|
+
return;
|
|
66
|
+
const marker = join(hippoRoot, OTHER_STORE_MARKER);
|
|
67
|
+
if (existsSync(marker))
|
|
68
|
+
throw new OtherStoreFolderError(markerKind(marker), marker);
|
|
48
69
|
}
|
|
49
70
|
/** `busyWaitMs` shortens every lock wait of this open, for a hook that must finish inside its own timeout. */
|
|
50
71
|
export function openHippoDb(hippoRoot, opts) {
|
|
51
|
-
assertSqliteAllowed();
|
|
72
|
+
assertSqliteAllowed(hippoRoot);
|
|
52
73
|
const stores = currentRequestStores();
|
|
53
74
|
return stores ? stores.get(hippoRoot, opts) : connectHippoDb(hippoRoot, opts?.busyWaitMs);
|
|
54
75
|
}
|
|
55
76
|
/** Open an existing store without changing it: no mkdir, WAL switch, migration or mirror cleanup. Throws when hippo.db is missing. */
|
|
56
77
|
export function openHippoDbReadOnly(hippoRoot) {
|
|
57
|
-
assertSqliteAllowed();
|
|
78
|
+
assertSqliteAllowed(hippoRoot);
|
|
58
79
|
const db = new DatabaseSync(getHippoDbPath(hippoRoot), { readOnly: true });
|
|
59
80
|
try {
|
|
60
81
|
db.exec('PRAGMA busy_timeout = 5000');
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
/** Thrown by a hippo.db open inside a request served from another store: the code path is not ported to the store port yet. */
|
|
2
2
|
export declare class SqliteBlockedError extends Error {
|
|
3
3
|
readonly storeKind: string;
|
|
4
|
-
constructor(storeKind: string);
|
|
4
|
+
constructor(storeKind: string, message?: string);
|
|
5
|
+
}
|
|
6
|
+
/** A store that lacks a whole group of port methods; it maps to the same 501, and the log names the group instead of hippo.db. */
|
|
7
|
+
export declare class StoreNotPortedError extends SqliteBlockedError {
|
|
8
|
+
readonly group: string;
|
|
9
|
+
constructor(storeKind: string, group: string);
|
|
10
|
+
}
|
|
11
|
+
/** Thrown by a hippo.db open in a folder whose marker file names the store that holds its memories, from the CLI as from a request. */
|
|
12
|
+
export declare class OtherStoreFolderError extends SqliteBlockedError {
|
|
13
|
+
readonly markerPath: string;
|
|
14
|
+
constructor(storeKind: string, markerPath: string);
|
|
5
15
|
}
|
|
6
16
|
//# sourceMappingURL=sqlite-blocked.d.ts.map
|
|
@@ -2,10 +2,28 @@
|
|
|
2
2
|
/** Thrown by a hippo.db open inside a request served from another store: the code path is not ported to the store port yet. */
|
|
3
3
|
export class SqliteBlockedError extends Error {
|
|
4
4
|
storeKind;
|
|
5
|
-
constructor(storeKind) {
|
|
6
|
-
super(
|
|
5
|
+
constructor(storeKind, message = `hippo.db is not opened while the '${storeKind}' store serves this request; this code path is not ported to the store yet`) {
|
|
6
|
+
super(message);
|
|
7
7
|
this.storeKind = storeKind;
|
|
8
8
|
this.name = 'SqliteBlockedError';
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
|
+
/** A store that lacks a whole group of port methods; it maps to the same 501, and the log names the group instead of hippo.db. */
|
|
12
|
+
export class StoreNotPortedError extends SqliteBlockedError {
|
|
13
|
+
group;
|
|
14
|
+
constructor(storeKind, group) {
|
|
15
|
+
super(storeKind, `the '${storeKind}' store has no '${group}' group; this code path is not ported to the store yet`);
|
|
16
|
+
this.group = group;
|
|
17
|
+
this.name = 'StoreNotPortedError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** Thrown by a hippo.db open in a folder whose marker file names the store that holds its memories, from the CLI as from a request. */
|
|
21
|
+
export class OtherStoreFolderError extends SqliteBlockedError {
|
|
22
|
+
markerPath;
|
|
23
|
+
constructor(storeKind, markerPath) {
|
|
24
|
+
super(storeKind, `This folder's memories live in the '${storeKind}' store, so hippo.db is not used here (marker: ${markerPath})`);
|
|
25
|
+
this.markerPath = markerPath;
|
|
26
|
+
this.name = 'OtherStoreFolderError';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
11
29
|
//# sourceMappingURL=sqlite-blocked.js.map
|
package/dist/db.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export { isSqliteBusy, isStoreBusy, StoreBusyError, STORE_BUSY_MESSAGE, execWith
|
|
|
3
3
|
export { getSchemaVersion, getMeta, setMeta, isFtsAvailable } from './db/meta.js';
|
|
4
4
|
export { countTableRows, pruneConsolidationRuns } from './db/tables.js';
|
|
5
5
|
export { getCurrentSchemaVersion, IncompatibleBinaryError, ftsRowCounts, repairFtsDrift } from './db/migrate.js';
|
|
6
|
-
export { getHippoDbPath, HOOK_DB_WAIT_MS, noteStoreBusy, withSharedStoreHandles, SERVER_DB_WAIT_MS, SLEEP_DB_WAIT_MS, scopedBusyWait, withSqliteBlocked, withSqliteAllowed, rethrowIfSqliteBlocked, openHippoDb, openHippoDbReadOnly, closeHippoDb, } from './db/open.js';
|
|
6
|
+
export { getHippoDbPath, HOOK_DB_WAIT_MS, noteStoreBusy, withSharedStoreHandles, SERVER_DB_WAIT_MS, SLEEP_DB_WAIT_MS, scopedBusyWait, withSqliteBlocked, withSqliteAllowed, rethrowIfSqliteBlocked, OTHER_STORE_MARKER, openHippoDb, openHippoDbReadOnly, closeHippoDb, } from './db/open.js';
|
|
7
7
|
export { RequestStores, runWithRequestStores, currentRequestStores, outsideRequestStores } from './db/request-stores.js';
|
|
8
|
-
export { SqliteBlockedError } from './db/sqlite-blocked.js';
|
|
8
|
+
export { OtherStoreFolderError, SqliteBlockedError } from './db/sqlite-blocked.js';
|
|
9
9
|
//# sourceMappingURL=db.d.ts.map
|
package/dist/db.js
CHANGED
|
@@ -2,7 +2,7 @@ export { isSqliteBusy, isStoreBusy, StoreBusyError, STORE_BUSY_MESSAGE, execWith
|
|
|
2
2
|
export { getSchemaVersion, getMeta, setMeta, isFtsAvailable } from './db/meta.js';
|
|
3
3
|
export { countTableRows, pruneConsolidationRuns } from './db/tables.js';
|
|
4
4
|
export { getCurrentSchemaVersion, IncompatibleBinaryError, ftsRowCounts, repairFtsDrift } from './db/migrate.js';
|
|
5
|
-
export { getHippoDbPath, HOOK_DB_WAIT_MS, noteStoreBusy, withSharedStoreHandles, SERVER_DB_WAIT_MS, SLEEP_DB_WAIT_MS, scopedBusyWait, withSqliteBlocked, withSqliteAllowed, rethrowIfSqliteBlocked, openHippoDb, openHippoDbReadOnly, closeHippoDb, } from './db/open.js';
|
|
5
|
+
export { getHippoDbPath, HOOK_DB_WAIT_MS, noteStoreBusy, withSharedStoreHandles, SERVER_DB_WAIT_MS, SLEEP_DB_WAIT_MS, scopedBusyWait, withSqliteBlocked, withSqliteAllowed, rethrowIfSqliteBlocked, OTHER_STORE_MARKER, openHippoDb, openHippoDbReadOnly, closeHippoDb, } from './db/open.js';
|
|
6
6
|
export { RequestStores, runWithRequestStores, currentRequestStores, outsideRequestStores } from './db/request-stores.js';
|
|
7
|
-
export { SqliteBlockedError } from './db/sqlite-blocked.js';
|
|
7
|
+
export { OtherStoreFolderError, SqliteBlockedError } from './db/sqlite-blocked.js';
|
|
8
8
|
//# sourceMappingURL=db.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { MemoryEntry } from './memory.js';
|
|
2
2
|
import { type PromptRecallGate } from './prompt-recall.js';
|
|
3
|
-
export type DeliveryRuntime = 'claude-code' | 'codex' | 'unknown';
|
|
3
|
+
export type DeliveryRuntime = 'claude-code' | 'codex' | 'copilot' | 'unknown';
|
|
4
4
|
export type DeliveryEventType = 'prompt-submit' | 'pinned-manual';
|
|
5
5
|
export type DeliverySurface = 'hook' | 'context';
|
|
6
6
|
export type DeliveryWriteStore = 'local' | 'global';
|
|
@@ -121,6 +121,8 @@ export interface DeliveryRecorderInit {
|
|
|
121
121
|
tenantId: string;
|
|
122
122
|
stdinText?: string;
|
|
123
123
|
envSessionId?: string;
|
|
124
|
+
/** Set by the caller's runtime flag; Copilot payloads carry hook_event_name too, so inference would say claude-code. */
|
|
125
|
+
runtime?: DeliveryRuntime;
|
|
124
126
|
}
|
|
125
127
|
/** A recorder for one call; every observer method is guarded, and a throw marks it broken instead of escaping. */
|
|
126
128
|
export declare function createDeliveryRecorder(init: DeliveryRecorderInit): DeliveryRecorder;
|
|
@@ -92,7 +92,7 @@ function buildEvent(init, payload, state, ts, startedMs) {
|
|
|
92
92
|
return {
|
|
93
93
|
ts,
|
|
94
94
|
tenantId: init.tenantId,
|
|
95
|
-
runtime: payload.hostTurnId !== null ? 'codex' : payload.hookEvent !== null ? 'claude-code' : 'unknown',
|
|
95
|
+
runtime: init.runtime ?? (payload.hostTurnId !== null ? 'codex' : payload.hookEvent !== null ? 'claude-code' : 'unknown'),
|
|
96
96
|
eventType: payload.hookEvent === 'UserPromptSubmit' ? 'prompt-submit' : 'pinned-manual',
|
|
97
97
|
surface: 'hook',
|
|
98
98
|
storeHash: init.storeHash,
|
package/dist/embeddings.d.ts
CHANGED
|
@@ -64,8 +64,19 @@ export declare function embeddingInputText(entry: {
|
|
|
64
64
|
* consistent with the compare side in `embeddingModelRequiresReindex`.
|
|
65
65
|
*/
|
|
66
66
|
export declare function saveStoredEmbeddingModel(hippoRoot: string, model: string): void;
|
|
67
|
+
/** What a store's vector index was built by: the stored identity, and whether any vector exists. */
|
|
68
|
+
export interface EmbeddingIndexState {
|
|
69
|
+
readonly storedModel: string | null;
|
|
70
|
+
readonly hasVectors: boolean;
|
|
71
|
+
}
|
|
72
|
+
/** The model the index was built by: the stored identity, else the default model when vectors exist. */
|
|
73
|
+
export declare function indexedModel(state: EmbeddingIndexState): string | null;
|
|
74
|
+
/** The one rebuild rule every store applies to its indexed model. */
|
|
75
|
+
export declare function indexNeedsRebuild(indexed: string | null, providerId: string): boolean;
|
|
67
76
|
export declare function resolveIndexedEmbeddingModel(hippoRoot: string, index?: Record<string, number[]>): string | null;
|
|
68
77
|
export declare function embeddingModelRequiresReindex(hippoRoot: string, model: string, index?: Record<string, number[]>): boolean;
|
|
78
|
+
/** hippo.db's index state, the meta row and the EXISTS on one handle. */
|
|
79
|
+
export declare function embeddingIndexStateAt(hippoRoot: string): EmbeddingIndexState;
|
|
69
80
|
/**
|
|
70
81
|
* Cosine similarity between two vectors. Handles unnormalized vectors.
|
|
71
82
|
* Returns 0 for empty or mismatched vectors.
|
|
@@ -75,8 +86,6 @@ export declare function cosineSimilarity(a: number[], b: number[]): number;
|
|
|
75
86
|
export declare function loadEmbeddingIndex(hippoRoot: string): Record<string, number[]>;
|
|
76
87
|
/** Stored vectors for `ids` only. */
|
|
77
88
|
export declare function loadStoredVectors(hippoRoot: string, ids: readonly string[]): Map<string, number[]>;
|
|
78
|
-
/** Whether the store holds any vector at all. */
|
|
79
|
-
export declare function hasEmbeddings(hippoRoot: string): boolean;
|
|
80
89
|
/** Replace every stored vector with `index`; `model` defaults to the stored index identity. */
|
|
81
90
|
export declare function saveEmbeddingIndex(hippoRoot: string, index: Record<string, number[]>, model?: string): void;
|
|
82
91
|
/**
|