@aliyunrds/ctxdb 0.0.7 → 0.0.8-beta.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/{chunk-LIC44DR6.js → chunk-C62I23HL.js} +34 -3
- package/dist/{chunk-Q2EEP4CE.js → chunk-KEQMJ6IO.js} +1 -1
- package/dist/{chunk-S45GOYUU.js → chunk-U3T5O6NX.js} +11 -2
- package/dist/cli/main.js +12 -9
- package/dist/hooks/session-start.js +64 -32
- package/dist/hooks/stop.js +9 -15
- package/dist/hooks/user-prompt-submit.js +41 -45
- package/dist/setup/skills/contextdb-knowledge/SKILL.md +133 -105
- package/dist/setup/skills/contextdb-memory/SKILL.md +135 -95
- package/package.json +2 -3
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
listKnowledgeBases
|
|
4
|
+
} from "./chunk-6S5RJYBC.js";
|
|
2
5
|
import {
|
|
3
6
|
isConnectionError,
|
|
4
7
|
resetCircuit,
|
|
5
8
|
tripCircuit
|
|
6
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-KEQMJ6IO.js";
|
|
7
10
|
import {
|
|
8
11
|
CtxdbError
|
|
9
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-U3T5O6NX.js";
|
|
10
13
|
|
|
11
14
|
// src/lib/recall-orchestrator.ts
|
|
12
15
|
import {
|
|
@@ -138,6 +141,34 @@ async function recallTurn(prompt, cfg, client, agent = "default") {
|
|
|
138
141
|
};
|
|
139
142
|
}
|
|
140
143
|
|
|
144
|
+
// src/lib/kb-catalog.ts
|
|
145
|
+
function sanitizeKeyEntities(raw) {
|
|
146
|
+
if (!Array.isArray(raw)) return [];
|
|
147
|
+
const out = [];
|
|
148
|
+
for (const e of raw) {
|
|
149
|
+
if (typeof e !== "string") continue;
|
|
150
|
+
const cleaned = e.replace(/\s+/g, " ").trim();
|
|
151
|
+
if (cleaned.length > 0) out.push(cleaned);
|
|
152
|
+
}
|
|
153
|
+
return out;
|
|
154
|
+
}
|
|
155
|
+
async function fetchKbCatalogBlock(client, agent) {
|
|
156
|
+
const kbs = await listKnowledgeBases(client);
|
|
157
|
+
const active = kbs.filter((kb) => kb.status === "active");
|
|
158
|
+
if (active.length === 0) return "";
|
|
159
|
+
const lines = active.map((kb) => {
|
|
160
|
+
const ents = sanitizeKeyEntities(kb.key_entities);
|
|
161
|
+
return ents.length > 0 ? `\xB7 ${kb.name}: ${ents.join(", ")}` : `\xB7 ${kb.name}`;
|
|
162
|
+
});
|
|
163
|
+
return [
|
|
164
|
+
"<available-knowledge-bases>",
|
|
165
|
+
`When you identify that relevant information may exist in the knowledge bases below, you MUST run \`ctxdb kb search "<query>" --kb=<name> --agent=${agent}\` with targeted keywords after initial analysis to supplement and correct your approach. Knowledge bases:`,
|
|
166
|
+
...lines,
|
|
167
|
+
"</available-knowledge-bases>"
|
|
168
|
+
].join("\n");
|
|
169
|
+
}
|
|
170
|
+
|
|
141
171
|
export {
|
|
142
|
-
recallTurn
|
|
172
|
+
recallTurn,
|
|
173
|
+
fetchKbCatalogBlock
|
|
143
174
|
};
|
|
@@ -308,6 +308,7 @@ var DEFAULT_USER_ID = "default";
|
|
|
308
308
|
var DEFAULT_TOP_K = 5;
|
|
309
309
|
var DEFAULT_THRESHOLD = 0.4;
|
|
310
310
|
var DEFAULT_KNOWLEDGE_TOP_K = 6;
|
|
311
|
+
var DEFAULT_KB_CATALOG_INJECTION = "session_start";
|
|
311
312
|
function isComplete(cfg) {
|
|
312
313
|
return Boolean(cfg.apiKey && cfg.baseUrl);
|
|
313
314
|
}
|
|
@@ -330,6 +331,12 @@ function coerceBool(v, fallback) {
|
|
|
330
331
|
if (v === void 0 || v === null) return fallback;
|
|
331
332
|
return Boolean(v);
|
|
332
333
|
}
|
|
334
|
+
function coerceKbCatalogInjection(v) {
|
|
335
|
+
if (v === "session_start" || v === "user_prompt_submit" || v === "off") {
|
|
336
|
+
return v;
|
|
337
|
+
}
|
|
338
|
+
return DEFAULT_KB_CATALOG_INJECTION;
|
|
339
|
+
}
|
|
333
340
|
function readRaw(path) {
|
|
334
341
|
if (!existsSync(path)) return {};
|
|
335
342
|
try {
|
|
@@ -367,7 +374,8 @@ function configFromDisk(raw) {
|
|
|
367
374
|
topK: coerceInt(raw.top_k, DEFAULT_TOP_K),
|
|
368
375
|
threshold: coerceFloat(raw.threshold, DEFAULT_THRESHOLD),
|
|
369
376
|
knowledgeTopK: coerceInt(raw.knowledge_top_k, DEFAULT_KNOWLEDGE_TOP_K),
|
|
370
|
-
debug: coerceBool(raw.debug, false)
|
|
377
|
+
debug: coerceBool(raw.debug, false),
|
|
378
|
+
kbCatalogInjection: coerceKbCatalogInjection(raw.kb_catalog_injection)
|
|
371
379
|
};
|
|
372
380
|
}
|
|
373
381
|
function applyEnv(cfg, env) {
|
|
@@ -416,7 +424,8 @@ function configToDisk(cfg) {
|
|
|
416
424
|
top_k: cfg.topK,
|
|
417
425
|
threshold: cfg.threshold,
|
|
418
426
|
knowledge_top_k: cfg.knowledgeTopK,
|
|
419
|
-
debug: cfg.debug
|
|
427
|
+
debug: cfg.debug,
|
|
428
|
+
kb_catalog_injection: cfg.kbCatalogInjection
|
|
420
429
|
};
|
|
421
430
|
}
|
|
422
431
|
function removeAgent(agent, path, options = {}) {
|
package/dist/cli/main.js
CHANGED
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
removeAgent,
|
|
30
30
|
save,
|
|
31
31
|
writeInstalledPkgVersion
|
|
32
|
-
} from "../chunk-
|
|
32
|
+
} from "../chunk-U3T5O6NX.js";
|
|
33
33
|
|
|
34
34
|
// src/cli/util.ts
|
|
35
35
|
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
@@ -624,11 +624,15 @@ function checkHookNodePaths(agent) {
|
|
|
624
624
|
if (!entryIsCtxdb(entry)) continue;
|
|
625
625
|
for (const h of entry.hooks ?? []) {
|
|
626
626
|
if (h?.type !== "command" || typeof h.command !== "string") continue;
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
627
|
+
if (Array.isArray(h.args) && h.args.length > 0) {
|
|
628
|
+
nodePaths.add(h.command);
|
|
629
|
+
} else {
|
|
630
|
+
const cmd = h.command;
|
|
631
|
+
if (cmd.includes("@aliyunrds/ctxdb")) {
|
|
632
|
+
const parts = cmd.split(" ");
|
|
633
|
+
if (parts.length >= 2 && !parts[0].endsWith(".js") && !parts[0].endsWith(".ts")) {
|
|
634
|
+
nodePaths.add(parts[0]);
|
|
635
|
+
}
|
|
632
636
|
}
|
|
633
637
|
}
|
|
634
638
|
}
|
|
@@ -865,16 +869,15 @@ var LEGACY_MARKER_KEYS = ["_ctxdbQoder", "_ctxdbPackage"];
|
|
|
865
869
|
var LEGACY_MARKER_VALUES = ["@aliyunrds/ctxdb-qoder"];
|
|
866
870
|
var TOOL_SCOPED_EVENTS = /* @__PURE__ */ new Set(["PreToolUse", "PostToolUse"]);
|
|
867
871
|
function appendOne(hooks, event, command, agent) {
|
|
868
|
-
const commandWithAgent = `${process.execPath} ${command} --agent=${agent}`;
|
|
869
872
|
if (!Array.isArray(hooks[event])) hooks[event] = [];
|
|
870
873
|
const dup = hooks[event].some(
|
|
871
874
|
(entry2) => Array.isArray(entry2?.hooks) && entry2.hooks.some(
|
|
872
|
-
(h) => h?.type === "command" &&
|
|
875
|
+
(h) => h?.type === "command" && Array.isArray(h.args) && h.args[0] === command
|
|
873
876
|
)
|
|
874
877
|
);
|
|
875
878
|
if (dup) return;
|
|
876
879
|
const entry = {
|
|
877
|
-
hooks: [{ type: "command", command:
|
|
880
|
+
hooks: [{ type: "command", command: process.execPath, args: [command, `--agent=${agent}`], timeout: 60 }],
|
|
878
881
|
[ENTRY_MARKER_KEY]: ENTRY_MARKER_VALUE,
|
|
879
882
|
[ENTRY_AGENT_KEY]: agent
|
|
880
883
|
};
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
fetchKbCatalogBlock,
|
|
3
4
|
recallTurn
|
|
4
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-C62I23HL.js";
|
|
6
|
+
import "../chunk-6S5RJYBC.js";
|
|
5
7
|
import {
|
|
6
8
|
isCircuitOpen
|
|
7
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-KEQMJ6IO.js";
|
|
8
10
|
import {
|
|
9
11
|
HttpClient,
|
|
10
12
|
agentFromArgvWithFallback,
|
|
@@ -12,7 +14,10 @@ import {
|
|
|
12
14
|
isComplete,
|
|
13
15
|
load,
|
|
14
16
|
setDebug
|
|
15
|
-
} from "../chunk-
|
|
17
|
+
} from "../chunk-U3T5O6NX.js";
|
|
18
|
+
|
|
19
|
+
// src/hooks/session-start.ts
|
|
20
|
+
import { pathToFileURL } from "url";
|
|
16
21
|
|
|
17
22
|
// src/lib/warmup-recall.ts
|
|
18
23
|
import { execSync } from "child_process";
|
|
@@ -67,6 +72,41 @@ async function warmupRecall(cwd, cfg, client) {
|
|
|
67
72
|
|
|
68
73
|
// src/hooks/session-start.ts
|
|
69
74
|
var HOOK_TIMEOUT_MS = 5e3;
|
|
75
|
+
function timeout(ms) {
|
|
76
|
+
return new Promise((resolve) => setTimeout(() => resolve(null), ms));
|
|
77
|
+
}
|
|
78
|
+
async function composeSessionStart(cfg, agent, client, cwd, timeoutMs = HOOK_TIMEOUT_MS) {
|
|
79
|
+
const kbInjectHere = cfg.kbCatalogInjection === "session_start";
|
|
80
|
+
const warmupPromise = cfg.warmupRecall ? Promise.race([warmupRecall(cwd, cfg, client), timeout(timeoutMs).then(() => null)]) : Promise.resolve(null);
|
|
81
|
+
const kbPromise = kbInjectHere ? Promise.race([fetchKbCatalogBlock(client, agent).catch(() => ""), timeout(timeoutMs).then(() => "")]) : Promise.resolve("");
|
|
82
|
+
const [result, kbBlock] = await Promise.all([warmupPromise, kbPromise]);
|
|
83
|
+
const warmupTimedOut = cfg.warmupRecall && result === null;
|
|
84
|
+
const warmupCtx = result && result.ok ? result.additionalContext || "" : "";
|
|
85
|
+
if (!warmupCtx && result && !result.ok) {
|
|
86
|
+
debug("warmup", `no result: ${result.reason}`);
|
|
87
|
+
}
|
|
88
|
+
let ctx = warmupCtx;
|
|
89
|
+
if (kbBlock) ctx = ctx ? `${ctx}
|
|
90
|
+
|
|
91
|
+
${kbBlock}` : kbBlock;
|
|
92
|
+
return {
|
|
93
|
+
ctx,
|
|
94
|
+
memoryCount: result?.ok ? result.memoryCount : 0,
|
|
95
|
+
kbChunkCount: result?.ok ? result.knowledgeChunkCount : 0,
|
|
96
|
+
kbCatalogLines: kbBlock ? kbBlock.split("\n").length : 0,
|
|
97
|
+
warmupTimedOut
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function formatSessionStartStdout(agent, ctx) {
|
|
101
|
+
if (agent === "codex") return ctx + "\n";
|
|
102
|
+
const out = {
|
|
103
|
+
hookSpecificOutput: {
|
|
104
|
+
hookEventName: "SessionStart",
|
|
105
|
+
additionalContext: ctx
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
return JSON.stringify(out) + "\n";
|
|
109
|
+
}
|
|
70
110
|
async function readStdinJson() {
|
|
71
111
|
let raw = "";
|
|
72
112
|
for await (const chunk of process.stdin) raw += chunk;
|
|
@@ -78,9 +118,6 @@ async function readStdinJson() {
|
|
|
78
118
|
return {};
|
|
79
119
|
}
|
|
80
120
|
}
|
|
81
|
-
function timeout(ms) {
|
|
82
|
-
return new Promise((resolve) => setTimeout(() => resolve(null), ms));
|
|
83
|
-
}
|
|
84
121
|
async function main() {
|
|
85
122
|
try {
|
|
86
123
|
const event = await readStdinJson();
|
|
@@ -96,8 +133,9 @@ async function main() {
|
|
|
96
133
|
const cfg = load({ agent });
|
|
97
134
|
setDebug(cfg.debug);
|
|
98
135
|
debug("warmup", "start", { cwd, userId: cfg.userId });
|
|
99
|
-
|
|
100
|
-
|
|
136
|
+
const kbInjectHere = cfg.kbCatalogInjection === "session_start";
|
|
137
|
+
if (!isComplete(cfg) || !cfg.warmupRecall && !kbInjectHere) {
|
|
138
|
+
debug("warmup", "skip (config incomplete or both warmup+kb off)");
|
|
101
139
|
return 0;
|
|
102
140
|
}
|
|
103
141
|
if (isCircuitOpen(agent, cfg.baseUrl)) {
|
|
@@ -109,34 +147,21 @@ async function main() {
|
|
|
109
147
|
apiKey: cfg.apiKey,
|
|
110
148
|
timeoutMs: HOOK_TIMEOUT_MS
|
|
111
149
|
});
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
timeout(HOOK_TIMEOUT_MS).then(() => null)
|
|
115
|
-
]);
|
|
116
|
-
if (!result) {
|
|
150
|
+
const composed = await composeSessionStart(cfg, agent, client, cwd);
|
|
151
|
+
if (composed.warmupTimedOut) {
|
|
117
152
|
process.stderr.write("ctxdb warmup: timeout\n");
|
|
118
|
-
return 0;
|
|
119
153
|
}
|
|
120
|
-
if (!
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
154
|
+
if (!composed.ctx) return 0;
|
|
155
|
+
const { memoryCount, kbChunkCount, kbCatalogLines, ctx } = composed;
|
|
156
|
+
debug(
|
|
157
|
+
"warmup",
|
|
158
|
+
`ok, memories=${memoryCount} kb=${kbChunkCount} kb_catalog=${kbCatalogLines}`
|
|
159
|
+
);
|
|
125
160
|
process.stderr.write(
|
|
126
|
-
`ctxdb warmup: ok (${
|
|
161
|
+
`ctxdb warmup: ok (${memoryCount} memories, ${kbChunkCount} kb chunks, kb_catalog=${kbCatalogLines} lines)
|
|
127
162
|
`
|
|
128
163
|
);
|
|
129
|
-
|
|
130
|
-
process.stdout.write(result.additionalContext + "\n");
|
|
131
|
-
} else {
|
|
132
|
-
const out = {
|
|
133
|
-
hookSpecificOutput: {
|
|
134
|
-
hookEventName: "SessionStart",
|
|
135
|
-
additionalContext: result.additionalContext
|
|
136
|
-
}
|
|
137
|
-
};
|
|
138
|
-
process.stdout.write(JSON.stringify(out) + "\n");
|
|
139
|
-
}
|
|
164
|
+
process.stdout.write(formatSessionStartStdout(agent, ctx));
|
|
140
165
|
return 0;
|
|
141
166
|
} catch (err) {
|
|
142
167
|
process.stderr.write(`ctxdb warmup: unexpected error: ${err?.message ?? err}
|
|
@@ -144,4 +169,11 @@ async function main() {
|
|
|
144
169
|
return 0;
|
|
145
170
|
}
|
|
146
171
|
}
|
|
147
|
-
|
|
172
|
+
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
|
|
173
|
+
main().then((code) => process.exit(code));
|
|
174
|
+
}
|
|
175
|
+
export {
|
|
176
|
+
HOOK_TIMEOUT_MS,
|
|
177
|
+
composeSessionStart,
|
|
178
|
+
formatSessionStartStdout
|
|
179
|
+
};
|
package/dist/hooks/stop.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
isConnectionError,
|
|
5
5
|
resetCircuit,
|
|
6
6
|
tripCircuit
|
|
7
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-KEQMJ6IO.js";
|
|
8
8
|
import {
|
|
9
9
|
CtxdbError,
|
|
10
10
|
HttpClient,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
debug,
|
|
13
13
|
load,
|
|
14
14
|
setDebug
|
|
15
|
-
} from "../chunk-
|
|
15
|
+
} from "../chunk-U3T5O6NX.js";
|
|
16
16
|
|
|
17
17
|
// src/lib/capture-orchestrator.ts
|
|
18
18
|
import {
|
|
@@ -32,7 +32,7 @@ function getGitContext() {
|
|
|
32
32
|
}).trim();
|
|
33
33
|
const lines = raw.split("\n");
|
|
34
34
|
if (lines.length < 2) return null;
|
|
35
|
-
const project = lines[0].split(
|
|
35
|
+
const project = lines[0].split(/[/\\]/).pop();
|
|
36
36
|
if (!project) return null;
|
|
37
37
|
const branch = lines[1];
|
|
38
38
|
return { project, branch };
|
|
@@ -305,18 +305,12 @@ async function captureTurn(transcriptPath, cfg, client, agent = "default") {
|
|
|
305
305
|
}
|
|
306
306
|
const git = getGitContext();
|
|
307
307
|
const skills = extractSkillSignals(rows);
|
|
308
|
-
const
|
|
309
|
-
if (git)
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
const label = s.trigger === "command" ? "user-invoked" : "agent-invoked";
|
|
315
|
-
return s.args ? `${s.skill} (${label}, args: "${s.args}")` : `${s.skill} (${label})`;
|
|
316
|
-
});
|
|
317
|
-
bgParts.push(`Skills used: ${descs.join(", ")}.`);
|
|
318
|
-
}
|
|
319
|
-
const messages = bgParts.length > 0 ? [{ role: "background", content: bgParts.join("\n") }, ...filtered] : filtered;
|
|
308
|
+
const bg = {};
|
|
309
|
+
if (git) bg.git = git;
|
|
310
|
+
if (skills.length > 0) bg.skills = skills.map(
|
|
311
|
+
({ skill, trigger, args }) => args ? { skill, trigger, args } : { skill, trigger }
|
|
312
|
+
);
|
|
313
|
+
const messages = bg.git || bg.skills ? [{ role: "background", content: JSON.stringify(bg) }, ...filtered] : filtered;
|
|
320
314
|
const payload = {
|
|
321
315
|
messages,
|
|
322
316
|
user_id: cfg.userId,
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
} from "../chunk-6S5RJYBC.js";
|
|
5
|
-
import {
|
|
3
|
+
fetchKbCatalogBlock,
|
|
6
4
|
recallTurn
|
|
7
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-C62I23HL.js";
|
|
6
|
+
import "../chunk-6S5RJYBC.js";
|
|
8
7
|
import {
|
|
9
8
|
isCircuitOpen
|
|
10
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-KEQMJ6IO.js";
|
|
11
10
|
import {
|
|
12
11
|
HttpClient,
|
|
13
12
|
agentFromArgvWithFallback,
|
|
@@ -15,23 +14,31 @@ import {
|
|
|
15
14
|
isComplete,
|
|
16
15
|
load,
|
|
17
16
|
setDebug
|
|
18
|
-
} from "../chunk-
|
|
19
|
-
|
|
20
|
-
// src/lib/kb-catalog.ts
|
|
21
|
-
async function fetchKbCatalogBlock(client, agent) {
|
|
22
|
-
const kbs = await listKnowledgeBases(client);
|
|
23
|
-
const active = kbs.filter((kb) => kb.status === "active");
|
|
24
|
-
if (active.length === 0) return "";
|
|
25
|
-
const lines = active.map((kb) => `\xB7 ${kb.name}`);
|
|
26
|
-
return [
|
|
27
|
-
"<available-knowledge-bases>",
|
|
28
|
-
`When you identify that relevant information may exist in the knowledge bases below, you MUST run \`ctxdb kb search "<query>" --kb=<name> --agent=${agent}\` with targeted keywords after initial analysis to supplement and correct your approach. Knowledge bases:`,
|
|
29
|
-
...lines,
|
|
30
|
-
"</available-knowledge-bases>"
|
|
31
|
-
].join("\n");
|
|
32
|
-
}
|
|
17
|
+
} from "../chunk-U3T5O6NX.js";
|
|
33
18
|
|
|
34
19
|
// src/hooks/user-prompt-submit.ts
|
|
20
|
+
import { pathToFileURL } from "url";
|
|
21
|
+
async function composeUserPromptSubmit(cfg, agent, client, prompt) {
|
|
22
|
+
const [recall, kbBlock] = await Promise.all([
|
|
23
|
+
recallTurn(prompt, cfg, client, agent),
|
|
24
|
+
cfg.kbCatalogInjection === "user_prompt_submit" ? fetchKbCatalogBlock(client, agent).catch(() => "") : Promise.resolve("")
|
|
25
|
+
]);
|
|
26
|
+
let ctx = recall.additionalContext || "";
|
|
27
|
+
if (kbBlock) ctx = ctx ? `${ctx}
|
|
28
|
+
|
|
29
|
+
${kbBlock}` : kbBlock;
|
|
30
|
+
return { ctx, recall, kbBlock };
|
|
31
|
+
}
|
|
32
|
+
function formatUserPromptSubmitStdout(agent, ctx) {
|
|
33
|
+
if (agent === "codex") return ctx + "\n";
|
|
34
|
+
const out = {
|
|
35
|
+
hookSpecificOutput: {
|
|
36
|
+
hookEventName: "UserPromptSubmit",
|
|
37
|
+
additionalContext: ctx
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
return JSON.stringify(out) + "\n";
|
|
41
|
+
}
|
|
35
42
|
async function readStdinJson() {
|
|
36
43
|
let raw = "";
|
|
37
44
|
for await (const chunk of process.stdin) raw += chunk;
|
|
@@ -69,36 +76,19 @@ async function main() {
|
|
|
69
76
|
return 0;
|
|
70
77
|
}
|
|
71
78
|
const client = new HttpClient({ baseUrl: cfg.baseUrl, apiKey: cfg.apiKey, timeoutMs: 5e3 });
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
debug("recall", `no result: ${reason}`, result);
|
|
79
|
-
if (result.reason && result.reason.startsWith("http_error:")) {
|
|
80
|
-
process.stderr.write(`ctxdb recall: ${result.reason}
|
|
79
|
+
const { ctx, recall, kbBlock } = await composeUserPromptSubmit(cfg, agent, client, prompt);
|
|
80
|
+
if (!recall.ok && !recall.additionalContext && !kbBlock) {
|
|
81
|
+
const reason = recall.reason ?? "no_context";
|
|
82
|
+
debug("recall", `no result: ${reason}`, recall);
|
|
83
|
+
if (recall.reason && recall.reason.startsWith("http_error:")) {
|
|
84
|
+
process.stderr.write(`ctxdb recall: ${recall.reason}
|
|
81
85
|
`);
|
|
82
86
|
}
|
|
83
87
|
return 0;
|
|
84
88
|
}
|
|
85
|
-
let ctx = result.additionalContext || "";
|
|
86
|
-
if (kbBlock) ctx = ctx ? `${ctx}
|
|
87
|
-
|
|
88
|
-
${kbBlock}` : kbBlock;
|
|
89
89
|
if (!ctx) return 0;
|
|
90
90
|
debug("recall", `ok, additionalContext length=${ctx.length}`);
|
|
91
|
-
|
|
92
|
-
process.stdout.write(ctx + "\n");
|
|
93
|
-
} else {
|
|
94
|
-
const out = {
|
|
95
|
-
hookSpecificOutput: {
|
|
96
|
-
hookEventName: "UserPromptSubmit",
|
|
97
|
-
additionalContext: ctx
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
process.stdout.write(JSON.stringify(out) + "\n");
|
|
101
|
-
}
|
|
91
|
+
process.stdout.write(formatUserPromptSubmitStdout(agent, ctx));
|
|
102
92
|
return 0;
|
|
103
93
|
} catch (err) {
|
|
104
94
|
process.stderr.write(`ctxdb recall: unexpected error: ${err?.message ?? err}
|
|
@@ -106,4 +96,10 @@ ${kbBlock}` : kbBlock;
|
|
|
106
96
|
return 0;
|
|
107
97
|
}
|
|
108
98
|
}
|
|
109
|
-
|
|
99
|
+
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
|
|
100
|
+
main().then((code) => process.exit(code));
|
|
101
|
+
}
|
|
102
|
+
export {
|
|
103
|
+
composeUserPromptSubmit,
|
|
104
|
+
formatUserPromptSubmitStdout
|
|
105
|
+
};
|
|
@@ -1,161 +1,189 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: contextdb-knowledge
|
|
3
|
-
description:
|
|
3
|
+
description: contextdb 知识库高频操作(查询 / 浏览 KB / 上传文档)和命令速查。当用户提到"查知识库 / 查 KB / 搜知识库 / 查 KB chunks / 上传到 KB / 灌进知识库 / 列出知识库 / 列出 KB / 看文档 / 建知识库 / 建 KB"等场景时使用。配套 contextdb-memory 处理长期记忆。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# contextdb Knowledge Base
|
|
6
|
+
# contextdb Knowledge Base 操作
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## 1. 什么时候用这个 skill
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
- 配置命令:`ctxdb setup --api-key=<key> --base-url=<url> [--user-id=<id>]`
|
|
12
|
-
- 带 `--agent <name>` 时写入指定 agent 的配置段并安装 hooks + skill
|
|
13
|
-
- 不带 `--agent` 时写入 `agents.default` 配置段(仅 CLI 使用)
|
|
14
|
-
- 连接检查:`ctxdb ping`,状态查看:`ctxdb status`
|
|
10
|
+
用户在做"查/灌/列知识库"的任何动作时进这个 skill。触发关键词:**查知识库 / 查 KB / 搜知识库 / 查 KB chunks / 上传到 KB / 灌进知识库 / 列出知识库 / 列出 KB / 看文档 / 建知识库**。
|
|
15
11
|
|
|
16
|
-
|
|
12
|
+
- 用户想**找已有 KB 里的内容** → §2 recipe 1
|
|
13
|
+
- 用户**不知道有哪些 KB** → §2 recipe 2
|
|
14
|
+
- 用户想**了解某个 KB 收了哪些文档** → §2 recipe 3
|
|
15
|
+
- 用户要**写入/上传内容** → §2 recipe 4/5
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
配套 skill:**contextdb-memory**(长期记忆操作)。两者用同一份 `~/.ctxdb/ctxdb.json` 配置和同一个 `--agent` 路由。
|
|
18
|
+
|
|
19
|
+
## 2. 高频 recipes
|
|
20
|
+
|
|
21
|
+
> 命令里 `--agent=qoder` 是必填参数。如果用户跑别的 agent(codex / claude),把 `qoder` 替换成对应名字。
|
|
22
|
+
|
|
23
|
+
### Recipe 1:在已知 KB 中查内容(最常见)
|
|
24
|
+
|
|
25
|
+
**场景**:用户问"X KB 里关于 Y 的内容"、"在知识库 Z 查一下 W"。
|
|
19
26
|
|
|
20
27
|
```sh
|
|
21
|
-
ctxdb kb search "<query>"
|
|
28
|
+
ctxdb kb search "<query>" --kb=<kb-name> --top-k=6 --agent=qoder
|
|
22
29
|
```
|
|
23
30
|
|
|
24
|
-
|
|
25
|
-
|------|------|--------|
|
|
26
|
-
| `<query>` | 搜索文本(必填) | |
|
|
27
|
-
| `--kb` | 指定搜索的知识库名称,逗号分隔 | 搜索所有 KB |
|
|
28
|
-
| `--top-k` | 返回 chunk 数上限 | 6 |
|
|
29
|
-
| `--threshold` | 相关度阈值 | 0.4 |
|
|
30
|
-
| `--verbose` | 每个 chunk 增加 `doc_name` / `kb_id` / `doc_id` / `tags` 字段 | false |
|
|
31
|
-
| `--raw` | 服务端原始响应(13+ 字段,含 tokenizer 细节) | false |
|
|
32
|
-
| `--agent` | 指定操作哪个 agent 的配置 | agents.default |
|
|
33
|
-
|
|
34
|
-
**输出 JSON 结构**(默认):
|
|
35
|
-
|
|
36
|
-
```json
|
|
37
|
-
{
|
|
38
|
-
"chunks": [
|
|
39
|
-
{
|
|
40
|
-
"content": "chunk 文本内容",
|
|
41
|
-
"score": 0.82
|
|
42
|
-
}
|
|
43
|
-
]
|
|
44
|
-
}
|
|
45
|
-
```
|
|
31
|
+
返回 `chunks` 数组,按 `score` 倒序判断相关性;默认 threshold 0.4,空结果时见 §5 注意事项 4。
|
|
46
32
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
```json
|
|
50
|
-
{
|
|
51
|
-
"chunks": [
|
|
52
|
-
{
|
|
53
|
-
"content": "chunk 文本内容",
|
|
54
|
-
"score": 0.82,
|
|
55
|
-
"doc_name": "zircodb-overview",
|
|
56
|
-
"kb_id": "kb-uuid",
|
|
57
|
-
"doc_id": "doc-uuid",
|
|
58
|
-
"tags": ["architecture"]
|
|
59
|
-
}
|
|
60
|
-
]
|
|
61
|
-
}
|
|
62
|
-
```
|
|
33
|
+
**何时不适用**:用户没指定 KB → 先走 recipe 2 列出来确认。
|
|
63
34
|
|
|
64
|
-
|
|
35
|
+
### Recipe 2:列出所有 KB(不知道有哪些)
|
|
65
36
|
|
|
66
|
-
|
|
37
|
+
**场景**:用户问"有哪些知识库"、"列一下 KB",或本身在写 recipe 1 之前需要确认 KB 名。
|
|
67
38
|
|
|
68
39
|
```sh
|
|
69
|
-
ctxdb kb
|
|
40
|
+
ctxdb kb list --agent=qoder
|
|
70
41
|
```
|
|
71
42
|
|
|
72
|
-
|
|
73
|
-
|------|------|
|
|
74
|
-
| `<kb-name>` | 知识库名称(必填) |
|
|
75
|
-
| `--description` | 知识库描述 |
|
|
43
|
+
返回 `knowledge_bases` 数组,每项含 `name` / `id` / `document_count`。跨多 KB 查 chunk 时把 `name` 拼起来:`--kb=a,b,c`。
|
|
76
44
|
|
|
77
|
-
|
|
45
|
+
**何时不适用**:hook 已注入 `<available-knowledge-bases>` 块时,直接读那个块更省一次调用(见 §4)。
|
|
78
46
|
|
|
79
|
-
|
|
47
|
+
### Recipe 3:列出某 KB 的文档清单
|
|
80
48
|
|
|
81
|
-
|
|
49
|
+
**场景**:用户问"X KB 收了哪些文档"、"X KB 里有几篇"。
|
|
82
50
|
|
|
83
51
|
```sh
|
|
84
|
-
ctxdb kb
|
|
52
|
+
ctxdb kb documents-list <kb-name> --agent=qoder
|
|
85
53
|
```
|
|
86
54
|
|
|
87
|
-
|
|
88
|
-
|------|------|
|
|
89
|
-
| `<kb-name>` | 目标知识库名称(须已存在) |
|
|
90
|
-
| `<local-path>` | 本机文件路径 |
|
|
91
|
-
| `--doc-name` | 服务端文档名(默认取文件名) |
|
|
92
|
-
| `--file-path` | 服务端逻辑路径(归档/分类用) |
|
|
93
|
-
| `--no-wait` | 不等待 chunking 完成,立即返回 |
|
|
55
|
+
返回 `documents` 数组,每项含 `name` / `id` / `size`。
|
|
94
56
|
|
|
95
|
-
|
|
57
|
+
**何时不适用**:用户要的是文档**正文**而不是清单 → 走 recipe 1 用 `kb search`(见 §5 注意事项 1)。
|
|
96
58
|
|
|
97
|
-
|
|
59
|
+
### Recipe 4:灌一段文字进 KB
|
|
98
60
|
|
|
99
|
-
|
|
61
|
+
**场景**:用户说"把这段记进 X KB"、"上传这段文字到知识库 Y"。
|
|
100
62
|
|
|
101
63
|
```sh
|
|
102
|
-
ctxdb kb upload-text <kb-name> <doc-name> --text="<body>"
|
|
64
|
+
ctxdb kb upload-text <kb-name> <doc-name> --text="<body>" --agent=qoder
|
|
103
65
|
```
|
|
104
66
|
|
|
105
|
-
|
|
106
|
-
|------|------|
|
|
107
|
-
| `<kb-name>` | 目标知识库名称(须已存在) |
|
|
108
|
-
| `<doc-name>` | 文档名称 |
|
|
109
|
-
| `--text` | 文本内容(必填) |
|
|
110
|
-
| `--file-path` | 服务端逻辑路径 |
|
|
111
|
-
| `--no-wait` | 不等待 chunking 完成 |
|
|
67
|
+
副作用操作。**用户没明示 KB 名前先 recipe 2 列出来确认**(见 §5 注意事项 3)。KB 不存在会直接报错,不自动创建。
|
|
112
68
|
|
|
113
|
-
|
|
69
|
+
**何时不适用**:内容是本地文件 → recipe 5。
|
|
114
70
|
|
|
115
|
-
|
|
71
|
+
### Recipe 5:上传本地文件到 KB
|
|
72
|
+
|
|
73
|
+
**场景**:用户说"把 X.pdf 上传到 Y KB"、"灌这个文件进知识库 Z"。
|
|
116
74
|
|
|
117
75
|
```sh
|
|
118
|
-
ctxdb kb
|
|
76
|
+
ctxdb kb upload-file <kb-name> <local-path> --agent=qoder
|
|
119
77
|
```
|
|
120
78
|
|
|
121
|
-
|
|
79
|
+
支持格式:PDF / DOCX / MD / TXT。默认等待 chunking 完成;想立即返回加 `--no-wait`(见 §6)。
|
|
122
80
|
|
|
123
|
-
|
|
81
|
+
**何时不适用**:要上传的内容是字符串而非本地文件 → recipe 4。
|
|
124
82
|
|
|
125
|
-
|
|
83
|
+
## 3. 命令速查
|
|
126
84
|
|
|
127
|
-
|
|
128
|
-
ctxdb kb documents-list <kb-name> [--agent=<name>]
|
|
129
|
-
```
|
|
85
|
+
`--agent=<name>` 必填;详细解析链见 §6。其他 advanced flag 见 §6。
|
|
130
86
|
|
|
131
|
-
|
|
87
|
+
| 子命令 | minimal signature |
|
|
88
|
+
|---|---|
|
|
89
|
+
| `kb search` | `ctxdb kb search "<query>" --agent=<name>` |
|
|
90
|
+
| `kb list` | `ctxdb kb list --agent=<name>` |
|
|
91
|
+
| `kb documents-list` | `ctxdb kb documents-list <kb-name> --agent=<name>` |
|
|
92
|
+
| `kb document-get` | `ctxdb kb document-get <kb-name> <doc-id> --agent=<name>` |
|
|
93
|
+
| `kb create` | `ctxdb kb create <kb-name> --agent=<name>` |
|
|
94
|
+
| `kb upload-text` | `ctxdb kb upload-text <kb-name> <doc-name> --text="<body>" --agent=<name>` |
|
|
95
|
+
| `kb upload-file` | `ctxdb kb upload-file <kb-name> <local-path> --agent=<name>` |
|
|
132
96
|
|
|
133
|
-
|
|
97
|
+
所有命令 JSON 写 stdout、错误写 stderr 非零退出码。
|
|
134
98
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
99
|
+
## 4. Hooks 感知
|
|
100
|
+
|
|
101
|
+
**`<available-knowledge-bases>` 块**:UPS / SessionStart hook 注入到 system context;存在时直接列出 workspace 内所有 KB 名(省一次 `kb list` 调用)。可直接喂给 `--kb=<name>`。
|
|
102
|
+
|
|
103
|
+
**`<recalled-memories>` 块**:是 contextdb-memory 的产物,但 `kb search --knowledge` 会复用同样的 retrieval 路径。两块独立。
|
|
104
|
+
|
|
105
|
+
**B-3c 守卫**:含 `kb upload-text` / `kb upload-file` 的对话轮次,其内容**不会**被 autoCapture 写入记忆(防文档原文混进记忆桶)。
|
|
106
|
+
|
|
107
|
+
**纯 CLI 模式**:对话里没有上面任何块 → 说明当前不在 hook 环境(agent 是裸 CLI 调用),所有信息只能主动调命令拿。
|
|
138
108
|
|
|
139
|
-
##
|
|
109
|
+
## 5. 注意事项
|
|
140
110
|
|
|
141
|
-
|
|
111
|
+
### 注意事项 1:`kb document-get` 只返 metadata,**不返正文**
|
|
142
112
|
|
|
143
|
-
-
|
|
144
|
-
-
|
|
145
|
-
-
|
|
113
|
+
- **现象**:想读文档内容,调 `kb document-get` 拿到的只有 `{id, name, size, upload_time, tags}`,没 `content` 字段
|
|
114
|
+
- **根因**:服务端当前只暴露元信息端点;获取正文需走 chunk 检索
|
|
115
|
+
- **正确做法**:用 `kb search "<关键词>" --kb=<name>` 拿 chunks,或 `kb documents-list <kb-name>` 后浏览返回的内嵌 chunk(如有)
|
|
146
116
|
|
|
147
|
-
|
|
117
|
+
### 注意事项 2:解析 JSON 用 `jq`,**不要 inline `python3 -c`**
|
|
148
118
|
|
|
149
|
-
|
|
119
|
+
- **现象**:在 shell 里写 `ctxdb kb list | python3 -c "import json,sys; ..."` 经常因为单/双引号嵌套 escape 失败
|
|
120
|
+
- **根因**:shell + python -c 的引号是两层独立 escape,组合时极容易漏
|
|
121
|
+
- **正确做法**:`ctxdb kb list --agent=qoder | jq '.knowledge_bases[].name'`;jq 表达式在单引号里不需要 escape
|
|
150
122
|
|
|
151
|
-
|
|
123
|
+
### 注意事项 3:`upload-*` / `create` 是副作用,没明示 KB 名前必须先确认
|
|
152
124
|
|
|
153
|
-
|
|
125
|
+
- **现象**:用户只说"把这段记进知识库"没指定 KB,agent 直接用了某个猜的 KB 名 → 写到错的桶
|
|
126
|
+
- **根因**:写命令不会跟 agent 二次确认;KB 不存在时报错,但**KB 名拼错却恰好命中另一个真实 KB**时会静默写错
|
|
127
|
+
- **正确做法**:用户没明示 → 必须先 `kb list` 拿候选清单,跟用户确认目标后才执行
|
|
154
128
|
|
|
155
|
-
|
|
129
|
+
### 注意事项 4:`kb search` 空结果时降阈值再试一次
|
|
130
|
+
|
|
131
|
+
- **现象**:query 明明跟内容相关,但 `chunks: []`
|
|
132
|
+
- **根因**:默认 `--threshold=0.4`,短 query / 关键词偏冷时打不到
|
|
133
|
+
- **正确做法**:降到 `--threshold=0.2` 或 `--threshold=0.3` 重试一次再判定真的没命中;仍空才放弃
|
|
134
|
+
|
|
135
|
+
### 注意事项 5:`upload-*` 不会自动建 KB
|
|
136
|
+
|
|
137
|
+
- **现象**:`kb upload-text foo bar --text=...` 报错 "kb 'foo' not found"
|
|
138
|
+
- **根因**:上传命令只写已有 KB,**不会**为了你 implicit 建一个
|
|
139
|
+
- **正确做法**:先 `ctxdb kb create foo --agent=qoder`,再 upload
|
|
140
|
+
|
|
141
|
+
## 6. 高级参数
|
|
142
|
+
|
|
143
|
+
### `kb search` 全部 flag
|
|
144
|
+
|
|
145
|
+
| 参数 | 说明 | 默认值 |
|
|
146
|
+
|------|------|--------|
|
|
147
|
+
| `--kb=<name1,name2>` | 限定搜索的 KB,逗号分隔 | 搜所有 |
|
|
148
|
+
| `--top-k=N` | chunk 数上限 | 6 |
|
|
149
|
+
| `--threshold=F` | 相关度阈值 | 0.4 |
|
|
150
|
+
| `--verbose` | 每个 chunk 加 `doc_name` / `kb_id` / `doc_id` / `tags` | false |
|
|
151
|
+
| `--raw` | 服务端原始响应(13+ 字段,含 tokenizer 细节) | false |
|
|
152
|
+
|
|
153
|
+
### `kb upload-file` / `kb upload-text` 全部 flag
|
|
154
|
+
|
|
155
|
+
| 参数 | 说明 |
|
|
156
|
+
|------|------|
|
|
157
|
+
| `--doc-name` | (`upload-file`)服务端文档名,默认取本地文件名 |
|
|
158
|
+
| `--file-path` | 服务端逻辑路径(归档/分类用) |
|
|
159
|
+
| `--no-wait` | 不等 chunking 完成立即返回 |
|
|
160
|
+
|
|
161
|
+
### `kb create` 全部 flag
|
|
162
|
+
|
|
163
|
+
| 参数 | 说明 |
|
|
164
|
+
|------|------|
|
|
165
|
+
| `--description` | 知识库描述 |
|
|
166
|
+
|
|
167
|
+
### `kb list` / `kb documents-list` / `kb document-get`
|
|
168
|
+
|
|
169
|
+
无额外 flag(除通用参数)。
|
|
170
|
+
|
|
171
|
+
### 通用参数
|
|
172
|
+
|
|
173
|
+
| 参数 | 说明 |
|
|
174
|
+
|------|------|
|
|
175
|
+
| `--agent=<name>` | **必填**。指定操作哪个 agent 的配置桶(`qoder` / `codex` / `claude` / `default`) |
|
|
156
176
|
|
|
157
|
-
|
|
177
|
+
`--agent` 解析顺序:
|
|
178
|
+
1. 命令行显式 `--agent=<name>` → 用它
|
|
179
|
+
2. 缺省 → 读 `CTXDB_AGENT` 环境变量
|
|
180
|
+
3. env 也没有 → 落到 `"default"` 桶
|
|
181
|
+
4. 若 `"default"` 桶未通过 `ctxdb setup --api-key=... --base-url=...` 配置过 → exit 2 "config incomplete for agent default"
|
|
158
182
|
|
|
159
|
-
|
|
183
|
+
## 7. 配置
|
|
160
184
|
|
|
161
|
-
|
|
185
|
+
- 配置文件:`~/.ctxdb/ctxdb.json`,分 `agents.<name>` 段(`qoder` / `codex` / `claude` / `default`),互不复用
|
|
186
|
+
- 配置命令:`ctxdb setup --agent <name> --api-key=<key> --base-url=<url> [--user-id=<id>]`
|
|
187
|
+
- 带 `--agent` 时还会装 hooks + skill;不带 `--agent` 时只写 `agents.default` 段(仅 CLI 用)
|
|
188
|
+
- 连接检查:`ctxdb ping --agent=<name>`
|
|
189
|
+
- 状态查看:`ctxdb status --agent=<name>`
|
|
@@ -1,142 +1,182 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: contextdb-memory
|
|
3
|
-
description:
|
|
3
|
+
description: contextdb 长期记忆高频操作(主动记忆 / 搜索过往 / 列出审计 / 改删某条)和命令速查。当用户提到"记住 / 记一下 / 记一笔 / 写进记忆 / 查记忆 / 找记忆 / 列记忆 / 删记忆 / 改记忆 / 之前说过 / 上次提到"等场景时使用。配套 contextdb-knowledge 处理知识库。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# contextdb Memory
|
|
6
|
+
# contextdb Memory 操作
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## 1. 什么时候用这个 skill
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
- 配置命令:`ctxdb setup --api-key=<key> --base-url=<url> [--user-id=<id>]`
|
|
12
|
-
- 带 `--agent <name>` 时写入指定 agent 的配置段并安装 hooks + skill
|
|
13
|
-
- 不带 `--agent` 时写入 `agents.default` 配置段(仅 CLI 使用)
|
|
14
|
-
- 连接检查:`ctxdb ping`,状态查看:`ctxdb status`
|
|
10
|
+
用户在做"写/找/改/删长期记忆"的任何动作时进这个 skill。触发关键词:**记住 / 记一下 / 记一笔 / 写进记忆 / 查记忆 / 找记忆 / 列记忆 / 删记忆 / 改记忆 / 之前说过 / 上次提到**。
|
|
15
11
|
|
|
16
|
-
|
|
12
|
+
- 用户说"记住 X"、"帮我记一下" → §2 recipe 1
|
|
13
|
+
- 用户问"之前我们聊过 Y 吗"、"上次怎么说的" → §2 recipe 2
|
|
14
|
+
- 用户想**审计/排查**记忆是否写进去 → §2 recipe 3
|
|
15
|
+
- 用户要**改某条 / 删某条** → §2 recipe 4
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
配套 skill:**contextdb-knowledge**(知识库操作)。两者用同一份 `~/.ctxdb/ctxdb.json` 配置和同一个 `--agent` 路由。
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
ctxdb memory add "<text>" [--no-infer] [--user-id=<id>] [--metadata=K1=V1,K2=V2] [--agent=<name>]
|
|
22
|
-
```
|
|
19
|
+
## 2. 高频 recipes
|
|
23
20
|
|
|
24
|
-
|
|
25
|
-
|------|------|
|
|
26
|
-
| `<text>` | 要记忆的文本内容(必填) |
|
|
27
|
-
| `--no-infer` | 跳过 LLM fact-extraction,原文直存 |
|
|
28
|
-
| `--user-id` | 覆盖配置中的 user_id |
|
|
29
|
-
| `--metadata` | 附加键值对元数据,逗号分隔 |
|
|
30
|
-
| `--agent` | 指定操作哪个 agent 的记忆桶(默认使用 agents.default 配置) |
|
|
31
|
-
|
|
32
|
-
服务端默认走同步模式(`async_mode: false`),等待 LLM fact-extraction 完成后返回结果。`--no-infer` 跳过提炼,适用于需要原文保留的场景(如项目代号、精确数值)。
|
|
33
|
-
|
|
34
|
-
**输出 JSON 结构**(同步模式):
|
|
35
|
-
|
|
36
|
-
```json
|
|
37
|
-
{
|
|
38
|
-
"results": [
|
|
39
|
-
{
|
|
40
|
-
"id": "mem-uuid",
|
|
41
|
-
"memory": "提炼后的事实文本",
|
|
42
|
-
"event": "ADD"
|
|
43
|
-
}
|
|
44
|
-
]
|
|
45
|
-
}
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
`event` 可能的值:`ADD`(新增)、`UPDATE`(更新已有记忆)、`NONE`(无新事实提取)。`results` 为空数组表示服务端未从输入中提取出新事实。
|
|
21
|
+
> 命令里 `--agent=qoder` 是必填参数。如果用户跑别的 agent(codex / claude),把 `qoder` 替换成对应名字。
|
|
49
22
|
|
|
50
|
-
|
|
23
|
+
### Recipe 1:主动记忆("记住 X")
|
|
51
24
|
|
|
52
|
-
|
|
25
|
+
**场景**:用户明确说"记住"、"帮我记一下"、"把这条写进记忆"。
|
|
53
26
|
|
|
54
27
|
```sh
|
|
55
|
-
ctxdb memory
|
|
28
|
+
ctxdb memory add "<text>" --agent=qoder
|
|
56
29
|
```
|
|
57
30
|
|
|
58
|
-
|
|
59
|
-
|------|------|--------|
|
|
60
|
-
| `<query>` | 搜索文本(必填) | |
|
|
61
|
-
| `--top-k` | 返回条数上限 | 5 |
|
|
62
|
-
| `--threshold` | 相关度阈值 | 0.4 |
|
|
63
|
-
| `--knowledge` | 同时搜索知识库 chunks | false |
|
|
64
|
-
| `--verbose` | 增加 doc_name/kb_id 等字段(knowledge 模式) | false |
|
|
65
|
-
| `--raw` | 服务端原始响应 | false |
|
|
66
|
-
| `--agent` | 指定操作哪个 agent 的记忆桶 | agents.default |
|
|
67
|
-
|
|
68
|
-
**输出 JSON 结构**:
|
|
69
|
-
|
|
70
|
-
```json
|
|
71
|
-
{
|
|
72
|
-
"results": [
|
|
73
|
-
{
|
|
74
|
-
"id": "mem-uuid",
|
|
75
|
-
"memory": "事实文本",
|
|
76
|
-
"score": 0.85
|
|
77
|
-
}
|
|
78
|
-
]
|
|
79
|
-
}
|
|
80
|
-
```
|
|
31
|
+
默认走 LLM fact-extraction(同步等结果)。想原文存(项目代号 / 精确数值)加 `--no-infer`(见 §6)。
|
|
81
32
|
|
|
82
|
-
|
|
33
|
+
**何时不适用**:用户没明示要记 → 不要主动调;autoCapture 已在每轮 Stop hook 自动跑(见 §4)。
|
|
83
34
|
|
|
84
|
-
|
|
35
|
+
**`event: NONE` 情况**:返回 `results: []` 时表示 LLM 觉得没新事实可提,是**正常返回不是错误**(见 §5 注意事项 1)。
|
|
85
36
|
|
|
86
|
-
|
|
37
|
+
### Recipe 2:找过去说过的(搜记忆)
|
|
87
38
|
|
|
88
|
-
|
|
39
|
+
**场景**:用户问"之前我们讨论过 X 吗"、"上次怎么说的"、"我之前提过 Y 没"。
|
|
89
40
|
|
|
90
41
|
```sh
|
|
91
|
-
ctxdb memory
|
|
42
|
+
ctxdb memory search "<query>" --agent=qoder
|
|
92
43
|
```
|
|
93
44
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
| `--category` | 按分类过滤 | |
|
|
45
|
+
返回 `results` 数组,每项含 `id` / `memory` / `score`,按 score 倒序。top-5 默认。想同时搜知识库 chunks 加 `--knowledge`(见 §6)。
|
|
46
|
+
|
|
47
|
+
**何时不适用**:用户想看的是当前会话的原对话(不是提炼后的事实) → 走 transcript / 聊天记录而非这个命令;记忆桶里只有提炼版(见 §5 注意事项 2)。
|
|
98
48
|
|
|
99
|
-
|
|
49
|
+
### Recipe 3:浏览/审计记忆
|
|
100
50
|
|
|
101
|
-
|
|
51
|
+
**场景**:排查"刚才那条记进去没"、用户想看"系统记了我什么"。
|
|
102
52
|
|
|
103
53
|
```sh
|
|
104
|
-
ctxdb memory
|
|
54
|
+
ctxdb memory list --page-size=20 --agent=qoder
|
|
105
55
|
```
|
|
106
56
|
|
|
107
|
-
|
|
57
|
+
返回 `results` 数组(含 `total` 计数)。按 category 过滤加 `--category=<cat>`(见 §6)。
|
|
108
58
|
|
|
109
|
-
|
|
59
|
+
**何时不适用**:明确知道要找某关键词 → 用 recipe 2 更精准。
|
|
60
|
+
|
|
61
|
+
### Recipe 4:改/删某条记忆
|
|
62
|
+
|
|
63
|
+
**场景**:用户说"把那条改成 X"、"删掉之前关于 Y 的记忆"。
|
|
110
64
|
|
|
111
65
|
```sh
|
|
112
|
-
|
|
66
|
+
# 改
|
|
67
|
+
ctxdb memory update <memory-id> --text="<new-text>" --agent=qoder
|
|
68
|
+
|
|
69
|
+
# 删单条
|
|
70
|
+
ctxdb memory delete <memory-id> --agent=qoder
|
|
113
71
|
```
|
|
114
72
|
|
|
115
|
-
|
|
73
|
+
`<memory-id>` 必须先用 recipe 2 或 recipe 3 拿到。
|
|
116
74
|
|
|
117
|
-
|
|
75
|
+
**何时不适用**:用户说"清空所有记忆" → 走 `ctxdb memory delete --all`,**必须先跟用户二次确认**(见 §5 注意事项 3,不可逆操作)。
|
|
118
76
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
77
|
+
## 3. 命令速查
|
|
78
|
+
|
|
79
|
+
`--agent=<name>` 必填;详细解析链见 §6。其他 advanced flag 见 §6。
|
|
80
|
+
|
|
81
|
+
| 子命令 | minimal signature |
|
|
82
|
+
|---|---|
|
|
83
|
+
| `memory add` | `ctxdb memory add "<text>" --agent=<name>` |
|
|
84
|
+
| `memory search` | `ctxdb memory search "<query>" --agent=<name>` |
|
|
85
|
+
| `memory list` | `ctxdb memory list --agent=<name>` |
|
|
86
|
+
| `memory get` | `ctxdb memory get <memory-id> --agent=<name>` |
|
|
87
|
+
| `memory update` | `ctxdb memory update <memory-id> --text="<new-text>" --agent=<name>` |
|
|
88
|
+
| `memory delete` | `ctxdb memory delete <memory-id> --agent=<name>` <br> `ctxdb memory delete --all --agent=<name>` |
|
|
89
|
+
|
|
90
|
+
所有命令 JSON 写 stdout、错误写 stderr 非零退出码。
|
|
91
|
+
|
|
92
|
+
## 4. Hooks 感知
|
|
93
|
+
|
|
94
|
+
**`<recalled-memories>` 块**:UserPromptSubmit hook 在每个 user prompt 提交时自动用 prompt 作 query 跑 `memory search`,把命中的 top-K 记忆塞进 system context。存在 → 当前在 hook 环境(recall 自动)。
|
|
95
|
+
|
|
96
|
+
**autoCapture(Stop hook)**:每轮对话结束后服务端异步提炼事实写记忆,**不用 agent 主动调** `memory add`。`memory add` 用于用户**明确强调**的内容(强加重要性)。
|
|
97
|
+
|
|
98
|
+
**B-3c 守卫**:含 `kb upload-text` / `kb upload-file` 的轮次,**autoCapture 跳过**这一轮(防文档原文进记忆桶)。
|
|
123
99
|
|
|
124
|
-
|
|
100
|
+
**纯 CLI 模式**:对话里没 `<recalled-memories>` 块 → 不在 hook 环境(裸 CLI),所有 recall / capture 只能主动 `ctxdb memory search` / `ctxdb memory add`。
|
|
125
101
|
|
|
126
|
-
##
|
|
102
|
+
## 5. 注意事项
|
|
127
103
|
|
|
128
|
-
|
|
104
|
+
### 注意事项 1:`memory add` 返回 `event: NONE` 是正常的
|
|
129
105
|
|
|
130
|
-
|
|
106
|
+
- **现象**:调 `memory add "<text>"` 后 `results` 为空数组、`event: NONE`
|
|
107
|
+
- **根因**:服务端默认走 LLM fact-extraction,LLM 判断这段话**没新事实可提**(已存在 / 太啰嗦 / 不构成事实)
|
|
108
|
+
- **正确做法**:想强制原文存加 `--no-infer`;如果用户输入的就是"已知事实重复",告诉用户"系统认为这条不构成新事实"而不是判定写失败
|
|
131
109
|
|
|
132
|
-
|
|
110
|
+
### 注意事项 2:`memory search` 拿到的是**提炼后事实**,不是原对话
|
|
133
111
|
|
|
134
|
-
|
|
112
|
+
- **现象**:用户问"上次我说过 X 吗",`memory search "X"` 返回的是 LLM 提炼出的事实,跟用户原话措辞不同;用户会觉得"这不是我说的"
|
|
113
|
+
- **根因**:autoCapture / `memory add` 默认走 fact-extraction,存的是提炼版
|
|
114
|
+
- **正确做法**:要拿用户原话 → 看 transcript / 聊天记录文件;记忆桶只能告诉你"用户曾说过类似 X 这个事实"
|
|
135
115
|
|
|
136
|
-
|
|
116
|
+
### 注意事项 3:`memory delete --all` 不可逆,**必须二次确认**
|
|
117
|
+
|
|
118
|
+
- **现象**:用户说"清空记忆",agent 直接跑 `memory delete --all`
|
|
119
|
+
- **根因**:这条命令清掉当前 `--agent` 桶下绑定 user_id 的**所有**记忆;服务端不存软删,事后无法恢复
|
|
120
|
+
- **正确做法**:跑之前必须跟用户确认"将清空 agent=<name> 桶下所有记忆,不可恢复,确认吗?";用户回 yes 才执行
|
|
121
|
+
|
|
122
|
+
### 注意事项 4:`--user-id` 覆盖配置桶,**别随手传**
|
|
123
|
+
|
|
124
|
+
- **现象**:调 `memory add "..." --user-id=foo` 后 `memory search` 找不到(搜默认桶);或反过来 search 用了 `--user-id` add 没用 → 写一个桶搜另一个桶
|
|
125
|
+
- **根因**:`--user-id` 直接覆盖配置文件里 `agents.<name>.user_id`,桶就切了
|
|
126
|
+
- **正确做法**:除非你明确知道"现在要操作另一个 user 的记忆",**否则不要传 `--user-id`**。让默认配置 user_id 兜底,写读自动一致
|
|
127
|
+
|
|
128
|
+
## 6. 高级参数
|
|
129
|
+
|
|
130
|
+
### `memory add` 全部 flag
|
|
131
|
+
|
|
132
|
+
| 参数 | 说明 |
|
|
133
|
+
|------|------|
|
|
134
|
+
| `--no-infer` | 跳过 LLM fact-extraction,原文直存(项目代号 / 精确数值用)|
|
|
135
|
+
| `--metadata=K1=V1,K2=V2` | 附加键值对元数据,逗号分隔 |
|
|
136
|
+
| `--user-id=<id>` | 覆盖配置 user_id(**慎用**,见 §5 注意事项 4)|
|
|
137
|
+
|
|
138
|
+
服务端默认同步模式(`async_mode: false`),等 LLM 完成才返回。`event` 字段可能:`ADD` / `UPDATE` / `NONE`。
|
|
139
|
+
|
|
140
|
+
### `memory search` 全部 flag
|
|
141
|
+
|
|
142
|
+
| 参数 | 说明 | 默认值 |
|
|
143
|
+
|------|------|--------|
|
|
144
|
+
| `--top-k=N` | 返回条数上限 | 5 |
|
|
145
|
+
| `--threshold=F` | 相关度阈值 | 0.4 |
|
|
146
|
+
| `--knowledge` | 同时搜知识库 chunks | false |
|
|
147
|
+
| `--verbose` | knowledge 模式下加 `doc_name` / `kb_id` / `doc_id` / `tags` | false |
|
|
148
|
+
| `--raw` | 服务端原始响应 | false |
|
|
149
|
+
| `--user-id=<id>` | 覆盖配置 user_id(**慎用**)| |
|
|
150
|
+
|
|
151
|
+
带 `--knowledge` 时输出额外 `knowledge` 数组(结构同 contextdb-knowledge 的 `chunks`)。
|
|
152
|
+
|
|
153
|
+
### `memory list` 全部 flag
|
|
154
|
+
|
|
155
|
+
| 参数 | 说明 | 默认值 |
|
|
156
|
+
|------|------|--------|
|
|
157
|
+
| `--page-size=N` | 每页条数 | 100 |
|
|
158
|
+
| `--category=<cat>` | 按分类过滤 | |
|
|
159
|
+
|
|
160
|
+
### `memory get` / `memory update` / `memory delete`
|
|
161
|
+
|
|
162
|
+
无额外 flag(除通用参数)。`delete --all` 见 §5 注意事项 3。
|
|
163
|
+
|
|
164
|
+
### 通用参数
|
|
165
|
+
|
|
166
|
+
| 参数 | 说明 |
|
|
167
|
+
|------|------|
|
|
168
|
+
| `--agent=<name>` | **必填**。指定操作哪个 agent 的配置桶(`qoder` / `codex` / `claude` / `default`) |
|
|
137
169
|
|
|
138
|
-
|
|
170
|
+
`--agent` 解析顺序:
|
|
171
|
+
1. 命令行显式 `--agent=<name>` → 用它
|
|
172
|
+
2. 缺省 → 读 `CTXDB_AGENT` 环境变量
|
|
173
|
+
3. env 也没有 → 落到 `"default"` 桶
|
|
174
|
+
4. 若 `"default"` 桶未通过 `ctxdb setup --api-key=... --base-url=...` 配置过 → exit 2 "config incomplete for agent default"
|
|
139
175
|
|
|
140
|
-
|
|
176
|
+
## 7. 配置
|
|
141
177
|
|
|
142
|
-
|
|
178
|
+
- 配置文件:`~/.ctxdb/ctxdb.json`,分 `agents.<name>` 段(`qoder` / `codex` / `claude` / `default`),互不复用
|
|
179
|
+
- 配置命令:`ctxdb setup --agent <name> --api-key=<key> --base-url=<url> [--user-id=<id>]`
|
|
180
|
+
- 带 `--agent` 时还会装 hooks + skill;不带 `--agent` 时只写 `agents.default` 段(仅 CLI 用)
|
|
181
|
+
- 连接检查:`ctxdb ping --agent=<name>`
|
|
182
|
+
- 状态查看:`ctxdb status --agent=<name>`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aliyunrds/ctxdb",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8-beta.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Unified access layer for RDS ContextDatabase: `ctxdb` CLI (memory + KB ops), one-shot `setup --agent <qoder|codex|claude>` installer, per-agent config, hooks, and SKILL.md.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
],
|
|
26
26
|
"author": "kuahai",
|
|
27
27
|
"engines": {
|
|
28
|
-
"node": ">=
|
|
28
|
+
"node": ">=20"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@aliyunrds/ctxdb-shared": "~0.0.3"
|
|
@@ -39,7 +39,6 @@
|
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"build": "tsup && mkdir -p dist/setup && cp -r src/setup/skills dist/setup/ && chmod +x dist/hooks/*.js dist/cli/main.js",
|
|
42
|
-
"postinstall": "chmod +x dist/hooks/*.js dist/cli/main.js 2>/dev/null || true",
|
|
43
42
|
"test": "vitest run"
|
|
44
43
|
}
|
|
45
44
|
}
|