@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 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
- const full = recall(q, {
80
- store,
81
- config,
82
- now,
83
- ...(deps.embed !== undefined && { embed: deps.embed }),
84
- ...(deps.rerank !== undefined && { rerank: deps.rerank }),
85
- });
86
- // The full recall may lose the race and fail later; nobody awaits it then.
87
- full.catch(() => { });
88
- try {
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
- else {
95
- result = r;
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
- catch {
99
- degraded.push('error');
100
- result = await recall(q, { store, config, now });
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
- const current = await deps.store.list(grantFor(scope));
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 (embed === undefined || decide === undefined || write === undefined) {
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
- await observe(line.obs, line.scope, {
248
- store: deps.store,
249
- embed,
250
- decide,
251
- write,
252
- config: deps.config,
253
- now: deps.now?.() ?? new Date(),
254
- harness: 'claude-code',
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.0",
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.0",
49
+ "@oortmem/core": "0.1.1",
50
50
  "zod": "4.6.7"
51
51
  },
52
52
  "devDependencies": {
53
- "@oortmem/store-sqlite": "0.1.0",
53
+ "@oortmem/store-sqlite": "0.1.1",
54
54
  "vitest": "^5.0.5"
55
55
  },
56
56
  "scripts": {