@oortmem/claude-code 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/deps.d.ts +28 -1
- package/dist/hooks.js +60 -21
- package/dist/index.d.ts +1 -1
- package/dist/queue.d.ts +11 -0
- package/dist/queue.js +37 -13
- package/package.json +3 -3
package/dist/deps.d.ts
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
|
-
import type { DecisionController, Embedder, MemoryStore, MemoryWriter, Reranker, ResolvedConfig, Scope } from '@oortmem/core';
|
|
1
|
+
import type { DecisionController, Embedder, Memory, MemoryStore, MemoryWriter, Observation, RecallResult, Reranker, ResolvedConfig, Scope } from '@oortmem/core';
|
|
2
2
|
import type { HookLogger } from './log.js';
|
|
3
|
+
/**
|
|
4
|
+
* Memory that lives somewhere else (Oort Cloud). When set, the hooks recall and save through it
|
|
5
|
+
* instead of the local engine; the local store still keeps the Stop cursors and the queue.
|
|
6
|
+
* Every method rejects on failure; the hooks then inject nothing (they never fall back to a local
|
|
7
|
+
* store that may hold nothing, or stale memories).
|
|
8
|
+
*/
|
|
9
|
+
export interface RemoteMemory {
|
|
10
|
+
recall(q: {
|
|
11
|
+
text: string;
|
|
12
|
+
projectId?: string;
|
|
13
|
+
}): Promise<RecallResult>;
|
|
14
|
+
/** Pinned and hot memories, for SessionStart. */
|
|
15
|
+
sessionMemories(q: {
|
|
16
|
+
projectId?: string;
|
|
17
|
+
}): Promise<Memory[]>;
|
|
18
|
+
/**
|
|
19
|
+
* Saves one turn. A rejection with `holdQueue: true` (sign-in, plan, rate limit, outage) keeps
|
|
20
|
+
* the line queued untouched and stops the drain run; any other rejection counts as an attempt.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Saves one turn. `turnRef` is stable for the turn across retries, so a drain that crashes after
|
|
24
|
+
* the save and runs again cannot save it twice.
|
|
25
|
+
*/
|
|
26
|
+
observe(obs: Observation, projectId?: string, turnRef?: string): Promise<void>;
|
|
27
|
+
}
|
|
3
28
|
export interface HookDeps {
|
|
4
29
|
store: MemoryStore;
|
|
5
30
|
embed?: Embedder;
|
|
@@ -11,4 +36,6 @@ export interface HookDeps {
|
|
|
11
36
|
now?: () => Date;
|
|
12
37
|
log: HookLogger;
|
|
13
38
|
spawnDrain?: () => void;
|
|
39
|
+
/** Oort Cloud mode: recall and save remotely (see `RemoteMemory`). */
|
|
40
|
+
remote?: RemoteMemory;
|
|
14
41
|
}
|
package/dist/hooks.js
CHANGED
|
@@ -76,30 +76,55 @@ export async function handleUserPromptSubmit(input, deps) {
|
|
|
76
76
|
const degraded = [];
|
|
77
77
|
const t = performance.now();
|
|
78
78
|
let result;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const r = await withTimeout(full, RECALL_TIMEOUT_MS);
|
|
90
|
-
if (r === TIMEOUT) {
|
|
91
|
-
degraded.push('timeout');
|
|
92
|
-
result = await recall(q, { store, config, now });
|
|
79
|
+
if (deps.remote !== undefined) {
|
|
80
|
+
// Oort Cloud: the cloud recalls. A timeout or an error injects nothing, never a local guess.
|
|
81
|
+
const remote = deps.remote.recall({
|
|
82
|
+
text: input.prompt,
|
|
83
|
+
...(scope.projectId !== undefined && { projectId: scope.projectId }),
|
|
84
|
+
});
|
|
85
|
+
remote.catch(() => { });
|
|
86
|
+
let r;
|
|
87
|
+
try {
|
|
88
|
+
r = await withTimeout(remote, RECALL_TIMEOUT_MS);
|
|
93
89
|
}
|
|
94
|
-
|
|
95
|
-
|
|
90
|
+
catch {
|
|
91
|
+
r = undefined;
|
|
96
92
|
}
|
|
93
|
+
entry.timings['recallMs'] = performance.now() - t;
|
|
94
|
+
if (r === TIMEOUT || r === undefined) {
|
|
95
|
+
entry.degraded = [r === TIMEOUT ? 'cloud-timeout' : 'cloud-error'];
|
|
96
|
+
entry.counts = { memories: 0, chars: 0 };
|
|
97
|
+
entry.outcome = 'empty';
|
|
98
|
+
return EMPTY;
|
|
99
|
+
}
|
|
100
|
+
result = r;
|
|
97
101
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
102
|
+
else {
|
|
103
|
+
const full = recall(q, {
|
|
104
|
+
store,
|
|
105
|
+
config,
|
|
106
|
+
now,
|
|
107
|
+
...(deps.embed !== undefined && { embed: deps.embed }),
|
|
108
|
+
...(deps.rerank !== undefined && { rerank: deps.rerank }),
|
|
109
|
+
});
|
|
110
|
+
// The full recall may lose the race and fail later; nobody awaits it then.
|
|
111
|
+
full.catch(() => { });
|
|
112
|
+
try {
|
|
113
|
+
const r = await withTimeout(full, RECALL_TIMEOUT_MS);
|
|
114
|
+
if (r === TIMEOUT) {
|
|
115
|
+
degraded.push('timeout');
|
|
116
|
+
result = await recall(q, { store, config, now });
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
result = r;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
degraded.push('error');
|
|
124
|
+
result = await recall(q, { store, config, now });
|
|
125
|
+
}
|
|
126
|
+
entry.timings['recallMs'] = performance.now() - t;
|
|
101
127
|
}
|
|
102
|
-
entry.timings['recallMs'] = performance.now() - t;
|
|
103
128
|
// Per-stage timings from recall (numbers only), so slow stages show in the log (CON-57).
|
|
104
129
|
for (const [k, ms] of Object.entries(result.timings)) {
|
|
105
130
|
if (k !== 'totalMs' && k !== 'gateMs')
|
|
@@ -179,7 +204,21 @@ export async function handleSessionStart(input, deps) {
|
|
|
179
204
|
entry.project = scope.projectId ?? null;
|
|
180
205
|
const now = deps.now?.() ?? new Date();
|
|
181
206
|
const { config } = deps;
|
|
182
|
-
|
|
207
|
+
let current;
|
|
208
|
+
if (deps.remote !== undefined) {
|
|
209
|
+
try {
|
|
210
|
+
current = await deps.remote.sessionMemories(scope.projectId !== undefined ? { projectId: scope.projectId } : {});
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
entry.degraded = ['cloud-error'];
|
|
214
|
+
entry.counts = { memories: 0, chars: 0 };
|
|
215
|
+
entry.outcome = 'empty';
|
|
216
|
+
return EMPTY;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
current = await deps.store.list(grantFor(scope));
|
|
221
|
+
}
|
|
183
222
|
const picked = current
|
|
184
223
|
.filter((m) => m.pinned || m.tier === 'hot')
|
|
185
224
|
.map((m) => ({
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { HookDeps } from './deps.js';
|
|
1
|
+
export type { HookDeps, RemoteMemory } from './deps.js';
|
|
2
2
|
export { createHookLogger, HOOK_LOG_MAX_BYTES, type HookLogEntry, type HookLogger } from './log.js';
|
|
3
3
|
export { HARNESS_TAGS, OORT_MEMORY_TAG, parseTranscript, parseTranscriptDetailed, selectAfterCursor, turnsToObservations, type DetailedTurn, type Turn, } from './transcript.js';
|
|
4
4
|
export { cursorKey, filterFor, grantFor, handleSessionStart, handleStop, handleUserPromptSubmit, RECALL_TIMEOUT_MS, runHook, STOP_MAX_OBSERVATIONS, type HookEvent, type HookOutput, } from './hooks.js';
|
package/dist/queue.d.ts
CHANGED
|
@@ -58,6 +58,17 @@ export interface DrainResult {
|
|
|
58
58
|
failed: number;
|
|
59
59
|
remaining: number;
|
|
60
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* A stable id for a queued turn: `<sessionId>:<transcript turn>` when both are known, else the
|
|
63
|
+
* queue line's own id (kept across retries, since a failed line stays in its file).
|
|
64
|
+
*/
|
|
65
|
+
export declare function turnRefOf(line: {
|
|
66
|
+
id: string;
|
|
67
|
+
obs: {
|
|
68
|
+
sessionId?: string;
|
|
69
|
+
turnRef?: string;
|
|
70
|
+
};
|
|
71
|
+
}): string;
|
|
61
72
|
/**
|
|
62
73
|
* Observes queued lines, oldest file first, at most 50 per run, under the
|
|
63
74
|
* drain lock. A line that observed is removed; one whose observe threw keeps
|
package/dist/queue.js
CHANGED
|
@@ -162,6 +162,15 @@ function acquireDrainLock(lock) {
|
|
|
162
162
|
return null;
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* A stable id for a queued turn: `<sessionId>:<transcript turn>` when both are known, else the
|
|
167
|
+
* queue line's own id (kept across retries, since a failed line stays in its file).
|
|
168
|
+
*/
|
|
169
|
+
export function turnRefOf(line) {
|
|
170
|
+
return line.obs.sessionId !== undefined && line.obs.turnRef !== undefined
|
|
171
|
+
? `${line.obs.sessionId}:${line.obs.turnRef}`
|
|
172
|
+
: line.id;
|
|
173
|
+
}
|
|
165
174
|
/**
|
|
166
175
|
* Observes queued lines, oldest file first, at most 50 per run, under the
|
|
167
176
|
* drain lock. A line that observed is removed; one whose observe threw keeps
|
|
@@ -172,8 +181,9 @@ export async function drainQueue(deps) {
|
|
|
172
181
|
const t0 = performance.now();
|
|
173
182
|
const dir = queueDir(deps.config.home);
|
|
174
183
|
const logBase = { event: 'drain', session: null, project: null };
|
|
175
|
-
const { embed, decide, write } = deps;
|
|
176
|
-
if (
|
|
184
|
+
const { embed, decide, write, remote } = deps;
|
|
185
|
+
if (remote === undefined &&
|
|
186
|
+
(embed === undefined || decide === undefined || write === undefined)) {
|
|
177
187
|
const remaining = countLines(dir);
|
|
178
188
|
deps.log.write({
|
|
179
189
|
...logBase,
|
|
@@ -209,6 +219,7 @@ export async function drainQueue(deps) {
|
|
|
209
219
|
let budget = DRAIN_MAX_PER_RUN;
|
|
210
220
|
let consecutive = 0;
|
|
211
221
|
let deadCount = 0;
|
|
222
|
+
let held = false;
|
|
212
223
|
const deadIds = [];
|
|
213
224
|
try {
|
|
214
225
|
fs.writeSync(lock, `${process.pid}\n`);
|
|
@@ -244,19 +255,32 @@ export async function drainQueue(deps) {
|
|
|
244
255
|
}
|
|
245
256
|
budget--;
|
|
246
257
|
try {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
258
|
+
if (remote !== undefined) {
|
|
259
|
+
await remote.observe(line.obs, line.scope.projectId, turnRefOf(line));
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
await observe(line.obs, line.scope, {
|
|
263
|
+
store: deps.store,
|
|
264
|
+
embed: embed,
|
|
265
|
+
decide: decide,
|
|
266
|
+
write: write,
|
|
267
|
+
config: deps.config,
|
|
268
|
+
now: deps.now?.() ?? new Date(),
|
|
269
|
+
harness: 'claude-code',
|
|
270
|
+
});
|
|
271
|
+
}
|
|
256
272
|
processed++;
|
|
257
273
|
consecutive = 0;
|
|
258
274
|
}
|
|
259
|
-
catch {
|
|
275
|
+
catch (e) {
|
|
276
|
+
if (e?.holdQueue === true) {
|
|
277
|
+
// The account cannot save right now (signed out, no plan, a limit, an outage): keep
|
|
278
|
+
// this line and the rest exactly as they are and try again on a later drain.
|
|
279
|
+
held = true;
|
|
280
|
+
keep.push(raw);
|
|
281
|
+
budget = 0;
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
260
284
|
failed++;
|
|
261
285
|
consecutive++;
|
|
262
286
|
const next = JSON.stringify({ ...line, attempts: line.attempts + 1 });
|
|
@@ -318,7 +342,7 @@ export async function drainQueue(deps) {
|
|
|
318
342
|
const remaining = countLines(dir);
|
|
319
343
|
deps.log.write({
|
|
320
344
|
...logBase,
|
|
321
|
-
outcome: 'ok',
|
|
345
|
+
outcome: held ? 'held' : 'ok',
|
|
322
346
|
counts: { processed, failed, remaining },
|
|
323
347
|
timings: { totalMs: performance.now() - t0 },
|
|
324
348
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oortmem/claude-code",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Oort for Claude Code: hooks that recall and save memories, and transcript import.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"oort",
|
|
@@ -46,11 +46,11 @@
|
|
|
46
46
|
"access": "public"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@oortmem/core": "0.1.
|
|
49
|
+
"@oortmem/core": "0.1.1",
|
|
50
50
|
"zod": "4.6.7"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@oortmem/store-sqlite": "0.1.
|
|
53
|
+
"@oortmem/store-sqlite": "0.1.1",
|
|
54
54
|
"vitest": "^5.0.5"
|
|
55
55
|
},
|
|
56
56
|
"scripts": {
|