@aliyunrds/ctxdb-shared 0.0.7 → 0.0.8-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.
package/dist/index.d.ts CHANGED
@@ -495,4 +495,147 @@ declare function sanitizeRecallFailureReason(reason: string, secrets?: Array<str
495
495
  declare function writeRecallTrace(record: RecallTraceRecord, options?: RecallTraceStoreOptions): boolean;
496
496
  declare function readRecallTraces(options?: ReadRecallTraceOptions): ReadRecallTraceResult;
497
497
 
498
- export { type AddOptions, type AddResult, type AddResultItem, type CategoryConfig, DOCUMENT_EVIDENCE_SECTION_TITLE, type DebugPolicy, type DebugReason, type DetectionResult, EXTERNAL_KNOWLEDGE_PREAMBLE_HEAD, type ExcludedRecallMemory, type ExternalContext, type ExternalContextChunk, GRAPH_CONTEXT_MAX_CHARS, GRAPH_CONTEXT_SECTION_TITLE, GRAPH_CONTEXT_SOURCE_LABEL, GRAPH_CONTEXT_TAG, GRAPH_CONTEXT_TRUNCATION_MARKER, GRAPH_CONTEXT_USAGE_NOTICE, type ListOptions, type Mem0Provider, type MemoryItem, type NormalizedGraphContext, OFFICIAL_PRODUCTION_BASE_URLS, type ParsedMessage, RECALLED_MEMORIES_SAFETY_NOTICE, RECALL_TRACE_AGENTS, RECALL_TRACE_FILENAME, RECALL_TRACE_MAX_BYTES, RECALL_TRACE_OUTCOMES, RECALL_TRACE_ROTATED_GENERATIONS, RECALL_TRACE_SCHEMA_VERSION, type ReadRecallTraceOptions, type ReadRecallTraceResult, type RecallFinishTraceInput, type RecallFinishTraceRecord, type RecallMemoryExclusionReason, type RecallMemorySelectionBase, type RecallResult, type RecallSelectionResult, type RecallStartTraceInput, type RecallStartTraceRecord, type RecallTraceAgent, type RecallTraceKind, type RecallTraceOutcome, type RecallTraceRecord, type RecallTraceRequest, type RecallTraceSelection, type RecallTraceStoreOptions, type SearchOptions, type SearchResultWithExternalContext, type SelectTurnOptions, type SelectedRecallMemory, type SkillsConfig, buildExternalKnowledgeBlock, buildExternalKnowledgePreamble, buildNormalizedGraphContext, createRecallFinishRecord, createRecallStartRecord, extractKnowledgeBaseIds, extractKnowledgeChunkContent, extractKnowledgeChunkSourceLabel, extractKnowledgeChunkTags, filterMessagesForExtraction, formatExternalKnowledgeChunks, formatRecalledMemoriesBlock, isGenericAssistantMessage, isGraphContextChunk, isKnowledgeBaseUploadTurn, isNoiseMessage, isOfficialProductionUrl, isSessionSpecificContent, normalizeBaseUrl, partitionKnowledgeChunks, readRecallTraces, recall, recallTracePath, resolveDebugPolicy, sanitizeChunkContent, sanitizeQuery, sanitizeRecallFailureReason, selectAndFormatRecalledMemories, selectTurnMessages, stripNoiseFromContent, truncateGraphContext, writeRecallTrace };
498
+ type OAuthScope = "read_only" | "read_write" | "default";
499
+ type OAuthLogin = {
500
+ baseUrl: string;
501
+ workspaceId: string;
502
+ memberId: string;
503
+ clientId: "ctxdb-cli";
504
+ scope: OAuthScope;
505
+ sessionId: string;
506
+ accountDisplay: string;
507
+ accessToken: string;
508
+ refreshToken: string;
509
+ /** Milliseconds; use the start of the successful token request, not response arrival, for the expiry bound. */
510
+ issuedAt: number;
511
+ expiresAt: number;
512
+ };
513
+ type Phase = "ready" | "refreshing" | "reauth_required";
514
+ type OAuthRefreshResponse = {
515
+ access_token: string;
516
+ refresh_token: string;
517
+ token_type: string;
518
+ expires_in: number;
519
+ session_id: string;
520
+ scope: string;
521
+ };
522
+ type OAuthRefreshTransport = {
523
+ refresh(refreshToken: string, baseUrl: string): Promise<OAuthRefreshResponse>;
524
+ };
525
+ type OAuthRevokeTransport = {
526
+ revoke(refreshToken: string, baseUrl: string): Promise<void>;
527
+ };
528
+ type OAuthRequestAuthorization = {
529
+ scheme: "Bearer";
530
+ value: string;
531
+ baseUrl: string;
532
+ expiresAt: number;
533
+ };
534
+ declare class OAuthCredentialError extends Error {
535
+ readonly code: "login_required" | "credential_busy" | "invalid_profile" | "unsafe_storage";
536
+ constructor(code: OAuthCredentialError["code"]);
537
+ }
538
+ /** A reference identifies one login, not an agent or a mutable global credential slot.
539
+ * Profiles store only this reference. A new login gets a new reference, so it never steals a
540
+ * crashed refresher's lock or lets a late response overwrite the replacement login.
541
+ */
542
+ declare class OAuthSessionStore {
543
+ private readonly directory;
544
+ private readonly now;
545
+ private readonly lockWaitMs;
546
+ constructor(directory: string, options?: {
547
+ now?: () => number;
548
+ lockWaitMs?: number;
549
+ });
550
+ saveLogin(profile: string, session: OAuthLogin): string;
551
+ status(ref: string, profile: string): {
552
+ state: "logged_out" | "missing";
553
+ profile?: undefined;
554
+ baseUrl?: undefined;
555
+ workspaceId?: undefined;
556
+ memberId?: undefined;
557
+ clientId?: undefined;
558
+ scope?: undefined;
559
+ sessionId?: undefined;
560
+ accountDisplay?: undefined;
561
+ accessExpiresAt?: undefined;
562
+ } | {
563
+ state: Phase;
564
+ profile: string;
565
+ baseUrl: string;
566
+ workspaceId: string;
567
+ memberId: string;
568
+ clientId: "ctxdb-cli";
569
+ scope: OAuthScope;
570
+ sessionId: string;
571
+ accountDisplay: string;
572
+ accessExpiresAt: number;
573
+ };
574
+ resolve(ref: string, profile: string, transport: OAuthRefreshTransport): Promise<OAuthRequestAuthorization>;
575
+ logout(ref: string, profile: string, transport: OAuthRevokeTransport): Promise<{
576
+ remoteRevoked: boolean;
577
+ }>;
578
+ private needsRefresh;
579
+ private path;
580
+ private isLoggedOut;
581
+ private prepareDirectory;
582
+ private read;
583
+ private write;
584
+ private atomicWrite;
585
+ private withLock;
586
+ }
587
+
588
+ type DevicePrompt = {
589
+ requestId: string;
590
+ userCode: string;
591
+ expiresIn: number;
592
+ interval: number;
593
+ };
594
+ type ErrorCode = "authorization_pending" | "slow_down" | "access_denied" | "expired_token" | "invalid_grant" | "invalid_response" | "network_error" | "cancelled" | "request_rejected" | "service_unavailable";
595
+ declare class DeviceFlowError extends Error {
596
+ readonly code: ErrorCode;
597
+ constructor(code: ErrorCode);
598
+ }
599
+ /** Strict Core origin; console routing/discovery does not belong to this client. */
600
+ declare function normalizeCoreOrigin(value: string): string;
601
+ /** JSON + Box adaptation. Never follows redirects, emits raw response errors, or retries a Token request. */
602
+ declare class CoreDeviceClient {
603
+ readonly baseUrl: string;
604
+ private readonly fetchImpl;
605
+ private readonly now;
606
+ private readonly sleep;
607
+ constructor(baseUrl: string, options?: {
608
+ fetchImpl?: typeof fetch;
609
+ now?: () => number;
610
+ sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
611
+ });
612
+ login(options: {
613
+ onPrompt: (prompt: DevicePrompt) => void;
614
+ signal?: AbortSignal;
615
+ }): Promise<OAuthLogin>;
616
+ refresh(refreshToken: string, baseUrl: string): Promise<OAuthRefreshResponse>;
617
+ revoke(refreshToken: string, baseUrl: string): Promise<void>;
618
+ private requireSameOrigin;
619
+ private post;
620
+ }
621
+
622
+ /** Safe config metadata, never AT/RT. References are explicit per profile and are not inherited. */
623
+ type OAuthProfile = {
624
+ reference: string;
625
+ profile: string;
626
+ configPath: string;
627
+ directory: string;
628
+ baseUrl: string;
629
+ };
630
+ declare function oauthProfile(reference: unknown, configPath: string, profile: string, baseUrl: string): OAuthProfile | undefined;
631
+ /** Shared by CLI, out-of-process hooks and the in-process OpenCode runtime.
632
+ * Re-read the selected reference so a long-lived runtime sees a new login. An endpoint
633
+ * change requires reloading the runtime; no credential is sent to the replacement host.
634
+ */
635
+ declare class OAuthProfileProvider {
636
+ private readonly selected;
637
+ constructor(selected: OAuthProfile);
638
+ resolve(): Promise<OAuthRequestAuthorization>;
639
+ }
640
+
641
+ export { type AddOptions, type AddResult, type AddResultItem, type CategoryConfig, CoreDeviceClient, DOCUMENT_EVIDENCE_SECTION_TITLE, type DebugPolicy, type DebugReason, type DetectionResult, DeviceFlowError, type DevicePrompt, EXTERNAL_KNOWLEDGE_PREAMBLE_HEAD, type ExcludedRecallMemory, type ExternalContext, type ExternalContextChunk, GRAPH_CONTEXT_MAX_CHARS, GRAPH_CONTEXT_SECTION_TITLE, GRAPH_CONTEXT_SOURCE_LABEL, GRAPH_CONTEXT_TAG, GRAPH_CONTEXT_TRUNCATION_MARKER, GRAPH_CONTEXT_USAGE_NOTICE, type ListOptions, type Mem0Provider, type MemoryItem, type NormalizedGraphContext, OAuthCredentialError, type OAuthLogin, type OAuthProfile, OAuthProfileProvider, type OAuthRequestAuthorization, type OAuthScope, OAuthSessionStore, OFFICIAL_PRODUCTION_BASE_URLS, type ParsedMessage, RECALLED_MEMORIES_SAFETY_NOTICE, RECALL_TRACE_AGENTS, RECALL_TRACE_FILENAME, RECALL_TRACE_MAX_BYTES, RECALL_TRACE_OUTCOMES, RECALL_TRACE_ROTATED_GENERATIONS, RECALL_TRACE_SCHEMA_VERSION, type ReadRecallTraceOptions, type ReadRecallTraceResult, type RecallFinishTraceInput, type RecallFinishTraceRecord, type RecallMemoryExclusionReason, type RecallMemorySelectionBase, type RecallResult, type RecallSelectionResult, type RecallStartTraceInput, type RecallStartTraceRecord, type RecallTraceAgent, type RecallTraceKind, type RecallTraceOutcome, type RecallTraceRecord, type RecallTraceRequest, type RecallTraceSelection, type RecallTraceStoreOptions, type SearchOptions, type SearchResultWithExternalContext, type SelectTurnOptions, type SelectedRecallMemory, type SkillsConfig, buildExternalKnowledgeBlock, buildExternalKnowledgePreamble, buildNormalizedGraphContext, createRecallFinishRecord, createRecallStartRecord, extractKnowledgeBaseIds, extractKnowledgeChunkContent, extractKnowledgeChunkSourceLabel, extractKnowledgeChunkTags, filterMessagesForExtraction, formatExternalKnowledgeChunks, formatRecalledMemoriesBlock, isGenericAssistantMessage, isGraphContextChunk, isKnowledgeBaseUploadTurn, isNoiseMessage, isOfficialProductionUrl, isSessionSpecificContent, normalizeBaseUrl, normalizeCoreOrigin, oauthProfile, partitionKnowledgeChunks, readRecallTraces, recall, recallTracePath, resolveDebugPolicy, sanitizeChunkContent, sanitizeQuery, sanitizeRecallFailureReason, selectAndFormatRecalledMemories, selectTurnMessages, stripNoiseFromContent, truncateGraphContext, writeRecallTrace };
package/dist/index.js CHANGED
@@ -483,8 +483,8 @@ function getMemoryImportance(memory) {
483
483
  };
484
484
  return defaults[cat] ?? 0.5;
485
485
  }
486
- function estimateTokens(text) {
487
- return Math.ceil(text.length / CHARS_PER_TOKEN);
486
+ function estimateTokens(text2) {
487
+ return Math.ceil(text2.length / CHARS_PER_TOKEN);
488
488
  }
489
489
  function rankMemories(memories, categoryOrder) {
490
490
  const orderMap = new Map(categoryOrder.map((cat, i) => [cat, i]));
@@ -809,9 +809,9 @@ function sanitizeRecallFailureReason(reason, secrets = []) {
809
809
  }
810
810
  function sanitizeTraceString(value, secrets) {
811
811
  let sanitized = value.replace(/authorization\s*[:=]\s*[^\r\n]*/gi, "Authorization: [REDACTED]").replace(/(api[_-]?key|access[_-]?token)\s*[:=]\s*[^\s,;]+/gi, "$1=[REDACTED]");
812
- for (const secret of secrets) {
813
- if (!secret) continue;
814
- sanitized = sanitized.split(secret).join("[REDACTED]");
812
+ for (const secret2 of secrets) {
813
+ if (!secret2) continue;
814
+ sanitized = sanitized.split(secret2).join("[REDACTED]");
815
815
  }
816
816
  return sanitized;
817
817
  }
@@ -946,6 +946,7 @@ function isRecallTraceRecord(value) {
946
946
  const baseValid = record.schema_version === RECALL_TRACE_SCHEMA_VERSION && (record.type === "recall.start" || record.type === "recall.finish") && isIsoTimestamp(record.timestamp) && typeof record.recall_event_id === "string" && record.recall_event_id.length > 0 && (typeof record.session_id === "string" || record.session_id === null) && isRecallTraceAgent(record.agent) && (record.kind === "prompt" || record.kind === "warmup") && typeof record.query === "string" && isRecallTraceRequest(record.request);
947
947
  if (!baseValid) return false;
948
948
  if (record.type === "recall.start") return true;
949
+ if (record.type !== "recall.finish") return false;
949
950
  const selection = record.selection;
950
951
  return isNonNegativeFinite(record.duration_ms) && typeof record.outcome === "string" && TRACE_OUTCOMES.has(record.outcome) && (typeof record.failure_reason === "string" || record.failure_reason === null) && Array.isArray(record.returned_memories) && Array.isArray(record.returned_knowledge) && Boolean(selection) && Array.isArray(selection?.selected) && Array.isArray(selection?.excluded) && isNonNegativeFinite(selection?.token_estimate) && typeof record.recall_context === "string";
951
952
  }
@@ -993,8 +994,567 @@ function readRecallTraces(options = {}) {
993
994
  });
994
995
  return { records: records.map(({ record }) => record), malformedLines };
995
996
  }
997
+
998
+ // src/device-flow.ts
999
+ import { setTimeout as delay } from "timers/promises";
1000
+ var DeviceFlowError = class extends Error {
1001
+ code;
1002
+ constructor(code) {
1003
+ super(code === "service_unavailable" ? "ContextDB authentication service is unavailable. No automatic retry was performed; start a new login later if authorization could not be completed." : `ContextDB device authorization: ${code}. Start a new login if authorization could not be completed.`);
1004
+ this.name = "DeviceFlowError";
1005
+ this.code = code;
1006
+ }
1007
+ };
1008
+ var invalid = () => new DeviceFlowError("invalid_response");
1009
+ var scopes = ["read_only", "read_write", "default"];
1010
+ var clientId = "ctxdb-cli";
1011
+ var grantType = "urn:ietf:params:oauth:grant-type:device_code";
1012
+ function normalizeCoreOrigin(value) {
1013
+ let url;
1014
+ try {
1015
+ url = new URL(value);
1016
+ } catch {
1017
+ throw new Error("Invalid Core server origin");
1018
+ }
1019
+ const local = ["127.0.0.1", "localhost", "[::1]"].includes(url.hostname);
1020
+ if (url.username || url.password || url.search || url.hash || url.pathname !== "/" || !(url.protocol === "https:" || local && url.protocol === "http:")) throw new Error("Invalid Core server origin");
1021
+ return url.origin;
1022
+ }
1023
+ var CoreDeviceClient = class {
1024
+ baseUrl;
1025
+ fetchImpl;
1026
+ now;
1027
+ sleep;
1028
+ constructor(baseUrl, options = {}) {
1029
+ this.baseUrl = normalizeCoreOrigin(baseUrl);
1030
+ this.fetchImpl = options.fetchImpl ?? globalThis.fetch;
1031
+ this.now = options.now ?? Date.now;
1032
+ this.sleep = options.sleep ?? ((ms, signal) => delay(ms, void 0, { signal }));
1033
+ }
1034
+ async login(options) {
1035
+ const started = this.now();
1036
+ const initial = await this.post("/v1/auth/device/authorize", { client_id: clientId }, options.signal);
1037
+ if (!initial.ok) throw new DeviceFlowError("request_rejected");
1038
+ const data = initial.data;
1039
+ const requestId = text(data.request_id, 32), userCode = text(data.user_code, 9), code = text(data.device_code, 43);
1040
+ if (!/^[0-9a-f]{32}$/.test(requestId) || !/^[0-9A-HJKMNP-TV-Z]{4}-[0-9A-HJKMNP-TV-Z]{4}$/.test(userCode) || !/^[A-Za-z0-9_-]{43}$/.test(code)) throw invalid();
1041
+ const expiresIn = integer(data.expires_in, 1, 3600);
1042
+ let interval = integer(data.interval, 1, 3600);
1043
+ const deadline = started + expiresIn * 1e3;
1044
+ options.onPrompt({ requestId, userCode, expiresIn, interval });
1045
+ while (true) {
1046
+ if (options.signal?.aborted) throw new DeviceFlowError("cancelled");
1047
+ if (this.now() >= deadline) throw new DeviceFlowError("expired_token");
1048
+ try {
1049
+ await this.sleep(Math.min(interval * 1e3, deadline - this.now()), options.signal);
1050
+ } catch {
1051
+ throw new DeviceFlowError("cancelled");
1052
+ }
1053
+ if (this.now() >= deadline) throw new DeviceFlowError("expired_token");
1054
+ const issuedAt = this.now();
1055
+ const result = await this.post("/v1/auth/device/token", { grant_type: grantType, client_id: clientId, device_code: code }, options.signal);
1056
+ if (result.ok) return loginResult(result.data, this.baseUrl, issuedAt, this.now());
1057
+ const error = result.data.error;
1058
+ if (error === "authorization_pending") continue;
1059
+ if (error === "slow_down") {
1060
+ interval = Math.max(interval + 5, integer(result.data.interval, 1, 3605));
1061
+ continue;
1062
+ }
1063
+ if (error === "access_denied" || error === "expired_token" || error === "invalid_grant") throw new DeviceFlowError(error);
1064
+ throw new DeviceFlowError("request_rejected");
1065
+ }
1066
+ }
1067
+ async refresh(refreshToken, baseUrl) {
1068
+ this.requireSameOrigin(baseUrl);
1069
+ const result = await this.post("/v1/auth/token/refresh", { refresh_token: secret(refreshToken) });
1070
+ if (!result.ok) throw new DeviceFlowError("request_rejected");
1071
+ return credentialResult(result.data);
1072
+ }
1073
+ async revoke(refreshToken, baseUrl) {
1074
+ this.requireSameOrigin(baseUrl);
1075
+ const result = await this.post("/v1/auth/token/revoke", { refresh_token: secret(refreshToken) });
1076
+ if (!result.ok) throw new DeviceFlowError("request_rejected");
1077
+ }
1078
+ requireSameOrigin(baseUrl) {
1079
+ if (normalizeCoreOrigin(baseUrl) !== this.baseUrl) throw new Error("OAuth credential Core origin cannot change");
1080
+ }
1081
+ async post(path, body, signal) {
1082
+ if (signal?.aborted) throw new DeviceFlowError("cancelled");
1083
+ const controller = new AbortController();
1084
+ const abort = () => controller.abort();
1085
+ signal?.addEventListener("abort", abort, { once: true });
1086
+ const timer = setTimeout(abort, 3e4);
1087
+ timer.unref?.();
1088
+ try {
1089
+ const response = await this.fetchImpl(this.baseUrl + path, {
1090
+ method: "POST",
1091
+ body: JSON.stringify(body),
1092
+ headers: { "Content-Type": "application/json", Accept: "application/json" },
1093
+ redirect: "error",
1094
+ credentials: "omit",
1095
+ cache: "no-store",
1096
+ signal: controller.signal
1097
+ });
1098
+ const box = await boundedJson(response);
1099
+ if (!Number.isInteger(box.errorCode)) throw invalid();
1100
+ const ok = response.ok && box.errorCode === 0;
1101
+ const data = (!ok || path === "/v1/auth/token/revoke") && box.data == null ? {} : object(box.data);
1102
+ if (!ok && response.status >= 500) throw new DeviceFlowError("service_unavailable");
1103
+ return { ok, data };
1104
+ } catch (error) {
1105
+ if (signal?.aborted) throw new DeviceFlowError("cancelled");
1106
+ if (error instanceof DeviceFlowError) throw error;
1107
+ throw new DeviceFlowError("network_error");
1108
+ } finally {
1109
+ clearTimeout(timer);
1110
+ signal?.removeEventListener("abort", abort);
1111
+ }
1112
+ }
1113
+ };
1114
+ function text(value, maximum) {
1115
+ if (typeof value !== "string" || !value || value.length > maximum || /[\x00-\x1f\x7f]/.test(value)) throw invalid();
1116
+ return value;
1117
+ }
1118
+ function integer(value, min, max) {
1119
+ if (!Number.isSafeInteger(value) || value < min || value > max) throw invalid();
1120
+ return value;
1121
+ }
1122
+ function secret(value) {
1123
+ const token = text(value, 8192);
1124
+ if (/\s/.test(token)) throw invalid();
1125
+ return token;
1126
+ }
1127
+ function object(value) {
1128
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw invalid();
1129
+ return value;
1130
+ }
1131
+ function credentialResult(data) {
1132
+ if (data.token_type !== "Bearer" || !scopes.includes(data.scope)) throw invalid();
1133
+ return {
1134
+ access_token: secret(data.access_token),
1135
+ refresh_token: secret(data.refresh_token),
1136
+ token_type: "Bearer",
1137
+ expires_in: integer(data.expires_in, 1, 3600),
1138
+ session_id: text(data.session_id, 64),
1139
+ scope: data.scope
1140
+ };
1141
+ }
1142
+ function loginResult(data, baseUrl, issuedAt, now) {
1143
+ const tokens = credentialResult(data), scope = tokens.scope;
1144
+ if (data.client_id !== clientId || scope !== "default") throw invalid();
1145
+ const expiresAt = issuedAt + tokens.expires_in * 1e3;
1146
+ if (expiresAt <= now) throw invalid();
1147
+ return {
1148
+ baseUrl,
1149
+ workspaceId: text(data.workspace_id, 64),
1150
+ memberId: text(data.member_id, 64),
1151
+ scope,
1152
+ clientId,
1153
+ sessionId: tokens.session_id,
1154
+ accountDisplay: text(data.account_display, 256),
1155
+ issuedAt,
1156
+ expiresAt,
1157
+ accessToken: tokens.access_token,
1158
+ refreshToken: tokens.refresh_token
1159
+ };
1160
+ }
1161
+ async function boundedJson(response) {
1162
+ if (!response.body || Number(response.headers.get("content-length")) > 65536) throw invalid();
1163
+ const reader = response.body.getReader();
1164
+ const chunks = [];
1165
+ let size = 0;
1166
+ try {
1167
+ while (true) {
1168
+ const { done, value } = await reader.read();
1169
+ if (done) break;
1170
+ size += value.byteLength;
1171
+ if (size > 65536) {
1172
+ await reader.cancel();
1173
+ throw invalid();
1174
+ }
1175
+ chunks.push(value);
1176
+ }
1177
+ try {
1178
+ return object(JSON.parse(Buffer.concat(chunks).toString("utf8")));
1179
+ } catch {
1180
+ throw invalid();
1181
+ }
1182
+ } finally {
1183
+ reader.releaseLock();
1184
+ }
1185
+ }
1186
+
1187
+ // src/oauth-credentials.ts
1188
+ import {
1189
+ constants,
1190
+ closeSync as closeSync2,
1191
+ existsSync as existsSync2,
1192
+ fstatSync,
1193
+ fsyncSync,
1194
+ lstatSync,
1195
+ mkdirSync as mkdirSync2,
1196
+ openSync as openSync2,
1197
+ readFileSync as readFileSync2,
1198
+ renameSync as renameSync2,
1199
+ unlinkSync,
1200
+ writeFileSync
1201
+ } from "fs";
1202
+ import { randomBytes } from "crypto";
1203
+ import { join as join3 } from "path";
1204
+ import { setTimeout as delay2 } from "timers/promises";
1205
+
1206
+ // src/windows-private-storage.ts
1207
+ import { execFileSync } from "child_process";
1208
+ import { join as join2 } from "path";
1209
+ var SCRIPT = `
1210
+ $ErrorActionPreference = 'Stop'
1211
+ $path = $env:CTXDB_PRIVATE_STORAGE_PATH
1212
+ $sid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User
1213
+ $item = Get-Item -LiteralPath $path -Force
1214
+ if (($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) { throw 'reparse' }
1215
+ if ($env:CTXDB_PRIVATE_STORAGE_INITIALIZE -eq '1') {
1216
+ if (-not $item.PSIsContainer) { throw 'not directory' }
1217
+ $acl = [System.Security.AccessControl.DirectorySecurity]::new()
1218
+ $acl.SetOwner($sid)
1219
+ $acl.SetAccessRuleProtection($true, $false)
1220
+ $rule = [System.Security.AccessControl.FileSystemAccessRule]::new($sid, 'FullControl', 'ContainerInherit,ObjectInherit', 'None', 'Allow')
1221
+ $acl.AddAccessRule($rule)
1222
+ Set-Acl -LiteralPath $path -AclObject $acl
1223
+ }
1224
+ $acl = Get-Acl -LiteralPath $path
1225
+ if ($acl.GetOwner([System.Security.Principal.SecurityIdentifier]).Value -ne $sid.Value) { throw 'owner' }
1226
+ $self = $false
1227
+ foreach ($rule in $acl.GetAccessRules($true, $true, [System.Security.Principal.SecurityIdentifier])) {
1228
+ if ($rule.AccessControlType -eq 'Allow') {
1229
+ if ($rule.IdentityReference.Value -ne $sid.Value) { throw 'other principal' }
1230
+ if (($rule.FileSystemRights -band [System.Security.AccessControl.FileSystemRights]::FullControl) -eq [System.Security.AccessControl.FileSystemRights]::FullControl) { $self = $true }
1231
+ }
1232
+ }
1233
+ if (-not $self) { throw 'missing owner access' }
1234
+ Write-Output 'private'
1235
+ `;
1236
+ function checkWindowsPrivateStorage(path, initialize, run = execFileSync) {
1237
+ const systemRoot = process.env.SystemRoot;
1238
+ if (!systemRoot) throw new Error("Private Windows credential storage could not be verified.");
1239
+ try {
1240
+ const result = run(
1241
+ join2(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe"),
1242
+ ["-NoProfile", "-NonInteractive", "-EncodedCommand", Buffer.from(SCRIPT, "utf16le").toString("base64")],
1243
+ {
1244
+ env: { ...process.env, CTXDB_PRIVATE_STORAGE_PATH: path, CTXDB_PRIVATE_STORAGE_INITIALIZE: initialize ? "1" : "0" },
1245
+ encoding: "utf8",
1246
+ windowsHide: true,
1247
+ timeout: 1e4,
1248
+ maxBuffer: 4096,
1249
+ stdio: ["ignore", "pipe", "pipe"]
1250
+ }
1251
+ );
1252
+ if (String(result).trim() !== "private") throw new Error();
1253
+ } catch {
1254
+ throw new Error("Private Windows credential storage could not be verified.");
1255
+ }
1256
+ }
1257
+
1258
+ // src/oauth-credentials.ts
1259
+ var OAuthCredentialError = class extends Error {
1260
+ code;
1261
+ constructor(code) {
1262
+ const messages = {
1263
+ login_required: "OAuth session needs login; run ctxdb login for this profile.",
1264
+ credential_busy: "OAuth credential is busy. If its previous process crashed, run ctxdb login for a new credential.",
1265
+ invalid_profile: "OAuth credential does not belong to this profile.",
1266
+ unsafe_storage: "OAuth credential storage is not private or is invalid."
1267
+ };
1268
+ super(messages[code]);
1269
+ this.name = "OAuthCredentialError";
1270
+ this.code = code;
1271
+ }
1272
+ };
1273
+ var loginRequired = () => new OAuthCredentialError("login_required");
1274
+ var unsafe = () => new OAuthCredentialError("unsafe_storage");
1275
+ var OAuthSessionStore = class {
1276
+ directory;
1277
+ now;
1278
+ lockWaitMs;
1279
+ constructor(directory, options = {}) {
1280
+ this.directory = directory;
1281
+ this.now = options.now ?? Date.now;
1282
+ this.lockWaitMs = options.lockWaitMs ?? 3e4;
1283
+ }
1284
+ saveLogin(profile, session) {
1285
+ requireProfile(profile);
1286
+ validateSession(session);
1287
+ this.prepareDirectory();
1288
+ const ref = randomBytes(16).toString("hex");
1289
+ this.write(ref, { version: 1, profile, state: "ready", session });
1290
+ return ref;
1291
+ }
1292
+ status(ref, profile) {
1293
+ const record = this.read(ref, profile);
1294
+ if (!record) return { state: this.isLoggedOut(ref) ? "logged_out" : "missing" };
1295
+ const s = record.session;
1296
+ return {
1297
+ state: record.state,
1298
+ profile,
1299
+ baseUrl: s.baseUrl,
1300
+ workspaceId: s.workspaceId,
1301
+ memberId: s.memberId,
1302
+ clientId: s.clientId,
1303
+ scope: s.scope,
1304
+ sessionId: s.sessionId,
1305
+ accountDisplay: s.accountDisplay,
1306
+ accessExpiresAt: s.expiresAt
1307
+ };
1308
+ }
1309
+ async resolve(ref, profile, transport) {
1310
+ const first = this.read(ref, profile);
1311
+ if (!first || first.state === "reauth_required") throw loginRequired();
1312
+ if (first.state === "ready" && !this.needsRefresh(first.session)) return authorization(first.session);
1313
+ return this.withLock(ref, async () => {
1314
+ const current = this.read(ref, profile);
1315
+ if (!current || current.state !== "ready") throw loginRequired();
1316
+ if (!this.needsRefresh(current.session)) return authorization(current.session);
1317
+ const started = this.now();
1318
+ this.write(ref, { ...current, state: "refreshing" });
1319
+ try {
1320
+ const response = await transport.refresh(current.session.refreshToken, current.session.baseUrl);
1321
+ const next = rotated(current.session, response, started);
1322
+ if (next.expiresAt <= this.now()) throw loginRequired();
1323
+ this.write(ref, { ...current, state: "ready", session: next });
1324
+ return authorization(next);
1325
+ } catch {
1326
+ if (!this.isLoggedOut(ref)) this.write(ref, { ...current, state: "reauth_required" });
1327
+ throw loginRequired();
1328
+ }
1329
+ });
1330
+ }
1331
+ async logout(ref, profile, transport) {
1332
+ const current = this.read(ref, profile);
1333
+ let remoteRevoked = false;
1334
+ try {
1335
+ if (current) {
1336
+ await transport.revoke(current.session.refreshToken, current.session.baseUrl);
1337
+ remoteRevoked = true;
1338
+ }
1339
+ } catch {
1340
+ } finally {
1341
+ this.prepareDirectory();
1342
+ this.atomicWrite(this.path(ref, ".logged-out"), "");
1343
+ remove(this.path(ref));
1344
+ }
1345
+ return { remoteRevoked };
1346
+ }
1347
+ needsRefresh(session) {
1348
+ const advance = Math.min(3e4, (session.expiresAt - session.issuedAt) / 2);
1349
+ return this.now() >= session.expiresAt - advance;
1350
+ }
1351
+ path(ref, suffix = ".json") {
1352
+ if (!/^[a-f0-9]{32}$/.test(ref)) throw unsafe();
1353
+ return join3(this.directory, ref + suffix);
1354
+ }
1355
+ isLoggedOut(ref) {
1356
+ return existsSync2(this.path(ref, ".logged-out"));
1357
+ }
1358
+ prepareDirectory() {
1359
+ if (!existsSync2(this.directory)) {
1360
+ mkdirSync2(join3(this.directory, ".."), { recursive: true, mode: 448 });
1361
+ try {
1362
+ mkdirSync2(this.directory, { mode: 448 });
1363
+ if (process.platform === "win32") checkWindowsPrivateStorage(this.directory, true);
1364
+ } catch (error) {
1365
+ if (error.code !== "EEXIST") throw unsafe();
1366
+ }
1367
+ }
1368
+ requirePrivate(this.directory, true);
1369
+ }
1370
+ read(ref, profile) {
1371
+ requireProfile(profile);
1372
+ if (this.isLoggedOut(ref)) return void 0;
1373
+ const path = this.path(ref);
1374
+ if (!existsSync2(path)) return void 0;
1375
+ requirePrivate(this.directory, true);
1376
+ requirePrivate(path, false);
1377
+ const fd = openSync2(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
1378
+ let text2;
1379
+ try {
1380
+ const stat = fstatSync(fd);
1381
+ if (stat.size > 65536 || !stat.isFile()) throw unsafe();
1382
+ text2 = readFileSync2(fd, "utf8");
1383
+ } finally {
1384
+ closeSync2(fd);
1385
+ }
1386
+ let doc;
1387
+ try {
1388
+ doc = JSON.parse(text2);
1389
+ } catch {
1390
+ throw unsafe();
1391
+ }
1392
+ if (!doc || doc.version !== 1 || !["ready", "refreshing", "reauth_required"].includes(doc.state)) throw unsafe();
1393
+ if (doc.profile !== profile) throw new OAuthCredentialError("invalid_profile");
1394
+ validateSession(doc.session);
1395
+ return this.isLoggedOut(ref) ? void 0 : doc;
1396
+ }
1397
+ write(ref, doc) {
1398
+ if (this.isLoggedOut(ref)) throw loginRequired();
1399
+ this.atomicWrite(this.path(ref), JSON.stringify(doc));
1400
+ if (this.isLoggedOut(ref)) {
1401
+ remove(this.path(ref));
1402
+ throw loginRequired();
1403
+ }
1404
+ }
1405
+ atomicWrite(path, text2) {
1406
+ this.prepareDirectory();
1407
+ const temporary = path + "." + randomBytes(12).toString("hex") + ".tmp";
1408
+ let fd;
1409
+ try {
1410
+ fd = openSync2(temporary, "wx", 384);
1411
+ writeFileSync(fd, text2);
1412
+ fsyncSync(fd);
1413
+ closeSync2(fd);
1414
+ fd = void 0;
1415
+ renameSync2(temporary, path);
1416
+ if (process.platform !== "win32") {
1417
+ const dir = openSync2(this.directory, "r");
1418
+ try {
1419
+ fsyncSync(dir);
1420
+ } finally {
1421
+ closeSync2(dir);
1422
+ }
1423
+ }
1424
+ } finally {
1425
+ if (fd !== void 0) closeSync2(fd);
1426
+ remove(temporary);
1427
+ }
1428
+ }
1429
+ async withLock(ref, task) {
1430
+ const path = this.path(ref, ".lock");
1431
+ const deadline = Date.now() + this.lockWaitMs;
1432
+ this.prepareDirectory();
1433
+ while (true) {
1434
+ let fd;
1435
+ try {
1436
+ fd = openSync2(path, "wx", 384);
1437
+ } catch (error) {
1438
+ if (error.code !== "EEXIST") throw unsafe();
1439
+ if (Date.now() >= deadline) throw new OAuthCredentialError("credential_busy");
1440
+ await delay2(25);
1441
+ continue;
1442
+ }
1443
+ try {
1444
+ writeFileSync(fd, String(process.pid));
1445
+ fsyncSync(fd);
1446
+ } catch {
1447
+ closeSync2(fd);
1448
+ remove(path);
1449
+ throw unsafe();
1450
+ }
1451
+ closeSync2(fd);
1452
+ try {
1453
+ return await task();
1454
+ } finally {
1455
+ remove(path);
1456
+ }
1457
+ }
1458
+ }
1459
+ };
1460
+ function requireProfile(profile) {
1461
+ if (typeof profile !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/.test(profile)) throw new OAuthCredentialError("invalid_profile");
1462
+ }
1463
+ function requirePrivate(path, directory) {
1464
+ const s = lstatSync(path);
1465
+ if (s.isSymbolicLink() || (directory ? !s.isDirectory() : !s.isFile())) throw unsafe();
1466
+ if (process.platform === "win32") {
1467
+ try {
1468
+ checkWindowsPrivateStorage(path, false);
1469
+ } catch {
1470
+ throw unsafe();
1471
+ }
1472
+ return;
1473
+ }
1474
+ if ((s.mode & 63) !== 0 || process.getuid && s.uid !== process.getuid()) throw unsafe();
1475
+ }
1476
+ function remove(path) {
1477
+ try {
1478
+ unlinkSync(path);
1479
+ } catch (error) {
1480
+ if (error.code !== "ENOENT") throw unsafe();
1481
+ }
1482
+ }
1483
+ function validateSession(s) {
1484
+ 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();
1485
+ for (const value of [s.workspaceId, s.memberId, s.sessionId, s.accountDisplay]) {
1486
+ if (typeof value !== "string" || !value || value.length > 256 || /[\r\n\x00-\x1f]/.test(value)) throw unsafe();
1487
+ }
1488
+ for (const value of [s.accessToken, s.refreshToken]) {
1489
+ if (typeof value !== "string" || !value || value.length > 8192 || /\s/.test(value)) throw unsafe();
1490
+ }
1491
+ let url;
1492
+ try {
1493
+ url = new URL(s.baseUrl);
1494
+ } catch {
1495
+ throw unsafe();
1496
+ }
1497
+ const loopback = ["127.0.0.1", "[::1]", "localhost"].includes(url.hostname);
1498
+ if (url.username || url.password || url.search || url.hash || url.pathname !== "/" || !(url.protocol === "https:" || loopback && url.protocol === "http:")) throw unsafe();
1499
+ }
1500
+ function rotated(current, response, issuedAt) {
1501
+ if (!response || response.token_type !== "Bearer" || response.session_id !== current.sessionId || response.scope !== current.scope || !Number.isSafeInteger(response.expires_in) || response.expires_in < 1 || response.expires_in > 3600 || response.refresh_token === current.refreshToken) throw loginRequired();
1502
+ const next = {
1503
+ ...current,
1504
+ accessToken: response.access_token,
1505
+ refreshToken: response.refresh_token,
1506
+ issuedAt,
1507
+ expiresAt: issuedAt + response.expires_in * 1e3
1508
+ };
1509
+ validateSession(next);
1510
+ return next;
1511
+ }
1512
+ function authorization(session) {
1513
+ return { scheme: "Bearer", value: session.accessToken, baseUrl: session.baseUrl, expiresAt: session.expiresAt };
1514
+ }
1515
+
1516
+ // src/oauth-profile.ts
1517
+ import { readFileSync as readFileSync3 } from "fs";
1518
+ import { dirname as dirname2, join as join4, resolve } from "path";
1519
+ function oauthProfile(reference, configPath, profile, baseUrl) {
1520
+ if (reference === void 0) return void 0;
1521
+ 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");
1522
+ const path = resolve(configPath);
1523
+ return {
1524
+ reference,
1525
+ profile,
1526
+ configPath: path,
1527
+ directory: join4(dirname2(path), "oauth-credentials"),
1528
+ baseUrl: normalizeCoreOrigin(baseUrl)
1529
+ };
1530
+ }
1531
+ var OAuthProfileProvider = class {
1532
+ selected;
1533
+ constructor(selected) {
1534
+ this.selected = selected;
1535
+ }
1536
+ async resolve() {
1537
+ const selected = this.selected;
1538
+ let raw;
1539
+ try {
1540
+ raw = JSON.parse(readFileSync3(selected.configPath, "utf8"));
1541
+ } catch {
1542
+ throw new OAuthCredentialError("login_required");
1543
+ }
1544
+ const profile = raw?.version === 2 ? raw.agents?.[selected.profile] : void 0;
1545
+ if (!profile || !Object.hasOwn(profile, "oauth_credential_ref")) throw new OAuthCredentialError("login_required");
1546
+ const current = oauthProfile(profile.oauth_credential_ref, selected.configPath, selected.profile, profile.base_url);
1547
+ if (!current || current.baseUrl !== selected.baseUrl) throw new OAuthCredentialError("login_required");
1548
+ const store = new OAuthSessionStore(current.directory);
1549
+ const status = store.status(current.reference, current.profile);
1550
+ if (!("baseUrl" in status) || typeof status.baseUrl !== "string" || normalizeCoreOrigin(status.baseUrl) !== current.baseUrl) throw new OAuthCredentialError("login_required");
1551
+ return store.resolve(current.reference, current.profile, new CoreDeviceClient(current.baseUrl));
1552
+ }
1553
+ };
996
1554
  export {
1555
+ CoreDeviceClient,
997
1556
  DOCUMENT_EVIDENCE_SECTION_TITLE,
1557
+ DeviceFlowError,
998
1558
  EXTERNAL_KNOWLEDGE_PREAMBLE_HEAD,
999
1559
  GRAPH_CONTEXT_MAX_CHARS,
1000
1560
  GRAPH_CONTEXT_SECTION_TITLE,
@@ -1002,6 +1562,9 @@ export {
1002
1562
  GRAPH_CONTEXT_TAG,
1003
1563
  GRAPH_CONTEXT_TRUNCATION_MARKER,
1004
1564
  GRAPH_CONTEXT_USAGE_NOTICE,
1565
+ OAuthCredentialError,
1566
+ OAuthProfileProvider,
1567
+ OAuthSessionStore,
1005
1568
  OFFICIAL_PRODUCTION_BASE_URLS,
1006
1569
  RECALLED_MEMORIES_SAFETY_NOTICE,
1007
1570
  RECALL_TRACE_AGENTS,
@@ -1029,6 +1592,8 @@ export {
1029
1592
  isOfficialProductionUrl,
1030
1593
  isSessionSpecificContent,
1031
1594
  normalizeBaseUrl,
1595
+ normalizeCoreOrigin,
1596
+ oauthProfile,
1032
1597
  partitionKnowledgeChunks,
1033
1598
  readRecallTraces,
1034
1599
  recall,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aliyunrds/ctxdb-shared",
3
- "version": "0.0.7",
3
+ "version": "0.0.8-beta.0",
4
4
  "type": "module",
5
5
  "description": "Shared B-rule modules + memory/KB types for RDS ContextDatabase access layers (OpenClaw fork + Qoder hooks).",
6
6
  "license": "Apache-2.0",