@aliyunrds/ctxdb 1.0.1-beta.2 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -11
- package/dist/{chunk-TKMIWM6Q.js → chunk-6FZL67GH.js} +3 -1
- package/dist/{chunk-CYCD234A.js → chunk-EUQ3OFCQ.js} +37 -32
- package/dist/chunk-FD3AMXVU.js +111 -0
- package/dist/chunk-IMYLU5C2.js +514 -0
- package/dist/chunk-RZONPKY4.js +185 -0
- package/dist/{chunk-UULWJJT4.js → chunk-TGVURF54.js} +1 -1
- package/dist/{chunk-6S5RJYBC.js → chunk-UH7AJF6F.js} +25 -19
- package/dist/cli/main.js +484 -165
- package/dist/hooks/hermes-post-llm-call.js +220 -0
- package/dist/hooks/hermes-pre-llm-call.js +145 -0
- package/dist/hooks/session-start.js +9 -178
- package/dist/hooks/stop.js +4 -488
- package/dist/hooks/user-prompt-submit.js +8 -105
- package/dist/opencode/index.js +142 -11
- package/dist/setup/skills/contextdb-knowledge/SKILL.md +24 -7
- package/dist/setup/skills/contextdb-memory/SKILL.md +6 -6
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -9,8 +9,9 @@ Unified access layer for RDS ContextDatabase. One `ctxdb` CLI (memory + KB ops),
|
|
|
9
9
|
| **codex** | ✅ UserPromptSubmit + Stop + SessionStart | `~/.codex/skills/ctxdb/` | hooks-driven (auto capture/recall) |
|
|
10
10
|
| **claude** | ✅ UserPromptSubmit + Stop + SessionStart | `~/.claude/skills/ctxdb/` | hooks-driven (auto capture/recall) |
|
|
11
11
|
| **opencode** | Plugin shim (`~/.config/opencode/plugins/ctxdb.ts`) | `~/.config/opencode/skills/` | in-process plugin (auto capture/recall + KB catalog injection) |
|
|
12
|
+
| **hermes** | ✅ `pre_llm_call` + `post_llm_call` | `~/.hermes/skills/` | shell hooks (auto capture/recall + first-turn warmup) |
|
|
12
13
|
|
|
13
|
-
Config and logs live under `~/.ctxdb/`. `~/.ctxdb/ctxdb.json` is one file, but runtime config is isolated per agent under `agents.qoder`, `agents.qoderwork`, `agents.codex`, `agents.claude`, and `agents.
|
|
14
|
+
Config and logs live under `~/.ctxdb/`. `~/.ctxdb/ctxdb.json` is one file, but runtime config is isolated per agent under `agents.qoder`, `agents.qoderwork`, `agents.codex`, `agents.claude`, `agents.opencode`, and `agents.hermes`. (Pre-2026-05-23 installs used `~/.ctxdb.json` at the home root; running `ctxdb setup --agent <name>` migrates the file into the selected agent section.)
|
|
14
15
|
|
|
15
16
|
## Install
|
|
16
17
|
|
|
@@ -27,6 +28,8 @@ ctxdb setup --agent codex --api-key ctxdb-...
|
|
|
27
28
|
ctxdb setup --agent claude --api-key ctxdb-...
|
|
28
29
|
# OpenCode (plugin shim + skill)
|
|
29
30
|
ctxdb setup --agent opencode --api-key ctxdb-...
|
|
31
|
+
# Hermes (pre_llm_call recall/warmup + post_llm_call capture + skill; macOS/Linux)
|
|
32
|
+
ctxdb setup --agent hermes --api-key ctxdb-...
|
|
30
33
|
|
|
31
34
|
# --base-url defaults to https://context-database.aliyuncs.com (public prod).
|
|
32
35
|
# Pass --base-url <host> only if you target a different deployment
|
|
@@ -39,9 +42,40 @@ ctxdb setup --agent opencode --api-key ctxdb-...
|
|
|
39
42
|
ctxdb status --json
|
|
40
43
|
```
|
|
41
44
|
|
|
42
|
-
Setup first checks that the target agent home exists (`~/.qoder`, `~/.qoderwork`, `~/.codex`, or `~/.claude`). If the directory is missing, install or start that agent once before running `ctxdb setup --agent <name>`. OpenCode
|
|
45
|
+
Setup first checks that the target agent home exists (`~/.qoder`, `~/.qoderwork`, `~/.codex`, or `~/.claude`). If the directory is missing, install or start that agent once before running `ctxdb setup --agent <name>`. OpenCode and Hermes are config-dir style: setup creates `~/.config/opencode` / `~/.hermes` when they are missing.
|
|
43
46
|
|
|
44
|
-
For **qoder**, **qoderwork**, **codex**, and **claude**, restart the harness (CLI: just exit + restart; app: Cmd+R or quit/relaunch) so it picks up the new hooks/skill. For **codex**, setup also writes `[features].hooks = true` into `~/.codex/config.toml` (creating the file if missing) — Codex won't fire any hook entries without it. On first use Codex may prompt you to trust the new hook commands. For **opencode**, restart OpenCode so it loads `~/.config/opencode/plugins/ctxdb.ts`.
|
|
47
|
+
For **qoder**, **qoderwork**, **codex**, and **claude**, restart the harness (CLI: just exit + restart; app: Cmd+R or quit/relaunch) so it picks up the new hooks/skill. For **codex**, setup also writes `[features].hooks = true` into `~/.codex/config.toml` (creating the file if missing) — Codex won't fire any hook entries without it. On first use Codex may prompt you to trust the new hook commands. For **opencode**, restart OpenCode so it loads `~/.config/opencode/plugins/ctxdb.ts`. For **hermes**, setup also checks `~/.hermes/shell-hooks-allowlist.json`; it reports setup as incomplete until both ctxdb hook commands are approved.
|
|
48
|
+
|
|
49
|
+
Hermes owns this security allowlist, so ctxdb only checks it and never writes approvals. Approve the ctxdb `pre_llm_call` and `post_llm_call` hooks with:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
hermes --accept-hooks chat
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
This command opens an interactive chat. After Hermes starts, exit the chat and rerun `ctxdb setup --agent hermes`; the allowlist check should then pass.
|
|
56
|
+
|
|
57
|
+
The two ctxdb events are `pre_llm_call` and `post_llm_call`. `ctxdb status --agent hermes` also verifies their exact `(event, command)` pairs and reports the specific missing approval.
|
|
58
|
+
|
|
59
|
+
## Update
|
|
60
|
+
|
|
61
|
+
`ctxdb update` (alias: `ctxdb upgrade`) now updates the package from npm by default, then re-runs the setup lifecycle for every configured built-in agent using the credentials already stored in `~/.ctxdb/ctxdb.json`:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
ctxdb update
|
|
65
|
+
|
|
66
|
+
# Limit setup to one configured agent.
|
|
67
|
+
ctxdb update --agent codex
|
|
68
|
+
|
|
69
|
+
# Skip the npm step and only re-run setup (also used internally after install).
|
|
70
|
+
ctxdb update --no-self-update
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The existing `--self-update` flag remains accepted for compatibility, but is no longer required. If the npm version check, global install, or setup pass fails, ctxdb prints the manual recovery commands:
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
npm install -g @aliyunrds/ctxdb
|
|
77
|
+
ctxdb setup
|
|
78
|
+
```
|
|
45
79
|
|
|
46
80
|
## What it does
|
|
47
81
|
|
|
@@ -50,24 +84,30 @@ For **qoder**, **qoderwork**, **codex**, and **claude**, hooks fire automaticall
|
|
|
50
84
|
| Trigger | Hook | What runs |
|
|
51
85
|
|---|---|---|
|
|
52
86
|
| Session starts | `SessionStart` | Build a short cwd/git-signal query, recall relevant memories, and inject warm-up context as `additionalContext` |
|
|
53
|
-
| User submits a prompt | `UserPromptSubmit` | Search `/v3/memories/search/`, format a `<recalled-memories>` block, inject as additional context. **By default only memory is recalled.** Set `recall_knowledge: true` in the selected agent config to also pull KB
|
|
87
|
+
| User submits a prompt | `UserPromptSubmit` | Search `/v3/memories/search/`, format a `<recalled-memories>` block, inject as additional context. **By default only memory is recalled.** Set `recall_knowledge: true` in the selected agent config to also pull KB context into an `<external-knowledge>` block; graph synthesis and document evidence are labeled separately inside that wrapper. Otherwise the recommended path for KB is the agent calling `ctxdb kb search --agent <name>` explicitly when the user asks (see SKILL.md) |
|
|
54
88
|
| Agent finishes its turn | `Stop` | Read transcript, detect KB-upload turns and skip capture (so uploaded content doesn't bleed into long-term memory), slice the current turn, sanitize the input, POST to memory store |
|
|
55
89
|
|
|
56
90
|
Implementation note: qoder/Claude consume the JSON `hookSpecificOutput.additionalContext` shape; Codex has been verified on this machine through `~/.codex/hooks.json`, `[features].hooks = true`, and hook audit logs, where non-empty stdout from `UserPromptSubmit`/`SessionStart` is treated as injected context. The same hook core is shared; setup passes `--agent=<name>` so each hook reads its own config section.
|
|
57
91
|
|
|
58
92
|
For **opencode**, `ctxdb setup --agent opencode` copies the self-contained plugin bundle to `~/.config/opencode/plugins/ctxdb-bundle.js` and writes a re-export shim at `~/.config/opencode/plugins/ctxdb.ts` referencing it via the relative path `./ctxdb-bundle.js` (Bun on Windows can't resolve absolute-path specifiers from a .ts shim). `ctxdb upgrade --agent opencode` re-copies the bundle + refreshes the shim.
|
|
59
93
|
|
|
94
|
+
For **hermes**, `ctxdb setup --agent hermes` writes two shell hooks under `~/.hermes/config.yaml`:
|
|
95
|
+
|
|
96
|
+
- `pre_llm_call` maps to recall on each user prompt and first-turn warmup. Hermes does not persist hook context into conversation history, so ctxdb re-injects the KB catalog on every user turn even when `kb_catalog_injection` keeps its default `"session_start"` value; this prevents the catalog from disappearing after turn one.
|
|
97
|
+
- stdout uses Hermes' `{"context":"..."}` protocol.
|
|
98
|
+
- `post_llm_call` captures `extra.user_message` + `extra.assistant_response` without a transcript file, while `extra.conversation_history` supplies current-turn tool calls for the B-3c KB-upload guard.
|
|
99
|
+
|
|
60
100
|
## CLI
|
|
61
101
|
|
|
62
102
|
`ctxdb` ships 18 subcommands:
|
|
63
103
|
|
|
64
|
-
- **Top-level**: `setup` / `status` / `ping` / `uninstall` / `upgrade`
|
|
104
|
+
- **Top-level**: `setup` / `status` / `ping` / `uninstall` / `update` (`upgrade` alias)
|
|
65
105
|
- **Memory**: `memory add|search|list|get|update|delete`
|
|
66
106
|
- **KB**: `kb upload-text|upload-file|list|documents-list|document-get|search`
|
|
67
107
|
|
|
68
108
|
See `ctxdb --help`.
|
|
69
109
|
|
|
70
|
-
`setup` without `--agent` writes `agents.default` in `~/.ctxdb/ctxdb.json` (CLI-only, no hooks or skills); with `--agent <qoder|qoderwork|codex|claude|opencode>` it writes agent config and installs hooks/plugins + 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`.
|
|
110
|
+
`setup` without `--agent` writes `agents.default` in `~/.ctxdb/ctxdb.json` (CLI-only, no hooks or skills); with `--agent <qoder|qoderwork|codex|claude|opencode|hermes>` it writes agent config and installs hooks/plugins + 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`.
|
|
71
111
|
|
|
72
112
|
`memory add … --no-infer` stores the text verbatim (skips server-side LLM
|
|
73
113
|
fact-extraction). Use it when the user explicitly asks for a verbatim
|
|
@@ -81,6 +121,32 @@ search). The SKILL points the agent here when the user explicitly asks for
|
|
|
81
121
|
KB lookup ("结合知识库查询" / "从知识库召回"). `--kb` is comma-separated;
|
|
82
122
|
omit it to search across all KBs.
|
|
83
123
|
|
|
124
|
+
Default and `--verbose` search output is graph-aware. Ordinary document
|
|
125
|
+
evidence remains in `chunks`; a GraphRAG synthesis row is moved to the optional
|
|
126
|
+
`graph_context` object and is explicitly non-citable:
|
|
127
|
+
|
|
128
|
+
```json
|
|
129
|
+
{
|
|
130
|
+
"graph_context": {
|
|
131
|
+
"type": "graph_context",
|
|
132
|
+
"content": "...",
|
|
133
|
+
"knowledge_base_ids": ["kb-id"],
|
|
134
|
+
"citation_eligible": false
|
|
135
|
+
},
|
|
136
|
+
"chunks": [{ "content": "direct document evidence", "score": 0.73 }],
|
|
137
|
+
"total": 1
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`--verbose` adds source fields to document chunks plus `source_label` / `tags`
|
|
142
|
+
to graph context. `--raw` is unchanged and returns the server response verbatim,
|
|
143
|
+
including the synthetic graph row inside `chunks`. Agents use graph context for
|
|
144
|
+
relationship understanding and, only when direct evidence is missing, at most
|
|
145
|
+
one layer of up to three targeted document searches; final claims and citations
|
|
146
|
+
come from document chunks. A future server contract should expose an explicit
|
|
147
|
+
graph result type and graph source document/chunk IDs so clients no longer need
|
|
148
|
+
tag/label detection and can provide real provenance.
|
|
149
|
+
|
|
84
150
|
## Configuration
|
|
85
151
|
|
|
86
152
|
Lives at `~/.ctxdb/ctxdb.json` (co-located with logs at `~/.ctxdb/logs/`). Schema:
|
|
@@ -105,12 +171,13 @@ Lives at `~/.ctxdb/ctxdb.json` (co-located with logs at `~/.ctxdb/logs/`). Schem
|
|
|
105
171
|
"qoderwork": { "...": "same schema, independent values" },
|
|
106
172
|
"codex": { "...": "same schema, independent values" },
|
|
107
173
|
"claude": { "...": "same schema, independent values" },
|
|
108
|
-
"opencode": { "...": "same schema, independent values" }
|
|
174
|
+
"opencode": { "...": "same schema, independent values" },
|
|
175
|
+
"hermes": { "...": "same schema, independent values" }
|
|
109
176
|
}
|
|
110
177
|
}
|
|
111
178
|
```
|
|
112
179
|
|
|
113
|
-
Agent selection is driven by `--agent <qoder|qoderwork|codex|claude|opencode|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.
|
|
180
|
+
Agent selection is driven by `--agent <qoder|qoderwork|codex|claude|opencode|hermes|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.
|
|
114
181
|
|
|
115
182
|
Field reference:
|
|
116
183
|
|
|
@@ -119,7 +186,7 @@ Field reference:
|
|
|
119
186
|
| `user_id` | str | `"default"` | mem0-layer bucket key on every capture/recall call. The server's tenant isolation runs on `member_id` (injected from `X-API-Key`), so this is an optional per-user/agent slice. Override with `ctxdb setup --agent <a> --user-id <bucket>` or `CTXDB_USER_ID=<bucket>` when you need to isolate this install from another access layer or machine sharing the same workspace |
|
|
120
187
|
| `agent_id` | str | null | Optional scope dimension on every capture/recall call. **Default null = not sent** — the server counts `agent_id` as scope on the search path, so emitting it by default would narrow every existing user's recall. Set only when you want per-agent isolation: `CTXDB_AGENT_ID=<id>` (env-only; no setup flag) |
|
|
121
188
|
| `app_id` | str | null | Optional app-scope dimension, same contract as `agent_id` (default null = not sent). Set via `CTXDB_APP_ID=<id>` (env-only) |
|
|
122
|
-
| `auto_capture` | bool | `true` |
|
|
189
|
+
| `auto_capture` | bool | `true` | End-of-turn hook/plugin captures each turn into long-term memory for the selected agent |
|
|
123
190
|
| `auto_recall` | bool | `true` | UserPromptSubmit hook recalls memory on each user prompt for the selected agent |
|
|
124
191
|
| `warmup_recall` | bool | `false` | SessionStart hook recalls cwd/git-related memories for the selected agent. **Default is off** — open it per-agent when you want session-start warmup (adds one bounded, circuit-protected recall call on session start) |
|
|
125
192
|
| `recall_knowledge` | bool | `false` | When `auto_recall` is on, also pull KB chunks alongside memory. Default is **off** — the recommended KB-recall path is the agent calling `kb search --agent <name>` explicitly when the user asks |
|
|
@@ -131,7 +198,7 @@ Env-var overrides apply to the selected agent config (env wins): `CTXDB_AGENT` /
|
|
|
131
198
|
|
|
132
199
|
Hook/plugin escape hatch: set `CTXDB_SKIP_HOOKS=TRUE` on the agent process to make all ctxdb hook entrypoints exit immediately before reading config or calling the API. Direct `ctxdb memory` / `ctxdb kb` / `ctxdb setup` CLI commands are unchanged.
|
|
133
200
|
|
|
134
|
-
`run_id` is **not** env-driven:
|
|
201
|
+
`run_id` is **not** env-driven: capture reads the host's session id from the lifecycle payload (Qoder / Codex / Claude Code and Hermes pass `session_id`; opencode reads `ev.properties.sessionID`) and sends it as `run_id` on capture only, so the server can associate turns within one session for richer extraction context. Recall never sends `run_id`. If the payload has no session id, `run_id` is omitted and capture proceeds normally (the server falls back to baseMessages).
|
|
135
202
|
|
|
136
203
|
## Architecture
|
|
137
204
|
|
|
@@ -177,6 +244,7 @@ ctxdb uninstall --agent qoderwork
|
|
|
177
244
|
ctxdb uninstall --agent codex
|
|
178
245
|
ctxdb uninstall --agent claude
|
|
179
246
|
ctxdb uninstall --agent opencode
|
|
247
|
+
ctxdb uninstall --agent hermes
|
|
180
248
|
|
|
181
249
|
# Wholesale across all agents, but keep config + logs for re-setup later:
|
|
182
250
|
ctxdb uninstall # all agents, keeps ~/.ctxdb/{ctxdb.json,logs/}
|
|
@@ -186,7 +254,7 @@ ctxdb uninstall --purge-logs # also deletes ~/.ctxdb/logs/
|
|
|
186
254
|
|
|
187
255
|
### What uninstall does (and doesn't) touch
|
|
188
256
|
|
|
189
|
-
- **Hook entries in `~/.qoder/settings.json`, `~/.codex/hooks.json`,
|
|
257
|
+
- **Hook entries in `~/.qoder/settings.json`, `~/.qoderwork/settings.json`, `~/.codex/hooks.json`, `~/.claude/settings.json`, and `~/.hermes/config.yaml`**: 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).
|
|
190
258
|
- **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.
|
|
191
259
|
- **`~/.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).
|
|
192
260
|
- **`~/.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.
|
|
@@ -262,7 +262,7 @@ function extractErrorDetail(text, fallback) {
|
|
|
262
262
|
// src/lib/agents.ts
|
|
263
263
|
import { homedir as homedir2 } from "os";
|
|
264
264
|
import { join as join3 } from "path";
|
|
265
|
-
var SUPPORTED_AGENTS = ["qoder", "qoderwork", "codex", "claude", "opencode"];
|
|
265
|
+
var SUPPORTED_AGENTS = ["qoder", "qoderwork", "codex", "claude", "opencode", "hermes"];
|
|
266
266
|
function isBuiltinAgent(v) {
|
|
267
267
|
return typeof v === "string" && SUPPORTED_AGENTS.includes(v);
|
|
268
268
|
}
|
|
@@ -282,6 +282,8 @@ function agentHomeDir(agent) {
|
|
|
282
282
|
return join3(homedir2(), ".claude");
|
|
283
283
|
case "opencode":
|
|
284
284
|
return join3(homedir2(), ".config", "opencode");
|
|
285
|
+
case "hermes":
|
|
286
|
+
return join3(homedir2(), ".hermes");
|
|
285
287
|
}
|
|
286
288
|
}
|
|
287
289
|
var AGENT_VARIANT_HOMES = {
|
|
@@ -1,15 +1,42 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
listKnowledgeBases
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-UH7AJF6F.js";
|
|
5
5
|
import {
|
|
6
6
|
isConnectionError,
|
|
7
7
|
resetCircuit,
|
|
8
8
|
tripCircuit
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-TGVURF54.js";
|
|
10
10
|
import {
|
|
11
11
|
CtxdbError
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-6FZL67GH.js";
|
|
13
|
+
|
|
14
|
+
// src/lib/kb-catalog.ts
|
|
15
|
+
function sanitizeKeyEntities(raw) {
|
|
16
|
+
if (!Array.isArray(raw)) return [];
|
|
17
|
+
const out = [];
|
|
18
|
+
for (const e of raw) {
|
|
19
|
+
if (typeof e !== "string") continue;
|
|
20
|
+
const cleaned = e.replace(/\s+/g, " ").trim();
|
|
21
|
+
if (cleaned.length > 0) out.push(cleaned);
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
async function fetchKbCatalogBlock(client, agent) {
|
|
26
|
+
const kbs = await listKnowledgeBases(client);
|
|
27
|
+
const active = kbs.filter((kb) => kb.status === "active");
|
|
28
|
+
if (active.length === 0) return "";
|
|
29
|
+
const lines = active.map((kb) => {
|
|
30
|
+
const ents = sanitizeKeyEntities(kb.key_entities);
|
|
31
|
+
return ents.length > 0 ? `\xB7 ${kb.name}: ${ents.join(", ")}` : `\xB7 ${kb.name}`;
|
|
32
|
+
});
|
|
33
|
+
return [
|
|
34
|
+
"<available-knowledge-bases>",
|
|
35
|
+
`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. For keyword-based KB search, keep each single query focused: use at most 5 keywords or short phrases; run multiple targeted searches if more are needed. When \`graph_context\` is returned, use it only for relationship understanding and query planning, never as direct evidence. If document \`chunks\` are insufficient, derive up to 3 focused queries and run one additional search layer in the same KB; base final answers and citations only on document \`chunks\`. Knowledge bases:`,
|
|
36
|
+
...lines,
|
|
37
|
+
"</available-knowledge-bases>"
|
|
38
|
+
].join("\n");
|
|
39
|
+
}
|
|
13
40
|
|
|
14
41
|
// src/lib/recall-orchestrator.ts
|
|
15
42
|
import {
|
|
@@ -18,6 +45,7 @@ import {
|
|
|
18
45
|
|
|
19
46
|
// src/lib/format-recall.ts
|
|
20
47
|
import {
|
|
48
|
+
RECALLED_MEMORIES_SAFETY_NOTICE,
|
|
21
49
|
formatRecalledMemoriesBlock
|
|
22
50
|
} from "@aliyunrds/ctxdb-shared";
|
|
23
51
|
var OPEN_TAG = "<recalled-memories>";
|
|
@@ -48,6 +76,10 @@ function slimRewrite(raw, userId, memoryCount) {
|
|
|
48
76
|
out.push(line);
|
|
49
77
|
continue;
|
|
50
78
|
}
|
|
79
|
+
if (line === RECALLED_MEMORIES_SAFETY_NOTICE) {
|
|
80
|
+
out.push(`${line} `);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
51
83
|
if (line.startsWith("Stored memories for ")) {
|
|
52
84
|
out.push(`Recalled ${memoryCount} memories for "${userId}":`);
|
|
53
85
|
continue;
|
|
@@ -146,34 +178,7 @@ async function recallTurn(prompt, cfg, client, agent = "default") {
|
|
|
146
178
|
};
|
|
147
179
|
}
|
|
148
180
|
|
|
149
|
-
// src/lib/kb-catalog.ts
|
|
150
|
-
function sanitizeKeyEntities(raw) {
|
|
151
|
-
if (!Array.isArray(raw)) return [];
|
|
152
|
-
const out = [];
|
|
153
|
-
for (const e of raw) {
|
|
154
|
-
if (typeof e !== "string") continue;
|
|
155
|
-
const cleaned = e.replace(/\s+/g, " ").trim();
|
|
156
|
-
if (cleaned.length > 0) out.push(cleaned);
|
|
157
|
-
}
|
|
158
|
-
return out;
|
|
159
|
-
}
|
|
160
|
-
async function fetchKbCatalogBlock(client, agent) {
|
|
161
|
-
const kbs = await listKnowledgeBases(client);
|
|
162
|
-
const active = kbs.filter((kb) => kb.status === "active");
|
|
163
|
-
if (active.length === 0) return "";
|
|
164
|
-
const lines = active.map((kb) => {
|
|
165
|
-
const ents = sanitizeKeyEntities(kb.key_entities);
|
|
166
|
-
return ents.length > 0 ? `\xB7 ${kb.name}: ${ents.join(", ")}` : `\xB7 ${kb.name}`;
|
|
167
|
-
});
|
|
168
|
-
return [
|
|
169
|
-
"<available-knowledge-bases>",
|
|
170
|
-
`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. For keyword-based KB search, keep each single query focused: use at most 5 keywords or short phrases; run multiple targeted searches if more are needed. Knowledge bases:`,
|
|
171
|
-
...lines,
|
|
172
|
-
"</available-knowledge-bases>"
|
|
173
|
-
].join("\n");
|
|
174
|
-
}
|
|
175
|
-
|
|
176
181
|
export {
|
|
177
|
-
|
|
178
|
-
|
|
182
|
+
fetchKbCatalogBlock,
|
|
183
|
+
recallTurn
|
|
179
184
|
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
fetchKbCatalogBlock,
|
|
4
|
+
recallTurn
|
|
5
|
+
} from "./chunk-EUQ3OFCQ.js";
|
|
6
|
+
import {
|
|
7
|
+
isCircuitOpen
|
|
8
|
+
} from "./chunk-TGVURF54.js";
|
|
9
|
+
import {
|
|
10
|
+
HttpClient,
|
|
11
|
+
agentFromArgvWithFallback,
|
|
12
|
+
debug,
|
|
13
|
+
isComplete,
|
|
14
|
+
load,
|
|
15
|
+
setDebug
|
|
16
|
+
} from "./chunk-6FZL67GH.js";
|
|
17
|
+
import {
|
|
18
|
+
shouldSkipHooks
|
|
19
|
+
} from "./chunk-UEKR2Z3S.js";
|
|
20
|
+
|
|
21
|
+
// src/hooks/user-prompt-submit.ts
|
|
22
|
+
import { pathToFileURL } from "url";
|
|
23
|
+
async function composeUserPromptSubmit(cfg, agent, client, prompt) {
|
|
24
|
+
const [recall, kbBlock] = await Promise.all([
|
|
25
|
+
recallTurn(prompt, cfg, client, agent),
|
|
26
|
+
cfg.kbCatalogInjection === "user_prompt_submit" ? fetchKbCatalogBlock(client, agent).catch(() => "") : Promise.resolve("")
|
|
27
|
+
]);
|
|
28
|
+
let ctx = recall.additionalContext || "";
|
|
29
|
+
if (kbBlock) ctx = ctx ? `${ctx}
|
|
30
|
+
|
|
31
|
+
${kbBlock}` : kbBlock;
|
|
32
|
+
return { ctx, recall, kbBlock };
|
|
33
|
+
}
|
|
34
|
+
function formatUserPromptSubmitStdout(agent, ctx) {
|
|
35
|
+
if (agent === "codex") return ctx + "\n";
|
|
36
|
+
const out = {
|
|
37
|
+
hookSpecificOutput: {
|
|
38
|
+
hookEventName: "UserPromptSubmit",
|
|
39
|
+
additionalContext: ctx
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
return JSON.stringify(out) + "\n";
|
|
43
|
+
}
|
|
44
|
+
async function readStdinJson() {
|
|
45
|
+
let raw = "";
|
|
46
|
+
for await (const chunk of process.stdin) raw += chunk;
|
|
47
|
+
if (!raw.trim()) return {};
|
|
48
|
+
try {
|
|
49
|
+
const obj = JSON.parse(raw);
|
|
50
|
+
return obj && typeof obj === "object" && !Array.isArray(obj) ? obj : {};
|
|
51
|
+
} catch {
|
|
52
|
+
return {};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function main() {
|
|
56
|
+
try {
|
|
57
|
+
if (shouldSkipHooks()) return 0;
|
|
58
|
+
const event = await readStdinJson();
|
|
59
|
+
const { agent, fellBack } = agentFromArgvWithFallback();
|
|
60
|
+
if (fellBack) {
|
|
61
|
+
process.stderr.write(
|
|
62
|
+
`ctxdb recall: agent unspecified, defaulting to ${agent}
|
|
63
|
+
`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
const prompt = typeof event.prompt === "string" ? event.prompt : "";
|
|
67
|
+
if (!prompt.trim()) return 0;
|
|
68
|
+
const cfg = load({ agent });
|
|
69
|
+
setDebug(cfg.debug);
|
|
70
|
+
debug("recall", "start", { prompt: prompt.slice(0, 200), userId: cfg.userId });
|
|
71
|
+
if (!isComplete(cfg) || !cfg.autoRecall) {
|
|
72
|
+
debug("recall", "skip (config incomplete or autoRecall=false)");
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
if (isCircuitOpen(agent, cfg.baseUrl)) {
|
|
76
|
+
debug("recall", "skip (circuit open)");
|
|
77
|
+
process.stderr.write(`ctxdb recall: skip (circuit_open: ${cfg.baseUrl})
|
|
78
|
+
`);
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
const client = new HttpClient({ baseUrl: cfg.baseUrl, apiKey: cfg.apiKey, timeoutMs: 5e3 });
|
|
82
|
+
const { ctx, recall, kbBlock } = await composeUserPromptSubmit(cfg, agent, client, prompt);
|
|
83
|
+
if (!recall.ok && !recall.additionalContext && !kbBlock) {
|
|
84
|
+
const reason = recall.reason ?? "no_context";
|
|
85
|
+
debug("recall", `no result: ${reason}`, recall);
|
|
86
|
+
if (recall.reason && recall.reason.startsWith("http_error:")) {
|
|
87
|
+
process.stderr.write(`ctxdb recall: ${recall.reason}
|
|
88
|
+
`);
|
|
89
|
+
}
|
|
90
|
+
return 0;
|
|
91
|
+
}
|
|
92
|
+
if (!ctx) return 0;
|
|
93
|
+
debug("recall", `ok, additionalContext length=${ctx.length}`);
|
|
94
|
+
process.stdout.write(formatUserPromptSubmitStdout(agent, ctx));
|
|
95
|
+
return 0;
|
|
96
|
+
} catch (err) {
|
|
97
|
+
process.stderr.write(`ctxdb recall: unexpected error: ${err?.message ?? err}
|
|
98
|
+
`);
|
|
99
|
+
return 0;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
|
|
103
|
+
main().then((code) => {
|
|
104
|
+
process.exitCode = code;
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export {
|
|
109
|
+
composeUserPromptSubmit,
|
|
110
|
+
formatUserPromptSubmitStdout
|
|
111
|
+
};
|