replicas-engine 0.1.609 → 0.1.611
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/dist/src/index.js +639 -301
- 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 isUsageRecord(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 isChatTurnUsageRecord(value) {
|
|
276
|
-
return isUsageRecord(value) && typeof value.turnId === "string" && typeof value.seconds === "number" && Number.isFinite(value.seconds) && value.seconds >= 0;
|
|
277
|
-
}
|
|
278
|
-
function isCallUsageRecord(value) {
|
|
279
|
-
return isUsageRecord(value) && typeof value.callId === "string" && value.callId.length > 0;
|
|
280
|
-
}
|
|
281
|
-
function isNamedCallUsageRecord(value, field) {
|
|
282
|
-
if (!isCallUsageRecord(value)) return false;
|
|
283
|
-
const name = value[field];
|
|
284
|
-
return typeof name === "string" && name.length > 0;
|
|
285
|
-
}
|
|
286
|
-
function isSkillUsageRecord(value) {
|
|
287
|
-
return isNamedCallUsageRecord(value, "skillName");
|
|
288
|
-
}
|
|
289
|
-
function isMcpUsageRecord(value) {
|
|
290
|
-
return isNamedCallUsageRecord(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
|
|
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 && !
|
|
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("
|
|
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
|
|
5174
|
+
return finalize(legacyMessages);
|
|
5127
5175
|
}
|
|
5128
5176
|
const nativeCodexMessages = shouldFilter ? filterDisplayMessages(parseCodexAspTranscript(codexAspTranscript), agentType) : parseCodexAspTranscript(codexAspTranscript);
|
|
5129
|
-
|
|
5130
|
-
|
|
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
|
-
|
|
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
|
|
6470
|
-
|
|
6471
|
-
|
|
6472
|
-
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
|
|
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
|
|
6578
|
-
|
|
6579
|
-
|
|
6580
|
-
|
|
6581
|
-
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
|
|
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
|
|
7598
|
-
var EVENTS_FILE = join12(
|
|
7689
|
+
var ENGINE_DIR2 = join12(homedir9(), ".replicas", "engine");
|
|
7690
|
+
var EVENTS_FILE = join12(ENGINE_DIR2, "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(
|
|
7695
|
+
await mkdir7(ENGINE_DIR2, { 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.
|
|
10871
|
+
var ENGINE_PACKAGE_VERSION = "0.1.611";
|
|
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 &&
|
|
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
|
|
11603
|
-
var CHATS_FILE = join17(
|
|
11604
|
-
var CLAUDE_HISTORY_DIR = join17(
|
|
11605
|
-
var RELAY_HISTORY_DIR = join17(
|
|
11606
|
-
var CODEX_HISTORY_DIR = join17(
|
|
11607
|
-
var CURSOR_HISTORY_DIR = join17(
|
|
11608
|
-
var OPENCODE_HISTORY_DIR = join17(
|
|
11609
|
-
var PI_HISTORY_DIR = join17(
|
|
11800
|
+
var ENGINE_DIR3 = join17(homedir13(), ".replicas", "engine");
|
|
11801
|
+
var CHATS_FILE = join17(ENGINE_DIR3, "chats.json");
|
|
11802
|
+
var CLAUDE_HISTORY_DIR = join17(ENGINE_DIR3, "claude-histories");
|
|
11803
|
+
var RELAY_HISTORY_DIR = join17(ENGINE_DIR3, "relay-histories");
|
|
11804
|
+
var CODEX_HISTORY_DIR = join17(ENGINE_DIR3, "codex-histories");
|
|
11805
|
+
var CURSOR_HISTORY_DIR = join17(ENGINE_DIR3, "cursor-histories");
|
|
11806
|
+
var OPENCODE_HISTORY_DIR = join17(ENGINE_DIR3, "opencode-histories");
|
|
11807
|
+
var PI_HISTORY_DIR = join17(ENGINE_DIR3, "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
|
-
|
|
11953
|
-
|
|
11954
|
-
|
|
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)
|
|
12012
|
-
|
|
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
|
-
|
|
12018
|
-
|
|
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();
|
|
@@ -14966,34 +15188,93 @@ var RelayManager = class {
|
|
|
14966
15188
|
}
|
|
14967
15189
|
};
|
|
14968
15190
|
|
|
14969
|
-
// src/analytics/
|
|
15191
|
+
// src/analytics/agent/activity/agent-chat-activity-tracker-service.ts
|
|
15192
|
+
import {
|
|
15193
|
+
appendFile as appendFile3,
|
|
15194
|
+
mkdir as mkdir14,
|
|
15195
|
+
readFile as readFile12,
|
|
15196
|
+
readdir as readdir6,
|
|
15197
|
+
rename as rename2,
|
|
15198
|
+
unlink as unlink3
|
|
15199
|
+
} from "fs/promises";
|
|
15200
|
+
import { join as join22 } from "path";
|
|
14970
15201
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
14971
15202
|
|
|
14972
|
-
// src/analytics/
|
|
14973
|
-
|
|
14974
|
-
|
|
15203
|
+
// src/analytics/agent/activity/skill-mcp-call-extractor.ts
|
|
15204
|
+
var NON_MCP_SERVERS = /* @__PURE__ */ new Set(["claude", "cursor", "opencode", "pi", "custom", "dynamic"]);
|
|
15205
|
+
function mcpNameFromToolCall(message) {
|
|
15206
|
+
const parsedName = parseMcpToolName(message.tool);
|
|
15207
|
+
if (parsedName) return parsedName.server;
|
|
15208
|
+
if (message.tool.startsWith("mcp.")) {
|
|
15209
|
+
const [server, ...toolParts] = message.tool.slice("mcp.".length).split(".");
|
|
15210
|
+
if (server && toolParts.length > 0) return server;
|
|
15211
|
+
}
|
|
15212
|
+
return NON_MCP_SERVERS.has(message.server) ? null : message.server;
|
|
15213
|
+
}
|
|
15214
|
+
function skillCallsFromCommand(message) {
|
|
15215
|
+
const skillNames = message.skillNames ?? [];
|
|
15216
|
+
return skillNames.map((skillName, index) => ({
|
|
15217
|
+
kind: "skill",
|
|
15218
|
+
id: skillNames.length === 1 ? message.id : `${message.id}-${index}`,
|
|
15219
|
+
skillName,
|
|
15220
|
+
occurredAt: message.timestamp
|
|
15221
|
+
}));
|
|
15222
|
+
}
|
|
15223
|
+
function extractSkillMcpCalls(messages, provider) {
|
|
15224
|
+
return messages.flatMap((message) => {
|
|
15225
|
+
if (message.type === "skill") {
|
|
15226
|
+
if (provider === "relay") return [];
|
|
15227
|
+
return [{ kind: "skill", id: message.id, skillName: message.skillName, occurredAt: message.timestamp }];
|
|
15228
|
+
}
|
|
15229
|
+
if (message.type === "command") {
|
|
15230
|
+
return skillCallsFromCommand(message);
|
|
15231
|
+
}
|
|
15232
|
+
if (message.type !== "tool_call") return [];
|
|
15233
|
+
const mcpName = mcpNameFromToolCall(message);
|
|
15234
|
+
return mcpName ? [{ kind: "mcp", id: message.id, mcpName, occurredAt: message.timestamp }] : [];
|
|
15235
|
+
});
|
|
15236
|
+
}
|
|
15237
|
+
|
|
15238
|
+
// src/analytics/agent/activity/agent-chat-activity-tracker-service.ts
|
|
14975
15239
|
var MAX_FAILED_RECORDS = 500;
|
|
14976
15240
|
var FLUSH_DEBOUNCE_MS = 15e3;
|
|
14977
15241
|
var MAX_UPLOADED_SEGMENTS = 50;
|
|
14978
15242
|
var UPLOADED_SUFFIX = ".uploaded";
|
|
14979
|
-
var
|
|
15243
|
+
var MAX_TRACKED_MESSAGES = 500;
|
|
15244
|
+
var AgentChatActivityBuffer = class {
|
|
14980
15245
|
constructor(options) {
|
|
14981
15246
|
this.options = options;
|
|
14982
|
-
this.
|
|
14983
|
-
|
|
15247
|
+
this.storageNames = [
|
|
15248
|
+
options.storageName,
|
|
15249
|
+
...options.legacyStorageNames ?? []
|
|
15250
|
+
];
|
|
15251
|
+
this.liveFile = join22(ENGINE_DIR, `${options.storageName}.jsonl`);
|
|
15252
|
+
this.segmentFilePatterns = this.storageNames.map(
|
|
15253
|
+
(storageName) => new RegExp(`^${storageName}\\.(\\d+)\\.jsonl$`)
|
|
15254
|
+
);
|
|
14984
15255
|
}
|
|
14985
15256
|
options;
|
|
14986
15257
|
liveFile;
|
|
14987
|
-
|
|
15258
|
+
storageNames;
|
|
15259
|
+
segmentFilePatterns;
|
|
14988
15260
|
failedRecords = [];
|
|
14989
15261
|
pendingAppends = /* @__PURE__ */ new Set();
|
|
14990
15262
|
flushTimer = null;
|
|
14991
|
-
activeFlush = Promise.resolve({
|
|
15263
|
+
activeFlush = Promise.resolve({
|
|
15264
|
+
flushed: 0,
|
|
15265
|
+
failed: 0
|
|
15266
|
+
});
|
|
14992
15267
|
append(record) {
|
|
14993
|
-
const pending = mkdir14(
|
|
14994
|
-
|
|
14995
|
-
|
|
14996
|
-
|
|
15268
|
+
const pending = mkdir14(ENGINE_DIR, { recursive: true }).then(
|
|
15269
|
+
() => appendFile3(this.liveFile, `${JSON.stringify(record)}
|
|
15270
|
+
`, "utf-8")
|
|
15271
|
+
).catch((error) => {
|
|
15272
|
+
console.error(
|
|
15273
|
+
`[${this.options.storageName}] Append failed, will retry on flush:`,
|
|
15274
|
+
error
|
|
15275
|
+
);
|
|
15276
|
+
if (this.failedRecords.length < MAX_FAILED_RECORDS)
|
|
15277
|
+
this.failedRecords.push(record);
|
|
14997
15278
|
});
|
|
14998
15279
|
this.pendingAppends.add(pending);
|
|
14999
15280
|
void pending.finally(() => this.pendingAppends.delete(pending));
|
|
@@ -15003,7 +15284,10 @@ var AnalyticsBufferService = class {
|
|
|
15003
15284
|
this.flushTimer = setTimeout(() => {
|
|
15004
15285
|
this.flushTimer = null;
|
|
15005
15286
|
void this.flush().catch((error) => {
|
|
15006
|
-
console.error(
|
|
15287
|
+
console.error(
|
|
15288
|
+
`[${this.options.storageName}] Scheduled flush failed, retrying on the next record:`,
|
|
15289
|
+
error
|
|
15290
|
+
);
|
|
15007
15291
|
});
|
|
15008
15292
|
}, FLUSH_DEBOUNCE_MS);
|
|
15009
15293
|
this.flushTimer.unref?.();
|
|
@@ -15019,20 +15303,30 @@ var AnalyticsBufferService = class {
|
|
|
15019
15303
|
}
|
|
15020
15304
|
async runFlush() {
|
|
15021
15305
|
for (const record of this.failedRecords.splice(0)) this.append(record);
|
|
15022
|
-
while (this.pendingAppends.size > 0)
|
|
15023
|
-
|
|
15024
|
-
|
|
15025
|
-
|
|
15306
|
+
while (this.pendingAppends.size > 0)
|
|
15307
|
+
await Promise.allSettled([...this.pendingAppends]);
|
|
15308
|
+
for (const storageName of this.storageNames) {
|
|
15309
|
+
await rename2(
|
|
15310
|
+
join22(ENGINE_DIR, `${storageName}.jsonl`),
|
|
15311
|
+
join22(ENGINE_DIR, `${storageName}.${Date.now()}.jsonl`)
|
|
15312
|
+
).catch(() => {
|
|
15313
|
+
});
|
|
15314
|
+
}
|
|
15315
|
+
const entries = await readdir6(ENGINE_DIR).catch(() => []);
|
|
15026
15316
|
let flushed = 0;
|
|
15027
15317
|
let failed = 0;
|
|
15028
15318
|
for (const entry of entries) {
|
|
15029
|
-
if (!this.
|
|
15319
|
+
if (!this.segmentFilePatterns.some((pattern) => pattern.test(entry)))
|
|
15320
|
+
continue;
|
|
15030
15321
|
try {
|
|
15031
|
-
await this.uploadSegment(join22(
|
|
15322
|
+
await this.uploadSegment(join22(ENGINE_DIR, entry));
|
|
15032
15323
|
flushed++;
|
|
15033
15324
|
} catch (error) {
|
|
15034
15325
|
failed++;
|
|
15035
|
-
console.error(
|
|
15326
|
+
console.error(
|
|
15327
|
+
`[${this.options.storageName}] Segment upload failed, retained for retry:`,
|
|
15328
|
+
{ entry, error }
|
|
15329
|
+
);
|
|
15036
15330
|
}
|
|
15037
15331
|
}
|
|
15038
15332
|
const failedRecords = this.failedRecords.splice(0);
|
|
@@ -15042,13 +15336,22 @@ var AnalyticsBufferService = class {
|
|
|
15042
15336
|
flushed++;
|
|
15043
15337
|
} catch (error) {
|
|
15044
15338
|
failed++;
|
|
15045
|
-
this.failedRecords.unshift(
|
|
15046
|
-
|
|
15339
|
+
this.failedRecords.unshift(
|
|
15340
|
+
...failedRecords.slice(0, MAX_FAILED_RECORDS)
|
|
15341
|
+
);
|
|
15342
|
+
console.error(
|
|
15343
|
+
`[${this.options.storageName}] Failed records upload failed, retained for retry:`,
|
|
15344
|
+
error
|
|
15345
|
+
);
|
|
15047
15346
|
}
|
|
15048
15347
|
}
|
|
15049
|
-
const uploaded = entries.filter(
|
|
15348
|
+
const uploaded = entries.filter(
|
|
15349
|
+
(entry) => this.storageNames.some(
|
|
15350
|
+
(storageName) => entry.startsWith(`${storageName}.`)
|
|
15351
|
+
) && entry.endsWith(UPLOADED_SUFFIX)
|
|
15352
|
+
).sort();
|
|
15050
15353
|
for (const entry of uploaded.slice(0, -MAX_UPLOADED_SEGMENTS)) {
|
|
15051
|
-
await unlink3(join22(
|
|
15354
|
+
await unlink3(join22(ENGINE_DIR, entry)).catch(() => {
|
|
15052
15355
|
});
|
|
15053
15356
|
}
|
|
15054
15357
|
return { flushed, failed };
|
|
@@ -15067,66 +15370,40 @@ var AnalyticsBufferService = class {
|
|
|
15067
15370
|
});
|
|
15068
15371
|
}
|
|
15069
15372
|
};
|
|
15070
|
-
|
|
15071
|
-
|
|
15072
|
-
|
|
15073
|
-
|
|
15074
|
-
|
|
15075
|
-
if (parsedName) return parsedName.server;
|
|
15076
|
-
if (message.tool.startsWith("mcp.")) {
|
|
15077
|
-
const [server, ...toolParts] = message.tool.slice("mcp.".length).split(".");
|
|
15078
|
-
if (server && toolParts.length > 0) return server;
|
|
15373
|
+
var AgentChatActivityRecordTracker = class {
|
|
15374
|
+
pendingRecords = /* @__PURE__ */ new Map();
|
|
15375
|
+
buffer;
|
|
15376
|
+
constructor(options) {
|
|
15377
|
+
this.buffer = new AgentChatActivityBuffer(options);
|
|
15079
15378
|
}
|
|
15080
|
-
|
|
15081
|
-
|
|
15082
|
-
|
|
15083
|
-
|
|
15084
|
-
|
|
15085
|
-
|
|
15086
|
-
|
|
15087
|
-
|
|
15088
|
-
|
|
15089
|
-
|
|
15090
|
-
|
|
15091
|
-
|
|
15092
|
-
|
|
15093
|
-
|
|
15094
|
-
|
|
15095
|
-
|
|
15096
|
-
|
|
15097
|
-
if (message.type === "command") {
|
|
15098
|
-
return skillCallsFromCommand(message);
|
|
15099
|
-
}
|
|
15100
|
-
if (message.type !== "tool_call") return [];
|
|
15101
|
-
const mcpName = mcpNameFromToolCall(message);
|
|
15102
|
-
return mcpName ? [{ kind: "mcp", id: message.id, mcpName, occurredAt: message.timestamp }] : [];
|
|
15103
|
-
});
|
|
15104
|
-
}
|
|
15105
|
-
|
|
15106
|
-
// src/analytics/analytics.service.ts
|
|
15107
|
-
var MAX_TRACKED_MESSAGES = 500;
|
|
15108
|
-
async function uploadAnalyticsRecords(endpoint, body) {
|
|
15109
|
-
const response = await monolithRequest(endpoint, { body });
|
|
15110
|
-
if (!response.ok) throw new Error(`upload failed: ${response.status} ${await response.text()}`);
|
|
15111
|
-
}
|
|
15112
|
-
var AnalyticsService = class {
|
|
15379
|
+
track(chatId, record) {
|
|
15380
|
+
const records = this.pendingRecords.get(chatId) ?? [];
|
|
15381
|
+
records.push(record);
|
|
15382
|
+
this.pendingRecords.set(chatId, records);
|
|
15383
|
+
}
|
|
15384
|
+
noteTurnEnded(chatId) {
|
|
15385
|
+
const records = this.pendingRecords.get(chatId);
|
|
15386
|
+
if (!records) return;
|
|
15387
|
+
this.pendingRecords.delete(chatId);
|
|
15388
|
+
for (const record of records) this.buffer.append(record);
|
|
15389
|
+
this.buffer.scheduleFlush();
|
|
15390
|
+
}
|
|
15391
|
+
flush() {
|
|
15392
|
+
return this.buffer.flush();
|
|
15393
|
+
}
|
|
15394
|
+
};
|
|
15395
|
+
var AgentChatTurnActivityTracker = class {
|
|
15113
15396
|
pendingMessages = /* @__PURE__ */ new Map();
|
|
15114
15397
|
activeTurns = /* @__PURE__ */ new Map();
|
|
15115
|
-
turnBuffer
|
|
15116
|
-
|
|
15117
|
-
|
|
15118
|
-
|
|
15119
|
-
|
|
15120
|
-
|
|
15121
|
-
|
|
15122
|
-
|
|
15123
|
-
|
|
15124
|
-
});
|
|
15125
|
-
mcpBuffer = new AnalyticsBufferService({
|
|
15126
|
-
name: "mcp-usage",
|
|
15127
|
-
validate: isMcpUsageRecord,
|
|
15128
|
-
upload: (mcps) => uploadAnalyticsRecords("/v1/engine/mcp-usage", { mcps })
|
|
15129
|
-
});
|
|
15398
|
+
turnBuffer;
|
|
15399
|
+
constructor(uploadTurns) {
|
|
15400
|
+
this.turnBuffer = new AgentChatActivityBuffer({
|
|
15401
|
+
storageName: "chat-turn-activity",
|
|
15402
|
+
legacyStorageNames: ["turn-usage"],
|
|
15403
|
+
validate: isAgentChatTurnActivityRecord,
|
|
15404
|
+
upload: uploadTurns
|
|
15405
|
+
});
|
|
15406
|
+
}
|
|
15130
15407
|
noteMessageAccepted(messageId, request) {
|
|
15131
15408
|
this.pendingMessages.set(messageId, {
|
|
15132
15409
|
model: request.model,
|
|
@@ -15149,30 +15426,14 @@ var AnalyticsService = class {
|
|
|
15149
15426
|
senderUserId: attributes.senderUserId,
|
|
15150
15427
|
credentialMethod: credential?.method,
|
|
15151
15428
|
credentialScope: credential?.scope,
|
|
15152
|
-
skills: [],
|
|
15153
|
-
mcps: [],
|
|
15154
15429
|
seenCallIds: /* @__PURE__ */ new Set()
|
|
15155
15430
|
});
|
|
15156
15431
|
}
|
|
15157
|
-
|
|
15158
|
-
|
|
15159
|
-
|
|
15160
|
-
|
|
15161
|
-
|
|
15162
|
-
turn.seenCallIds.add(call.id);
|
|
15163
|
-
const common = {
|
|
15164
|
-
callId: call.id,
|
|
15165
|
-
chatId,
|
|
15166
|
-
provider: turn.provider,
|
|
15167
|
-
model: turn.model,
|
|
15168
|
-
senderUserId: turn.senderUserId,
|
|
15169
|
-
occurredAt: call.occurredAt,
|
|
15170
|
-
credentialMethod: turn.credentialMethod,
|
|
15171
|
-
credentialScope: turn.credentialScope
|
|
15172
|
-
};
|
|
15173
|
-
if (call.kind === "skill") turn.skills.push({ ...common, skillName: call.skillName });
|
|
15174
|
-
else turn.mcps.push({ ...common, mcpName: call.mcpName });
|
|
15175
|
-
}
|
|
15432
|
+
getActiveTurn(chatId) {
|
|
15433
|
+
return this.activeTurns.get(chatId);
|
|
15434
|
+
}
|
|
15435
|
+
getActiveChatIds() {
|
|
15436
|
+
return [...this.activeTurns.keys()];
|
|
15176
15437
|
}
|
|
15177
15438
|
noteTurnEnded(chatId) {
|
|
15178
15439
|
const turn = this.activeTurns.get(chatId);
|
|
@@ -15189,22 +15450,91 @@ var AnalyticsService = class {
|
|
|
15189
15450
|
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15190
15451
|
seconds: Math.round((Date.now() - turn.startedAtMs) / 1e3)
|
|
15191
15452
|
});
|
|
15192
|
-
for (const skill of turn.skills) this.skillBuffer.append(skill);
|
|
15193
|
-
for (const mcp of turn.mcps) this.mcpBuffer.append(mcp);
|
|
15194
15453
|
this.turnBuffer.scheduleFlush();
|
|
15195
|
-
|
|
15196
|
-
|
|
15454
|
+
}
|
|
15455
|
+
flush() {
|
|
15456
|
+
return this.turnBuffer.flush();
|
|
15457
|
+
}
|
|
15458
|
+
};
|
|
15459
|
+
async function uploadAgentChatActivity(endpoint, body) {
|
|
15460
|
+
const response = await monolithRequest(endpoint, { body });
|
|
15461
|
+
if (!response.ok)
|
|
15462
|
+
throw new Error(
|
|
15463
|
+
`upload failed: ${response.status} ${await response.text()}`
|
|
15464
|
+
);
|
|
15465
|
+
}
|
|
15466
|
+
var AgentChatActivityTrackerService = class {
|
|
15467
|
+
turnActivityTracker;
|
|
15468
|
+
skillActivityTracker;
|
|
15469
|
+
mcpActivityTracker;
|
|
15470
|
+
constructor() {
|
|
15471
|
+
this.turnActivityTracker = new AgentChatTurnActivityTracker(
|
|
15472
|
+
(turns) => uploadAgentChatActivity("/v1/engine/chat-turn-activity", { turns })
|
|
15473
|
+
);
|
|
15474
|
+
this.skillActivityTracker = new AgentChatActivityRecordTracker({
|
|
15475
|
+
storageName: "skill-activity",
|
|
15476
|
+
legacyStorageNames: ["skill-usage"],
|
|
15477
|
+
validate: isAgentChatSkillActivityRecord,
|
|
15478
|
+
upload: (skills) => uploadAgentChatActivity("/v1/engine/skill-activity", { skills })
|
|
15479
|
+
});
|
|
15480
|
+
this.mcpActivityTracker = new AgentChatActivityRecordTracker({
|
|
15481
|
+
storageName: "mcp-activity",
|
|
15482
|
+
legacyStorageNames: ["mcp-usage"],
|
|
15483
|
+
validate: isAgentChatMcpActivityRecord,
|
|
15484
|
+
upload: (mcps) => uploadAgentChatActivity("/v1/engine/mcp-activity", { mcps })
|
|
15485
|
+
});
|
|
15486
|
+
}
|
|
15487
|
+
noteMessageAccepted(messageId, request) {
|
|
15488
|
+
this.turnActivityTracker.noteMessageAccepted(messageId, request);
|
|
15489
|
+
}
|
|
15490
|
+
noteTurnStarted(chatId, messageId, provider) {
|
|
15491
|
+
this.turnActivityTracker.noteTurnStarted(chatId, messageId, provider);
|
|
15492
|
+
}
|
|
15493
|
+
noteSkillMcpCalls(chatId, messages) {
|
|
15494
|
+
const turn = this.turnActivityTracker.getActiveTurn(chatId);
|
|
15495
|
+
if (!turn) return;
|
|
15496
|
+
for (const call of extractSkillMcpCalls(messages, turn.provider)) {
|
|
15497
|
+
if (turn.seenCallIds.has(call.id)) continue;
|
|
15498
|
+
turn.seenCallIds.add(call.id);
|
|
15499
|
+
const activityRecord = {
|
|
15500
|
+
callId: call.id,
|
|
15501
|
+
chatId,
|
|
15502
|
+
provider: turn.provider,
|
|
15503
|
+
model: turn.model,
|
|
15504
|
+
senderUserId: turn.senderUserId,
|
|
15505
|
+
occurredAt: call.occurredAt,
|
|
15506
|
+
credentialMethod: turn.credentialMethod,
|
|
15507
|
+
credentialScope: turn.credentialScope
|
|
15508
|
+
};
|
|
15509
|
+
if (call.kind === "skill") {
|
|
15510
|
+
this.skillActivityTracker.track(chatId, {
|
|
15511
|
+
...activityRecord,
|
|
15512
|
+
skillName: call.skillName
|
|
15513
|
+
});
|
|
15514
|
+
} else {
|
|
15515
|
+
this.mcpActivityTracker.track(chatId, {
|
|
15516
|
+
...activityRecord,
|
|
15517
|
+
mcpName: call.mcpName
|
|
15518
|
+
});
|
|
15519
|
+
}
|
|
15520
|
+
}
|
|
15521
|
+
}
|
|
15522
|
+
noteTurnEnded(chatId) {
|
|
15523
|
+
this.skillActivityTracker.noteTurnEnded(chatId);
|
|
15524
|
+
this.mcpActivityTracker.noteTurnEnded(chatId);
|
|
15525
|
+
this.turnActivityTracker.noteTurnEnded(chatId);
|
|
15197
15526
|
}
|
|
15198
15527
|
async flush(finalizeInFlight = true) {
|
|
15199
15528
|
if (finalizeInFlight) {
|
|
15200
|
-
for (const chatId of this.
|
|
15529
|
+
for (const chatId of this.turnActivityTracker.getActiveChatIds())
|
|
15530
|
+
this.noteTurnEnded(chatId);
|
|
15201
15531
|
}
|
|
15202
|
-
const [
|
|
15203
|
-
this.
|
|
15204
|
-
this.
|
|
15205
|
-
this.
|
|
15532
|
+
const [turnActivity, skillActivity, mcpActivity] = await Promise.all([
|
|
15533
|
+
this.turnActivityTracker.flush(),
|
|
15534
|
+
this.skillActivityTracker.flush(),
|
|
15535
|
+
this.mcpActivityTracker.flush()
|
|
15206
15536
|
]);
|
|
15207
|
-
return {
|
|
15537
|
+
return { turnActivity, skillActivity, mcpActivity };
|
|
15208
15538
|
}
|
|
15209
15539
|
};
|
|
15210
15540
|
|
|
@@ -15490,7 +15820,7 @@ import { basename as basename2, join as join25 } from "path";
|
|
|
15490
15820
|
|
|
15491
15821
|
// src/services/chat/chat-senders.ts
|
|
15492
15822
|
import { join as join24 } from "path";
|
|
15493
|
-
var CHAT_SENDERS_DIR = join24(
|
|
15823
|
+
var CHAT_SENDERS_DIR = join24(ENGINE_DIR3, "chat-senders");
|
|
15494
15824
|
function chatMessageSendersFilePath(chatId) {
|
|
15495
15825
|
return join24(CHAT_SENDERS_DIR, `${chatId}.jsonl`);
|
|
15496
15826
|
}
|
|
@@ -15508,9 +15838,9 @@ function parseChatMessageSendersJsonl(content) {
|
|
|
15508
15838
|
|
|
15509
15839
|
// src/services/upload-chat-transcripts.ts
|
|
15510
15840
|
var HISTORY_DIRS = [
|
|
15511
|
-
join25(
|
|
15512
|
-
join25(
|
|
15513
|
-
join25(
|
|
15841
|
+
join25(ENGINE_DIR3, "claude-histories"),
|
|
15842
|
+
join25(ENGINE_DIR3, "relay-histories"),
|
|
15843
|
+
join25(ENGINE_DIR3, "codex-histories")
|
|
15514
15844
|
];
|
|
15515
15845
|
async function putTranscript(uploadUrl, filePath, size) {
|
|
15516
15846
|
await new Promise((resolve5, reject) => {
|
|
@@ -15881,20 +16211,20 @@ function corruptChatsFilePath() {
|
|
|
15881
16211
|
return `${CHATS_FILE}.corrupt-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
15882
16212
|
}
|
|
15883
16213
|
var ChatService = class {
|
|
15884
|
-
constructor(workingDirectory,
|
|
16214
|
+
constructor(workingDirectory, agentChatActivityTrackerService2) {
|
|
15885
16215
|
this.workingDirectory = workingDirectory;
|
|
15886
|
-
this.
|
|
16216
|
+
this.agentChatActivityTrackerService = agentChatActivityTrackerService2;
|
|
15887
16217
|
keepAliveService.setActivityCheck(
|
|
15888
16218
|
() => [...this.chats.values()].some((chat) => !chat.persisted.deletedAt && hasActiveAgentWork(chat))
|
|
15889
16219
|
);
|
|
15890
16220
|
}
|
|
15891
16221
|
workingDirectory;
|
|
15892
|
-
|
|
16222
|
+
agentChatActivityTrackerService;
|
|
15893
16223
|
chats = /* @__PURE__ */ new Map();
|
|
15894
16224
|
persistInFlight = false;
|
|
15895
16225
|
persistQueued = false;
|
|
15896
16226
|
async initialize() {
|
|
15897
|
-
await mkdir15(
|
|
16227
|
+
await mkdir15(ENGINE_DIR3, { recursive: true });
|
|
15898
16228
|
await mkdir15(CLAUDE_HISTORY_DIR, { recursive: true });
|
|
15899
16229
|
await mkdir15(RELAY_HISTORY_DIR, { recursive: true });
|
|
15900
16230
|
await mkdir15(CODEX_HISTORY_DIR, { recursive: true });
|
|
@@ -16011,7 +16341,7 @@ var ChatService = class {
|
|
|
16011
16341
|
request.images
|
|
16012
16342
|
);
|
|
16013
16343
|
chat.pendingMessageIds.push(result.messageId);
|
|
16014
|
-
this.
|
|
16344
|
+
this.agentChatActivityTrackerService.noteMessageAccepted(result.messageId, request);
|
|
16015
16345
|
if (request.errorNotificationTarget) {
|
|
16016
16346
|
chat.errorNotificationTargets.set(result.messageId, request.errorNotificationTarget);
|
|
16017
16347
|
}
|
|
@@ -16067,7 +16397,7 @@ var ChatService = class {
|
|
|
16067
16397
|
async interrupt(chatId) {
|
|
16068
16398
|
const chat = this.requireChat(chatId);
|
|
16069
16399
|
const result = await chat.provider.interrupt();
|
|
16070
|
-
this.
|
|
16400
|
+
this.agentChatActivityTrackerService.noteTurnEnded(chatId);
|
|
16071
16401
|
chat.hasActiveTurn = false;
|
|
16072
16402
|
chat.activeMessageId = null;
|
|
16073
16403
|
chat.pendingMessageIds = [];
|
|
@@ -16089,7 +16419,7 @@ var ChatService = class {
|
|
|
16089
16419
|
return { interrupted: false, queue: [], goal: null };
|
|
16090
16420
|
}
|
|
16091
16421
|
const interruptResult = await chat.provider.interrupt();
|
|
16092
|
-
this.
|
|
16422
|
+
this.agentChatActivityTrackerService.noteTurnEnded(chatId);
|
|
16093
16423
|
chat.hasActiveTurn = false;
|
|
16094
16424
|
chat.activeMessageId = null;
|
|
16095
16425
|
chat.pendingMessageIds = [];
|
|
@@ -16176,7 +16506,7 @@ var ChatService = class {
|
|
|
16176
16506
|
messageId,
|
|
16177
16507
|
...chat.pendingMessageIds.filter((pendingMessageId) => pendingMessageId !== messageId && pendingMessageId !== interruptedMessageId)
|
|
16178
16508
|
];
|
|
16179
|
-
this.
|
|
16509
|
+
this.agentChatActivityTrackerService.noteTurnEnded(chatId);
|
|
16180
16510
|
chat.hasActiveTurn = false;
|
|
16181
16511
|
chat.activeMessageId = null;
|
|
16182
16512
|
if (interruptedMessageId) {
|
|
@@ -16310,14 +16640,22 @@ var ChatService = class {
|
|
|
16310
16640
|
const chatsById = new Map(
|
|
16311
16641
|
[...this.chats.entries()].map(([chatId, chat]) => [chatId, this.toSummary(chat)])
|
|
16312
16642
|
);
|
|
16313
|
-
const [chatTranscripts, canvas, repoState, engineLogs,
|
|
16643
|
+
const [chatTranscripts, canvas, repoState, engineLogs, chatActivity] = await Promise.all([
|
|
16314
16644
|
flushAllChatTranscripts(chatsById),
|
|
16315
16645
|
flushAllCanvasItems(),
|
|
16316
16646
|
flushRepoState(),
|
|
16317
16647
|
flushAllEngineLogs(),
|
|
16318
|
-
this.
|
|
16648
|
+
this.agentChatActivityTrackerService.flush()
|
|
16319
16649
|
]);
|
|
16320
|
-
return {
|
|
16650
|
+
return {
|
|
16651
|
+
chatTranscripts,
|
|
16652
|
+
canvas,
|
|
16653
|
+
repoState,
|
|
16654
|
+
engineLogs,
|
|
16655
|
+
turnActivity: chatActivity.turnActivity,
|
|
16656
|
+
skillActivity: chatActivity.skillActivity,
|
|
16657
|
+
mcpActivity: chatActivity.mcpActivity
|
|
16658
|
+
};
|
|
16321
16659
|
}
|
|
16322
16660
|
createRuntimeChat(persisted) {
|
|
16323
16661
|
const saveSession = async (sessionId) => {
|
|
@@ -16474,7 +16812,7 @@ var ChatService = class {
|
|
|
16474
16812
|
}
|
|
16475
16813
|
chat.hasActiveTurn = true;
|
|
16476
16814
|
chat.activeMessageId = messageId;
|
|
16477
|
-
this.
|
|
16815
|
+
this.agentChatActivityTrackerService.noteTurnStarted(chat.persisted.id, messageId, chat.persisted.provider);
|
|
16478
16816
|
chat.activeErrorNotificationTarget = chat.errorNotificationTargets.get(messageId) ?? null;
|
|
16479
16817
|
chat.errorNotificationTargets.delete(messageId);
|
|
16480
16818
|
this.publish({
|
|
@@ -16492,7 +16830,7 @@ var ChatService = class {
|
|
|
16492
16830
|
filter: false,
|
|
16493
16831
|
parentToolUseId: typeof event.payload.parent_tool_use_id === "string" ? event.payload.parent_tool_use_id : null
|
|
16494
16832
|
});
|
|
16495
|
-
this.
|
|
16833
|
+
this.agentChatActivityTrackerService.noteSkillMcpCalls(chatId, displayMessages);
|
|
16496
16834
|
}
|
|
16497
16835
|
const terminalErrors = terminalErrorsFromEvent(event, chat.persisted.provider, codexTranscript);
|
|
16498
16836
|
if (terminalErrors !== void 0) {
|
|
@@ -16537,7 +16875,7 @@ var ChatService = class {
|
|
|
16537
16875
|
}
|
|
16538
16876
|
}).catch(() => {
|
|
16539
16877
|
});
|
|
16540
|
-
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)) {
|
|
16541
16879
|
this.publish({
|
|
16542
16880
|
type: "chat.updated",
|
|
16543
16881
|
payload: { chat: this.toSummary(chat) }
|
|
@@ -16556,7 +16894,7 @@ var ChatService = class {
|
|
|
16556
16894
|
}
|
|
16557
16895
|
chat.hasActiveTurn = false;
|
|
16558
16896
|
chat.activeMessageId = null;
|
|
16559
|
-
this.
|
|
16897
|
+
this.agentChatActivityTrackerService.noteTurnEnded(chatId);
|
|
16560
16898
|
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
16561
16899
|
this.publish({
|
|
16562
16900
|
type: "chat.turn.completed",
|
|
@@ -18592,8 +18930,8 @@ var authMiddleware = async (c, next) => {
|
|
|
18592
18930
|
}
|
|
18593
18931
|
await next();
|
|
18594
18932
|
};
|
|
18595
|
-
var
|
|
18596
|
-
var chatService = new ChatService(gitService.getWorkspaceRoot(),
|
|
18933
|
+
var agentChatActivityTrackerService = new AgentChatActivityTrackerService();
|
|
18934
|
+
var chatService = new ChatService(gitService.getWorkspaceRoot(), agentChatActivityTrackerService);
|
|
18597
18935
|
app.get("/health", async (c) => {
|
|
18598
18936
|
const requestedWaitMs = Number(c.req.query(ENGINE_HEALTH_WAIT_QUERY_PARAM));
|
|
18599
18937
|
const waitMs = Number.isFinite(requestedWaitMs) ? Math.min(Math.max(requestedWaitMs, 0), ENGINE_HEALTH_MAX_WAIT_MS) : 0;
|