@aliyunrds/ctxdb 1.0.8-beta.4 → 1.0.8

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.
@@ -1,11 +1,6 @@
1
1
  // <define:__CTXDB_DISTRIBUTION_MANIFEST__>
2
2
  var define_CTXDB_DISTRIBUTION_MANIFEST_default = { id: "public", packageName: "@aliyunrds/ctxdb", packageRegistry: null, capabilities: { interactiveLogin: false, managedCredentials: false, deviceFlow: true } };
3
3
 
4
- // src/config.ts
5
- import { readFileSync as readFileSync4, existsSync as existsSync3, statSync as statSync2 } from "fs";
6
- import { homedir as homedir2 } from "os";
7
- import { dirname as dirname3, join as join5 } from "path";
8
-
9
4
  // ../shared/src/graph-context.ts
10
5
  var GRAPH_CONTEXT_TAG = "graphrag";
11
6
  var GRAPH_CONTEXT_SOURCE_LABEL = "Related content in Knowledge Graph";
@@ -589,6 +584,7 @@ function selectAndFormatRecalledMemories(memories, userId, config = {}) {
589
584
 
590
585
  // ../shared/src/debug-policy.ts
591
586
  var OFFICIAL_PRODUCTION_BASE_URLS = [
587
+ "https://api.cn-hangzhou.agentcontext.aliyuncs.com",
592
588
  "https://context-database.aliyuncs.com"
593
589
  ];
594
590
  function normalizeBaseUrl(value) {
@@ -861,16 +857,21 @@ var CoreDeviceClient = class {
861
857
  baseUrl;
862
858
  fetchImpl;
863
859
  now;
860
+ requestTimeoutMs;
864
861
  sleep;
865
862
  constructor(baseUrl, options = {}) {
866
863
  this.baseUrl = normalizeCoreOrigin(baseUrl);
867
864
  this.fetchImpl = options.fetchImpl ?? globalThis.fetch;
868
865
  this.now = options.now ?? Date.now;
866
+ this.requestTimeoutMs = options.requestTimeoutMs ?? 3e4;
869
867
  this.sleep = options.sleep ?? ((ms, signal) => delay(ms, void 0, { signal }));
870
868
  }
871
869
  async login(options) {
872
870
  const started = this.now();
873
- const initial = await this.post("/v1/auth/device/authorize", { client_id: clientId }, options.signal);
871
+ const initial = await this.post("/v1/auth/device/authorize", {
872
+ client_id: clientId,
873
+ ...options.initialAgent === void 0 ? {} : { initial_agent: options.initialAgent }
874
+ }, options.signal);
874
875
  if (!initial.ok) throw new DeviceFlowError("request_rejected");
875
876
  const data = initial.data;
876
877
  const requestId = text(data.request_id, 32), userCode = text(data.user_code, 9), code = text(data.device_code, 43);
@@ -920,7 +921,7 @@ var CoreDeviceClient = class {
920
921
  const controller = new AbortController();
921
922
  const abort = () => controller.abort();
922
923
  signal?.addEventListener("abort", abort, { once: true });
923
- const timer = setTimeout(abort, 3e4);
924
+ const timer = setTimeout(abort, this.requestTimeoutMs);
924
925
  timer.unref?.();
925
926
  try {
926
927
  const response = await this.fetchImpl(this.baseUrl + path, {
@@ -1022,22 +1023,17 @@ async function boundedJson(response) {
1022
1023
  }
1023
1024
 
1024
1025
  // ../shared/src/oauth-credentials.ts
1025
- import {
1026
- constants,
1027
- closeSync as closeSync2,
1028
- existsSync as existsSync2,
1029
- fstatSync,
1030
- fsyncSync,
1031
- lstatSync,
1032
- mkdirSync as mkdirSync2,
1033
- openSync as openSync2,
1034
- readFileSync as readFileSync2,
1035
- renameSync as renameSync2,
1036
- unlinkSync,
1037
- writeFileSync
1038
- } from "fs";
1039
- import { randomBytes } from "crypto";
1026
+ import { randomBytes as randomBytes3 } from "crypto";
1027
+
1028
+ // ../shared/src/credentials/local-credential-provider.ts
1029
+ import { createHash as createHash2, randomBytes as randomBytes2 } from "crypto";
1030
+ import { homedir as homedir2 } from "os";
1040
1031
  import { join as join3 } from "path";
1032
+
1033
+ // ../shared/src/credentials/storage.ts
1034
+ import { constants, closeSync as closeSync2, existsSync as existsSync2, fstatSync, fsyncSync, lstatSync, mkdirSync as mkdirSync2, openSync as openSync2, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
1035
+ import { randomBytes, createHash } from "crypto";
1036
+ import { dirname as dirname2 } from "path";
1041
1037
  import { setTimeout as delay2 } from "timers/promises";
1042
1038
 
1043
1039
  // ../shared/src/windows-private-storage.ts
@@ -1092,6 +1088,331 @@ function checkWindowsPrivateStorage(path, initialize, run = execFileSync) {
1092
1088
  }
1093
1089
  }
1094
1090
 
1091
+ // ../shared/src/credentials/storage.ts
1092
+ function privatePath(path, directory = false) {
1093
+ const s = lstatSync(path);
1094
+ if (s.isSymbolicLink() || (directory ? !s.isDirectory() : !s.isFile())) throw new Error("credentials: storage is not private or is invalid");
1095
+ if (process.platform === "win32") {
1096
+ checkWindowsPrivateStorage(path, false);
1097
+ return;
1098
+ }
1099
+ if (s.mode & 63 || process.getuid && s.uid !== process.getuid()) throw new Error("credentials: storage must be private and owner-only (run chmod 600 on credentials.json)");
1100
+ }
1101
+ function prepareStorage(path) {
1102
+ const dir = dirname2(path);
1103
+ if (!existsSync2(dir)) {
1104
+ mkdirSync2(dir, { recursive: true, mode: 448 });
1105
+ if (process.platform === "win32") checkWindowsPrivateStorage(dir, true);
1106
+ }
1107
+ privatePath(dir, true);
1108
+ }
1109
+ function readPrivate(path) {
1110
+ if (!existsSync2(path)) return void 0;
1111
+ privatePath(dirname2(path), true);
1112
+ privatePath(path);
1113
+ const fd = openSync2(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
1114
+ try {
1115
+ const stat = fstatSync(fd);
1116
+ if (!stat.isFile() || stat.size > 16 * 1024 * 1024) throw new Error("credentials: invalid storage");
1117
+ return readFileSync2(fd, "utf8");
1118
+ } finally {
1119
+ closeSync2(fd);
1120
+ }
1121
+ }
1122
+ function atomicCredentialWrite(path, value) {
1123
+ prepareStorage(path);
1124
+ if (existsSync2(path)) privatePath(path);
1125
+ const temp = path + "." + randomBytes(12).toString("hex") + ".tmp";
1126
+ let fd;
1127
+ try {
1128
+ fd = openSync2(temp, "wx", 384);
1129
+ writeFileSync(fd, JSON.stringify(value, null, 2) + "\n");
1130
+ fsyncSync(fd);
1131
+ closeSync2(fd);
1132
+ fd = void 0;
1133
+ renameSync2(temp, path);
1134
+ if (process.platform !== "win32") {
1135
+ try {
1136
+ const dir = openSync2(dirname2(path), "r");
1137
+ try {
1138
+ fsyncSync(dir);
1139
+ } finally {
1140
+ closeSync2(dir);
1141
+ }
1142
+ } catch {
1143
+ }
1144
+ }
1145
+ } finally {
1146
+ if (fd !== void 0) closeSync2(fd);
1147
+ if (existsSync2(temp)) unlinkSync(temp);
1148
+ }
1149
+ }
1150
+ function withCredentialWriteLock(path, operation) {
1151
+ prepareStorage(path);
1152
+ const lock = path + ".lock", deadline = Date.now() + 1e4;
1153
+ let fd;
1154
+ while (true) {
1155
+ try {
1156
+ fd = openSync2(lock, "wx", 384);
1157
+ break;
1158
+ } catch (error) {
1159
+ if (error.code !== "EEXIST") throw error;
1160
+ if (Date.now() >= deadline) throw new Error("credentials: writer lock is busy; check the previous CLI process");
1161
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 20);
1162
+ }
1163
+ }
1164
+ try {
1165
+ return operation();
1166
+ } finally {
1167
+ closeSync2(fd);
1168
+ unlinkSync(lock);
1169
+ }
1170
+ }
1171
+ function operationLockPath(path, key) {
1172
+ return path + "." + createHash("sha256").update(key).digest("hex") + ".lock";
1173
+ }
1174
+ async function withCredentialOperation(path, key, operation, options = {}) {
1175
+ prepareStorage(path);
1176
+ const lock = operationLockPath(path, key), deadline = Date.now() + (options.lockWaitMs ?? 3e4);
1177
+ while (true) {
1178
+ options.signal?.throwIfAborted();
1179
+ let fd;
1180
+ try {
1181
+ fd = openSync2(lock, "wx", 384);
1182
+ } catch (error) {
1183
+ if (error.code !== "EEXIST") throw error;
1184
+ if (Date.now() >= deadline) throw new Error("credentials: credential_busy");
1185
+ await delay2(25, void 0, { signal: options.signal });
1186
+ continue;
1187
+ }
1188
+ try {
1189
+ writeFileSync(fd, String(process.pid));
1190
+ } finally {
1191
+ closeSync2(fd);
1192
+ }
1193
+ try {
1194
+ return await operation();
1195
+ } finally {
1196
+ unlinkSync(lock);
1197
+ }
1198
+ }
1199
+ }
1200
+
1201
+ // ../shared/src/credentials/types.ts
1202
+ var ACTIVE_CONTEXTDB_CREDENTIAL = "contextdb/active";
1203
+
1204
+ // ../shared/src/credentials/local-credential-provider.ts
1205
+ function defaultCredentialsPath() {
1206
+ return join3(homedir2(), ".ctxdb", "credentials.json");
1207
+ }
1208
+ var object2 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
1209
+ var fail = (message) => {
1210
+ throw new Error("credentials: " + message);
1211
+ };
1212
+ function origin(value) {
1213
+ if (typeof value !== "string" || !value) return fail("invalid service URL");
1214
+ let u;
1215
+ try {
1216
+ u = new URL(value);
1217
+ } catch {
1218
+ return fail("invalid service URL");
1219
+ }
1220
+ if (!["http:", "https:"].includes(u.protocol) || u.username || u.password || u.search || u.hash) return fail("invalid service URL");
1221
+ return u.toString().replace(/\/$/, "");
1222
+ }
1223
+ function validateApi(raw, internal, v1 = false) {
1224
+ if (!object2(raw.payload) || typeof raw.payload.api_key !== "string" || !raw.payload.api_key) fail("invalid api-key payload");
1225
+ const metadata = v1 ? raw.payload : raw.metadata;
1226
+ origin(metadata.base_url);
1227
+ if (internal) {
1228
+ origin(metadata.login_server);
1229
+ if (!object2(raw.metadata) || !["browser-loopback", "device-code"].includes(raw.metadata.authorization_method) || typeof raw.metadata.issued_at !== "string" || !Number.isFinite(Date.parse(raw.metadata.issued_at))) fail("invalid api-key metadata");
1230
+ }
1231
+ }
1232
+ function validate(key, raw) {
1233
+ if (!object2(raw) || !["api-key", "oauth-session"].includes(raw.kind) || !object2(raw.payload) || !object2(raw.metadata) || !object2(raw.metadata.owner) || !["ready", "refreshing", "reauth_required", "logged_out"].includes(raw.metadata.state) || typeof raw.metadata.generation !== "string" || !/^[a-f0-9]{32}$/.test(raw.metadata.generation) || !Number.isSafeInteger(raw.metadata.revision) || raw.metadata.revision < 1) fail("invalid credential record");
1234
+ if (key === ACTIVE_CONTEXTDB_CREDENTIAL) {
1235
+ if (raw.kind !== "api-key" || raw.metadata.owner.distribution !== "internal" || raw.metadata.state !== "ready") fail("invalid contextdb/active record");
1236
+ } else if (!/^[a-f0-9]{32}$/.test(key) || raw.metadata.owner.distribution !== "public" || typeof raw.metadata.owner.agent !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/.test(raw.metadata.owner.agent) || raw.metadata.owner.loginId !== void 0 && (typeof raw.metadata.owner.loginId !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/.test(raw.metadata.owner.loginId))) fail("invalid credential ownership");
1237
+ const fields = raw.kind === "api-key" ? ["api_key"] : ["ready", "refreshing"].includes(raw.metadata.state) ? ["accessToken", "refreshToken"] : [];
1238
+ if (Object.keys(raw.payload).length !== fields.length || fields.some((field) => typeof raw.payload[field] !== "string" || !raw.payload[field])) fail("invalid credential payload; only credential values are allowed");
1239
+ if (raw.kind === "api-key") validateApi(raw, key === ACTIVE_CONTEXTDB_CREDENTIAL);
1240
+ }
1241
+ function readDocument(path) {
1242
+ const bytes = readPrivate(path);
1243
+ if (bytes === void 0) return { version: 2, records: {} };
1244
+ let doc;
1245
+ try {
1246
+ doc = JSON.parse(bytes);
1247
+ } catch {
1248
+ return fail("credentials.json is not valid JSON");
1249
+ }
1250
+ if (!object2(doc) || ![1, 2].includes(doc.version) || !object2(doc.records)) return fail("unsupported credentials.json schema/version");
1251
+ if (doc.version === 1) {
1252
+ if (Object.keys(doc.records).some((k) => k !== ACTIVE_CONTEXTDB_CREDENTIAL)) fail("unsupported record key in v1 credentials");
1253
+ const active = doc.records[ACTIVE_CONTEXTDB_CREDENTIAL];
1254
+ if (active !== void 0) {
1255
+ if (active.kind !== "api-key") fail("unsupported record kind");
1256
+ validateApi(active, true, true);
1257
+ }
1258
+ } else for (const [key, record] of Object.entries(doc.records)) validate(key, record);
1259
+ return doc;
1260
+ }
1261
+ function upgrade(doc) {
1262
+ if (doc.version === 2) return doc;
1263
+ const record = doc.records[ACTIVE_CONTEXTDB_CREDENTIAL];
1264
+ return { ...doc, version: 2, records: record ? { [ACTIVE_CONTEXTDB_CREDENTIAL]: legacy(record) } : {} };
1265
+ }
1266
+ function legacy(record) {
1267
+ const { api_key, ...details } = record.payload;
1268
+ return { kind: record.kind, payload: { api_key }, metadata: {
1269
+ ...details,
1270
+ ...record.metadata,
1271
+ base_url: record.payload.base_url,
1272
+ login_server: record.payload.login_server,
1273
+ owner: { distribution: "internal" },
1274
+ state: "ready",
1275
+ revision: 1,
1276
+ generation: createHash2("sha256").update(JSON.stringify(record)).digest("hex").slice(0, 32)
1277
+ } };
1278
+ }
1279
+ function snapshot(doc, key) {
1280
+ const raw = Object.hasOwn(doc.records, key) ? doc.records[key] : void 0;
1281
+ const record = raw && (doc.version === 1 ? legacy(raw) : raw);
1282
+ return { key, record, fingerprint: createHash2("sha256").update(JSON.stringify(record) ?? "missing").digest("hex") };
1283
+ }
1284
+ function apiRecord(record) {
1285
+ if (!record) return void 0;
1286
+ if (record.kind !== "api-key" || record.metadata.owner.distribution !== "internal") return fail("invalid contextdb/active record");
1287
+ return {
1288
+ kind: "api-key",
1289
+ payload: { apiKey: record.payload.api_key },
1290
+ metadata: {
1291
+ baseUrl: origin(record.metadata.base_url),
1292
+ loginServer: origin(record.metadata.login_server),
1293
+ authorizationMethod: record.metadata.authorization_method,
1294
+ issuedAt: record.metadata.issued_at
1295
+ }
1296
+ };
1297
+ }
1298
+ function internalCandidate(record) {
1299
+ return {
1300
+ kind: "api-key",
1301
+ payload: { api_key: record.payload.apiKey },
1302
+ metadata: {
1303
+ base_url: record.metadata.baseUrl,
1304
+ login_server: record.metadata.loginServer,
1305
+ authorization_method: record.metadata.authorizationMethod,
1306
+ issued_at: record.metadata.issuedAt,
1307
+ owner: { distribution: "internal" },
1308
+ state: "ready"
1309
+ }
1310
+ };
1311
+ }
1312
+ var LocalCredentialProvider = class {
1313
+ listeners = /* @__PURE__ */ new Set();
1314
+ path;
1315
+ constructor(path = defaultCredentialsPath()) {
1316
+ this.path = path;
1317
+ }
1318
+ snapshot(key) {
1319
+ return snapshot(readDocument(this.path), key);
1320
+ }
1321
+ readStoredRecord(key) {
1322
+ return this.snapshot(key).record;
1323
+ }
1324
+ async readRecord(key) {
1325
+ return apiRecord(this.readStoredRecord(key));
1326
+ }
1327
+ async describeRecord(key) {
1328
+ const r = this.readStoredRecord(key);
1329
+ return { configured: !!r && r.metadata.state === "ready", kind: r?.kind, writable: true };
1330
+ }
1331
+ onRecordUpdated(listener) {
1332
+ this.listeners.add(listener);
1333
+ return () => this.listeners.delete(listener);
1334
+ }
1335
+ /** Synchronous short commit; callbacks may acquire a config lock, never the reverse. */
1336
+ transaction(operation) {
1337
+ return withCredentialWriteLock(this.path, () => {
1338
+ let doc = readDocument(this.path);
1339
+ const read = (key) => snapshot(doc, key);
1340
+ const check = (expected) => {
1341
+ if (read(expected.key).fingerprint !== expected.fingerprint) fail("authentication changed; the newer credential was preserved, retry the command");
1342
+ };
1343
+ const save = () => {
1344
+ doc = upgrade(doc);
1345
+ atomicCredentialWrite(this.path, doc);
1346
+ };
1347
+ return operation({
1348
+ read,
1349
+ put: (key, candidate, expected) => {
1350
+ if (expected) {
1351
+ if (expected.key !== key) fail("invalid commit target");
1352
+ check(expected);
1353
+ }
1354
+ const before = read(key), previous = before.record;
1355
+ const next = { kind: candidate.kind, payload: candidate.payload, metadata: {
1356
+ ...candidate.metadata,
1357
+ generation: previous?.metadata.generation ?? randomBytes2(16).toString("hex"),
1358
+ revision: (previous?.metadata.revision ?? 0) + 1
1359
+ } };
1360
+ validate(key, next);
1361
+ doc = upgrade(doc);
1362
+ doc.records[key] = next;
1363
+ save();
1364
+ return { before, after: read(key) };
1365
+ },
1366
+ remove: (expected) => {
1367
+ check(expected);
1368
+ if (!read(expected.key).record) return;
1369
+ doc = upgrade(doc);
1370
+ delete doc.records[expected.key];
1371
+ save();
1372
+ }
1373
+ });
1374
+ });
1375
+ }
1376
+ commit(key, record, expected = this.snapshot(key)) {
1377
+ return this.transaction((tx) => tx.put(key, record, expected));
1378
+ }
1379
+ rollback(receipt) {
1380
+ return this.transaction((tx) => {
1381
+ if (tx.read(receipt.after.key).fingerprint !== receipt.after.fingerprint) return false;
1382
+ if (receipt.before.record) tx.put(receipt.after.key, receipt.before.record, receipt.after);
1383
+ else tx.remove(receipt.after);
1384
+ return true;
1385
+ });
1386
+ }
1387
+ async modifyRecord(key, mutate, signal) {
1388
+ const before = this.snapshot(key), next = await mutate(apiRecord(before.record));
1389
+ signal?.throwIfAborted();
1390
+ if (next === void 0) return apiRecord(before.record);
1391
+ const receipt = this.commit(key, internalCandidate(next), before);
1392
+ for (const listener of this.listeners) {
1393
+ try {
1394
+ listener(key);
1395
+ } catch {
1396
+ }
1397
+ }
1398
+ return apiRecord(receipt.after.record);
1399
+ }
1400
+ async deleteRecord(key, signal) {
1401
+ signal?.throwIfAborted();
1402
+ const before = this.snapshot(key);
1403
+ this.transaction((tx) => tx.remove(before));
1404
+ for (const listener of this.listeners) {
1405
+ try {
1406
+ listener(key);
1407
+ } catch {
1408
+ }
1409
+ }
1410
+ }
1411
+ };
1412
+ function readActiveCredentialSync(path = defaultCredentialsPath()) {
1413
+ return apiRecord(new LocalCredentialProvider(path).readStoredRecord(ACTIVE_CONTEXTDB_CREDENTIAL));
1414
+ }
1415
+
1095
1416
  // ../shared/src/oauth-credentials.ts
1096
1417
  var OAuthCredentialError = class extends Error {
1097
1418
  code;
@@ -1110,29 +1431,31 @@ var OAuthCredentialError = class extends Error {
1110
1431
  var loginRequired = () => new OAuthCredentialError("login_required");
1111
1432
  var unsafe = () => new OAuthCredentialError("unsafe_storage");
1112
1433
  var OAuthSessionStore = class {
1113
- directory;
1434
+ provider;
1114
1435
  now;
1115
1436
  lockWaitMs;
1116
- constructor(directory, options = {}) {
1117
- this.directory = directory;
1437
+ path;
1438
+ constructor(path, options = {}) {
1439
+ this.path = path;
1440
+ this.provider = new LocalCredentialProvider(path);
1118
1441
  this.now = options.now ?? Date.now;
1119
1442
  this.lockWaitMs = options.lockWaitMs ?? 3e4;
1120
1443
  }
1121
- saveLogin(profile, session) {
1444
+ saveLogin(profile, session, loginId) {
1122
1445
  requireProfile(profile);
1123
1446
  validateSession(session);
1124
- this.prepareDirectory();
1125
- const ref = randomBytes(16).toString("hex");
1126
- this.write(ref, { version: 1, profile, state: "ready", session });
1447
+ if (loginId !== void 0) requireProfile(loginId);
1448
+ const ref = randomBytes3(16).toString("hex");
1449
+ this.write(ref, { profile, ...loginId ? { loginId } : {}, state: "ready", session });
1127
1450
  return ref;
1128
1451
  }
1129
- status(ref, profile) {
1130
- const record = this.read(ref, profile);
1131
- if (!record) return { state: this.isLoggedOut(ref) ? "logged_out" : "missing" };
1452
+ status(ref, owner) {
1453
+ const record = this.read(ref, owner);
1454
+ if (!record) return { state: "missing" };
1132
1455
  const s = record.session;
1133
1456
  return {
1134
1457
  state: record.state,
1135
- profile,
1458
+ profile: record.profile,
1136
1459
  baseUrl: s.baseUrl,
1137
1460
  workspaceId: s.workspaceId,
1138
1461
  memberId: s.memberId,
@@ -1143,12 +1466,12 @@ var OAuthSessionStore = class {
1143
1466
  accessExpiresAt: s.expiresAt
1144
1467
  };
1145
1468
  }
1146
- async resolve(ref, profile, transport) {
1147
- const first = this.read(ref, profile);
1148
- if (!first || first.state === "reauth_required") throw loginRequired();
1469
+ async resolve(ref, owner, transport) {
1470
+ const first = this.read(ref, owner);
1471
+ if (!first || ["reauth_required", "logged_out"].includes(first.state)) throw loginRequired();
1149
1472
  if (first.state === "ready" && !this.needsRefresh(first.session)) return authorization(first.session);
1150
1473
  return this.withLock(ref, async () => {
1151
- const current = this.read(ref, profile);
1474
+ const current = this.read(ref, owner);
1152
1475
  if (!current || current.state !== "ready") throw loginRequired();
1153
1476
  if (!this.needsRefresh(current.session)) return authorization(current.session);
1154
1477
  const started = this.now();
@@ -1159,170 +1482,160 @@ var OAuthSessionStore = class {
1159
1482
  if (next.expiresAt <= this.now()) throw loginRequired();
1160
1483
  this.write(ref, { ...current, state: "ready", session: next });
1161
1484
  return authorization(next);
1162
- } catch {
1163
- if (!this.isLoggedOut(ref)) this.write(ref, { ...current, state: "reauth_required" });
1485
+ } catch (error) {
1486
+ this.write(ref, { ...current, state: "reauth_required" });
1487
+ if (error instanceof OAuthCredentialError) throw error;
1164
1488
  throw loginRequired();
1165
1489
  }
1166
1490
  });
1167
1491
  }
1168
- async logout(ref, profile, transport) {
1169
- const current = this.read(ref, profile);
1170
- let remoteRevoked = false;
1171
- try {
1172
- if (current) {
1173
- await transport.revoke(current.session.refreshToken, current.session.baseUrl);
1174
- remoteRevoked = true;
1492
+ async share(ref, profile, loginId, commit) {
1493
+ requireProfile(loginId);
1494
+ return this.withLock(ref, async () => {
1495
+ const current = this.read(ref, profile);
1496
+ if (!current || current.state !== "ready") throw loginRequired();
1497
+ if (current.loginId && current.loginId !== loginId) throw new OAuthCredentialError("invalid_profile");
1498
+ const receipt = this.write(ref, { ...current, loginId });
1499
+ try {
1500
+ return commit();
1501
+ } catch (error) {
1502
+ this.provider.rollback(receipt);
1503
+ throw error;
1175
1504
  }
1176
- } catch {
1177
- } finally {
1178
- this.prepareDirectory();
1179
- this.atomicWrite(this.path(ref, ".logged-out"), "");
1180
- remove(this.path(ref));
1181
- }
1182
- return { remoteRevoked };
1183
- }
1184
- needsRefresh(session) {
1185
- const advance = Math.min(3e4, (session.expiresAt - session.issuedAt) / 2);
1186
- return this.now() >= session.expiresAt - advance;
1505
+ });
1187
1506
  }
1188
- path(ref, suffix = ".json") {
1189
- if (!/^[a-f0-9]{32}$/.test(ref)) throw unsafe();
1190
- return join3(this.directory, ref + suffix);
1507
+ /** Setup changes the DATA endpoint without replacing the identity or racing a refresh. */
1508
+ async changeBaseUrl(ref, owner, baseUrl, beforeWrite) {
1509
+ await this.withLock(ref, async () => {
1510
+ this.provider.transaction((tx) => {
1511
+ beforeWrite();
1512
+ const current = this.read(ref, owner);
1513
+ if (!current || current.state !== "ready") throw loginRequired();
1514
+ const session = { ...current.session, baseUrl };
1515
+ validateSession(session);
1516
+ const before = tx.read(ref), candidate = credentialForSession({ ...current, session });
1517
+ tx.put(ref, { ...candidate, metadata: { ...before.record?.metadata, ...candidate.metadata } }, before);
1518
+ });
1519
+ });
1191
1520
  }
1192
- isLoggedOut(ref) {
1193
- return existsSync2(this.path(ref, ".logged-out"));
1521
+ async logout(ref, owner, transport, beforeRevoke = () => {
1522
+ }) {
1523
+ return this.withLock(ref, async () => {
1524
+ const current = this.read(ref, owner);
1525
+ this.provider.transaction((tx) => {
1526
+ beforeRevoke();
1527
+ const before = tx.read(ref);
1528
+ if (current && before.record) tx.put(ref, { ...before.record, metadata: { ...before.record.metadata, state: "logged_out" }, payload: {} }, before);
1529
+ });
1530
+ let remoteRevoked = false;
1531
+ try {
1532
+ if (current?.session.refreshToken && current.state !== "logged_out") {
1533
+ await transport.revoke(current.session.refreshToken, current.session.baseUrl);
1534
+ remoteRevoked = true;
1535
+ }
1536
+ } catch {
1537
+ }
1538
+ return { remoteRevoked };
1539
+ });
1194
1540
  }
1195
- prepareDirectory() {
1196
- if (!existsSync2(this.directory)) {
1197
- mkdirSync2(join3(this.directory, ".."), { recursive: true, mode: 448 });
1541
+ /** Release config references under the write lock, then delete an unused session.
1542
+ * Waiting on the operation lock first lets an in-flight refresh supply its latest RT.
1543
+ * Returning false from release preserves a session that still has users.
1544
+ */
1545
+ async retire(ref, owner, transport, release) {
1546
+ return this.withLock(ref, async () => {
1547
+ const current = this.read(ref, owner);
1548
+ const removed = this.provider.transaction((tx) => {
1549
+ if (!release()) return false;
1550
+ tx.remove(tx.read(ref));
1551
+ return true;
1552
+ });
1553
+ if (!removed) return { removed: false, remoteRevoked: false, remoteUnconfirmed: false };
1554
+ if (!current || current.state === "logged_out") return { removed: true, remoteRevoked: false, remoteUnconfirmed: false };
1198
1555
  try {
1199
- mkdirSync2(this.directory, { mode: 448 });
1200
- if (process.platform === "win32") checkWindowsPrivateStorage(this.directory, true);
1201
- } catch (error) {
1202
- if (error.code !== "EEXIST") throw unsafe();
1556
+ if (!current.session.refreshToken) throw loginRequired();
1557
+ await transport.revoke(current.session.refreshToken, current.session.baseUrl);
1558
+ return { removed: true, remoteRevoked: true, remoteUnconfirmed: false };
1559
+ } catch {
1560
+ return { removed: true, remoteRevoked: false, remoteUnconfirmed: true };
1203
1561
  }
1204
- }
1205
- requirePrivate(this.directory, true);
1562
+ });
1206
1563
  }
1207
- read(ref, profile) {
1208
- requireProfile(profile);
1209
- if (this.isLoggedOut(ref)) return void 0;
1210
- const path = this.path(ref);
1211
- if (!existsSync2(path)) return void 0;
1212
- requirePrivate(this.directory, true);
1213
- requirePrivate(path, false);
1214
- const fd = openSync2(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
1215
- let text2;
1216
- try {
1217
- const stat = fstatSync(fd);
1218
- if (stat.size > 65536 || !stat.isFile()) throw unsafe();
1219
- text2 = readFileSync2(fd, "utf8");
1220
- } finally {
1221
- closeSync2(fd);
1222
- }
1223
- let doc;
1564
+ needsRefresh(session) {
1565
+ return this.now() >= session.expiresAt - Math.min(3e4, (session.expiresAt - session.issuedAt) / 2);
1566
+ }
1567
+ read(ref, owner) {
1568
+ if (!/^[a-f0-9]{32}$/.test(ref)) throw unsafe();
1569
+ requireProfile(typeof owner === "string" ? owner : owner.loginId);
1570
+ let stored;
1224
1571
  try {
1225
- doc = JSON.parse(text2);
1572
+ stored = this.provider.readStoredRecord(ref);
1226
1573
  } catch {
1227
1574
  throw unsafe();
1228
1575
  }
1229
- if (!doc || doc.version !== 1 || !["ready", "refreshing", "reauth_required"].includes(doc.state)) throw unsafe();
1230
- if (doc.profile !== profile) throw new OAuthCredentialError("invalid_profile");
1231
- validateSession(doc.session);
1232
- return this.isLoggedOut(ref) ? void 0 : doc;
1576
+ if (!stored) return void 0;
1577
+ if (stored.kind !== "oauth-session" || stored.metadata.owner.distribution !== "public") throw new OAuthCredentialError("invalid_profile");
1578
+ const who = stored.metadata.owner;
1579
+ if (typeof owner === "string" ? who.agent !== owner : who.loginId !== owner.loginId) throw new OAuthCredentialError("invalid_profile");
1580
+ return { profile: who.agent, loginId: who.loginId, state: stored.metadata.state, session: oauthSessionFromCredential(stored) };
1233
1581
  }
1234
1582
  write(ref, doc) {
1235
- if (this.isLoggedOut(ref)) throw loginRequired();
1236
- this.atomicWrite(this.path(ref), JSON.stringify(doc));
1237
- if (this.isLoggedOut(ref)) {
1238
- remove(this.path(ref));
1239
- throw loginRequired();
1240
- }
1241
- }
1242
- atomicWrite(path, text2) {
1243
- this.prepareDirectory();
1244
- const temporary = path + "." + randomBytes(12).toString("hex") + ".tmp";
1245
- let fd;
1246
- try {
1247
- fd = openSync2(temporary, "wx", 384);
1248
- writeFileSync(fd, text2);
1249
- fsyncSync(fd);
1250
- closeSync2(fd);
1251
- fd = void 0;
1252
- renameSync2(temporary, path);
1253
- if (process.platform !== "win32") {
1254
- const dir = openSync2(this.directory, "r");
1255
- try {
1256
- fsyncSync(dir);
1257
- } finally {
1258
- closeSync2(dir);
1259
- }
1260
- }
1261
- } finally {
1262
- if (fd !== void 0) closeSync2(fd);
1263
- remove(temporary);
1264
- }
1583
+ const before = this.provider.snapshot(ref);
1584
+ if (before.record && ["logged_out", "reauth_required"].includes(before.record.metadata.state)) throw loginRequired();
1585
+ const candidate = credentialForSession(doc);
1586
+ return this.provider.commit(ref, { ...candidate, metadata: { ...before.record?.metadata, ...candidate.metadata } }, before);
1265
1587
  }
1266
1588
  async withLock(ref, task) {
1267
- const path = this.path(ref, ".lock");
1268
- const deadline = Date.now() + this.lockWaitMs;
1269
- this.prepareDirectory();
1270
- while (true) {
1271
- let fd;
1272
- try {
1273
- fd = openSync2(path, "wx", 384);
1274
- } catch (error) {
1275
- if (error.code !== "EEXIST") throw unsafe();
1276
- if (Date.now() >= deadline) throw new OAuthCredentialError("credential_busy");
1277
- await delay2(25);
1278
- continue;
1279
- }
1280
- try {
1281
- writeFileSync(fd, String(process.pid));
1282
- fsyncSync(fd);
1283
- } catch {
1284
- closeSync2(fd);
1285
- remove(path);
1286
- throw unsafe();
1287
- }
1288
- closeSync2(fd);
1289
- try {
1290
- return await task();
1291
- } finally {
1292
- remove(path);
1293
- }
1589
+ if (!/^[a-f0-9]{32}$/.test(ref)) throw unsafe();
1590
+ try {
1591
+ return await withCredentialOperation(this.path, ref, task, { lockWaitMs: this.lockWaitMs });
1592
+ } catch (error) {
1593
+ if (error instanceof Error && error.message === "credentials: credential_busy") throw new OAuthCredentialError("credential_busy");
1594
+ throw error;
1294
1595
  }
1295
1596
  }
1296
1597
  };
1297
- function requireProfile(profile) {
1298
- if (typeof profile !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/.test(profile)) throw new OAuthCredentialError("invalid_profile");
1299
- }
1300
- function requirePrivate(path, directory) {
1301
- const s = lstatSync(path);
1302
- if (s.isSymbolicLink() || (directory ? !s.isDirectory() : !s.isFile())) throw unsafe();
1303
- if (process.platform === "win32") {
1304
- try {
1305
- checkWindowsPrivateStorage(path, false);
1306
- } catch {
1307
- throw unsafe();
1598
+ function credentialForSession(doc) {
1599
+ const { accessToken, refreshToken, ...metadata } = doc.session;
1600
+ return {
1601
+ kind: "oauth-session",
1602
+ payload: ["ready", "refreshing"].includes(doc.state) ? { accessToken, refreshToken } : {},
1603
+ metadata: {
1604
+ ...metadata,
1605
+ owner: { distribution: "public", agent: doc.profile, ...doc.loginId ? { loginId: doc.loginId } : {} },
1606
+ state: doc.state,
1607
+ authorization_method: "device-code"
1308
1608
  }
1309
- return;
1310
- }
1311
- if ((s.mode & 63) !== 0 || process.getuid && s.uid !== process.getuid()) throw unsafe();
1609
+ };
1312
1610
  }
1313
- function remove(path) {
1314
- try {
1315
- unlinkSync(path);
1316
- } catch (error) {
1317
- if (error.code !== "ENOENT") throw unsafe();
1318
- }
1611
+ function oauthSessionFromCredential(record) {
1612
+ if (record.kind !== "oauth-session") throw unsafe();
1613
+ const m = record.metadata, p = record.payload;
1614
+ const session = {
1615
+ baseUrl: m.baseUrl,
1616
+ workspaceId: m.workspaceId,
1617
+ memberId: m.memberId,
1618
+ clientId: m.clientId,
1619
+ scope: m.scope,
1620
+ sessionId: m.sessionId,
1621
+ accountDisplay: m.accountDisplay,
1622
+ issuedAt: m.issuedAt,
1623
+ expiresAt: m.expiresAt,
1624
+ accessToken: p.accessToken,
1625
+ refreshToken: p.refreshToken
1626
+ };
1627
+ validateSession(session, ["ready", "refreshing"].includes(m.state));
1628
+ return session;
1319
1629
  }
1320
- function validateSession(s) {
1630
+ function requireProfile(profile) {
1631
+ if (typeof profile !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/.test(profile)) throw new OAuthCredentialError("invalid_profile");
1632
+ }
1633
+ function validateSession(s, requireTokens = true) {
1321
1634
  if (!s || typeof s !== "object" || s.clientId !== "ctxdb-cli" || !["read_only", "read_write", "default"].includes(s.scope) || !Number.isSafeInteger(s.issuedAt) || !Number.isSafeInteger(s.expiresAt) || s.expiresAt - s.issuedAt < 1e3 || s.expiresAt - s.issuedAt > 36e5) throw unsafe();
1322
1635
  for (const value of [s.workspaceId, s.memberId, s.sessionId, s.accountDisplay]) {
1323
1636
  if (typeof value !== "string" || !value || value.length > 256 || /[\r\n\x00-\x1f]/.test(value)) throw unsafe();
1324
1637
  }
1325
- for (const value of [s.accessToken, s.refreshToken]) {
1638
+ for (const value of requireTokens ? [s.accessToken, s.refreshToken] : []) {
1326
1639
  if (typeof value !== "string" || !value || value.length > 8192 || /\s/.test(value)) throw unsafe();
1327
1640
  }
1328
1641
  let url;
@@ -1351,8 +1664,56 @@ function authorization(session) {
1351
1664
  }
1352
1665
 
1353
1666
  // ../shared/src/oauth-profile.ts
1354
- import { readFileSync as readFileSync3 } from "fs";
1355
- import { dirname as dirname2, join as join4, resolve } from "path";
1667
+ import { dirname as dirname4, join as join4, resolve } from "path";
1668
+
1669
+ // ../shared/src/auth-config.ts
1670
+ import { closeSync as closeSync3, existsSync as existsSync3, fsyncSync as fsyncSync2, mkdirSync as mkdirSync3, openSync as openSync3, readFileSync as readFileSync3, renameSync as renameSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
1671
+ import { createHash as createHash3, randomBytes as randomBytes4 } from "crypto";
1672
+ import { dirname as dirname3 } from "path";
1673
+ var AuthConfigError = class extends Error {
1674
+ constructor(message) {
1675
+ super(message);
1676
+ this.name = "AuthConfigError";
1677
+ }
1678
+ };
1679
+ function readAuthConfig(path) {
1680
+ if (!existsSync3(path)) return { version: 2, agents: {} };
1681
+ let raw;
1682
+ try {
1683
+ raw = JSON.parse(readFileSync3(path, "utf8"));
1684
+ } catch {
1685
+ throw new AuthConfigError("Invalid ctxdb.json; repair the JSON before changing authentication.");
1686
+ }
1687
+ if (raw?.version !== 2 || !object3(raw.agents) || raw.logins !== void 0 && !object3(raw.logins)) {
1688
+ throw new AuthConfigError("Unsupported ctxdb configuration; authentication was not changed.");
1689
+ }
1690
+ return raw;
1691
+ }
1692
+ function object3(value) {
1693
+ return !!value && typeof value === "object" && !Array.isArray(value);
1694
+ }
1695
+ function ownAuthProfile(raw, agent) {
1696
+ const value = Object.hasOwn(raw.agents, agent) ? raw.agents[agent] : void 0;
1697
+ if (value !== void 0 && !object3(value)) throw new AuthConfigError("Invalid agent configuration; repair its section in ctxdb.json.");
1698
+ return value ?? {};
1699
+ }
1700
+ function requireLoginId(id) {
1701
+ if (typeof id !== "string" || id === "default" || !/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/.test(id) || ["__proto__", "constructor", "prototype"].includes(id)) throw new AuthConfigError("Invalid login ID; use an ID from ctxdb auth status --all.");
1702
+ }
1703
+ function validateLoginChoice(section) {
1704
+ if (Object.hasOwn(section, "credential_ref")) {
1705
+ if (typeof section.credential_ref !== "string" || !/^[a-f0-9]{32}$/.test(section.credential_ref)) throw new AuthConfigError("Invalid credential_ref; explicitly select --api-key or --login again.");
1706
+ if (Object.hasOwn(section, "access_credential") || section.api_key || section.oauth_credential_ref) throw new AuthConfigError("Conflicting credential_ref and access_credential/api_key/oauth_credential_ref. Keep one authentication choice.");
1707
+ }
1708
+ if (!Object.hasOwn(section, "access_credential")) return;
1709
+ if (section.access_credential === null) return;
1710
+ requireLoginId(section.access_credential);
1711
+ if (section.api_key || section.oauth_credential_ref) {
1712
+ throw new AuthConfigError("Conflicting access_credential and api_key/oauth_credential_ref in this Agent. Keep one authentication choice, or use setup --api-key/--login to replace it.");
1713
+ }
1714
+ }
1715
+
1716
+ // ../shared/src/oauth-profile.ts
1356
1717
  function oauthProfile(reference, configPath, profile, baseUrl) {
1357
1718
  if (reference === void 0) return void 0;
1358
1719
  if (typeof reference !== "string" || !/^[a-f0-9]{32}$/.test(reference) || !/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/.test(profile)) throw new OAuthCredentialError("unsafe_storage");
@@ -1361,33 +1722,149 @@ function oauthProfile(reference, configPath, profile, baseUrl) {
1361
1722
  reference,
1362
1723
  profile,
1363
1724
  configPath: path,
1364
- directory: join4(dirname2(path), "oauth-credentials"),
1725
+ credentialsPath: join4(dirname4(path), "credentials.json"),
1365
1726
  baseUrl: normalizeCoreOrigin(baseUrl)
1366
1727
  };
1367
1728
  }
1729
+ function oauthProfileFromConfig(raw, path, agent, baseUrlOverride) {
1730
+ const section = ownAuthProfile(raw, agent);
1731
+ validateLoginChoice(section);
1732
+ if (section.access_credential === null) return void 0;
1733
+ if (Object.hasOwn(section, "access_credential")) {
1734
+ const id = section.access_credential;
1735
+ requireLoginId(id);
1736
+ const record = raw.logins && Object.hasOwn(raw.logins, id) ? raw.logins[id] : void 0;
1737
+ if (!record?.credential_ref) throw new AuthConfigError("access_credential points to a missing login. Use ctxdb auth status --all and explicitly bind a valid login or log in again.");
1738
+ return storedProfile(record.credential_ref, path, agent, id, baseUrlOverride);
1739
+ }
1740
+ return storedProfile(section.oauth_credential_ref, path, agent, void 0, baseUrlOverride);
1741
+ }
1742
+ function storedProfile(reference, path, agent, loginId, baseUrlOverride) {
1743
+ const descriptor = oauthProfile(reference, path, agent, "https://api.cn-hangzhou.agentcontext.aliyuncs.com");
1744
+ if (!descriptor) return void 0;
1745
+ const profile = { ...descriptor, ...loginId ? { loginId } : {} };
1746
+ const status = new OAuthSessionStore(profile.credentialsPath).status(profile.reference, oauthOwner(profile));
1747
+ if (!("baseUrl" in status) || typeof status.baseUrl !== "string") throw new OAuthCredentialError("login_required");
1748
+ profile.baseUrl = normalizeCoreOrigin(status.baseUrl);
1749
+ if (baseUrlOverride && normalizeCoreOrigin(baseUrlOverride) !== profile.baseUrl) {
1750
+ throw new AuthConfigError("Requested base URL does not match the login Core origin; no token was sent.");
1751
+ }
1752
+ return profile;
1753
+ }
1754
+ function oauthOwner(profile) {
1755
+ return profile.loginId ? { loginId: profile.loginId } : profile.profile;
1756
+ }
1757
+ function oauthProfileStatus(profile) {
1758
+ const status = new OAuthSessionStore(profile.credentialsPath).status(profile.reference, oauthOwner(profile));
1759
+ if (typeof status.baseUrl === "string" && normalizeCoreOrigin(status.baseUrl) !== profile.baseUrl) {
1760
+ throw new AuthConfigError("Requested base URL does not match the login Core origin; no token was sent.");
1761
+ }
1762
+ return status;
1763
+ }
1368
1764
  var OAuthProfileProvider = class {
1369
1765
  selected;
1370
1766
  constructor(selected) {
1371
1767
  this.selected = selected;
1372
1768
  }
1373
- async resolve() {
1769
+ async resolve(options = {}) {
1770
+ const timeoutMs = options.timeoutMs ?? 3e4, deadline = Date.now() + timeoutMs;
1374
1771
  const selected = this.selected;
1375
- let raw;
1376
- try {
1377
- raw = JSON.parse(readFileSync3(selected.configPath, "utf8"));
1378
- } catch {
1379
- throw new OAuthCredentialError("login_required");
1380
- }
1381
- const profile = raw?.version === 2 ? raw.agents?.[selected.profile] : void 0;
1382
- if (!profile || !Object.hasOwn(profile, "oauth_credential_ref")) throw new OAuthCredentialError("login_required");
1383
- const current = oauthProfile(profile.oauth_credential_ref, selected.configPath, selected.profile, profile.base_url);
1772
+ const readCurrent = () => {
1773
+ const raw = readAuthConfig(selected.configPath);
1774
+ return oauthProfileFromConfig(raw, selected.configPath, selected.profile);
1775
+ };
1776
+ const current = readCurrent();
1384
1777
  if (!current || current.baseUrl !== selected.baseUrl) throw new OAuthCredentialError("login_required");
1385
- const store = new OAuthSessionStore(current.directory);
1386
- const status = store.status(current.reference, current.profile);
1778
+ const store = new OAuthSessionStore(current.credentialsPath, { lockWaitMs: timeoutMs });
1779
+ const status = oauthProfileStatus(current);
1387
1780
  if (!("baseUrl" in status) || typeof status.baseUrl !== "string" || normalizeCoreOrigin(status.baseUrl) !== current.baseUrl) throw new OAuthCredentialError("login_required");
1388
- return store.resolve(current.reference, current.profile, new CoreDeviceClient(current.baseUrl));
1781
+ const authorization2 = await store.resolve(current.reference, oauthOwner(current), {
1782
+ refresh: (rt, url) => new CoreDeviceClient(url, { requestTimeoutMs: Math.max(1, deadline - Date.now()) }).refresh(rt, url)
1783
+ });
1784
+ const after = readCurrent();
1785
+ if (!after || after.reference !== current.reference || after.loginId !== current.loginId || after.baseUrl !== current.baseUrl || oauthProfileStatus(after).state !== "ready") throw new OAuthCredentialError("login_required");
1786
+ return authorization2;
1787
+ }
1788
+ };
1789
+
1790
+ // ../shared/src/login-bindings.ts
1791
+ import { randomBytes as randomBytes5 } from "crypto";
1792
+ import { dirname as dirname6, join as join6 } from "path";
1793
+
1794
+ // ../shared/src/data-auth.ts
1795
+ import { dirname as dirname5, join as join5 } from "path";
1796
+ function selectDataApiCredential(cfg, env = process.env) {
1797
+ const accessToken = env.CTXDB_ACCESS_TOKEN?.trim();
1798
+ if (accessToken) return { type: "access-token", value: accessToken };
1799
+ if (env.CTXDB_API_KEY) return { type: "api-key", value: env.CTXDB_API_KEY };
1800
+ const context = cfg.authContext ? { context: cfg.authContext } : {};
1801
+ if (cfg.oauthCredential) return { type: "oauth-session", profile: cfg.oauthCredential, ...context };
1802
+ return cfg.apiKey ? { type: "api-key", value: cfg.apiKey, ...context } : null;
1803
+ }
1804
+ var DataApiAuthorizationProvider = class {
1805
+ context;
1806
+ constructor(context) {
1807
+ this.context = context;
1808
+ }
1809
+ async resolve(options = {}) {
1810
+ const c = this.context, raw = readAuthConfig(c.configPath), section = ownAuthProfile(raw, c.agent);
1811
+ const fallback = c.inheritDefaultKey && c.agent !== "default" ? ownAuthProfile(raw, "default") : {};
1812
+ if (c.managedCredentials) {
1813
+ const active = readActiveCredentialSync(c.credentialsPath ?? join5(dirname5(c.configPath), "credentials.json"));
1814
+ if (active) {
1815
+ const baseUrl2 = c.baseUrlOverride ?? active.metadata.baseUrl;
1816
+ if (baseUrl2 !== c.baseUrl) throw new OAuthCredentialError("login_required");
1817
+ return { scheme: "Token", value: active.payload.apiKey, baseUrl: baseUrl2, expiresAt: null };
1818
+ }
1819
+ throw new OAuthCredentialError("login_required");
1820
+ }
1821
+ const stored = publicAuthenticationFromConfig(raw, c.configPath, c.agent, c.baseUrlOverride);
1822
+ const baseUrl = stored?.baseUrl ?? String(c.baseUrlOverride || section.base_url || fallback.base_url || "https://api.cn-hangzhou.agentcontext.aliyuncs.com").replace(/\/+$/, "");
1823
+ if (baseUrl !== c.baseUrl) throw new OAuthCredentialError("login_required");
1824
+ if (stored?.apiKey) return { scheme: "Token", value: stored.apiKey, baseUrl, expiresAt: null };
1825
+ if (stored?.oauthCredential) return new OAuthProfileProvider(stored.oauthCredential).resolve(options);
1826
+ if (Object.hasOwn(section, "access_credential")) throw new OAuthCredentialError("login_required");
1827
+ const key = Object.hasOwn(section, "api_key") ? section.api_key : fallback.api_key;
1828
+ if (typeof key !== "string" || !key) throw new OAuthCredentialError("login_required");
1829
+ return { scheme: "Token", value: key, baseUrl, expiresAt: null };
1389
1830
  }
1390
1831
  };
1832
+ function publicAuthenticationFromConfig(raw, path, agent, baseUrlOverride) {
1833
+ const section = ownAuthProfile(raw, agent);
1834
+ validateLoginChoice(section);
1835
+ if (!Object.hasOwn(section, "credential_ref")) {
1836
+ const oauthCredential = oauthProfileFromConfig(raw, path, agent, baseUrlOverride);
1837
+ return oauthCredential ? { baseUrl: oauthCredential.baseUrl, oauthCredential } : void 0;
1838
+ }
1839
+ const r = new LocalCredentialProvider(join5(dirname5(path), "credentials.json")).readStoredRecord(section.credential_ref);
1840
+ if (!r || r.kind !== "api-key" || r.metadata.state !== "ready" || r.metadata.owner.distribution !== "public" || r.metadata.owner.agent !== agent || r.metadata.owner.loginId) {
1841
+ throw new AuthConfigError("credential_ref is missing, invalid or belongs to another Agent. Use setup --api-key/--login to choose a valid credential.");
1842
+ }
1843
+ const baseUrl = String(r.metadata.base_url).replace(/\/+$/, "");
1844
+ if (baseUrlOverride && baseUrl !== baseUrlOverride.replace(/\/+$/, "")) throw new AuthConfigError("Requested base URL does not match the credential origin; no credential was sent.");
1845
+ return { baseUrl, apiKey: r.payload.api_key };
1846
+ }
1847
+ function createRequestAuthorizationProvider(credential, baseUrl) {
1848
+ if (credential?.context) return new DataApiAuthorizationProvider(credential.context);
1849
+ if (credential?.type === "oauth-session") return new OAuthProfileProvider(credential.profile);
1850
+ return { async resolve(_options) {
1851
+ if (!credential) throw new OAuthCredentialError("login_required");
1852
+ return {
1853
+ scheme: credential.type === "access-token" ? "Bearer" : "Token",
1854
+ value: credential.value,
1855
+ baseUrl,
1856
+ expiresAt: null
1857
+ };
1858
+ } };
1859
+ }
1860
+
1861
+ // ../shared/src/authorization/public-target.ts
1862
+ import { dirname as dirname7, join as join7 } from "path";
1863
+
1864
+ // src/config.ts
1865
+ import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
1866
+ import { homedir as homedir3 } from "os";
1867
+ import { dirname as dirname8, join as join8 } from "path";
1391
1868
 
1392
1869
  // src/distribution-capabilities.ts
1393
1870
  var PUBLIC_DISTRIBUTION = {
@@ -1403,7 +1880,7 @@ var PUBLIC_DISTRIBUTION = {
1403
1880
  var DISTRIBUTION_MANIFEST = typeof define_CTXDB_DISTRIBUTION_MANIFEST_default === "undefined" ? PUBLIC_DISTRIBUTION : define_CTXDB_DISTRIBUTION_MANIFEST_default;
1404
1881
 
1405
1882
  // src/config.ts
1406
- var DEFAULT_BASE_URL = "https://context-database.aliyuncs.com";
1883
+ var DEFAULT_BASE_URL = "https://api.cn-hangzhou.agentcontext.aliyuncs.com";
1407
1884
  var DEFAULT_USER_ID = "default";
1408
1885
  var DEFAULT_TOP_K = 5;
1409
1886
  var DEFAULT_THRESHOLD = 0.4;
@@ -1411,7 +1888,7 @@ var DEFAULT_KNOWLEDGE_TOP_K = 6;
1411
1888
  var DEFAULT_KB_CATALOG_INJECTION = "session_start";
1412
1889
  function defaultPath(env) {
1413
1890
  if (env.CTXDB_CONFIG_PATH) return env.CTXDB_CONFIG_PATH;
1414
- return join5(homedir2(), ".ctxdb", "ctxdb.json");
1891
+ return join8(homedir3(), ".ctxdb", "ctxdb.json");
1415
1892
  }
1416
1893
  function coerceInt(v, fallback) {
1417
1894
  if (v === null || v === void 0 || v === "") return fallback;
@@ -1433,7 +1910,7 @@ function coerceKbCatalogInjection(v) {
1433
1910
  return DEFAULT_KB_CATALOG_INJECTION;
1434
1911
  }
1435
1912
  function readRaw(path) {
1436
- if (!existsSync3(path)) return {};
1913
+ if (!existsSync4(path)) return {};
1437
1914
  try {
1438
1915
  const parsed = JSON.parse(readFileSync4(path, "utf-8"));
1439
1916
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
@@ -1470,40 +1947,6 @@ function applyDebugPolicy(cfg) {
1470
1947
  cfg.debugReason = policy.debugReason;
1471
1948
  return cfg;
1472
1949
  }
1473
- function managedCredential(path) {
1474
- if (!existsSync3(path)) return null;
1475
- if (process.platform !== "win32" && (statSync2(path).mode & 63) !== 0) {
1476
- throw new Error(`credentials: ${path} must be owner-only (run chmod 600)`);
1477
- }
1478
- const raw = JSON.parse(readFileSync4(path, "utf8"));
1479
- if (raw.version !== 1 || !raw.records || typeof raw.records !== "object") {
1480
- throw new Error("credentials: unsupported credentials.json schema");
1481
- }
1482
- const unknownKeys = Object.keys(raw.records).filter(
1483
- (key) => key !== "contextdb/active"
1484
- );
1485
- if (unknownKeys.length > 0) {
1486
- throw new Error(`credentials: unsupported record key ${unknownKeys[0]}`);
1487
- }
1488
- const record = raw.records["contextdb/active"];
1489
- if (!record) return null;
1490
- if (record.kind !== "api-key" || typeof record.payload?.api_key !== "string" || !record.payload.api_key || typeof record.payload?.base_url !== "string" || !record.payload.base_url) {
1491
- throw new Error("credentials: invalid contextdb/active record");
1492
- }
1493
- let baseUrl;
1494
- try {
1495
- baseUrl = new URL(record.payload.base_url);
1496
- } catch {
1497
- throw new Error("credentials: invalid contextdb/active base_url");
1498
- }
1499
- if (!["https:", "http:"].includes(baseUrl.protocol) || baseUrl.username || baseUrl.password || baseUrl.search || baseUrl.hash) {
1500
- throw new Error("credentials: invalid contextdb/active base_url");
1501
- }
1502
- return {
1503
- apiKey: record.payload.api_key,
1504
- baseUrl: baseUrl.toString().replace(/\/$/, "")
1505
- };
1506
- }
1507
1950
  function loadOpencodeConfig(options = {}) {
1508
1951
  const env = options.env ?? process.env;
1509
1952
  const path = options.path ?? defaultPath(env);
@@ -1529,16 +1972,41 @@ function loadOpencodeConfig(options = {}) {
1529
1972
  debugReason: null,
1530
1973
  kbCatalogInjection: coerceKbCatalogInjection(section.kb_catalog_injection)
1531
1974
  };
1532
- const managed = DISTRIBUTION_MANIFEST.capabilities.managedCredentials && options.managedCredentials !== false ? managedCredential(
1533
- options.credentialsPath ?? join5(dirname3(path), "credentials.json")
1975
+ const managed = DISTRIBUTION_MANIFEST.capabilities.managedCredentials && options.managedCredentials !== false ? readActiveCredentialSync(
1976
+ options.credentialsPath ?? join8(dirname8(path), "credentials.json")
1534
1977
  ) : null;
1535
1978
  if (managed) {
1536
- cfg.apiKey = managed.apiKey;
1537
- cfg.baseUrl = managed.baseUrl;
1979
+ cfg.apiKey = managed.payload.apiKey;
1980
+ cfg.baseUrl = managed.metadata.baseUrl;
1981
+ }
1982
+ if (DISTRIBUTION_MANIFEST.capabilities.deviceFlow) {
1983
+ validateLoginChoice(section);
1984
+ const stored = env.CTXDB_API_KEY || env.CTXDB_ACCESS_TOKEN?.trim() ? void 0 : publicAuthenticationFromConfig({ ...raw, version: 2, agents: raw.agents ?? {} }, path, "opencode", env.CTXDB_BASE_URL);
1985
+ if (stored) {
1986
+ cfg.baseUrl = stored.baseUrl;
1987
+ cfg.apiKey = stored.apiKey ?? null;
1988
+ cfg.oauthCredential = stored.oauthCredential;
1989
+ } else if (section.credential_ref) cfg.apiKey = null;
1538
1990
  }
1539
1991
  applyEnv(cfg, env);
1992
+ if (managed) cfg.authContext = {
1993
+ configPath: path,
1994
+ agent: "opencode",
1995
+ baseUrl: cfg.baseUrl,
1996
+ inheritDefaultKey: false,
1997
+ managedCredentials: true,
1998
+ credentialsPath: options.credentialsPath,
1999
+ ...env.CTXDB_BASE_URL ? { baseUrlOverride: env.CTXDB_BASE_URL } : {}
2000
+ };
1540
2001
  if (DISTRIBUTION_MANIFEST.capabilities.deviceFlow) {
1541
- cfg.oauthCredential = oauthProfile(section.oauth_credential_ref, path, "opencode", cfg.baseUrl);
2002
+ cfg.authContext = {
2003
+ configPath: path,
2004
+ agent: "opencode",
2005
+ baseUrl: cfg.baseUrl,
2006
+ inheritDefaultKey: false,
2007
+ ...env.CTXDB_BASE_URL ? { baseUrlOverride: env.CTXDB_BASE_URL } : {}
2008
+ };
2009
+ if (Object.hasOwn(section, "access_credential") && !env.CTXDB_API_KEY) cfg.apiKey = null;
1542
2010
  }
1543
2011
  if (env.CTXDB_API_KEY || accessTokenFromEnv(env)) cfg.oauthCredential = void 0;
1544
2012
  return cfg;
@@ -1566,25 +2034,28 @@ var HttpClient = class {
1566
2034
  userAgent;
1567
2035
  fetchImpl;
1568
2036
  oauthProvider;
2037
+ credential;
1569
2038
  constructor(opts) {
1570
2039
  this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
1571
2040
  this.apiKey = opts.apiKey;
1572
2041
  this.accessToken = opts.accessToken ?? null;
1573
- this.oauthProvider = opts.oauthCredential ? new OAuthProfileProvider(opts.oauthCredential) : void 0;
2042
+ this.credential = selectDataApiCredential(opts, { CTXDB_ACCESS_TOKEN: opts.accessToken ?? void 0, CTXDB_API_KEY: opts.environmentApiKey });
2043
+ this.oauthProvider = createRequestAuthorizationProvider(this.credential, this.baseUrl);
1574
2044
  this.userAgent = opts.userAgent ?? "ctxdb-opencode-plugin/0.0.0";
1575
2045
  this.fetchImpl = opts.fetchImpl ?? globalThis.fetch.bind(globalThis);
1576
2046
  }
1577
- async headers(contentType) {
2047
+ async headers(contentType, timeoutMs) {
1578
2048
  const h = {
1579
2049
  "User-Agent": this.userAgent,
1580
2050
  Connection: "close"
1581
2051
  };
1582
- if (this.accessToken) h.Authorization = `Bearer ${this.accessToken}`;
1583
- else if (this.oauthProvider) {
1584
- const authorization2 = await this.oauthProvider.resolve();
2052
+ if (this.oauthProvider) {
2053
+ const authorization2 = await this.oauthProvider.resolve({ timeoutMs });
1585
2054
  if (authorization2.baseUrl !== this.baseUrl) throw new Error("OAuth environment changed; reload this runtime.");
1586
- h.Authorization = `Bearer ${authorization2.value}`;
1587
- } else if (this.apiKey) h.Authorization = `Token ${this.apiKey}`;
2055
+ h.Authorization = `${authorization2.scheme} ${authorization2.value}`;
2056
+ } else if (this.credential && this.credential.type !== "oauth-session") {
2057
+ h.Authorization = `${this.credential.type === "access-token" ? "Bearer" : "Token"} ${this.credential.value}`;
2058
+ }
1588
2059
  if (contentType) h["Content-Type"] = contentType;
1589
2060
  return h;
1590
2061
  }
@@ -1605,12 +2076,12 @@ var HttpClient = class {
1605
2076
  return this.request("POST", url, path, JSON.stringify(body), "application/json", options.timeoutMs);
1606
2077
  }
1607
2078
  async request(method, url, path, body, contentType, timeoutMs) {
1608
- const headers = await this.headers(contentType);
1609
- const bearer = headers.Authorization?.startsWith("Bearer ") ? headers.Authorization.slice(7) : void 0;
2079
+ const effectiveTimeout = timeoutMs ?? DEFAULT_TIMEOUT_MS, started = Date.now();
2080
+ const headers = await this.headers(contentType, effectiveTimeout);
2081
+ const bearer = headers.Authorization?.replace(/^(Bearer|Token) /, "");
1610
2082
  const safeText = (value) => bearer ? String(value).split(bearer).join("[REDACTED]") : String(value);
1611
- const effectiveTimeout = timeoutMs ?? DEFAULT_TIMEOUT_MS;
1612
2083
  const controller = new AbortController();
1613
- const timer = setTimeout(() => controller.abort(), effectiveTimeout);
2084
+ const timer = setTimeout(() => controller.abort(), Math.max(1, effectiveTimeout - (Date.now() - started)));
1614
2085
  try {
1615
2086
  let resp;
1616
2087
  try {
@@ -1630,7 +2101,8 @@ var HttpClient = class {
1630
2101
  if (resp.status === 204) return {};
1631
2102
  let text2;
1632
2103
  try {
1633
- text2 = safeText(await resp.text());
2104
+ const bodyText = await resp.text();
2105
+ text2 = headers.Authorization?.startsWith("Bearer ") || !resp.ok ? safeText(bodyText) : bodyText;
1634
2106
  } catch (err) {
1635
2107
  throw new CtxdbHttpError(path, resp.status, `body read failed: ${safeText(err?.message ?? err)}`);
1636
2108
  }
@@ -1904,7 +2376,7 @@ function buildWarmupQuery(cwd, git) {
1904
2376
  }
1905
2377
 
1906
2378
  // src/capture.ts
1907
- import { createHash } from "crypto";
2379
+ import { createHash as createHash4 } from "crypto";
1908
2380
  var EMPTY2 = {
1909
2381
  captured: false,
1910
2382
  reason: "",
@@ -1971,7 +2443,7 @@ function toParsedMessage(m, index) {
1971
2443
  return null;
1972
2444
  }
1973
2445
  function fingerprintMessages(messages) {
1974
- const h = createHash("sha256");
2446
+ const h = createHash4("sha256");
1975
2447
  for (const m of messages) {
1976
2448
  h.update(m.role);
1977
2449
  h.update("\0");
@@ -2045,6 +2517,8 @@ function buildRuntime(config, cwd, env = process.env) {
2045
2517
  baseUrl: config.baseUrl,
2046
2518
  apiKey: config.apiKey,
2047
2519
  oauthCredential: config.oauthCredential,
2520
+ authContext: config.authContext,
2521
+ environmentApiKey: env.CTXDB_API_KEY,
2048
2522
  accessToken: accessTokenFromEnv(env)
2049
2523
  }),
2050
2524
  sessionState: /* @__PURE__ */ new Map(),
@@ -2084,7 +2558,13 @@ function extractTextPrompt(parts) {
2084
2558
  }
2085
2559
  async function buildHooks(input) {
2086
2560
  if (shouldSkipHooks()) return {};
2087
- const config = loadOpencodeConfig();
2561
+ let config;
2562
+ try {
2563
+ config = loadOpencodeConfig();
2564
+ } catch {
2565
+ process.stderr.write("[ctxdb] Invalid authentication configuration; OpenCode integration skipped. Run ctxdb auth status --all.\n");
2566
+ return {};
2567
+ }
2088
2568
  if (!isConfigured(config)) {
2089
2569
  logDebug(
2090
2570
  config,