@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.
- package/README.md +79 -11
- package/dist/{chunk-TKMIWM6Q.js → chunk-6FZL67GH.js} +3 -1
- package/dist/{chunk-CYCD234A.js → chunk-EUQ3OFCQ.js} +37 -32
- package/dist/chunk-FD3AMXVU.js +111 -0
- package/dist/chunk-IMYLU5C2.js +514 -0
- package/dist/chunk-RZONPKY4.js +185 -0
- package/dist/{chunk-UULWJJT4.js → chunk-TGVURF54.js} +1 -1
- package/dist/{chunk-6S5RJYBC.js → chunk-UH7AJF6F.js} +25 -19
- package/dist/cli/main.js +484 -165
- package/dist/hooks/hermes-post-llm-call.js +220 -0
- package/dist/hooks/hermes-pre-llm-call.js +145 -0
- package/dist/hooks/session-start.js +9 -178
- package/dist/hooks/stop.js +4 -488
- package/dist/hooks/user-prompt-submit.js +8 -105
- package/dist/opencode/index.js +142 -11
- package/dist/setup/skills/contextdb-knowledge/SKILL.md +24 -7
- package/dist/setup/skills/contextdb-memory/SKILL.md +6 -6
- package/package.json +5 -3
|
@@ -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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import
|
|
11
|
-
|
|
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,
|