@aliyunrds/ctxdb 0.0.4 → 0.0.7
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 +17 -17
- package/dist/chunk-6S5RJYBC.js +198 -0
- package/dist/{chunk-7NOXAU2X.js → chunk-LIC44DR6.js} +9 -2
- package/dist/chunk-Q2EEP4CE.js +69 -0
- package/dist/{chunk-QSSNPN3M.js → chunk-S45GOYUU.js} +46 -12
- package/dist/cli/main.js +481 -448
- package/dist/hooks/session-start.js +10 -3
- package/dist/hooks/stop.js +100 -21
- package/dist/hooks/user-prompt-submit.js +42 -8
- package/dist/setup/skills/contextdb-knowledge/SKILL.md +161 -0
- package/dist/setup/skills/contextdb-memory/SKILL.md +142 -0
- package/package.json +2 -2
- 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
|
|---|---|---|---|
|
|
@@ -51,15 +51,15 @@ Implementation note: qoder/Claude consume the JSON `hookSpecificOutput.additiona
|
|
|
51
51
|
|
|
52
52
|
## CLI
|
|
53
53
|
|
|
54
|
-
`ctxdb` ships
|
|
54
|
+
`ctxdb` ships 18 subcommands:
|
|
55
55
|
|
|
56
|
-
- **Top-level**: `
|
|
56
|
+
- **Top-level**: `setup` / `status` / `ping` / `uninstall` / `upgrade`
|
|
57
57
|
- **Memory**: `memory add|search|list|get|update|delete`
|
|
58
58
|
- **KB**: `kb upload-text|upload-file|list|documents-list|document-get|search`
|
|
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
|
|
|
@@ -132,19 +132,19 @@ Two npm packages total. `@aliyunrds/ctxdb-shared` stays separate because OpenCla
|
|
|
132
132
|
|
|
133
133
|
## Uninstall
|
|
134
134
|
|
|
135
|
-
> ⚠️ **Order matters.** npm 7+ removed the `preuninstall` / `postuninstall` lifecycle hooks — `npm uninstall -g` does **not** run any cleanup we ship. If you `npm uninstall` first you'll leave agent hook entries pointing at a now-missing binary, plus orphaned skills and `~/.ctxdb/` state. Always run **`ctxdb
|
|
135
|
+
> ⚠️ **Order matters.** npm 7+ removed the `preuninstall` / `postuninstall` lifecycle hooks — `npm uninstall -g` does **not** run any cleanup we ship. If you `npm uninstall` first you'll leave agent hook entries pointing at a now-missing binary, plus orphaned skills and `~/.ctxdb/` state. Always run **`ctxdb uninstall` first, then `npm uninstall -g`**.
|
|
136
136
|
|
|
137
137
|
### Full removal (recommended)
|
|
138
138
|
|
|
139
139
|
```sh
|
|
140
140
|
# 1. Strip hooks + skills + (optionally) config + logs while the binary still works.
|
|
141
|
-
ctxdb
|
|
141
|
+
ctxdb uninstall --purge-all # everything: hooks, skills, ~/.ctxdb/ctxdb.json, ~/.ctxdb/logs/
|
|
142
142
|
|
|
143
143
|
# 2. Then drop the npm binaries.
|
|
144
144
|
npm uninstall -g @aliyunrds/ctxdb @aliyunrds/ctxdb-shared
|
|
145
145
|
```
|
|
146
146
|
|
|
147
|
-
After step 1, `ctxdb
|
|
147
|
+
After step 1, `ctxdb uninstall` prints the exact `npm uninstall` command to run next — copy it from your terminal.
|
|
148
148
|
|
|
149
149
|
### Partial removal
|
|
150
150
|
|
|
@@ -154,23 +154,23 @@ After step 1, `ctxdb teardown` prints the exact `npm uninstall` command to run n
|
|
|
154
154
|
# preserved verbatim. If <X> was the last configured agent, ~/.ctxdb/ctxdb.json
|
|
155
155
|
# itself is unlinked so a future `ctxdb setup --agent <X>` runs through the
|
|
156
156
|
# first-time install path cleanly.
|
|
157
|
-
ctxdb
|
|
158
|
-
ctxdb
|
|
159
|
-
ctxdb
|
|
157
|
+
ctxdb uninstall --agent qoder
|
|
158
|
+
ctxdb uninstall --agent codex
|
|
159
|
+
ctxdb uninstall --agent claude
|
|
160
160
|
|
|
161
161
|
# Wholesale across all agents, but keep config + logs for re-setup later:
|
|
162
|
-
ctxdb
|
|
163
|
-
ctxdb
|
|
164
|
-
ctxdb
|
|
162
|
+
ctxdb uninstall # all agents, keeps ~/.ctxdb/{ctxdb.json,logs/}
|
|
163
|
+
ctxdb uninstall --purge-config # also deletes ~/.ctxdb/ctxdb.json
|
|
164
|
+
ctxdb uninstall --purge-logs # also deletes ~/.ctxdb/logs/
|
|
165
165
|
```
|
|
166
166
|
|
|
167
|
-
### What
|
|
167
|
+
### What uninstall does (and doesn't) touch
|
|
168
168
|
|
|
169
169
|
- **Hook entries in `~/.qoder/settings.json`, `~/.codex/hooks.json`, and `~/.claude/settings.json`**: stripped precisely by marker (`_ctxdb = @aliyunrds/ctxdb`, plus legacy keys `_ctxdbQoder` / `_ctxdbPackage` and the legacy `@aliyunrds/ctxdb-qoder` value for installs predating the unified marker). Any hooks you added yourself stay. A timestamped `*.bak-ctxdb-<TS>` is written before each modification (rotation keeps the 5 most recent).
|
|
170
170
|
- **Skill directories**: `~/.<agent>/skills/ctxdb/` for each set-up agent, plus legacy dirs (`ctxdb-qoder`, `rds-ctxdb-qoder`, `qoder-ctxdb`) under `~/.qoder/skills/` from older package names.
|
|
171
171
|
- **`~/.ctxdb/` state**: only with `--purge-config` / `--purge-logs` / `--purge-all`. Defensive: `--purge-all` removes the `~/.ctxdb/` root only if it's empty after the named files are deleted (won't blanket-rm an unknown directory).
|
|
172
|
-
- **`~/.codex/config.toml` `[features].hooks`**: **NOT** reverted. Setup adds `hooks = true` so Codex will fire ctxdb's hook entries;
|
|
173
|
-
- **`npm` itself**: never. `ctxdb
|
|
172
|
+
- **`~/.codex/config.toml` `[features].hooks`**: **NOT** reverted. Setup adds `hooks = true` so Codex will fire ctxdb's hook entries; uninstall leaves the flag alone because (a) the user may have wanted it on for non-ctxdb hooks, and (b) it's harmless when `~/.codex/hooks.json` is empty. If you want it off, edit the file by hand.
|
|
173
|
+
- **`npm` itself**: never. `ctxdb uninstall` does not call your host package manager — that's the second command above.
|
|
174
174
|
|
|
175
175
|
## Remote SSH (Qoder IDE)
|
|
176
176
|
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/lib/kb.ts
|
|
4
|
+
import { readFileSync, existsSync, statSync } from "fs";
|
|
5
|
+
import { basename, extname } from "path";
|
|
6
|
+
import { homedir } from "os";
|
|
7
|
+
import { resolve } from "path";
|
|
8
|
+
var KB_COLLECTION = "/v1/knowledge/knowledge_bases";
|
|
9
|
+
var DOCUMENTS = "/v1/knowledge/documents";
|
|
10
|
+
var FILES = "/v1/knowledge/files";
|
|
11
|
+
var DOCUMENT_DETAIL = "/v1/knowledge/documents/detail";
|
|
12
|
+
var DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
13
|
+
var DEFAULT_INGEST_TIMEOUT_MS = 3e4;
|
|
14
|
+
var DEFAULT_FILE_INGEST_TIMEOUT_MS = 6e4;
|
|
15
|
+
var MIME_BY_EXT = {
|
|
16
|
+
".pdf": "application/pdf",
|
|
17
|
+
".txt": "text/plain",
|
|
18
|
+
".md": "text/markdown",
|
|
19
|
+
".markdown": "text/markdown",
|
|
20
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
21
|
+
".doc": "application/msword",
|
|
22
|
+
".html": "text/html",
|
|
23
|
+
".htm": "text/html",
|
|
24
|
+
".json": "application/json",
|
|
25
|
+
".csv": "text/csv",
|
|
26
|
+
".xml": "application/xml",
|
|
27
|
+
".rtf": "application/rtf"
|
|
28
|
+
};
|
|
29
|
+
async function listKnowledgeBases(client) {
|
|
30
|
+
const resp = await client.get(KB_COLLECTION);
|
|
31
|
+
if (Array.isArray(resp)) return resp;
|
|
32
|
+
if (resp && typeof resp === "object") {
|
|
33
|
+
const o = resp;
|
|
34
|
+
return o.knowledge_bases ?? o.results ?? [];
|
|
35
|
+
}
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
async function createKb(client, kbName, description = "") {
|
|
39
|
+
return client.postJson(KB_COLLECTION, { name: kbName, description: description || "" });
|
|
40
|
+
}
|
|
41
|
+
async function uploadText(client, kbName, docName, text, mimeType = "text/plain", filePath) {
|
|
42
|
+
const body = {
|
|
43
|
+
knowledge_base_name: kbName,
|
|
44
|
+
name: docName,
|
|
45
|
+
text,
|
|
46
|
+
mime_type: mimeType
|
|
47
|
+
};
|
|
48
|
+
if (filePath !== void 0 && filePath !== "") body.file_path = filePath;
|
|
49
|
+
return client.postJson(DOCUMENTS, body);
|
|
50
|
+
}
|
|
51
|
+
async function uploadFile(client, kbName, localPath, options = {}) {
|
|
52
|
+
const MAX_UPLOAD_BYTES = 100 * 1024 * 1024;
|
|
53
|
+
const expanded = expandHome(localPath);
|
|
54
|
+
if (!existsSync(expanded)) throw new Error(`file not found: ${expanded}`);
|
|
55
|
+
const stat = statSync(expanded);
|
|
56
|
+
if (!stat.isFile()) throw new Error(`not a file: ${expanded}`);
|
|
57
|
+
if (stat.size > MAX_UPLOAD_BYTES) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
`file too large (${(stat.size / 1024 / 1024).toFixed(1)} MB), maximum is 100 MB`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
const filename = basename(expanded);
|
|
63
|
+
const docName = options.docName ?? filename;
|
|
64
|
+
const content = readFileSync(expanded);
|
|
65
|
+
const mime = guessMime(expanded);
|
|
66
|
+
const fields = {
|
|
67
|
+
knowledge_base_name: kbName,
|
|
68
|
+
name: docName
|
|
69
|
+
};
|
|
70
|
+
if (options.filePath !== void 0 && options.filePath !== "") {
|
|
71
|
+
fields.file_path = options.filePath;
|
|
72
|
+
}
|
|
73
|
+
return client.postMultipart(
|
|
74
|
+
FILES,
|
|
75
|
+
fields,
|
|
76
|
+
{ file: { filename, content, mimeType: mime } },
|
|
77
|
+
{ timeoutMs: options.timeoutMs }
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
function guessMime(path) {
|
|
81
|
+
const ext = extname(path).toLowerCase();
|
|
82
|
+
if (ext in MIME_BY_EXT) return MIME_BY_EXT[ext];
|
|
83
|
+
return "application/octet-stream";
|
|
84
|
+
}
|
|
85
|
+
async function getDocument(client, kbName, docId) {
|
|
86
|
+
return client.get(DOCUMENT_DETAIL, {
|
|
87
|
+
knowledge_base_name: kbName,
|
|
88
|
+
document_id: docId
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
async function listDocuments(client, kbName) {
|
|
92
|
+
const resp = await client.get(DOCUMENTS, { knowledge_base_name: kbName });
|
|
93
|
+
if (Array.isArray(resp)) return resp;
|
|
94
|
+
if (resp && typeof resp === "object") {
|
|
95
|
+
const o = resp;
|
|
96
|
+
return o.documents ?? o.results ?? [];
|
|
97
|
+
}
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
async function pollIngest(client, kbName, docId, options = {}) {
|
|
101
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_INGEST_TIMEOUT_MS;
|
|
102
|
+
const intervalMs = options.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
103
|
+
const sleep = options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
104
|
+
const now = options.now ?? (() => performance.now());
|
|
105
|
+
const deadline = now() + timeoutMs;
|
|
106
|
+
let doc = await getDocument(client, kbName, docId);
|
|
107
|
+
let timedOut = false;
|
|
108
|
+
while (ingestInFlight(doc)) {
|
|
109
|
+
if (now() >= deadline) {
|
|
110
|
+
timedOut = true;
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
await sleep(intervalMs);
|
|
114
|
+
try {
|
|
115
|
+
doc = await getDocument(client, kbName, docId);
|
|
116
|
+
} catch {
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (doc && typeof doc === "object") {
|
|
121
|
+
return { ...doc, _pollingTimedOut: timedOut };
|
|
122
|
+
}
|
|
123
|
+
return { _pollingTimedOut: timedOut };
|
|
124
|
+
}
|
|
125
|
+
function ingestInFlight(doc) {
|
|
126
|
+
if (!doc || typeof doc !== "object") return false;
|
|
127
|
+
const status = doc.ingest_status;
|
|
128
|
+
return status === "processing" || status === "pending" || status === "in_progress";
|
|
129
|
+
}
|
|
130
|
+
function expandHome(p) {
|
|
131
|
+
if (p.startsWith("~/") || p === "~") {
|
|
132
|
+
return resolve(homedir(), p.slice(2));
|
|
133
|
+
}
|
|
134
|
+
return resolve(p);
|
|
135
|
+
}
|
|
136
|
+
function compactChunk(raw) {
|
|
137
|
+
const c = raw ?? {};
|
|
138
|
+
const content = c.content_with_weight ?? c.content ?? c.chunk ?? c.text ?? "";
|
|
139
|
+
const doc_name = c.docnm_kwd ?? c.doc_name ?? c.docnm ?? "";
|
|
140
|
+
const kbRaw = c.kb_id ?? c.dataset_id;
|
|
141
|
+
const kb_id = Array.isArray(kbRaw) ? typeof kbRaw[0] === "string" ? kbRaw[0] : "" : typeof kbRaw === "string" ? kbRaw : "";
|
|
142
|
+
const docIdRaw = c.doc_id;
|
|
143
|
+
const scoreRaw = c.similarity ?? c.rerank_score;
|
|
144
|
+
const tagsRaw = c.tag_kwd;
|
|
145
|
+
const out = {
|
|
146
|
+
content,
|
|
147
|
+
doc_name,
|
|
148
|
+
kb_id,
|
|
149
|
+
score: typeof scoreRaw === "number" ? scoreRaw : 0
|
|
150
|
+
};
|
|
151
|
+
if (typeof docIdRaw === "string" && docIdRaw.length > 0) {
|
|
152
|
+
out.doc_id = docIdRaw;
|
|
153
|
+
}
|
|
154
|
+
if (Array.isArray(tagsRaw) && tagsRaw.length > 0) {
|
|
155
|
+
out.tags = tagsRaw;
|
|
156
|
+
}
|
|
157
|
+
return out;
|
|
158
|
+
}
|
|
159
|
+
function compactKbQueryResponse(raw) {
|
|
160
|
+
if (!raw || typeof raw !== "object") return { chunks: [] };
|
|
161
|
+
const r = raw;
|
|
162
|
+
const chunksIn = Array.isArray(r.chunks) ? r.chunks : [];
|
|
163
|
+
const out = {
|
|
164
|
+
chunks: chunksIn.map(compactChunk)
|
|
165
|
+
};
|
|
166
|
+
if (typeof r.total === "number") out.total = r.total;
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
function minimalChunk(raw) {
|
|
170
|
+
const c = compactChunk(raw);
|
|
171
|
+
return { content: c.content, score: c.score };
|
|
172
|
+
}
|
|
173
|
+
function minimalKbQueryResponse(raw) {
|
|
174
|
+
if (!raw || typeof raw !== "object") return { chunks: [] };
|
|
175
|
+
const r = raw;
|
|
176
|
+
const chunksIn = Array.isArray(r.chunks) ? r.chunks : [];
|
|
177
|
+
const out = {
|
|
178
|
+
chunks: chunksIn.map(minimalChunk)
|
|
179
|
+
};
|
|
180
|
+
if (typeof r.total === "number") out.total = r.total;
|
|
181
|
+
return out;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export {
|
|
185
|
+
DEFAULT_INGEST_TIMEOUT_MS,
|
|
186
|
+
DEFAULT_FILE_INGEST_TIMEOUT_MS,
|
|
187
|
+
listKnowledgeBases,
|
|
188
|
+
createKb,
|
|
189
|
+
uploadText,
|
|
190
|
+
uploadFile,
|
|
191
|
+
getDocument,
|
|
192
|
+
listDocuments,
|
|
193
|
+
pollIngest,
|
|
194
|
+
compactChunk,
|
|
195
|
+
compactKbQueryResponse,
|
|
196
|
+
minimalChunk,
|
|
197
|
+
minimalKbQueryResponse
|
|
198
|
+
};
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
isConnectionError,
|
|
4
|
+
resetCircuit,
|
|
5
|
+
tripCircuit
|
|
6
|
+
} from "./chunk-Q2EEP4CE.js";
|
|
2
7
|
import {
|
|
3
8
|
CtxdbError
|
|
4
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-S45GOYUU.js";
|
|
5
10
|
|
|
6
11
|
// src/lib/recall-orchestrator.ts
|
|
7
12
|
import {
|
|
@@ -83,7 +88,7 @@ var EMPTY = {
|
|
|
83
88
|
memoryCount: 0,
|
|
84
89
|
knowledgeChunkCount: 0
|
|
85
90
|
};
|
|
86
|
-
async function recallTurn(prompt, cfg, client) {
|
|
91
|
+
async function recallTurn(prompt, cfg, client, agent = "default") {
|
|
87
92
|
if (!prompt.trim()) return { ...EMPTY, reason: "empty_prompt" };
|
|
88
93
|
if (!cfg.apiKey || !cfg.baseUrl) {
|
|
89
94
|
return { ...EMPTY, reason: "config_incomplete" };
|
|
@@ -101,8 +106,10 @@ async function recallTurn(prompt, cfg, client) {
|
|
|
101
106
|
let resp;
|
|
102
107
|
try {
|
|
103
108
|
resp = await client.postJson("/v3/memories/search/", body);
|
|
109
|
+
resetCircuit(agent);
|
|
104
110
|
} catch (err) {
|
|
105
111
|
const msg = err instanceof CtxdbError ? err.message : String(err);
|
|
112
|
+
if (isConnectionError(err)) tripCircuit(agent, cfg.baseUrl, msg);
|
|
106
113
|
return { ...EMPTY, reason: `http_error: ${msg}` };
|
|
107
114
|
}
|
|
108
115
|
if (!resp || typeof resp !== "object") {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
configDir
|
|
4
|
+
} from "./chunk-S45GOYUU.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,6 +300,9 @@ 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;
|
|
@@ -297,8 +311,8 @@ var DEFAULT_KNOWLEDGE_TOP_K = 6;
|
|
|
297
311
|
function isComplete(cfg) {
|
|
298
312
|
return Boolean(cfg.apiKey && cfg.baseUrl);
|
|
299
313
|
}
|
|
300
|
-
function resolveConfigAgent(options = {}) {
|
|
301
|
-
if (
|
|
314
|
+
function resolveConfigAgent(options = {}, preloadedRaw) {
|
|
315
|
+
if (isAgentSlug(options.agent)) return options.agent;
|
|
302
316
|
return agentFromEnv(options.env);
|
|
303
317
|
}
|
|
304
318
|
function coerceInt(v, fallback) {
|
|
@@ -365,8 +379,8 @@ function applyEnv(cfg, env) {
|
|
|
365
379
|
function load(options = {}) {
|
|
366
380
|
const path = options.path ?? defaultConfigPath();
|
|
367
381
|
const env = options.env ?? process.env;
|
|
368
|
-
const agent = resolveConfigAgent({ agent: options.agent, env });
|
|
369
382
|
const raw = readRaw(path);
|
|
383
|
+
const agent = resolveConfigAgent({ ...options, path, env }, raw);
|
|
370
384
|
if (!isV2Schema(raw)) {
|
|
371
385
|
try {
|
|
372
386
|
process.stderr.write(
|
|
@@ -377,7 +391,18 @@ function load(options = {}) {
|
|
|
377
391
|
}
|
|
378
392
|
return applyEnv(configFromDisk({}), env);
|
|
379
393
|
}
|
|
380
|
-
|
|
394
|
+
const agentRaw = agentRawFromFile(raw, agent);
|
|
395
|
+
if (agent !== "default") {
|
|
396
|
+
const defaultRaw = agentRawFromFile(raw, "default");
|
|
397
|
+
if (Object.keys(defaultRaw).length > 0) {
|
|
398
|
+
for (const [k, v] of Object.entries(defaultRaw)) {
|
|
399
|
+
if (!(k in agentRaw)) {
|
|
400
|
+
agentRaw[k] = v;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return applyEnv(configFromDisk(agentRaw), env);
|
|
381
406
|
}
|
|
382
407
|
function configToDisk(cfg) {
|
|
383
408
|
return {
|
|
@@ -446,23 +471,32 @@ function configuredAgents(path) {
|
|
|
446
471
|
if (!isV2Schema(raw) || !raw.agents || typeof raw.agents !== "object") return [];
|
|
447
472
|
return Object.keys(raw.agents).filter(isAgent);
|
|
448
473
|
}
|
|
474
|
+
function writeInstalledPkgVersion(version, path) {
|
|
475
|
+
const target = path ?? defaultConfigPath();
|
|
476
|
+
const raw = readRaw(target);
|
|
477
|
+
const updated = { ...raw, installed_pkg_version: version };
|
|
478
|
+
mkdirSync2(dirname3(target), { recursive: true });
|
|
479
|
+
writeFileSync(target, JSON.stringify(updated, null, 2) + "\n", "utf-8");
|
|
480
|
+
}
|
|
449
481
|
|
|
450
482
|
export {
|
|
451
483
|
setDebug,
|
|
452
484
|
debug,
|
|
453
485
|
CtxdbError,
|
|
454
|
-
NotFoundError,
|
|
455
486
|
HttpClient,
|
|
456
487
|
SUPPORTED_AGENTS,
|
|
457
|
-
|
|
488
|
+
isBuiltinAgent,
|
|
489
|
+
isAgentSlug,
|
|
458
490
|
agentHomeDir,
|
|
459
491
|
agentFromEnv,
|
|
460
492
|
agentFromArgvWithFallback,
|
|
493
|
+
configDir,
|
|
461
494
|
DEFAULT_BASE_URL,
|
|
462
495
|
DEFAULT_USER_ID,
|
|
463
496
|
isComplete,
|
|
464
497
|
load,
|
|
465
498
|
removeAgent,
|
|
466
499
|
save,
|
|
467
|
-
configuredAgents
|
|
500
|
+
configuredAgents,
|
|
501
|
+
writeInstalledPkgVersion
|
|
468
502
|
};
|