@aliyunrds/ctxdb 1.0.0 → 1.0.1-beta.2

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
@@ -117,6 +117,8 @@ Field reference:
117
117
  | Key | Type | Default | Meaning |
118
118
  |---|---|---|---|
119
119
  | `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
+ | `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
+ | `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) |
120
122
  | `auto_capture` | bool | `true` | Stop hook captures each turn into long-term memory for the selected agent |
121
123
  | `auto_recall` | bool | `true` | UserPromptSubmit hook recalls memory on each user prompt for the selected agent |
122
124
  | `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,7 +127,11 @@ Field reference:
125
127
  | `knowledge_top_k` | int | `6` | KB chunks pulled per recall (only effective when `recall_knowledge: true`) |
126
128
  | `debug` | bool | `false` | Verbose hook logging (`~/.ctxdb/logs/ctxdb.log`) |
127
129
 
128
- Env-var overrides apply to the selected agent config (env wins): `CTXDB_AGENT` / `CTXDB_API_KEY` / `CTXDB_BASE_URL` / `CTXDB_USER_ID`.
130
+ Env-var overrides apply to the selected agent config (env wins): `CTXDB_AGENT` / `CTXDB_API_KEY` / `CTXDB_BASE_URL` / `CTXDB_USER_ID` / `CTXDB_AGENT_ID` / `CTXDB_APP_ID`. The last two default to unset — the request body omits `agent_id`/`app_id` entirely; set them only when you want per-agent / per-app scope isolation on the server.
131
+
132
+ 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
+
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).
129
135
 
130
136
  ## Architecture
131
137
 
@@ -6,10 +6,10 @@ import {
6
6
  isConnectionError,
7
7
  resetCircuit,
8
8
  tripCircuit
9
- } from "./chunk-PLWEIEFH.js";
9
+ } from "./chunk-UULWJJT4.js";
10
10
  import {
11
11
  CtxdbError
12
- } from "./chunk-QHYJ7OXC.js";
12
+ } from "./chunk-TKMIWM6Q.js";
13
13
 
14
14
  // src/lib/recall-orchestrator.ts
15
15
  import {
@@ -109,6 +109,8 @@ async function recallTurn(prompt, cfg, client, agent = "default") {
109
109
  if (cfg.recallKnowledge) {
110
110
  body.knowledge = { enable: true, top_k: cfg.knowledgeTopK };
111
111
  }
112
+ if (cfg.agentId) body.agent_id = cfg.agentId;
113
+ if (cfg.appId) body.app_id = cfg.appId;
112
114
  let resp;
113
115
  try {
114
116
  resp = await client.postJson("/v3/memories/search/", body);
@@ -387,6 +387,8 @@ function configFromDisk(raw) {
387
387
  apiKey: typeof raw.api_key === "string" && raw.api_key ? raw.api_key : null,
388
388
  baseUrl: typeof raw.base_url === "string" && raw.base_url ? String(raw.base_url).replace(/\/+$/, "") : DEFAULT_BASE_URL,
389
389
  userId: typeof raw.user_id === "string" && raw.user_id ? raw.user_id : DEFAULT_USER_ID,
390
+ agentId: typeof raw.agent_id === "string" && raw.agent_id ? raw.agent_id : null,
391
+ appId: typeof raw.app_id === "string" && raw.app_id ? raw.app_id : null,
390
392
  autoCapture: coerceBool(raw.auto_capture, true),
391
393
  autoRecall: coerceBool(raw.auto_recall, true),
392
394
  warmupRecall: coerceBool(raw.warmup_recall, false),
@@ -402,6 +404,8 @@ function applyEnv(cfg, env) {
402
404
  if (env.CTXDB_API_KEY) cfg.apiKey = env.CTXDB_API_KEY;
403
405
  if (env.CTXDB_BASE_URL) cfg.baseUrl = env.CTXDB_BASE_URL.replace(/\/+$/, "");
404
406
  if (env.CTXDB_USER_ID) cfg.userId = env.CTXDB_USER_ID;
407
+ if (env.CTXDB_AGENT_ID) cfg.agentId = env.CTXDB_AGENT_ID;
408
+ if (env.CTXDB_APP_ID) cfg.appId = env.CTXDB_APP_ID;
405
409
  return cfg;
406
410
  }
407
411
  function load(options = {}) {
@@ -437,6 +441,8 @@ function configToDisk(cfg) {
437
441
  api_key: cfg.apiKey,
438
442
  base_url: cfg.baseUrl,
439
443
  user_id: cfg.userId,
444
+ agent_id: cfg.agentId,
445
+ app_id: cfg.appId,
440
446
  auto_capture: cfg.autoCapture,
441
447
  auto_recall: cfg.autoRecall,
442
448
  warmup_recall: cfg.warmupRecall,
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lib/hook-env.ts
4
+ var CTXDB_SKIP_HOOKS = "CTXDB_SKIP_HOOKS";
5
+ function shouldSkipHooks(env = process.env) {
6
+ return env[CTXDB_SKIP_HOOKS] === "TRUE";
7
+ }
8
+
9
+ export {
10
+ shouldSkipHooks
11
+ };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  configDir
4
- } from "./chunk-QHYJ7OXC.js";
4
+ } from "./chunk-TKMIWM6Q.js";
5
5
 
6
6
  // src/lib/circuit.ts
7
7
  import { statSync, writeFileSync, unlinkSync, mkdirSync, readdirSync, readFileSync } from "fs";
package/dist/cli/main.js CHANGED
@@ -31,7 +31,7 @@ import {
31
31
  removeAgent,
32
32
  save,
33
33
  writeInstalledPkgVersion
34
- } from "../chunk-QHYJ7OXC.js";
34
+ } from "../chunk-TKMIWM6Q.js";
35
35
 
36
36
  // src/cli/util.ts
37
37
  var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
@@ -2257,6 +2257,11 @@ AGENT RESOLUTION (when --agent is omitted):
2257
2257
 
2258
2258
  ENV VARS (override selected ~/.ctxdb/ctxdb.json agent config):
2259
2259
  CTXDB_AGENT CTXDB_API_KEY CTXDB_BASE_URL CTXDB_USER_ID
2260
+ CTXDB_AGENT_ID CTXDB_APP_ID
2261
+
2262
+ HOOK ENV VARS:
2263
+ CTXDB_SKIP_HOOKS=TRUE Hook/plugin entrypoints exit immediately.
2264
+ Direct ctxdb CLI commands are unchanged.
2260
2265
 
2261
2266
  CONFIG: ~/.ctxdb/ctxdb.json (agents.default / agents.qoder / agents.qoderwork / agents.codex / agents.claude / agents.opencode)
2262
2267
  LOGS: ~/.ctxdb/logs/ctxdb.log
@@ -1,7 +1,11 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ shouldSkipHooks
4
+ } from "../chunk-UEKR2Z3S.js";
2
5
 
3
6
  // src/hooks/pre-tool-use.ts
4
7
  async function main() {
8
+ if (shouldSkipHooks()) return 0;
5
9
  for await (const _chunk of process.stdin) {
6
10
  }
7
11
  return 0;
@@ -2,11 +2,11 @@
2
2
  import {
3
3
  fetchKbCatalogBlock,
4
4
  recallTurn
5
- } from "../chunk-QPFFMW52.js";
5
+ } from "../chunk-CYCD234A.js";
6
6
  import "../chunk-6S5RJYBC.js";
7
7
  import {
8
8
  isCircuitOpen
9
- } from "../chunk-PLWEIEFH.js";
9
+ } from "../chunk-UULWJJT4.js";
10
10
  import {
11
11
  HttpClient,
12
12
  agentFromArgvWithFallback,
@@ -14,7 +14,10 @@ import {
14
14
  isComplete,
15
15
  load,
16
16
  setDebug
17
- } from "../chunk-QHYJ7OXC.js";
17
+ } from "../chunk-TKMIWM6Q.js";
18
+ import {
19
+ shouldSkipHooks
20
+ } from "../chunk-UEKR2Z3S.js";
18
21
 
19
22
  // src/hooks/session-start.ts
20
23
  import { pathToFileURL } from "url";
@@ -120,6 +123,7 @@ async function readStdinJson() {
120
123
  }
121
124
  async function main() {
122
125
  try {
126
+ if (shouldSkipHooks()) return 0;
123
127
  const event = await readStdinJson();
124
128
  const { agent, fellBack } = agentFromArgvWithFallback();
125
129
  if (fellBack) {
@@ -4,7 +4,7 @@ import {
4
4
  isConnectionError,
5
5
  resetCircuit,
6
6
  tripCircuit
7
- } from "../chunk-PLWEIEFH.js";
7
+ } from "../chunk-UULWJJT4.js";
8
8
  import {
9
9
  CtxdbError,
10
10
  HttpClient,
@@ -13,7 +13,10 @@ import {
13
13
  isDebug,
14
14
  load,
15
15
  setDebug
16
- } from "../chunk-QHYJ7OXC.js";
16
+ } from "../chunk-TKMIWM6Q.js";
17
+ import {
18
+ shouldSkipHooks
19
+ } from "../chunk-UEKR2Z3S.js";
17
20
 
18
21
  // src/lib/capture-orchestrator.ts
19
22
  import {
@@ -304,7 +307,7 @@ function extractSkillSignals(rows) {
304
307
  }
305
308
 
306
309
  // src/lib/capture-orchestrator.ts
307
- async function captureTurn(transcriptPath, cfg, client, agent = "default") {
310
+ async function captureTurn(transcriptPath, cfg, client, agent = "default", sessionId) {
308
311
  if (!cfg.apiKey || !cfg.baseUrl) {
309
312
  return { captured: false, reason: "config_incomplete", messageCount: 0 };
310
313
  }
@@ -373,6 +376,9 @@ async function captureTurn(transcriptPath, cfg, client, agent = "default") {
373
376
  user_id: cfg.userId,
374
377
  async_mode: true
375
378
  };
379
+ if (cfg.agentId) payload.agent_id = cfg.agentId;
380
+ if (cfg.appId) payload.app_id = cfg.appId;
381
+ if (sessionId) payload.run_id = sessionId;
376
382
  let resp;
377
383
  try {
378
384
  resp = await client.postJson("/v3/memories/add/", payload);
@@ -504,8 +510,13 @@ async function readStdinJson() {
504
510
  return {};
505
511
  }
506
512
  }
513
+ function extractHookSessionId(event) {
514
+ const v = event.session_id ?? event.sessionId;
515
+ return typeof v === "string" && v ? v : void 0;
516
+ }
507
517
  async function main() {
508
518
  try {
519
+ if (shouldSkipHooks()) return 0;
509
520
  const event = await readStdinJson();
510
521
  const { agent, fellBack } = agentFromArgvWithFallback();
511
522
  if (fellBack) {
@@ -516,6 +527,7 @@ async function main() {
516
527
  }
517
528
  const transcriptPath = event.transcript_path;
518
529
  if (typeof transcriptPath !== "string" || !transcriptPath) return 0;
530
+ const sessionId = extractHookSessionId(event);
519
531
  const cfg = load({ agent });
520
532
  setDebug(cfg.debug);
521
533
  debug("capture", "start", { transcriptPath, userId: cfg.userId });
@@ -524,7 +536,7 @@ async function main() {
524
536
  return 0;
525
537
  }
526
538
  const client = new HttpClient({ baseUrl: cfg.baseUrl, apiKey: cfg.apiKey, timeoutMs: 5e3 });
527
- const result = await captureTurn(transcriptPath, cfg, client, agent);
539
+ const result = await captureTurn(transcriptPath, cfg, client, agent, sessionId);
528
540
  if (result.captured) {
529
541
  debug("capture", `ok (${result.messageCount} msgs)`, result);
530
542
  process.stderr.write(
@@ -2,11 +2,11 @@
2
2
  import {
3
3
  fetchKbCatalogBlock,
4
4
  recallTurn
5
- } from "../chunk-QPFFMW52.js";
5
+ } from "../chunk-CYCD234A.js";
6
6
  import "../chunk-6S5RJYBC.js";
7
7
  import {
8
8
  isCircuitOpen
9
- } from "../chunk-PLWEIEFH.js";
9
+ } from "../chunk-UULWJJT4.js";
10
10
  import {
11
11
  HttpClient,
12
12
  agentFromArgvWithFallback,
@@ -14,7 +14,10 @@ import {
14
14
  isComplete,
15
15
  load,
16
16
  setDebug
17
- } from "../chunk-QHYJ7OXC.js";
17
+ } from "../chunk-TKMIWM6Q.js";
18
+ import {
19
+ shouldSkipHooks
20
+ } from "../chunk-UEKR2Z3S.js";
18
21
 
19
22
  // src/hooks/user-prompt-submit.ts
20
23
  import { pathToFileURL } from "url";
@@ -52,6 +55,7 @@ async function readStdinJson() {
52
55
  }
53
56
  async function main() {
54
57
  try {
58
+ if (shouldSkipHooks()) return 0;
55
59
  const event = await readStdinJson();
56
60
  const { agent, fellBack } = agentFromArgvWithFallback();
57
61
  if (fellBack) {
@@ -54,6 +54,8 @@ function applyEnv(cfg, env) {
54
54
  if (env.CTXDB_API_KEY) cfg.apiKey = env.CTXDB_API_KEY;
55
55
  if (env.CTXDB_BASE_URL) cfg.baseUrl = env.CTXDB_BASE_URL.replace(/\/+$/, "");
56
56
  if (env.CTXDB_USER_ID) cfg.userId = env.CTXDB_USER_ID;
57
+ if (env.CTXDB_AGENT_ID) cfg.agentId = env.CTXDB_AGENT_ID;
58
+ if (env.CTXDB_APP_ID) cfg.appId = env.CTXDB_APP_ID;
57
59
  return cfg;
58
60
  }
59
61
  function loadOpencodeConfig(options = {}) {
@@ -65,6 +67,8 @@ function loadOpencodeConfig(options = {}) {
65
67
  apiKey: typeof section.api_key === "string" && section.api_key ? section.api_key : null,
66
68
  baseUrl: typeof section.base_url === "string" && section.base_url ? String(section.base_url).replace(/\/+$/, "") : DEFAULT_BASE_URL,
67
69
  userId: typeof section.user_id === "string" && section.user_id ? section.user_id : DEFAULT_USER_ID,
70
+ agentId: typeof section.agent_id === "string" && section.agent_id ? section.agent_id : null,
71
+ appId: typeof section.app_id === "string" && section.app_id ? section.app_id : null,
68
72
  autoCapture: coerceBool(section.auto_capture, true),
69
73
  autoRecall: coerceBool(section.auto_recall, true),
70
74
  warmupRecall: coerceBool(section.warmup_recall, false),
@@ -590,6 +594,8 @@ async function searchAndFormatRecall(prompt, cfg, client, timeoutMs) {
590
594
  if (cfg.recallKnowledge) {
591
595
  body.knowledge = { enable: true, top_k: cfg.knowledgeTopK };
592
596
  }
597
+ if (cfg.agentId) body.agent_id = cfg.agentId;
598
+ if (cfg.appId) body.app_id = cfg.appId;
593
599
  let resp;
594
600
  try {
595
601
  resp = await client.postJson("/v3/memories/search/", body, { timeoutMs });
@@ -772,7 +778,7 @@ function fingerprintMessages(messages) {
772
778
  }
773
779
  return h.digest("hex");
774
780
  }
775
- async function runCapture(messages, cfg, client, timeoutMs, lastFingerprint) {
781
+ async function runCapture(messages, cfg, client, timeoutMs, lastFingerprint, sessionId) {
776
782
  if (!cfg.autoCapture) return { ...EMPTY2, reason: "auto_capture_disabled" };
777
783
  if (!cfg.baseUrl) return { ...EMPTY2, reason: "config_incomplete" };
778
784
  if (messages.length === 0) return { ...EMPTY2, reason: "empty_transcript" };
@@ -796,6 +802,9 @@ async function runCapture(messages, cfg, client, timeoutMs, lastFingerprint) {
796
802
  user_id: cfg.userId,
797
803
  async_mode: true
798
804
  };
805
+ if (cfg.agentId) payload.agent_id = cfg.agentId;
806
+ if (cfg.appId) payload.app_id = cfg.appId;
807
+ if (sessionId) payload.run_id = sessionId;
799
808
  let resp;
800
809
  try {
801
810
  resp = await client.postJson("/v3/memories/add/", payload, { timeoutMs });
@@ -816,6 +825,12 @@ async function runCapture(messages, cfg, client, timeoutMs, lastFingerprint) {
816
825
  };
817
826
  }
818
827
 
828
+ // src/hook-env.ts
829
+ var CTXDB_SKIP_HOOKS = "CTXDB_SKIP_HOOKS";
830
+ function shouldSkipHooks(env = process.env) {
831
+ return env[CTXDB_SKIP_HOOKS] === "TRUE";
832
+ }
833
+
819
834
  // src/hooks.ts
820
835
  var SESSION_TTL_MS = 15 * 60 * 1e3;
821
836
  var SESSION_MAX = 100;
@@ -861,6 +876,7 @@ function extractTextPrompt(parts) {
861
876
  return chunks.join("\n").trim();
862
877
  }
863
878
  async function buildHooks(input) {
879
+ if (shouldSkipHooks()) return {};
864
880
  const config = loadOpencodeConfig();
865
881
  if (!isConfigured(config)) {
866
882
  logDebug(
@@ -944,7 +960,8 @@ async function buildHooks(input) {
944
960
  rt.config,
945
961
  rt.http,
946
962
  CAPTURE_TIMEOUT_MS,
947
- state.lastFingerprint
963
+ state.lastFingerprint,
964
+ sessionID
948
965
  );
949
966
  if (outcome.captured && outcome.fingerprint) {
950
967
  state.lastFingerprint = outcome.fingerprint;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aliyunrds/ctxdb",
3
- "version": "1.0.0",
3
+ "version": "1.0.1-beta.2",
4
4
  "type": "module",
5
5
  "description": "Unified access layer for RDS ContextDatabase: `ctxdb` CLI (memory + KB ops), one-shot `setup --agent <qoder|qoderwork|codex|claude|opencode>` installer, per-agent config, hooks/plugins, and SKILL.md.",
6
6
  "license": "Apache-2.0",