@aliyunrds/ctxdb 0.0.5 → 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/README.md +3 -3
- package/dist/{chunk-HAUTENYD.js → chunk-6S5RJYBC.js} +20 -77
- package/dist/{chunk-7NOXAU2X.js → chunk-C62I23HL.js} +41 -3
- package/dist/chunk-KEQMJ6IO.js +69 -0
- package/dist/{chunk-QSSNPN3M.js → chunk-U3T5O6NX.js} +57 -14
- package/dist/cli/main.js +428 -172
- package/dist/hooks/session-start.js +71 -32
- package/dist/hooks/stop.js +94 -21
- package/dist/hooks/user-prompt-submit.js +50 -45
- package/dist/setup/skills/contextdb-knowledge/SKILL.md +189 -0
- package/dist/setup/skills/contextdb-memory/SKILL.md +182 -0
- package/package.json +3 -4
- package/dist/setup/skills/cli-only/SKILL.md +0 -116
- package/dist/setup/skills/hooks-driven/SKILL.md +0 -144
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @aliyunrds/ctxdb
|
|
2
2
|
|
|
3
|
-
Unified access layer for RDS ContextDatabase. One `ctxdb` CLI (memory + KB ops), one `setup
|
|
3
|
+
Unified access layer for RDS ContextDatabase. One `ctxdb` CLI (memory + KB ops), one `setup` installer (`--agent <…>` for hooks/skills, bare for CLI-only), per-agent SKILL.md — supports multiple code agents from a single package.
|
|
4
4
|
|
|
5
5
|
| Agent | Hooks | Skill install path | Skill style |
|
|
6
6
|
|---|---|---|---|
|
|
@@ -59,7 +59,7 @@ Implementation note: qoder/Claude consume the JSON `hookSpecificOutput.additiona
|
|
|
59
59
|
|
|
60
60
|
See `ctxdb --help`.
|
|
61
61
|
|
|
62
|
-
`setup`
|
|
62
|
+
`setup` without `--agent` writes `agents.default` in `~/.ctxdb/ctxdb.json` (CLI-only, no hooks or skills); with `--agent <qoder|codex|claude>` it writes agent config and installs hooks + skills for that harness. Memory and KB commands also accept `--agent <name>` so agents use their own config section; when omitted, `CTXDB_AGENT` env wins, otherwise `agents.default` is used. `uninstall` without `--agent` loops every supported agent; with `--agent <name>` it targets just that one. `teardown` is an alias for `uninstall`.
|
|
63
63
|
|
|
64
64
|
`memory add … --no-infer` stores the text verbatim (skips server-side LLM
|
|
65
65
|
fact-extraction). Use it when the user explicitly asks for a verbatim
|
|
@@ -100,7 +100,7 @@ Lives at `~/.ctxdb/ctxdb.json` (co-located with logs at `~/.ctxdb/logs/`). Schem
|
|
|
100
100
|
}
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
-
Agent selection is driven by `--agent <qoder|codex|claude>` on every CLI invocation, falling back to `CTXDB_AGENT` env, then to `
|
|
103
|
+
Agent selection is driven by `--agent <qoder|codex|claude|default>` on every CLI invocation, falling back to `CTXDB_AGENT` env, then to `default`. There is no `default_agent` field — older installs that have one written get it dropped on the next save.
|
|
104
104
|
|
|
105
105
|
Field reference:
|
|
106
106
|
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
NotFoundError
|
|
4
|
-
} from "./chunk-QSSNPN3M.js";
|
|
5
2
|
|
|
6
3
|
// src/lib/kb.ts
|
|
7
4
|
import { readFileSync, existsSync, statSync } from "fs";
|
|
@@ -12,16 +9,6 @@ var KB_COLLECTION = "/v1/knowledge/knowledge_bases";
|
|
|
12
9
|
var DOCUMENTS = "/v1/knowledge/documents";
|
|
13
10
|
var FILES = "/v1/knowledge/files";
|
|
14
11
|
var DOCUMENT_DETAIL = "/v1/knowledge/documents/detail";
|
|
15
|
-
async function tryNewThenLegacy(newPath, newCall, legacyCall) {
|
|
16
|
-
try {
|
|
17
|
-
return await newCall();
|
|
18
|
-
} catch (err) {
|
|
19
|
-
if (err instanceof NotFoundError && err.path === newPath) {
|
|
20
|
-
return legacyCall();
|
|
21
|
-
}
|
|
22
|
-
throw err;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
12
|
var DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
26
13
|
var DEFAULT_INGEST_TIMEOUT_MS = 3e4;
|
|
27
14
|
var DEFAULT_FILE_INGEST_TIMEOUT_MS = 6e4;
|
|
@@ -48,40 +35,20 @@ async function listKnowledgeBases(client) {
|
|
|
48
35
|
}
|
|
49
36
|
return [];
|
|
50
37
|
}
|
|
51
|
-
async function findKb(client, kbNameOrId) {
|
|
52
|
-
for (const kb of await listKnowledgeBases(client)) {
|
|
53
|
-
if (!kb || typeof kb !== "object") continue;
|
|
54
|
-
if (kb.name === kbNameOrId || kb.id === kbNameOrId) return kb;
|
|
55
|
-
}
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
38
|
async function createKb(client, kbName, description = "") {
|
|
59
39
|
return client.postJson(KB_COLLECTION, { name: kbName, description: description || "" });
|
|
60
40
|
}
|
|
61
|
-
async function
|
|
62
|
-
const existing = await findKb(client, kbName);
|
|
63
|
-
if (existing) return { kb: existing, created: false };
|
|
64
|
-
const created = await createKb(client, kbName, description);
|
|
65
|
-
return { kb: created, created: true };
|
|
66
|
-
}
|
|
67
|
-
async function uploadText(client, kbId, docName, text, mimeType = "text/plain", filePath) {
|
|
41
|
+
async function uploadText(client, kbName, docName, text, mimeType = "text/plain", filePath) {
|
|
68
42
|
const body = {
|
|
69
|
-
|
|
43
|
+
knowledge_base_name: kbName,
|
|
70
44
|
name: docName,
|
|
71
45
|
text,
|
|
72
46
|
mime_type: mimeType
|
|
73
47
|
};
|
|
74
48
|
if (filePath !== void 0 && filePath !== "") body.file_path = filePath;
|
|
75
|
-
return
|
|
76
|
-
DOCUMENTS,
|
|
77
|
-
() => client.postJson(DOCUMENTS, body),
|
|
78
|
-
() => {
|
|
79
|
-
const { knowledge_base_id: _drop, ...legacyBody } = body;
|
|
80
|
-
return client.postJson(`${KB_COLLECTION}/${encodeURIComponent(kbId)}/documents`, legacyBody);
|
|
81
|
-
}
|
|
82
|
-
);
|
|
49
|
+
return client.postJson(DOCUMENTS, body);
|
|
83
50
|
}
|
|
84
|
-
async function uploadFile(client,
|
|
51
|
+
async function uploadFile(client, kbName, localPath, options = {}) {
|
|
85
52
|
const MAX_UPLOAD_BYTES = 100 * 1024 * 1024;
|
|
86
53
|
const expanded = expandHome(localPath);
|
|
87
54
|
if (!existsSync(expanded)) throw new Error(`file not found: ${expanded}`);
|
|
@@ -97,30 +64,17 @@ async function uploadFile(client, kbId, localPath, options = {}) {
|
|
|
97
64
|
const content = readFileSync(expanded);
|
|
98
65
|
const mime = guessMime(expanded);
|
|
99
66
|
const fields = {
|
|
100
|
-
|
|
67
|
+
knowledge_base_name: kbName,
|
|
101
68
|
name: docName
|
|
102
69
|
};
|
|
103
70
|
if (options.filePath !== void 0 && options.filePath !== "") {
|
|
104
71
|
fields.file_path = options.filePath;
|
|
105
72
|
}
|
|
106
|
-
|
|
107
|
-
return tryNewThenLegacy(
|
|
73
|
+
return client.postMultipart(
|
|
108
74
|
FILES,
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
{ file: { filename, content, mimeType: mime } },
|
|
113
|
-
uploadOpts
|
|
114
|
-
),
|
|
115
|
-
() => {
|
|
116
|
-
const { knowledge_base_id: _drop, ...legacyFields } = fields;
|
|
117
|
-
return client.postMultipart(
|
|
118
|
-
`${KB_COLLECTION}/${encodeURIComponent(kbId)}/files`,
|
|
119
|
-
legacyFields,
|
|
120
|
-
{ file: { filename, content, mimeType: mime } },
|
|
121
|
-
uploadOpts
|
|
122
|
-
);
|
|
123
|
-
}
|
|
75
|
+
fields,
|
|
76
|
+
{ file: { filename, content, mimeType: mime } },
|
|
77
|
+
{ timeoutMs: options.timeoutMs }
|
|
124
78
|
);
|
|
125
79
|
}
|
|
126
80
|
function guessMime(path) {
|
|
@@ -128,24 +82,14 @@ function guessMime(path) {
|
|
|
128
82
|
if (ext in MIME_BY_EXT) return MIME_BY_EXT[ext];
|
|
129
83
|
return "application/octet-stream";
|
|
130
84
|
}
|
|
131
|
-
async function getDocument(client,
|
|
132
|
-
return
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
document_id: docId
|
|
137
|
-
}),
|
|
138
|
-
() => client.get(
|
|
139
|
-
`${KB_COLLECTION}/${encodeURIComponent(kbId)}/documents/${encodeURIComponent(docId)}`
|
|
140
|
-
)
|
|
141
|
-
);
|
|
85
|
+
async function getDocument(client, kbName, docId) {
|
|
86
|
+
return client.get(DOCUMENT_DETAIL, {
|
|
87
|
+
knowledge_base_name: kbName,
|
|
88
|
+
document_id: docId
|
|
89
|
+
});
|
|
142
90
|
}
|
|
143
|
-
async function listDocuments(client,
|
|
144
|
-
const resp = await
|
|
145
|
-
DOCUMENTS,
|
|
146
|
-
() => client.get(DOCUMENTS, { knowledge_base_id: kbId }),
|
|
147
|
-
() => client.get(`${KB_COLLECTION}/${encodeURIComponent(kbId)}/documents`)
|
|
148
|
-
);
|
|
91
|
+
async function listDocuments(client, kbName) {
|
|
92
|
+
const resp = await client.get(DOCUMENTS, { knowledge_base_name: kbName });
|
|
149
93
|
if (Array.isArray(resp)) return resp;
|
|
150
94
|
if (resp && typeof resp === "object") {
|
|
151
95
|
const o = resp;
|
|
@@ -153,13 +97,13 @@ async function listDocuments(client, kbId) {
|
|
|
153
97
|
}
|
|
154
98
|
return [];
|
|
155
99
|
}
|
|
156
|
-
async function pollIngest(client,
|
|
100
|
+
async function pollIngest(client, kbName, docId, options = {}) {
|
|
157
101
|
const timeoutMs = options.timeoutMs ?? DEFAULT_INGEST_TIMEOUT_MS;
|
|
158
102
|
const intervalMs = options.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
159
103
|
const sleep = options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
160
104
|
const now = options.now ?? (() => performance.now());
|
|
161
105
|
const deadline = now() + timeoutMs;
|
|
162
|
-
let doc = await getDocument(client,
|
|
106
|
+
let doc = await getDocument(client, kbName, docId);
|
|
163
107
|
let timedOut = false;
|
|
164
108
|
while (ingestInFlight(doc)) {
|
|
165
109
|
if (now() >= deadline) {
|
|
@@ -168,7 +112,7 @@ async function pollIngest(client, kbId, docId, options = {}) {
|
|
|
168
112
|
}
|
|
169
113
|
await sleep(intervalMs);
|
|
170
114
|
try {
|
|
171
|
-
doc = await getDocument(client,
|
|
115
|
+
doc = await getDocument(client, kbName, docId);
|
|
172
116
|
} catch {
|
|
173
117
|
break;
|
|
174
118
|
}
|
|
@@ -241,8 +185,7 @@ export {
|
|
|
241
185
|
DEFAULT_INGEST_TIMEOUT_MS,
|
|
242
186
|
DEFAULT_FILE_INGEST_TIMEOUT_MS,
|
|
243
187
|
listKnowledgeBases,
|
|
244
|
-
|
|
245
|
-
findOrCreateKb,
|
|
188
|
+
createKb,
|
|
246
189
|
uploadText,
|
|
247
190
|
uploadFile,
|
|
248
191
|
getDocument,
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
listKnowledgeBases
|
|
4
|
+
} from "./chunk-6S5RJYBC.js";
|
|
5
|
+
import {
|
|
6
|
+
isConnectionError,
|
|
7
|
+
resetCircuit,
|
|
8
|
+
tripCircuit
|
|
9
|
+
} from "./chunk-KEQMJ6IO.js";
|
|
2
10
|
import {
|
|
3
11
|
CtxdbError
|
|
4
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-U3T5O6NX.js";
|
|
5
13
|
|
|
6
14
|
// src/lib/recall-orchestrator.ts
|
|
7
15
|
import {
|
|
@@ -83,7 +91,7 @@ var EMPTY = {
|
|
|
83
91
|
memoryCount: 0,
|
|
84
92
|
knowledgeChunkCount: 0
|
|
85
93
|
};
|
|
86
|
-
async function recallTurn(prompt, cfg, client) {
|
|
94
|
+
async function recallTurn(prompt, cfg, client, agent = "default") {
|
|
87
95
|
if (!prompt.trim()) return { ...EMPTY, reason: "empty_prompt" };
|
|
88
96
|
if (!cfg.apiKey || !cfg.baseUrl) {
|
|
89
97
|
return { ...EMPTY, reason: "config_incomplete" };
|
|
@@ -101,8 +109,10 @@ async function recallTurn(prompt, cfg, client) {
|
|
|
101
109
|
let resp;
|
|
102
110
|
try {
|
|
103
111
|
resp = await client.postJson("/v3/memories/search/", body);
|
|
112
|
+
resetCircuit(agent);
|
|
104
113
|
} catch (err) {
|
|
105
114
|
const msg = err instanceof CtxdbError ? err.message : String(err);
|
|
115
|
+
if (isConnectionError(err)) tripCircuit(agent, cfg.baseUrl, msg);
|
|
106
116
|
return { ...EMPTY, reason: `http_error: ${msg}` };
|
|
107
117
|
}
|
|
108
118
|
if (!resp || typeof resp !== "object") {
|
|
@@ -131,6 +141,34 @@ async function recallTurn(prompt, cfg, client) {
|
|
|
131
141
|
};
|
|
132
142
|
}
|
|
133
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
|
+
|
|
134
171
|
export {
|
|
135
|
-
recallTurn
|
|
172
|
+
recallTurn,
|
|
173
|
+
fetchKbCatalogBlock
|
|
136
174
|
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
configDir
|
|
4
|
+
} from "./chunk-U3T5O6NX.js";
|
|
5
|
+
|
|
6
|
+
// src/lib/circuit.ts
|
|
7
|
+
import { statSync, writeFileSync, unlinkSync, mkdirSync, readdirSync, readFileSync } from "fs";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
var CIRCUIT_DIR_NAME = "circuit";
|
|
10
|
+
var COOLDOWN_MS = 6e4;
|
|
11
|
+
function circuitDir() {
|
|
12
|
+
const dir = join(configDir(), CIRCUIT_DIR_NAME);
|
|
13
|
+
try {
|
|
14
|
+
mkdirSync(dir, { recursive: true });
|
|
15
|
+
} catch {
|
|
16
|
+
}
|
|
17
|
+
return dir;
|
|
18
|
+
}
|
|
19
|
+
function circuitPath(agent) {
|
|
20
|
+
return join(circuitDir(), `${agent}.circuit`);
|
|
21
|
+
}
|
|
22
|
+
function isCircuitOpen(agent, baseUrl) {
|
|
23
|
+
const own = checkFile(circuitPath(agent), baseUrl);
|
|
24
|
+
if (own) return true;
|
|
25
|
+
try {
|
|
26
|
+
for (const f of readdirSync(circuitDir())) {
|
|
27
|
+
if (!f.endsWith(".circuit") || f === `${agent}.circuit`) continue;
|
|
28
|
+
if (checkFile(join(circuitDir(), f), baseUrl)) return true;
|
|
29
|
+
}
|
|
30
|
+
} catch {
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
function checkFile(path, baseUrl) {
|
|
35
|
+
try {
|
|
36
|
+
const st = statSync(path);
|
|
37
|
+
if (Date.now() - st.mtimeMs > COOLDOWN_MS) return false;
|
|
38
|
+
const line = readFileSync(path, "utf-8").split("\n")[0] || "";
|
|
39
|
+
return line.startsWith(baseUrl);
|
|
40
|
+
} catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function tripCircuit(agent, baseUrl, reason) {
|
|
45
|
+
try {
|
|
46
|
+
writeFileSync(circuitPath(agent), `${baseUrl}|${reason}|${(/* @__PURE__ */ new Date()).toISOString()}
|
|
47
|
+
`);
|
|
48
|
+
} catch {
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function resetCircuit(agent) {
|
|
52
|
+
try {
|
|
53
|
+
unlinkSync(circuitPath(agent));
|
|
54
|
+
} catch {
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function isConnectionError(err) {
|
|
58
|
+
if (!err || typeof err !== "object") return false;
|
|
59
|
+
if (err.name === "CtxdbHttpError") return false;
|
|
60
|
+
const msg = err.message ?? "";
|
|
61
|
+
return /timeout|ECONNREFUSED|ETIMEDOUT|EHOSTUNREACH|ENETUNREACH|network error/i.test(msg);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export {
|
|
65
|
+
isCircuitOpen,
|
|
66
|
+
tripCircuit,
|
|
67
|
+
resetCircuit,
|
|
68
|
+
isConnectionError
|
|
69
|
+
};
|
|
@@ -16,7 +16,14 @@ function isDebug() {
|
|
|
16
16
|
}
|
|
17
17
|
function debug(tag, msg, data) {
|
|
18
18
|
if (_enabled !== true) return;
|
|
19
|
-
const
|
|
19
|
+
const now = /* @__PURE__ */ new Date();
|
|
20
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
21
|
+
const ms = String(now.getMilliseconds()).padStart(3, "0");
|
|
22
|
+
const tz = -now.getTimezoneOffset();
|
|
23
|
+
const tzSign = tz >= 0 ? "+" : "-";
|
|
24
|
+
const tzH = pad(Math.floor(Math.abs(tz) / 60));
|
|
25
|
+
const tzM = pad(Math.abs(tz) % 60);
|
|
26
|
+
const ts = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}T${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}.${ms}${tzSign}${tzH}:${tzM}`;
|
|
20
27
|
let line = `${ts} [${tag}] ${msg}`;
|
|
21
28
|
if (data !== void 0) {
|
|
22
29
|
const s = typeof data === "string" ? data : JSON.stringify(data, null, 2);
|
|
@@ -251,9 +258,13 @@ function extractErrorDetail(text, fallback) {
|
|
|
251
258
|
import { homedir as homedir2 } from "os";
|
|
252
259
|
import { join as join3 } from "path";
|
|
253
260
|
var SUPPORTED_AGENTS = ["qoder", "codex", "claude"];
|
|
254
|
-
function
|
|
261
|
+
function isBuiltinAgent(v) {
|
|
255
262
|
return typeof v === "string" && SUPPORTED_AGENTS.includes(v);
|
|
256
263
|
}
|
|
264
|
+
function isAgentSlug(v) {
|
|
265
|
+
return v === "default" || isBuiltinAgent(v);
|
|
266
|
+
}
|
|
267
|
+
var isAgent = isAgentSlug;
|
|
257
268
|
function agentHomeDir(agent) {
|
|
258
269
|
switch (agent) {
|
|
259
270
|
case "qoder":
|
|
@@ -265,17 +276,17 @@ function agentHomeDir(agent) {
|
|
|
265
276
|
}
|
|
266
277
|
}
|
|
267
278
|
function agentFromEnv(env = process.env) {
|
|
268
|
-
return
|
|
279
|
+
return isAgentSlug(env.CTXDB_AGENT) ? env.CTXDB_AGENT : "default";
|
|
269
280
|
}
|
|
270
281
|
function agentFromArgvWithFallback(argv = process.argv.slice(2), env = process.env) {
|
|
271
282
|
for (let i = 0; i < argv.length; i++) {
|
|
272
283
|
const tok = argv[i];
|
|
273
|
-
if (tok === "--agent" &&
|
|
284
|
+
if (tok === "--agent" && isAgentSlug(argv[i + 1])) {
|
|
274
285
|
return { agent: argv[i + 1], fellBack: false };
|
|
275
286
|
}
|
|
276
287
|
if (tok.startsWith("--agent=")) {
|
|
277
288
|
const raw = tok.slice("--agent=".length);
|
|
278
|
-
if (
|
|
289
|
+
if (isAgentSlug(raw)) return { agent: raw, fellBack: false };
|
|
279
290
|
}
|
|
280
291
|
}
|
|
281
292
|
return { agent: agentFromEnv(env), fellBack: true };
|
|
@@ -289,16 +300,20 @@ function defaultConfigPath() {
|
|
|
289
300
|
return join4(homedir3(), ".ctxdb", "ctxdb.json");
|
|
290
301
|
}
|
|
291
302
|
var DEFAULT_CONFIG_PATH = join4(homedir3(), ".ctxdb", "ctxdb.json");
|
|
303
|
+
function configDir() {
|
|
304
|
+
return join4(homedir3(), ".ctxdb");
|
|
305
|
+
}
|
|
292
306
|
var DEFAULT_BASE_URL = "https://context-database.aliyuncs.com";
|
|
293
307
|
var DEFAULT_USER_ID = "default";
|
|
294
308
|
var DEFAULT_TOP_K = 5;
|
|
295
309
|
var DEFAULT_THRESHOLD = 0.4;
|
|
296
310
|
var DEFAULT_KNOWLEDGE_TOP_K = 6;
|
|
311
|
+
var DEFAULT_KB_CATALOG_INJECTION = "session_start";
|
|
297
312
|
function isComplete(cfg) {
|
|
298
313
|
return Boolean(cfg.apiKey && cfg.baseUrl);
|
|
299
314
|
}
|
|
300
|
-
function resolveConfigAgent(options = {}) {
|
|
301
|
-
if (
|
|
315
|
+
function resolveConfigAgent(options = {}, preloadedRaw) {
|
|
316
|
+
if (isAgentSlug(options.agent)) return options.agent;
|
|
302
317
|
return agentFromEnv(options.env);
|
|
303
318
|
}
|
|
304
319
|
function coerceInt(v, fallback) {
|
|
@@ -316,6 +331,12 @@ function coerceBool(v, fallback) {
|
|
|
316
331
|
if (v === void 0 || v === null) return fallback;
|
|
317
332
|
return Boolean(v);
|
|
318
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
|
+
}
|
|
319
340
|
function readRaw(path) {
|
|
320
341
|
if (!existsSync(path)) return {};
|
|
321
342
|
try {
|
|
@@ -353,7 +374,8 @@ function configFromDisk(raw) {
|
|
|
353
374
|
topK: coerceInt(raw.top_k, DEFAULT_TOP_K),
|
|
354
375
|
threshold: coerceFloat(raw.threshold, DEFAULT_THRESHOLD),
|
|
355
376
|
knowledgeTopK: coerceInt(raw.knowledge_top_k, DEFAULT_KNOWLEDGE_TOP_K),
|
|
356
|
-
debug: coerceBool(raw.debug, false)
|
|
377
|
+
debug: coerceBool(raw.debug, false),
|
|
378
|
+
kbCatalogInjection: coerceKbCatalogInjection(raw.kb_catalog_injection)
|
|
357
379
|
};
|
|
358
380
|
}
|
|
359
381
|
function applyEnv(cfg, env) {
|
|
@@ -365,8 +387,8 @@ function applyEnv(cfg, env) {
|
|
|
365
387
|
function load(options = {}) {
|
|
366
388
|
const path = options.path ?? defaultConfigPath();
|
|
367
389
|
const env = options.env ?? process.env;
|
|
368
|
-
const agent = resolveConfigAgent({ agent: options.agent, env });
|
|
369
390
|
const raw = readRaw(path);
|
|
391
|
+
const agent = resolveConfigAgent({ ...options, path, env }, raw);
|
|
370
392
|
if (!isV2Schema(raw)) {
|
|
371
393
|
try {
|
|
372
394
|
process.stderr.write(
|
|
@@ -377,7 +399,18 @@ function load(options = {}) {
|
|
|
377
399
|
}
|
|
378
400
|
return applyEnv(configFromDisk({}), env);
|
|
379
401
|
}
|
|
380
|
-
|
|
402
|
+
const agentRaw = agentRawFromFile(raw, agent);
|
|
403
|
+
if (agent !== "default") {
|
|
404
|
+
const defaultRaw = agentRawFromFile(raw, "default");
|
|
405
|
+
if (Object.keys(defaultRaw).length > 0) {
|
|
406
|
+
for (const [k, v] of Object.entries(defaultRaw)) {
|
|
407
|
+
if (!(k in agentRaw)) {
|
|
408
|
+
agentRaw[k] = v;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return applyEnv(configFromDisk(agentRaw), env);
|
|
381
414
|
}
|
|
382
415
|
function configToDisk(cfg) {
|
|
383
416
|
return {
|
|
@@ -391,7 +424,8 @@ function configToDisk(cfg) {
|
|
|
391
424
|
top_k: cfg.topK,
|
|
392
425
|
threshold: cfg.threshold,
|
|
393
426
|
knowledge_top_k: cfg.knowledgeTopK,
|
|
394
|
-
debug: cfg.debug
|
|
427
|
+
debug: cfg.debug,
|
|
428
|
+
kb_catalog_injection: cfg.kbCatalogInjection
|
|
395
429
|
};
|
|
396
430
|
}
|
|
397
431
|
function removeAgent(agent, path, options = {}) {
|
|
@@ -446,23 +480,32 @@ function configuredAgents(path) {
|
|
|
446
480
|
if (!isV2Schema(raw) || !raw.agents || typeof raw.agents !== "object") return [];
|
|
447
481
|
return Object.keys(raw.agents).filter(isAgent);
|
|
448
482
|
}
|
|
483
|
+
function writeInstalledPkgVersion(version, path) {
|
|
484
|
+
const target = path ?? defaultConfigPath();
|
|
485
|
+
const raw = readRaw(target);
|
|
486
|
+
const updated = { ...raw, installed_pkg_version: version };
|
|
487
|
+
mkdirSync2(dirname3(target), { recursive: true });
|
|
488
|
+
writeFileSync(target, JSON.stringify(updated, null, 2) + "\n", "utf-8");
|
|
489
|
+
}
|
|
449
490
|
|
|
450
491
|
export {
|
|
451
492
|
setDebug,
|
|
452
493
|
debug,
|
|
453
494
|
CtxdbError,
|
|
454
|
-
NotFoundError,
|
|
455
495
|
HttpClient,
|
|
456
496
|
SUPPORTED_AGENTS,
|
|
457
|
-
|
|
497
|
+
isBuiltinAgent,
|
|
498
|
+
isAgentSlug,
|
|
458
499
|
agentHomeDir,
|
|
459
500
|
agentFromEnv,
|
|
460
501
|
agentFromArgvWithFallback,
|
|
502
|
+
configDir,
|
|
461
503
|
DEFAULT_BASE_URL,
|
|
462
504
|
DEFAULT_USER_ID,
|
|
463
505
|
isComplete,
|
|
464
506
|
load,
|
|
465
507
|
removeAgent,
|
|
466
508
|
save,
|
|
467
|
-
configuredAgents
|
|
509
|
+
configuredAgents,
|
|
510
|
+
writeInstalledPkgVersion
|
|
468
511
|
};
|