replicas-engine 0.1.610 → 0.1.612

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 (2) hide show
  1. package/dist/src/index.js +402 -180
  2. package/package.json +1 -1
package/dist/src/index.js CHANGED
@@ -60,6 +60,9 @@ var VALID_CODING_AGENT_PROVIDERS = VALID_AGENT_PROVIDERS.filter(
60
60
  function isValidAgentProvider(value) {
61
61
  return VALID_AGENT_PROVIDERS.some((p) => p === value);
62
62
  }
63
+ function isValidCodingAgentProvider(value) {
64
+ return VALID_CODING_AGENT_PROVIDERS.some((provider) => provider === value);
65
+ }
63
66
  var VALID_THINKING_LEVELS = ["low", "medium", "high", "xhigh", "max", "ultra", "ultracode"];
64
67
  var STANDARD_THINKING_LEVELS = ["low", "medium", "high", "xhigh", "max"];
65
68
  var THINKING_LEVELS_BY_AGENT = {
@@ -86,6 +89,117 @@ function codexReasoningEffortForThinkingLevel(thinkingLevel) {
86
89
  return thinkingLevel ? CODEX_REASONING_EFFORT_BY_THINKING_LEVEL[thinkingLevel] : void 0;
87
90
  }
88
91
 
92
+ // ../shared/src/agent-quota.ts
93
+ var OUT_OF_CREDITS_PATTERNS = [
94
+ /credit balance is too low/,
95
+ /out.of.credits/,
96
+ /credits?.?(?:depleted|exhausted|remaining)/,
97
+ /insufficient credits/,
98
+ /insufficient_quota/
99
+ ];
100
+ var RATE_LIMIT_PATTERNS = [
101
+ // Loose on purpose: providers word this as "rate limit", "rate_limit",
102
+ // "usageLimitExceeded" and so on, and these only ever run against error text.
103
+ /rate.?limit/,
104
+ /usage.?limit/,
105
+ /too many requests/,
106
+ /\bquota\b/
107
+ ];
108
+ var HTTP_429_PATTERN = /(?:^|[^0-9])429(?:$|[^0-9])/;
109
+ var HTTP_CONTEXT_PATTERN = /(api error|request|response|status|http)/;
110
+ function detectAgentQuotaLimit(text) {
111
+ const normalized = text.toLowerCase();
112
+ if (!normalized) return null;
113
+ if (OUT_OF_CREDITS_PATTERNS.some((pattern) => pattern.test(normalized))) return "out_of_credits";
114
+ if (RATE_LIMIT_PATTERNS.some((pattern) => pattern.test(normalized))) return "rate_limit";
115
+ return HTTP_429_PATTERN.test(normalized) && HTTP_CONTEXT_PATTERN.test(normalized) ? "rate_limit" : null;
116
+ }
117
+
118
+ // ../shared/src/credentials/types.ts
119
+ var CREDENTIAL_SCOPE = {
120
+ USER: "user",
121
+ ORG: "org"
122
+ };
123
+ var CREDENTIAL_SCOPES = Object.values(CREDENTIAL_SCOPE);
124
+ var CREDENTIAL_SCOPE_PRIORITY = [CREDENTIAL_SCOPE.USER, CREDENTIAL_SCOPE.ORG];
125
+
126
+ // ../shared/src/credentials/agents.ts
127
+ var CREDENTIAL_METHOD = {
128
+ OAUTH: "oauth",
129
+ API_KEY: "api_key",
130
+ FOUNDRY: "foundry",
131
+ BEDROCK: "bedrock",
132
+ OPENCODE_GO: "opencode-go",
133
+ ASTER: "aster",
134
+ OPENROUTER: "openrouter"
135
+ };
136
+ var CREDENTIAL_TYPE = {
137
+ CLAUDE_OAUTH: "claude",
138
+ ANTHROPIC_API_KEY: "claude_anthropic_api_key",
139
+ CLAUDE_FOUNDRY: "claude_foundry",
140
+ CLAUDE_BEDROCK: "claude_bedrock",
141
+ CODEX_OAUTH: "codex",
142
+ OPENAI_API_KEY: "openai_api_key",
143
+ CODEX_FOUNDRY: "codex_foundry",
144
+ CURSOR_API_KEY: "cursor_api_key",
145
+ OPENCODE_GO_API_KEY: "opencode_go_api_key",
146
+ ASTER_API_KEY: "aster_api_key",
147
+ OPENROUTER_API_KEY: "openrouter_api_key"
148
+ };
149
+ var AGENT_CREDENTIAL_METHODS_IN_ORDER = {
150
+ [AGENT.CLAUDE]: [
151
+ { method: CREDENTIAL_METHOD.OAUTH, credentialType: CREDENTIAL_TYPE.CLAUDE_OAUTH },
152
+ { method: CREDENTIAL_METHOD.API_KEY, credentialType: CREDENTIAL_TYPE.ANTHROPIC_API_KEY },
153
+ { method: CREDENTIAL_METHOD.FOUNDRY, credentialType: CREDENTIAL_TYPE.CLAUDE_FOUNDRY },
154
+ { method: CREDENTIAL_METHOD.BEDROCK, credentialType: CREDENTIAL_TYPE.CLAUDE_BEDROCK }
155
+ ],
156
+ [AGENT.CODEX]: [
157
+ { method: CREDENTIAL_METHOD.OAUTH, credentialType: CREDENTIAL_TYPE.CODEX_OAUTH },
158
+ { method: CREDENTIAL_METHOD.API_KEY, credentialType: CREDENTIAL_TYPE.OPENAI_API_KEY },
159
+ { method: CREDENTIAL_METHOD.FOUNDRY, credentialType: CREDENTIAL_TYPE.CODEX_FOUNDRY }
160
+ ],
161
+ [AGENT.CURSOR]: [
162
+ { method: CREDENTIAL_METHOD.API_KEY, credentialType: CREDENTIAL_TYPE.CURSOR_API_KEY }
163
+ ],
164
+ [AGENT.OPENCODE]: [
165
+ { method: CREDENTIAL_METHOD.OPENCODE_GO, credentialType: CREDENTIAL_TYPE.OPENCODE_GO_API_KEY },
166
+ { method: CREDENTIAL_METHOD.ASTER, credentialType: CREDENTIAL_TYPE.ASTER_API_KEY },
167
+ { method: CREDENTIAL_METHOD.OPENROUTER, credentialType: CREDENTIAL_TYPE.OPENROUTER_API_KEY }
168
+ ],
169
+ [AGENT.PI]: [
170
+ { method: CREDENTIAL_METHOD.ASTER, credentialType: CREDENTIAL_TYPE.ASTER_API_KEY },
171
+ { method: CREDENTIAL_METHOD.OPENROUTER, credentialType: CREDENTIAL_TYPE.OPENROUTER_API_KEY }
172
+ ]
173
+ };
174
+
175
+ // ../shared/src/analytics/types.ts
176
+ function isAgentChatActivityRecord(value) {
177
+ return isRecord(value) && typeof value.chatId === "string" && typeof value.provider === "string" && isValidAgentProvider(value.provider) && typeof value.model === "string" && (value.credentialMethod === void 0 || isAuthMethod(value.credentialMethod)) && (value.credentialScope === void 0 || isCredentialScope(value.credentialScope)) && value.credentialMethod === void 0 === (value.credentialScope === void 0) && (value.senderUserId === void 0 || typeof value.senderUserId === "string") && typeof value.occurredAt === "string";
178
+ }
179
+ function isAuthMethod(value) {
180
+ return typeof value === "string" && Object.values(CREDENTIAL_METHOD).some((method) => method === value);
181
+ }
182
+ function isCredentialScope(value) {
183
+ return typeof value === "string" && CREDENTIAL_SCOPES.some((scope) => scope === value);
184
+ }
185
+ function isAgentChatTurnActivityRecord(value) {
186
+ return isAgentChatActivityRecord(value) && typeof value.turnId === "string" && typeof value.seconds === "number" && Number.isFinite(value.seconds) && value.seconds >= 0;
187
+ }
188
+ function isAgentChatActivityCallRecord(value) {
189
+ return isAgentChatActivityRecord(value) && typeof value.callId === "string" && value.callId.length > 0;
190
+ }
191
+ function isNamedAgentChatActivityRecord(value, field) {
192
+ if (!isAgentChatActivityCallRecord(value)) return false;
193
+ const name = value[field];
194
+ return typeof name === "string" && name.length > 0;
195
+ }
196
+ function isAgentChatSkillActivityRecord(value) {
197
+ return isNamedAgentChatActivityRecord(value, "skillName");
198
+ }
199
+ function isAgentChatMcpActivityRecord(value) {
200
+ return isNamedAgentChatActivityRecord(value, "mcpName");
201
+ }
202
+
89
203
  // ../shared/src/event.ts
90
204
  var CLAUDE_PARTIAL_MESSAGE_EVENT_TYPE = "claude-partial-message";
91
205
  function coerceClaudePartialMessagePayload(payload) {
@@ -112,6 +226,23 @@ var COMPACTION_STATUS_EVENT_TYPE = "compaction-status";
112
226
  var CHAT_INTERRUPTED_EVENT_TYPE = "replicas-interrupted";
113
227
  var CHAT_GOAL_EVENT_TYPE = "chat-goal";
114
228
  var AUTH_RETRY_STATUS_EVENT_TYPE = "auth-retry-status";
229
+ var AUTH_FALLBACK_EVENT_TYPE = "auth-fallback";
230
+ function coerceAuthFallbackPayload(payload) {
231
+ const { provider, reason, exhaustedMethod, exhaustedScope, candidateMethod, candidateScope, status, detail } = payload;
232
+ if (typeof provider !== "string" || !isValidCodingAgentProvider(provider) || reason !== "rate_limit" && reason !== "out_of_credits" || !isAuthMethod(exhaustedMethod) || !isAuthMethod(candidateMethod) || !isCredentialScope(candidateScope) || status !== "switched" && status !== "failed") {
233
+ return null;
234
+ }
235
+ return {
236
+ provider,
237
+ reason,
238
+ exhaustedMethod,
239
+ ...isCredentialScope(exhaustedScope) ? { exhaustedScope } : {},
240
+ candidateMethod,
241
+ candidateScope,
242
+ status,
243
+ ...typeof detail === "string" && detail ? { detail } : {}
244
+ };
245
+ }
115
246
  var CONTEXT_USAGE_EVENT_TYPE = "context-usage";
116
247
 
117
248
  // ../shared/src/languages.ts
@@ -205,91 +336,6 @@ function detectLanguageByPath(filePath) {
205
336
  return EXT_TO_LANGUAGE[ext] ?? null;
206
337
  }
207
338
 
208
- // ../shared/src/credentials/types.ts
209
- var CREDENTIAL_SCOPE = {
210
- USER: "user",
211
- ORG: "org"
212
- };
213
- var CREDENTIAL_SCOPES = Object.values(CREDENTIAL_SCOPE);
214
- var CREDENTIAL_SCOPE_PRIORITY = [CREDENTIAL_SCOPE.USER, CREDENTIAL_SCOPE.ORG];
215
-
216
- // ../shared/src/credentials/agents.ts
217
- var CREDENTIAL_METHOD = {
218
- OAUTH: "oauth",
219
- API_KEY: "api_key",
220
- FOUNDRY: "foundry",
221
- BEDROCK: "bedrock",
222
- OPENCODE_GO: "opencode-go",
223
- ASTER: "aster",
224
- OPENROUTER: "openrouter"
225
- };
226
- var CREDENTIAL_TYPE = {
227
- CLAUDE_OAUTH: "claude",
228
- ANTHROPIC_API_KEY: "claude_anthropic_api_key",
229
- CLAUDE_FOUNDRY: "claude_foundry",
230
- CLAUDE_BEDROCK: "claude_bedrock",
231
- CODEX_OAUTH: "codex",
232
- OPENAI_API_KEY: "openai_api_key",
233
- CODEX_FOUNDRY: "codex_foundry",
234
- CURSOR_API_KEY: "cursor_api_key",
235
- OPENCODE_GO_API_KEY: "opencode_go_api_key",
236
- ASTER_API_KEY: "aster_api_key",
237
- OPENROUTER_API_KEY: "openrouter_api_key"
238
- };
239
- var AGENT_CREDENTIAL_METHODS_IN_ORDER = {
240
- [AGENT.CLAUDE]: [
241
- { method: CREDENTIAL_METHOD.OAUTH, credentialType: CREDENTIAL_TYPE.CLAUDE_OAUTH },
242
- { method: CREDENTIAL_METHOD.API_KEY, credentialType: CREDENTIAL_TYPE.ANTHROPIC_API_KEY },
243
- { method: CREDENTIAL_METHOD.FOUNDRY, credentialType: CREDENTIAL_TYPE.CLAUDE_FOUNDRY },
244
- { method: CREDENTIAL_METHOD.BEDROCK, credentialType: CREDENTIAL_TYPE.CLAUDE_BEDROCK }
245
- ],
246
- [AGENT.CODEX]: [
247
- { method: CREDENTIAL_METHOD.OAUTH, credentialType: CREDENTIAL_TYPE.CODEX_OAUTH },
248
- { method: CREDENTIAL_METHOD.API_KEY, credentialType: CREDENTIAL_TYPE.OPENAI_API_KEY },
249
- { method: CREDENTIAL_METHOD.FOUNDRY, credentialType: CREDENTIAL_TYPE.CODEX_FOUNDRY }
250
- ],
251
- [AGENT.CURSOR]: [
252
- { method: CREDENTIAL_METHOD.API_KEY, credentialType: CREDENTIAL_TYPE.CURSOR_API_KEY }
253
- ],
254
- [AGENT.OPENCODE]: [
255
- { method: CREDENTIAL_METHOD.OPENCODE_GO, credentialType: CREDENTIAL_TYPE.OPENCODE_GO_API_KEY },
256
- { method: CREDENTIAL_METHOD.ASTER, credentialType: CREDENTIAL_TYPE.ASTER_API_KEY },
257
- { method: CREDENTIAL_METHOD.OPENROUTER, credentialType: CREDENTIAL_TYPE.OPENROUTER_API_KEY }
258
- ],
259
- [AGENT.PI]: [
260
- { method: CREDENTIAL_METHOD.ASTER, credentialType: CREDENTIAL_TYPE.ASTER_API_KEY },
261
- { method: CREDENTIAL_METHOD.OPENROUTER, credentialType: CREDENTIAL_TYPE.OPENROUTER_API_KEY }
262
- ]
263
- };
264
-
265
- // ../shared/src/analytics/types.ts
266
- function isAgentChatActivityRecord(value) {
267
- return isRecord(value) && typeof value.chatId === "string" && typeof value.provider === "string" && isValidAgentProvider(value.provider) && typeof value.model === "string" && (value.credentialMethod === void 0 || isAuthMethod(value.credentialMethod)) && (value.credentialScope === void 0 || isCredentialScope(value.credentialScope)) && value.credentialMethod === void 0 === (value.credentialScope === void 0) && (value.senderUserId === void 0 || typeof value.senderUserId === "string") && typeof value.occurredAt === "string";
268
- }
269
- function isAuthMethod(value) {
270
- return typeof value === "string" && Object.values(CREDENTIAL_METHOD).some((method) => method === value);
271
- }
272
- function isCredentialScope(value) {
273
- return typeof value === "string" && CREDENTIAL_SCOPES.some((scope) => scope === value);
274
- }
275
- function isAgentChatTurnActivityRecord(value) {
276
- return isAgentChatActivityRecord(value) && typeof value.turnId === "string" && typeof value.seconds === "number" && Number.isFinite(value.seconds) && value.seconds >= 0;
277
- }
278
- function isAgentChatActivityCallRecord(value) {
279
- return isAgentChatActivityRecord(value) && typeof value.callId === "string" && value.callId.length > 0;
280
- }
281
- function isNamedAgentChatActivityRecord(value, field) {
282
- if (!isAgentChatActivityCallRecord(value)) return false;
283
- const name = value[field];
284
- return typeof name === "string" && name.length > 0;
285
- }
286
- function isAgentChatSkillActivityRecord(value) {
287
- return isNamedAgentChatActivityRecord(value, "skillName");
288
- }
289
- function isAgentChatMcpActivityRecord(value) {
290
- return isNamedAgentChatActivityRecord(value, "mcpName");
291
- }
292
-
293
339
  // ../shared/src/aster.ts
294
340
  var ASTER_PROVIDER = "aster";
295
341
  var ASTER_BASE_URL = "https://api.asterlab.ai/v1";
@@ -2501,7 +2547,8 @@ var DEFAULT_USER_PREFERENCES = {
2501
2547
  auto_draft_prs: false,
2502
2548
  open_prs_in_graphite: false,
2503
2549
  default_fast_mode: false,
2504
- agent_defaults: { ...EMPTY_AGENT_DEFAULT_SETTINGS }
2550
+ agent_defaults: { ...EMPTY_AGENT_DEFAULT_SETTINGS },
2551
+ credential_priority: null
2505
2552
  };
2506
2553
  function defaultReplicasAgentAbilities() {
2507
2554
  const abilities = {};
@@ -2686,7 +2733,7 @@ var TRANSIENT_NETWORK_ERROR_PATTERNS = [
2686
2733
  /connection closed/,
2687
2734
  /closed unexpectedly/
2688
2735
  ];
2689
- var HTTP_CONTEXT_PATTERN = /(api error|request|response|status|http)/;
2736
+ var HTTP_CONTEXT_PATTERN2 = /(api error|request|response|status|http)/;
2690
2737
  var HTTP_STATUS_PATTERN = /(^|[^0-9])([1-5][0-9]{2})(?=$|[^0-9])/g;
2691
2738
  function extractErrorText(error) {
2692
2739
  const parts = [];
@@ -2728,7 +2775,7 @@ function isTransientErrorText(text, options = {}) {
2728
2775
  const httpStatuses = new Set(options.httpStatuses ?? []);
2729
2776
  const httpStatusClasses = new Set(options.httpStatusClasses ?? []);
2730
2777
  if (httpStatuses.size === 0 && httpStatusClasses.size === 0) return false;
2731
- if (options.requireHttpContext !== false && !HTTP_CONTEXT_PATTERN.test(normalized)) return false;
2778
+ if (options.requireHttpContext !== false && !HTTP_CONTEXT_PATTERN2.test(normalized)) return false;
2732
2779
  for (const match of normalized.matchAll(HTTP_STATUS_PATTERN)) {
2733
2780
  const status = Number(match[2]);
2734
2781
  if (httpStatuses.has(status) || httpStatusClasses.has(Math.floor(status / 100))) return true;
@@ -2739,7 +2786,7 @@ function isTransientErrorText(text, options = {}) {
2739
2786
  // ../shared/src/claude-auth.ts
2740
2787
  function isClaudeAuthErrorText(text) {
2741
2788
  const lower = text.toLowerCase();
2742
- return lower.includes("failed to authenticate") || lower.includes("authentication_error") || lower.includes("authentication_failed") || lower.includes("authentication failed") || lower.includes("invalid authentication credentials") || lower.includes("not logged in") || lower.includes("please run /login") || lower.includes("credit balance is too low") || lower.includes("401") && lower.includes("authentic");
2789
+ return lower.includes("failed to authenticate") || lower.includes("authentication_error") || lower.includes("authentication_failed") || lower.includes("authentication failed") || lower.includes("invalid authentication credentials") || lower.includes("not logged in") || lower.includes("please run /login") || lower.includes("401") && lower.includes("authentic");
2743
2790
  }
2744
2791
 
2745
2792
  // ../shared/src/codex-auth.ts
@@ -5122,21 +5169,24 @@ function parseDisplayMessages(events, agentType, codexAspTranscript, options = {
5122
5169
  const shouldFilter = options.filter ?? true;
5123
5170
  const parsedEvents = agentType === "claude" || agentType === "relay" ? parseClaudeEvents(events, options.parentToolUseId) : parseAgentEvents(events, agentType);
5124
5171
  const legacyMessages = shouldFilter ? filterDisplayMessages(parsedEvents, agentType) : parsedEvents;
5172
+ const finalize = (messages) => shouldFilter ? applyAuthFallbackNotices(applyInterruptions(messages, events), events) : messages;
5125
5173
  if (agentType !== "codex" || !codexAspTranscript) {
5126
- return shouldFilter ? applyInterruptions(legacyMessages, events) : legacyMessages;
5174
+ return finalize(legacyMessages);
5127
5175
  }
5128
5176
  const nativeCodexMessages = shouldFilter ? filterDisplayMessages(parseCodexAspTranscript(codexAspTranscript), agentType) : parseCodexAspTranscript(codexAspTranscript);
5129
- const merged = mergeCodexAspDisplayMessages(nativeCodexMessages, legacyMessages);
5130
- return shouldFilter ? applyInterruptions(merged, events) : merged;
5177
+ return finalize(mergeCodexAspDisplayMessages(nativeCodexMessages, legacyMessages));
5178
+ }
5179
+ function insertByTimestamp(messages, message) {
5180
+ const messageMs = parseTimestampMs(message.timestamp);
5181
+ let index = messages.length;
5182
+ while (index > 0 && parseTimestampMs(messages[index - 1].timestamp) > messageMs) index--;
5183
+ messages.splice(index, 0, message);
5131
5184
  }
5132
5185
  function applyInterruptions(messages, events) {
5133
5186
  const result = [...messages];
5134
5187
  for (const event of events) {
5135
5188
  if (event.type !== CHAT_INTERRUPTED_EVENT_TYPE) continue;
5136
- const eventMs = parseTimestampMs(event.timestamp);
5137
- let index = result.length;
5138
- while (index > 0 && parseTimestampMs(result[index - 1].timestamp) > eventMs) index--;
5139
- result.splice(index, 0, {
5189
+ insertByTimestamp(result, {
5140
5190
  id: `interruption-${event.timestamp}`,
5141
5191
  type: "interruption",
5142
5192
  timestamp: event.timestamp
@@ -5168,6 +5218,24 @@ function applyInterruptions(messages, events) {
5168
5218
  }
5169
5219
  return finalized;
5170
5220
  }
5221
+ function applyAuthFallbackNotices(messages, events) {
5222
+ const notices = [];
5223
+ events.forEach((event, index) => {
5224
+ if (event.type !== AUTH_FALLBACK_EVENT_TYPE) return;
5225
+ const payload = coerceAuthFallbackPayload(event.payload);
5226
+ if (!payload) return;
5227
+ notices.push({
5228
+ ...payload,
5229
+ id: `auth-fallback-${event.timestamp}-${index}`,
5230
+ type: "auth_fallback",
5231
+ timestamp: event.timestamp
5232
+ });
5233
+ });
5234
+ if (notices.length === 0) return messages;
5235
+ const result = [...messages];
5236
+ for (const notice of notices) insertByTimestamp(result, notice);
5237
+ return result;
5238
+ }
5171
5239
  function isCodexInitializationPrompt(message) {
5172
5240
  return message.type === "user" && removeReplicasInstructions(message.content).trim() === "Hello";
5173
5241
  }
@@ -5392,6 +5460,28 @@ function setAgentCredentialSnapshot(provider, snapshot) {
5392
5460
  process.env.REPLICAS_AGENT_CREDENTIALS = JSON.stringify(ENGINE_ENV.REPLICAS_AGENT_CREDENTIALS);
5393
5461
  }
5394
5462
 
5463
+ // src/services/credential-fallbacks.ts
5464
+ var fallbacksByAgent = /* @__PURE__ */ new Map();
5465
+ var exhaustedByAgent = /* @__PURE__ */ new Map();
5466
+ function recordCredentialFallback(notice) {
5467
+ fallbacksByAgent.set(notice.provider, notice);
5468
+ }
5469
+ function listCredentialFallbacks() {
5470
+ return [...fallbacksByAgent.values()].filter((notice) => {
5471
+ const live = ENGINE_ENV.REPLICAS_AGENT_CREDENTIALS[notice.provider];
5472
+ if (!live) return false;
5473
+ return notice.status === "switched" ? live.method === notice.candidateMethod && live.scope === notice.candidateScope : live.method === notice.exhaustedMethod && live.scope === notice.exhaustedScope;
5474
+ });
5475
+ }
5476
+ function listExhaustedCredentials(provider) {
5477
+ return [...exhaustedByAgent.get(provider)?.values() ?? []];
5478
+ }
5479
+ function recordExhaustedCredential(provider, credential) {
5480
+ const spent = exhaustedByAgent.get(provider) ?? /* @__PURE__ */ new Map();
5481
+ spent.set(`${credential.method}|${credential.scope}`, credential);
5482
+ exhaustedByAgent.set(provider, spent);
5483
+ }
5484
+
5395
5485
  // src/managers/base-refresh-manager.ts
5396
5486
  var BaseRefreshManager = class {
5397
5487
  constructor(managerName, intervalMs = 15 * 60 * 1e3) {
@@ -5446,6 +5536,29 @@ var BaseRefreshManager = class {
5446
5536
  }
5447
5537
  this.scheduleNextRefresh();
5448
5538
  }
5539
+ async swapCredentials(params) {
5540
+ if (!this.getRuntimeConfig()) {
5541
+ return createErrorResult({ message: `${this.managerName} has no runtime config`, code: "not_configured" });
5542
+ }
5543
+ try {
5544
+ console.log(`[${this.managerName}] Fetching fresh credentials from monolith (${params.failureKind})...`);
5545
+ const excludeCredentials = listExhaustedCredentials(params.provider);
5546
+ await params.refresh(excludeCredentials.length > 0 ? { excludeCredentials } : {});
5547
+ if (params.isOauthNow()) {
5548
+ this.start().catch((error) => {
5549
+ console.error(`[${this.managerName}] Failed to restart OAuth refresh service after fallback:`, error);
5550
+ });
5551
+ }
5552
+ return createSuccessResult();
5553
+ } catch (error) {
5554
+ const message = error instanceof Error ? error.message : String(error);
5555
+ console.error(`[${this.managerName}] Failed to fetch fresh credentials:`, error);
5556
+ return createErrorResult({
5557
+ message,
5558
+ code: message.includes('"code":"no_credentials"') ? "no_credentials" : "refresh_failed"
5559
+ });
5560
+ }
5561
+ }
5449
5562
  stop() {
5450
5563
  if (!this.intervalHandle) {
5451
5564
  return;
@@ -6465,26 +6578,14 @@ var ClaudeTokenManager = class extends BaseRefreshManager {
6465
6578
  if (data.scope) setAgentCredentialSnapshot("claude", { method: data.type, scope: data.scope });
6466
6579
  console.log(`[ClaudeTokenManager] Credentials refreshed (method=${data.type})`);
6467
6580
  }
6468
- async fetchFreshCredentials(failureReason) {
6469
- const config = this.getRuntimeConfig();
6470
- if (!config) return false;
6471
- try {
6472
- console.log("[ClaudeTokenManager] Fetching fresh credentials from monolith after auth failure...");
6473
- const failedMethod = ENGINE_ENV.REPLICAS_CLAUDE_AUTH_METHOD;
6474
- await this.refreshWithRequest(failedMethod && failedMethod !== "none" ? {
6475
- failedMethod,
6476
- failureReason
6477
- } : void 0);
6478
- if (ENGINE_ENV.REPLICAS_CLAUDE_AUTH_METHOD === "oauth") {
6479
- this.start().catch((error) => {
6480
- console.error("[ClaudeTokenManager] Failed to restart OAuth refresh service after fallback:", error);
6481
- });
6482
- }
6483
- return true;
6484
- } catch (error) {
6485
- console.error("[ClaudeTokenManager] Failed to fetch fresh credentials:", error);
6486
- return false;
6487
- }
6581
+ async fetchFreshCredentials(failureReason, failureKind = "rejected") {
6582
+ const failedMethod = ENGINE_ENV.REPLICAS_CLAUDE_AUTH_METHOD;
6583
+ return this.swapCredentials({
6584
+ provider: "claude",
6585
+ failureKind,
6586
+ refresh: (exclusions) => this.refreshWithRequest(failedMethod && failedMethod !== "none" ? { failedMethod, failureReason, failureKind, ...exclusions } : exclusions),
6587
+ isOauthNow: () => ENGINE_ENV.REPLICAS_CLAUDE_AUTH_METHOD === "oauth"
6588
+ });
6488
6589
  }
6489
6590
  async applyCredentialsResponse(response) {
6490
6591
  if (response.type === "oauth") {
@@ -6573,26 +6674,16 @@ var CodexTokenManager = class extends BaseRefreshManager {
6573
6674
  if (data.scope) setAgentCredentialSnapshot("codex", { method: data.type, scope: data.scope });
6574
6675
  console.log(`[CodexTokenManager] Credentials refreshed (method=${data.type})`);
6575
6676
  }
6576
- async fetchFreshCredentials(failureReason) {
6577
- const config = this.getRuntimeConfig();
6578
- if (!config) return false;
6579
- try {
6580
- console.log("[CodexTokenManager] Fetching fresh credentials from monolith after auth failure...");
6581
- const failedMethod = ENGINE_ENV.REPLICAS_CODEX_AUTH_METHOD;
6582
- await this.refreshWithRequest(failedMethod === "oauth" || failedMethod === "api_key" || failedMethod === "foundry" ? {
6583
- failedMethod,
6584
- failureReason
6585
- } : void 0);
6586
- if (ENGINE_ENV.REPLICAS_CODEX_AUTH_METHOD === "oauth") {
6587
- this.start().catch((error) => {
6588
- console.error("[CodexTokenManager] Failed to restart OAuth refresh service after fallback:", error);
6589
- });
6590
- }
6591
- return true;
6592
- } catch (error) {
6593
- console.error("[CodexTokenManager] Failed to fetch fresh credentials:", error);
6594
- return false;
6595
- }
6677
+ async fetchFreshCredentials(failureReason, failureKind = "rejected") {
6678
+ const failedMethod = ENGINE_ENV.REPLICAS_CODEX_AUTH_METHOD;
6679
+ return this.swapCredentials({
6680
+ provider: "codex",
6681
+ failureKind,
6682
+ refresh: (exclusions) => this.refreshWithRequest(
6683
+ failedMethod === "oauth" || failedMethod === "api_key" || failedMethod === "foundry" ? { failedMethod, failureReason, failureKind, ...exclusions } : exclusions
6684
+ ),
6685
+ isOauthNow: () => ENGINE_ENV.REPLICAS_CODEX_AUTH_METHOD === "oauth"
6686
+ });
6596
6687
  }
6597
6688
  async applyCredentialsResponse(response) {
6598
6689
  if (response.type === "oauth") {
@@ -6990,6 +7081,7 @@ var EnvironmentDetailsService = class {
6990
7081
  details.cursorAuthMethod = detectCursorAuthMethod();
6991
7082
  details.opencodeAuthMethod = detectOpencodeAuthMethod();
6992
7083
  details.piAuthMethod = detectPiAuthMethod();
7084
+ details.credentialFallbacks = listCredentialFallbacks();
6993
7085
  details.gitIdentityConfigured = gitIdentityConfigured;
6994
7086
  const ghConfigured = existsSync2(GH_HOSTS_PATH);
6995
7087
  details.githubAccessConfigured = ghConfigured;
@@ -7594,13 +7686,13 @@ import { mkdir as mkdir7 } from "fs/promises";
7594
7686
  import { homedir as homedir9 } from "os";
7595
7687
  import { join as join12 } from "path";
7596
7688
  import { randomUUID } from "crypto";
7597
- var ENGINE_DIR2 = join12(homedir9(), ".replicas", "engine");
7598
- var EVENTS_FILE = join12(ENGINE_DIR2, "events.jsonl");
7689
+ var ENGINE_DIR = join12(homedir9(), ".replicas", "engine");
7690
+ var EVENTS_FILE = join12(ENGINE_DIR, "events.jsonl");
7599
7691
  var EventService = class {
7600
7692
  subscribers = /* @__PURE__ */ new Map();
7601
7693
  writer = new StreamWriter();
7602
7694
  async initialize() {
7603
- await mkdir7(ENGINE_DIR2, { recursive: true });
7695
+ await mkdir7(ENGINE_DIR, { recursive: true });
7604
7696
  this.writer.open(EVENTS_FILE);
7605
7697
  }
7606
7698
  subscribe(subscriber) {
@@ -8262,6 +8354,48 @@ async function removeTempImageFiles(paths) {
8262
8354
  await Promise.allSettled(paths.map((path6) => unlink2(path6)));
8263
8355
  }
8264
8356
 
8357
+ // src/managers/auth-fallback.ts
8358
+ var SWAPPABLE_AGENTS = {
8359
+ claude: claudeTokenManager,
8360
+ codex: codexTokenManager
8361
+ };
8362
+ var AuthFallbackCoordinator = class {
8363
+ constructor(provider, record) {
8364
+ this.provider = provider;
8365
+ this.record = record;
8366
+ }
8367
+ provider;
8368
+ record;
8369
+ async fallBack(reason, detail) {
8370
+ const tokenManager = SWAPPABLE_AGENTS[this.provider];
8371
+ if (!tokenManager) return "unavailable";
8372
+ const exhausted = ENGINE_ENV.REPLICAS_AGENT_CREDENTIALS[this.provider];
8373
+ if (!exhausted) return "unavailable";
8374
+ recordExhaustedCredential(this.provider, exhausted);
8375
+ const swap = await tokenManager.fetchFreshCredentials(
8376
+ `${this.provider} ${reason === "out_of_credits" ? "is out of credits" : "hit its usage limit"}: ${detail}`,
8377
+ "exhausted"
8378
+ );
8379
+ if (!swap.ok && swap.error.code === "no_credentials") return "unavailable";
8380
+ const applied = ENGINE_ENV.REPLICAS_AGENT_CREDENTIALS[this.provider] ?? exhausted;
8381
+ const moved = swap.ok && !(applied.method === exhausted.method && applied.scope === exhausted.scope);
8382
+ const payload = {
8383
+ provider: this.provider,
8384
+ reason,
8385
+ exhaustedMethod: exhausted.method,
8386
+ exhaustedScope: exhausted.scope,
8387
+ candidateMethod: applied.method,
8388
+ candidateScope: applied.scope,
8389
+ status: moved ? "switched" : "failed",
8390
+ detail: moved ? detail : "Replicas could not load an alternate credential."
8391
+ };
8392
+ const at = (/* @__PURE__ */ new Date()).toISOString();
8393
+ this.record({ timestamp: at, type: AUTH_FALLBACK_EVENT_TYPE, payload: { ...payload } });
8394
+ recordCredentialFallback({ ...payload, at });
8395
+ return moved ? "switched" : "failed";
8396
+ }
8397
+ };
8398
+
8265
8399
  // src/services/message-queue-service.ts
8266
8400
  var MessageQueueService = class {
8267
8401
  queue = [];
@@ -8445,6 +8579,7 @@ var CodingAgentManager = class {
8445
8579
  onEvent;
8446
8580
  hostOnTurnComplete;
8447
8581
  onProcessingChanged;
8582
+ authFallback;
8448
8583
  compacting = false;
8449
8584
  constructor(options) {
8450
8585
  this.workingDirectory = options.workingDirectory ?? ENGINE_ENV.WORKSPACE_ROOT;
@@ -8454,6 +8589,14 @@ var CodingAgentManager = class {
8454
8589
  this.hostOnTurnComplete = options.onTurnComplete;
8455
8590
  this.onProcessingChanged = options.onProcessingChanged ?? (() => {
8456
8591
  });
8592
+ this.authFallback = options.provider ? new AuthFallbackCoordinator(options.provider, (event) => {
8593
+ this.onEvent(event);
8594
+ this.getHistorySink()?.append(event);
8595
+ }) : null;
8596
+ }
8597
+ async fallBackCredential(reason, detail) {
8598
+ if (!this.authFallback) return "unavailable";
8599
+ return this.authFallback.fallBack(reason, detail);
8457
8600
  }
8458
8601
  onTurnComplete = async () => {
8459
8602
  if (this.compacting) {
@@ -9444,7 +9587,16 @@ var ClaudeTransientTurnError = class extends Error {
9444
9587
  }
9445
9588
  midTurn;
9446
9589
  };
9590
+ var ClaudeQuotaError = class extends Error {
9591
+ constructor(message, kind) {
9592
+ super(message);
9593
+ this.kind = kind;
9594
+ this.name = "ClaudeQuotaError";
9595
+ }
9596
+ kind;
9597
+ };
9447
9598
  var MAX_AUTH_RETRIES = 2;
9599
+ var MAX_QUOTA_FALLBACKS = 2;
9448
9600
  var MAX_TRANSIENT_RETRIES = 2;
9449
9601
  var MAX_MIDTURN_CONTINUE_RETRIES = 2;
9450
9602
  var TRANSIENT_RETRY_DELAYS_MS = [1e3, 2500];
@@ -9490,7 +9642,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
9490
9642
  slashCommandsDiscovery = null;
9491
9643
  authRetrying = false;
9492
9644
  constructor(options) {
9493
- super(options);
9645
+ super({ ...options, provider: AGENT.CLAUDE });
9494
9646
  this.historyFilePath = options.historyFilePath ?? join16(homedir12(), ".replicas", "claude", "history.jsonl");
9495
9647
  this.historyFile = new CodexHistoryFile(this.historyFilePath);
9496
9648
  this.systemPromptOverride = options.systemPromptOverride;
@@ -9722,6 +9874,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
9722
9874
  async processMessageInternal(request) {
9723
9875
  let lastError;
9724
9876
  let authRetryExhausted = false;
9877
+ let quotaFallbacks = 0;
9725
9878
  let attempt = 0;
9726
9879
  let authRetries = 0;
9727
9880
  let transientRetries = 0;
@@ -9734,6 +9887,19 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
9734
9887
  return;
9735
9888
  } catch (error) {
9736
9889
  lastError = error;
9890
+ const quotaKind = _ClaudeManager.quotaLimitKind(error);
9891
+ if (quotaKind) {
9892
+ const detail = error instanceof ClaudeQuotaError ? error.message : extractErrorText(error);
9893
+ const outcome = quotaFallbacks < MAX_QUOTA_FALLBACKS ? await this.fallBackCredential(quotaKind, detail) : "unavailable";
9894
+ if (outcome === "switched") {
9895
+ quotaFallbacks++;
9896
+ await this.tearDownSession();
9897
+ attempt++;
9898
+ continue;
9899
+ }
9900
+ await this.emitQuotaExhaustedEvent(quotaKind, outcome, detail);
9901
+ return;
9902
+ }
9737
9903
  if (_ClaudeManager.isAuthError(error)) {
9738
9904
  if (authRetries >= MAX_AUTH_RETRIES) {
9739
9905
  authRetryExhausted = true;
@@ -9748,7 +9914,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
9748
9914
  const refreshed = await claudeTokenManager.fetchFreshCredentials(
9749
9915
  error instanceof Error ? error.message : String(error)
9750
9916
  );
9751
- if (!refreshed) {
9917
+ if (!refreshed.ok) {
9752
9918
  authRetryExhausted = true;
9753
9919
  throw error;
9754
9920
  }
@@ -9840,6 +10006,11 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
9840
10006
  return "Claude authentication failed after multiple attempts. Check your Claude, Anthropic API key, or Bedrock credentials in Settings \u2192 Agents and try again.";
9841
10007
  }
9842
10008
  }
10009
+ async emitQuotaExhaustedEvent(kind, outcome, detail) {
10010
+ const limit = kind === "out_of_credits" ? "Claude ran out of credits on the credential this workspace is using." : "Claude hit the usage limit on the credential this workspace is using.";
10011
+ const next = outcome === "failed" ? "Switching to the alternate credential failed. Check the credentials in Settings \u2192 Agents and try again." : "No other credential is available for this workspace. Add one in Settings \u2192 Agents, or try again once the limit resets.";
10012
+ await this.emitTerminalErrorResult(detail, [`${limit} ${next}`]);
10013
+ }
9843
10014
  async emitMidTurnExhaustedEvent(error) {
9844
10015
  const detail = error instanceof Error ? error.message : String(error);
9845
10016
  await this.emitTerminalErrorResult(
@@ -10097,6 +10268,17 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
10097
10268
  }
10098
10269
  return;
10099
10270
  }
10271
+ const quotaError = _ClaudeManager.detectQuotaErrorInMessage(msg);
10272
+ if (quotaError) {
10273
+ suppressLinearResponseFlush = true;
10274
+ this.failPendingTurn(quotaError);
10275
+ try {
10276
+ response.close();
10277
+ } catch (err) {
10278
+ console.warn("[ClaudeManager] query.close() during quota error failed:", err);
10279
+ }
10280
+ return;
10281
+ }
10100
10282
  const transientErrorMessage = _ClaudeManager.detectTransientTurnErrorInMessage(msg);
10101
10283
  if (transientErrorMessage) {
10102
10284
  suppressLinearResponseFlush = true;
@@ -10293,6 +10475,22 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
10293
10475
  if (error instanceof ClaudeAuthError) return true;
10294
10476
  return isClaudeAuthErrorText(error instanceof Error ? error.message : String(error));
10295
10477
  }
10478
+ static quotaLimitKind(error) {
10479
+ if (error instanceof ClaudeQuotaError) return error.kind;
10480
+ return detectAgentQuotaLimit(extractErrorText(error));
10481
+ }
10482
+ static detectQuotaErrorInMessage(message) {
10483
+ if (message.type === "assistant" && "error" in message) {
10484
+ const content = Array.isArray(message.message?.content) ? message.message.content.map((block) => "text" in block ? block.text : "").join("\n") : "";
10485
+ const text2 = [typeof message.error === "string" ? message.error : "", content].filter(Boolean).join("\n");
10486
+ const kind2 = detectAgentQuotaLimit(text2);
10487
+ if (kind2) return new ClaudeQuotaError(text2, kind2);
10488
+ }
10489
+ if (message.type !== "result" || !message.is_error) return null;
10490
+ const text = message.subtype === "success" ? message.result : message.errors.join("\n");
10491
+ const kind = detectAgentQuotaLimit(text);
10492
+ return kind ? new ClaudeQuotaError(text, kind) : null;
10493
+ }
10296
10494
  static isTransientTurnError(error) {
10297
10495
  if (error instanceof ClaudeTransientTurnError) return true;
10298
10496
  return _ClaudeManager.isTransientTurnErrorText(extractErrorText(error));
@@ -10670,7 +10868,7 @@ var DEFAULT_CODEX_ARGS = ["app-server", "--listen", "stdio://"];
10670
10868
  var MIN_CODEX_CLI_VERSION = "0.144.6";
10671
10869
  var CODEX_UPGRADE_TIMEOUT_MS = 12e4;
10672
10870
  var codexCliVersionEnsured = null;
10673
- var ENGINE_PACKAGE_VERSION = "0.1.610";
10871
+ var ENGINE_PACKAGE_VERSION = "0.1.612";
10674
10872
  var INITIALIZE_METHOD = "initialize";
10675
10873
  var INITIALIZED_NOTIFICATION = "initialized";
10676
10874
  var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
@@ -11267,7 +11465,7 @@ function isQuotaTurnFailure(turn) {
11267
11465
  return false;
11268
11466
  }
11269
11467
  function isQuotaErrorMessage(error) {
11270
- return error instanceof Error && /\b429\b|rate.?limit|usage.?limit|quota|out.of.credits|credits?.?(?:depleted|exhausted|remaining)/i.test(error.message);
11468
+ return error instanceof Error && detectAgentQuotaLimit(error.message) !== null;
11271
11469
  }
11272
11470
  function formatTurnFailure(turn) {
11273
11471
  const turnError = turn.error;
@@ -11599,14 +11797,14 @@ var TranscriptUpdateCoalescer = class {
11599
11797
  // src/services/chat/history-paths.ts
11600
11798
  import { homedir as homedir13 } from "os";
11601
11799
  import { join as join17 } from "path";
11602
- var ENGINE_DIR3 = join17(homedir13(), ".replicas", "engine");
11603
- var CHATS_FILE = join17(ENGINE_DIR3, "chats.json");
11604
- var CLAUDE_HISTORY_DIR = join17(ENGINE_DIR3, "claude-histories");
11605
- var RELAY_HISTORY_DIR = join17(ENGINE_DIR3, "relay-histories");
11606
- var CODEX_HISTORY_DIR = join17(ENGINE_DIR3, "codex-histories");
11607
- var CURSOR_HISTORY_DIR = join17(ENGINE_DIR3, "cursor-histories");
11608
- var OPENCODE_HISTORY_DIR = join17(ENGINE_DIR3, "opencode-histories");
11609
- var PI_HISTORY_DIR = join17(ENGINE_DIR3, "pi-histories");
11800
+ var ENGINE_DIR2 = join17(homedir13(), ".replicas", "engine");
11801
+ var CHATS_FILE = join17(ENGINE_DIR2, "chats.json");
11802
+ var CLAUDE_HISTORY_DIR = join17(ENGINE_DIR2, "claude-histories");
11803
+ var RELAY_HISTORY_DIR = join17(ENGINE_DIR2, "relay-histories");
11804
+ var CODEX_HISTORY_DIR = join17(ENGINE_DIR2, "codex-histories");
11805
+ var CURSOR_HISTORY_DIR = join17(ENGINE_DIR2, "cursor-histories");
11806
+ var OPENCODE_HISTORY_DIR = join17(ENGINE_DIR2, "opencode-histories");
11807
+ var PI_HISTORY_DIR = join17(ENGINE_DIR2, "pi-histories");
11610
11808
  var HISTORY_DIR_BY_PROVIDER = {
11611
11809
  claude: CLAUDE_HISTORY_DIR,
11612
11810
  relay: RELAY_HISTORY_DIR,
@@ -11637,6 +11835,12 @@ var DuplicateDefaultChatError = class extends Error {
11637
11835
  };
11638
11836
 
11639
11837
  // src/managers/codex-asp/codex-asp-manager.ts
11838
+ var CodexQuotaError = class extends Error {
11839
+ constructor(message) {
11840
+ super(message);
11841
+ this.name = "CodexQuotaError";
11842
+ }
11843
+ };
11640
11844
  var GOAL_TURN_CONTINUATION_GRACE_MS = 5e3;
11641
11845
  var CODEX_SLASH_COMMANDS_CACHE_MS = 3e4;
11642
11846
  var POLICY_REASON_INJECTION_TIMEOUT_MS = 5e3;
@@ -11721,7 +11925,7 @@ var CodexAspManager = class extends CodingAgentManager {
11721
11925
  steeredTempImagePaths = [];
11722
11926
  steerLock = new AsyncLock();
11723
11927
  constructor(options) {
11724
- super(options);
11928
+ super({ ...options, provider: AGENT.CODEX });
11725
11929
  this.historyFile = options.historyFilePath ? new CodexHistoryFile(options.historyFilePath) : null;
11726
11930
  this.initializeManager(this.processMessageInternal.bind(this));
11727
11931
  }
@@ -11901,6 +12105,7 @@ var CodexAspManager = class extends CodingAgentManager {
11901
12105
  }
11902
12106
  async processMessageInternal(request) {
11903
12107
  let userMessageRecorded = false;
12108
+ let quotaFallbackAttempted = false;
11904
12109
  const recordUserMessage = (extraPayload = {}) => {
11905
12110
  if (userMessageRecorded) return;
11906
12111
  userMessageRecorded = true;
@@ -11946,17 +12151,26 @@ var CodexAspManager = class extends CodingAgentManager {
11946
12151
  await dispatch();
11947
12152
  } catch (error) {
11948
12153
  let terminalError = error;
12154
+ if (terminalError instanceof CodexQuotaError && !quotaFallbackAttempted) {
12155
+ quotaFallbackAttempted = true;
12156
+ const outcome = await this.fallBackCredential(
12157
+ detectAgentQuotaLimit(terminalError.message) ?? "rate_limit",
12158
+ terminalError.message
12159
+ );
12160
+ if (outcome === "switched") {
12161
+ const retryFailure = await this.restartAndRetry(dispatch);
12162
+ if (!retryFailure) return;
12163
+ terminalError = retryFailure.error;
12164
+ } else if (await this.confirmOutOfCredits(await getCodexAspHost())) {
12165
+ return;
12166
+ }
12167
+ }
11949
12168
  if (isCodexAuthError(terminalError)) {
11950
12169
  const refreshed = await codexTokenManager.fetchFreshCredentials(terminalError instanceof Error ? terminalError.message : String(terminalError));
11951
- if (refreshed) {
11952
- try {
11953
- await restartCodexAspHost();
11954
- this.threadAttached = false;
11955
- await dispatch();
11956
- return;
11957
- } catch (retryError) {
11958
- terminalError = retryError;
11959
- }
12170
+ if (refreshed.ok) {
12171
+ const retryFailure = await this.restartAndRetry(dispatch);
12172
+ if (!retryFailure) return;
12173
+ terminalError = retryFailure.error;
11960
12174
  }
11961
12175
  }
11962
12176
  const event = this.recordHistoryEvent("codex-asp-error", {
@@ -12008,16 +12222,14 @@ var CodexAspManager = class extends CodingAgentManager {
12008
12222
  try {
12009
12223
  completedTurn = await runTurn(host, threadId);
12010
12224
  } catch (error) {
12011
- if (isQuotaErrorMessage(error) && await this.confirmOutOfCredits(host)) {
12012
- return;
12225
+ if (isQuotaErrorMessage(error)) {
12226
+ throw new CodexQuotaError(error instanceof Error ? error.message : String(error));
12013
12227
  }
12014
12228
  throw error;
12015
12229
  }
12016
12230
  if (completedTurn.status === "failed") {
12017
- if (isQuotaTurnFailure(completedTurn) && await this.confirmOutOfCredits(host)) {
12018
- return;
12019
- }
12020
- throw new Error(formatTurnFailure(completedTurn));
12231
+ const failure = formatTurnFailure(completedTurn);
12232
+ throw isQuotaTurnFailure(completedTurn) ? new CodexQuotaError(failure) : new Error(failure);
12021
12233
  }
12022
12234
  this.emitQuotaEvent(this.quotaStatus.recordTurnSuccess());
12023
12235
  }
@@ -12887,6 +13099,16 @@ var CodexAspManager = class extends CodingAgentManager {
12887
13099
  } catch {
12888
13100
  }
12889
13101
  }
13102
+ async restartAndRetry(dispatch) {
13103
+ try {
13104
+ await restartCodexAspHost();
13105
+ this.threadAttached = false;
13106
+ await dispatch();
13107
+ return null;
13108
+ } catch (error) {
13109
+ return { error };
13110
+ }
13111
+ }
12890
13112
  async confirmOutOfCredits(host) {
12891
13113
  await this.refreshQuotaSnapshot(host);
12892
13114
  const event = this.quotaStatus.confirmOutOfCredits();
@@ -15026,7 +15248,7 @@ var AgentChatActivityBuffer = class {
15026
15248
  options.storageName,
15027
15249
  ...options.legacyStorageNames ?? []
15028
15250
  ];
15029
- this.liveFile = join22(ENGINE_DIR, `${options.storageName}.jsonl`);
15251
+ this.liveFile = join22(ENGINE_DIR2, `${options.storageName}.jsonl`);
15030
15252
  this.segmentFilePatterns = this.storageNames.map(
15031
15253
  (storageName) => new RegExp(`^${storageName}\\.(\\d+)\\.jsonl$`)
15032
15254
  );
@@ -15043,7 +15265,7 @@ var AgentChatActivityBuffer = class {
15043
15265
  failed: 0
15044
15266
  });
15045
15267
  append(record) {
15046
- const pending = mkdir14(ENGINE_DIR, { recursive: true }).then(
15268
+ const pending = mkdir14(ENGINE_DIR2, { recursive: true }).then(
15047
15269
  () => appendFile3(this.liveFile, `${JSON.stringify(record)}
15048
15270
  `, "utf-8")
15049
15271
  ).catch((error) => {
@@ -15085,19 +15307,19 @@ var AgentChatActivityBuffer = class {
15085
15307
  await Promise.allSettled([...this.pendingAppends]);
15086
15308
  for (const storageName of this.storageNames) {
15087
15309
  await rename2(
15088
- join22(ENGINE_DIR, `${storageName}.jsonl`),
15089
- join22(ENGINE_DIR, `${storageName}.${Date.now()}.jsonl`)
15310
+ join22(ENGINE_DIR2, `${storageName}.jsonl`),
15311
+ join22(ENGINE_DIR2, `${storageName}.${Date.now()}.jsonl`)
15090
15312
  ).catch(() => {
15091
15313
  });
15092
15314
  }
15093
- const entries = await readdir6(ENGINE_DIR).catch(() => []);
15315
+ const entries = await readdir6(ENGINE_DIR2).catch(() => []);
15094
15316
  let flushed = 0;
15095
15317
  let failed = 0;
15096
15318
  for (const entry of entries) {
15097
15319
  if (!this.segmentFilePatterns.some((pattern) => pattern.test(entry)))
15098
15320
  continue;
15099
15321
  try {
15100
- await this.uploadSegment(join22(ENGINE_DIR, entry));
15322
+ await this.uploadSegment(join22(ENGINE_DIR2, entry));
15101
15323
  flushed++;
15102
15324
  } catch (error) {
15103
15325
  failed++;
@@ -15129,7 +15351,7 @@ var AgentChatActivityBuffer = class {
15129
15351
  ) && entry.endsWith(UPLOADED_SUFFIX)
15130
15352
  ).sort();
15131
15353
  for (const entry of uploaded.slice(0, -MAX_UPLOADED_SEGMENTS)) {
15132
- await unlink3(join22(ENGINE_DIR, entry)).catch(() => {
15354
+ await unlink3(join22(ENGINE_DIR2, entry)).catch(() => {
15133
15355
  });
15134
15356
  }
15135
15357
  return { flushed, failed };
@@ -15598,7 +15820,7 @@ import { basename as basename2, join as join25 } from "path";
15598
15820
 
15599
15821
  // src/services/chat/chat-senders.ts
15600
15822
  import { join as join24 } from "path";
15601
- var CHAT_SENDERS_DIR = join24(ENGINE_DIR3, "chat-senders");
15823
+ var CHAT_SENDERS_DIR = join24(ENGINE_DIR2, "chat-senders");
15602
15824
  function chatMessageSendersFilePath(chatId) {
15603
15825
  return join24(CHAT_SENDERS_DIR, `${chatId}.jsonl`);
15604
15826
  }
@@ -15616,9 +15838,9 @@ function parseChatMessageSendersJsonl(content) {
15616
15838
 
15617
15839
  // src/services/upload-chat-transcripts.ts
15618
15840
  var HISTORY_DIRS = [
15619
- join25(ENGINE_DIR3, "claude-histories"),
15620
- join25(ENGINE_DIR3, "relay-histories"),
15621
- join25(ENGINE_DIR3, "codex-histories")
15841
+ join25(ENGINE_DIR2, "claude-histories"),
15842
+ join25(ENGINE_DIR2, "relay-histories"),
15843
+ join25(ENGINE_DIR2, "codex-histories")
15622
15844
  ];
15623
15845
  async function putTranscript(uploadUrl, filePath, size) {
15624
15846
  await new Promise((resolve5, reject) => {
@@ -16002,7 +16224,7 @@ var ChatService = class {
16002
16224
  persistInFlight = false;
16003
16225
  persistQueued = false;
16004
16226
  async initialize() {
16005
- await mkdir15(ENGINE_DIR3, { recursive: true });
16227
+ await mkdir15(ENGINE_DIR2, { recursive: true });
16006
16228
  await mkdir15(CLAUDE_HISTORY_DIR, { recursive: true });
16007
16229
  await mkdir15(RELAY_HISTORY_DIR, { recursive: true });
16008
16230
  await mkdir15(CODEX_HISTORY_DIR, { recursive: true });
@@ -16653,7 +16875,7 @@ var ChatService = class {
16653
16875
  }
16654
16876
  }).catch(() => {
16655
16877
  });
16656
- if (event.type === "replicas-tool-input-request" || event.type === "replicas-tool-input-resolved" || event.type === "compaction-status" || event.type === AUTH_RETRY_STATUS_EVENT_TYPE || event.type === CHAT_GOAL_EVENT_TYPE || event.type === CLAUDE_ACTIVITY_STATUS_EVENT_TYPE || event.type === "claude-result" || event.type === "claude-system" && (event.payload.subtype === "init" || event.payload.subtype === "session_state_changed" || event.payload.subtype === "background_tasks_changed" || coerceBackgroundTaskPayload(event.payload) !== null)) {
16878
+ if (event.type === "replicas-tool-input-request" || event.type === "replicas-tool-input-resolved" || event.type === "compaction-status" || event.type === AUTH_RETRY_STATUS_EVENT_TYPE || event.type === AUTH_FALLBACK_EVENT_TYPE || event.type === CHAT_GOAL_EVENT_TYPE || event.type === CLAUDE_ACTIVITY_STATUS_EVENT_TYPE || event.type === "claude-result" || event.type === "claude-system" && (event.payload.subtype === "init" || event.payload.subtype === "session_state_changed" || event.payload.subtype === "background_tasks_changed" || coerceBackgroundTaskPayload(event.payload) !== null)) {
16657
16879
  this.publish({
16658
16880
  type: "chat.updated",
16659
16881
  payload: { chat: this.toSummary(chat) }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.610",
3
+ "version": "0.1.612",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",