@aliyunrds/ctxdb 1.0.0 → 1.0.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md 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,9 @@ 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
+ `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
133
 
130
134
  ## Architecture
131
135
 
@@ -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,
@@ -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([
@@ -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,7 @@ import {
14
14
  isComplete,
15
15
  load,
16
16
  setDebug
17
- } from "../chunk-QHYJ7OXC.js";
17
+ } from "../chunk-TKMIWM6Q.js";
18
18
 
19
19
  // src/hooks/session-start.ts
20
20
  import { pathToFileURL } from "url";
@@ -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,7 @@ import {
13
13
  isDebug,
14
14
  load,
15
15
  setDebug
16
- } from "../chunk-QHYJ7OXC.js";
16
+ } from "../chunk-TKMIWM6Q.js";
17
17
 
18
18
  // src/lib/capture-orchestrator.ts
19
19
  import {
@@ -304,7 +304,7 @@ function extractSkillSignals(rows) {
304
304
  }
305
305
 
306
306
  // src/lib/capture-orchestrator.ts
307
- async function captureTurn(transcriptPath, cfg, client, agent = "default") {
307
+ async function captureTurn(transcriptPath, cfg, client, agent = "default", sessionId) {
308
308
  if (!cfg.apiKey || !cfg.baseUrl) {
309
309
  return { captured: false, reason: "config_incomplete", messageCount: 0 };
310
310
  }
@@ -373,6 +373,9 @@ async function captureTurn(transcriptPath, cfg, client, agent = "default") {
373
373
  user_id: cfg.userId,
374
374
  async_mode: true
375
375
  };
376
+ if (cfg.agentId) payload.agent_id = cfg.agentId;
377
+ if (cfg.appId) payload.app_id = cfg.appId;
378
+ if (sessionId) payload.run_id = sessionId;
376
379
  let resp;
377
380
  try {
378
381
  resp = await client.postJson("/v3/memories/add/", payload);
@@ -504,6 +507,10 @@ async function readStdinJson() {
504
507
  return {};
505
508
  }
506
509
  }
510
+ function extractHookSessionId(event) {
511
+ const v = event.session_id ?? event.sessionId;
512
+ return typeof v === "string" && v ? v : void 0;
513
+ }
507
514
  async function main() {
508
515
  try {
509
516
  const event = await readStdinJson();
@@ -516,6 +523,7 @@ async function main() {
516
523
  }
517
524
  const transcriptPath = event.transcript_path;
518
525
  if (typeof transcriptPath !== "string" || !transcriptPath) return 0;
526
+ const sessionId = extractHookSessionId(event);
519
527
  const cfg = load({ agent });
520
528
  setDebug(cfg.debug);
521
529
  debug("capture", "start", { transcriptPath, userId: cfg.userId });
@@ -524,7 +532,7 @@ async function main() {
524
532
  return 0;
525
533
  }
526
534
  const client = new HttpClient({ baseUrl: cfg.baseUrl, apiKey: cfg.apiKey, timeoutMs: 5e3 });
527
- const result = await captureTurn(transcriptPath, cfg, client, agent);
535
+ const result = await captureTurn(transcriptPath, cfg, client, agent, sessionId);
528
536
  if (result.captured) {
529
537
  debug("capture", `ok (${result.messageCount} msgs)`, result);
530
538
  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,7 @@ import {
14
14
  isComplete,
15
15
  load,
16
16
  setDebug
17
- } from "../chunk-QHYJ7OXC.js";
17
+ } from "../chunk-TKMIWM6Q.js";
18
18
 
19
19
  // src/hooks/user-prompt-submit.ts
20
20
  import { pathToFileURL } from "url";
@@ -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 });
@@ -944,7 +953,8 @@ async function buildHooks(input) {
944
953
  rt.config,
945
954
  rt.http,
946
955
  CAPTURE_TIMEOUT_MS,
947
- state.lastFingerprint
956
+ state.lastFingerprint,
957
+ sessionID
948
958
  );
949
959
  if (outcome.captured && outcome.fingerprint) {
950
960
  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.1",
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",