openmeld 0.3.42

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.
@@ -0,0 +1,746 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Nt as openMeldRootDir, hn as __exportAll } from "./base-url-Bdqmyw0D.js";
4
+ import { t as CURRENT_DAEMON_EXECUTION_CONTRACT_EPOCH } from "./runtime-transport-rv43yBPB.js";
5
+ import { join, resolve } from "node:path";
6
+ import { createHash, randomUUID } from "node:crypto";
7
+ import { mkdir, open, readFile, rename, rm, writeFile } from "node:fs/promises";
8
+ //#region src/strategy-constants.ts
9
+ const DEFAULT_GATEWAY_CHAIN_TIMEOUT_MS = 3e4;
10
+ const DEFAULT_REMOTE_SESSION_TIMEOUT_MS = 8e3;
11
+ const LOOPBACK_REMOTE_SESSION_TIMEOUT_MS = 1500;
12
+ const DEFAULT_SPACE_REQUEST_TIMEOUT_MS = 1e4;
13
+ const MIN_SPACE_REQUEST_TIMEOUT_MS = 1e3;
14
+ const MAX_SPACE_REQUEST_TIMEOUT_MS = 12e4;
15
+ const DEFAULT_HEARTBEAT_INTERVAL_MS = 25e3;
16
+ const MIN_HEARTBEAT_INTERVAL_MS = 5e3;
17
+ const MAX_HEARTBEAT_INTERVAL_MS = 6e4;
18
+ const DAEMON_STREAM_KEEPALIVE_INTERVAL_MS = 2e4;
19
+ const DAEMON_STREAM_KEEPALIVE_PONG_TIMEOUT_MS = 1e4;
20
+ const CATALOG_SYNC_RETRY_INTERVAL_MS = 5e3;
21
+ const CATALOG_SYNC_SIGNAL_POLL_INTERVAL_MS = 1e3;
22
+ const ROUTE_CATALOG_LOAD_TIMEOUT_MS = 15e3;
23
+ const RUNTIME_AGENT_CONTROLLER_REPORT_SYNC_INTERVAL_MS = 5 * 6e4;
24
+ const RUNTIME_AGENT_CONTROLLER_REPORT_SYNC_RETRY_INTERVAL_MS = 6e4;
25
+ const PROVIDER_CONVERSATION_PROOF_SYNC_INTERVAL_MS = 5 * 6e4;
26
+ const PROVIDER_CONVERSATION_PROOF_SYNC_RETRY_INTERVAL_MS = 6e4;
27
+ const RUNTIME_STATE_PERSIST_MIN_INTERVAL_MS = 6e4;
28
+ const DAEMON_STREAM_CONNECT_TIMEOUT_MS = 15e3;
29
+ const REGISTER_RESPONSE_TIMEOUT_MS = 1e4;
30
+ const HEARTBEAT_RESPONSE_TIMEOUT_MS = 15e3;
31
+ const DAEMON_RUNTIME_CONTRACT_EPOCH = CURRENT_DAEMON_EXECUTION_CONTRACT_EPOCH;
32
+ const OPENCLAW_CLI_TIMEOUT_MS = 3e4;
33
+ //#endregion
34
+ //#region src/config/token-store.ts
35
+ const TOKEN_STORE_KEYCHAIN_SERVICE = "sh.openmeld.auth.token";
36
+ const INSECURE_FALLBACK_ENV_KEY = "OPENMELD_AUTH_ALLOW_INSECURE_FILE_TOKEN";
37
+ const FORCE_INSECURE_STORE_ENV_KEY = "OPENMELD_AUTH_FORCE_INSECURE_FILE_TOKEN";
38
+ const COMMAND_MAX_BUFFER_BYTES = 1024 * 1024;
39
+ const COMMAND_TIMEOUT_MS = 3e4;
40
+ const TOKEN_FILE_KEY_PATTERN = /^[a-f0-9]{64}$/u;
41
+ const EXEC_FILE_BASE_OPTIONS = {
42
+ windowsHide: true,
43
+ maxBuffer: COMMAND_MAX_BUFFER_BYTES,
44
+ timeout: COMMAND_TIMEOUT_MS
45
+ };
46
+ let insecureFallbackWarningShown = false;
47
+ async function storeAccessToken(input) {
48
+ const key = buildTokenKey({
49
+ authBaseUrl: input.authBaseUrl,
50
+ clientId: input.clientId
51
+ });
52
+ if (shouldForceInsecureStore()) {
53
+ await setInsecureFileToken({
54
+ key,
55
+ accessToken: input.accessToken
56
+ });
57
+ return {
58
+ provider: "insecure-file",
59
+ key
60
+ };
61
+ }
62
+ if (process.platform === "darwin") try {
63
+ const account = buildMacOsAccount(key);
64
+ await setMacOsKeychainToken({
65
+ service: TOKEN_STORE_KEYCHAIN_SERVICE,
66
+ account,
67
+ accessToken: input.accessToken
68
+ });
69
+ return {
70
+ provider: "macos-keychain",
71
+ service: TOKEN_STORE_KEYCHAIN_SERVICE,
72
+ account
73
+ };
74
+ } catch (error) {
75
+ if (allowInsecureFallback()) {
76
+ await setInsecureFileToken({
77
+ key,
78
+ accessToken: input.accessToken
79
+ });
80
+ return {
81
+ provider: "insecure-file",
82
+ key
83
+ };
84
+ }
85
+ throw new Error(`failed to store access token in macOS Keychain: ${toErrorMessage(error)}`);
86
+ }
87
+ if (process.platform === "win32") try {
88
+ await setWindowsDpapiToken({
89
+ key,
90
+ accessToken: input.accessToken
91
+ });
92
+ return {
93
+ provider: "windows-dpapi",
94
+ key
95
+ };
96
+ } catch (error) {
97
+ if (allowInsecureFallback()) {
98
+ await setInsecureFileToken({
99
+ key,
100
+ accessToken: input.accessToken
101
+ });
102
+ return {
103
+ provider: "insecure-file",
104
+ key
105
+ };
106
+ }
107
+ throw new Error(`failed to store access token via Windows DPAPI: ${toErrorMessage(error)}`);
108
+ }
109
+ if (allowInsecureFallback()) {
110
+ await setInsecureFileToken({
111
+ key,
112
+ accessToken: input.accessToken
113
+ });
114
+ return {
115
+ provider: "insecure-file",
116
+ key
117
+ };
118
+ }
119
+ throw new Error(`secure token storage is unavailable on platform "${process.platform}". Use ${INSECURE_FALLBACK_ENV_KEY}=1 only for local testing fallback.`);
120
+ }
121
+ async function readAccessToken(ref) {
122
+ try {
123
+ if (ref.provider === "macos-keychain") return toAccessTokenReadResult(await getMacOsKeychainToken(ref));
124
+ if (ref.provider === "windows-dpapi") return toAccessTokenReadResult(await getWindowsDpapiToken(ref));
125
+ if (ref.provider === "insecure-file") return toAccessTokenReadResult(await getInsecureFileToken(ref));
126
+ return { status: "missing" };
127
+ } catch (error) {
128
+ return {
129
+ errorMessage: toErrorMessage(error),
130
+ status: "unavailable"
131
+ };
132
+ }
133
+ }
134
+ async function deleteAccessToken(ref) {
135
+ if (ref.provider === "macos-keychain") {
136
+ await deleteMacOsKeychainToken(ref).catch(() => void 0);
137
+ return;
138
+ }
139
+ if (ref.provider === "windows-dpapi") {
140
+ await deleteWindowsDpapiToken(ref).catch(() => void 0);
141
+ return;
142
+ }
143
+ if (ref.provider === "insecure-file") await deleteInsecureFileToken(ref).catch(() => void 0);
144
+ }
145
+ function isStoredTokenRef(value) {
146
+ if (!(value && typeof value === "object")) return false;
147
+ const record = value;
148
+ const provider = normalizeString(record.provider);
149
+ if (provider === "macos-keychain") return Boolean(normalizeString(record.service) && normalizeString(record.account));
150
+ if (provider === "windows-dpapi" || provider === "insecure-file") return normalizeTokenFileKey(record.key) !== null;
151
+ return false;
152
+ }
153
+ function buildTokenKey(input) {
154
+ const rootDir = resolve(openMeldRootDir());
155
+ return createHash("sha256").update(`${input.clientId}\n${input.authBaseUrl}\n${rootDir}`).digest("hex");
156
+ }
157
+ function buildMacOsAccount(key) {
158
+ return `openmeld-auth-${key}`;
159
+ }
160
+ async function setMacOsKeychainToken(input) {
161
+ await runCommand("security", [
162
+ "add-generic-password",
163
+ "-U",
164
+ "-a",
165
+ input.account,
166
+ "-s",
167
+ input.service,
168
+ "-w",
169
+ input.accessToken
170
+ ]);
171
+ }
172
+ async function getMacOsKeychainToken(input) {
173
+ try {
174
+ return (await runCommand("security", [
175
+ "find-generic-password",
176
+ "-a",
177
+ input.account,
178
+ "-s",
179
+ input.service,
180
+ "-w"
181
+ ])).stdout.trim();
182
+ } catch (error) {
183
+ if (isMacOsNotFoundError(error)) return null;
184
+ throw error;
185
+ }
186
+ }
187
+ async function deleteMacOsKeychainToken(input) {
188
+ try {
189
+ await runCommand("security", [
190
+ "delete-generic-password",
191
+ "-a",
192
+ input.account,
193
+ "-s",
194
+ input.service
195
+ ]);
196
+ } catch (error) {
197
+ if (isMacOsNotFoundError(error)) return;
198
+ throw error;
199
+ }
200
+ }
201
+ async function setWindowsDpapiToken(input) {
202
+ const encrypted = await runPowerShellScript(WINDOWS_DPAPI_ENCRYPT_SCRIPT, input.accessToken, input.key);
203
+ await mkdir(tokenDirectoryPath(), { recursive: true });
204
+ await writeFile(windowsDpapiTokenPath(input.key), `${encrypted.trim()}\n`, {
205
+ encoding: "utf8",
206
+ mode: 384
207
+ });
208
+ }
209
+ async function getWindowsDpapiToken(input) {
210
+ const encrypted = await readFile(windowsDpapiTokenPath(input.key), "utf8").catch((error) => {
211
+ if (isFileNotFoundError(error)) return null;
212
+ throw error;
213
+ });
214
+ if (!encrypted) return null;
215
+ return runPowerShellScript(WINDOWS_DPAPI_DECRYPT_SCRIPT, encrypted.trim(), input.key);
216
+ }
217
+ async function deleteWindowsDpapiToken(input) {
218
+ await rm(windowsDpapiTokenPath(input.key), { force: true });
219
+ }
220
+ async function setInsecureFileToken(input) {
221
+ await mkdir(tokenDirectoryPath(), { recursive: true });
222
+ await writeFile(insecureTokenPath(input.key), `${input.accessToken}\n`, {
223
+ encoding: "utf8",
224
+ mode: 384
225
+ });
226
+ }
227
+ async function getInsecureFileToken(input) {
228
+ const raw = await readFile(insecureTokenPath(input.key), "utf8").catch((error) => {
229
+ if (isFileNotFoundError(error)) return null;
230
+ throw error;
231
+ });
232
+ return raw ? raw.trim() : null;
233
+ }
234
+ async function deleteInsecureFileToken(input) {
235
+ await rm(insecureTokenPath(input.key), { force: true });
236
+ }
237
+ function tokenDirectoryPath() {
238
+ return join(openMeldRootDir(), "auth", "tokens");
239
+ }
240
+ function windowsDpapiTokenPath(key) {
241
+ return join(tokenDirectoryPath(), `${requireValidTokenFileKey(key)}.dpapi`);
242
+ }
243
+ function insecureTokenPath(key) {
244
+ return join(tokenDirectoryPath(), `${requireValidTokenFileKey(key)}.token`);
245
+ }
246
+ function allowInsecureFallback() {
247
+ if (process.env[INSECURE_FALLBACK_ENV_KEY] === "1") {
248
+ warnInsecureFallback("env");
249
+ return true;
250
+ }
251
+ const fallbackEnabled = !isNativeSecureStorePlatform();
252
+ if (fallbackEnabled) warnInsecureFallback("platform");
253
+ return fallbackEnabled;
254
+ }
255
+ function warnInsecureFallback(reason) {
256
+ if (insecureFallbackWarningShown) return;
257
+ insecureFallbackWarningShown = true;
258
+ const reasonText = reason === "env" ? `enabled by ${INSECURE_FALLBACK_ENV_KEY}=1` : `enabled automatically on platform "${process.platform}"`;
259
+ console.warn(`[openmeld auth] plaintext file token storage fallback is active (${reasonText}). Unset ${INSECURE_FALLBACK_ENV_KEY} to disable explicit opt-in, and prefer macOS or Windows for native secure token storage.`);
260
+ }
261
+ function shouldForceInsecureStore() {
262
+ return process.env[FORCE_INSECURE_STORE_ENV_KEY] === "1";
263
+ }
264
+ function isNativeSecureStorePlatform() {
265
+ return process.platform === "darwin" || process.platform === "win32";
266
+ }
267
+ async function runPowerShellScript(script, token, entropy) {
268
+ const payload = JSON.stringify({
269
+ token,
270
+ entropy
271
+ });
272
+ return (await runCommand("powershell.exe", [
273
+ "-NoProfile",
274
+ "-NonInteractive",
275
+ "-ExecutionPolicy",
276
+ "Bypass",
277
+ "-Command",
278
+ script
279
+ ], payload)).stdout.trim();
280
+ }
281
+ async function runCommand(command, args, stdinPayload) {
282
+ try {
283
+ if (stdinPayload === void 0) return await runExecFile(command, args);
284
+ const { execFile } = await import("node:child_process");
285
+ return await new Promise((resolve, reject) => {
286
+ let stdout = "";
287
+ let stderr = "";
288
+ const child = execFile(command, args, { ...EXEC_FILE_BASE_OPTIONS });
289
+ child.stdout?.setEncoding("utf8");
290
+ child.stderr?.setEncoding("utf8");
291
+ child.stdout?.on("data", (chunk) => {
292
+ stdout += String(chunk);
293
+ });
294
+ child.stderr?.on("data", (chunk) => {
295
+ stderr += String(chunk);
296
+ });
297
+ child.on("error", (error) => {
298
+ reject(toCommandError(error, command));
299
+ });
300
+ child.on("close", (code) => {
301
+ if (code === 0) {
302
+ resolve({
303
+ stdout,
304
+ stderr
305
+ });
306
+ return;
307
+ }
308
+ reject(toCommandError({ message: `command failed (${command}) with exit code ${code}` }, command));
309
+ });
310
+ child.stdin?.write(stdinPayload);
311
+ child.stdin?.end();
312
+ });
313
+ } catch (error) {
314
+ throw toCommandError(error, command);
315
+ }
316
+ }
317
+ async function runExecFile(command, args) {
318
+ const { execFile } = await import("node:child_process");
319
+ return await new Promise((resolve, reject) => {
320
+ execFile(command, args, {
321
+ ...EXEC_FILE_BASE_OPTIONS,
322
+ encoding: "utf8"
323
+ }, (error, stdout, stderr) => {
324
+ if (error) {
325
+ reject(toCommandError(error, command));
326
+ return;
327
+ }
328
+ resolve({
329
+ stdout: String(stdout ?? ""),
330
+ stderr: String(stderr ?? "")
331
+ });
332
+ });
333
+ });
334
+ }
335
+ function toCommandError(error, command) {
336
+ if (!(error && typeof error === "object")) return /* @__PURE__ */ new Error(`command failed (${command}): ${String(error)}`);
337
+ const e = error;
338
+ const code = typeof e.code === "number" ? e.code : null;
339
+ const messageParts = [`command failed (${command})`];
340
+ if (typeof e.stderr === "string" && e.stderr.trim()) messageParts.push(`stderr: ${e.stderr.trim()}`);
341
+ else if (e.message) messageParts.push(e.message);
342
+ const wrapped = new Error(messageParts.join("; "));
343
+ wrapped.code = code;
344
+ wrapped.stdout = e.stdout;
345
+ wrapped.stderr = e.stderr;
346
+ return wrapped;
347
+ }
348
+ function isMacOsNotFoundError(error) {
349
+ if (!(error && typeof error === "object")) return false;
350
+ const record = error;
351
+ const text = `${record.stderr ?? ""} ${record.message ?? ""}`.toLowerCase();
352
+ return record.code === 44 || text.includes("could not be found in the keychain") || text.includes("the specified item could not be found");
353
+ }
354
+ function normalizeString(value) {
355
+ return String(value ?? "").trim();
356
+ }
357
+ function normalizeTokenFileKey(value) {
358
+ const normalized = normalizeString(value);
359
+ if (!TOKEN_FILE_KEY_PATTERN.test(normalized)) return null;
360
+ return normalized;
361
+ }
362
+ function requireValidTokenFileKey(value) {
363
+ const normalized = normalizeTokenFileKey(value);
364
+ if (normalized === null) throw new Error("stored token key is invalid");
365
+ return normalized;
366
+ }
367
+ function normalizeToken(value) {
368
+ if (value === null) return null;
369
+ const normalized = value.trim();
370
+ return normalized.length > 0 ? normalized : null;
371
+ }
372
+ function toAccessTokenReadResult(value) {
373
+ const accessToken = normalizeToken(value);
374
+ if (!accessToken) return { status: "missing" };
375
+ return {
376
+ accessToken,
377
+ status: "found"
378
+ };
379
+ }
380
+ function toErrorMessage(error) {
381
+ if (error instanceof Error) return error.message;
382
+ return String(error);
383
+ }
384
+ function isFileNotFoundError(error) {
385
+ return Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT");
386
+ }
387
+ const WINDOWS_DPAPI_ENCRYPT_SCRIPT = [
388
+ "$ErrorActionPreference='Stop'",
389
+ "$rawInput = [Console]::In.ReadToEnd()",
390
+ "$payload = $rawInput | ConvertFrom-Json",
391
+ "$plain=[System.Text.Encoding]::UTF8.GetBytes($payload.token)",
392
+ "$entropy=[System.Text.Encoding]::UTF8.GetBytes($payload.entropy)",
393
+ "$cipher=[System.Security.Cryptography.ProtectedData]::Protect($plain,$entropy,[System.Security.Cryptography.DataProtectionScope]::CurrentUser)",
394
+ "[System.Convert]::ToBase64String($cipher)"
395
+ ].join(";");
396
+ const WINDOWS_DPAPI_DECRYPT_SCRIPT = [
397
+ "$ErrorActionPreference='Stop'",
398
+ "$rawInput = [Console]::In.ReadToEnd()",
399
+ "$payload = $rawInput | ConvertFrom-Json",
400
+ "$cipher=[System.Convert]::FromBase64String($payload.token)",
401
+ "$entropy=[System.Text.Encoding]::UTF8.GetBytes($payload.entropy)",
402
+ "$plain=[System.Security.Cryptography.ProtectedData]::Unprotect($cipher,$entropy,[System.Security.Cryptography.DataProtectionScope]::CurrentUser)",
403
+ "[System.Text.Encoding]::UTF8.GetString($plain)"
404
+ ].join(";");
405
+ //#endregion
406
+ //#region src/config/auth-session.ts
407
+ var auth_session_exports = /* @__PURE__ */ __exportAll({
408
+ clearAuthSession: () => clearAuthSession,
409
+ getAuthSession: () => getAuthSession,
410
+ getAuthSessionMetadata: () => getAuthSessionMetadata,
411
+ getAuthSessionState: () => getAuthSessionState,
412
+ getCurrentAuthOwnerUserId: () => getCurrentAuthOwnerUserId,
413
+ normalizeStoredActiveOrganization: () => normalizeStoredActiveOrganization,
414
+ setAuthSession: () => setAuthSession,
415
+ updateAuthSessionActiveOrganization: () => updateAuthSessionActiveOrganization
416
+ });
417
+ const TRAILING_SLASHES_RE = /\/+$/u;
418
+ async function getAuthSessionState(options = {}) {
419
+ const localState = await getLocalAuthSessionState();
420
+ switch (localState.state) {
421
+ case "missing":
422
+ case "invalid":
423
+ case "expired": return localState;
424
+ case "valid": break;
425
+ default: return localState;
426
+ }
427
+ if (!options.validateRemote) return localState;
428
+ const remoteValidation = await getRemoteAuthSessionState(localState.session);
429
+ if (remoteValidation.state === "valid") return localState;
430
+ if (remoteValidation.state === "unknown") return localState;
431
+ return {
432
+ reasonCode: resolveRemoteAuthSessionReasonCode(remoteValidation.state),
433
+ state: remoteValidation.state,
434
+ session: null
435
+ };
436
+ }
437
+ async function getCurrentAuthOwnerUserId() {
438
+ const ownerUserId = (await getAuthSessionState({ validateRemote: false }).catch(() => null))?.session?.user?.id?.trim() ?? "";
439
+ return ownerUserId.length > 0 ? ownerUserId : null;
440
+ }
441
+ async function setAuthSession(input) {
442
+ const accessToken = String(input.accessToken ?? "").trim();
443
+ if (!accessToken) throw new Error("access token is required");
444
+ const authBaseUrl = String(input.authBaseUrl ?? "").trim();
445
+ if (!authBaseUrl) throw new Error("auth base URL is required");
446
+ const clientId = String(input.clientId ?? "").trim();
447
+ if (!clientId) throw new Error("client_id is required");
448
+ const tokenRef = await storeAccessToken({
449
+ accessToken,
450
+ authBaseUrl,
451
+ clientId
452
+ });
453
+ const payload = {
454
+ v: 1,
455
+ tokenRef,
456
+ tokenType: String(input.tokenType ?? "Bearer").trim() || "Bearer",
457
+ authBaseUrl,
458
+ clientId,
459
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
460
+ ...input.expiresAt ? { expiresAt: input.expiresAt } : {},
461
+ ...input.scope ? { scope: input.scope } : {},
462
+ ...buildAuthUserPayload(input.user),
463
+ ...buildActiveOrganizationPayload(input.activeOrganization)
464
+ };
465
+ const path = authSessionPath();
466
+ await mkdir(join(openMeldRootDir(), "auth"), { recursive: true });
467
+ try {
468
+ await writeSessionPayloadAtomically(path, payload);
469
+ } catch (error) {
470
+ await deleteAccessToken(tokenRef).catch(() => void 0);
471
+ throw error;
472
+ }
473
+ }
474
+ async function getAuthSession() {
475
+ const localState = await getLocalAuthSessionState();
476
+ if (localState.state === "missing" || localState.state === "invalid") return null;
477
+ return localState.session;
478
+ }
479
+ async function getAuthSessionMetadata() {
480
+ const payload = await readSessionPayload();
481
+ if (payload?.v !== 1) return null;
482
+ const authBaseUrl = normalizeOptionalString(payload.authBaseUrl);
483
+ const clientId = normalizeOptionalString(payload.clientId);
484
+ const createdAt = normalizeOptionalString(payload.createdAt);
485
+ if (!(authBaseUrl && clientId && createdAt)) return null;
486
+ return {
487
+ authBaseUrl,
488
+ clientId,
489
+ createdAt,
490
+ ...typeof payload.expiresAt === "string" && payload.expiresAt.trim() ? { expiresAt: payload.expiresAt.trim() } : {},
491
+ ...typeof payload.scope === "string" && payload.scope.trim() ? { scope: payload.scope.trim() } : {},
492
+ ...buildAuthUserPayload(payload.user),
493
+ ...buildActiveOrganizationPayload(payload.activeOrganization)
494
+ };
495
+ }
496
+ async function updateAuthSessionActiveOrganization(activeOrganization) {
497
+ const payload = await readSessionPayload();
498
+ if (payload?.v !== 1) throw new Error("cannot update active organization without auth session");
499
+ const tokenRef = normalizeTokenRef(payload.tokenRef);
500
+ const tokenType = normalizeOptionalString(payload.tokenType);
501
+ const authBaseUrl = normalizeOptionalString(payload.authBaseUrl);
502
+ const clientId = normalizeOptionalString(payload.clientId);
503
+ const createdAt = normalizeOptionalString(payload.createdAt);
504
+ if (!(tokenRef && tokenType && authBaseUrl && clientId && createdAt)) throw new Error("cannot update active organization for invalid auth session");
505
+ const nextPayload = {
506
+ v: 1,
507
+ tokenRef,
508
+ tokenType,
509
+ authBaseUrl,
510
+ clientId,
511
+ createdAt,
512
+ ...typeof payload.expiresAt === "string" && payload.expiresAt.trim() ? { expiresAt: payload.expiresAt.trim() } : {},
513
+ ...typeof payload.scope === "string" && payload.scope.trim() ? { scope: payload.scope.trim() } : {},
514
+ ...buildAuthUserPayload(payload.user),
515
+ ...buildActiveOrganizationPayload(activeOrganization)
516
+ };
517
+ await writeSessionPayloadAtomically(authSessionPath(), nextPayload);
518
+ }
519
+ async function clearAuthSession() {
520
+ const payload = await readSessionPayload();
521
+ if (payload?.v === 1 && isStoredTokenRef(payload.tokenRef)) await deleteAccessToken(payload.tokenRef).catch(() => void 0);
522
+ await removeSessionFileBestEffort();
523
+ }
524
+ async function getLocalAuthSessionState() {
525
+ const payload = await readSessionPayload();
526
+ if (payload?.v !== 1) return {
527
+ reasonCode: "session_missing",
528
+ state: "missing",
529
+ session: null
530
+ };
531
+ const tokenRef = normalizeTokenRef(payload.tokenRef);
532
+ if (!tokenRef) return {
533
+ reasonCode: "session_token_ref_invalid",
534
+ state: "invalid",
535
+ session: null
536
+ };
537
+ const tokenType = normalizeOptionalString(payload.tokenType);
538
+ const authBaseUrl = normalizeOptionalString(payload.authBaseUrl);
539
+ const clientId = normalizeOptionalString(payload.clientId);
540
+ const createdAt = normalizeOptionalString(payload.createdAt);
541
+ if (!(tokenType && authBaseUrl && clientId && createdAt)) return {
542
+ reasonCode: "session_metadata_invalid",
543
+ state: "invalid",
544
+ session: null
545
+ };
546
+ const accessToken = await readAccessToken(tokenRef);
547
+ if (accessToken.status === "missing") return {
548
+ reasonCode: "session_token_missing",
549
+ state: "invalid",
550
+ session: null
551
+ };
552
+ if (accessToken.status === "unavailable") return {
553
+ errorMessage: accessToken.errorMessage,
554
+ reasonCode: "session_token_unavailable",
555
+ state: "invalid",
556
+ session: null
557
+ };
558
+ const session = {
559
+ v: 1,
560
+ accessToken: accessToken.accessToken,
561
+ tokenType,
562
+ authBaseUrl,
563
+ clientId,
564
+ createdAt,
565
+ ...typeof payload.expiresAt === "string" && payload.expiresAt.trim() ? { expiresAt: payload.expiresAt.trim() } : {},
566
+ ...typeof payload.scope === "string" && payload.scope.trim() ? { scope: payload.scope.trim() } : {},
567
+ ...buildAuthUserPayload(payload.user),
568
+ ...buildActiveOrganizationPayload(payload.activeOrganization)
569
+ };
570
+ if (isExpired(session.expiresAt)) return {
571
+ reasonCode: "session_expired",
572
+ state: "expired",
573
+ session
574
+ };
575
+ return {
576
+ state: "valid",
577
+ session
578
+ };
579
+ }
580
+ function normalizeTokenRef(input) {
581
+ if (!isStoredTokenRef(input)) return null;
582
+ return input;
583
+ }
584
+ async function getRemoteAuthSessionState(session) {
585
+ let endpoint = "";
586
+ try {
587
+ endpoint = toAuthEndpoint(session.authBaseUrl, "/api/auth/get-session");
588
+ } catch {
589
+ return { state: "invalid" };
590
+ }
591
+ try {
592
+ const timeoutMs = resolveRemoteSessionTimeoutMs(endpoint);
593
+ const response = await fetch(endpoint, {
594
+ method: "GET",
595
+ headers: { Authorization: `${normalizeTokenType(session.tokenType)} ${session.accessToken}` },
596
+ signal: AbortSignal.timeout(timeoutMs)
597
+ });
598
+ if (response.status === 401 || response.status === 403) return { state: "revoked" };
599
+ if (!response.ok) return { state: "unknown" };
600
+ if (!hasActiveSessionPayload(await parseResponseJson(response))) return { state: "invalid" };
601
+ return { state: "valid" };
602
+ } catch {
603
+ return { state: "unknown" };
604
+ }
605
+ }
606
+ function resolveRemoteSessionTimeoutMs(endpoint) {
607
+ const url = parseUrlOrNull(endpoint);
608
+ if (url && isLoopbackHostname(url.hostname)) return LOOPBACK_REMOTE_SESSION_TIMEOUT_MS;
609
+ return DEFAULT_REMOTE_SESSION_TIMEOUT_MS;
610
+ }
611
+ function parseUrlOrNull(value) {
612
+ try {
613
+ return new URL(value);
614
+ } catch {
615
+ return null;
616
+ }
617
+ }
618
+ function isLoopbackHostname(hostname) {
619
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
620
+ }
621
+ function hasActiveSessionPayload(payload) {
622
+ if (!isRecord(payload)) return false;
623
+ return isRecord(payload.session) && isRecord(payload.user);
624
+ }
625
+ function isExpired(expiresAt) {
626
+ if (!expiresAt) return false;
627
+ const expiresAtMs = Date.parse(expiresAt);
628
+ if (Number.isNaN(expiresAtMs)) return false;
629
+ return expiresAtMs <= Date.now();
630
+ }
631
+ function resolveRemoteAuthSessionReasonCode(state) {
632
+ switch (state) {
633
+ case "invalid": return "remote_session_invalid";
634
+ case "revoked": return "remote_session_revoked";
635
+ default: throw new Error(`unsupported remote auth session state: ${state}`);
636
+ }
637
+ }
638
+ async function readSessionPayload() {
639
+ const raw = await readFile(authSessionPath(), "utf8").catch(() => null);
640
+ if (!raw) return null;
641
+ try {
642
+ return JSON.parse(raw);
643
+ } catch {
644
+ return null;
645
+ }
646
+ }
647
+ function authSessionPath() {
648
+ return join(openMeldRootDir(), "auth", "session.json");
649
+ }
650
+ async function writeSessionPayloadAtomically(path, payload) {
651
+ const tempPath = `${path}.tmp-${process.pid}-${Date.now()}-${randomUUID()}`;
652
+ try {
653
+ const file = await open(tempPath, "wx");
654
+ try {
655
+ await file.writeFile(`${JSON.stringify(payload, null, 2)}\n`, "utf8");
656
+ await file.sync();
657
+ } finally {
658
+ await file.close();
659
+ }
660
+ await rename(tempPath, path);
661
+ } catch (error) {
662
+ await rm(tempPath, { force: true }).catch(() => void 0);
663
+ throw error;
664
+ }
665
+ }
666
+ function buildAuthUserPayload(value) {
667
+ if (!(value && typeof value === "object")) return {};
668
+ const record = value;
669
+ const id = normalizeOptionalString(record.id);
670
+ const email = normalizeOptionalString(record.email);
671
+ const name = normalizeOptionalString(record.name);
672
+ if (!(id || email || name)) return {};
673
+ return { user: {
674
+ ...id ? { id } : {},
675
+ ...email ? { email } : {},
676
+ ...name ? { name } : {}
677
+ } };
678
+ }
679
+ function buildActiveOrganizationPayload(value) {
680
+ const activeOrganization = normalizeStoredActiveOrganization(value);
681
+ return activeOrganization ? { activeOrganization } : {};
682
+ }
683
+ function normalizeStoredActiveOrganization(value) {
684
+ if (!isRecord(value)) return null;
685
+ const id = normalizeOptionalString(value.id);
686
+ const slug = normalizeOptionalString(value.slug);
687
+ const name = normalizeOptionalString(value.name);
688
+ const role = normalizeOptionalString(value.role);
689
+ const personal = readOrganizationIsPersonal(value);
690
+ if (!(id && slug && name)) return null;
691
+ return {
692
+ id,
693
+ slug,
694
+ name,
695
+ ...role ? { role } : {},
696
+ ...personal ? { personal } : {}
697
+ };
698
+ }
699
+ function normalizeTokenType(value) {
700
+ const normalized = value.trim();
701
+ return normalized.length > 0 ? normalized : "Bearer";
702
+ }
703
+ function normalizeOptionalString(value) {
704
+ const normalized = String(value ?? "").trim();
705
+ return normalized.length > 0 ? normalized : null;
706
+ }
707
+ function readOrganizationIsPersonal(value) {
708
+ if (value.personal === true || value.isPersonal === true) return true;
709
+ return readOrganizationMetadata(value.metadata)?.personal === true;
710
+ }
711
+ function readOrganizationMetadata(metadata) {
712
+ if (metadata === null || metadata === void 0) return null;
713
+ if (typeof metadata === "string") try {
714
+ const parsed = JSON.parse(metadata);
715
+ return isRecord(parsed) ? parsed : null;
716
+ } catch {
717
+ return null;
718
+ }
719
+ return isRecord(metadata) ? metadata : null;
720
+ }
721
+ function toAuthEndpoint(baseUrl, pathname) {
722
+ const url = new URL(baseUrl);
723
+ url.pathname = `${url.pathname.replace(TRAILING_SLASHES_RE, "")}${pathname.startsWith("/") ? pathname : `/${pathname}`}` || "/";
724
+ url.search = "";
725
+ url.hash = "";
726
+ return url.toString();
727
+ }
728
+ function isRecord(value) {
729
+ return Boolean(value && typeof value === "object");
730
+ }
731
+ async function parseResponseJson(response) {
732
+ const raw = await response.text();
733
+ if (!raw) return null;
734
+ try {
735
+ return JSON.parse(raw);
736
+ } catch {
737
+ return { message: raw };
738
+ }
739
+ }
740
+ async function removeSessionFileBestEffort() {
741
+ await rm(authSessionPath(), { force: true }).catch(() => void 0);
742
+ }
743
+ //#endregion
744
+ export { RUNTIME_AGENT_CONTROLLER_REPORT_SYNC_RETRY_INTERVAL_MS as A, MIN_SPACE_REQUEST_TIMEOUT_MS as C, REGISTER_RESPONSE_TIMEOUT_MS as D, PROVIDER_CONVERSATION_PROOF_SYNC_RETRY_INTERVAL_MS as E, ROUTE_CATALOG_LOAD_TIMEOUT_MS as O, MIN_HEARTBEAT_INTERVAL_MS as S, PROVIDER_CONVERSATION_PROOF_SYNC_INTERVAL_MS as T, DEFAULT_HEARTBEAT_INTERVAL_MS as _, getAuthSessionState as a, MAX_HEARTBEAT_INTERVAL_MS as b, setAuthSession as c, CATALOG_SYNC_SIGNAL_POLL_INTERVAL_MS as d, DAEMON_RUNTIME_CONTRACT_EPOCH as f, DEFAULT_GATEWAY_CHAIN_TIMEOUT_MS as g, DAEMON_STREAM_KEEPALIVE_PONG_TIMEOUT_MS as h, getAuthSessionMetadata as i, RUNTIME_STATE_PERSIST_MIN_INTERVAL_MS as j, RUNTIME_AGENT_CONTROLLER_REPORT_SYNC_INTERVAL_MS as k, updateAuthSessionActiveOrganization as l, DAEMON_STREAM_KEEPALIVE_INTERVAL_MS as m, clearAuthSession as n, getCurrentAuthOwnerUserId as o, DAEMON_STREAM_CONNECT_TIMEOUT_MS as p, getAuthSession as r, normalizeStoredActiveOrganization as s, auth_session_exports as t, CATALOG_SYNC_RETRY_INTERVAL_MS as u, DEFAULT_SPACE_REQUEST_TIMEOUT_MS as v, OPENCLAW_CLI_TIMEOUT_MS as w, MAX_SPACE_REQUEST_TIMEOUT_MS as x, HEARTBEAT_RESPONSE_TIMEOUT_MS as y };
745
+
746
+ //# sourceMappingURL=auth-session-_ideKYyk.js.map