@aliyunrds/ctxdb 1.0.1-beta.2 → 1.0.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.
@@ -0,0 +1,514 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ isCircuitOpen,
4
+ isConnectionError,
5
+ resetCircuit,
6
+ tripCircuit
7
+ } from "./chunk-TGVURF54.js";
8
+ import {
9
+ CtxdbError,
10
+ debug,
11
+ isDebug
12
+ } from "./chunk-6FZL67GH.js";
13
+
14
+ // src/lib/capture-orchestrator.ts
15
+ import {
16
+ filterMessagesForExtraction,
17
+ isKnowledgeBaseUploadTurn,
18
+ selectTurnMessages
19
+ } from "@aliyunrds/ctxdb-shared";
20
+
21
+ // src/lib/git-context.ts
22
+ import { execSync } from "child_process";
23
+ function getGitContext() {
24
+ try {
25
+ const raw = execSync("git rev-parse --show-toplevel --abbrev-ref HEAD", {
26
+ encoding: "utf-8",
27
+ timeout: 3e3,
28
+ stdio: ["ignore", "pipe", "ignore"]
29
+ }).trim();
30
+ const lines = raw.split("\n");
31
+ if (lines.length < 2) return null;
32
+ const project = lines[0].split(/[/\\]/).pop();
33
+ if (!project) return null;
34
+ const branch = lines[1];
35
+ return { project, branch };
36
+ } catch {
37
+ return null;
38
+ }
39
+ }
40
+
41
+ // src/lib/transcript.ts
42
+ import { existsSync, readFileSync } from "fs";
43
+ var ANTHROPIC_MESSAGE_TYPES = /* @__PURE__ */ new Set(["user", "assistant"]);
44
+ var CODEX_RESPONSE_ITEM = "response_item";
45
+ function readTranscript(path) {
46
+ if (!existsSync(path)) return [];
47
+ let text;
48
+ try {
49
+ text = readFileSync(path, "utf-8");
50
+ } catch {
51
+ return [];
52
+ }
53
+ const out = [];
54
+ for (const line of text.split("\n")) {
55
+ const s = line.trim();
56
+ if (!s) continue;
57
+ let obj;
58
+ try {
59
+ obj = JSON.parse(s);
60
+ } catch {
61
+ continue;
62
+ }
63
+ if (!obj || typeof obj !== "object" || Array.isArray(obj)) continue;
64
+ const o = obj;
65
+ const rawType = typeof o.type === "string" ? o.type : "";
66
+ if (rawType === CODEX_RESPONSE_ITEM) {
67
+ const payload = o.payload;
68
+ if (payload && typeof payload === "object" && !Array.isArray(payload)) {
69
+ out.push({ ...o, type: CODEX_RESPONSE_ITEM });
70
+ }
71
+ continue;
72
+ }
73
+ const message = o.message;
74
+ const role = message && typeof message === "object" ? message.role : void 0;
75
+ const inferred = ANTHROPIC_MESSAGE_TYPES.has(rawType) ? rawType : typeof role === "string" && ANTHROPIC_MESSAGE_TYPES.has(role) ? role : null;
76
+ if (!inferred) continue;
77
+ out.push({ ...o, type: inferred });
78
+ }
79
+ return out;
80
+ }
81
+ function toKbDetectionMessages(rows) {
82
+ const out = [];
83
+ for (const row of rows) {
84
+ if (!row || typeof row !== "object") continue;
85
+ if (row.type === CODEX_RESPONSE_ITEM) {
86
+ const p = row.payload;
87
+ if (!p || typeof p !== "object") continue;
88
+ const ptype = p.type;
89
+ if (ptype === "message") {
90
+ const role = p.role;
91
+ if (role !== "user" && role !== "assistant") continue;
92
+ out.push({ role, content: p.content });
93
+ continue;
94
+ }
95
+ if (ptype === "function_call") {
96
+ const name = typeof p.name === "string" ? p.name : "";
97
+ const args = parseArgs(p.arguments);
98
+ const command = typeof args.cmd === "string" ? args.cmd : typeof args.command === "string" ? args.command : "";
99
+ out.push({
100
+ role: "assistant",
101
+ content: [
102
+ {
103
+ type: "tool_use",
104
+ // Map any codex tool name to "bash" so the shared
105
+ // SHELL_TOOL_NAMES set fires; the actual command text is
106
+ // preserved in input.command for the regex check.
107
+ name: "bash",
108
+ input: { command, codex_tool: name }
109
+ }
110
+ ]
111
+ });
112
+ continue;
113
+ }
114
+ continue;
115
+ }
116
+ const msg = row.message;
117
+ if (!msg || typeof msg !== "object") continue;
118
+ out.push({ role: msg.role, content: msg.content });
119
+ }
120
+ return out;
121
+ }
122
+ function selectTurnRows(rows) {
123
+ let turnStart = -1;
124
+ for (let ri = rows.length - 1; ri >= 0; ri--) {
125
+ if (hasExtractableUserText(rows[ri])) {
126
+ turnStart = ri;
127
+ while (turnStart > 0 && hasExtractableUserText(rows[turnStart - 1])) {
128
+ turnStart--;
129
+ }
130
+ break;
131
+ }
132
+ }
133
+ return turnStart >= 0 ? rows.slice(turnStart) : [];
134
+ }
135
+ function hasExtractableUserText(row) {
136
+ if (!row || typeof row !== "object") return false;
137
+ if (row.type === CODEX_RESPONSE_ITEM) {
138
+ const p = row.payload;
139
+ if (!p || typeof p !== "object") return false;
140
+ if (p.type !== "message" || p.role !== "user") return false;
141
+ const content2 = p.content;
142
+ if (!Array.isArray(content2)) return false;
143
+ return content2.some((block) => {
144
+ if (!block || typeof block !== "object") return false;
145
+ if (block.type !== "input_text") return false;
146
+ const text = block.text;
147
+ return typeof text === "string" && text.trim() && !isCodexScaffoldingText(text);
148
+ });
149
+ }
150
+ if (row.isMeta === true) return false;
151
+ const msg = row.message;
152
+ if (!msg || typeof msg !== "object") return false;
153
+ if (msg.role !== "user") return false;
154
+ const content = msg.content;
155
+ if (typeof content === "string") return Boolean(content.trim());
156
+ if (!Array.isArray(content)) return false;
157
+ return content.some((block) => {
158
+ if (!block || typeof block !== "object") return false;
159
+ if (block.type !== "text") return false;
160
+ const text = block.text;
161
+ return typeof text === "string" && Boolean(text.trim());
162
+ });
163
+ }
164
+ function parseArgs(raw) {
165
+ if (typeof raw !== "string") return {};
166
+ try {
167
+ const parsed = JSON.parse(raw);
168
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
169
+ return parsed;
170
+ }
171
+ } catch {
172
+ }
173
+ return {};
174
+ }
175
+ var CODEX_USER_SCAFFOLDING_PREFIXES = [
176
+ "<environment_context>",
177
+ "<permissions instructions>",
178
+ "<collaboration_mode>",
179
+ "<apps_instructions>",
180
+ "<skills_instructions>",
181
+ "<plugins_instructions>",
182
+ "# AGENTS.md instructions for "
183
+ ];
184
+ var CODEX_MEMORY_PREAMBLE_RE = /^## Memory\s+You have access to a memory folder/;
185
+ function isCodexScaffoldingText(text) {
186
+ const t = text.trimStart();
187
+ if (!t) return false;
188
+ for (const prefix of CODEX_USER_SCAFFOLDING_PREFIXES) {
189
+ if (t.startsWith(prefix)) return true;
190
+ }
191
+ return CODEX_MEMORY_PREAMBLE_RE.test(t);
192
+ }
193
+ function toParsedMessages(rows) {
194
+ const parsed = [];
195
+ let idx = 0;
196
+ const push = (role, text) => {
197
+ if (text && text.trim()) {
198
+ parsed.push({ role, content: text, index: idx });
199
+ idx++;
200
+ }
201
+ };
202
+ for (const row of rows) {
203
+ if (!row || typeof row !== "object") continue;
204
+ if (row.type === CODEX_RESPONSE_ITEM) {
205
+ const p = row.payload;
206
+ if (!p || typeof p !== "object") continue;
207
+ const ptype = p.type;
208
+ if (ptype === "message") {
209
+ const role2 = p.role;
210
+ if (role2 !== "user" && role2 !== "assistant") continue;
211
+ const content2 = p.content;
212
+ if (!Array.isArray(content2)) continue;
213
+ for (const block of content2) {
214
+ if (!block || typeof block !== "object") continue;
215
+ const btype = block.type;
216
+ if (btype === "input_text" || btype === "output_text") {
217
+ const v = block.text;
218
+ if (typeof v !== "string") continue;
219
+ if (role2 === "user" && isCodexScaffoldingText(v)) continue;
220
+ push(role2, v);
221
+ }
222
+ }
223
+ continue;
224
+ }
225
+ if (ptype === "reasoning") continue;
226
+ continue;
227
+ }
228
+ if (row.isMeta === true) continue;
229
+ const msg = row.message;
230
+ if (!msg || typeof msg !== "object") continue;
231
+ const role = msg.role;
232
+ if (role !== "user" && role !== "assistant") continue;
233
+ const content = msg.content;
234
+ if (typeof content === "string") {
235
+ push(role, content);
236
+ continue;
237
+ }
238
+ if (Array.isArray(content)) {
239
+ for (const block of content) {
240
+ if (!block || typeof block !== "object") continue;
241
+ const btype = block.type;
242
+ let text = null;
243
+ if (btype === "text") {
244
+ const v = block.text;
245
+ if (typeof v === "string") text = v;
246
+ }
247
+ if (text) push(role, text);
248
+ }
249
+ }
250
+ }
251
+ return parsed;
252
+ }
253
+ var COMMAND_MESSAGE_RE = /<command-message>([\s\S]*?)<\/command-message>/;
254
+ var COMMAND_ARGS_RE = /<command-args>([\s\S]*?)<\/command-args>/;
255
+ function extractSkillSignals(rows) {
256
+ const signals = [];
257
+ const seen = /* @__PURE__ */ new Set();
258
+ const add = (s) => {
259
+ const key = `${s.trigger}:${s.skill}`;
260
+ if (seen.has(key)) return;
261
+ seen.add(key);
262
+ signals.push(s);
263
+ };
264
+ for (const row of rows) {
265
+ if (!row || typeof row !== "object") continue;
266
+ if (row.type === "user") {
267
+ const msg = row.message;
268
+ if (!msg || typeof msg !== "object") continue;
269
+ const content = msg.content;
270
+ if (typeof content === "string") {
271
+ const m = COMMAND_MESSAGE_RE.exec(content);
272
+ if (m) {
273
+ const argsMatch = COMMAND_ARGS_RE.exec(content);
274
+ add({
275
+ skill: m[1].trim(),
276
+ args: argsMatch?.[1]?.trim() || void 0,
277
+ trigger: "command"
278
+ });
279
+ }
280
+ }
281
+ }
282
+ if (row.type === "assistant") {
283
+ const msg = row.message;
284
+ if (!msg || typeof msg !== "object") continue;
285
+ const content = msg.content;
286
+ if (!Array.isArray(content)) continue;
287
+ for (const block of content) {
288
+ if (block && typeof block === "object" && block.type === "tool_use" && block.name === "Skill") {
289
+ const input = block.input;
290
+ if (input && typeof input === "object") {
291
+ const skill = typeof input.skill === "string" ? input.skill : "";
292
+ const args = typeof input.args === "string" ? input.args : void 0;
293
+ if (skill) add({ skill, args, trigger: "agent" });
294
+ }
295
+ }
296
+ }
297
+ }
298
+ }
299
+ return signals;
300
+ }
301
+
302
+ // src/lib/capture-orchestrator.ts
303
+ async function captureTurn(transcriptPath, cfg, client, agent = "default", sessionId) {
304
+ const preflight = capturePreflight(cfg, agent);
305
+ if (preflight) return preflight;
306
+ const rows = readTranscript(transcriptPath);
307
+ if (rows.length === 0) {
308
+ return { captured: false, reason: "empty_transcript", messageCount: 0 };
309
+ }
310
+ const dbg = isDebug();
311
+ if (dbg) debug("capture", "transcript rows", summarizeTranscriptRows(rows));
312
+ const parsed = toParsedMessages(rows);
313
+ if (parsed.length === 0) {
314
+ return {
315
+ captured: false,
316
+ reason: `transcript_schema_mismatch: rows=${rows.length} parsed=0`,
317
+ messageCount: 0
318
+ };
319
+ }
320
+ const turnRows = selectTurnRows(rows);
321
+ if (dbg) debug("capture", "current turn rows", summarizeTranscriptRows(turnRows));
322
+ return captureParsedMessages(parsed, cfg, client, agent, sessionId, {
323
+ kbDetectionMessages: toKbDetectionMessages(turnRows),
324
+ skills: extractSkillSignals(rows)
325
+ });
326
+ }
327
+ async function captureParsedMessages(parsed, cfg, client, agent = "default", sessionId, options = {}) {
328
+ const preflight = capturePreflight(cfg, agent);
329
+ if (preflight) return preflight;
330
+ if (parsed.length === 0) {
331
+ return { captured: false, reason: "empty_messages", messageCount: 0 };
332
+ }
333
+ const dbg = isDebug();
334
+ if (dbg) debug("capture", "parsed messages", summarizeTextMessages(parsed));
335
+ const turn = selectTurnMessages(parsed);
336
+ if (dbg) debug("capture", "selected turn", summarizeTextMessages(turn));
337
+ if (turn.length === 0) {
338
+ return { captured: false, reason: "empty_turn_slice", messageCount: 0 };
339
+ }
340
+ if (!turn.some((m) => m.role === "user")) {
341
+ return { captured: false, reason: "no_user_in_turn", messageCount: 0 };
342
+ }
343
+ const AUTOMATED_REVIEW_PREFIX = "[SYSTEM: This is an automated background review task. It is NOT a user message.";
344
+ const userMsgs = turn.filter((m) => m.role === "user");
345
+ if (userMsgs.every((m) => m.content.startsWith(AUTOMATED_REVIEW_PREFIX))) {
346
+ return { captured: false, reason: "automated_review_skip", messageCount: 0 };
347
+ }
348
+ const kb = isKnowledgeBaseUploadTurn(options.kbDetectionMessages);
349
+ if (kb.detected) {
350
+ return {
351
+ captured: false,
352
+ reason: `b3c_skip: ${kb.reason ?? "(no reason)"}`,
353
+ messageCount: 0
354
+ };
355
+ }
356
+ const rawMessages = turn.map((m) => ({ role: m.role, content: m.content }));
357
+ const filtered = filterMessagesForExtraction(rawMessages);
358
+ if (dbg) debug("capture", "filtered messages", summarizeTextMessages(filtered));
359
+ if (filtered.length === 0) {
360
+ return { captured: false, reason: "all_filtered", messageCount: 0 };
361
+ }
362
+ const git = getGitContext();
363
+ const skills = options.skills ?? [];
364
+ const bg = {};
365
+ if (git) bg.git = git;
366
+ if (skills.length > 0) bg.skills = skills.map(
367
+ ({ skill, trigger, args }) => args ? { skill, trigger, args } : { skill, trigger }
368
+ );
369
+ const hasBackground = Boolean(bg.git || bg.skills);
370
+ if (hasBackground) debug("capture", "background", bg);
371
+ const messages = hasBackground ? [{ role: "background", content: JSON.stringify(bg) }, ...filtered] : filtered;
372
+ const payload = {
373
+ messages,
374
+ user_id: cfg.userId,
375
+ async_mode: true
376
+ };
377
+ if (cfg.agentId) payload.agent_id = cfg.agentId;
378
+ if (cfg.appId) payload.app_id = cfg.appId;
379
+ if (sessionId) payload.run_id = sessionId;
380
+ let resp;
381
+ try {
382
+ resp = await client.postJson("/v3/memories/add/", payload);
383
+ resetCircuit(agent);
384
+ } catch (err) {
385
+ const msg = err instanceof CtxdbError ? err.message : String(err);
386
+ if (isConnectionError(err)) tripCircuit(agent, cfg.baseUrl, msg);
387
+ return {
388
+ captured: false,
389
+ reason: `http_error: ${msg}`,
390
+ messageCount: filtered.length
391
+ };
392
+ }
393
+ return {
394
+ captured: true,
395
+ reason: "ok",
396
+ serverResponse: resp,
397
+ messageCount: filtered.length
398
+ };
399
+ }
400
+ function capturePreflight(cfg, agent) {
401
+ if (!cfg.apiKey || !cfg.baseUrl) {
402
+ return { captured: false, reason: "config_incomplete", messageCount: 0 };
403
+ }
404
+ if (!cfg.autoCapture) {
405
+ return { captured: false, reason: "auto_capture_disabled", messageCount: 0 };
406
+ }
407
+ if (isCircuitOpen(agent, cfg.baseUrl)) {
408
+ return { captured: false, reason: `circuit_open: ${cfg.baseUrl}`, messageCount: 0 };
409
+ }
410
+ return null;
411
+ }
412
+ var DEBUG_TAIL_LIMIT = 25;
413
+ var DEBUG_TEXT_LIMIT = 240;
414
+ var DEBUG_BLOCK_LIMIT = 8;
415
+ function summarizeTextMessages(messages) {
416
+ return {
417
+ count: messages.length,
418
+ roles: countRoles(messages),
419
+ tail: tailWithIndex(messages).map(({ index, item }) => ({
420
+ index,
421
+ parsedIndex: typeof item.index === "number" ? item.index : void 0,
422
+ role: item.role ?? "(unknown)",
423
+ content: summarizeText(item.content)
424
+ }))
425
+ };
426
+ }
427
+ function summarizeTranscriptRows(rows) {
428
+ return {
429
+ count: rows.length,
430
+ tail: tailWithIndex(rows).map(({ index, item: row }) => ({
431
+ index,
432
+ type: row.type,
433
+ role: rowRole(row),
434
+ isMeta: row.isMeta === true || void 0,
435
+ payloadType: row.payload?.type,
436
+ content: summarizeRowContent(row)
437
+ }))
438
+ };
439
+ }
440
+ function tailWithIndex(items) {
441
+ const start = Math.max(0, items.length - DEBUG_TAIL_LIMIT);
442
+ return items.slice(start).map((item, offset) => ({ index: start + offset, item }));
443
+ }
444
+ function countRoles(messages) {
445
+ const counts = {};
446
+ for (const msg of messages) {
447
+ const role = msg.role ?? "(unknown)";
448
+ counts[role] = (counts[role] ?? 0) + 1;
449
+ }
450
+ return counts;
451
+ }
452
+ function rowRole(row) {
453
+ const msgRole = row.message && typeof row.message === "object" ? row.message.role : void 0;
454
+ if (typeof msgRole === "string") return msgRole;
455
+ const payloadRole = row.payload && typeof row.payload === "object" ? row.payload.role : void 0;
456
+ return typeof payloadRole === "string" ? payloadRole : void 0;
457
+ }
458
+ function summarizeRowContent(row) {
459
+ if (row.type === "response_item") {
460
+ const p = row.payload;
461
+ if (!p || typeof p !== "object") return { kind: "missing" };
462
+ if (p.type === "function_call") {
463
+ return {
464
+ kind: "function_call",
465
+ name: p.name,
466
+ arguments: summarizeText(p.arguments)
467
+ };
468
+ }
469
+ return summarizeContent(p.content);
470
+ }
471
+ return summarizeContent(row.message?.content);
472
+ }
473
+ function summarizeContent(content) {
474
+ if (typeof content === "string") return summarizeText(content);
475
+ if (!Array.isArray(content)) {
476
+ return { kind: content === void 0 ? "missing" : typeof content };
477
+ }
478
+ return {
479
+ kind: "blocks",
480
+ count: content.length,
481
+ blocks: content.slice(0, DEBUG_BLOCK_LIMIT).map((block) => summarizeBlock(block)),
482
+ truncated: content.length > DEBUG_BLOCK_LIMIT || void 0
483
+ };
484
+ }
485
+ function summarizeBlock(block) {
486
+ if (!block || typeof block !== "object") return { kind: typeof block };
487
+ const b = block;
488
+ const summary = { type: b.type };
489
+ if (typeof b.name === "string") summary.name = b.name;
490
+ if (typeof b.text === "string") summary.text = summarizeText(b.text);
491
+ if (typeof b.thinking === "string") summary.thinking = summarizeText(b.thinking);
492
+ if (typeof b.content === "string") summary.content = summarizeText(b.content);
493
+ const input = b.input;
494
+ if (input && typeof input === "object" && !Array.isArray(input)) {
495
+ const i = input;
496
+ summary.inputKeys = Object.keys(i).sort();
497
+ if (typeof i.command === "string") summary.command = summarizeText(i.command);
498
+ }
499
+ return summary;
500
+ }
501
+ function summarizeText(text) {
502
+ if (typeof text !== "string") return { kind: text === void 0 ? "missing" : typeof text };
503
+ const normalized = text.replace(/\s+/g, " ").trim();
504
+ return {
505
+ kind: "text",
506
+ length: text.length,
507
+ snippet: normalized.length > DEBUG_TEXT_LIMIT ? `${normalized.slice(0, DEBUG_TEXT_LIMIT)}...` : normalized
508
+ };
509
+ }
510
+
511
+ export {
512
+ captureTurn,
513
+ captureParsedMessages
514
+ };
@@ -0,0 +1,185 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ fetchKbCatalogBlock,
4
+ recallTurn
5
+ } from "./chunk-EUQ3OFCQ.js";
6
+ import {
7
+ isCircuitOpen
8
+ } from "./chunk-TGVURF54.js";
9
+ import {
10
+ HttpClient,
11
+ agentFromArgvWithFallback,
12
+ debug,
13
+ isComplete,
14
+ load,
15
+ setDebug
16
+ } from "./chunk-6FZL67GH.js";
17
+ import {
18
+ shouldSkipHooks
19
+ } from "./chunk-UEKR2Z3S.js";
20
+
21
+ // src/hooks/session-start.ts
22
+ import { pathToFileURL } from "url";
23
+
24
+ // src/lib/warmup-recall.ts
25
+ import { execSync } from "child_process";
26
+ import { basename } from "path";
27
+ function collectGitSignals(cwd) {
28
+ const result = { branch: "", recentCommits: [] };
29
+ try {
30
+ result.branch = execSync("git rev-parse --abbrev-ref HEAD", {
31
+ cwd,
32
+ timeout: 500,
33
+ encoding: "utf-8",
34
+ stdio: ["ignore", "pipe", "ignore"]
35
+ }).trim();
36
+ } catch {
37
+ }
38
+ try {
39
+ const log = execSync("git log --oneline -3 --no-decorate", {
40
+ cwd,
41
+ timeout: 500,
42
+ encoding: "utf-8",
43
+ stdio: ["ignore", "pipe", "ignore"]
44
+ }).trim();
45
+ if (log) {
46
+ result.recentCommits = log.split("\n").map((l) => {
47
+ const idx = l.indexOf(" ");
48
+ return idx > 0 ? l.slice(idx + 1) : l;
49
+ });
50
+ }
51
+ } catch {
52
+ }
53
+ return result;
54
+ }
55
+ function buildWarmupQuery(cwd, git) {
56
+ const project = basename(cwd) || "unknown";
57
+ const parts = [`project: ${project}`];
58
+ if (git.branch) {
59
+ parts.push(`branch: ${git.branch}`);
60
+ }
61
+ if (git.recentCommits.length > 0) {
62
+ parts.push(`recent work: ${git.recentCommits.join("; ")}`);
63
+ }
64
+ return parts.join(", ");
65
+ }
66
+ async function warmupRecall(cwd, cfg, client) {
67
+ if (!cfg.warmupRecall) {
68
+ return { ok: false, reason: "warmup_recall_disabled", additionalContext: "", memoryCount: 0, knowledgeChunkCount: 0 };
69
+ }
70
+ const git = collectGitSignals(cwd);
71
+ const query = buildWarmupQuery(cwd, git);
72
+ return recallTurn(query, cfg, client);
73
+ }
74
+
75
+ // src/hooks/session-start.ts
76
+ var HOOK_TIMEOUT_MS = 5e3;
77
+ function timeout(ms) {
78
+ return new Promise((resolve) => setTimeout(() => resolve(null), ms));
79
+ }
80
+ async function composeSessionStart(cfg, agent, client, cwd, timeoutMs = HOOK_TIMEOUT_MS) {
81
+ const kbInjectHere = cfg.kbCatalogInjection === "session_start";
82
+ const warmupPromise = cfg.warmupRecall ? Promise.race([warmupRecall(cwd, cfg, client), timeout(timeoutMs).then(() => null)]) : Promise.resolve(null);
83
+ const kbPromise = kbInjectHere ? Promise.race([fetchKbCatalogBlock(client, agent).catch(() => ""), timeout(timeoutMs).then(() => "")]) : Promise.resolve("");
84
+ const [result, kbBlock] = await Promise.all([warmupPromise, kbPromise]);
85
+ const warmupTimedOut = cfg.warmupRecall && result === null;
86
+ const warmupCtx = result && result.ok ? result.additionalContext || "" : "";
87
+ if (!warmupCtx && result && !result.ok) {
88
+ debug("warmup", `no result: ${result.reason}`);
89
+ }
90
+ let ctx = warmupCtx;
91
+ if (kbBlock) ctx = ctx ? `${ctx}
92
+
93
+ ${kbBlock}` : kbBlock;
94
+ return {
95
+ ctx,
96
+ memoryCount: result?.ok ? result.memoryCount : 0,
97
+ kbChunkCount: result?.ok ? result.knowledgeChunkCount : 0,
98
+ kbCatalogLines: kbBlock ? kbBlock.split("\n").length : 0,
99
+ warmupTimedOut
100
+ };
101
+ }
102
+ function formatSessionStartStdout(agent, ctx) {
103
+ if (agent === "codex") return ctx + "\n";
104
+ const out = {
105
+ hookSpecificOutput: {
106
+ hookEventName: "SessionStart",
107
+ additionalContext: ctx
108
+ }
109
+ };
110
+ return JSON.stringify(out) + "\n";
111
+ }
112
+ async function readStdinJson() {
113
+ let raw = "";
114
+ for await (const chunk of process.stdin) raw += chunk;
115
+ if (!raw.trim()) return {};
116
+ try {
117
+ const obj = JSON.parse(raw);
118
+ return obj && typeof obj === "object" && !Array.isArray(obj) ? obj : {};
119
+ } catch {
120
+ return {};
121
+ }
122
+ }
123
+ async function main() {
124
+ try {
125
+ if (shouldSkipHooks()) return 0;
126
+ const event = await readStdinJson();
127
+ const { agent, fellBack } = agentFromArgvWithFallback();
128
+ if (fellBack) {
129
+ process.stderr.write(
130
+ `ctxdb warmup: agent unspecified, defaulting to ${agent}
131
+ `
132
+ );
133
+ }
134
+ const cwd = typeof event.cwd === "string" ? event.cwd : "";
135
+ if (!cwd) return 0;
136
+ const cfg = load({ agent });
137
+ setDebug(cfg.debug);
138
+ debug("warmup", "start", { cwd, userId: cfg.userId });
139
+ const kbInjectHere = cfg.kbCatalogInjection === "session_start";
140
+ if (!isComplete(cfg) || !cfg.warmupRecall && !kbInjectHere) {
141
+ debug("warmup", "skip (config incomplete or both warmup+kb off)");
142
+ return 0;
143
+ }
144
+ if (isCircuitOpen(agent, cfg.baseUrl)) {
145
+ debug("warmup", "skip (circuit open)");
146
+ return 0;
147
+ }
148
+ const client = new HttpClient({
149
+ baseUrl: cfg.baseUrl,
150
+ apiKey: cfg.apiKey,
151
+ timeoutMs: HOOK_TIMEOUT_MS
152
+ });
153
+ const composed = await composeSessionStart(cfg, agent, client, cwd);
154
+ if (composed.warmupTimedOut) {
155
+ process.stderr.write("ctxdb warmup: timeout\n");
156
+ }
157
+ if (!composed.ctx) return 0;
158
+ const { memoryCount, kbChunkCount, kbCatalogLines, ctx } = composed;
159
+ debug(
160
+ "warmup",
161
+ `ok, memories=${memoryCount} kb=${kbChunkCount} kb_catalog=${kbCatalogLines}`
162
+ );
163
+ process.stderr.write(
164
+ `ctxdb warmup: ok (${memoryCount} memories, ${kbChunkCount} kb chunks, kb_catalog=${kbCatalogLines} lines)
165
+ `
166
+ );
167
+ process.stdout.write(formatSessionStartStdout(agent, ctx));
168
+ return 0;
169
+ } catch (err) {
170
+ process.stderr.write(`ctxdb warmup: unexpected error: ${err?.message ?? err}
171
+ `);
172
+ return 0;
173
+ }
174
+ }
175
+ if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
176
+ main().then((code) => {
177
+ process.exitCode = code;
178
+ });
179
+ }
180
+
181
+ export {
182
+ HOOK_TIMEOUT_MS,
183
+ composeSessionStart,
184
+ formatSessionStartStdout
185
+ };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  configDir
4
- } from "./chunk-TKMIWM6Q.js";
4
+ } from "./chunk-6FZL67GH.js";
5
5
 
6
6
  // src/lib/circuit.ts
7
7
  import { statSync, writeFileSync, unlinkSync, mkdirSync, readdirSync, readFileSync } from "fs";