@aliyunrds/ctxdb 1.0.8-beta.4 → 1.0.9-beta.0

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.
@@ -4,16 +4,11 @@ import {
4
4
  } from "./chunk-5ITT5GSP.js";
5
5
 
6
6
  // src/lib/credentials.ts
7
+ import { selectDataApiCredential } from "@aliyunrds/ctxdb-shared";
7
8
  function apiKeyCredential(apiKey) {
8
9
  return { type: "api-key", value: apiKey };
9
10
  }
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
- }
11
+ var resolveDataApiCredential = selectDataApiCredential;
17
12
  function isDataApiReady(cfg, env = process.env) {
18
13
  return Boolean(cfg.baseUrl && resolveDataApiCredential(cfg, env));
19
14
  }
@@ -84,7 +79,8 @@ var PACKAGE_NAME = PACKAGE_IDENTITY.name;
84
79
  var PACKAGE_VERSION = PACKAGE_IDENTITY.version;
85
80
 
86
81
  // src/lib/http-client.ts
87
- import { OAuthProfileProvider } from "@aliyunrds/ctxdb-shared";
82
+ import { createRequestAuthorizationProvider } from "@aliyunrds/ctxdb-shared";
83
+ import "@aliyunrds/ctxdb-shared";
88
84
  var DEFAULT_TIMEOUT_MS = 3e4;
89
85
  var CtxdbError = class extends Error {
90
86
  constructor(message) {
@@ -178,7 +174,7 @@ var HttpClient = class {
178
174
  this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
179
175
  this.credential = credential;
180
176
  this.apiKey = opts.apiKey ?? "";
181
- this.authorizationProvider = opts.authorizationProvider ?? (credential?.type === "oauth-session" ? new OAuthProfileProvider(credential.profile) : void 0);
177
+ this.authorizationProvider = opts.authorizationProvider ?? createRequestAuthorizationProvider(credential, this.baseUrl);
182
178
  this.timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
183
179
  this.userAgent = opts.userAgent ?? `ctxdb-cli/${PACKAGE_VERSION}`;
184
180
  this.extraHeaders = { ...opts.extraHeaders ?? {} };
@@ -201,9 +197,10 @@ var HttpClient = class {
201
197
  return h;
202
198
  }
203
199
  async doRequest(method, path, init = {}) {
200
+ const effectiveTimeout = init.timeoutMs ?? this.timeoutMs, started = Date.now();
204
201
  let authorization;
205
202
  if (this.authorizationProvider) {
206
- authorization = await this.authorizationProvider.resolve();
203
+ authorization = await this.authorizationProvider.resolve({ timeoutMs: effectiveTimeout });
207
204
  } else {
208
205
  const credential = this.credential;
209
206
  if (!credential || credential.type === "oauth-session") throw new CtxdbError("DATA API credential is required");
@@ -216,7 +213,7 @@ var HttpClient = class {
216
213
  }
217
214
  const safeText = (value) => {
218
215
  const text = String(value);
219
- return authorization.scheme === "Bearer" ? text.split(authorization.value).join("[REDACTED]") : text;
216
+ return text.split(authorization.value).join("[REDACTED]");
220
217
  };
221
218
  let url = `${authorization.baseUrl.replace(/\/+$/, "")}${path}`;
222
219
  if (init.params) {
@@ -227,7 +224,6 @@ var HttpClient = class {
227
224
  const s = qs.toString();
228
225
  if (s) url = `${url}?${s}`;
229
226
  }
230
- const effectiveTimeout = init.timeoutMs ?? this.timeoutMs;
231
227
  const dbg = isDebug();
232
228
  let t0 = 0;
233
229
  if (dbg) {
@@ -235,7 +231,7 @@ var HttpClient = class {
235
231
  debug("http", `\u2192 ${method} ${url} (timeout=${effectiveTimeout}ms)`);
236
232
  }
237
233
  const controller = new AbortController();
238
- const timer = setTimeout(() => controller.abort(), effectiveTimeout);
234
+ const timer = setTimeout(() => controller.abort(), Math.max(1, effectiveTimeout - (Date.now() - started)));
239
235
  try {
240
236
  let resp;
241
237
  try {
@@ -264,7 +260,8 @@ var HttpClient = class {
264
260
  }
265
261
  let text;
266
262
  try {
267
- text = safeText(await resp.text());
263
+ const bodyText = await resp.text();
264
+ text = authorization.scheme === "Bearer" || !resp.ok ? safeText(bodyText) : bodyText;
268
265
  } catch (err) {
269
266
  if (err?.name === "AbortError") {
270
267
  if (dbg) debug("http", `\u2717 ${method} ${path} body-read timeout after ${Date.now() - t0}ms`);
@@ -459,7 +456,8 @@ function agentHomeDir(agent, home = homedir2()) {
459
456
  var AGENT_VARIANT_HOMES = {
460
457
  qoder: [".qoder-cn"],
461
458
  qoderwork: [".qoderworkcn"],
462
- qwenwork: [".qwenworkcn"]
459
+ qwenwork: [".qwenworkcn"],
460
+ workbuddy: [".workbuddy"]
463
461
  };
464
462
  function agentHomeDirs(agent, home = homedir2()) {
465
463
  const primary = agentHomeDir(agent, home);
@@ -605,9 +603,10 @@ function agentFromArgvWithFallback(argv = process.argv.slice(2), env = process.e
605
603
  }
606
604
 
607
605
  // src/lib/config.ts
608
- import { readFileSync as readFileSync3, existsSync as existsSync4, unlinkSync as unlinkSync3 } from "fs";
609
- import { homedir as homedir4 } from "os";
610
- import { dirname as dirname5, join as join5 } from "path";
606
+ import { publicAuthenticationFromConfig } from "@aliyunrds/ctxdb-shared";
607
+ import { readFileSync as readFileSync2, existsSync as existsSync3, unlinkSync as unlinkSync2 } from "fs";
608
+ import { homedir as homedir3 } from "os";
609
+ import { dirname as dirname4, join as join4 } from "path";
611
610
 
612
611
  // src/lib/secure-file.ts
613
612
  import {
@@ -707,146 +706,39 @@ function secureAtomicWrite(target, content, options = {}) {
707
706
  // src/lib/secrets.ts
708
707
  import { createHmac, randomBytes as randomBytes2 } from "crypto";
709
708
  var PROCESS_FINGERPRINT_KEY = randomBytes2(32);
710
-
711
- // src/credentials/local-credential-provider.ts
712
- import {
713
- closeSync as closeSync2,
714
- existsSync as existsSync3,
715
- mkdirSync as mkdirSync3,
716
- openSync as openSync2,
717
- readFileSync as readFileSync2,
718
- statSync as statSync2,
719
- unlinkSync as unlinkSync2,
720
- writeFileSync as writeFileSync2
721
- } from "fs";
722
- import { homedir as homedir3 } from "os";
723
- import { dirname as dirname4, join as join4 } from "path";
724
- import { setTimeout as delay } from "timers/promises";
725
-
726
- // src/credentials/types.ts
727
- var ACTIVE_CONTEXTDB_CREDENTIAL = "contextdb/active";
728
-
729
- // src/credentials/local-credential-provider.ts
730
- var DOCUMENT_VERSION = 1;
731
- var STALE_LOCK_MS = 10 * 60 * 1e3;
732
- function defaultCredentialsPath() {
733
- return join4(homedir3(), ".ctxdb", "credentials.json");
709
+ function maskApiKey(apiKey) {
710
+ if (!apiKey) return "";
711
+ const payload = apiKey.startsWith("ctxdb-") ? apiKey.slice("ctxdb-".length) : null;
712
+ if (payload && payload.length > 12) return `ctxdb-xxxxxx${payload.slice(-6)}`;
713
+ if (payload !== null) return "x".repeat(apiKey.length);
714
+ if (apiKey.length <= 12) return "x".repeat(apiKey.length);
715
+ return `xxxxxx${apiKey.slice(-6)}`;
734
716
  }
735
- function emptyDocument() {
736
- return { version: DOCUMENT_VERSION, records: {} };
737
- }
738
- function normalizeOrigin(value, field) {
739
- if (typeof value !== "string" || !value) {
740
- throw new Error(`credentials: ${field} must be a non-empty URL`);
741
- }
742
- let url;
743
- try {
744
- url = new URL(value);
745
- } catch {
746
- throw new Error(`credentials: ${field} must be a valid URL`);
747
- }
748
- if (!["https:", "http:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) {
749
- throw new Error(`credentials: ${field} must be an HTTP(S) origin`);
750
- }
751
- return url.toString().replace(/\/$/, "");
752
- }
753
- function parseRecord(value) {
754
- if (!value || typeof value !== "object" || Array.isArray(value)) {
755
- throw new Error("credentials: record must be an object");
756
- }
757
- const raw = value;
758
- if (raw.kind !== "api-key") {
759
- throw new Error(`credentials: unsupported record kind ${String(raw.kind)}`);
760
- }
761
- const payload = raw.payload;
762
- const metadata = raw.metadata;
763
- if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
764
- throw new Error("credentials: api-key payload must be an object");
765
- }
766
- if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
767
- throw new Error("credentials: api-key metadata must be an object");
768
- }
769
- const body = payload;
770
- const meta = metadata;
771
- if (typeof body.api_key !== "string" || !body.api_key) {
772
- throw new Error("credentials: api_key must be a non-empty string");
773
- }
774
- if (meta.authorization_method !== "browser-loopback" && meta.authorization_method !== "device-code") {
775
- throw new Error("credentials: authorization_method is invalid");
776
- }
777
- if (typeof meta.issued_at !== "string" || !Number.isFinite(Date.parse(meta.issued_at))) {
778
- throw new Error("credentials: issued_at is invalid");
779
- }
780
- return {
781
- kind: "api-key",
782
- payload: {
783
- apiKey: body.api_key,
784
- baseUrl: normalizeOrigin(body.base_url, "base_url"),
785
- loginServer: normalizeOrigin(body.login_server, "login_server")
786
- },
787
- metadata: {
788
- authorizationMethod: meta.authorization_method,
789
- issuedAt: meta.issued_at
790
- }
791
- };
792
- }
793
- function parseDocument(text) {
794
- let raw;
795
- try {
796
- raw = JSON.parse(text);
797
- } catch {
798
- throw new Error("credentials: credentials.json is not valid JSON");
799
- }
800
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
801
- throw new Error("credentials: document must be an object");
802
- }
803
- const document = raw;
804
- if (document.version !== DOCUMENT_VERSION) {
805
- throw new Error(`credentials: unsupported document version ${String(document.version)}`);
806
- }
807
- if (!document.records || typeof document.records !== "object" || Array.isArray(document.records)) {
808
- throw new Error("credentials: records must be an object");
809
- }
810
- const records = document.records;
811
- const unknown = Object.keys(records).filter((key) => key !== ACTIVE_CONTEXTDB_CREDENTIAL);
812
- if (unknown.length > 0) {
813
- throw new Error(`credentials: unsupported record key ${unknown[0]}`);
814
- }
815
- const active = records[ACTIVE_CONTEXTDB_CREDENTIAL];
816
- return {
817
- version: DOCUMENT_VERSION,
818
- records: active === void 0 ? {} : { [ACTIVE_CONTEXTDB_CREDENTIAL]: parseRecord(active) }
819
- };
820
- }
821
- function assertOwnerOnly(path) {
822
- if (process.platform === "win32" || !existsSync3(path)) return;
823
- const mode = statSync2(path).mode & 511;
824
- if ((mode & 63) !== 0) {
825
- throw new Error(`credentials: ${path} must be owner-only (run chmod 600)`);
826
- }
827
- }
828
- function readDocument(path) {
829
- if (!existsSync3(path)) return emptyDocument();
830
- assertOwnerOnly(path);
831
- return parseDocument(readFileSync2(path, "utf8"));
832
- }
833
- function readActiveCredentialSync(path = defaultCredentialsPath()) {
834
- return readDocument(path).records[ACTIVE_CONTEXTDB_CREDENTIAL];
717
+ function credentialFingerprint(apiKey, processKey = PROCESS_FINGERPRINT_KEY) {
718
+ if (!apiKey) return "";
719
+ return createHmac("sha256", processKey).update(apiKey).digest("hex").slice(0, 12);
835
720
  }
836
721
 
722
+ // src/credentials/local-credential-provider.ts
723
+ import { LocalCredentialProvider, defaultCredentialsPath, readActiveCredentialSync, withAuthorizationLock, internalCandidate } from "@aliyunrds/ctxdb-shared";
724
+
837
725
  // src/lib/config.ts
838
726
  import {
839
727
  resolveDebugPolicy,
840
- oauthProfile
728
+ oauthProfile,
729
+ validateLoginChoice,
730
+ authSnapshot,
731
+ assertAuthSnapshot,
732
+ withAuthConfigLock
841
733
  } from "@aliyunrds/ctxdb-shared";
842
734
  function defaultConfigPath(env = process.env) {
843
- return env.CTXDB_CONFIG_PATH || join5(homedir4(), ".ctxdb", "ctxdb.json");
735
+ return env.CTXDB_CONFIG_PATH || join4(homedir3(), ".ctxdb", "ctxdb.json");
844
736
  }
845
- var DEFAULT_CONFIG_PATH = join5(homedir4(), ".ctxdb", "ctxdb.json");
737
+ var DEFAULT_CONFIG_PATH = join4(homedir3(), ".ctxdb", "ctxdb.json");
846
738
  function configDir() {
847
- return join5(homedir4(), ".ctxdb");
739
+ return join4(homedir3(), ".ctxdb");
848
740
  }
849
- var DEFAULT_BASE_URL = "https://context-database.aliyuncs.com";
741
+ var DEFAULT_BASE_URL = "https://api.cn-hangzhou.agentcontext.aliyuncs.com";
850
742
  var DEFAULT_USER_ID = "default";
851
743
  var DEFAULT_TOP_K = 5;
852
744
  var DEFAULT_THRESHOLD = 0.4;
@@ -878,9 +770,9 @@ function coerceKbCatalogInjection(v) {
878
770
  return DEFAULT_KB_CATALOG_INJECTION;
879
771
  }
880
772
  function readRaw(path) {
881
- if (!existsSync4(path)) return {};
773
+ if (!existsSync3(path)) return {};
882
774
  try {
883
- const parsed = JSON.parse(readFileSync3(path, "utf-8"));
775
+ const parsed = JSON.parse(readFileSync2(path, "utf-8"));
884
776
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
885
777
  return parsed;
886
778
  }
@@ -888,6 +780,99 @@ function readRaw(path) {
888
780
  }
889
781
  return {};
890
782
  }
783
+ function asRawObject(value) {
784
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
785
+ }
786
+ function explicitAgentRaw(raw, agent) {
787
+ const agents = asRawObject(raw.agents);
788
+ return agents ? asRawObject(agents[agent]) : null;
789
+ }
790
+ function explicitString(raw, key) {
791
+ const value = raw?.[key];
792
+ return typeof value === "string" && value ? value : null;
793
+ }
794
+ function inspectConfig(options = {}) {
795
+ const path = options.path ?? defaultConfigPath();
796
+ const env = options.env ?? process.env;
797
+ if (!existsSync3(path)) {
798
+ return {
799
+ path,
800
+ state: "missing",
801
+ profiles: diagnosticProfiles({}, env),
802
+ unknownProfiles: [],
803
+ safeMessage: null
804
+ };
805
+ }
806
+ let parsed;
807
+ try {
808
+ parsed = JSON.parse(readFileSync2(path, "utf-8"));
809
+ } catch {
810
+ return {
811
+ path,
812
+ state: "malformed-json",
813
+ profiles: diagnosticProfiles({}, env),
814
+ unknownProfiles: [],
815
+ safeMessage: "Configuration contains malformed JSON and requires confirmation before repair."
816
+ };
817
+ }
818
+ const raw = asRawObject(parsed);
819
+ if (!raw || !isV2Schema(raw)) {
820
+ return {
821
+ path,
822
+ state: "unsupported-schema",
823
+ profiles: diagnosticProfiles({}, env),
824
+ unknownProfiles: [],
825
+ safeMessage: "Configuration schema is unsupported and requires confirmation before repair."
826
+ };
827
+ }
828
+ const agents = asRawObject(raw.agents) ?? {};
829
+ const known = /* @__PURE__ */ new Set(["default", ...SUPPORTED_AGENTS]);
830
+ return {
831
+ path,
832
+ state: "valid-v2",
833
+ profiles: diagnosticProfiles(raw, env),
834
+ unknownProfiles: Object.keys(agents).filter((name) => !known.has(name)),
835
+ safeMessage: null
836
+ };
837
+ }
838
+ function diagnosticProfiles(raw, env) {
839
+ const profiles = ["default", ...SUPPORTED_AGENTS];
840
+ return profiles.map((agent) => {
841
+ const explicit = explicitAgentRaw(raw, agent);
842
+ const inherited = agent === "default" ? null : explicitAgentRaw(raw, "default");
843
+ const explicitKey = explicitString(explicit, "api_key");
844
+ const inheritedKey = explicitString(inherited, "api_key");
845
+ const apiKey = env.CTXDB_API_KEY || explicitKey || inheritedKey;
846
+ const credentialSource = env.CTXDB_API_KEY ? "environment" : explicitKey ? "profile" : inheritedKey ? "default" : "none";
847
+ const credentialProfile = credentialSource === "profile" ? agent : credentialSource === "default" ? "default" : null;
848
+ const explicitBase = explicitString(explicit, "base_url");
849
+ const inheritedBase = explicitString(inherited, "base_url");
850
+ const baseUrl = (env.CTXDB_BASE_URL || explicitBase || inheritedBase || DEFAULT_BASE_URL).replace(/\/+$/, "");
851
+ const baseUrlSource = env.CTXDB_BASE_URL ? "environment" : explicitBase ? "profile" : inheritedBase ? "default" : "built-in";
852
+ const explicitUser = explicitString(explicit, "user_id");
853
+ const inheritedUser = explicitString(inherited, "user_id");
854
+ const userId = env.CTXDB_USER_ID || explicitUser || inheritedUser || DEFAULT_USER_ID;
855
+ const userIdSource = env.CTXDB_USER_ID ? "environment" : explicitUser ? "profile" : inheritedUser ? "default" : "built-in";
856
+ return {
857
+ agent,
858
+ explicitProfile: explicit !== null,
859
+ explicitFields: explicit ? Object.keys(explicit).sort() : [],
860
+ credential: {
861
+ source: credentialSource,
862
+ profile: credentialProfile,
863
+ masked: apiKey ? maskApiKey(apiKey) : null,
864
+ fingerprint: apiKey ? credentialFingerprint(apiKey) : null
865
+ },
866
+ effective: {
867
+ baseUrl,
868
+ baseUrlSource,
869
+ userId,
870
+ userIdSource,
871
+ complete: Boolean(apiKey && baseUrl)
872
+ }
873
+ };
874
+ });
875
+ }
891
876
  function agentRawFromFile(raw, agent) {
892
877
  const agents = raw.agents;
893
878
  if (agents && typeof agents === "object" && !Array.isArray(agents)) {
@@ -955,20 +940,32 @@ function load(options = {}) {
955
940
  }
956
941
  const cfg2 = configFromDisk({});
957
942
  const managed2 = !DISTRIBUTION_MANIFEST.capabilities.managedCredentials || options.managedCredentials === false ? void 0 : readActiveCredentialSync(
958
- options.credentialsPath ?? join5(dirname5(path), "credentials.json")
943
+ options.credentialsPath ?? join4(dirname4(path), "credentials.json")
959
944
  );
960
945
  if (managed2) {
961
946
  cfg2.apiKey = managed2.payload.apiKey;
962
- cfg2.baseUrl = managed2.payload.baseUrl;
947
+ cfg2.baseUrl = managed2.metadata.baseUrl;
963
948
  }
964
- return applyEnv(cfg2, env);
949
+ applyEnv(cfg2, env);
950
+ if (managed2) cfg2.authContext = {
951
+ configPath: path,
952
+ agent,
953
+ baseUrl: cfg2.baseUrl,
954
+ inheritDefaultKey: true,
955
+ managedCredentials: true,
956
+ credentialsPath: options.credentialsPath,
957
+ ...env.CTXDB_BASE_URL ? { baseUrlOverride: env.CTXDB_BASE_URL } : {}
958
+ };
959
+ return cfg2;
965
960
  }
966
- const agentRaw = agentRawFromFile(raw, agent);
961
+ const explicit = agentRawFromFile(raw, agent);
962
+ if (DISTRIBUTION_MANIFEST.capabilities.deviceFlow) validateLoginChoice(explicit);
963
+ const agentRaw = { ...explicit };
967
964
  if (agent !== "default") {
968
965
  const defaultRaw = agentRawFromFile(raw, "default");
969
966
  if (Object.keys(defaultRaw).length > 0) {
970
967
  for (const [k, v] of Object.entries(defaultRaw)) {
971
- if (k !== "oauth_credential_ref" && !(k in agentRaw)) {
968
+ if (k !== "credential_ref" && k !== "oauth_credential_ref" && k !== "access_credential" && !(k in agentRaw)) {
972
969
  agentRaw[k] = v;
973
970
  }
974
971
  }
@@ -976,23 +973,62 @@ function load(options = {}) {
976
973
  }
977
974
  const cfg = configFromDisk(agentRaw);
978
975
  const managed = !DISTRIBUTION_MANIFEST.capabilities.managedCredentials || options.managedCredentials === false ? void 0 : readActiveCredentialSync(
979
- options.credentialsPath ?? join5(dirname5(path), "credentials.json")
976
+ options.credentialsPath ?? join4(dirname4(path), "credentials.json")
980
977
  );
981
978
  if (managed) {
982
979
  cfg.apiKey = managed.payload.apiKey;
983
- cfg.baseUrl = managed.payload.baseUrl;
980
+ cfg.baseUrl = managed.metadata.baseUrl;
984
981
  }
982
+ const environmentCredential = !!(env.CTXDB_API_KEY || env.CTXDB_ACCESS_TOKEN?.trim());
983
+ if (DISTRIBUTION_MANIFEST.capabilities.deviceFlow) {
984
+ cfg.credentialRef = explicit.credential_ref;
985
+ const stored = environmentCredential ? void 0 : publicAuthenticationFromConfig({ ...raw, version: 2, agents: raw.agents ?? {} }, path, agent, env.CTXDB_BASE_URL);
986
+ if (stored) {
987
+ cfg.baseUrl = stored.baseUrl;
988
+ cfg.apiKey = stored.apiKey ?? null;
989
+ cfg.oauthCredential = stored.oauthCredential;
990
+ } else if (cfg.credentialRef) cfg.apiKey = null;
991
+ if (environmentCredential && explicit.oauth_credential_ref) cfg.oauthCredential = oauthProfile(explicit.oauth_credential_ref, path, agent, cfg.baseUrl);
992
+ }
993
+ const persistedApiKey = cfg.apiKey;
985
994
  applyEnv(cfg, env);
986
995
  if (DISTRIBUTION_MANIFEST.capabilities.deviceFlow) {
987
- cfg.oauthCredential = oauthProfile(agentRaw.oauth_credential_ref, path, agent, cfg.baseUrl);
996
+ const authRaw = { ...raw, version: 2, agents: raw.agents ?? {} };
997
+ if (Object.hasOwn(explicit, "access_credential")) {
998
+ cfg.accessCredential = explicit.access_credential;
999
+ if (!env.CTXDB_API_KEY) cfg.apiKey = null;
1000
+ }
1001
+ if (env.CTXDB_API_KEY) {
1002
+ cfg.environmentApiKey = env.CTXDB_API_KEY;
1003
+ cfg.persistedApiKey = cfg.accessCredential !== void 0 || cfg.oauthCredential ? null : persistedApiKey;
1004
+ }
1005
+ cfg.authSnapshot = authSnapshot(authRaw, agent);
1006
+ cfg.authAgent = agent;
1007
+ cfg.authContext = {
1008
+ configPath: path,
1009
+ agent,
1010
+ baseUrl: cfg.baseUrl,
1011
+ inheritDefaultKey: true,
1012
+ ...env.CTXDB_BASE_URL ? { baseUrlOverride: env.CTXDB_BASE_URL } : {}
1013
+ };
988
1014
  }
1015
+ if (managed) cfg.authContext = {
1016
+ configPath: path,
1017
+ agent,
1018
+ baseUrl: cfg.baseUrl,
1019
+ inheritDefaultKey: true,
1020
+ managedCredentials: true,
1021
+ credentialsPath: options.credentialsPath,
1022
+ ...env.CTXDB_BASE_URL ? { baseUrlOverride: env.CTXDB_BASE_URL } : {}
1023
+ };
989
1024
  return cfg;
990
1025
  }
991
1026
  function configToDisk(cfg) {
992
1027
  return {
993
- ...cfg.oauthCredential ? { oauth_credential_ref: cfg.oauthCredential.reference } : {},
994
- api_key: cfg.oauthCredential ? null : cfg.apiKey,
995
- base_url: cfg.baseUrl,
1028
+ ...cfg.credentialRef ? { credential_ref: cfg.credentialRef } : {},
1029
+ ...cfg.accessCredential !== void 0 ? { access_credential: cfg.accessCredential } : cfg.oauthCredential ? { oauth_credential_ref: cfg.oauthCredential.reference } : {},
1030
+ api_key: cfg.credentialRef || cfg.accessCredential !== void 0 || cfg.oauthCredential ? null : cfg.environmentApiKey && cfg.apiKey === cfg.environmentApiKey ? cfg.persistedApiKey ?? null : cfg.apiKey,
1031
+ ...!cfg.credentialRef && cfg.accessCredential === void 0 && !cfg.oauthCredential && !cfg.authContext?.managedCredentials ? { base_url: cfg.baseUrl } : {},
996
1032
  user_id: cfg.userId,
997
1033
  agent_id: cfg.agentId,
998
1034
  app_id: cfg.appId,
@@ -1009,53 +1045,85 @@ function configToDisk(cfg) {
1009
1045
  }
1010
1046
  function removeAgent(agent, path, options = {}) {
1011
1047
  const target = path ?? defaultConfigPath();
1012
- if (!existsSync4(target)) {
1013
- return { removed: false, remainingAgents: [], fileDeleted: false };
1014
- }
1015
- const raw = readRaw(target);
1016
- if (!isV2Schema(raw) || !raw.agents || typeof raw.agents !== "object" || Array.isArray(raw.agents)) {
1017
- return { removed: false, remainingAgents: [], fileDeleted: false };
1018
- }
1019
- const agents = { ...raw.agents };
1020
- if (!(agent in agents)) {
1021
- return {
1022
- removed: false,
1023
- remainingAgents: Object.keys(agents),
1024
- fileDeleted: false
1025
- };
1026
- }
1027
- delete agents[agent];
1028
- const remaining = Object.keys(agents);
1029
- if (remaining.length === 0 && !options.keepEmptyShell) {
1030
- try {
1031
- unlinkSync3(target);
1032
- return { removed: true, remainingAgents: [], fileDeleted: true };
1033
- } catch {
1048
+ return withAuthConfigLock(target, () => {
1049
+ if (!existsSync3(target)) {
1050
+ return { removed: false, remainingAgents: [], fileDeleted: false };
1034
1051
  }
1035
- }
1036
- const onDisk = { ...raw, version: 2, agents };
1037
- delete onDisk.default_agent;
1038
- secureAtomicWrite(target, JSON.stringify(onDisk, null, 2) + "\n");
1039
- return { removed: true, remainingAgents: remaining, fileDeleted: false };
1052
+ const raw = readRaw(target);
1053
+ if (!isV2Schema(raw) || !raw.agents || typeof raw.agents !== "object" || Array.isArray(raw.agents)) {
1054
+ return { removed: false, remainingAgents: [], fileDeleted: false };
1055
+ }
1056
+ const agents = { ...raw.agents };
1057
+ if (!(agent in agents)) {
1058
+ return {
1059
+ removed: false,
1060
+ remainingAgents: Object.keys(agents),
1061
+ fileDeleted: false
1062
+ };
1063
+ }
1064
+ delete agents[agent];
1065
+ const remaining = Object.keys(agents);
1066
+ if (remaining.length === 0 && !options.keepEmptyShell && !Object.keys(asRawObject(raw.logins) ?? {}).length) {
1067
+ try {
1068
+ unlinkSync2(target);
1069
+ return { removed: true, remainingAgents: [], fileDeleted: true };
1070
+ } catch {
1071
+ }
1072
+ }
1073
+ const onDisk = { ...raw, version: 2, agents };
1074
+ delete onDisk.default_agent;
1075
+ secureAtomicWrite(target, JSON.stringify(onDisk, null, 2) + "\n");
1076
+ return { removed: true, remainingAgents: remaining, fileDeleted: false };
1077
+ });
1040
1078
  }
1041
1079
  function save(cfg, path, options = {}) {
1042
1080
  const target = path ?? defaultConfigPath();
1043
1081
  const agent = resolveConfigAgent(options);
1044
- const raw = readRaw(target);
1045
- const validRaw = isV2Schema(raw) ? raw : {};
1046
- const existingAgents = validRaw.agents && typeof validRaw.agents === "object" && !Array.isArray(validRaw.agents) ? { ...validRaw.agents } : {};
1047
- const serialized = configToDisk(cfg);
1048
- if (options.omitApiKey) delete serialized.api_key;
1049
- const onDisk = {
1050
- ...validRaw,
1051
- version: 2,
1052
- agents: {
1053
- ...existingAgents,
1054
- [agent]: serialized
1082
+ withAuthConfigLock(target, () => {
1083
+ const raw = readRaw(target);
1084
+ const validRaw = isV2Schema(raw) ? raw : {};
1085
+ if (DISTRIBUTION_MANIFEST.capabilities.deviceFlow) {
1086
+ const authRaw = { ...validRaw, version: 2, agents: validRaw.agents ?? {} };
1087
+ if (cfg.authSnapshot && cfg.authAgent === agent) assertAuthSnapshot(authRaw, agent, cfg.authSnapshot);
1055
1088
  }
1056
- };
1057
- delete onDisk.default_agent;
1058
- secureAtomicWrite(target, JSON.stringify(onDisk, null, 2) + "\n");
1089
+ const existingAgents = validRaw.agents && typeof validRaw.agents === "object" && !Array.isArray(validRaw.agents) ? { ...validRaw.agents } : {};
1090
+ const serialized = configToDisk(cfg);
1091
+ if (options.omitApiKey) delete serialized.api_key;
1092
+ const previous = asRawObject(existingAgents[agent]) ?? {};
1093
+ const next = { ...previous, ...serialized };
1094
+ if (cfg.credentialRef) {
1095
+ delete next.access_credential;
1096
+ delete next.oauth_credential_ref;
1097
+ delete next.api_key;
1098
+ } else if (cfg.accessCredential !== void 0) {
1099
+ delete next.credential_ref;
1100
+ delete next.oauth_credential_ref;
1101
+ delete next.api_key;
1102
+ } else if (cfg.oauthCredential) {
1103
+ delete next.access_credential;
1104
+ next.api_key = null;
1105
+ } else {
1106
+ delete next.access_credential;
1107
+ delete next.oauth_credential_ref;
1108
+ }
1109
+ if (options.omitApiKey) {
1110
+ delete next.api_key;
1111
+ for (const field of ["access_credential", "credential_ref", "oauth_credential_ref"]) {
1112
+ if (Object.hasOwn(previous, field)) next[field] = previous[field];
1113
+ }
1114
+ }
1115
+ if (next.credential_ref || Object.hasOwn(next, "access_credential") || next.oauth_credential_ref || cfg.authContext?.managedCredentials) delete next.base_url;
1116
+ const onDisk = {
1117
+ ...validRaw,
1118
+ version: 2,
1119
+ agents: {
1120
+ ...existingAgents,
1121
+ [agent]: next
1122
+ }
1123
+ };
1124
+ delete onDisk.default_agent;
1125
+ secureAtomicWrite(target, JSON.stringify(onDisk, null, 2) + "\n");
1126
+ });
1059
1127
  }
1060
1128
  function configuredAgents(path) {
1061
1129
  const target = path ?? defaultConfigPath();
@@ -1075,21 +1143,23 @@ function hasConfiguredAgent(agent, path) {
1075
1143
  }
1076
1144
  function updateConfiguredDebug(agent, configured, path) {
1077
1145
  const target = path ?? defaultConfigPath();
1078
- const raw = readRaw(target);
1079
- if (!isV2Schema(raw) || !raw.agents || typeof raw.agents !== "object" || Array.isArray(raw.agents)) {
1080
- return false;
1081
- }
1082
- const agents = { ...raw.agents };
1083
- const profile = agents[agent];
1084
- if (!profile || typeof profile !== "object" || Array.isArray(profile)) {
1085
- return false;
1086
- }
1087
- agents[agent] = { ...profile, debug: configured };
1088
- secureAtomicWrite(
1089
- target,
1090
- JSON.stringify({ ...raw, agents }, null, 2) + "\n"
1091
- );
1092
- return true;
1146
+ return withAuthConfigLock(target, () => {
1147
+ const raw = readRaw(target);
1148
+ if (!isV2Schema(raw) || !raw.agents || typeof raw.agents !== "object" || Array.isArray(raw.agents)) {
1149
+ return false;
1150
+ }
1151
+ const agents = { ...raw.agents };
1152
+ const profile = agents[agent];
1153
+ if (!profile || typeof profile !== "object" || Array.isArray(profile)) {
1154
+ return false;
1155
+ }
1156
+ agents[agent] = { ...profile, debug: configured };
1157
+ secureAtomicWrite(
1158
+ target,
1159
+ JSON.stringify({ ...raw, agents }, null, 2) + "\n"
1160
+ );
1161
+ return true;
1162
+ });
1093
1163
  }
1094
1164
  function writeInstalledPkgVersion(version, path) {
1095
1165
  const target = path ?? defaultConfigPath();
@@ -1120,11 +1190,11 @@ export {
1120
1190
  detectInstalledAgents,
1121
1191
  agentFromEnv,
1122
1192
  agentFromArgvWithFallback,
1123
- secureAtomicWrite,
1124
1193
  defaultConfigPath,
1125
1194
  configDir,
1126
1195
  DEFAULT_BASE_URL,
1127
1196
  DEFAULT_USER_ID,
1197
+ inspectConfig,
1128
1198
  load,
1129
1199
  removeAgent,
1130
1200
  save,