@letta-ai/letta-code 0.33.6 → 0.33.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.
Files changed (35) hide show
  1. package/dist/agent-presets.js.map +1 -1
  2. package/dist/channels-public.js.map +1 -1
  3. package/dist/mcp-client.js +53 -11
  4. package/dist/mcp-client.js.map +3 -3
  5. package/dist/mcp-oauth.js +18545 -0
  6. package/dist/mcp-oauth.js.map +155 -0
  7. package/dist/types/agent/client-skills.d.ts.map +1 -1
  8. package/dist/types/agent/skills.d.ts +5 -0
  9. package/dist/types/agent/skills.d.ts.map +1 -1
  10. package/dist/types/backend/backend.d.ts +2 -0
  11. package/dist/types/backend/backend.d.ts.map +1 -1
  12. package/dist/types/backend/dev/fake-headless-backend.d.ts +2 -0
  13. package/dist/types/backend/dev/fake-headless-backend.d.ts.map +1 -1
  14. package/dist/types/backend/dev/pi-ollama-provider.d.ts +7 -16
  15. package/dist/types/backend/dev/pi-ollama-provider.d.ts.map +1 -1
  16. package/dist/types/constants.d.ts +1 -1
  17. package/dist/types/constants.d.ts.map +1 -1
  18. package/dist/types/experiments/manager.d.ts.map +1 -1
  19. package/dist/types/experiments/types.d.ts +1 -1
  20. package/dist/types/experiments/types.d.ts.map +1 -1
  21. package/dist/types/mcp-client.d.ts +6 -0
  22. package/dist/types/mcp-client.d.ts.map +1 -1
  23. package/dist/types/mcp-oauth-callback.d.ts +11 -0
  24. package/dist/types/mcp-oauth-callback.d.ts.map +1 -0
  25. package/dist/types/mcp-oauth-public.d.ts +55 -0
  26. package/dist/types/mcp-oauth-public.d.ts.map +1 -0
  27. package/dist/types/mcp-oauth.d.ts +56 -0
  28. package/dist/types/mcp-oauth.d.ts.map +1 -0
  29. package/dist/types/tools/toolset-catalog.d.ts +1 -1
  30. package/dist/types/types/protocol_v2.d.ts +1 -1
  31. package/dist/types/types/protocol_v2.d.ts.map +1 -1
  32. package/dist/types/utils/secrets.d.ts.map +1 -1
  33. package/letta.js +868 -237
  34. package/package.json +11 -1
  35. package/skills/curating-memory-palace/SKILL.md +105 -0
package/letta.js CHANGED
@@ -4358,7 +4358,7 @@ function isDuplicateKeychainItemError(error) {
4358
4358
  return message.includes("already exists in the keychain") || message.includes("code: -25299");
4359
4359
  }
4360
4360
  async function getSecretValue(name, label) {
4361
- if (!secretsAvailable && !secretGetOverrideForTests) {
4361
+ if (!secrets && !secretGetOverrideForTests) {
4362
4362
  return null;
4363
4363
  }
4364
4364
  try {
@@ -4381,7 +4381,7 @@ async function getSecretValue(name, label) {
4381
4381
  }
4382
4382
  }
4383
4383
  async function setSecretValue(name, value) {
4384
- if (!secretsAvailable) {
4384
+ if (!secrets) {
4385
4385
  throw new Error("Secrets API unavailable");
4386
4386
  }
4387
4387
  try {
@@ -4409,7 +4409,7 @@ async function setSecretValue(name, value) {
4409
4409
  });
4410
4410
  }
4411
4411
  async function deleteSecretValue(name) {
4412
- if (!secretsAvailable) {
4412
+ if (!secrets) {
4413
4413
  return false;
4414
4414
  }
4415
4415
  try {
@@ -4423,7 +4423,7 @@ async function deleteSecretValue(name) {
4423
4423
  }
4424
4424
  }
4425
4425
  async function setApiKey(apiKey) {
4426
- if (!secretsAvailable) {
4426
+ if (!secrets) {
4427
4427
  throw new Error("Secrets API unavailable");
4428
4428
  }
4429
4429
  await setSecretValue(API_KEY_NAME, apiKey);
@@ -4432,7 +4432,7 @@ async function getApiKey() {
4432
4432
  return getSecretValue(API_KEY_NAME, "API key");
4433
4433
  }
4434
4434
  async function setRefreshToken(refreshToken2) {
4435
- if (!secretsAvailable) {
4435
+ if (!secrets) {
4436
4436
  throw new Error("Secrets API unavailable");
4437
4437
  }
4438
4438
  await setSecretValue(REFRESH_TOKEN_NAME, refreshToken2);
@@ -4463,7 +4463,7 @@ async function setSecureTokens(tokens) {
4463
4463
  }
4464
4464
  }
4465
4465
  async function deleteApiKey() {
4466
- if (secretsAvailable) {
4466
+ if (secrets) {
4467
4467
  try {
4468
4468
  await secrets.delete({
4469
4469
  service: SERVICE_NAME,
@@ -4476,7 +4476,7 @@ async function deleteApiKey() {
4476
4476
  }
4477
4477
  }
4478
4478
  async function deleteRefreshToken() {
4479
- if (secretsAvailable) {
4479
+ if (secrets) {
4480
4480
  try {
4481
4481
  await secrets.delete({
4482
4482
  service: SERVICE_NAME,
@@ -4498,7 +4498,7 @@ async function isKeychainAvailable() {
4498
4498
  if (process.platform === "linux" && !process.env.DBUS_SESSION_BUS_ADDRESS?.trim()) {
4499
4499
  return false;
4500
4500
  }
4501
- if (!secretsAvailable) {
4501
+ if (!secrets) {
4502
4502
  return false;
4503
4503
  }
4504
4504
  try {
@@ -4511,15 +4511,10 @@ async function isKeychainAvailable() {
4511
4511
  return false;
4512
4512
  }
4513
4513
  }
4514
- var secrets, secretsAvailable = false, SERVICE_NAME, API_KEY_NAME = "letta-api-key", REFRESH_TOKEN_NAME = "letta-refresh-token", warnedSecretReadFailures, secretGetOverrideForTests = null;
4514
+ var secrets, SERVICE_NAME, API_KEY_NAME = "letta-api-key", REFRESH_TOKEN_NAME = "letta-refresh-token", warnedSecretReadFailures, secretGetOverrideForTests = null;
4515
4515
  var init_secrets = __esm(() => {
4516
4516
  init_debug();
4517
- try {
4518
- secrets = globalThis.Bun.secrets;
4519
- secretsAvailable = true;
4520
- } catch {
4521
- secretsAvailable = false;
4522
- }
4517
+ secrets = globalThis.Bun?.secrets;
4523
4518
  SERVICE_NAME = scopeServiceName("letta-code");
4524
4519
  warnedSecretReadFailures = new Set;
4525
4520
  });
@@ -4771,8 +4766,8 @@ class SettingsManager {
4771
4766
  const baseSettings = this.getSettings();
4772
4767
  const secureTokens = { ...this.secureTokensCache };
4773
4768
  if (!getRuntimeContext()) {
4774
- const secretsAvailable2 = await this.isKeychainAvailable();
4775
- if (secretsAvailable2) {
4769
+ const secretsAvailable = await this.isKeychainAvailable();
4770
+ if (secretsAvailable) {
4776
4771
  const storedTokens = await this.getSecureTokens();
4777
4772
  secureTokens.apiKey = storedTokens.apiKey ?? secureTokens.apiKey;
4778
4773
  secureTokens.refreshToken = storedTokens.refreshToken ?? secureTokens.refreshToken;
@@ -4861,8 +4856,8 @@ class SettingsManager {
4861
4856
  this.pendingWrites.add(writePromise);
4862
4857
  }
4863
4858
  async persistSettingsAndTokens(secureTokens) {
4864
- const secretsAvailable2 = await this.isKeychainAvailable();
4865
- if (secretsAvailable2 && Object.keys(secureTokens).length > 0) {
4859
+ const secretsAvailable = await this.isKeychainAvailable();
4860
+ if (secretsAvailable && Object.keys(secureTokens).length > 0) {
4866
4861
  try {
4867
4862
  await Promise.all([
4868
4863
  this.persistSettings(),
@@ -5653,7 +5648,7 @@ var package_default;
5653
5648
  var init_package = __esm(() => {
5654
5649
  package_default = {
5655
5650
  name: "@letta-ai/letta-code",
5656
- version: "0.33.6",
5651
+ version: "0.33.8",
5657
5652
  lettaStartupLogProtocol: 1,
5658
5653
  description: "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
5659
5654
  type: "module",
@@ -5680,6 +5675,8 @@ var init_package = __esm(() => {
5680
5675
  "dist/memory-constraints.js.map",
5681
5676
  "dist/mcp-client.js",
5682
5677
  "dist/mcp-client.js.map",
5678
+ "dist/mcp-oauth.js",
5679
+ "dist/mcp-oauth.js.map",
5683
5680
  "dist/agent-presets.js",
5684
5681
  "dist/agent-presets.js.map",
5685
5682
  "dist/schedules.js",
@@ -5724,6 +5721,11 @@ var init_package = __esm(() => {
5724
5721
  import: "./dist/mcp-client.js",
5725
5722
  default: "./dist/mcp-client.js"
5726
5723
  },
5724
+ "./mcp-oauth": {
5725
+ types: "./dist/types/mcp-oauth-public.d.ts",
5726
+ import: "./dist/mcp-oauth.js",
5727
+ default: "./dist/mcp-oauth.js"
5728
+ },
5727
5729
  "./protocol": {
5728
5730
  types: "./dist/types/types/protocol.d.ts"
5729
5731
  },
@@ -5881,6 +5883,9 @@ var init_package = __esm(() => {
5881
5883
  "mcp-client": [
5882
5884
  "./dist/types/mcp-client.d.ts"
5883
5885
  ],
5886
+ "mcp-oauth": [
5887
+ "./dist/types/mcp-oauth-public.d.ts"
5888
+ ],
5884
5889
  channels: [
5885
5890
  "./dist/types/channels-public.d.ts"
5886
5891
  ],
@@ -90887,7 +90892,7 @@ var init_context = __esm(() => {
90887
90892
  });
90888
90893
 
90889
90894
  // src/constants.ts
90890
- var DEFAULT_SUMMARIZATION_MODEL = "letta/auto", DEFAULT_TITLE_SUMMARIZATION_MODEL = "openai/gpt-5.6-luna", DEFAULT_AGENT_NAME = "Letta Code", INTERRUPTED_BY_USER = "Interrupted by user", TURN_DID_NOT_COMPLETE = "Turn did not complete", SYSTEM_REMINDER_TAG = "system-reminder", SYSTEM_REMINDER_OPEN, SYSTEM_REMINDER_CLOSE, SYSTEM_ALERT_TAG = "system-alert", SYSTEM_ALERT_OPEN, SYSTEM_ALERT_CLOSE, COMPACTION_SUMMARY_HEADER = "(Earlier messages in this conversation have been compacted to free up context, summarized below)", TOKEN_DISPLAY_THRESHOLD = 100, ELAPSED_DISPLAY_THRESHOLD_MS;
90895
+ var DEFAULT_SUMMARIZATION_MODEL = "letta/auto", DEFAULT_TITLE_SUMMARIZATION_MODEL = "openai/gpt-6-luna", DEFAULT_AGENT_NAME = "Letta Code", INTERRUPTED_BY_USER = "Interrupted by user", TURN_DID_NOT_COMPLETE = "Turn did not complete", SYSTEM_REMINDER_TAG = "system-reminder", SYSTEM_REMINDER_OPEN, SYSTEM_REMINDER_CLOSE, SYSTEM_ALERT_TAG = "system-alert", SYSTEM_ALERT_OPEN, SYSTEM_ALERT_CLOSE, COMPACTION_SUMMARY_HEADER = "(Earlier messages in this conversation have been compacted to free up context, summarized below)", TOKEN_DISPLAY_THRESHOLD = 100, ELAPSED_DISPLAY_THRESHOLD_MS;
90891
90896
  var init_constants2 = __esm(() => {
90892
90897
  SYSTEM_REMINDER_OPEN = `<${SYSTEM_REMINDER_TAG}>`;
90893
90898
  SYSTEM_REMINDER_CLOSE = `</${SYSTEM_REMINDER_TAG}>`;
@@ -106236,10 +106241,19 @@ class HeadlessBackend {
106236
106241
  if (!run || run.agent_id !== agentId || isTerminalRun(run)) {
106237
106242
  return { [runId]: "failed" };
106238
106243
  }
106244
+ return this.cancelRunById(runId);
106245
+ }
106246
+ async cancelRunById(runId) {
106247
+ const run = this.runs.get(runId);
106248
+ if (!run || isTerminalRun(run)) {
106249
+ return { [runId]: "failed" };
106250
+ }
106239
106251
  if (run.conversation_id) {
106240
- this.store.settleInterruptedToolCalls(run.conversation_id, { agentId });
106252
+ this.store.settleInterruptedToolCalls(run.conversation_id, {
106253
+ agentId: run.agent_id
106254
+ });
106241
106255
  } else {
106242
- this.store.settleInterruptedToolCalls(agentId);
106256
+ this.store.settleInterruptedToolCalls(run.agent_id);
106243
106257
  }
106244
106258
  const controller = this.runControllerByRunId.get(runId);
106245
106259
  this.recordRunChunk(runId, {
@@ -106250,6 +106264,17 @@ class HeadlessBackend {
106250
106264
  controller?.abort();
106251
106265
  return { [runId]: "cancelled" };
106252
106266
  }
106267
+ async cancelConversationRun(...args) {
106268
+ const [conversationId, requestedRunId] = args;
106269
+ const runId = requestedRunId ?? this.activeRunByConversation.get(conversationId);
106270
+ if (!runId)
106271
+ return {};
106272
+ const run = this.runs.get(runId);
106273
+ if (!run || run.conversation_id !== conversationId || isTerminalRun(run)) {
106274
+ return { [runId]: "failed" };
106275
+ }
106276
+ return this.cancelRunById(runId);
106277
+ }
106253
106278
  async retrieveRun(runId) {
106254
106279
  const run = this.runs.get(runId);
106255
106280
  if (!run)
@@ -107133,27 +107158,63 @@ function parseOllamaTags(data) {
107133
107158
  };
107134
107159
  }).filter((entry) => entry !== undefined);
107135
107160
  }
107136
- function parseOllamaRunningContexts(data) {
107137
- const contexts = new Map;
107161
+ function ollamaIdentityKeys(id2) {
107162
+ const normalized = normalizeOllamaModelId(id2);
107163
+ return normalized === id2 ? [id2] : [id2, normalized];
107164
+ }
107165
+ function parseOllamaPsEntries(data) {
107138
107166
  if (!data || typeof data !== "object")
107139
- return contexts;
107167
+ return [];
107140
107168
  const models3 = data.models;
107141
107169
  if (!Array.isArray(models3))
107142
- return contexts;
107170
+ return [];
107171
+ const entries = [];
107143
107172
  for (const entry of models3) {
107144
107173
  if (!entry || typeof entry !== "object")
107145
107174
  continue;
107146
107175
  const record4 = entry;
107147
- const id2 = record4.name ?? record4.model;
107148
- const contextLength = record4.context_length;
107149
- if (typeof id2 !== "string" || id2.length === 0)
107176
+ const rawIds = [record4.name, record4.model].filter((id2) => typeof id2 === "string" && id2.length > 0);
107177
+ if (rawIds.length === 0)
107150
107178
  continue;
107151
- if (typeof contextLength !== "number" || contextLength <= 0)
107179
+ const contextLength = record4.context_length;
107180
+ entries.push({
107181
+ ids: [...new Set(rawIds.flatMap(ollamaIdentityKeys))],
107182
+ displayId: rawIds[0],
107183
+ ...typeof contextLength === "number" && contextLength > 0 ? { contextLength } : {}
107184
+ });
107185
+ }
107186
+ return entries;
107187
+ }
107188
+ function parseOllamaRunningContexts(data) {
107189
+ const contexts = new Map;
107190
+ for (const entry of parseOllamaPsEntries(data)) {
107191
+ if (entry.contextLength === undefined)
107152
107192
  continue;
107153
- contexts.set(id2, contextLength);
107193
+ for (const id2 of entry.ids)
107194
+ contexts.set(id2, entry.contextLength);
107154
107195
  }
107155
107196
  return contexts;
107156
107197
  }
107198
+ function readOllamaServedContextOverride(env4) {
107199
+ const raw = env4[OLLAMA_SERVED_CONTEXT_OVERRIDE_ENV];
107200
+ if (raw === undefined || raw.trim() === "")
107201
+ return;
107202
+ const trimmed = raw.trim();
107203
+ if (!/^[1-9]\d*$/.test(trimmed) || !Number.isSafeInteger(Number(trimmed))) {
107204
+ throw new Error(`${OLLAMA_SERVED_CONTEXT_OVERRIDE_ENV}=${JSON.stringify(trimmed)} is not a positive integer token count. ` + `Set it to the context window your Ollama daemon actually serves.`);
107205
+ }
107206
+ return Number(trimmed);
107207
+ }
107208
+ function missingServedContextError(modelId, data) {
107209
+ const entries = parseOllamaPsEntries(data);
107210
+ const match2 = entries.find((entry) => entry.ids.includes(modelId));
107211
+ if (match2) {
107212
+ return `Ollama lists "${modelId}" in /api/ps but did not report context_length. ` + `Refusing to send the prompt because Ollama may silently truncate it. ` + `context_length requires Ollama 0.10.0 or newer. ` + `Upgrade Ollama, or set ${OLLAMA_SERVED_CONTEXT_OVERRIDE_ENV} to the window your daemon actually serves.`;
107213
+ }
107214
+ const running = entries.map((entry) => entry.displayId).filter((id2) => id2 !== undefined);
107215
+ const runningNote = running.length > 0 ? ` Running models: ${running.join(", ")}.` : ` /api/ps lists no running models.`;
107216
+ return `Ollama loaded "${modelId}", but /api/ps does not list it. ` + `Refusing to send the prompt because Ollama may silently truncate it. ` + `If OLLAMA_KEEP_ALIVE=0, the model unloaded before this check.` + runningNote + ` Set ${OLLAMA_SERVED_CONTEXT_OVERRIDE_ENV} to the window your daemon actually serves.`;
107217
+ }
107157
107218
  async function fetchOllamaNative(fetchImpl, url, options) {
107158
107219
  const controller = new AbortController;
107159
107220
  const timeout = setTimeout(() => controller.abort(), options.timeoutMs);
@@ -107185,42 +107246,49 @@ async function fetchOllamaNative(fetchImpl, url, options) {
107185
107246
  }
107186
107247
  }
107187
107248
  async function resolveOllamaServedContext(options) {
107249
+ const override = readOllamaServedContextOverride(options.env ?? process.env);
107250
+ if (override !== undefined)
107251
+ return override;
107188
107252
  const fetchImpl = options.fetchImpl ?? fetch;
107189
107253
  const modelId = normalizeOllamaModelId(options.modelId);
107190
107254
  const nativeBaseURL = localEndpointNativeBaseURL(options.baseURL);
107191
107255
  const psURL = `${nativeBaseURL}/api/ps`;
107192
- const runningContext = async () => {
107193
- const data = await fetchOllamaNative(fetchImpl, psURL, {
107194
- ...options.apiKey ? { apiKey: options.apiKey } : {},
107195
- ...options.signal ? { signal: options.signal } : {},
107196
- timeoutMs: OLLAMA_STATUS_TIMEOUT_MS,
107197
- consume: (response) => response.json()
107198
- });
107199
- return parseOllamaRunningContexts(data).get(modelId);
107200
- };
107256
+ const overrideHint = `or set ${OLLAMA_SERVED_CONTEXT_OVERRIDE_ENV} to the window your daemon actually serves`;
107201
107257
  try {
107202
107258
  await fetchOllamaNative(fetchImpl, `${nativeBaseURL}/api/generate`, {
107203
107259
  ...options.apiKey ? { apiKey: options.apiKey } : {},
107204
107260
  ...options.signal ? { signal: options.signal } : {},
107205
- body: { model: modelId, prompt: "", stream: false },
107261
+ body: {
107262
+ model: modelId,
107263
+ prompt: "",
107264
+ stream: false,
107265
+ keep_alive: OLLAMA_PREFLIGHT_KEEP_ALIVE
107266
+ },
107206
107267
  timeoutMs: OLLAMA_MODEL_LOAD_TIMEOUT_MS,
107207
107268
  consume: (response) => response.text()
107208
107269
  });
107209
107270
  } catch (error4) {
107210
107271
  if (options.signal?.aborted)
107211
107272
  throw options.signal.reason ?? error4;
107212
- throw new Error(`Unable to load Ollama model "${modelId}" to determine its served context window. ` + `Refusing to send the prompt because Ollama may silently truncate it. ` + `Check the Ollama endpoint, model installation, and available memory. (${error4 instanceof Error ? error4.message : String(error4)})`);
107273
+ throw new Error(`Unable to load Ollama model "${modelId}" to determine its served context window. ` + `Refusing to send the prompt because Ollama may silently truncate it. ` + `Check the Ollama endpoint, model installation, and available memory, ${overrideHint}. (${error4 instanceof Error ? error4.message : String(error4)})`);
107213
107274
  }
107275
+ let running;
107214
107276
  try {
107215
- const loadedContext = await runningContext();
107216
- if (loadedContext !== undefined)
107217
- return loadedContext;
107277
+ running = await fetchOllamaNative(fetchImpl, psURL, {
107278
+ ...options.apiKey ? { apiKey: options.apiKey } : {},
107279
+ ...options.signal ? { signal: options.signal } : {},
107280
+ timeoutMs: OLLAMA_STATUS_TIMEOUT_MS,
107281
+ consume: (response) => response.json()
107282
+ });
107218
107283
  } catch (error4) {
107219
107284
  if (options.signal?.aborted)
107220
107285
  throw options.signal.reason ?? error4;
107221
- throw new Error(`Ollama loaded model "${modelId}", but /api/ps could not verify its served context window. ` + `Refusing to send the prompt because Ollama may silently truncate it. ` + `Check that the endpoint supports /api/ps. (${error4 instanceof Error ? error4.message : String(error4)})`);
107286
+ throw new Error(`Ollama loaded model "${modelId}", but /api/ps could not verify its served context window. ` + `Refusing to send the prompt because Ollama may silently truncate it. ` + `Check that the endpoint supports /api/ps, ${overrideHint}. (${error4 instanceof Error ? error4.message : String(error4)})`);
107222
107287
  }
107223
- throw new Error(`Ollama did not report an exact served context window for loaded model "${modelId}" in /api/ps. ` + `Refusing to send the prompt because Ollama may silently truncate it. ` + `Check the model name and Ollama server logs.`);
107288
+ const loadedContext = parseOllamaRunningContexts(running).get(modelId);
107289
+ if (loadedContext !== undefined)
107290
+ return loadedContext;
107291
+ throw new Error(missingServedContextError(modelId, running));
107224
107292
  }
107225
107293
  function parseOllamaShow(modelId, data, runtimeContextLength) {
107226
107294
  if (!data || typeof data !== "object")
@@ -107250,7 +107318,7 @@ function createOllamaPiProvider(options) {
107250
107318
  discover: ollamaDiscover
107251
107319
  });
107252
107320
  }
107253
- var OLLAMA_PI_PROVIDER_ID = "ollama", OLLAMA_CLOUD_PI_PROVIDER_ID = "ollama-cloud", OLLAMA_STATUS_TIMEOUT_MS = 2000, OLLAMA_MODEL_LOAD_TIMEOUT_MS = 120000, ollamaDiscover = async (context3) => {
107321
+ var OLLAMA_PI_PROVIDER_ID = "ollama", OLLAMA_CLOUD_PI_PROVIDER_ID = "ollama-cloud", OLLAMA_STATUS_TIMEOUT_MS = 2000, OLLAMA_MODEL_LOAD_TIMEOUT_MS = 120000, OLLAMA_PREFLIGHT_KEEP_ALIVE = "5m", OLLAMA_SERVED_CONTEXT_OVERRIDE_ENV = "LETTA_OLLAMA_CONTEXT_LENGTH", ollamaDiscover = async (context3) => {
107254
107322
  const tags = await context3.fetchJson(`${context3.nativeBaseURL}/api/tags`);
107255
107323
  const entries = parseOllamaTags(tags);
107256
107324
  const runtimeContexts = await context3.fetchJson(`${context3.nativeBaseURL}/api/ps`).then(parseOllamaRunningContexts).catch(() => new Map);
@@ -110931,6 +110999,9 @@ class APIBackend {
110931
110999
  const client = await this.getClient();
110932
111000
  return client.agents.messages.cancel(agentId, { run_ids: [runId] });
110933
111001
  }
111002
+ async cancelConversationRun(_conversationId, _runId) {
111003
+ throw new Error("API backend does not support exact cancellation for conversations with agent_id:null");
111004
+ }
110934
111005
  async retrieveRun(runId, options) {
110935
111006
  const client = await this.getClient();
110936
111007
  return client.runs.retrieve(runId, options);
@@ -188489,6 +188560,12 @@ var init_manager = __esm(() => {
188489
188560
  label: "diffs",
188490
188561
  description: "Open browser-based worktree diff previews powered by Diffs from Pierre."
188491
188562
  },
188563
+ {
188564
+ id: "memory_palace",
188565
+ label: "memory palace",
188566
+ description: "Opt-in memory palace experiment flag for disposable local listener runs. Disabled by default; no behavior changes unless consumers gate on it.",
188567
+ envVar: "LETTA_MEMORY_PALACE"
188568
+ },
188492
188569
  {
188493
188570
  id: "reflection_arena",
188494
188571
  label: "reflection arena",
@@ -198022,6 +198099,7 @@ var init_protocol_inbound = __esm(() => {
198022
198099
  EXPERIMENT_IDS = new Set([
198023
198100
  "conversation_titles",
198024
198101
  "desktop_conversation_bootstrap",
198102
+ "memory_palace",
198025
198103
  "tui_cron"
198026
198104
  ]);
198027
198105
  });
@@ -213117,7 +213195,7 @@ class CodexAppServerClient {
213117
213195
  await this.request("initialize", {
213118
213196
  clientInfo: {
213119
213197
  name: "letta-code",
213120
- version: "0.33.6"
213198
+ version: "0.33.8"
213121
213199
  },
213122
213200
  capabilities: null
213123
213201
  });
@@ -218276,6 +218354,7 @@ __export(exports_skills2, {
218276
218354
  getBundledSkills: () => getBundledSkills,
218277
218355
  getAgentSkillsDir: () => getAgentSkillsDir,
218278
218356
  formatSkillsAsSystemReminder: () => formatSkillsAsSystemReminder,
218357
+ enabledSkillExperiments: () => enabledSkillExperiments,
218279
218358
  discoverSkills: () => discoverSkills,
218280
218359
  compareSkills: () => compareSkills,
218281
218360
  SKILLS_DIR: () => SKILLS_DIR,
@@ -218331,7 +218410,14 @@ function isModelInvocableSkill(skill) {
218331
218410
  function isUserInvocableSkill(skill) {
218332
218411
  return skill.userInvocable !== false;
218333
218412
  }
218413
+ function enabledSkillExperiments() {
218414
+ return [...new Set(EXPERIMENT_BUNDLED_SKILLS.values())].filter((id2) => experimentManager.isEnabled(id2)).sort().join(",");
218415
+ }
218334
218416
  function isSkillAvailableForAgent(skill, agentId) {
218417
+ const experiment = skill.source === "bundled" ? EXPERIMENT_BUNDLED_SKILLS.get(skill.id) : undefined;
218418
+ if (experiment && !experimentManager.isEnabled(experiment)) {
218419
+ return false;
218420
+ }
218335
218421
  if (skill.source === "bundled" && agentId && isLocalAgentId(agentId) && LOCAL_AGENT_EXCLUDED_BUNDLED_SKILLS.has(skill.id)) {
218336
218422
  return false;
218337
218423
  }
@@ -218395,7 +218481,7 @@ async function discoverSkills(projectSkillsPath = join61(process.cwd(), SKILLS_D
218395
218481
  }
218396
218482
  }
218397
218483
  return {
218398
- skills: Array.from(skillsById.values()).sort(compareSkills),
218484
+ skills: Array.from(skillsById.values()).filter((skill) => isSkillAvailableForAgent(skill, agentId)).sort(compareSkills),
218399
218485
  errors: allErrors
218400
218486
  };
218401
218487
  }
@@ -218503,14 +218589,18 @@ ${lines.join(`
218503
218589
  `)}
218504
218590
  </system-reminder>`;
218505
218591
  }
218506
- var LOCAL_AGENT_EXCLUDED_BUNDLED_SKILLS, PROJECT_SKILLS_DIR, SKILLS_DIR = ".skills", GLOBAL_SKILLS_DIR;
218592
+ var LOCAL_AGENT_EXCLUDED_BUNDLED_SKILLS, EXPERIMENT_BUNDLED_SKILLS, PROJECT_SKILLS_DIR, SKILLS_DIR = ".skills", GLOBAL_SKILLS_DIR;
218507
218593
  var init_skills5 = __esm(() => {
218594
+ init_manager();
218508
218595
  init_skill_sources();
218509
218596
  LOCAL_AGENT_EXCLUDED_BUNDLED_SKILLS = new Set([
218510
218597
  "image-generation",
218511
218598
  "managing-shared-memory",
218512
218599
  "working-across-computers"
218513
218600
  ]);
218601
+ EXPERIMENT_BUNDLED_SKILLS = new Map([
218602
+ ["curating-memory-palace", "memory_palace"]
218603
+ ]);
218514
218604
  PROJECT_SKILLS_DIR = join61(".agents", "skills");
218515
218605
  GLOBAL_SKILLS_DIR = join61(process.env.HOME || process.env.USERPROFILE || "~", ".letta/skills");
218516
218606
  });
@@ -228020,7 +228110,8 @@ function computeCacheKey(components) {
228020
228110
  components.primaryProjectSkillsDirectory,
228021
228111
  [...components.memorySkillsDirs].sort().join(","),
228022
228112
  [...components.sharedMemorySkillsDirs].sort().join(","),
228023
- components.sharedMemoryErrors.map((error4) => `${error4.path}:${error4.message}`).sort().join(",")
228113
+ components.sharedMemoryErrors.map((error4) => `${error4.path}:${error4.message}`).sort().join(","),
228114
+ components.skillExperiments
228024
228115
  ].join("|");
228025
228116
  }
228026
228117
  function getSkillRoots(components) {
@@ -228313,7 +228404,8 @@ async function buildClientSkillsPayload(options = {}) {
228313
228404
  primaryProjectSkillsDirectory,
228314
228405
  memorySkillsDirs,
228315
228406
  sharedMemorySkillsDirs: sharedMemoryContext.skillsDirs,
228316
- sharedMemoryErrors: sharedMemoryContext.errors
228407
+ sharedMemoryErrors: sharedMemoryContext.errors,
228408
+ skillExperiments: enabledSkillExperiments()
228317
228409
  };
228318
228410
  const cacheKey = computeCacheKey(cacheComponents);
228319
228411
  if (useCache) {
@@ -412450,12 +412542,12 @@ function rowToolCalls(row, index, diagnostics2) {
412450
412542
  }
412451
412543
  return calls;
412452
412544
  }
412453
- function contentText3(content) {
412545
+ function contentText2(content) {
412454
412546
  if (typeof content === "string") {
412455
412547
  if (content.startsWith(CONTENT_JSON_PREFIX)) {
412456
412548
  const encoded = content.slice(CONTENT_JSON_PREFIX.length);
412457
412549
  try {
412458
- return contentText3(JSON.parse(encoded));
412550
+ return contentText2(JSON.parse(encoded));
412459
412551
  } catch {
412460
412552
  return encoded;
412461
412553
  }
@@ -412541,7 +412633,7 @@ var init_hermes = __esm(() => {
412541
412633
  });
412542
412634
  };
412543
412635
  if (row.role === "user") {
412544
- const content = contentText3(row.content);
412636
+ const content = contentText2(row.content);
412545
412637
  if (content) {
412546
412638
  emit({
412547
412639
  type: "message",
@@ -412561,7 +412653,7 @@ var init_hermes = __esm(() => {
412561
412653
  ...timestamp ? { timestamp } : {}
412562
412654
  });
412563
412655
  }
412564
- const content = contentText3(row.content);
412656
+ const content = contentText2(row.content);
412565
412657
  if (content) {
412566
412658
  emit({
412567
412659
  type: "message",
@@ -412584,7 +412676,7 @@ var init_hermes = __esm(() => {
412584
412676
  if (row.role === "tool") {
412585
412677
  emit({
412586
412678
  type: "tool_result",
412587
- content: contentText3(row.content),
412679
+ content: contentText2(row.content),
412588
412680
  ...typeof row.tool_call_id === "string" && row.tool_call_id ? { callId: row.tool_call_id } : {},
412589
412681
  ...timestamp ? { timestamp } : {}
412590
412682
  });
@@ -418452,6 +418544,15 @@ async function handleAbortMessageInput(listener, params, deps = {}) {
418452
418544
  throw new Error(`Backend did not cancel run ${runId}`);
418453
418545
  }
418454
418546
  },
418547
+ cancelConversationRun: async (conversationId, runId) => {
418548
+ const result3 = await getBackend().cancelConversationRun(conversationId, runId);
418549
+ if (runId && result3[runId] !== "cancelled") {
418550
+ throw new Error(`Backend did not cancel run ${runId}`);
418551
+ }
418552
+ if (!runId && !Object.values(result3).includes("cancelled")) {
418553
+ throw new Error(`Backend did not cancel the active run for ${conversationId}`);
418554
+ }
418555
+ },
418455
418556
  ...deps
418456
418557
  };
418457
418558
  if (listener !== resolvedDeps.getActiveRuntime() || listener.intentionallyClosed) {
@@ -418471,7 +418572,7 @@ async function handleAbortMessageInput(listener, params, deps = {}) {
418471
418572
  return false;
418472
418573
  }
418473
418574
  const cancellation = scopedRuntime.turnLifecycle.requestCancellation({
418474
- waitForExternalSettlement: hasActiveTurn && Boolean(scopedRuntime.agentId)
418575
+ waitForExternalSettlement: hasActiveTurn
418475
418576
  });
418476
418577
  scopedRuntime.queueRuntime.pause();
418477
418578
  const interruptedRunId = cancellation.runId;
@@ -418535,19 +418636,17 @@ async function handleAbortMessageInput(listener, params, deps = {}) {
418535
418636
  }
418536
418637
  const cancelConversationId = scopedRuntime.conversationId;
418537
418638
  const cancelAgentId = scopedRuntime.agentId;
418538
- if (cancelAgentId) {
418539
- const cancelRunId = interruptedRunId ?? params.command.run_id ?? null;
418540
- const backendCancellation = cancelRunId ? resolvedDeps.cancelRun(cancelAgentId, cancelRunId).catch(() => resolvedDeps.cancelConversation(cancelAgentId, cancelConversationId)) : resolvedDeps.cancelConversation(cancelAgentId, cancelConversationId);
418541
- backendCancellation.catch(() => {}).finally(() => {
418542
- if (!cancellation.lease) {
418543
- return;
418544
- }
418545
- const settlement = scopedRuntime.turnLifecycle.settleCancellation(cancellation.lease);
418546
- if (settlement.released) {
418547
- resolvedDeps.scheduleQueuePump(scopedRuntime, params.socket, params.opts, params.processQueuedTurn);
418548
- }
418549
- });
418550
- }
418639
+ const cancelRunId = interruptedRunId ?? params.command.run_id ?? null;
418640
+ const backendCancellation = cancelAgentId && cancelRunId ? resolvedDeps.cancelRun(cancelAgentId, cancelRunId).catch(() => resolvedDeps.cancelConversation(cancelAgentId, cancelConversationId)) : cancelAgentId ? resolvedDeps.cancelConversation(cancelAgentId, cancelConversationId) : resolvedDeps.cancelConversationRun(cancelConversationId, cancelRunId);
418641
+ backendCancellation.catch(() => {}).finally(() => {
418642
+ if (!cancellation.lease) {
418643
+ return;
418644
+ }
418645
+ const settlement = scopedRuntime.turnLifecycle.settleCancellation(cancellation.lease);
418646
+ if (settlement.released) {
418647
+ resolvedDeps.scheduleQueuePump(scopedRuntime, params.socket, params.opts, params.processQueuedTurn);
418648
+ }
418649
+ });
418551
418650
  resolvedDeps.scheduleQueuePump(scopedRuntime, params.socket, params.opts, params.processQueuedTurn);
418552
418651
  return true;
418553
418652
  }
@@ -445147,23 +445246,24 @@ var init_streamableHttp = __esm(() => {
445147
445246
  async function connectMcpServer(config2, options = {}) {
445148
445247
  let client = new Client(options.clientInfo ?? DEFAULT_CLIENT_INFO);
445149
445248
  try {
445249
+ options.signal?.throwIfAborted();
445150
445250
  const transport = createTransport2(config2, options);
445151
445251
  try {
445152
- await client.connect(transport);
445252
+ await client.connect(transport, options.signal ? { signal: options.signal } : undefined);
445153
445253
  } catch (error5) {
445154
445254
  if (!(error5 instanceof UnauthorizedError) || !options.oauth?.waitForAuthorizationCode || !supportsOAuthCompletion(transport)) {
445155
445255
  throw error5;
445156
445256
  }
445157
- const authorizationCode = await options.oauth.waitForAuthorizationCode();
445158
- await transport.finishAuth(authorizationCode);
445257
+ const authorizationCode = await withAbort(options.oauth.waitForAuthorizationCode(), options.signal, () => options.oauth?.close());
445258
+ await withAbort(transport.finishAuth(authorizationCode), options.signal, () => transport.close());
445159
445259
  await client.close().catch(() => transport.close().catch(() => {
445160
445260
  return;
445161
445261
  }));
445162
445262
  client = new Client(options.clientInfo ?? DEFAULT_CLIENT_INFO);
445163
- await client.connect(createTransport2(config2, options));
445263
+ await client.connect(createTransport2(config2, options), options.signal ? { signal: options.signal } : undefined);
445164
445264
  }
445165
- await options.oauth?.close();
445166
- const response = await client.listTools();
445265
+ await (options.oauth?.closeCallback?.() ?? options.oauth?.close() ?? Promise.resolve());
445266
+ const response = await client.listTools(undefined, options.signal ? { signal: options.signal } : undefined);
445167
445267
  const tools = response.tools.map((tool) => ({
445168
445268
  ...tool,
445169
445269
  inputSchema: normalizeInputSchema(tool.inputSchema),
@@ -445186,7 +445286,13 @@ async function connectMcpServer(config2, options = {}) {
445186
445286
  if (closed)
445187
445287
  return;
445188
445288
  closed = true;
445189
- await client.close();
445289
+ const results = await Promise.allSettled([
445290
+ client.close(),
445291
+ options.oauth?.close()
445292
+ ]);
445293
+ const failure2 = results.find((result3) => result3.status === "rejected");
445294
+ if (failure2)
445295
+ throw failure2.reason;
445190
445296
  }
445191
445297
  };
445192
445298
  } catch (error5) {
@@ -445197,12 +445303,34 @@ async function connectMcpServer(config2, options = {}) {
445197
445303
  throw error5;
445198
445304
  }
445199
445305
  }
445306
+ async function withAbort(operation, signal, onAbort) {
445307
+ if (!signal)
445308
+ return operation;
445309
+ signal.throwIfAborted();
445310
+ let abort;
445311
+ const cancellation = new Promise((_resolve, reject2) => {
445312
+ abort = () => {
445313
+ Promise.resolve(onAbort?.()).catch(() => {
445314
+ return;
445315
+ });
445316
+ reject2(signal.reason ?? new DOMException("Operation aborted", "AbortError"));
445317
+ };
445318
+ signal.addEventListener("abort", abort, { once: true });
445319
+ });
445320
+ try {
445321
+ return await Promise.race([operation, cancellation]);
445322
+ } finally {
445323
+ if (abort)
445324
+ signal.removeEventListener("abort", abort);
445325
+ }
445326
+ }
445200
445327
  function createTransport2(config2, options) {
445201
445328
  if (config2.transport === "http") {
445202
445329
  const headers = resolveHeaderEnvironment(config2.headers);
445203
445330
  return new StreamableHTTPClientTransport(new URL(config2.url), {
445204
445331
  requestInit: headersRequestInit(headers),
445205
- authProvider: options.oauth?.authProvider
445332
+ authProvider: options.oauth?.authProvider,
445333
+ fetch: abortAwareFetch(options.signal, undefined, options.fetch)
445206
445334
  });
445207
445335
  }
445208
445336
  if (config2.transport === "sse") {
@@ -445211,7 +445339,7 @@ function createTransport2(config2, options) {
445211
445339
  return new SSEClientTransport(new URL(config2.url), {
445212
445340
  requestInit,
445213
445341
  authProvider: options.oauth?.authProvider,
445214
- fetch: headers ? (url2, init) => fetch(url2, mergeHeaders4(init, headers)) : undefined
445342
+ fetch: abortAwareFetch(options.signal, headers, options.fetch)
445215
445343
  });
445216
445344
  }
445217
445345
  return new StdioClientTransport({
@@ -445222,6 +445350,19 @@ function createTransport2(config2, options) {
445222
445350
  stderr: options.stderr ?? "inherit"
445223
445351
  });
445224
445352
  }
445353
+ function abortAwareFetch(operationSignal2, headers, fetchFn) {
445354
+ if (!operationSignal2 && !headers)
445355
+ return fetchFn;
445356
+ const request = fetchFn ?? fetch;
445357
+ return (url2, init) => {
445358
+ const requestInit = headers ? mergeHeaders4(init, headers) : { ...init };
445359
+ const requestSignal3 = requestInit.signal ?? undefined;
445360
+ return request(url2, {
445361
+ ...requestInit,
445362
+ signal: operationSignal2 && requestSignal3 ? AbortSignal.any([operationSignal2, requestSignal3]) : operationSignal2 ?? requestSignal3
445363
+ });
445364
+ };
445365
+ }
445225
445366
  function supportsOAuthCompletion(transport) {
445226
445367
  return "finishAuth" in transport && typeof transport.finishAuth === "function";
445227
445368
  }
@@ -445268,60 +445409,281 @@ var init_mcp_client = __esm(() => {
445268
445409
  init_streamableHttp();
445269
445410
  DEFAULT_CLIENT_INFO = {
445270
445411
  name: "letta-code",
445271
- version: "0.33.6"
445412
+ version: "0.33.8"
445413
+ };
445414
+ });
445415
+
445416
+ // src/mcp-oauth-callback.ts
445417
+ import { createServer as createServer3 } from "node:http";
445418
+ async function startOAuthCallbackServer2(preferredPort) {
445419
+ try {
445420
+ return await startOAuthCallbackServerOnPort(preferredPort ?? 0);
445421
+ } catch (error5) {
445422
+ if (!preferredPort)
445423
+ throw error5;
445424
+ return startOAuthCallbackServerOnPort(0);
445425
+ }
445426
+ }
445427
+ function callbackPort(redirectUrl) {
445428
+ if (!redirectUrl)
445429
+ return;
445430
+ try {
445431
+ const parsed = new URL(redirectUrl);
445432
+ return parsed.hostname === "127.0.0.1" && parsed.port ? Number(parsed.port) : undefined;
445433
+ } catch {
445434
+ return;
445435
+ }
445436
+ }
445437
+ async function startOAuthCallbackServerOnPort(port) {
445438
+ const expectedState = {};
445439
+ let server2;
445440
+ let completed = false;
445441
+ let settle3;
445442
+ let reject2;
445443
+ const codePromise = new Promise((resolve39, rejectPromise) => {
445444
+ settle3 = (code2) => {
445445
+ completed = true;
445446
+ resolve39(code2);
445447
+ };
445448
+ reject2 = (error5) => {
445449
+ completed = true;
445450
+ rejectPromise(error5);
445451
+ };
445452
+ });
445453
+ codePromise.catch(() => {
445454
+ return;
445455
+ });
445456
+ server2 = createServer3((request, response) => {
445457
+ if (request.method !== "GET" || !request.url?.startsWith("/") || request.url.startsWith("//") || request.url.includes("\\")) {
445458
+ response.writeHead(400, { "Content-Type": "text/plain" });
445459
+ response.end("Invalid request target");
445460
+ return;
445461
+ }
445462
+ const address2 = server2.address();
445463
+ const expectedHost = address2 && typeof address2 !== "string" ? `127.0.0.1:${address2.port}` : undefined;
445464
+ if (!expectedHost || request.headers.host !== expectedHost) {
445465
+ response.writeHead(400, { "Content-Type": "text/plain" });
445466
+ response.end("Invalid callback host");
445467
+ return;
445468
+ }
445469
+ let url2;
445470
+ try {
445471
+ url2 = new URL(request.url, `http://${expectedHost}`);
445472
+ } catch {
445473
+ response.writeHead(400, { "Content-Type": "text/plain" });
445474
+ response.end("Invalid request target");
445475
+ return;
445476
+ }
445477
+ if (url2.origin !== `http://${expectedHost}` || url2.pathname !== "/callback") {
445478
+ response.writeHead(404).end("Not found");
445479
+ return;
445480
+ }
445481
+ const error5 = url2.searchParams.get("error");
445482
+ const code2 = url2.searchParams.get("code");
445483
+ const state = url2.searchParams.get("state");
445484
+ if (!state || state !== expectedState.value) {
445485
+ response.writeHead(400, { "Content-Type": "text/html" });
445486
+ response.end(callbackPage("Authorization failed", "Invalid OAuth callback"));
445487
+ return;
445488
+ }
445489
+ if (error5) {
445490
+ response.writeHead(400, { "Content-Type": "text/html" });
445491
+ reject2?.(new Error(`MCP OAuth authorization failed: ${error5}`));
445492
+ response.end(callbackPage("Authorization failed", error5), () => {
445493
+ closeServer(true);
445494
+ });
445495
+ return;
445496
+ }
445497
+ if (!code2) {
445498
+ response.writeHead(400, { "Content-Type": "text/html" });
445499
+ response.end(callbackPage("Authorization failed", "Missing authorization code"));
445500
+ return;
445501
+ }
445502
+ response.writeHead(200, { "Content-Type": "text/html" });
445503
+ settle3?.(code2);
445504
+ response.end(callbackPage("Authorization complete", "You can close this tab and return to Letta Code."), () => {
445505
+ closeServer(true);
445506
+ });
445507
+ });
445508
+ server2.on("clientError", (_error, socket) => {
445509
+ if (!socket.writable)
445510
+ return;
445511
+ socket.end(`HTTP/1.1 400 Bad Request\r
445512
+ Connection: close\r
445513
+ Content-Length: 0\r
445514
+ \r
445515
+ `);
445516
+ });
445517
+ let serverClosing = false;
445518
+ const closeServer = (force = false) => {
445519
+ if (serverClosing)
445520
+ return;
445521
+ serverClosing = true;
445522
+ server2.close();
445523
+ if (force)
445524
+ server2.closeAllConnections();
445525
+ };
445526
+ await new Promise((resolve39, rejectListen) => {
445527
+ server2.once("error", rejectListen);
445528
+ server2.listen(port, "127.0.0.1", resolve39);
445529
+ });
445530
+ server2.unref();
445531
+ const address = server2.address();
445532
+ if (!address || typeof address === "string") {
445533
+ server2.close();
445534
+ throw new Error("Could not start MCP OAuth callback server");
445535
+ }
445536
+ const timeout = setTimeout(() => {
445537
+ reject2?.(new Error("Timed out waiting for MCP OAuth authorization"));
445538
+ closeServer(true);
445539
+ }, CALLBACK_TIMEOUT_MS);
445540
+ timeout.unref();
445541
+ return {
445542
+ redirectUrl: `http://127.0.0.1:${address.port}/callback`,
445543
+ expectedState,
445544
+ waitForCode: () => codePromise.finally(() => clearTimeout(timeout)),
445545
+ close: () => {
445546
+ clearTimeout(timeout);
445547
+ if (!completed)
445548
+ reject2?.(new Error("MCP OAuth flow was cancelled"));
445549
+ closeServer(true);
445550
+ }
445272
445551
  };
445552
+ }
445553
+ function callbackPage(title, message) {
445554
+ return `<!doctype html><html><body style="font-family:system-ui;padding:2rem"><h1>${escapeHtml2(title)}</h1><p>${escapeHtml2(message)}</p></body></html>`;
445555
+ }
445556
+ function escapeHtml2(value) {
445557
+ return value.replace(/[&<>"']/g, (character) => {
445558
+ const entities = {
445559
+ "&": "&amp;",
445560
+ "<": "&lt;",
445561
+ ">": "&gt;",
445562
+ '"': "&quot;",
445563
+ "'": "&#39;"
445564
+ };
445565
+ return entities[character] ?? character;
445566
+ });
445567
+ }
445568
+ var CALLBACK_TIMEOUT_MS;
445569
+ var init_mcp_oauth_callback = __esm(() => {
445570
+ CALLBACK_TIMEOUT_MS = 5 * 60 * 1000;
445273
445571
  });
445274
445572
 
445275
445573
  // src/mcp-oauth.ts
445276
445574
  import { createHash as createHash11, randomBytes as randomBytes2 } from "node:crypto";
445277
- import { createServer as createServer3 } from "node:http";
445278
445575
  async function clearMcpOAuthCredentials(agentId, serverName, serverUrl) {
445279
- return deleteSecretValue(oauthSecretName(agentId, serverName, serverUrl));
445576
+ const credentialKey = mcpOAuthCredentialKey(agentId, serverName, serverUrl);
445577
+ const coordinationKey = storageCoordinationKey(bunSecretStorage, credentialKey, "bun-secrets");
445578
+ const { result: result3 } = await clearStoredCredentials(bunSecretStorage, credentialKey, coordinationKey);
445579
+ return result3 === true;
445280
445580
  }
445281
445581
  async function createMcpOAuthSession(agentId, serverName, serverUrl, options) {
445282
- const secretName = oauthSecretName(agentId, serverName, serverUrl);
445283
- const persisted = await loadState(secretName);
445284
- if (!options.interactive && !persisted)
445285
- return;
445286
- const callback = options.interactive ? await startOAuthCallbackServer2(callbackPort(persisted?.redirectUrl)) : undefined;
445287
- const redirectUrl = callback?.redirectUrl ?? persisted?.redirectUrl;
445288
- if (!redirectUrl)
445289
- return;
445290
- const provider = new PersistentMcpOAuthProvider({
445291
- secretName,
445292
- redirectUrl,
445293
- persisted,
445582
+ const credentialKey = mcpOAuthCredentialKey(agentId, serverName, serverUrl);
445583
+ return createMcpOAuthSessionWithStorage({
445584
+ credentialKey,
445585
+ storageNamespace: "bun-secrets",
445586
+ storage: bunSecretStorage,
445294
445587
  interactive: options.interactive,
445295
445588
  onStatus: options.onStatus,
445296
- openBrowser: options.openBrowser ?? openSystemBrowser,
445297
- expectedState: callback?.expectedState
445589
+ openBrowser: options.openBrowser ?? openSystemBrowser
445298
445590
  });
445299
- return {
445300
- authProvider: provider,
445301
- ...callback ? { waitForAuthorizationCode: () => callback.waitForCode() } : {},
445302
- close: async () => callback?.close()
445303
- };
445591
+ }
445592
+ async function createMcpOAuthSessionWithStorage(options) {
445593
+ const coordinationKey = storageCoordinationKey(options.storage, options.credentialKey, options.storageNamespace);
445594
+ retainCredentialCoordinator(coordinationKey);
445595
+ let provider;
445596
+ let callback;
445597
+ try {
445598
+ const loaded = await loadState(options.storage, options.credentialKey, coordinationKey);
445599
+ const persisted = loaded.state;
445600
+ if (!options.interactive && !persisted) {
445601
+ await releaseCredentialCoordinator(coordinationKey);
445602
+ return;
445603
+ }
445604
+ callback = options.interactive ? await startOAuthCallbackServer2(callbackPort(persisted?.redirectUrl)) : undefined;
445605
+ const redirectUrl = callback?.redirectUrl ?? persisted?.redirectUrl;
445606
+ if (!redirectUrl) {
445607
+ await releaseCredentialCoordinator(coordinationKey);
445608
+ return;
445609
+ }
445610
+ provider = new PersistentMcpOAuthProvider({
445611
+ credentialKey: options.credentialKey,
445612
+ coordinationKey,
445613
+ storage: options.storage,
445614
+ redirectUrl,
445615
+ persisted,
445616
+ interactive: options.interactive,
445617
+ onStatus: options.onStatus,
445618
+ openBrowser: options.openBrowser ?? openSystemBrowser,
445619
+ expectedState: callback?.expectedState,
445620
+ generation: loaded.generation
445621
+ });
445622
+ const activeProvider = provider;
445623
+ const activeCallback = callback;
445624
+ return {
445625
+ authProvider: activeProvider,
445626
+ ...activeCallback ? { waitForAuthorizationCode: () => activeCallback.waitForCode() } : {},
445627
+ exportCredentials: () => activeProvider.exportCredentials(),
445628
+ closeCallback: async () => activeCallback?.close(),
445629
+ close: async () => {
445630
+ activeCallback?.close();
445631
+ await activeProvider.dispose();
445632
+ }
445633
+ };
445634
+ } catch (error5) {
445635
+ callback?.close();
445636
+ if (provider)
445637
+ await provider.dispose();
445638
+ else
445639
+ await releaseCredentialCoordinator(coordinationKey);
445640
+ throw error5;
445641
+ }
445304
445642
  }
445305
445643
 
445306
445644
  class PersistentMcpOAuthProvider {
445307
445645
  stateData;
445308
- secretName;
445646
+ pendingClientInformation;
445647
+ pendingDiscoveryState;
445648
+ codeVerifierValue;
445649
+ generation;
445650
+ observedClientVersion;
445651
+ observedTokenVersion;
445652
+ observedDiscoveryVersion;
445653
+ tokenOperationGeneration;
445654
+ tokenOperationVersion;
445655
+ coordinatorValue;
445656
+ credentialKey;
445657
+ coordinationKey;
445658
+ storage;
445309
445659
  interactive;
445310
445660
  onStatus;
445311
445661
  openBrowser;
445312
445662
  expectedState;
445663
+ closed = false;
445313
445664
  constructor(options) {
445314
- this.secretName = options.secretName;
445665
+ this.credentialKey = options.credentialKey;
445666
+ this.coordinationKey = options.coordinationKey;
445667
+ this.storage = options.storage;
445315
445668
  this.interactive = options.interactive;
445316
445669
  this.onStatus = options.onStatus;
445317
445670
  this.openBrowser = options.openBrowser;
445318
445671
  this.expectedState = options.expectedState;
445672
+ this.generation = options.generation;
445673
+ const coordinator = getCredentialCoordinator(options.coordinationKey);
445674
+ this.coordinatorValue = coordinator;
445675
+ this.observedClientVersion = coordinator.clientVersion;
445676
+ this.observedTokenVersion = coordinator.tokenVersion;
445677
+ this.observedDiscoveryVersion = coordinator.discoveryVersion;
445678
+ this.tokenOperationGeneration = coordinator.generation;
445679
+ this.tokenOperationVersion = coordinator.tokenVersion;
445319
445680
  this.stateData = {
445320
445681
  ...options.persisted,
445321
445682
  redirectUrl: options.redirectUrl
445322
445683
  };
445323
445684
  }
445324
445685
  get redirectUrl() {
445686
+ this.assertOpen();
445325
445687
  return this.stateData.redirectUrl;
445326
445688
  }
445327
445689
  get clientMetadata() {
@@ -445334,27 +445696,127 @@ class PersistentMcpOAuthProvider {
445334
445696
  };
445335
445697
  }
445336
445698
  state() {
445699
+ this.assertOpen();
445337
445700
  const state = randomBytes2(24).toString("base64url");
445338
445701
  if (this.expectedState)
445339
445702
  this.expectedState.value = state;
445340
445703
  return state;
445341
445704
  }
445342
445705
  clientInformation() {
445343
- const client = this.stateData.clientInformation;
445344
- return client && "redirect_uris" in client && client.redirect_uris.includes(this.redirectUrl) ? client : undefined;
445706
+ this.assertOpen();
445707
+ if (this.generation !== this.coordinator.generation)
445708
+ return;
445709
+ if (this.pendingClientInformation && this.pendingClientInformation.baseVersion !== this.coordinator.clientVersion) {
445710
+ this.pendingClientInformation = undefined;
445711
+ }
445712
+ const client = this.pendingClientInformation?.value ?? this.coordinator.state?.clientInformation;
445713
+ this.observedClientVersion = this.coordinator.clientVersion;
445714
+ return client && "redirect_uris" in client && Array.isArray(client.redirect_uris) && client.redirect_uris.includes(this.redirectUrl) ? client : undefined;
445345
445715
  }
445346
445716
  saveClientInformation(clientInformation) {
445717
+ this.assertOpen();
445718
+ assertCurrentGeneration(this.coordinationKey, this.generation);
445347
445719
  this.stateData.clientInformation = clientInformation;
445720
+ this.pendingClientInformation = {
445721
+ value: clientInformation,
445722
+ baseVersion: this.coordinator.clientVersion
445723
+ };
445724
+ if (!this.coordinator.state?.tokens) {
445725
+ this.tokenOperationGeneration = this.coordinator.generation;
445726
+ this.tokenOperationVersion = this.coordinator.tokenVersion;
445727
+ }
445348
445728
  }
445349
445729
  tokens() {
445350
- return this.stateData.tokens;
445730
+ this.assertOpen();
445731
+ if (this.generation !== this.coordinator.generation)
445732
+ return;
445733
+ this.observedTokenVersion = this.coordinator.tokenVersion;
445734
+ this.tokenOperationGeneration = this.coordinator.generation;
445735
+ this.tokenOperationVersion = this.coordinator.tokenVersion;
445736
+ return this.coordinator.state?.tokens;
445737
+ }
445738
+ async exportCredentials() {
445739
+ this.assertOpen();
445740
+ const expectedGeneration = this.generation;
445741
+ return enqueueStorageMutation(this.coordinationKey, async () => {
445742
+ this.assertOpen();
445743
+ assertCurrentGeneration(this.coordinationKey, expectedGeneration);
445744
+ const latest = await readStoredState(this.storage, this.credentialKey);
445745
+ if (!latest) {
445746
+ throw new Error("MCP OAuth authorization is not complete");
445747
+ }
445748
+ this.assertOpen();
445749
+ this.coordinator.state = latest;
445750
+ this.stateData = latest;
445751
+ return exportCredentialSnapshot(latest);
445752
+ });
445351
445753
  }
445352
445754
  async saveTokens(tokens) {
445353
- this.stateData.tokens = tokens;
445354
- delete this.stateData.codeVerifier;
445355
- await this.persist();
445755
+ this.assertOpen();
445756
+ const expectedGeneration = this.tokenOperationGeneration;
445757
+ const expectedTokenVersion = this.tokenOperationVersion;
445758
+ const tokenExpiresAt = tokens.expires_in === undefined ? undefined : Date.now() + tokens.expires_in * 1000;
445759
+ const pendingClientInformation = this.pendingClientInformation;
445760
+ const pendingDiscoveryState = this.pendingDiscoveryState;
445761
+ const result3 = await enqueueStorageMutation(this.coordinationKey, async () => {
445762
+ this.assertOpen();
445763
+ assertCurrentGeneration(this.coordinationKey, expectedGeneration);
445764
+ if (this.coordinator.tokenVersion !== expectedTokenVersion) {
445765
+ throw new Error("MCP OAuth token response is stale because credentials changed");
445766
+ }
445767
+ if (pendingClientInformation && this.coordinator.clientVersion !== pendingClientInformation.baseVersion || pendingDiscoveryState && this.coordinator.discoveryVersion !== pendingDiscoveryState.baseVersion) {
445768
+ throw new Error("MCP OAuth token response is stale because protocol state changed");
445769
+ }
445770
+ const state = await readStoredState(this.storage, this.credentialKey) ?? { redirectUrl: this.redirectUrl };
445771
+ let clientMerged = false;
445772
+ let discoveryMerged = false;
445773
+ if (pendingClientInformation && this.coordinator.clientVersion === pendingClientInformation.baseVersion) {
445774
+ state.redirectUrl = this.redirectUrl;
445775
+ state.clientInformation = pendingClientInformation.value;
445776
+ clientMerged = true;
445777
+ }
445778
+ if (pendingDiscoveryState && this.coordinator.discoveryVersion === pendingDiscoveryState.baseVersion) {
445779
+ state.discoveryState = pendingDiscoveryState.value;
445780
+ discoveryMerged = true;
445781
+ }
445782
+ state.tokens = {
445783
+ ...tokens,
445784
+ refresh_token: tokens.refresh_token ?? state.tokens?.refresh_token
445785
+ };
445786
+ state.tokenExpiresAt = tokenExpiresAt;
445787
+ await this.storage.set(this.credentialKey, JSON.stringify(state));
445788
+ this.coordinator.state = state;
445789
+ this.coordinator.tokenVersion += 1;
445790
+ if (clientMerged)
445791
+ this.coordinator.clientVersion += 1;
445792
+ if (discoveryMerged)
445793
+ this.coordinator.discoveryVersion += 1;
445794
+ return { state, clientMerged, discoveryMerged };
445795
+ });
445796
+ this.codeVerifierValue = undefined;
445797
+ if (this.closed) {
445798
+ this.scrubLocalState();
445799
+ return;
445800
+ }
445801
+ this.stateData = result3.state;
445802
+ this.observedTokenVersion = this.coordinator.tokenVersion;
445803
+ this.tokenOperationGeneration = this.coordinator.generation;
445804
+ this.tokenOperationVersion = this.coordinator.tokenVersion;
445805
+ this.observedClientVersion = this.coordinator.clientVersion;
445806
+ this.observedDiscoveryVersion = this.coordinator.discoveryVersion;
445807
+ if (this.pendingClientInformation === pendingClientInformation) {
445808
+ this.pendingClientInformation = undefined;
445809
+ } else if (this.pendingClientInformation) {
445810
+ this.pendingClientInformation.baseVersion = this.coordinator.clientVersion;
445811
+ }
445812
+ if (this.pendingDiscoveryState === pendingDiscoveryState) {
445813
+ this.pendingDiscoveryState = undefined;
445814
+ } else if (this.pendingDiscoveryState) {
445815
+ this.pendingDiscoveryState.baseVersion = this.coordinator.discoveryVersion;
445816
+ }
445356
445817
  }
445357
445818
  async redirectToAuthorization(authorizationUrl) {
445819
+ this.assertOpen();
445358
445820
  if (!this.interactive) {
445359
445821
  throw new Error("MCP authentication requires user authorization. Open /mcp and press R to sign in.");
445360
445822
  }
@@ -445364,147 +445826,268 @@ ${authorizationUrl.toString()}`);
445364
445826
  await this.openBrowser(authorizationUrl.toString());
445365
445827
  }
445366
445828
  saveCodeVerifier(codeVerifier) {
445367
- this.stateData.codeVerifier = codeVerifier;
445829
+ this.assertOpen();
445830
+ this.codeVerifierValue = codeVerifier;
445368
445831
  }
445369
445832
  codeVerifier() {
445370
- if (!this.stateData.codeVerifier) {
445833
+ this.assertOpen();
445834
+ if (!this.codeVerifierValue) {
445371
445835
  throw new Error("No MCP OAuth PKCE verifier is available");
445372
445836
  }
445373
- return this.stateData.codeVerifier;
445837
+ return this.codeVerifierValue;
445374
445838
  }
445375
445839
  saveDiscoveryState(state) {
445840
+ this.assertOpen();
445841
+ assertCurrentGeneration(this.coordinationKey, this.generation);
445376
445842
  this.stateData.discoveryState = state;
445843
+ this.pendingDiscoveryState = {
445844
+ value: state,
445845
+ baseVersion: this.coordinator.discoveryVersion
445846
+ };
445847
+ if (!this.coordinator.state?.tokens) {
445848
+ this.tokenOperationGeneration = this.coordinator.generation;
445849
+ this.tokenOperationVersion = this.coordinator.tokenVersion;
445850
+ }
445377
445851
  }
445378
445852
  discoveryState() {
445379
- return this.stateData.discoveryState;
445380
- }
445381
- async invalidateCredentials(scope) {
445382
- if (scope === "all") {
445383
- this.stateData = { redirectUrl: this.redirectUrl };
445384
- await deleteSecretValue(this.secretName);
445853
+ this.assertOpen();
445854
+ if (this.generation !== this.coordinator.generation)
445385
445855
  return;
445856
+ if (this.pendingDiscoveryState && this.pendingDiscoveryState.baseVersion !== this.coordinator.discoveryVersion) {
445857
+ this.pendingDiscoveryState = undefined;
445386
445858
  }
445387
- if (scope === "client")
445388
- delete this.stateData.clientInformation;
445389
- if (scope === "tokens")
445390
- delete this.stateData.tokens;
445391
- if (scope === "verifier")
445392
- delete this.stateData.codeVerifier;
445393
- if (scope === "discovery")
445394
- delete this.stateData.discoveryState;
445395
- await this.persist();
445396
- }
445397
- async persist() {
445398
- await setSecretValue(this.secretName, JSON.stringify(this.stateData));
445399
- }
445400
- }
445401
- async function startOAuthCallbackServer2(preferredPort) {
445402
- try {
445403
- return await startOAuthCallbackServerOnPort(preferredPort ?? 0);
445404
- } catch (error5) {
445405
- if (!preferredPort)
445406
- throw error5;
445407
- return startOAuthCallbackServerOnPort(0);
445859
+ this.observedDiscoveryVersion = this.coordinator.discoveryVersion;
445860
+ return this.pendingDiscoveryState?.value ?? this.coordinator.state?.discoveryState;
445408
445861
  }
445409
- }
445410
- async function startOAuthCallbackServerOnPort(port) {
445411
- const expectedState = {};
445412
- let server2;
445413
- let completed = false;
445414
- let settle3;
445415
- let reject2;
445416
- const codePromise = new Promise((resolve39, rejectPromise) => {
445417
- settle3 = (code2) => {
445418
- completed = true;
445419
- resolve39(code2);
445420
- };
445421
- reject2 = (error5) => {
445422
- completed = true;
445423
- rejectPromise(error5);
445424
- };
445425
- });
445426
- codePromise.catch(() => {
445427
- return;
445428
- });
445429
- server2 = createServer3((request, response) => {
445430
- const url2 = new URL(request.url ?? "/", "http://127.0.0.1");
445431
- if (url2.pathname !== "/callback") {
445432
- response.writeHead(404).end("Not found");
445862
+ async invalidateCredentials(scope) {
445863
+ this.assertOpen();
445864
+ if (scope === "verifier") {
445865
+ this.codeVerifierValue = undefined;
445433
445866
  return;
445434
445867
  }
445435
- const error5 = url2.searchParams.get("error");
445436
- const code2 = url2.searchParams.get("code");
445437
- const state = url2.searchParams.get("state");
445438
- if (error5) {
445439
- response.writeHead(400, { "Content-Type": "text/html" });
445440
- response.end(callbackPage("Authorization failed", error5));
445441
- reject2?.(new Error(`MCP OAuth authorization failed: ${error5}`));
445442
- server2.close();
445868
+ if (scope === "all") {
445869
+ const redirectUrl = this.redirectUrl;
445870
+ const { generation: generation3, cleared } = await clearStoredCredentials(this.storage, this.credentialKey, this.coordinationKey, {
445871
+ generation: this.generation,
445872
+ clientVersion: this.observedClientVersion,
445873
+ tokenVersion: this.observedTokenVersion,
445874
+ discoveryVersion: this.observedDiscoveryVersion
445875
+ });
445876
+ if (this.closed)
445877
+ return;
445878
+ if (!cleared) {
445879
+ this.stateData = this.coordinator.state ?? { redirectUrl };
445880
+ this.pendingClientInformation = undefined;
445881
+ this.pendingDiscoveryState = undefined;
445882
+ this.codeVerifierValue = undefined;
445883
+ this.observedClientVersion = this.coordinator.clientVersion;
445884
+ this.observedTokenVersion = this.coordinator.tokenVersion;
445885
+ this.observedDiscoveryVersion = this.coordinator.discoveryVersion;
445886
+ return;
445887
+ }
445888
+ this.generation = generation3;
445889
+ this.stateData = { redirectUrl };
445890
+ this.pendingClientInformation = undefined;
445891
+ this.pendingDiscoveryState = undefined;
445892
+ this.codeVerifierValue = undefined;
445893
+ this.observedClientVersion = this.coordinator.clientVersion;
445894
+ this.observedTokenVersion = this.coordinator.tokenVersion;
445895
+ this.observedDiscoveryVersion = this.coordinator.discoveryVersion;
445896
+ return;
445897
+ }
445898
+ const expectedGeneration = this.generation;
445899
+ const pendingClientInformation = this.pendingClientInformation;
445900
+ const pendingDiscoveryState = this.pendingDiscoveryState;
445901
+ const expectedVersion = scope === "client" ? this.observedClientVersion : scope === "tokens" ? this.observedTokenVersion : this.observedDiscoveryVersion;
445902
+ const result3 = await enqueueStorageMutation(this.coordinationKey, async () => {
445903
+ this.assertOpen();
445904
+ assertCurrentGeneration(this.coordinationKey, expectedGeneration);
445905
+ const currentVersion = scope === "client" ? this.coordinator.clientVersion : scope === "tokens" ? this.coordinator.tokenVersion : this.coordinator.discoveryVersion;
445906
+ if (currentVersion !== expectedVersion) {
445907
+ return this.coordinator.state;
445908
+ }
445909
+ const state = await readStoredState(this.storage, this.credentialKey);
445910
+ if (state) {
445911
+ if (scope === "client")
445912
+ delete state.clientInformation;
445913
+ if (scope === "tokens") {
445914
+ delete state.tokens;
445915
+ delete state.tokenExpiresAt;
445916
+ }
445917
+ if (scope === "discovery")
445918
+ delete state.discoveryState;
445919
+ await this.storage.set(this.credentialKey, JSON.stringify(state));
445920
+ }
445921
+ this.coordinator.state = state;
445922
+ if (scope === "client")
445923
+ this.coordinator.clientVersion += 1;
445924
+ if (scope === "tokens")
445925
+ this.coordinator.tokenVersion += 1;
445926
+ if (scope === "discovery")
445927
+ this.coordinator.discoveryVersion += 1;
445928
+ return state;
445929
+ });
445930
+ if (this.closed)
445443
445931
  return;
445932
+ this.stateData = result3 ?? { redirectUrl: this.redirectUrl };
445933
+ if (scope === "client") {
445934
+ this.observedClientVersion = this.coordinator.clientVersion;
445935
+ if (this.pendingClientInformation === pendingClientInformation) {
445936
+ this.pendingClientInformation = undefined;
445937
+ } else if (this.pendingClientInformation) {
445938
+ this.pendingClientInformation.baseVersion = this.coordinator.clientVersion;
445939
+ }
445444
445940
  }
445445
- if (!code2 || !state || state !== expectedState.value) {
445446
- response.writeHead(400, { "Content-Type": "text/html" });
445447
- response.end(callbackPage("Authorization failed", "Invalid OAuth callback"));
445448
- reject2?.(new Error("Invalid MCP OAuth callback state"));
445449
- server2.close();
445941
+ if (scope === "tokens") {
445942
+ this.observedTokenVersion = this.coordinator.tokenVersion;
445943
+ }
445944
+ if (scope === "discovery") {
445945
+ this.observedDiscoveryVersion = this.coordinator.discoveryVersion;
445946
+ if (this.pendingDiscoveryState === pendingDiscoveryState) {
445947
+ this.pendingDiscoveryState = undefined;
445948
+ } else if (this.pendingDiscoveryState) {
445949
+ this.pendingDiscoveryState.baseVersion = this.coordinator.discoveryVersion;
445950
+ }
445951
+ }
445952
+ }
445953
+ async dispose() {
445954
+ if (this.closed)
445450
445955
  return;
445956
+ this.closed = true;
445957
+ this.scrubLocalState();
445958
+ try {
445959
+ await releaseCredentialCoordinator(this.coordinationKey);
445960
+ } finally {
445961
+ this.coordinatorValue = undefined;
445962
+ this.scrubLocalState();
445451
445963
  }
445452
- response.writeHead(200, { "Content-Type": "text/html" });
445453
- response.end(callbackPage("Authorization complete", "You can close this tab and return to Letta Code."));
445454
- settle3?.(code2);
445455
- server2.close();
445456
- });
445457
- await new Promise((resolve39, rejectListen) => {
445458
- server2.once("error", rejectListen);
445459
- server2.listen(port, "127.0.0.1", resolve39);
445460
- });
445461
- server2.unref();
445462
- const address = server2.address();
445463
- if (!address || typeof address === "string") {
445464
- server2.close();
445465
- throw new Error("Could not start MCP OAuth callback server");
445466
445964
  }
445467
- const timeout = setTimeout(() => {
445468
- reject2?.(new Error("Timed out waiting for MCP OAuth authorization"));
445469
- server2.close();
445470
- }, CALLBACK_TIMEOUT_MS);
445471
- timeout.unref();
445965
+ get coordinator() {
445966
+ if (!this.coordinatorValue)
445967
+ throw new Error("MCP OAuth session is closed");
445968
+ return this.coordinatorValue;
445969
+ }
445970
+ scrubLocalState() {
445971
+ this.codeVerifierValue = undefined;
445972
+ this.pendingClientInformation = undefined;
445973
+ this.pendingDiscoveryState = undefined;
445974
+ this.stateData = { redirectUrl: this.stateData.redirectUrl };
445975
+ }
445976
+ assertOpen() {
445977
+ if (this.closed)
445978
+ throw new Error("MCP OAuth session is closed");
445979
+ }
445980
+ }
445981
+ function exportCredentialSnapshot(state) {
445982
+ const clientInformation = state.clientInformation;
445983
+ const tokens = state.tokens;
445984
+ const validClient = clientInformation && "redirect_uris" in clientInformation && clientInformation.redirect_uris.includes(state.redirectUrl) ? clientInformation : undefined;
445985
+ if (!validClient?.client_id || !tokens?.access_token) {
445986
+ throw new Error("MCP OAuth authorization is not complete");
445987
+ }
445472
445988
  return {
445473
- redirectUrl: `http://127.0.0.1:${address.port}/callback`,
445474
- expectedState,
445475
- waitForCode: () => codePromise.finally(() => clearTimeout(timeout)),
445476
- close: () => {
445477
- clearTimeout(timeout);
445478
- if (!completed)
445479
- reject2?.(new Error("MCP OAuth flow was cancelled"));
445480
- server2.close();
445481
- }
445989
+ access_token: tokens.access_token,
445990
+ ...tokens.refresh_token ? { refresh_token: tokens.refresh_token } : {},
445991
+ client_id: validClient.client_id,
445992
+ ...validClient.client_secret ? { client_secret: validClient.client_secret } : {},
445993
+ redirect_uri: state.redirectUrl,
445994
+ ...tokens.token_type ? { token_type: tokens.token_type } : {},
445995
+ ...state.tokenExpiresAt !== undefined ? {
445996
+ expires_in: Math.max(0, Math.ceil((state.tokenExpiresAt - Date.now()) / 1000))
445997
+ } : {},
445998
+ ...tokens.scope ? { scope: tokens.scope } : {}
445482
445999
  };
445483
446000
  }
445484
- function callbackPort(redirectUrl) {
445485
- if (!redirectUrl)
446001
+ function enqueueStorageMutation(coordinationKey, mutation) {
446002
+ const coordinator = getCredentialCoordinator(coordinationKey);
446003
+ const pending = coordinator.queue.then(mutation, mutation);
446004
+ coordinator.queue = pending.then(() => {
445486
446005
  return;
445487
- try {
445488
- const parsed = new URL(redirectUrl);
445489
- return parsed.hostname === "127.0.0.1" && parsed.port ? Number(parsed.port) : undefined;
445490
- } catch {
446006
+ }, () => {
445491
446007
  return;
446008
+ });
446009
+ return pending;
446010
+ }
446011
+ function getCredentialCoordinator(coordinationKey) {
446012
+ const existing = credentialCoordinators.get(coordinationKey);
446013
+ if (existing)
446014
+ return existing;
446015
+ const coordinator = {
446016
+ queue: Promise.resolve(),
446017
+ generation: 0,
446018
+ activeProviders: 0,
446019
+ clientVersion: 0,
446020
+ tokenVersion: 0,
446021
+ discoveryVersion: 0
446022
+ };
446023
+ credentialCoordinators.set(coordinationKey, coordinator);
446024
+ return coordinator;
446025
+ }
446026
+ function storageCoordinationKey(storage, credentialKey, storageNamespace) {
446027
+ const namespace = storageNamespace;
446028
+ if (namespace !== undefined && (!namespace.trim() || namespace.includes("\x00"))) {
446029
+ throw new Error("MCP OAuth storage namespace must be non-empty and contain no NUL bytes");
446030
+ }
446031
+ let identity2;
446032
+ if (!namespace) {
446033
+ let objectId = storageObjectIds.get(storage);
446034
+ if (!objectId) {
446035
+ objectId = nextStorageObjectId;
446036
+ nextStorageObjectId += 1;
446037
+ storageObjectIds.set(storage, objectId);
446038
+ }
446039
+ identity2 = `object:${objectId}`;
446040
+ } else {
446041
+ identity2 = `namespace:${namespace}`;
445492
446042
  }
446043
+ return `${identity2}\x00${credentialKey}`;
445493
446044
  }
445494
- function callbackPage(title, message) {
445495
- return `<!doctype html><html><body style="font-family:system-ui;padding:2rem"><h1>${escapeHtml2(title)}</h1><p>${escapeHtml2(message)}</p></body></html>`;
446045
+ function retainCredentialCoordinator(coordinationKey) {
446046
+ getCredentialCoordinator(coordinationKey).activeProviders += 1;
445496
446047
  }
445497
- function escapeHtml2(value) {
445498
- return value.replace(/[&<>"']/g, (character) => {
445499
- const entities = {
445500
- "&": "&amp;",
445501
- "<": "&lt;",
445502
- ">": "&gt;",
445503
- '"': "&quot;",
445504
- "'": "&#39;"
445505
- };
445506
- return entities[character] ?? character;
446048
+ async function releaseCredentialCoordinator(coordinationKey) {
446049
+ const coordinator = getCredentialCoordinator(coordinationKey);
446050
+ await enqueueStorageMutation(coordinationKey, async () => {
446051
+ coordinator.activeProviders = Math.max(0, coordinator.activeProviders - 1);
446052
+ if (coordinator.activeProviders === 0)
446053
+ coordinator.state = undefined;
445507
446054
  });
446055
+ await coordinator.queue;
446056
+ if (coordinator.activeProviders === 0 && credentialCoordinators.get(coordinationKey) === coordinator) {
446057
+ credentialCoordinators.delete(coordinationKey);
446058
+ }
446059
+ }
446060
+ function assertCurrentGeneration(coordinationKey, expectedGeneration) {
446061
+ if (getCredentialCoordinator(coordinationKey).generation !== expectedGeneration) {
446062
+ throw new Error("MCP OAuth session is stale because credentials were cleared");
446063
+ }
446064
+ }
446065
+ async function clearStoredCredentials(storage, credentialKey, coordinationKey, expected) {
446066
+ const coordinator = getCredentialCoordinator(coordinationKey);
446067
+ const outcome = await enqueueStorageMutation(coordinationKey, async () => {
446068
+ if (expected) {
446069
+ assertCurrentGeneration(coordinationKey, expected.generation);
446070
+ if (coordinator.clientVersion !== expected.clientVersion || coordinator.tokenVersion !== expected.tokenVersion || coordinator.discoveryVersion !== expected.discoveryVersion) {
446071
+ return {
446072
+ result: false,
446073
+ generation: coordinator.generation,
446074
+ cleared: false
446075
+ };
446076
+ }
446077
+ }
446078
+ const result3 = await storage.delete(credentialKey);
446079
+ coordinator.generation += 1;
446080
+ coordinator.clientVersion += 1;
446081
+ coordinator.tokenVersion += 1;
446082
+ coordinator.discoveryVersion += 1;
446083
+ coordinator.state = undefined;
446084
+ return { result: result3, generation: coordinator.generation, cleared: true };
446085
+ });
446086
+ await coordinator.queue;
446087
+ if (coordinator.activeProviders === 0 && credentialCoordinators.get(coordinationKey) === coordinator) {
446088
+ credentialCoordinators.delete(coordinationKey);
446089
+ }
446090
+ return outcome;
445508
446091
  }
445509
446092
  async function openSystemBrowser(url2) {
445510
446093
  try {
@@ -445513,25 +446096,73 @@ async function openSystemBrowser(url2) {
445513
446096
  subprocess.on("error", () => {});
445514
446097
  } catch {}
445515
446098
  }
445516
- function oauthSecretName(agentId, serverName, serverUrl) {
446099
+ function mcpOAuthCredentialKey(agentId, serverName, serverUrl) {
445517
446100
  const digest = createHash11("sha256").update(`${agentId}\x00${serverName}\x00${serverUrl}`).digest("hex").slice(0, 32);
445518
446101
  return `mcp-oauth-${digest}`;
445519
446102
  }
445520
- async function loadState(secretName) {
445521
- const value = await getSecretValue(secretName, "MCP OAuth credentials");
446103
+ async function loadState(storage, credentialKey, coordinationKey) {
446104
+ return enqueueStorageMutation(coordinationKey, async () => {
446105
+ const coordinator = getCredentialCoordinator(coordinationKey);
446106
+ const state = await readStoredState(storage, credentialKey);
446107
+ coordinator.state = state;
446108
+ return { state, generation: coordinator.generation };
446109
+ });
446110
+ }
446111
+ async function readStoredState(storage, credentialKey) {
446112
+ const value = await storage.get(credentialKey);
445522
446113
  if (!value)
445523
446114
  return;
445524
446115
  try {
445525
- return JSON.parse(value);
446116
+ const parsed = JSON.parse(value);
446117
+ if (!isPersistedMcpOAuthState(parsed)) {
446118
+ await storage.delete(credentialKey);
446119
+ return;
446120
+ }
446121
+ return parsed;
445526
446122
  } catch {
445527
- await deleteSecretValue(secretName);
446123
+ await storage.delete(credentialKey);
445528
446124
  return;
445529
446125
  }
445530
446126
  }
445531
- var CALLBACK_TIMEOUT_MS;
446127
+ function isPersistedMcpOAuthState(value) {
446128
+ if (!isRecord17(value) || !SafeUrlSchema.safeParse(value.redirectUrl).success || "codeVerifier" in value) {
446129
+ return false;
446130
+ }
446131
+ if (value.clientInformation !== undefined) {
446132
+ if (!isRecord17(value.clientInformation))
446133
+ return false;
446134
+ const schema5 = "redirect_uris" in value.clientInformation ? OAuthClientInformationFullSchema : OAuthClientInformationSchema;
446135
+ if (!schema5.safeParse(value.clientInformation).success)
446136
+ return false;
446137
+ }
446138
+ if (value.tokens !== undefined && !OAuthTokensSchema.safeParse(value.tokens).success) {
446139
+ return false;
446140
+ }
446141
+ if (value.tokenExpiresAt !== undefined && (typeof value.tokenExpiresAt !== "number" || !Number.isFinite(value.tokenExpiresAt))) {
446142
+ return false;
446143
+ }
446144
+ if (value.discoveryState !== undefined) {
446145
+ if (!isRecord17(value.discoveryState) || !SafeUrlSchema.safeParse(value.discoveryState.authorizationServerUrl).success || value.discoveryState.resourceMetadataUrl !== undefined && !SafeUrlSchema.safeParse(value.discoveryState.resourceMetadataUrl).success || value.discoveryState.authorizationServerMetadata !== undefined && !OAuthMetadataSchema.safeParse(value.discoveryState.authorizationServerMetadata).success && !OpenIdProviderDiscoveryMetadataSchema.safeParse(value.discoveryState.authorizationServerMetadata).success || value.discoveryState.resourceMetadata !== undefined && !OAuthProtectedResourceMetadataSchema.safeParse(value.discoveryState.resourceMetadata).success) {
446146
+ return false;
446147
+ }
446148
+ }
446149
+ return true;
446150
+ }
446151
+ function isRecord17(value) {
446152
+ return typeof value === "object" && value !== null && !Array.isArray(value);
446153
+ }
446154
+ var credentialCoordinators, storageObjectIds, nextStorageObjectId = 1, bunSecretStorage;
445532
446155
  var init_mcp_oauth = __esm(() => {
446156
+ init_auth2();
446157
+ init_mcp_oauth_callback();
445533
446158
  init_secrets();
445534
- CALLBACK_TIMEOUT_MS = 5 * 60 * 1000;
446159
+ credentialCoordinators = new Map;
446160
+ storageObjectIds = new WeakMap;
446161
+ bunSecretStorage = {
446162
+ get: (credentialKey) => getSecretValue(credentialKey, "MCP OAuth credentials"),
446163
+ set: setSecretValue,
446164
+ delete: deleteSecretValue
446165
+ };
445535
446166
  });
445536
446167
 
445537
446168
  // src/mcp-runtime.ts
@@ -447936,7 +448567,7 @@ import {
447936
448567
  } from "node:fs";
447937
448568
  import { tmpdir as tmpdir12 } from "node:os";
447938
448569
  import path45 from "node:path";
447939
- function isRecord17(value) {
448570
+ function isRecord18(value) {
447940
448571
  return typeof value === "object" && value !== null && !Array.isArray(value);
447941
448572
  }
447942
448573
  function isPathInsideOrEqual2(childPath, parentPath) {
@@ -447952,7 +448583,7 @@ function readPackageJson(packageJsonPath) {
447952
448583
  } catch (error5) {
447953
448584
  throw new Error(`Could not read package.json: ${error5 instanceof Error ? error5.message : String(error5)}`);
447954
448585
  }
447955
- if (!isRecord17(parsed)) {
448586
+ if (!isRecord18(parsed)) {
447956
448587
  throw new Error("package.json must be an object");
447957
448588
  }
447958
448589
  return parsed;
@@ -447962,7 +448593,7 @@ function formatRepository(repository) {
447962
448593
  const trimmed2 = repository.trim();
447963
448594
  return trimmed2 || undefined;
447964
448595
  }
447965
- if (!isRecord17(repository))
448596
+ if (!isRecord18(repository))
447966
448597
  return;
447967
448598
  const url2 = repository.url;
447968
448599
  if (typeof url2 !== "string")
@@ -448492,7 +449123,7 @@ function hasRuntimeDependencies(packageJson) {
448492
449123
  if (!packageJson)
448493
449124
  return false;
448494
449125
  const dependencies4 = packageJson.dependencies;
448495
- return isRecord17(dependencies4) && Object.keys(dependencies4).length > 0;
449126
+ return isRecord18(dependencies4) && Object.keys(dependencies4).length > 0;
448496
449127
  }
448497
449128
  function readPackageJsonIfExists(packageDirectory) {
448498
449129
  const packageJsonPath = path45.join(packageDirectory, "package.json");
@@ -512630,4 +513261,4 @@ function registerBunOAuthFlows() {
512630
513261
  registerBunOAuthFlows();
512631
513262
  await init_src5().then(() => exports_src2);
512632
513263
 
512633
- //# debugId=8FBCBEB92238D00764756E2164756E21
513264
+ //# debugId=7B5F416253AF257964756E2164756E21