@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 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.opencode`. (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
+ 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 is XDG-style: setup creates `~/.config/opencode` and `~/.config/opencode/plugins` when they are missing.
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 chunks into a `<external-knowledge>` block; otherwise the recommended path for KB is the agent calling `ctxdb kb search --agent <name>` explicitly when the user asks (see SKILL.md) |
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` | Stop hook captures each turn into long-term memory for the selected agent |
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: the Stop hook reads the host's `session_id` from the hook payload (Qoder / Codex / Claude Code all pass it at the top level; 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).
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`, 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).
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-6S5RJYBC.js";
4
+ } from "./chunk-UH7AJF6F.js";
5
5
  import {
6
6
  isConnectionError,
7
7
  resetCircuit,
8
8
  tripCircuit
9
- } from "./chunk-UULWJJT4.js";
9
+ } from "./chunk-TGVURF54.js";
10
10
  import {
11
11
  CtxdbError
12
- } from "./chunk-TKMIWM6Q.js";
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
- recallTurn,
178
- fetchKbCatalogBlock
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
+ };