@aliyunrds/ctxdb 1.0.8-beta.2 → 1.0.8-beta.4

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
@@ -17,9 +17,42 @@ Config and logs live under `~/.ctxdb/`. `~/.ctxdb/ctxdb.json` is one file, but r
17
17
 
18
18
  ## Install
19
19
 
20
+ ### Public Device Flow login (requires the enabled Core/API phase 2 service)
21
+
22
+ ```sh
23
+ ctxdb login --agent qoder --server https://<core-host> --no-browser
24
+ # Complete the displayed user code through the console/independent POP test client.
25
+ ctxdb auth status --agent qoder --json
26
+ ctxdb setup --agent qoder
27
+ ctxdb logout --agent qoder --json
28
+ ```
29
+
30
+ Public login uses Core Device Flow only; it never calls POP, starts a localhost callback,
31
+ or uses the internal login server. `--device-code` explicitly selects the same default flow.
32
+ Device Flow always uses `default`, following the current Member permissions and resource ACLs.
33
+ `login` does not accept `--scope`; creation and approval do not send a scope selector.
34
+ Login does not install hooks/skills. Run setup separately for each selected Agent.
35
+
36
+ Each profile contains only an `oauth_credential_ref`; AT/RT live in owner-only files under
37
+ `~/.ctxdb/oauth-credentials/`. `CTXDB_CONFIG_PATH` can select an isolated configuration file;
38
+ the OAuth directory is its sibling. CLI, Hooks and OpenCode share a cross-process, single-flight
39
+ refresh implementation. A lost refresh response or crashed refresh requires a new login, not
40
+ a retry of the old RT. Managed failure/logout never falls back to an API key or another profile.
41
+ Re-login replaces only the selected profile; logout reports remote-revoke failure separately.
42
+ AT/RT must not be copied into Agent/Skill configuration or handed to a sandbox.
43
+
44
+ The optional sibling `device-environments.json` (`version: 1`, `environments: [{core_url,
45
+ authorization_url}]`) maps a Core origin to a deployment-approved HTTPS console page.
46
+ CLI appends only `user_code` to that locally configured URL. There is no server discovery or
47
+ invented default console page. Without a mapping, the code can be used with the independent POP
48
+ test client. This phase never opens a browser automatically; `--no-browser` is supported.
49
+
50
+ POSIX uses private files/directories; Windows validates an owner-only ACL using local PowerShell.
51
+ Native Windows acceptance remains separate from the macOS local test record.
52
+
20
53
  ### Internal browser-login distribution
21
54
 
22
- The same source also builds `@ali/ctxdb-internal`. That artifact alone
55
+ The same source also builds `@ali/ctxdb-internal`. Its existing prototype
23
56
  adds `ctxdb login --server <url>`, `ctxdb login --device-code`, `ctxdb logout`,
24
57
  and `ctxdb auth status`. The default login uses a browser loopback callback;
25
58
  `--device-code` supports remote or headless terminals. Both methods discover a
@@ -31,17 +64,19 @@ Internal login stores its single active connection in owner-only
31
64
  without writing an `api_key` field into the Agent or default profile; an
32
65
  existing field in either updated profile is removed. At runtime this managed
33
66
  credential overrides the API key and DATA URL in the existing v2
34
- `~/.ctxdb/ctxdb.json`; `CTXDB_API_KEY` and `CTXDB_BASE_URL` remain the highest
35
- priority overrides. Re-login replaces the active connection and revokes the
36
- previous key after setup succeeds. The public `@aliyunrds/ctxdb` build does not
37
- contain or advertise these commands and continues to use `ctxdb setup` and the
38
- v2 config exactly as before. Build the internal staging package with
39
- `pnpm build:internal`.
67
+ `~/.ctxdb/ctxdb.json`; `CTXDB_ACCESS_TOKEN`, `CTXDB_API_KEY`, and
68
+ `CTXDB_BASE_URL` remain higher-priority overrides. `ctxdb login` rejects all
69
+ three while `ctxdb logout` rejects the two credential overrides, so command
70
+ output cannot disagree with subsequent DATA requests. Re-login replaces the
71
+ active connection and revokes the previous key after setup succeeds. The
72
+ public `@aliyunrds/ctxdb` build uses the distinct Device Flow implementation above and
73
+ continues to support API-key setup. It never reads the internal credential store. Build the
74
+ internal staging package with `pnpm build:internal`.
40
75
 
41
76
  The distinction is a compile-time distribution manifest, not a runtime
42
77
  environment-variable or file-existence check. The `public` manifest disables
43
- `interactiveLogin` and `managedCredentials`; the `internal` manifest enables
44
- both. The CLI hooks and the separately bundled OpenCode plugin receive the
78
+ internal-protocol flags `interactiveLogin` and `managedCredentials`, and enables `deviceFlow`;
79
+ the `internal` manifest enables the former two and disables `deviceFlow`. The CLI hooks and the separately bundled OpenCode plugin receive the
45
80
  same manifest, so installing a public build on a machine that happens to have
46
81
  `~/.ctxdb/credentials.json` does not make that build consume the file.
47
82
 
@@ -603,7 +638,9 @@ Field reference:
603
638
  | `knowledge_top_k` | int | `6` | KB chunks pulled per recall (only effective when `recall_knowledge: true`) |
604
639
  | `debug` | bool | `false` | Configured preference for verbose hook logging and full recall tracing. Effective debug is forced on for every final `base_url` outside the exact official-production allowlist; see “Debug control and recall replay”. General logs go to `~/.ctxdb/logs/ctxdb.log`, structured full-content recall records to `~/.ctxdb/logs/recall-trace.jsonl` |
605
640
 
606
- 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 internal login command additionally reads `CTXDB_LOGIN_SERVER` as its provider-neutral login endpoint. 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.
641
+ Env-var overrides apply to the selected agent config (env wins): `CTXDB_AGENT` / `CTXDB_ACCESS_TOKEN` / `CTXDB_API_KEY` / `CTXDB_BASE_URL` / `CTXDB_USER_ID` / `CTXDB_AGENT_ID` / `CTXDB_APP_ID`. The internal login command additionally reads `CTXDB_LOGIN_SERVER` as its provider-neutral login endpoint. 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.
642
+
643
+ Downstream Agent hosts can set `CTXDB_ACCESS_TOKEN` to send `Authorization: Bearer <token>` to the Data API. It is runtime-only, never written to `~/.ctxdb/ctxdb.json`, and takes precedence over a managed session or API key for CLI, process hooks, and OpenCode; a rejected token does not fall back to another credential. `ctxdb setup` can use a managed reference or an environment AT for validation, but never persists the AT. An environment-only credential must remain available to the Agent runtime after setup.
607
644
 
608
645
  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.
609
646
 
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  configDir
4
- } from "./chunk-DMS5YHIK.js";
4
+ } from "./chunk-SVO6H62S.js";
5
5
 
6
6
  // src/lib/circuit.ts
7
7
  import { statSync, writeFileSync, unlinkSync, mkdirSync, readdirSync, readFileSync } from "fs";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  define_CTXDB_DISTRIBUTION_MANIFEST_default
4
- } from "./chunk-VIG4SYLU.js";
4
+ } from "./chunk-PI2SUS3M.js";
5
5
 
6
6
  // src/lib/distribution-capabilities.ts
7
7
  var PUBLIC_DISTRIBUTION = {
@@ -10,7 +10,8 @@ var PUBLIC_DISTRIBUTION = {
10
10
  packageRegistry: null,
11
11
  capabilities: {
12
12
  interactiveLogin: false,
13
- managedCredentials: false
13
+ managedCredentials: false,
14
+ deviceFlow: true
14
15
  }
15
16
  };
16
17
  var DISTRIBUTION_MANIFEST = typeof define_CTXDB_DISTRIBUTION_MANIFEST_default === "undefined" ? PUBLIC_DISTRIBUTION : define_CTXDB_DISTRIBUTION_MANIFEST_default;
@@ -2,10 +2,10 @@
2
2
  import {
3
3
  fetchKbCatalogBlock,
4
4
  recallTurn
5
- } from "./chunk-H33NUAHP.js";
5
+ } from "./chunk-WJTV7JNR.js";
6
6
  import {
7
7
  debug
8
- } from "./chunk-DMS5YHIK.js";
8
+ } from "./chunk-SVO6H62S.js";
9
9
 
10
10
  // src/lib/warmup-recall.ts
11
11
  import { execSync } from "child_process";
@@ -4,12 +4,13 @@ import {
4
4
  isConnectionError,
5
5
  resetCircuit,
6
6
  tripCircuit
7
- } from "./chunk-X75B57M2.js";
7
+ } from "./chunk-5DGRNP55.js";
8
8
  import {
9
9
  CtxdbError,
10
10
  debug,
11
+ isDataApiReady,
11
12
  isDebug
12
- } from "./chunk-DMS5YHIK.js";
13
+ } from "./chunk-SVO6H62S.js";
13
14
 
14
15
  // src/lib/capture-orchestrator.ts
15
16
  import {
@@ -398,7 +399,7 @@ async function captureParsedMessages(parsed, cfg, client, agent = "default", ses
398
399
  };
399
400
  }
400
401
  function capturePreflight(cfg, agent) {
401
- if (!cfg.apiKey || !cfg.baseUrl) {
402
+ if (!isDataApiReady(cfg)) {
402
403
  return { captured: false, reason: "config_incomplete", messageCount: 0 };
403
404
  }
404
405
  if (!cfg.autoCapture) {
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  fetchKbCatalogBlock,
4
4
  recallTurn
5
- } from "./chunk-H33NUAHP.js";
5
+ } from "./chunk-WJTV7JNR.js";
6
6
 
7
7
  // src/lib/user-prompt-submit-compose.ts
8
8
  async function composeUserPromptSubmit(cfg, agent, client, prompt, sessionId = null) {
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  DISTRIBUTION_MANIFEST
4
- } from "./chunk-R67JELM7.js";
4
+ } from "./chunk-5ITT5GSP.js";
5
5
 
6
6
  // src/lib/self-update.ts
7
7
  import { spawnSync } from "child_process";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // <define:__CTXDB_DISTRIBUTION_MANIFEST__>
4
- var define_CTXDB_DISTRIBUTION_MANIFEST_default = { id: "public", packageName: "@aliyunrds/ctxdb", packageRegistry: null, capabilities: { interactiveLogin: false, managedCredentials: false } };
4
+ var define_CTXDB_DISTRIBUTION_MANIFEST_default = { id: "public", packageName: "@aliyunrds/ctxdb", packageRegistry: null, capabilities: { interactiveLogin: false, managedCredentials: false, deviceFlow: true } };
5
5
 
6
6
  export {
7
7
  define_CTXDB_DISTRIBUTION_MANIFEST_default
@@ -1,7 +1,22 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  DISTRIBUTION_MANIFEST
4
- } from "./chunk-R67JELM7.js";
4
+ } from "./chunk-5ITT5GSP.js";
5
+
6
+ // src/lib/credentials.ts
7
+ function apiKeyCredential(apiKey) {
8
+ return { type: "api-key", value: apiKey };
9
+ }
10
+ function resolveDataApiCredential(cfg, env = process.env) {
11
+ const accessToken = env.CTXDB_ACCESS_TOKEN?.trim();
12
+ if (accessToken) return { type: "access-token", value: accessToken };
13
+ if (env.CTXDB_API_KEY) return apiKeyCredential(env.CTXDB_API_KEY);
14
+ if (cfg.oauthCredential) return { type: "oauth-session", profile: cfg.oauthCredential };
15
+ return cfg.apiKey ? apiKeyCredential(cfg.apiKey) : null;
16
+ }
17
+ function isDataApiReady(cfg, env = process.env) {
18
+ return Boolean(cfg.baseUrl && resolveDataApiCredential(cfg, env));
19
+ }
5
20
 
6
21
  // src/lib/logger.ts
7
22
  import { appendFileSync, mkdirSync } from "fs";
@@ -69,6 +84,7 @@ var PACKAGE_NAME = PACKAGE_IDENTITY.name;
69
84
  var PACKAGE_VERSION = PACKAGE_IDENTITY.version;
70
85
 
71
86
  // src/lib/http-client.ts
87
+ import { OAuthProfileProvider } from "@aliyunrds/ctxdb-shared";
72
88
  var DEFAULT_TIMEOUT_MS = 3e4;
73
89
  var CtxdbError = class extends Error {
74
90
  constructor(message) {
@@ -82,7 +98,7 @@ var AuthError = class extends CtxdbError {
82
98
  errorMessage;
83
99
  data;
84
100
  responseBody;
85
- constructor(message = "Unauthorized (HTTP 401) \u2014 check api_key", fields = {}) {
101
+ constructor(message = "Unauthorized (HTTP 401) \u2014 check Data API credential", fields = {}) {
86
102
  super(message);
87
103
  this.name = "AuthError";
88
104
  this.errorCode = fields.errorCode;
@@ -147,6 +163,7 @@ var CtxdbHttpError = class extends CtxdbError {
147
163
  };
148
164
  var HttpClient = class {
149
165
  baseUrl;
166
+ credential;
150
167
  apiKey;
151
168
  timeoutMs;
152
169
  userAgent;
@@ -154,12 +171,14 @@ var HttpClient = class {
154
171
  fetchImpl;
155
172
  authorizationProvider;
156
173
  constructor(opts) {
157
- this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
158
- if (!opts.apiKey && !opts.authorizationProvider) {
159
- throw new Error("HttpClient requires apiKey or authorizationProvider");
174
+ const credential = opts.credential ?? (opts.apiKey ? apiKeyCredential(opts.apiKey) : null);
175
+ if (!credential && !opts.authorizationProvider) {
176
+ throw new CtxdbError("DATA API credential is required");
160
177
  }
178
+ this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
179
+ this.credential = credential;
161
180
  this.apiKey = opts.apiKey ?? "";
162
- this.authorizationProvider = opts.authorizationProvider;
181
+ this.authorizationProvider = opts.authorizationProvider ?? (credential?.type === "oauth-session" ? new OAuthProfileProvider(credential.profile) : void 0);
163
182
  this.timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
164
183
  this.userAgent = opts.userAgent ?? `ctxdb-cli/${PACKAGE_VERSION}`;
165
184
  this.extraHeaders = { ...opts.extraHeaders ?? {} };
@@ -174,15 +193,30 @@ var HttpClient = class {
174
193
  ...this.extraHeaders,
175
194
  ...requestHeaders
176
195
  };
196
+ if (authorization.scheme === "Bearer") {
197
+ for (const key of Object.keys(h)) if (key.toLowerCase() === "authorization") delete h[key];
198
+ h.Authorization = `Bearer ${authorization.value}`;
199
+ }
177
200
  if (contentType) h["Content-Type"] = contentType;
178
201
  return h;
179
202
  }
180
203
  async doRequest(method, path, init = {}) {
181
- const authorization = this.authorizationProvider ? await this.authorizationProvider.resolve() : {
182
- scheme: "Token",
183
- value: this.apiKey,
184
- baseUrl: this.baseUrl,
185
- expiresAt: null
204
+ let authorization;
205
+ if (this.authorizationProvider) {
206
+ authorization = await this.authorizationProvider.resolve();
207
+ } else {
208
+ const credential = this.credential;
209
+ if (!credential || credential.type === "oauth-session") throw new CtxdbError("DATA API credential is required");
210
+ authorization = {
211
+ scheme: credential.type === "access-token" ? "Bearer" : "Token",
212
+ value: credential.value,
213
+ baseUrl: this.baseUrl,
214
+ expiresAt: null
215
+ };
216
+ }
217
+ const safeText = (value) => {
218
+ const text = String(value);
219
+ return authorization.scheme === "Bearer" ? text.split(authorization.value).join("[REDACTED]") : text;
186
220
  };
187
221
  let url = `${authorization.baseUrl.replace(/\/+$/, "")}${path}`;
188
222
  if (init.params) {
@@ -213,15 +247,16 @@ var HttpClient = class {
213
247
  init.headers
214
248
  ),
215
249
  body: init.body,
216
- signal: controller.signal
250
+ signal: controller.signal,
251
+ ...authorization.scheme === "Bearer" ? { redirect: "error" } : {}
217
252
  });
218
253
  } catch (err) {
219
254
  if (err?.name === "AbortError") {
220
255
  if (dbg) debug("http", `\u2717 ${method} ${path} timeout after ${Date.now() - t0}ms`);
221
256
  throw new CtxdbError(`request timeout after ${effectiveTimeout}ms: ${url}`);
222
257
  }
223
- if (dbg) debug("http", `\u2717 ${method} ${path} network error after ${Date.now() - t0}ms: ${err?.message ?? err}`);
224
- throw new CtxdbError(`network error contacting ${url}: ${err?.message ?? err}`);
258
+ if (dbg) debug("http", `\u2717 ${method} ${path} network error after ${Date.now() - t0}ms: ${safeText(err?.message ?? err)}`);
259
+ throw new CtxdbError(`network error contacting ${url}: ${safeText(err?.message ?? err)}`);
225
260
  }
226
261
  if (resp.status === 204) {
227
262
  if (dbg) debug("http", `\u2190 ${method} ${path} 204 (${Date.now() - t0}ms)`);
@@ -229,14 +264,14 @@ var HttpClient = class {
229
264
  }
230
265
  let text;
231
266
  try {
232
- text = await resp.text();
267
+ text = safeText(await resp.text());
233
268
  } catch (err) {
234
269
  if (err?.name === "AbortError") {
235
270
  if (dbg) debug("http", `\u2717 ${method} ${path} body-read timeout after ${Date.now() - t0}ms`);
236
271
  throw new CtxdbError(`request timeout after ${effectiveTimeout}ms (body read): ${url}`);
237
272
  }
238
- if (dbg) debug("http", `\u2717 ${method} ${path} body-read error after ${Date.now() - t0}ms: ${err?.message ?? err}`);
239
- throw new CtxdbError(`network error reading body from ${url}: ${err?.message ?? err}`);
273
+ if (dbg) debug("http", `\u2717 ${method} ${path} body-read error after ${Date.now() - t0}ms: ${safeText(err?.message ?? err)}`);
274
+ throw new CtxdbError(`network error reading body from ${url}: ${safeText(err?.message ?? err)}`);
240
275
  }
241
276
  if (!resp.ok) {
242
277
  const parsedError = parseErrorResponse(text, `HTTP ${resp.status}`);
@@ -801,10 +836,11 @@ function readActiveCredentialSync(path = defaultCredentialsPath()) {
801
836
 
802
837
  // src/lib/config.ts
803
838
  import {
804
- resolveDebugPolicy
839
+ resolveDebugPolicy,
840
+ oauthProfile
805
841
  } from "@aliyunrds/ctxdb-shared";
806
- function defaultConfigPath() {
807
- return join5(homedir4(), ".ctxdb", "ctxdb.json");
842
+ function defaultConfigPath(env = process.env) {
843
+ return env.CTXDB_CONFIG_PATH || join5(homedir4(), ".ctxdb", "ctxdb.json");
808
844
  }
809
845
  var DEFAULT_CONFIG_PATH = join5(homedir4(), ".ctxdb", "ctxdb.json");
810
846
  function configDir() {
@@ -816,9 +852,6 @@ var DEFAULT_TOP_K = 5;
816
852
  var DEFAULT_THRESHOLD = 0.4;
817
853
  var DEFAULT_KNOWLEDGE_TOP_K = 6;
818
854
  var DEFAULT_KB_CATALOG_INJECTION = "session_start";
819
- function isComplete(cfg) {
820
- return Boolean(cfg.apiKey && cfg.baseUrl);
821
- }
822
855
  function resolveConfigAgent(options = {}, preloadedRaw) {
823
856
  if (isAgentSlug(options.agent)) return options.agent;
824
857
  return agentFromEnv(options.env);
@@ -908,8 +941,8 @@ function applyEnv(cfg, env) {
908
941
  return applyDebugPolicy(cfg);
909
942
  }
910
943
  function load(options = {}) {
911
- const path = options.path ?? defaultConfigPath();
912
944
  const env = options.env ?? process.env;
945
+ const path = options.path ?? defaultConfigPath(env);
913
946
  const raw = readRaw(path);
914
947
  const agent = resolveConfigAgent({ ...options, path, env }, raw);
915
948
  if (!isV2Schema(raw)) {
@@ -935,7 +968,7 @@ function load(options = {}) {
935
968
  const defaultRaw = agentRawFromFile(raw, "default");
936
969
  if (Object.keys(defaultRaw).length > 0) {
937
970
  for (const [k, v] of Object.entries(defaultRaw)) {
938
- if (!(k in agentRaw)) {
971
+ if (k !== "oauth_credential_ref" && !(k in agentRaw)) {
939
972
  agentRaw[k] = v;
940
973
  }
941
974
  }
@@ -949,11 +982,16 @@ function load(options = {}) {
949
982
  cfg.apiKey = managed.payload.apiKey;
950
983
  cfg.baseUrl = managed.payload.baseUrl;
951
984
  }
952
- return applyEnv(cfg, env);
985
+ applyEnv(cfg, env);
986
+ if (DISTRIBUTION_MANIFEST.capabilities.deviceFlow) {
987
+ cfg.oauthCredential = oauthProfile(agentRaw.oauth_credential_ref, path, agent, cfg.baseUrl);
988
+ }
989
+ return cfg;
953
990
  }
954
991
  function configToDisk(cfg) {
955
992
  return {
956
- api_key: cfg.apiKey,
993
+ ...cfg.oauthCredential ? { oauth_credential_ref: cfg.oauthCredential.reference } : {},
994
+ api_key: cfg.oauthCredential ? null : cfg.apiKey,
957
995
  base_url: cfg.baseUrl,
958
996
  user_id: cfg.userId,
959
997
  agent_id: cfg.agentId,
@@ -1061,6 +1099,8 @@ function writeInstalledPkgVersion(version, path) {
1061
1099
  }
1062
1100
 
1063
1101
  export {
1102
+ resolveDataApiCredential,
1103
+ isDataApiReady,
1064
1104
  setDebug,
1065
1105
  isDebug,
1066
1106
  debug,
@@ -1080,10 +1120,11 @@ export {
1080
1120
  detectInstalledAgents,
1081
1121
  agentFromEnv,
1082
1122
  agentFromArgvWithFallback,
1123
+ secureAtomicWrite,
1124
+ defaultConfigPath,
1083
1125
  configDir,
1084
1126
  DEFAULT_BASE_URL,
1085
1127
  DEFAULT_USER_ID,
1086
- isComplete,
1087
1128
  load,
1088
1129
  removeAgent,
1089
1130
  save,
@@ -1,15 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  listKnowledgeBases
4
- } from "./chunk-CULTDVI2.js";
4
+ } from "./chunk-ZW7YXNJ6.js";
5
5
  import {
6
6
  isConnectionError,
7
7
  resetCircuit,
8
8
  tripCircuit
9
- } from "./chunk-X75B57M2.js";
9
+ } from "./chunk-5DGRNP55.js";
10
10
  import {
11
- CtxdbError
12
- } from "./chunk-DMS5YHIK.js";
11
+ CtxdbError,
12
+ isDataApiReady,
13
+ resolveDataApiCredential
14
+ } from "./chunk-SVO6H62S.js";
13
15
 
14
16
  // src/lib/kb-catalog.ts
15
17
  function sanitizeKeyEntities(raw) {
@@ -152,12 +154,13 @@ async function recallTurn(prompt, cfg, client, agentOrOptions = "default") {
152
154
  const options = typeof agentOrOptions === "string" ? { agent: agentOrOptions } : agentOrOptions;
153
155
  const agent = options.agent ?? "default";
154
156
  if (!prompt.trim()) return { ...EMPTY, reason: "empty_prompt" };
155
- if (!cfg.apiKey || !cfg.baseUrl) {
157
+ if (!isDataApiReady(cfg)) {
156
158
  return { ...EMPTY, reason: "config_incomplete" };
157
159
  }
158
160
  if (!cfg.autoRecall) {
159
161
  return { ...EMPTY, reason: "auto_recall_disabled" };
160
162
  }
163
+ const credential = resolveDataApiCredential(cfg);
161
164
  const query = stripSystemReminders(prompt);
162
165
  const trace = cfg.debug ? {
163
166
  agent,
@@ -175,7 +178,11 @@ async function recallTurn(prompt, cfg, client, agentOrOptions = "default") {
175
178
  },
176
179
  store: {
177
180
  ...options.traceStore,
178
- secrets: [...options.traceStore?.secrets ?? [], cfg.apiKey]
181
+ secrets: [
182
+ ...options.traceStore?.secrets ?? [],
183
+ cfg.apiKey,
184
+ credential.value
185
+ ]
179
186
  }
180
187
  } : null;
181
188
  if (trace) {
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  CtxdbError
4
- } from "./chunk-DMS5YHIK.js";
4
+ } from "./chunk-SVO6H62S.js";
5
5
 
6
6
  // src/lib/kb.ts
7
7
  import {