@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,220 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ captureParsedMessages
4
+ } from "../chunk-IMYLU5C2.js";
5
+ import "../chunk-TGVURF54.js";
6
+ import {
7
+ HttpClient,
8
+ agentFromArgvWithFallback,
9
+ debug,
10
+ load,
11
+ setDebug
12
+ } from "../chunk-6FZL67GH.js";
13
+ import {
14
+ shouldSkipHooks
15
+ } from "../chunk-UEKR2Z3S.js";
16
+
17
+ // src/hooks/hermes-post-llm-call.ts
18
+ import { pathToFileURL } from "url";
19
+ function asRecord(value) {
20
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
21
+ }
22
+ function firstString(...values) {
23
+ for (const value of values) {
24
+ if (typeof value === "string") return value;
25
+ }
26
+ return "";
27
+ }
28
+ function textContent(content) {
29
+ if (typeof content === "string") return content.trim();
30
+ if (!Array.isArray(content)) return "";
31
+ const chunks = [];
32
+ for (const rawBlock of content) {
33
+ if (typeof rawBlock === "string") {
34
+ if (rawBlock.trim()) chunks.push(rawBlock.trim());
35
+ continue;
36
+ }
37
+ const block = asRecord(rawBlock);
38
+ const text = firstString(block.text, block.content);
39
+ if (text.trim()) chunks.push(text.trim());
40
+ }
41
+ return chunks.join("\n").trim();
42
+ }
43
+ function currentTurnHistory(history) {
44
+ const records = history.map(asRecord);
45
+ let start = -1;
46
+ for (let i = records.length - 1; i >= 0; i--) {
47
+ if (records[i].role === "user") {
48
+ start = i;
49
+ break;
50
+ }
51
+ }
52
+ return start >= 0 ? records.slice(start) : records;
53
+ }
54
+ function parsedHistoryMessages(history) {
55
+ const messages = [];
56
+ for (const item of history) {
57
+ const role = item.role;
58
+ if (role !== "user" && role !== "assistant") continue;
59
+ const content = textContent(item.content);
60
+ if (!content) continue;
61
+ messages.push({
62
+ role,
63
+ content,
64
+ index: messages.length,
65
+ isSummary: false
66
+ });
67
+ }
68
+ return messages;
69
+ }
70
+ function parseToolArguments(value) {
71
+ if (value && typeof value === "object" && !Array.isArray(value)) {
72
+ return value;
73
+ }
74
+ if (typeof value !== "string") return {};
75
+ try {
76
+ const parsed = JSON.parse(value);
77
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
78
+ } catch {
79
+ return {};
80
+ }
81
+ }
82
+ function normalizeToolCall(rawCall) {
83
+ const call = asRecord(rawCall);
84
+ const fn = asRecord(call.function);
85
+ const name = firstString(fn.name, call.name);
86
+ if (!name) return null;
87
+ const detectionName = name.toLowerCase() === "terminal" ? "bash" : name;
88
+ const args = parseToolArguments(fn.arguments ?? call.arguments ?? call.input);
89
+ return {
90
+ type: "toolCall",
91
+ name: detectionName,
92
+ arguments: args
93
+ };
94
+ }
95
+ function toHermesKbDetectionMessages(history) {
96
+ const messages = [];
97
+ for (const item of history) {
98
+ if (item.role !== "assistant") continue;
99
+ const blocks = [];
100
+ if (Array.isArray(item.content)) {
101
+ blocks.push(...item.content.filter((block) => block && typeof block === "object"));
102
+ }
103
+ if (Array.isArray(item.tool_calls)) {
104
+ for (const rawCall of item.tool_calls) {
105
+ const block = normalizeToolCall(rawCall);
106
+ if (block) blocks.push(block);
107
+ }
108
+ }
109
+ if (blocks.length > 0) messages.push({ role: "assistant", content: blocks });
110
+ }
111
+ return messages;
112
+ }
113
+ function extractHermesPostLlmCall(event) {
114
+ const extra = asRecord(event.extra);
115
+ const rawHistory = Array.isArray(extra.conversation_history) ? extra.conversation_history : Array.isArray(event.conversation_history) ? event.conversation_history : [];
116
+ const turnHistory = currentTurnHistory(rawHistory);
117
+ const userMessage = firstString(extra.user_message, event.user_message).trim();
118
+ const assistantResponse = firstString(
119
+ extra.assistant_response,
120
+ event.assistant_response
121
+ ).trim();
122
+ const explicitMessages = [];
123
+ if (userMessage) {
124
+ explicitMessages.push({
125
+ role: "user",
126
+ content: userMessage,
127
+ index: explicitMessages.length,
128
+ isSummary: false
129
+ });
130
+ }
131
+ if (assistantResponse) {
132
+ explicitMessages.push({
133
+ role: "assistant",
134
+ content: assistantResponse,
135
+ index: explicitMessages.length,
136
+ isSummary: false
137
+ });
138
+ }
139
+ const sessionId = firstString(event.session_id, extra.session_id) || void 0;
140
+ const historyMessages = parsedHistoryMessages(turnHistory);
141
+ return {
142
+ sessionId,
143
+ messages: userMessage && assistantResponse ? explicitMessages : historyMessages.length > 0 ? historyMessages : explicitMessages,
144
+ kbDetectionMessages: toHermesKbDetectionMessages(turnHistory)
145
+ };
146
+ }
147
+ async function readStdinJson() {
148
+ let raw = "";
149
+ for await (const chunk of process.stdin) raw += chunk;
150
+ if (!raw.trim()) return {};
151
+ try {
152
+ const parsed = JSON.parse(raw);
153
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
154
+ } catch {
155
+ return {};
156
+ }
157
+ }
158
+ async function main() {
159
+ try {
160
+ if (shouldSkipHooks()) return 0;
161
+ const event = await readStdinJson();
162
+ const { agent, fellBack } = agentFromArgvWithFallback();
163
+ if (fellBack) {
164
+ process.stderr.write(
165
+ `ctxdb hermes post_llm_call: agent unspecified, defaulting to ${agent}
166
+ `
167
+ );
168
+ }
169
+ const input = extractHermesPostLlmCall(event);
170
+ const cfg = load({ agent });
171
+ setDebug(cfg.debug);
172
+ debug("hermes.post_llm_call", "start", {
173
+ sessionId: input.sessionId,
174
+ messageCount: input.messages.length,
175
+ userId: cfg.userId
176
+ });
177
+ if (!cfg.apiKey || !cfg.baseUrl) {
178
+ debug("hermes.post_llm_call", "skip (config incomplete)");
179
+ return 0;
180
+ }
181
+ const client = new HttpClient({
182
+ baseUrl: cfg.baseUrl,
183
+ apiKey: cfg.apiKey,
184
+ timeoutMs: 5e3
185
+ });
186
+ const result = await captureParsedMessages(
187
+ input.messages,
188
+ cfg,
189
+ client,
190
+ agent,
191
+ input.sessionId,
192
+ { kbDetectionMessages: input.kbDetectionMessages }
193
+ );
194
+ if (result.captured) {
195
+ debug("hermes.post_llm_call", `ok (${result.messageCount} msgs)`, result);
196
+ process.stderr.write(`ctxdb hermes capture: ok (${result.messageCount} msgs)
197
+ `);
198
+ } else {
199
+ debug("hermes.post_llm_call", `skip (${result.reason})`, result);
200
+ process.stderr.write(`ctxdb hermes capture: skip (${result.reason})
201
+ `);
202
+ }
203
+ return 0;
204
+ } catch (err) {
205
+ process.stderr.write(
206
+ `ctxdb hermes post_llm_call: unexpected error: ${err?.message ?? err}
207
+ `
208
+ );
209
+ return 0;
210
+ }
211
+ }
212
+ if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
213
+ main().then((code) => {
214
+ process.exitCode = code;
215
+ });
216
+ }
217
+ export {
218
+ extractHermesPostLlmCall,
219
+ toHermesKbDetectionMessages
220
+ };
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ HOOK_TIMEOUT_MS,
4
+ composeSessionStart
5
+ } from "../chunk-RZONPKY4.js";
6
+ import {
7
+ composeUserPromptSubmit
8
+ } from "../chunk-FD3AMXVU.js";
9
+ import {
10
+ fetchKbCatalogBlock
11
+ } from "../chunk-EUQ3OFCQ.js";
12
+ import "../chunk-UH7AJF6F.js";
13
+ import {
14
+ isCircuitOpen
15
+ } from "../chunk-TGVURF54.js";
16
+ import {
17
+ HttpClient,
18
+ agentFromArgvWithFallback,
19
+ debug,
20
+ isComplete,
21
+ load,
22
+ setDebug
23
+ } from "../chunk-6FZL67GH.js";
24
+ import {
25
+ shouldSkipHooks
26
+ } from "../chunk-UEKR2Z3S.js";
27
+
28
+ // src/hooks/hermes-pre-llm-call.ts
29
+ import { pathToFileURL } from "url";
30
+ function asRecord(v) {
31
+ return v && typeof v === "object" && !Array.isArray(v) ? v : {};
32
+ }
33
+ function firstString(...values) {
34
+ for (const v of values) {
35
+ if (typeof v === "string") return v;
36
+ }
37
+ return "";
38
+ }
39
+ function extractHermesPreLlmCall(event) {
40
+ const extra = asRecord(event.extra);
41
+ return {
42
+ prompt: firstString(extra.user_message, event.user_message, event.prompt),
43
+ cwd: firstString(event.cwd, extra.cwd),
44
+ isFirstTurn: extra.is_first_turn === true || event.is_first_turn === true
45
+ };
46
+ }
47
+ function formatHermesPreLlmCallStdout(ctx) {
48
+ return JSON.stringify({ context: ctx }) + "\n";
49
+ }
50
+ async function composeHermesPreLlmCall(cfg, agent, client, input) {
51
+ const pieces = [];
52
+ const sessionStartNeeded = input.isFirstTurn && Boolean(input.cwd) && (cfg.warmupRecall || cfg.kbCatalogInjection === "session_start");
53
+ const sessionStart = sessionStartNeeded ? await composeSessionStart(cfg, agent, client, input.cwd, HOOK_TIMEOUT_MS) : void 0;
54
+ if (sessionStart?.ctx) pieces.push(sessionStart.ctx);
55
+ const repeatSessionCatalog = cfg.kbCatalogInjection === "session_start" && !sessionStartNeeded;
56
+ const recallNeeded = cfg.autoRecall && Boolean(input.prompt.trim());
57
+ const [kbCatalogBlock, recall] = await Promise.all([
58
+ repeatSessionCatalog ? fetchKbCatalogBlock(client, agent).catch(() => "") : Promise.resolve(""),
59
+ recallNeeded ? composeUserPromptSubmit(cfg, agent, client, input.prompt) : Promise.resolve(void 0)
60
+ ]);
61
+ if (kbCatalogBlock) pieces.push(kbCatalogBlock);
62
+ if (recall?.ctx) pieces.push(recall.ctx);
63
+ return { ctx: pieces.join("\n\n"), recall, sessionStart };
64
+ }
65
+ async function readStdinJson() {
66
+ let raw = "";
67
+ for await (const chunk of process.stdin) raw += chunk;
68
+ if (!raw.trim()) return {};
69
+ try {
70
+ const obj = JSON.parse(raw);
71
+ return obj && typeof obj === "object" && !Array.isArray(obj) ? obj : {};
72
+ } catch {
73
+ return {};
74
+ }
75
+ }
76
+ async function main() {
77
+ try {
78
+ if (shouldSkipHooks()) return 0;
79
+ const event = await readStdinJson();
80
+ const { agent, fellBack } = agentFromArgvWithFallback();
81
+ if (fellBack) {
82
+ process.stderr.write(
83
+ `ctxdb hermes pre_llm_call: agent unspecified, defaulting to ${agent}
84
+ `
85
+ );
86
+ }
87
+ const input = extractHermesPreLlmCall(event);
88
+ const cfg = load({ agent });
89
+ setDebug(cfg.debug);
90
+ debug("hermes.pre_llm_call", "start", {
91
+ prompt: input.prompt.slice(0, 200),
92
+ cwd: input.cwd,
93
+ isFirstTurn: input.isFirstTurn,
94
+ userId: cfg.userId
95
+ });
96
+ const wantsRecall = cfg.autoRecall && Boolean(input.prompt.trim());
97
+ const wantsSessionStart = input.isFirstTurn && Boolean(input.cwd) && (cfg.warmupRecall || cfg.kbCatalogInjection === "session_start");
98
+ const wantsRepeatedSessionCatalog = cfg.kbCatalogInjection === "session_start";
99
+ if (!isComplete(cfg) || !wantsRecall && !wantsSessionStart && !wantsRepeatedSessionCatalog) {
100
+ debug("hermes.pre_llm_call", "skip (config incomplete or no enabled injection)");
101
+ return 0;
102
+ }
103
+ if (isCircuitOpen(agent, cfg.baseUrl)) {
104
+ debug("hermes.pre_llm_call", "skip (circuit open)");
105
+ process.stderr.write(`ctxdb hermes pre_llm_call: skip (circuit_open: ${cfg.baseUrl})
106
+ `);
107
+ return 0;
108
+ }
109
+ const client = new HttpClient({
110
+ baseUrl: cfg.baseUrl,
111
+ apiKey: cfg.apiKey,
112
+ timeoutMs: HOOK_TIMEOUT_MS
113
+ });
114
+ const composed = await composeHermesPreLlmCall(cfg, agent, client, input);
115
+ if (composed.sessionStart?.warmupTimedOut) {
116
+ process.stderr.write("ctxdb hermes warmup: timeout\n");
117
+ }
118
+ if (composed.recall && !composed.recall.recall.ok && !composed.recall.ctx) {
119
+ const reason = composed.recall.recall.reason ?? "no_context";
120
+ debug("hermes.pre_llm_call", `recall no result: ${reason}`, composed.recall.recall);
121
+ if (reason.startsWith("http_error:")) {
122
+ process.stderr.write(`ctxdb hermes recall: ${reason}
123
+ `);
124
+ }
125
+ }
126
+ if (!composed.ctx) return 0;
127
+ debug("hermes.pre_llm_call", `ok, context length=${composed.ctx.length}`);
128
+ process.stdout.write(formatHermesPreLlmCallStdout(composed.ctx));
129
+ return 0;
130
+ } catch (err) {
131
+ process.stderr.write(`ctxdb hermes pre_llm_call: unexpected error: ${err?.message ?? err}
132
+ `);
133
+ return 0;
134
+ }
135
+ }
136
+ if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
137
+ main().then((code) => {
138
+ process.exitCode = code;
139
+ });
140
+ }
141
+ export {
142
+ composeHermesPreLlmCall,
143
+ extractHermesPreLlmCall,
144
+ formatHermesPreLlmCallStdout
145
+ };
@@ -1,183 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- fetchKbCatalogBlock,
4
- recallTurn
5
- } from "../chunk-CYCD234A.js";
6
- import "../chunk-6S5RJYBC.js";
7
- import {
8
- isCircuitOpen
9
- } from "../chunk-UULWJJT4.js";
10
- import {
11
- HttpClient,
12
- agentFromArgvWithFallback,
13
- debug,
14
- isComplete,
15
- load,
16
- setDebug
17
- } from "../chunk-TKMIWM6Q.js";
18
- import {
19
- shouldSkipHooks
20
- } from "../chunk-UEKR2Z3S.js";
21
-
22
- // src/hooks/session-start.ts
23
- import { pathToFileURL } from "url";
24
-
25
- // src/lib/warmup-recall.ts
26
- import { execSync } from "child_process";
27
- import { basename } from "path";
28
- function collectGitSignals(cwd) {
29
- const result = { branch: "", recentCommits: [] };
30
- try {
31
- result.branch = execSync("git rev-parse --abbrev-ref HEAD", {
32
- cwd,
33
- timeout: 500,
34
- encoding: "utf-8",
35
- stdio: ["ignore", "pipe", "ignore"]
36
- }).trim();
37
- } catch {
38
- }
39
- try {
40
- const log = execSync("git log --oneline -3 --no-decorate", {
41
- cwd,
42
- timeout: 500,
43
- encoding: "utf-8",
44
- stdio: ["ignore", "pipe", "ignore"]
45
- }).trim();
46
- if (log) {
47
- result.recentCommits = log.split("\n").map((l) => {
48
- const idx = l.indexOf(" ");
49
- return idx > 0 ? l.slice(idx + 1) : l;
50
- });
51
- }
52
- } catch {
53
- }
54
- return result;
55
- }
56
- function buildWarmupQuery(cwd, git) {
57
- const project = basename(cwd) || "unknown";
58
- const parts = [`project: ${project}`];
59
- if (git.branch) {
60
- parts.push(`branch: ${git.branch}`);
61
- }
62
- if (git.recentCommits.length > 0) {
63
- parts.push(`recent work: ${git.recentCommits.join("; ")}`);
64
- }
65
- return parts.join(", ");
66
- }
67
- async function warmupRecall(cwd, cfg, client) {
68
- if (!cfg.warmupRecall) {
69
- return { ok: false, reason: "warmup_recall_disabled", additionalContext: "", memoryCount: 0, knowledgeChunkCount: 0 };
70
- }
71
- const git = collectGitSignals(cwd);
72
- const query = buildWarmupQuery(cwd, git);
73
- return recallTurn(query, cfg, client);
74
- }
75
-
76
- // src/hooks/session-start.ts
77
- var HOOK_TIMEOUT_MS = 5e3;
78
- function timeout(ms) {
79
- return new Promise((resolve) => setTimeout(() => resolve(null), ms));
80
- }
81
- async function composeSessionStart(cfg, agent, client, cwd, timeoutMs = HOOK_TIMEOUT_MS) {
82
- const kbInjectHere = cfg.kbCatalogInjection === "session_start";
83
- const warmupPromise = cfg.warmupRecall ? Promise.race([warmupRecall(cwd, cfg, client), timeout(timeoutMs).then(() => null)]) : Promise.resolve(null);
84
- const kbPromise = kbInjectHere ? Promise.race([fetchKbCatalogBlock(client, agent).catch(() => ""), timeout(timeoutMs).then(() => "")]) : Promise.resolve("");
85
- const [result, kbBlock] = await Promise.all([warmupPromise, kbPromise]);
86
- const warmupTimedOut = cfg.warmupRecall && result === null;
87
- const warmupCtx = result && result.ok ? result.additionalContext || "" : "";
88
- if (!warmupCtx && result && !result.ok) {
89
- debug("warmup", `no result: ${result.reason}`);
90
- }
91
- let ctx = warmupCtx;
92
- if (kbBlock) ctx = ctx ? `${ctx}
93
-
94
- ${kbBlock}` : kbBlock;
95
- return {
96
- ctx,
97
- memoryCount: result?.ok ? result.memoryCount : 0,
98
- kbChunkCount: result?.ok ? result.knowledgeChunkCount : 0,
99
- kbCatalogLines: kbBlock ? kbBlock.split("\n").length : 0,
100
- warmupTimedOut
101
- };
102
- }
103
- function formatSessionStartStdout(agent, ctx) {
104
- if (agent === "codex") return ctx + "\n";
105
- const out = {
106
- hookSpecificOutput: {
107
- hookEventName: "SessionStart",
108
- additionalContext: ctx
109
- }
110
- };
111
- return JSON.stringify(out) + "\n";
112
- }
113
- async function readStdinJson() {
114
- let raw = "";
115
- for await (const chunk of process.stdin) raw += chunk;
116
- if (!raw.trim()) return {};
117
- try {
118
- const obj = JSON.parse(raw);
119
- return obj && typeof obj === "object" && !Array.isArray(obj) ? obj : {};
120
- } catch {
121
- return {};
122
- }
123
- }
124
- async function main() {
125
- try {
126
- if (shouldSkipHooks()) return 0;
127
- const event = await readStdinJson();
128
- const { agent, fellBack } = agentFromArgvWithFallback();
129
- if (fellBack) {
130
- process.stderr.write(
131
- `ctxdb warmup: agent unspecified, defaulting to ${agent}
132
- `
133
- );
134
- }
135
- const cwd = typeof event.cwd === "string" ? event.cwd : "";
136
- if (!cwd) return 0;
137
- const cfg = load({ agent });
138
- setDebug(cfg.debug);
139
- debug("warmup", "start", { cwd, userId: cfg.userId });
140
- const kbInjectHere = cfg.kbCatalogInjection === "session_start";
141
- if (!isComplete(cfg) || !cfg.warmupRecall && !kbInjectHere) {
142
- debug("warmup", "skip (config incomplete or both warmup+kb off)");
143
- return 0;
144
- }
145
- if (isCircuitOpen(agent, cfg.baseUrl)) {
146
- debug("warmup", "skip (circuit open)");
147
- return 0;
148
- }
149
- const client = new HttpClient({
150
- baseUrl: cfg.baseUrl,
151
- apiKey: cfg.apiKey,
152
- timeoutMs: HOOK_TIMEOUT_MS
153
- });
154
- const composed = await composeSessionStart(cfg, agent, client, cwd);
155
- if (composed.warmupTimedOut) {
156
- process.stderr.write("ctxdb warmup: timeout\n");
157
- }
158
- if (!composed.ctx) return 0;
159
- const { memoryCount, kbChunkCount, kbCatalogLines, ctx } = composed;
160
- debug(
161
- "warmup",
162
- `ok, memories=${memoryCount} kb=${kbChunkCount} kb_catalog=${kbCatalogLines}`
163
- );
164
- process.stderr.write(
165
- `ctxdb warmup: ok (${memoryCount} memories, ${kbChunkCount} kb chunks, kb_catalog=${kbCatalogLines} lines)
166
- `
167
- );
168
- process.stdout.write(formatSessionStartStdout(agent, ctx));
169
- return 0;
170
- } catch (err) {
171
- process.stderr.write(`ctxdb warmup: unexpected error: ${err?.message ?? err}
172
- `);
173
- return 0;
174
- }
175
- }
176
- if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
177
- main().then((code) => {
178
- process.exitCode = code;
179
- });
180
- }
3
+ HOOK_TIMEOUT_MS,
4
+ composeSessionStart,
5
+ formatSessionStartStdout
6
+ } from "../chunk-RZONPKY4.js";
7
+ import "../chunk-EUQ3OFCQ.js";
8
+ import "../chunk-UH7AJF6F.js";
9
+ import "../chunk-TGVURF54.js";
10
+ import "../chunk-6FZL67GH.js";
11
+ import "../chunk-UEKR2Z3S.js";
181
12
  export {
182
13
  HOOK_TIMEOUT_MS,
183
14
  composeSessionStart,