hippo-memory 1.66.0 → 1.68.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 +16 -11
- package/dist/api/auth.js +117 -61
- package/dist/api/types.d.ts +9 -0
- package/dist/auth.d.ts +22 -1
- package/dist/auth.js +20 -8
- 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/sqlite-blocked.js +1 -1
- package/dist/delivery-recorder.d.ts +3 -1
- package/dist/delivery-recorder.js +1 -1
- package/dist/embeddings.d.ts +10 -10
- package/dist/embeddings.js +103 -18
- 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/search/vector.js +2 -5
- package/dist/server/routes/admin.js +3 -3
- package/dist/server/types.d.ts +5 -4
- package/dist/server.d.ts +11 -8
- package/dist/server.js +20 -19
- 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/key-writes.d.ts +9 -0
- package/dist/store/key-writes.js +30 -0
- package/dist/store/vector-writes.d.ts +6 -0
- package/dist/store/vector-writes.js +59 -0
- package/dist/store-port.d.ts +101 -9
- package/dist/store-port.js +53 -2
- 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
|
@@ -5,6 +5,7 @@ import { isObjectLike, isStringValue } from '../capture-contract.js';
|
|
|
5
5
|
import { errorMessage } from '../log.js';
|
|
6
6
|
import { PRE_COMPACT_TAIL_BYTES, readTranscriptTail, truncateCodePointSafe } from '../transcript-tail.js';
|
|
7
7
|
import { humanUserText, summariseTranscript } from './transcript.js';
|
|
8
|
+
import { copilotTurn } from './copilot-transcript.js';
|
|
8
9
|
export const PRE_COMPACT_TASK_CAP = 200;
|
|
9
10
|
export const PRE_COMPACT_SUMMARY_CAP = 2000;
|
|
10
11
|
export const PRE_COMPACT_NEXT_STEP_CAP = 500;
|
|
@@ -48,7 +49,7 @@ export function truncateKeepNewest(text, maxChars) {
|
|
|
48
49
|
start = nl + 1;
|
|
49
50
|
return TRIM_MARKER + text.slice(start);
|
|
50
51
|
}
|
|
51
|
-
/** Most recent plain-text user message in a JSONL tail
|
|
52
|
+
/** Most recent plain-text user message in a JSONL tail, from a Claude Code or a Copilot transcript. */
|
|
52
53
|
function lastPlainUserMessage(jsonl) {
|
|
53
54
|
const lines = jsonl.split('\n').filter((l) => l.trim());
|
|
54
55
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
@@ -61,7 +62,12 @@ function lastPlainUserMessage(jsonl) {
|
|
|
61
62
|
}
|
|
62
63
|
if (!isObjectLike(entry))
|
|
63
64
|
continue;
|
|
64
|
-
if (!('type' in entry)
|
|
65
|
+
if (!('type' in entry))
|
|
66
|
+
continue;
|
|
67
|
+
const copilot = copilotTurn(entry);
|
|
68
|
+
if (copilot?.role === 'user')
|
|
69
|
+
return copilot.text;
|
|
70
|
+
if (entry.type !== 'user')
|
|
65
71
|
continue;
|
|
66
72
|
// An isMeta user line holds an earlier compact summary and an isSidechain one is a sub-agent turn; neither is the human's task.
|
|
67
73
|
if (('isMeta' in entry && entry.isMeta === true) || ('isSidechain' in entry && entry.isSidechain === true))
|
|
@@ -88,7 +94,12 @@ function lastAssistantTextBlock(jsonl) {
|
|
|
88
94
|
}
|
|
89
95
|
if (!isObjectLike(entry))
|
|
90
96
|
continue;
|
|
91
|
-
if (!('type' in entry)
|
|
97
|
+
if (!('type' in entry))
|
|
98
|
+
continue;
|
|
99
|
+
const copilot = copilotTurn(entry);
|
|
100
|
+
if (copilot?.role === 'assistant')
|
|
101
|
+
return copilot.text;
|
|
102
|
+
if (entry.type !== 'assistant')
|
|
92
103
|
continue;
|
|
93
104
|
// Same meta/sidechain guard as lastPlainUserMessage: sub-agent turns
|
|
94
105
|
// (isSidechain) are not this session's next step.
|
|
@@ -42,6 +42,8 @@ export interface ProgressCursor {
|
|
|
42
42
|
export declare function isStringValue<T>(value: T): value is T & string;
|
|
43
43
|
/** An object guard that avoids `typeof`; arrays count as objects, as they do for `typeof`. */
|
|
44
44
|
export declare function isObjectLike<T>(value: T): value is T & object;
|
|
45
|
-
/**
|
|
46
|
-
export
|
|
45
|
+
/** The hook runtimes a `--runtime` flag names; absent means Claude Code. */
|
|
46
|
+
export type HookRuntime = 'claude-code' | 'copilot';
|
|
47
|
+
/** Reads a Claude Code PreCompact payload, or a Copilot one after stdin.ts mapped it to the same snake_case keys; only an empty stdin counts as a manual run. */
|
|
48
|
+
export declare function readClaudeCodePreCompact(stdinText: string | undefined, timedOut: boolean, runtime?: HookRuntime): CaptureReceipt;
|
|
47
49
|
//# sourceMappingURL=capture-contract.d.ts.map
|
package/dist/capture-contract.js
CHANGED
|
@@ -8,13 +8,13 @@ export function isStringValue(value) {
|
|
|
8
8
|
export function isObjectLike(value) {
|
|
9
9
|
return value !== null && value instanceof Object;
|
|
10
10
|
}
|
|
11
|
-
/** Reads a Claude Code PreCompact payload; only an empty stdin counts as a manual run. */
|
|
12
|
-
export function readClaudeCodePreCompact(stdinText, timedOut) {
|
|
11
|
+
/** Reads a Claude Code PreCompact payload, or a Copilot one after stdin.ts mapped it to the same snake_case keys; only an empty stdin counts as a manual run. */
|
|
12
|
+
export function readClaudeCodePreCompact(stdinText, timedOut, runtime = 'claude-code') {
|
|
13
13
|
const empty = !stdinText || stdinText.trim() === '';
|
|
14
14
|
if (timedOut && empty) {
|
|
15
15
|
return { status: 'unavailable', reason: 'no PreCompact payload arrived before the stdin wait window closed' };
|
|
16
16
|
}
|
|
17
|
-
const base = { runtime
|
|
17
|
+
const base = { runtime, event: 'pre-compact' };
|
|
18
18
|
if (empty) {
|
|
19
19
|
return { status: 'received', input: { ...base, manual: true, sessionId: null, cwd: null, transcriptPath: null, trigger: null } };
|
|
20
20
|
}
|
package/dist/cli/context.js
CHANGED
|
@@ -7,14 +7,14 @@ import { isGlobalStoreRoot } from '../project-identity.js';
|
|
|
7
7
|
import { autoDetectContext } from '../context-auto.js';
|
|
8
8
|
import { detectScope } from '../scope.js';
|
|
9
9
|
import { ledgerRoot, withLedgerDb } from '../ledger-db.js';
|
|
10
|
-
import {
|
|
10
|
+
import { readHookStdin } from '../stdin.js';
|
|
11
11
|
import * as api from '../api.js';
|
|
12
12
|
import { resolveTenantId } from '../tenant.js';
|
|
13
13
|
import { renderAmbientSummary } from '../ambient.js';
|
|
14
14
|
import { contextBlockLines, contextCost, crossProjectLines, settleTokens } from '../context-render.js';
|
|
15
|
-
import { additionalContextOutput, flushDeliveryRecorder, hasContextData, toRenderItems, } from '../prompt-hook.js';
|
|
15
|
+
import { additionalContextOutput, flushDeliveryRecorder, hasContextData, sessionStartEnvelope, toRenderItems, } from '../prompt-hook.js';
|
|
16
16
|
import { printError } from './output.js';
|
|
17
|
-
import { parseLimitFlag, parseCountFlag, parseBudgetFlag, requireInit, printActiveTaskSnapshot, printSessionEvents, printHandoff, hostSessionId, captureConsole, hookStoreRoot, runHookWithStores, inPilotHoldout, } from './shared.js';
|
|
17
|
+
import { parseLimitFlag, parseCountFlag, parseBudgetFlag, requireInit, printActiveTaskSnapshot, printSessionEvents, printHandoff, hostSessionId, captureConsole, hookStoreRoot, hookRuntime, payloadCwdRoot, runHookWithStores, inPilotHoldout, } from './shared.js';
|
|
18
18
|
export async function cmdContext(hippoRoot, args, flags, stdinText) {
|
|
19
19
|
const rec = startDeliveryRecorder(hippoRoot, flags, stdinText);
|
|
20
20
|
// No try/finally: a render throw keeps its own exit code and writes no event.
|
|
@@ -37,6 +37,7 @@ function startDeliveryRecorder(hippoRoot, flags, stdinText) {
|
|
|
37
37
|
tenantId: resolveTenantId({}),
|
|
38
38
|
stdinText,
|
|
39
39
|
envSessionId: hostSessionId(),
|
|
40
|
+
runtime: hookRuntime(flags) === 'copilot' ? 'copilot' : undefined,
|
|
40
41
|
});
|
|
41
42
|
}
|
|
42
43
|
catch (error) {
|
|
@@ -102,11 +103,12 @@ async function renderContext(hippoRoot, args, flags, stdinText, rec) {
|
|
|
102
103
|
rec?.delivered({ state: 'empty' });
|
|
103
104
|
return;
|
|
104
105
|
}
|
|
105
|
-
const
|
|
106
|
+
const envelope = format === 'copilot' ? sessionStartEnvelope(stdinText) : undefined;
|
|
107
|
+
const view = { hippoRoot, tenantId: ctx.tenantId, ledgerSessionId, payloadSessionId, pinnedOnly, framing, rec, result, envelope };
|
|
106
108
|
if (format === 'json') {
|
|
107
109
|
renderContextJson(view, query);
|
|
108
110
|
}
|
|
109
|
-
else if (format === 'additional-context') {
|
|
111
|
+
else if (format === 'additional-context' || format === 'copilot') {
|
|
110
112
|
const stdout = additionalContextOutput(view);
|
|
111
113
|
if (stdout)
|
|
112
114
|
process.stdout.write(stdout);
|
|
@@ -145,7 +147,7 @@ function buildContextOpts(flags, input) {
|
|
|
145
147
|
currentSessionId: input.session.currentSessionId,
|
|
146
148
|
prompt: input.session.prompt,
|
|
147
149
|
// JSON is budgeted as the markdown it stands for, so one budget picks the same memories in every format.
|
|
148
|
-
cost: contextCost(input.format === 'additional-context' ? 'additional-context' : 'markdown', input.framing),
|
|
150
|
+
cost: contextCost(input.format === 'additional-context' || input.format === 'copilot' ? 'additional-context' : 'markdown', input.framing),
|
|
149
151
|
deliveryObserver: input.rec ?? undefined,
|
|
150
152
|
};
|
|
151
153
|
}
|
|
@@ -225,7 +227,8 @@ export function printContextMarkdown(items, totalTokens, framing = 'observe', op
|
|
|
225
227
|
}
|
|
226
228
|
export async function handleContext({ hippoRoot, args, flags }) {
|
|
227
229
|
// Bounded, not a TTY guard: the hot stdin path and a manual run share this one command.
|
|
228
|
-
const { text: stdinText } = await
|
|
229
|
-
|
|
230
|
+
const { text: stdinText } = await readHookStdin();
|
|
231
|
+
const root = payloadCwdRoot(hippoRoot, stdinText, hookRuntime(flags));
|
|
232
|
+
await runHookWithStores(() => cmdContext(hookStoreRoot(root), args, flags, stdinText));
|
|
230
233
|
}
|
|
231
234
|
//# sourceMappingURL=context.js.map
|
|
@@ -15,4 +15,6 @@ export declare function hippoBlock(text: string): {
|
|
|
15
15
|
inner: string;
|
|
16
16
|
owner?: string;
|
|
17
17
|
} | null;
|
|
18
|
+
/** `text` with every hippo block and its markers removed, trimmed. */
|
|
19
|
+
export declare function withoutHookBlock(text: string): string;
|
|
18
20
|
//# sourceMappingURL=hook-blocks.d.ts.map
|
package/dist/cli/hook-blocks.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// The hippo block each agent's instruction file carries, and how init recognises one it wrote earlier.
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
|
+
import { escapeRegex } from '../escape.js';
|
|
3
4
|
export const HOOK_MARKERS = {
|
|
4
5
|
start: '<!-- hippo:start -->',
|
|
5
6
|
end: '<!-- hippo:end -->',
|
|
@@ -230,4 +231,9 @@ export function hippoBlock(text) {
|
|
|
230
231
|
const owner = Object.keys(HOOKS).find((k) => HOOKS[k].content === inner) ?? SHIPPED_HOOK_HASHES.get(createHash('sha256').update(inner).digest('hex'));
|
|
231
232
|
return { start, end, eol: raw.includes('\r\n') ? '\r\n' : '\n', inner, owner };
|
|
232
233
|
}
|
|
234
|
+
/** `text` with every hippo block and its markers removed, trimmed. */
|
|
235
|
+
export function withoutHookBlock(text) {
|
|
236
|
+
const re = new RegExp(`\\n?${escapeRegex(HOOK_MARKERS.start)}[\\s\\S]*?${escapeRegex(HOOK_MARKERS.end)}\\n?`, 'g');
|
|
237
|
+
return text.replace(re, '\n').replace(/\n{3,}/g, '\n\n').trim();
|
|
238
|
+
}
|
|
233
239
|
//# sourceMappingURL=hook-blocks.js.map
|
|
@@ -20,7 +20,7 @@ export declare function cmdCodexRun(hippoRoot: string, args: string[]): void;
|
|
|
20
20
|
export declare function cmdCodexSessionEndWorker(hippoRoot: string, flags: Record<string, string | boolean | string[]>): Promise<void>;
|
|
21
21
|
export declare function handlePreCompact({ hippoRoot, flags }: CommandContext): Promise<void>;
|
|
22
22
|
export declare function handlePostCompact({ hippoRoot, flags }: CommandContext): Promise<void>;
|
|
23
|
-
export declare function handleCaptureError({ hippoRoot }: CommandContext): Promise<void>;
|
|
23
|
+
export declare function handleCaptureError({ hippoRoot, flags }: CommandContext): Promise<void>;
|
|
24
24
|
export declare function handleCompactResume({ hippoRoot }: CommandContext): Promise<void>;
|
|
25
25
|
export declare function handleCapture({ hippoRoot, flags }: CommandContext): Promise<void>;
|
|
26
26
|
//# sourceMappingURL=session-hooks.d.ts.map
|
|
@@ -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
|
|
@@ -12,7 +12,7 @@ export class SqliteBlockedError extends Error {
|
|
|
12
12
|
export class StoreNotPortedError extends SqliteBlockedError {
|
|
13
13
|
group;
|
|
14
14
|
constructor(storeKind, group) {
|
|
15
|
-
super(storeKind, `the '${storeKind}' store has no '${group}'
|
|
15
|
+
super(storeKind, `the '${storeKind}' store has no '${group}' group; this code path is not ported to the store yet`);
|
|
16
16
|
this.group = group;
|
|
17
17
|
this.name = 'StoreNotPortedError';
|
|
18
18
|
}
|
|
@@ -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,
|