@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.
@@ -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-Q2EEP4CE.js";
9
+ } from "./chunk-KEQMJ6IO.js";
7
10
  import {
8
11
  CtxdbError
9
- } from "./chunk-S45GOYUU.js";
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
  };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  configDir
4
- } from "./chunk-S45GOYUU.js";
4
+ } from "./chunk-U3T5O6NX.js";
5
5
 
6
6
  // src/lib/circuit.ts
7
7
  import { statSync, writeFileSync, unlinkSync, mkdirSync, readdirSync, readFileSync } from "fs";
@@ -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-S45GOYUU.js";
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
- const cmd = h.command;
628
- if (cmd.includes("@aliyunrds/ctxdb")) {
629
- const parts = cmd.split(" ");
630
- if (parts.length >= 2 && !parts[0].endsWith(".js") && !parts[0].endsWith(".ts")) {
631
- nodePaths.add(parts[0]);
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" && typeof h.command === "string" && h.command === commandWithAgent
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: commandWithAgent, timeout: 60 }],
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-LIC44DR6.js";
5
+ } from "../chunk-C62I23HL.js";
6
+ import "../chunk-6S5RJYBC.js";
5
7
  import {
6
8
  isCircuitOpen
7
- } from "../chunk-Q2EEP4CE.js";
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-S45GOYUU.js";
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
- if (!isComplete(cfg) || !cfg.warmupRecall) {
100
- debug("warmup", "skip (config incomplete or warmupRecall=false)");
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 result = await Promise.race([
113
- warmupRecall(cwd, cfg, client),
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 (!result.ok || !result.additionalContext) {
121
- debug("warmup", `no result: ${result.reason}`);
122
- return 0;
123
- }
124
- debug("warmup", `ok, memories=${result.memoryCount} kb=${result.knowledgeChunkCount}`);
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 (${result.memoryCount} memories, ${result.knowledgeChunkCount} kb chunks)
161
+ `ctxdb warmup: ok (${memoryCount} memories, ${kbChunkCount} kb chunks, kb_catalog=${kbCatalogLines} lines)
127
162
  `
128
163
  );
129
- if (agent === "codex") {
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
- main().then((code) => process.exit(code));
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
+ };
@@ -4,7 +4,7 @@ import {
4
4
  isConnectionError,
5
5
  resetCircuit,
6
6
  tripCircuit
7
- } from "../chunk-Q2EEP4CE.js";
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-S45GOYUU.js";
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("/").pop();
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 bgParts = [];
309
- if (git) {
310
- bgParts.push(`This conversation took place in project "${git.project}", branch "${git.branch}".`);
311
- }
312
- if (skills.length > 0) {
313
- const descs = skills.map((s) => {
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
- listKnowledgeBases
4
- } from "../chunk-6S5RJYBC.js";
5
- import {
3
+ fetchKbCatalogBlock,
6
4
  recallTurn
7
- } from "../chunk-LIC44DR6.js";
5
+ } from "../chunk-C62I23HL.js";
6
+ import "../chunk-6S5RJYBC.js";
8
7
  import {
9
8
  isCircuitOpen
10
- } from "../chunk-Q2EEP4CE.js";
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-S45GOYUU.js";
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 [result, kbBlock] = await Promise.all([
73
- recallTurn(prompt, cfg, client, agent),
74
- fetchKbCatalogBlock(client, agent).catch(() => "")
75
- ]);
76
- if (!result.ok && !result.additionalContext && !kbBlock) {
77
- const reason = result.reason ?? "no_context";
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
- if (agent === "codex") {
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
- main().then((code) => process.exit(code));
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: RDS contextdb 知识库操作全量参考。覆盖 kb create / search / upload-text / upload-file / list / documents-list / document-get 的完整参数和 JSON 输出结构。当用户提到"查知识库"、"查 KB"、"上传到 KB"、"灌进知识库"、"列出知识库"、"看文档"、"建知识库"等场景时使用。
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
- - 配置文件:`~/.ctxdb/ctxdb.json`
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
- ## kb search
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>" [--kb=<name1,name2>] [--top-k=N] [--threshold=F] [--verbose] [--raw] [--agent=<name>]
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
- `--verbose` 模式增加字段:
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
- ## kb create
35
+ ### Recipe 2:列出所有 KB(不知道有哪些)
65
36
 
66
- 创建知识库。
37
+ **场景**:用户问"有哪些知识库"、"列一下 KB",或本身在写 recipe 1 之前需要确认 KB 名。
67
38
 
68
39
  ```sh
69
- ctxdb kb create <kb-name> [--description=<desc>] [--agent=<name>]
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
- 上传前须先创建 KB。`upload-text` / `upload-file` 不会自动创建 KB,KB 不存在时会报错。
45
+ **何时不适用**:hook 已注入 `<available-knowledge-bases>` 块时,直接读那个块更省一次调用(见 §4)。
78
46
 
79
- ## kb upload-file
47
+ ### Recipe 3:列出某 KB 的文档清单
80
48
 
81
- 上传本地文件到知识库。
49
+ **场景**:用户问"X KB 收了哪些文档"、"X KB 里有几篇"。
82
50
 
83
51
  ```sh
84
- ctxdb kb upload-file <kb-name> <local-path> [--doc-name=<name>] [--file-path=<server-logical-path>] [--no-wait] [--agent=<name>]
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
- 支持格式:PDF、DOCX、MD、TXT。
57
+ **何时不适用**:用户要的是文档**正文**而不是清单 → 走 recipe 1 用 `kb search`(见 §5 注意事项 1)。
96
58
 
97
- ## kb upload-text
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>" [--file-path=<server-logical-path>] [--no-wait] [--agent=<name>]
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
- ## kb list
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 list [--agent=<name>]
76
+ ctxdb kb upload-file <kb-name> <local-path> --agent=qoder
119
77
  ```
120
78
 
121
- 返回 `knowledge_bases` 数组,每项含知识库名称、ID、文档数等。
79
+ 支持格式:PDF / DOCX / MD / TXT。默认等待 chunking 完成;想立即返回加 `--no-wait`(见 §6)。
122
80
 
123
- ## kb documents-list
81
+ **何时不适用**:要上传的内容是字符串而非本地文件 recipe 4。
124
82
 
125
- 列出知识库中的文档。
83
+ ## 3. 命令速查
126
84
 
127
- ```sh
128
- ctxdb kb documents-list <kb-name> [--agent=<name>]
129
- ```
85
+ `--agent=<name>` 必填;详细解析链见 §6。其他 advanced flag 见 §6。
130
86
 
131
- ## kb document-get
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
- ```sh
136
- ctxdb kb document-get <kb-name> <doc-id> [--agent=<name>]
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
- `kb create`、`kb upload-text`、`kb upload-file` 是副作用操作(创建 KB / 写入文档)。执行前须明确目标 KB 名称:
111
+ ### 注意事项 1:`kb document-get` 只返 metadata,**不返正文**
142
112
 
143
- - 用户指定了 KB 名称 直接使用
144
- - 用户未指定 先用 `kb list` 查看现有知识库,向用户确认目标 KB 后再操作
145
- - KB 不存在时 upload 命令会自动创建,需确认用户意图是新建还是写入已有 KB
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
- ## --agent 参数
117
+ ### 注意事项 2:解析 JSON 用 `jq`,**不要 inline `python3 -c`**
148
118
 
149
- 所有 kb 命令支持 `--agent <name>` 指定操作目标。知识库归属于 workspace,不同 agent 若共享同一 workspace 则访问相同的知识库。
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
- 不带 `--agent` 时的解析顺序:`CTXDB_AGENT` 环境变量 `agents.default` 配置段。
123
+ ### 注意事项 3:`upload-*` / `create` 是副作用,没明示 KB 名前必须先确认
152
124
 
153
- ## Hooks 感知
125
+ - **现象**:用户只说"把这段记进知识库"没指定 KB,agent 直接用了某个猜的 KB 名 → 写到错的桶
126
+ - **根因**:写命令不会跟 agent 二次确认;KB 不存在时报错,但**KB 名拼错却恰好命中另一个真实 KB**时会静默写错
127
+ - **正确做法**:用户没明示 → 必须先 `kb list` 拿候选清单,跟用户确认目标后才执行
154
128
 
155
- **KB 目录注入**:如果对话中出现 `<available-knowledge-bases>` 块,表示 hooks 已启用,该块列出可用知识库名称,可直接用于 `--kb=<name>` 参数。
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
- **B-3c 守卫**:包含 `kb upload-text` 或 `kb upload-file` 操作的对话轮次,其内容不会被 autoCapture 写入记忆(防止文档原文混入记忆)。
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
- 如果对话中没有 `<available-knowledge-bases>`,表示当前为纯 CLI 模式,用 `kb list` 查看可用知识库。
183
+ ## 7. 配置
160
184
 
161
- 所有命令输出 JSON stdout,错误输出到 stderr 并返回非零退出码。
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: RDS contextdb 记忆操作全量参考。覆盖 memory add / search / list / get / update / delete 的完整参数、JSON 输出结构和 hooks 感知机制。当用户提到"记住"、"记一下"、"查记忆"、"删记忆"、"改记忆"、"之前说过"等场景时使用。
3
+ description: contextdb 长期记忆高频操作(主动记忆 / 搜索过往 / 列出审计 / 改删某条)和命令速查。当用户提到"记住 / 记一下 / 记一笔 / 写进记忆 / 查记忆 / 找记忆 / 列记忆 / 删记忆 / 改记忆 / 之前说过 / 上次提到"等场景时使用。配套 contextdb-knowledge 处理知识库。
4
4
  ---
5
5
 
6
- # contextdb Memory 操作参考
6
+ # contextdb Memory 操作
7
7
 
8
- ## 配置
8
+ ## 1. 什么时候用这个 skill
9
9
 
10
- - 配置文件:`~/.ctxdb/ctxdb.json`
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
- ## memory add
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
- ```sh
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
- ## memory search
23
+ ### Recipe 1:主动记忆("记住 X")
51
24
 
52
- 搜索长期记忆。
25
+ **场景**:用户明确说"记住"、"帮我记一下"、"把这条写进记忆"。
53
26
 
54
27
  ```sh
55
- ctxdb memory search "<query>" [--top-k=N] [--threshold=F] [--knowledge] [--verbose] [--raw] [--agent=<name>]
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
- `results` 为空数组表示未找到匹配记忆。
33
+ **何时不适用**:用户没明示要记 → 不要主动调;autoCapture 已在每轮 Stop hook 自动跑(见 §4)。
83
34
 
84
- `--knowledge` 时额外返回 `knowledge` 数组(每项含 `content` / `score`,`--verbose` 增加 `doc_name` / `kb_id` / `doc_id` / `tags`)。
35
+ **`event: NONE` 情况**:返回 `results: []` 时表示 LLM 觉得没新事实可提,是**正常返回不是错误**(见 §5 注意事项 1)。
85
36
 
86
- ## memory list
37
+ ### Recipe 2:找过去说过的(搜记忆)
87
38
 
88
- 列出记忆。
39
+ **场景**:用户问"之前我们讨论过 X 吗"、"上次怎么说的"、"我之前提过 Y 没"。
89
40
 
90
41
  ```sh
91
- ctxdb memory list [--page-size=N] [--category=<cat>] [--agent=<name>]
42
+ ctxdb memory search "<query>" --agent=qoder
92
43
  ```
93
44
 
94
- | 参数 | 说明 | 默认值 |
95
- |------|------|--------|
96
- | `--page-size` | 每页条数 | 100 |
97
- | `--category` | 按分类过滤 | |
45
+ 返回 `results` 数组,每项含 `id` / `memory` / `score`,按 score 倒序。top-5 默认。想同时搜知识库 chunks 加 `--knowledge`(见 §6)。
46
+
47
+ **何时不适用**:用户想看的是当前会话的原对话(不是提炼后的事实) transcript / 聊天记录而非这个命令;记忆桶里只有提炼版(见 §5 注意事项 2)。
98
48
 
99
- ## memory get
49
+ ### Recipe 3:浏览/审计记忆
100
50
 
101
- 查看单条记忆。
51
+ **场景**:排查"刚才那条记进去没"、用户想看"系统记了我什么"。
102
52
 
103
53
  ```sh
104
- ctxdb memory get <memory-id> [--agent=<name>]
54
+ ctxdb memory list --page-size=20 --agent=qoder
105
55
  ```
106
56
 
107
- ## memory update
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
- ctxdb memory update <memory-id> --text="<new-text>" [--agent=<name>]
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
- ## memory delete
73
+ `<memory-id>` 必须先用 recipe 2 或 recipe 3 拿到。
116
74
 
117
- 删除记忆。
75
+ **何时不适用**:用户说"清空所有记忆" → 走 `ctxdb memory delete --all`,**必须先跟用户二次确认**(见 §5 注意事项 3,不可逆操作)。
118
76
 
119
- ```sh
120
- ctxdb memory delete <memory-id> [--agent=<name>]
121
- ctxdb memory delete --all [--agent=<name>]
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
- `--all` 删除当前 user_id 下的所有记忆。
100
+ **纯 CLI 模式**:对话里没 `<recalled-memories>` 块 → 不在 hook 环境(裸 CLI),所有 recall / capture 只能主动 `ctxdb memory search` / `ctxdb memory add`。
125
101
 
126
- ## --agent 参数
102
+ ## 5. 注意事项
127
103
 
128
- 所有 memory 命令支持 `--agent <name>` 指定操作目标。不同 agent 的记忆存储在各自的配置桶中(由 `~/.ctxdb/ctxdb.json` `agents.<name>.user_id` 决定)。
104
+ ### 注意事项 1:`memory add` 返回 `event: NONE` 是正常的
129
105
 
130
- 不带 `--agent` 时的解析顺序:`CTXDB_AGENT` 环境变量 `agents.default` 配置段。
106
+ - **现象**:调 `memory add "<text>"` `results` 为空数组、`event: NONE`
107
+ - **根因**:服务端默认走 LLM fact-extraction,LLM 判断这段话**没新事实可提**(已存在 / 太啰嗦 / 不构成事实)
108
+ - **正确做法**:想强制原文存加 `--no-infer`;如果用户输入的就是"已知事实重复",告诉用户"系统认为这条不构成新事实"而不是判定写失败
131
109
 
132
- ## Hooks 感知
110
+ ### 注意事项 2:`memory search` 拿到的是**提炼后事实**,不是原对话
133
111
 
134
- **autoCapture**:如果对话中出现 `<recalled-memories>` 块,表示 Stop hook 在每轮对话结束后自动提取事实写入记忆。`memory add` 在此基础上用于用户主动强调的内容(加强记忆)。
112
+ - **现象**:用户问"上次我说过 X 吗",`memory search "X"` 返回的是 LLM 提炼出的事实,跟用户原话措辞不同;用户会觉得"这不是我说的"
113
+ - **根因**:autoCapture / `memory add` 默认走 fact-extraction,存的是提炼版
114
+ - **正确做法**:要拿用户原话 → 看 transcript / 聊天记录文件;记忆桶只能告诉你"用户曾说过类似 X 这个事实"
135
115
 
136
- **autoRecall**:`<recalled-memories>` 块内容是 UserPromptSubmit hook 自动搜索当前 prompt 相关记忆的结果。
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
- **B-3c 守卫**:包含 `kb upload-text` 或 `kb upload-file` 操作的对话轮次,其内容不会被 autoCapture 写入记忆(防止文档原文混入记忆)。
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
- 如果对话中没有 `<recalled-memories>`,表示当前为纯 CLI 模式,所有操作需主动调用。
176
+ ## 7. 配置
141
177
 
142
- 所有命令输出 JSON stdout,错误输出到 stderr 并返回非零退出码。
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.7",
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": ">=22"
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
  }