replicas-engine 0.1.735 → 0.1.736

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.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  mayCreatePullRequest
4
- } from "./chunk-EZ6Z6GSU.js";
4
+ } from "./chunk-NQFMLRS4.js";
5
5
 
6
6
  // src/services/post-tool-pr-notifier.ts
7
7
  async function notifyPostToolUse(toolName, toolInput) {
@@ -5,15 +5,17 @@ import {
5
5
  import {
6
6
  REPLICAS_RUNTIME_ENV_ALIASES,
7
7
  agentCredentialSnapshotSchema,
8
- chatForkFallbackResponseSchema,
8
+ createErrorResult,
9
+ createSuccessResult,
9
10
  extractCommandProtectionCommandText,
11
+ fallbackPlanResponseSchema,
10
12
  findGitCommitSignals,
11
13
  findPrMergeSignals,
12
14
  isRecord,
13
15
  isValidAgentProvider,
14
16
  parsePosixEnvFile,
15
17
  readReplicasRuntimeEnv
16
- } from "./chunk-EZ6Z6GSU.js";
18
+ } from "./chunk-NQFMLRS4.js";
17
19
 
18
20
  // src/engine-env.ts
19
21
  import { readFileSync as readFileSync2 } from "fs";
@@ -198,15 +200,19 @@ var MonolithService = class {
198
200
  return null;
199
201
  }
200
202
  }
201
- async getChatForkFallback(params) {
203
+ /** `unsupported` means an older monolith; `unavailable` means this one could not be read. */
204
+ async getFallbackPlan(params) {
202
205
  const query = new URLSearchParams({ provider: params.provider, exclude: params.exclude.join(",") });
206
+ if (params.exhaustedMethod) query.set("exhaustedMethod", params.exhaustedMethod);
203
207
  if (params.userId) query.set("userId", params.userId);
204
208
  try {
205
- const response = await monolithRequest(`/v1/engine/chat-fork-fallback?${query}`, { method: "GET" });
206
- if (!response.ok) return null;
207
- return chatForkFallbackResponseSchema.safeParse(await response.json()).data ?? null;
208
- } catch {
209
- return null;
209
+ const response = await monolithRequest(`/v1/engine/fallback-plan?${query}`, { method: "GET" });
210
+ if (response.status === 404) return createErrorResult({ message: "Monolith has no fallback-plan route", code: "unsupported" });
211
+ if (!response.ok) return createErrorResult({ message: `Fallback plan request failed: ${response.status}`, code: "unavailable" });
212
+ const parsed = fallbackPlanResponseSchema.safeParse(await response.json());
213
+ return parsed.success ? createSuccessResult(parsed.data) : createErrorResult({ message: "Fallback plan response did not match the schema", code: "unavailable" });
214
+ } catch (error) {
215
+ return createErrorResult({ message: error instanceof Error ? error.message : String(error), code: "unavailable" });
210
216
  }
211
217
  }
212
218
  async sendEvent(event) {
@@ -1268,43 +1268,54 @@ function describeAuthFallback(payload) {
1268
1268
  }
1269
1269
 
1270
1270
  // ../shared/src/chat-fork.ts
1271
+ import { z as z5 } from "zod";
1272
+
1273
+ // ../shared/src/fallback-chains.ts
1271
1274
  import { z as z4 } from "zod";
1272
- var quotaLimitKindSchema = z4.enum(["rate_limit", "out_of_credits"]);
1273
- var chatForkInfoSchema = z4.object({
1274
- source: z4.object({
1275
- chatId: z4.string().min(1),
1276
- provider: z4.enum(VALID_AGENT_PROVIDERS),
1277
- title: z4.string()
1275
+ var fallbackHarnessStepSchema = z4.object({
1276
+ kind: z4.literal("harness"),
1277
+ provider: z4.enum(VALID_AGENT_PROVIDERS),
1278
+ model: z4.string().min(1),
1279
+ thinkingLevel: z4.enum(VALID_THINKING_LEVELS).optional()
1280
+ });
1281
+ var fallbackPlanStepSchema = z4.discriminatedUnion("kind", [
1282
+ z4.object({ kind: z4.literal("credentials"), methods: z4.array(z4.custom(isAuthMethod)).min(1) }),
1283
+ fallbackHarnessStepSchema
1284
+ ]);
1285
+ var fallbackPlanResponseSchema = z4.object({ steps: z4.array(fallbackPlanStepSchema) });
1286
+
1287
+ // ../shared/src/chat-fork.ts
1288
+ var quotaLimitKindSchema = z5.enum(["rate_limit", "out_of_credits"]);
1289
+ var chatForkInfoSchema = z5.object({
1290
+ source: z5.object({
1291
+ chatId: z5.string().min(1),
1292
+ provider: z5.enum(VALID_AGENT_PROVIDERS),
1293
+ title: z5.string()
1278
1294
  }),
1279
- trigger: z4.enum(["manual", "auto"]),
1295
+ trigger: z5.enum(["manual", "auto"]),
1280
1296
  reason: quotaLimitKindSchema.optional(),
1281
- state: z4.enum(["preparing", "ready", "failed"]),
1282
- error: z4.string().optional(),
1283
- startedAt: z4.string(),
1284
- completedAt: z4.string().optional()
1297
+ state: z5.enum(["preparing", "ready", "failed"]),
1298
+ error: z5.string().optional(),
1299
+ startedAt: z5.string(),
1300
+ completedAt: z5.string().optional()
1285
1301
  });
1286
1302
  function coerceChatForkInfo(value) {
1287
1303
  const parsed = chatForkInfoSchema.safeParse(value);
1288
1304
  return parsed.success ? parsed.data : null;
1289
1305
  }
1290
- var forkChatRequestSchema = z4.object({
1291
- provider: z4.enum(VALID_AGENT_PROVIDERS),
1292
- model: z4.string().min(1).optional(),
1293
- thinkingLevel: z4.enum(VALID_THINKING_LEVELS).optional(),
1294
- title: z4.string().min(1).max(200).optional(),
1295
- id: z4.string().uuid().optional(),
1296
- clientRequestId: z4.string().min(1).max(128).optional(),
1297
- enableInteractiveTools: z4.boolean().optional(),
1306
+ var forkChatRequestSchema = z5.object({
1307
+ provider: z5.enum(VALID_AGENT_PROVIDERS),
1308
+ model: z5.string().min(1).optional(),
1309
+ thinkingLevel: z5.enum(VALID_THINKING_LEVELS).optional(),
1310
+ title: z5.string().min(1).max(200).optional(),
1311
+ id: z5.string().uuid().optional(),
1312
+ clientRequestId: z5.string().min(1).max(128).optional(),
1313
+ enableInteractiveTools: z5.boolean().optional(),
1298
1314
  // Server-injected by the monolith from the authenticated request; never trusted from clients.
1299
- senderUserId: z4.string().optional(),
1300
- senderEmail: z4.string().optional(),
1301
- senderDisplayName: z4.string().optional(),
1302
- senderAvatarUrl: z4.string().optional()
1303
- });
1304
- var chatForkFallbackResponseSchema = z4.object({
1305
- provider: z4.enum(VALID_AGENT_PROVIDERS).nullable(),
1306
- model: z4.string().min(1).optional(),
1307
- thinkingLevel: z4.enum(VALID_THINKING_LEVELS).optional()
1315
+ senderUserId: z5.string().optional(),
1316
+ senderEmail: z5.string().optional(),
1317
+ senderDisplayName: z5.string().optional(),
1318
+ senderAvatarUrl: z5.string().optional()
1308
1319
  });
1309
1320
  function describeQuotaLimit(reason) {
1310
1321
  return reason === "out_of_credits" ? "ran out of credits" : "hit its usage limit";
@@ -1313,10 +1324,11 @@ function describeChatForkReason(reason, provider) {
1313
1324
  return reason ? `${getProviderDisplayName(provider)} ${describeQuotaLimit(reason)}` : null;
1314
1325
  }
1315
1326
  var AGENT_QUOTA_EXHAUSTED_EVENT_TYPE = "agent-quota-exhausted";
1316
- var agentQuotaExhaustedPayloadSchema = z4.object({
1317
- provider: z4.enum(VALID_CODING_AGENT_PROVIDERS),
1327
+ var agentQuotaExhaustedPayloadSchema = z5.object({
1328
+ provider: z5.enum(VALID_CODING_AGENT_PROVIDERS),
1318
1329
  reason: quotaLimitKindSchema,
1319
- detail: z4.string().optional()
1330
+ detail: z5.string().optional(),
1331
+ forkTo: fallbackHarnessStepSchema.optional()
1320
1332
  });
1321
1333
  function defaultForkChatTitle(source, provider) {
1322
1334
  return isDefaultChat(source) ? `Forked from ${source.title}` : `${source.title} (${getProviderDisplayName(provider)})`;
@@ -3272,9 +3284,7 @@ var DEFAULT_USER_PREFERENCES = {
3272
3284
  open_links_in_desktop_app: false,
3273
3285
  default_fast_mode: false,
3274
3286
  agent_defaults: { ...EMPTY_AGENT_DEFAULT_SETTINGS },
3275
- credential_priority: null,
3276
- chat_fork_fallbacks_enabled: false,
3277
- chat_fork_fallback_providers: []
3287
+ fallback_chains: null
3278
3288
  };
3279
3289
  function defaultReplicasAgentAbilities() {
3280
3290
  const abilities = {};
@@ -5928,20 +5938,20 @@ var TEAM_PLAN = PLANS.team;
5928
5938
  var ENTERPRISE_PLAN = PLANS.enterprise;
5929
5939
 
5930
5940
  // ../shared/src/models-dev.ts
5931
- import { z as z5 } from "zod";
5941
+ import { z as z6 } from "zod";
5932
5942
  var MODELS_DEV_API_URL = "https://models.dev/api.json";
5933
5943
  var MODELS_DEV_CACHE_MS = 5 * 6e4;
5934
- var modelsDevCatalogSchema = z5.record(z5.string(), z5.object({
5935
- models: z5.record(z5.string(), z5.object({
5936
- id: z5.string().optional(),
5937
- name: z5.string(),
5938
- description: z5.string().optional(),
5939
- status: z5.enum(["alpha", "beta", "deprecated"]).optional(),
5940
- cost: z5.object({
5941
- input: z5.number().finite().nonnegative().optional(),
5942
- output: z5.number().finite().nonnegative().optional(),
5943
- cache_read: z5.number().finite().nonnegative().optional(),
5944
- cache_write: z5.number().finite().nonnegative().optional()
5944
+ var modelsDevCatalogSchema = z6.record(z6.string(), z6.object({
5945
+ models: z6.record(z6.string(), z6.object({
5946
+ id: z6.string().optional(),
5947
+ name: z6.string(),
5948
+ description: z6.string().optional(),
5949
+ status: z6.enum(["alpha", "beta", "deprecated"]).optional(),
5950
+ cost: z6.object({
5951
+ input: z6.number().finite().nonnegative().optional(),
5952
+ output: z6.number().finite().nonnegative().optional(),
5953
+ cache_read: z6.number().finite().nonnegative().optional(),
5954
+ cache_write: z6.number().finite().nonnegative().optional()
5945
5955
  }).optional()
5946
5956
  }))
5947
5957
  }));
@@ -6873,46 +6883,46 @@ var DEFAULT_WORKSPACE_FILTERS = {
6873
6883
  var ONE_DAY_MS = 24 * 60 * 60 * 1e3;
6874
6884
 
6875
6885
  // ../shared/src/routes/workspace-events.ts
6876
- import { z as z6 } from "zod";
6877
- var workspaceChangedEventSchema = z6.discriminatedUnion("type", [
6878
- z6.object({
6879
- type: z6.literal("workspace.changed"),
6880
- workspaceId: z6.string(),
6881
- completion: z6.object({
6882
- workspaceName: z6.string(),
6883
- completedAt: z6.string(),
6884
- processing: z6.boolean().optional()
6886
+ import { z as z7 } from "zod";
6887
+ var workspaceChangedEventSchema = z7.discriminatedUnion("type", [
6888
+ z7.object({
6889
+ type: z7.literal("workspace.changed"),
6890
+ workspaceId: z7.string(),
6891
+ completion: z7.object({
6892
+ workspaceName: z7.string(),
6893
+ completedAt: z7.string(),
6894
+ processing: z7.boolean().optional()
6885
6895
  }).optional(),
6886
- ts: z6.string()
6896
+ ts: z7.string()
6887
6897
  }),
6888
- z6.object({
6889
- type: z6.literal("workspace.chat.changed"),
6890
- workspaceId: z6.string(),
6891
- chatId: z6.string(),
6892
- ts: z6.string()
6898
+ z7.object({
6899
+ type: z7.literal("workspace.chat.changed"),
6900
+ workspaceId: z7.string(),
6901
+ chatId: z7.string(),
6902
+ ts: z7.string()
6893
6903
  }),
6894
- z6.object({ type: z6.literal("presence.changed"), ts: z6.string() }),
6895
- z6.object({
6896
- type: z6.literal("mobile-testing.changed"),
6897
- workspaceId: z6.string(),
6898
- ts: z6.string()
6904
+ z7.object({ type: z7.literal("presence.changed"), ts: z7.string() }),
6905
+ z7.object({
6906
+ type: z7.literal("mobile-testing.changed"),
6907
+ workspaceId: z7.string(),
6908
+ ts: z7.string()
6899
6909
  })
6900
6910
  ]);
6901
6911
 
6902
6912
  // ../shared/src/routes/presence.ts
6903
- import { z as z7 } from "zod";
6904
- var presenceStatusSchema = z7.enum(["online", "typing"]);
6905
- var presenceLocationSchema = z7.object({
6906
- environmentId: z7.string().optional(),
6907
- workspaceId: z7.string().optional()
6913
+ import { z as z8 } from "zod";
6914
+ var presenceStatusSchema = z8.enum(["online", "typing"]);
6915
+ var presenceLocationSchema = z8.object({
6916
+ environmentId: z8.string().optional(),
6917
+ workspaceId: z8.string().optional()
6908
6918
  });
6909
- var presenceEntrySchema = z7.object({
6910
- userId: z7.string(),
6919
+ var presenceEntrySchema = z8.object({
6920
+ userId: z8.string(),
6911
6921
  status: presenceStatusSchema,
6912
6922
  location: presenceLocationSchema,
6913
- ts: z7.string()
6923
+ ts: z8.string()
6914
6924
  });
6915
- var updatePresenceRequestSchema = z7.object({
6925
+ var updatePresenceRequestSchema = z8.object({
6916
6926
  status: presenceStatusSchema,
6917
6927
  location: presenceLocationSchema.optional()
6918
6928
  });
@@ -7177,31 +7187,31 @@ function parseChatTranscriptArtifact(value) {
7177
7187
  }
7178
7188
 
7179
7189
  // ../shared/src/memory.ts
7180
- import { z as z8 } from "zod";
7190
+ import { z as z9 } from "zod";
7181
7191
  var MEMORY_ROOT = `${SANDBOX_PATHS.REPLICAS_DIR}/memories`;
7182
7192
  var MEMORY_INDEX_FILENAME = "MEMORY.md";
7183
7193
  var MEMORY_BUNDLE_FILENAME = "memory.tar.gz";
7184
- var memoryExtractionResultSchema = z8.object({
7185
- seatSummary: z8.string().nullable(),
7186
- organizationSummary: z8.string().nullable()
7194
+ var memoryExtractionResultSchema = z9.object({
7195
+ seatSummary: z9.string().nullable(),
7196
+ organizationSummary: z9.string().nullable()
7187
7197
  });
7188
- var { $schema: _, ...MEMORY_EXTRACTION_SCHEMA } = z8.toJSONSchema(memoryExtractionResultSchema);
7189
- var memoryGenerationManifestSchema = z8.object({
7190
- domainId: z8.string(),
7191
- generation: z8.number().int().nonnegative(),
7192
- previousManifestKey: z8.string().nullable(),
7193
- createdAt: z8.string(),
7194
- promptVersion: z8.string(),
7195
- harness: z8.enum(["claude", "codex", "none"]),
7196
- model: z8.string(),
7197
- bundle: z8.object({
7198
- path: z8.literal(MEMORY_BUNDLE_FILENAME),
7199
- sha256: z8.string().regex(/^[a-f0-9]{64}$/),
7200
- sizeBytes: z8.number().int().positive()
7198
+ var { $schema: _, ...MEMORY_EXTRACTION_SCHEMA } = z9.toJSONSchema(memoryExtractionResultSchema);
7199
+ var memoryGenerationManifestSchema = z9.object({
7200
+ domainId: z9.string(),
7201
+ generation: z9.number().int().nonnegative(),
7202
+ previousManifestKey: z9.string().nullable(),
7203
+ createdAt: z9.string(),
7204
+ promptVersion: z9.string(),
7205
+ harness: z9.enum(["claude", "codex", "none"]),
7206
+ model: z9.string(),
7207
+ bundle: z9.object({
7208
+ path: z9.literal(MEMORY_BUNDLE_FILENAME),
7209
+ sha256: z9.string().regex(/^[a-f0-9]{64}$/),
7210
+ sizeBytes: z9.number().int().positive()
7201
7211
  }).nullable(),
7202
- files: z8.record(z8.string(), z8.string()),
7203
- rollouts: z8.record(z8.string(), z8.string()),
7204
- rolloutSources: z8.record(z8.string(), z8.string()).optional()
7212
+ files: z9.record(z9.string(), z9.string()),
7213
+ rollouts: z9.record(z9.string(), z9.string()),
7214
+ rolloutSources: z9.record(z9.string(), z9.string()).optional()
7205
7215
  });
7206
7216
 
7207
7217
  // ../shared/src/skill-registry.ts
@@ -7229,8 +7239,17 @@ export {
7229
7239
  isValidAgentProvider,
7230
7240
  VALID_THINKING_LEVELS,
7231
7241
  codexReasoningEffortForThinkingLevel,
7242
+ getProviderDisplayName,
7232
7243
  detectAgentQuotaLimit,
7233
7244
  CREDENTIAL_METHOD,
7245
+ agentCredentialSnapshotSchema,
7246
+ isAgentChatTurnActivityRecord,
7247
+ isAgentChatSkillActivityRecord,
7248
+ isAgentChatMcpActivityRecord,
7249
+ TOKEN_USAGE_PATH,
7250
+ isTokenUsageCounts,
7251
+ isTokenUsageUploadRecord,
7252
+ fallbackPlanResponseSchema,
7234
7253
  ASTER_PROVIDER,
7235
7254
  ASTER_BASE_URL,
7236
7255
  ASTER_MODELS,
@@ -7275,17 +7294,10 @@ export {
7275
7294
  paginateEngineLogContent,
7276
7295
  coerceChatForkInfo,
7277
7296
  forkChatRequestSchema,
7278
- chatForkFallbackResponseSchema,
7297
+ describeQuotaLimit,
7279
7298
  AGENT_QUOTA_EXHAUSTED_EVENT_TYPE,
7280
7299
  agentQuotaExhaustedPayloadSchema,
7281
7300
  defaultForkChatTitle,
7282
- agentCredentialSnapshotSchema,
7283
- isAgentChatTurnActivityRecord,
7284
- isAgentChatSkillActivityRecord,
7285
- isAgentChatMcpActivityRecord,
7286
- TOKEN_USAGE_PATH,
7287
- isTokenUsageCounts,
7288
- isTokenUsageUploadRecord,
7289
7301
  CLAUDE_PARTIAL_MESSAGE_EVENT_TYPE,
7290
7302
  getClaudePartialMessageStreamId,
7291
7303
  ACCEPTED_USER_MESSAGE_SOURCE,
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  HOOK_EXEC_MAX_BUFFER_BYTES,
4
4
  isVersionBelow
5
- } from "./chunk-EZ6Z6GSU.js";
5
+ } from "./chunk-NQFMLRS4.js";
6
6
 
7
7
  // src/utils/presigned-upload.ts
8
8
  import { createReadStream } from "fs";
@@ -272,7 +272,7 @@ var DEFAULT_CODEX_ARGS = [
272
272
  var MIN_CODEX_CLI_VERSION = "0.144.6";
273
273
  var CODEX_UPGRADE_TIMEOUT_MS = 12e4;
274
274
  var codexCliVersionEnsured = null;
275
- var ENGINE_PACKAGE_VERSION = "0.1.735";
275
+ var ENGINE_PACKAGE_VERSION = "0.1.736";
276
276
  var INITIALIZE_METHOD = "initialize";
277
277
  var INITIALIZED_NOTIFICATION = "initialized";
278
278
  var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  evaluateCommandProtection
4
- } from "./chunk-CZXDETV5.js";
4
+ } from "./chunk-KIBFWYCG.js";
5
5
  import {
6
6
  isRecord
7
7
  } from "./chunk-2RB7SIP3.js";
8
- import "./chunk-EZ6Z6GSU.js";
8
+ import "./chunk-NQFMLRS4.js";
9
9
 
10
10
  // src/command-protection-hook.ts
11
11
  var provider = process.argv[2];
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  evaluateCommandProtection
4
- } from "./chunk-CZXDETV5.js";
4
+ } from "./chunk-KIBFWYCG.js";
5
5
  import {
6
6
  notifyPostToolUse
7
- } from "./chunk-2VCV6W5F.js";
7
+ } from "./chunk-AQXSBHCI.js";
8
8
  import "./chunk-2RB7SIP3.js";
9
- import "./chunk-EZ6Z6GSU.js";
9
+ import "./chunk-NQFMLRS4.js";
10
10
 
11
11
  // src/deepseek-command-protection-plugin.ts
12
12
  function replicasCommandProtection(context) {
@@ -4,12 +4,12 @@ import {
4
4
  buildCodexAgentEnv,
5
5
  putPresignedFile,
6
6
  recoverCompletedTurn
7
- } from "./chunk-3FHZVVPZ.js";
7
+ } from "./chunk-ZQSFPKUG.js";
8
8
  import {
9
9
  AGENT,
10
10
  getMemoryOutputSafetyViolation,
11
11
  headlessAgentRequestSchema
12
- } from "./chunk-EZ6Z6GSU.js";
12
+ } from "./chunk-NQFMLRS4.js";
13
13
 
14
14
  // src/headless-agent.ts
15
15
  import { createHash } from "crypto";
package/dist/src/index.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  monolithService,
9
9
  reportCommandProtectionBlock,
10
10
  setAgentCredentialSnapshot
11
- } from "./chunk-CZXDETV5.js";
11
+ } from "./chunk-KIBFWYCG.js";
12
12
  import {
13
13
  ACCOUNT_RATE_LIMITS_UPDATED_METHOD,
14
14
  AGENT_MESSAGE_DELTA_METHOD,
@@ -36,7 +36,7 @@ import {
36
36
  execFileAsync,
37
37
  putPresignedFile,
38
38
  recoverCompletedTurn
39
- } from "./chunk-3FHZVVPZ.js";
39
+ } from "./chunk-ZQSFPKUG.js";
40
40
  import {
41
41
  isRecord as isRecord2
42
42
  } from "./chunk-2RB7SIP3.js";
@@ -148,6 +148,7 @@ import {
148
148
  createUuidFromHex,
149
149
  decodePathSegments,
150
150
  defaultForkChatTitle,
151
+ describeQuotaLimit,
151
152
  detectAgentQuotaLimit,
152
153
  detectLanguageByPath,
153
154
  extractErrorText,
@@ -168,6 +169,7 @@ import {
168
169
  getFxTextChunk,
169
170
  getGoalCommand,
170
171
  getGoalCommandObjectiveValidationError,
172
+ getProviderDisplayName,
171
173
  getSlashCommandsForProvider,
172
174
  getUserMessage,
173
175
  getUserMessageId,
@@ -228,7 +230,7 @@ import {
228
230
  shellQuotePosix,
229
231
  stripAgentDiagnosticErrors,
230
232
  withTimeout
231
- } from "./chunk-EZ6Z6GSU.js";
233
+ } from "./chunk-NQFMLRS4.js";
232
234
 
233
235
  // src/index.ts
234
236
  import { serve } from "@hono/node-server";
@@ -323,7 +325,10 @@ var BaseRefreshManager = class {
323
325
  try {
324
326
  console.log(`[${this.managerName}] Fetching fresh credentials from monolith (${params.failureKind})...`);
325
327
  const excludeCredentials = listExhaustedCredentials(params.provider);
326
- await params.refresh(excludeCredentials.length > 0 ? { excludeCredentials } : {});
328
+ await params.refresh({
329
+ ...excludeCredentials.length > 0 ? { excludeCredentials } : {},
330
+ ...params.allowedMethods ? { allowedMethods: [...params.allowedMethods] } : {}
331
+ });
327
332
  if (params.isOauthNow()) {
328
333
  this.start().catch((error) => {
329
334
  console.error(`[${this.managerName}] Failed to restart OAuth refresh service after fallback:`, error);
@@ -1310,11 +1315,12 @@ var ClaudeTokenManager = class extends BaseRefreshManager {
1310
1315
  }
1311
1316
  console.log(`[ClaudeTokenManager] Credentials refreshed (method=${data.type})`);
1312
1317
  }
1313
- async fetchFreshCredentials(failureReason, failureKind = "rejected", failedCredential = ENGINE_ENV.REPLICAS_AGENT_CREDENTIALS.claude) {
1318
+ async fetchFreshCredentials(failureReason, failureKind = "rejected", failedCredential = ENGINE_ENV.REPLICAS_AGENT_CREDENTIALS.claude, allowedMethods) {
1314
1319
  const failedMethod = failedCredential?.method === "oauth" || failedCredential?.method === "api_key" || failedCredential?.method === "bedrock" || failedCredential?.method === "foundry" ? failedCredential.method : ENGINE_ENV.REPLICAS_CLAUDE_AUTH_METHOD;
1315
1320
  return this.swapCredentials({
1316
1321
  provider: "claude",
1317
1322
  failureKind,
1323
+ allowedMethods,
1318
1324
  refresh: (exclusions) => this.refreshWithRequest(failedMethod && failedMethod !== "none" ? {
1319
1325
  failedMethod,
1320
1326
  ...failedCredential?.method === failedMethod ? { failedCredential } : {},
@@ -1469,11 +1475,12 @@ var CodexTokenManager = class extends BaseRefreshManager {
1469
1475
  });
1470
1476
  }
1471
1477
  }
1472
- async fetchFreshCredentials(failureReason, failureKind = "rejected", failedCredential = ENGINE_ENV.REPLICAS_AGENT_CREDENTIALS.codex) {
1478
+ async fetchFreshCredentials(failureReason, failureKind = "rejected", failedCredential = ENGINE_ENV.REPLICAS_AGENT_CREDENTIALS.codex, allowedMethods) {
1473
1479
  const failedMethod = failedCredential?.method === "oauth" || failedCredential?.method === "api_key" || failedCredential?.method === "foundry" ? failedCredential.method : ENGINE_ENV.REPLICAS_CODEX_AUTH_METHOD;
1474
1480
  return this.swapCredentials({
1475
1481
  provider: "codex",
1476
1482
  failureKind,
1483
+ allowedMethods,
1477
1484
  refresh: async (exclusions) => {
1478
1485
  await this.refreshWithRequest(
1479
1486
  failedMethod === "oauth" || failedMethod === "api_key" || failedMethod === "foundry" ? {
@@ -1903,6 +1910,7 @@ var EnvironmentDetailsService = class {
1903
1910
  details.supportsClaudeMidTurnSteering = true;
1904
1911
  details.supportsGoalUpdates = true;
1905
1912
  details.supportsQueuedMessageEditing = true;
1913
+ details.supportsChatForking = true;
1906
1914
  details.claudeAuthMethod = detectAgentAuthMethod(AGENT.CLAUDE);
1907
1915
  details.codexAuthMethod = detectAgentAuthMethod(AGENT.CODEX);
1908
1916
  details.cursorAuthMethod = detectAgentAuthMethod(AGENT.CURSOR);
@@ -3353,10 +3361,11 @@ var MuseTokenManager = class extends BaseRefreshManager {
3353
3361
  doRefresh() {
3354
3362
  return Promise.resolve();
3355
3363
  }
3356
- async fetchFreshCredentials(failureReason, failureKind = "exhausted", failedCredential = ENGINE_ENV.REPLICAS_AGENT_CREDENTIALS.muse) {
3364
+ async fetchFreshCredentials(failureReason, failureKind = "exhausted", failedCredential = ENGINE_ENV.REPLICAS_AGENT_CREDENTIALS.muse, allowedMethods) {
3357
3365
  return this.swapCredentials({
3358
3366
  provider: "muse",
3359
3367
  failureKind,
3368
+ allowedMethods,
3360
3369
  refresh: async (exclusions) => {
3361
3370
  const request = {
3362
3371
  failedMethod: CREDENTIAL_METHOD.MUSE_OAUTH,
@@ -3392,25 +3401,61 @@ var SWAPPABLE_AGENTS = {
3392
3401
  muse: museTokenManager
3393
3402
  };
3394
3403
  var AuthFallbackCoordinator = class {
3395
- constructor(provider, record) {
3404
+ constructor(provider, record, context) {
3396
3405
  this.provider = provider;
3397
3406
  this.record = record;
3407
+ this.context = context;
3398
3408
  }
3399
3409
  provider;
3400
3410
  record;
3411
+ context;
3401
3412
  async fallBack(reason, detail) {
3402
3413
  const tokenManager = SWAPPABLE_AGENTS[this.provider];
3403
- if (!tokenManager) return "unavailable";
3404
3414
  const exhausted = ENGINE_ENV.REPLICAS_AGENT_CREDENTIALS[this.provider];
3405
- if (!exhausted) return "unavailable";
3406
- recordExhaustedCredential(this.provider, exhausted);
3407
- const swap = await tokenManager.fetchFreshCredentials(
3408
- `${this.provider} ${reason === "out_of_credits" ? "is out of credits" : "hit its usage limit"}: ${detail}`,
3409
- "exhausted"
3410
- );
3411
- if (!swap.ok && swap.error.code === "no_credentials") return "not_configured";
3412
- const applied = ENGINE_ENV.REPLICAS_AGENT_CREDENTIALS[this.provider] ?? exhausted;
3413
- const moved = swap.ok && !(applied.method === exhausted.method && applied.scope === exhausted.scope);
3415
+ if (exhausted) recordExhaustedCredential(this.provider, exhausted);
3416
+ const { userId, excludeProviders, forkable } = this.context();
3417
+ const plan = await monolithService.getFallbackPlan({
3418
+ provider: this.provider,
3419
+ exhaustedMethod: exhausted?.method,
3420
+ exclude: excludeProviders,
3421
+ userId
3422
+ });
3423
+ if (!plan.ok && plan.error.code === "unavailable") {
3424
+ console.error(`[AuthFallback] ${this.provider} fallback stopped; plan unavailable:`, plan.error.message);
3425
+ return { outcome: "unavailable" };
3426
+ }
3427
+ const steps = plan.ok ? plan.data.steps : tokenManager && exhausted ? [{ kind: "credentials", methods: [] }] : [];
3428
+ let failed = false;
3429
+ for (const step of steps) {
3430
+ if (step.kind === "harness") {
3431
+ if (forkable && isAgentAvailable(step.provider)) return { outcome: "fork", target: step };
3432
+ continue;
3433
+ }
3434
+ if (!tokenManager || !exhausted) continue;
3435
+ const swap = await tokenManager.fetchFreshCredentials(
3436
+ `${this.provider} ${describeQuotaLimit(reason)}: ${detail}`,
3437
+ "exhausted",
3438
+ void 0,
3439
+ step.methods.length > 0 ? step.methods : void 0
3440
+ );
3441
+ if (!swap.ok) {
3442
+ failed ||= swap.error.code !== "no_credentials";
3443
+ continue;
3444
+ }
3445
+ const applied = ENGINE_ENV.REPLICAS_AGENT_CREDENTIALS[this.provider] ?? exhausted;
3446
+ if (applied.method === exhausted.method && applied.scope === exhausted.scope) {
3447
+ failed = true;
3448
+ continue;
3449
+ }
3450
+ this.recordNotice(reason, exhausted, applied, "switched", detail);
3451
+ return { outcome: "switched" };
3452
+ }
3453
+ if (!tokenManager || !exhausted) return { outcome: "unavailable" };
3454
+ if (!failed) return { outcome: "not_configured" };
3455
+ this.recordNotice(reason, exhausted, exhausted, "failed", "Replicas could not load an alternate credential.");
3456
+ return { outcome: "failed" };
3457
+ }
3458
+ recordNotice(reason, exhausted, applied, status, detail) {
3414
3459
  const payload = {
3415
3460
  provider: this.provider,
3416
3461
  reason,
@@ -3418,13 +3463,12 @@ var AuthFallbackCoordinator = class {
3418
3463
  exhaustedScope: exhausted.scope,
3419
3464
  candidateMethod: applied.method,
3420
3465
  candidateScope: applied.scope,
3421
- status: moved ? "switched" : "failed",
3422
- detail: moved ? detail : "Replicas could not load an alternate credential."
3466
+ status,
3467
+ detail
3423
3468
  };
3424
3469
  const at = (/* @__PURE__ */ new Date()).toISOString();
3425
3470
  this.record({ timestamp: at, type: AUTH_FALLBACK_EVENT_TYPE, payload: { ...payload } });
3426
3471
  recordCredentialFallback({ ...payload, at });
3427
- return moved ? "switched" : "failed";
3428
3472
  }
3429
3473
  };
3430
3474
 
@@ -3686,10 +3730,10 @@ var CodingAgentManager = class {
3686
3730
  this.authFallback = options.provider ? new AuthFallbackCoordinator(options.provider, (event) => {
3687
3731
  this.onEvent(event);
3688
3732
  this.getHistorySink()?.append(event);
3689
- }) : null;
3733
+ }, options.fallbackContext ?? (() => ({ excludeProviders: [], forkable: false }))) : null;
3690
3734
  }
3691
3735
  async fallBackCredential(reason, detail) {
3692
- if (!this.authFallback) return "unavailable";
3736
+ if (!this.authFallback) return { outcome: "unavailable" };
3693
3737
  return this.authFallback.fallBack(reason, detail);
3694
3738
  }
3695
3739
  onTurnComplete = async () => {
@@ -5001,14 +5045,14 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
5001
5045
  const quotaKind = _ClaudeManager.quotaLimitKind(error);
5002
5046
  if (quotaKind) {
5003
5047
  const detail = error instanceof ClaudeQuotaError ? error.message : extractErrorText(error);
5004
- const outcome = quotaFallbacks < MAX_QUOTA_FALLBACKS ? await this.fallBackCredential(quotaKind, detail) : "unavailable";
5005
- if (outcome === "switched") {
5048
+ const fallback = quotaFallbacks < MAX_QUOTA_FALLBACKS ? await this.fallBackCredential(quotaKind, detail) : { outcome: "unavailable" };
5049
+ if (fallback.outcome === "switched") {
5006
5050
  quotaFallbacks++;
5007
5051
  await this.tearDownSession();
5008
5052
  attempt++;
5009
5053
  continue;
5010
5054
  }
5011
- await this.emitQuotaExhaustedEvent(quotaKind, outcome, detail);
5055
+ await this.emitQuotaExhaustedEvent(quotaKind, fallback, detail);
5012
5056
  return;
5013
5057
  }
5014
5058
  if (_ClaudeManager.isAuthError(error)) {
@@ -5119,11 +5163,16 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
5119
5163
  return "Claude authentication failed after multiple attempts. Check your Claude, Anthropic API key, or Bedrock credentials in Settings \u2192 Agents and try again.";
5120
5164
  }
5121
5165
  }
5122
- async emitQuotaExhaustedEvent(kind, outcome, detail) {
5166
+ async emitQuotaExhaustedEvent(kind, fallback, detail) {
5123
5167
  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.";
5124
- const next = outcome === "failed" ? "The configured fallback credential could not be used. Check it in Settings \u2192 Agents and try again." : outcome === "not_configured" ? "No fallback credential is configured for this workspace. Add one in Settings \u2192 Agents, or try again once the limit resets." : "No other credential is available for this workspace. Check Settings \u2192 Agents, or try again once the limit resets.";
5168
+ const next = fallback.outcome === "fork" ? `Forking this chat to ${getProviderDisplayName(fallback.target.provider, "long")}.` : fallback.outcome === "failed" ? "The next credential in the fallback chain could not be used. Check it in Settings \u2192 Fallbacks and try again." : fallback.outcome === "not_configured" ? "Nothing further is configured in the fallback chain. Add a credential or harness in Settings \u2192 Fallbacks, or try again once the limit resets." : "No other credential is available for this workspace. Check Settings \u2192 Fallbacks, or try again once the limit resets.";
5125
5169
  await this.emitTerminalErrorResult(detail, [`${limit} ${next}`]);
5126
- this.emitQuotaExhausted({ provider: "claude", reason: kind, detail });
5170
+ this.emitQuotaExhausted({
5171
+ provider: "claude",
5172
+ reason: kind,
5173
+ detail,
5174
+ ...fallback.outcome === "fork" ? { forkTo: fallback.target } : {}
5175
+ });
5127
5176
  }
5128
5177
  async emitMidTurnExhaustedEvent(error) {
5129
5178
  const detail = error instanceof Error ? error.message : String(error);
@@ -7001,11 +7050,11 @@ var CodexAspManager = class extends CodingAgentManager {
7001
7050
  let terminalError = error;
7002
7051
  if (terminalError instanceof CodexQuotaError && !quotaFallbackAttempted) {
7003
7052
  quotaFallbackAttempted = true;
7004
- const outcome = await this.fallBackCredential(
7053
+ const fallback = await this.fallBackCredential(
7005
7054
  detectAgentQuotaLimit(terminalError.message) ?? "rate_limit",
7006
7055
  terminalError.message
7007
7056
  );
7008
- if (outcome === "switched") {
7057
+ if (fallback.outcome === "switched") {
7009
7058
  attemptedCredential = ENGINE_ENV.REPLICAS_AGENT_CREDENTIALS.codex;
7010
7059
  const retryFailure = await this.restartAndRetry(dispatch);
7011
7060
  if (!retryFailure) return;
@@ -7015,7 +7064,8 @@ var CodexAspManager = class extends CodingAgentManager {
7015
7064
  this.emitQuotaExhausted({
7016
7065
  provider: "codex",
7017
7066
  reason: outOfCredits ? "out_of_credits" : detectAgentQuotaLimit(terminalError.message) ?? "rate_limit",
7018
- detail: terminalError.message
7067
+ detail: terminalError.message,
7068
+ ...fallback.outcome === "fork" ? { forkTo: fallback.target } : {}
7019
7069
  });
7020
7070
  if (outOfCredits) return;
7021
7071
  }
@@ -9782,8 +9832,8 @@ ${pending}
9782
9832
  } catch (error) {
9783
9833
  if (error instanceof MuseQuotaError) {
9784
9834
  const quotaKind = detectAgentQuotaLimit(error.message);
9785
- const outcome = await this.fallBackCredential(quotaKind ?? "rate_limit", error.message);
9786
- if (outcome === "switched") {
9835
+ const fallback = await this.fallBackCredential(quotaKind ?? "rate_limit", error.message);
9836
+ if (fallback.outcome === "switched") {
9787
9837
  this.stopProcess();
9788
9838
  try {
9789
9839
  await dispatch();
@@ -9794,6 +9844,8 @@ ${pending}
9794
9844
  }, this.historyFile);
9795
9845
  throw retryError;
9796
9846
  }
9847
+ } else if (fallback.outcome === "fork") {
9848
+ this.emitQuotaExhausted({ provider: "muse", reason: quotaKind ?? "rate_limit", detail: error.message, forkTo: fallback.target });
9797
9849
  }
9798
9850
  return;
9799
9851
  }
@@ -10186,7 +10238,7 @@ var OpencodeManager = class extends CodingAgentManager {
10186
10238
  slashCommandsCache = null;
10187
10239
  slashCommandsRequest = null;
10188
10240
  constructor(options) {
10189
- super(options);
10241
+ super({ ...options, provider: "opencode" });
10190
10242
  this.sessionId = options.initialSessionId;
10191
10243
  this.historyFilePath = options.historyFilePath ?? join22(ENGINE_ENV.HOME_DIR, ".replicas", "opencode", "history.jsonl");
10192
10244
  this.historyFile = new CodexHistoryFile(this.historyFilePath);
@@ -10487,7 +10539,13 @@ var OpencodeManager = class extends CodingAgentManager {
10487
10539
  this.handledAccountRateLimit = true;
10488
10540
  const message = typeof status.message === "string" ? status.message : typeof action.message === "string" ? action.message : "OpenCode Go usage limit reached.";
10489
10541
  this.recordHistoryEvent("opencode-error", { message, code: "account_rate_limit" }, this.historyFile);
10490
- this.emitQuotaExhausted({ provider: "opencode", reason: "rate_limit", detail: message });
10542
+ const fallback = await this.fallBackCredential("rate_limit", message);
10543
+ this.emitQuotaExhausted({
10544
+ provider: "opencode",
10545
+ reason: "rate_limit",
10546
+ detail: message,
10547
+ ...fallback.outcome === "fork" ? { forkTo: fallback.target } : {}
10548
+ });
10491
10549
  await this.abortActiveTurn();
10492
10550
  }
10493
10551
  recordOpencodeMessageRole(payload) {
@@ -13694,7 +13752,7 @@ var ChatService = class {
13694
13752
  /** Resolves once the target chat exists; the handoff and its first turn run in the background. */
13695
13753
  async forkChat(sourceChatId, request, trigger = "manual", reason) {
13696
13754
  const source = this.requireChat(sourceChatId);
13697
- if (!isAgentAvailable(request.provider)) throw new ChatProviderUnavailableError(request.provider);
13755
+ if (trigger === "manual" && !isAgentAvailable(request.provider)) throw new ChatProviderUnavailableError(request.provider);
13698
13756
  const fork = {
13699
13757
  source: { chatId: source.persisted.id, provider: source.persisted.provider, title: source.persisted.title },
13700
13758
  trigger,
@@ -13719,6 +13777,7 @@ var ChatService = class {
13719
13777
  const fork = target?.persisted.fork;
13720
13778
  if (!target || !fork) return;
13721
13779
  try {
13780
+ if (!isAgentAvailable(request.provider)) throw new ChatProviderUnavailableError(request.provider);
13722
13781
  const handoff = await buildForkHandoff({
13723
13782
  sourceChatId: fork.source.chatId,
13724
13783
  sourceProvider: fork.source.provider,
@@ -13771,20 +13830,15 @@ var ChatService = class {
13771
13830
  return providers;
13772
13831
  }
13773
13832
  async autoForkAfterQuota(chat, exhaustion) {
13774
- if (chat.persisted.parentChatId !== null || chat.persisted.deletedAt) return;
13833
+ const target = exhaustion.forkTo;
13834
+ if (!target || chat.persisted.deletedAt) return;
13775
13835
  const alreadyForked = [...this.chats.values()].some((other) => other.persisted.fork?.trigger === "auto" && other.persisted.fork.source.chatId === chat.persisted.id && !other.persisted.deletedAt);
13776
13836
  if (alreadyForked) return;
13777
13837
  const last = chat.lastSendRequest;
13778
- const fallback = await monolithService.getChatForkFallback({
13779
- provider: chat.persisted.provider,
13780
- exclude: this.forkChainProviders(chat),
13781
- userId: last?.senderUserId
13782
- });
13783
- if (!fallback?.provider || !isAgentAvailable(fallback.provider)) return;
13784
13838
  await this.forkChat(chat.persisted.id, {
13785
- provider: fallback.provider,
13786
- model: fallback.model,
13787
- thinkingLevel: fallback.thinkingLevel,
13839
+ provider: target.provider,
13840
+ model: target.model,
13841
+ thinkingLevel: target.thinkingLevel,
13788
13842
  enableInteractiveTools: last?.enableInteractiveTools,
13789
13843
  senderUserId: last?.senderUserId,
13790
13844
  senderEmail: last?.senderEmail,
@@ -13908,6 +13962,15 @@ var ChatService = class {
13908
13962
  }).catch(() => {
13909
13963
  });
13910
13964
  };
13965
+ const fallbackContext = () => {
13966
+ const chat = this.chats.get(persisted.id);
13967
+ return {
13968
+ userId: chat?.lastSendRequest?.senderUserId,
13969
+ excludeProviders: chat ? this.forkChainProviders(chat) : [persisted.provider],
13970
+ // A subagent thread is driven by its parent's tool call, which a sibling chat would leave hanging.
13971
+ forkable: (chat?.persisted ?? persisted).parentChatId === null
13972
+ };
13973
+ };
13911
13974
  if (persisted.parentChatId === void 0) {
13912
13975
  persisted.parentChatId = null;
13913
13976
  }
@@ -13921,7 +13984,8 @@ var ChatService = class {
13921
13984
  onTurnComplete: onProviderTurnComplete,
13922
13985
  onEvent: onProviderEvent,
13923
13986
  onProcessingChanged,
13924
- onMessageStarted
13987
+ onMessageStarted,
13988
+ fallbackContext
13925
13989
  });
13926
13990
  } else if (persisted.provider === "relay") {
13927
13991
  const getProviderAvailability = () => ({
@@ -14003,6 +14067,7 @@ var ChatService = class {
14003
14067
  onEvent: onProviderEvent,
14004
14068
  onProcessingChanged,
14005
14069
  onMessageStarted,
14070
+ fallbackContext,
14006
14071
  onUsageUpdate: onProviderUsage
14007
14072
  });
14008
14073
  } else if (persisted.provider === "opencode") {
@@ -14015,7 +14080,8 @@ var ChatService = class {
14015
14080
  onEvent: onProviderEvent,
14016
14081
  onProcessingChanged,
14017
14082
  onMessageStarted,
14018
- onUsageUpdate: onProviderUsage
14083
+ onUsageUpdate: onProviderUsage,
14084
+ fallbackContext
14019
14085
  });
14020
14086
  } else if (persisted.provider === "pi") {
14021
14087
  provider = new PiManager({
@@ -14044,6 +14110,7 @@ var ChatService = class {
14044
14110
  onEvent: onProviderEvent,
14045
14111
  onProcessingChanged,
14046
14112
  onMessageStarted,
14113
+ fallbackContext,
14047
14114
  onCodexTurnStarted: codexTokenUsage.onTurnStarted,
14048
14115
  onCodexTokenUsage: codexTokenUsage.onTokenUsage,
14049
14116
  onCodexModelRerouted: codexTokenUsage.onModelRerouted,
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  notifyPostToolUse
4
- } from "./chunk-2VCV6W5F.js";
4
+ } from "./chunk-AQXSBHCI.js";
5
5
  import {
6
6
  isRecord
7
7
  } from "./chunk-2RB7SIP3.js";
8
- import "./chunk-EZ6Z6GSU.js";
8
+ import "./chunk-NQFMLRS4.js";
9
9
 
10
10
  // src/post-tool-pr-hook.ts
11
11
  async function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.735",
3
+ "version": "0.1.736",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",