replicas-engine 0.1.1027 → 0.1.1029

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.
@@ -5,12 +5,12 @@ import {
5
5
  getCodexAspHost,
6
6
  restartCodexAspHost,
7
7
  restartCodexAspHostIfRunning
8
- } from "./chunk-73446GJ3.js";
9
- import "./chunk-QFJFKNXF.js";
10
- import "./chunk-FOVYFK2H.js";
11
- import "./chunk-IPL36DTU.js";
8
+ } from "./chunk-DWWWY7HA.js";
9
+ import "./chunk-IPOU7CXA.js";
10
+ import "./chunk-SMKDV5DS.js";
11
+ import "./chunk-HIRAX6GE.js";
12
12
  import "./chunk-UZSNFLDQ.js";
13
- import "./chunk-HLUAG4YU.js";
13
+ import "./chunk-BOAIMJKK.js";
14
14
  import "./chunk-VEQXQN22.js";
15
15
  export {
16
16
  getCodexAspHost,
@@ -1548,6 +1548,19 @@ var mcpWorkloadAuthSchema = z4.strictObject({
1548
1548
  type: z4.literal("workspace_identity"),
1549
1549
  audience: workspaceAudienceSchema
1550
1550
  });
1551
+ var mcpAuthSchema = z4.discriminatedUnion("type", [
1552
+ mcpWorkloadAuthSchema,
1553
+ z4.strictObject({ type: z4.literal("oauth") })
1554
+ ]);
1555
+ var engineMcpOAuthTokenRequestSchema = z4.object({
1556
+ environment_mcp_id: z4.string().uuid(),
1557
+ force_refresh: z4.boolean().optional()
1558
+ });
1559
+ var engineMcpOAuthTokenResponseSchema = z4.object({
1560
+ url: z4.string(),
1561
+ access_token: z4.string(),
1562
+ expires_at: z4.string().nullable()
1563
+ });
1551
1564
 
1552
1565
  // ../shared/src/runtime-env.ts
1553
1566
  var REPLICAS_RUNTIME_ENV_ALIASES = {
@@ -2748,15 +2761,30 @@ function createUuidFromHex(value) {
2748
2761
  }
2749
2762
  var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
2750
2763
 
2764
+ // ../shared/src/automations/github/index.ts
2765
+ var REVIEW_REQUESTED_EVENT = "pull_request.review_requested";
2766
+
2751
2767
  // ../shared/src/automations/trigger-schemas.ts
2768
+ function getReviewerLoginsError(event, logins) {
2769
+ const isReviewRequested = event === REVIEW_REQUESTED_EVENT;
2770
+ if (isReviewRequested === !!logins?.some((login) => login.trim().replace(/^@+/, ""))) return null;
2771
+ return isReviewRequested ? "The PR review requested trigger needs at least one GitHub username" : "Requested reviewers only apply to the PR review requested trigger";
2772
+ }
2752
2773
  var repositoryTriggerConfigSchema = z10.object({
2753
2774
  event: z10.string(),
2754
2775
  repository_ids: z10.array(z10.string().regex(UUID_RE, "must be a repository id")).optional(),
2755
2776
  group_pr_events: z10.boolean().optional(),
2756
- excluded_actors: z10.array(z10.string()).optional()
2777
+ excluded_actors: z10.array(z10.string()).optional(),
2778
+ reviewer_logins: z10.array(z10.string()).optional()
2757
2779
  });
2758
2780
  var cronTriggerSchema = z10.object({ type: z10.literal("cron"), config: z10.object({ schedule: z10.string(), timezone: z10.string().optional() }) });
2759
- var githubTriggerSchema = z10.object({ type: z10.literal("github"), config: repositoryTriggerConfigSchema });
2781
+ var githubTriggerSchema = z10.object({
2782
+ type: z10.literal("github"),
2783
+ config: repositoryTriggerConfigSchema.superRefine((config, ctx) => {
2784
+ const message2 = getReviewerLoginsError(config.event, config.reviewer_logins);
2785
+ if (message2) ctx.addIssue({ code: "custom", message: message2, path: ["reviewer_logins"] });
2786
+ })
2787
+ });
2760
2788
  var gitlabTriggerSchema = z10.object({ type: z10.literal("gitlab"), config: repositoryTriggerConfigSchema });
2761
2789
  var automationTriggerSchema = z10.discriminatedUnion("type", [
2762
2790
  cronTriggerSchema,
@@ -2776,7 +2804,7 @@ var createEnvironmentMcpRequestSchema = z11.object({
2776
2804
  transport: z11.enum(ENVIRONMENT_MCP_TRANSPORTS),
2777
2805
  config: z11.union([
2778
2806
  z11.object({ command: z11.string().min(1), args: z11.array(z11.string()).optional(), env: stringMap.optional() }),
2779
- z11.object({ url: z11.url(), headers: stringMap.optional(), auth: mcpWorkloadAuthSchema.optional() })
2807
+ z11.object({ url: z11.url(), headers: stringMap.optional(), auth: mcpAuthSchema.optional() })
2780
2808
  ]).describe("stdio: `{ command, args, env }`. http/sse: `{ url, headers }`.")
2781
2809
  });
2782
2810
  var updateEnvironmentMcpRequestSchema = createEnvironmentMcpRequestSchema.partial();
@@ -3377,7 +3405,7 @@ Trigger shapes:
3377
3405
  { "type": "cron", "config": { "schedule": "0 9 * * 1-5", "timezone": "UTC" } }
3378
3406
  \`\`\`
3379
3407
 
3380
- GitHub events: \`pull_request.opened\`, \`pull_request.synchronize\`, \`pull_request.merged\`, \`pull_request.closed\`. GitLab events: \`merge_request.opened\`, \`merge_request.updated\`, \`merge_request.merged\`, \`merge_request.closed\`. Use one trigger type family per automation and pick the code host the repositories actually live on.
3408
+ GitHub events: \`pull_request.opened\`, \`pull_request.synchronize\`, \`pull_request.review_requested\` (requires a \`reviewer_logins\` list of GitHub usernames), \`pull_request.merged\`, \`pull_request.closed\`. GitLab events: \`merge_request.opened\`, \`merge_request.updated\`, \`merge_request.merged\`, \`merge_request.closed\`. Use one trigger type family per automation and pick the code host the repositories actually live on.
3381
3409
 
3382
3410
  Test the hooks before proposing them: run the drafted warm hook and start hook scripts in this workspace (from \`~/workspaces\`) and fix the earliest failure until both exit 0 on a second run.
3383
3411
 
@@ -7619,6 +7647,7 @@ var EGRESS_SHELL_CONFIG = [
7619
7647
  `EGRESS_UDP_MAX_ASSOCIATIONS=${EGRESS_UDP_MAX_ASSOCIATIONS}`
7620
7648
  ].join("\n");
7621
7649
  var EGRESS_TUNNEL_SERVICE = "replicas-egress.service";
7650
+ var EGRESS_ROUTES_SERVICE = "replicas-egress-routes.service";
7622
7651
  var EGRESS_TUNNEL_INTERFACE = "rpxtun0";
7623
7652
  var EGRESS_PATHS = {
7624
7653
  /**
@@ -7631,9 +7660,11 @@ var EGRESS_PATHS = {
7631
7660
  CONFIG_DIR: "/run/replicas-egress",
7632
7661
  CONFIG_FILE: "/run/replicas-egress/config.json",
7633
7662
  REVISION_FILE: "/run/replicas-egress/revision",
7663
+ DESTINATIONS_FILE: "/run/replicas-egress/destinations",
7634
7664
  /** Persistent: this records the workspace's original resolver so the tunnel can put it back, and must outlive a reboot. */
7635
7665
  RESOLV_BACKUP: "/var/lib/replicas-egress-resolv.conf.orig",
7636
7666
  UNIT_FILE: `/etc/systemd/system/${EGRESS_TUNNEL_SERVICE}`,
7667
+ ROUTES_UNIT_FILE: `/etc/systemd/system/${EGRESS_ROUTES_SERVICE}`,
7637
7668
  UP_SCRIPT: "/usr/local/lib/replicas-egress-up",
7638
7669
  DOWN_SCRIPT: "/usr/local/lib/replicas-egress-down"
7639
7670
  };
@@ -9355,6 +9386,7 @@ export {
9355
9386
  workspaceTokenRequestSchema,
9356
9387
  workspaceTokenResponseSchema,
9357
9388
  legacyWorkspaceTokenBundleSchema,
9389
+ engineMcpOAuthTokenResponseSchema,
9358
9390
  SANDBOX_SKILL_DIRS,
9359
9391
  HISTORY_ROOT,
9360
9392
  HISTORY_INDEX_FILENAME,
@@ -5,15 +5,15 @@ import {
5
5
  ENGINE_ENV,
6
6
  monolithRequest,
7
7
  setAgentCredentialSnapshot
8
- } from "./chunk-QFJFKNXF.js";
8
+ } from "./chunk-IPOU7CXA.js";
9
9
  import {
10
10
  AppServerProcess,
11
11
  buildCodexAgentEnv
12
- } from "./chunk-FOVYFK2H.js";
12
+ } from "./chunk-SMKDV5DS.js";
13
13
  import {
14
14
  SUBPROCESS_MAX_BUFFER,
15
15
  execFileAsync
16
- } from "./chunk-IPL36DTU.js";
16
+ } from "./chunk-HIRAX6GE.js";
17
17
  import {
18
18
  isRecord
19
19
  } from "./chunk-UZSNFLDQ.js";
@@ -28,7 +28,7 @@ import {
28
28
  listOpenAICompatibleModels,
29
29
  readOllamaAuthEnv,
30
30
  resolveOpenAICompatibleModelsUrl
31
- } from "./chunk-HLUAG4YU.js";
31
+ } from "./chunk-BOAIMJKK.js";
32
32
 
33
33
  // src/managers/codex-token-manager.ts
34
34
  import { promises as fs } from "fs";
@@ -264,7 +264,7 @@ var CodexTokenManager = class extends BaseRefreshManager {
264
264
  await this.applyCredentialUpdate(++this.credentialGeneration, async () => {
265
265
  await this.applyCredentialsResponse(data);
266
266
  setAgentCredentialSnapshot("codex", data);
267
- const { restartCodexAspHostIfRunning: restartCodexAspHostIfRunning2 } = await import("./asp-host-R6LN7ANF.js");
267
+ const { restartCodexAspHostIfRunning: restartCodexAspHostIfRunning2 } = await import("./asp-host-2QL3R5O5.js");
268
268
  await restartCodexAspHostIfRunning2();
269
269
  });
270
270
  await this.start(true);
@@ -285,7 +285,7 @@ var CodexTokenManager = class extends BaseRefreshManager {
285
285
  const accountChanged = ENGINE_ENV.REPLICAS_AGENT_CREDENTIALS.codex?.credentialId !== data.credentialId;
286
286
  await this.applyCredentialsResponse(data);
287
287
  if (restartOnChange && (accountChanged || CODEX_AUTH_ENV_KEYS.some((key, index) => process.env[key] !== previousEnv[index]))) {
288
- const { restartCodexAspHostIfRunning: restartCodexAspHostIfRunning2 } = await import("./asp-host-R6LN7ANF.js");
288
+ const { restartCodexAspHostIfRunning: restartCodexAspHostIfRunning2 } = await import("./asp-host-2QL3R5O5.js");
289
289
  await restartCodexAspHostIfRunning2();
290
290
  }
291
291
  setAgentCredentialSnapshot("codex", data);
@@ -5,7 +5,7 @@ import {
5
5
  extractCommandProtectionCommandText,
6
6
  isRecord,
7
7
  parseCodeHostPrUrl
8
- } from "./chunk-HLUAG4YU.js";
8
+ } from "./chunk-BOAIMJKK.js";
9
9
 
10
10
  // src/services/published-pull-request-urls.ts
11
11
  function publishedPullRequestUrls(result) {
@@ -3,7 +3,7 @@ import { createRequire as __createRequire } from 'node:module';
3
3
  const require = __createRequire(import.meta.url);
4
4
  import {
5
5
  HOOK_EXEC_MAX_BUFFER_BYTES
6
- } from "./chunk-HLUAG4YU.js";
6
+ } from "./chunk-BOAIMJKK.js";
7
7
 
8
8
  // src/utils/exec.ts
9
9
  import { exec, execFile } from "child_process";
@@ -14,7 +14,7 @@ import {
14
14
  isValidAgentProvider,
15
15
  parsePosixEnvFile,
16
16
  readReplicasRuntimeEnv
17
- } from "./chunk-HLUAG4YU.js";
17
+ } from "./chunk-BOAIMJKK.js";
18
18
 
19
19
  // src/runtime-env-loader.ts
20
20
  import { readFileSync } from "fs";
@@ -3,10 +3,10 @@ import { createRequire as __createRequire } from 'node:module';
3
3
  const require = __createRequire(import.meta.url);
4
4
  import {
5
5
  execFileAsync
6
- } from "./chunk-IPL36DTU.js";
6
+ } from "./chunk-HIRAX6GE.js";
7
7
  import {
8
8
  isVersionBelow
9
- } from "./chunk-HLUAG4YU.js";
9
+ } from "./chunk-BOAIMJKK.js";
10
10
 
11
11
  // src/utils/codex-agent-env.ts
12
12
  function buildCodexAgentEnv(source = process.env) {
@@ -240,7 +240,7 @@ function getCodexModelProvider(method) {
240
240
  if (method === "foundry") return "azure";
241
241
  return method === "ollama" || method === "openai-compatible" ? "replicas_openai_compatible" : "openai";
242
242
  }
243
- var ENGINE_PACKAGE_VERSION = "0.1.1027";
243
+ var ENGINE_PACKAGE_VERSION = "0.1.1029";
244
244
  var INITIALIZE_METHOD = "initialize";
245
245
  var INITIALIZED_NOTIFICATION = "initialized";
246
246
  var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
@@ -3,18 +3,18 @@ import { createRequire as __createRequire } from 'node:module';
3
3
  const require = __createRequire(import.meta.url);
4
4
  import {
5
5
  monolithRequest
6
- } from "./chunk-QFJFKNXF.js";
6
+ } from "./chunk-IPOU7CXA.js";
7
7
  import {
8
8
  SUBPROCESS_MAX_BUFFER,
9
9
  execFileAsync
10
- } from "./chunk-IPL36DTU.js";
10
+ } from "./chunk-HIRAX6GE.js";
11
11
  import {
12
12
  extractCommandProtectionCommandText,
13
13
  findCommandProtectionGitLookups,
14
14
  findGitCommitSignals,
15
15
  findPrMergeSignals,
16
16
  parseCommandProtectionRequest
17
- } from "./chunk-HLUAG4YU.js";
17
+ } from "./chunk-BOAIMJKK.js";
18
18
 
19
19
  // src/services/command-protection-service.ts
20
20
  import { readFile, stat } from "fs/promises";
@@ -3,13 +3,13 @@ import { createRequire as __createRequire } from 'node:module';
3
3
  const require = __createRequire(import.meta.url);
4
4
  import {
5
5
  evaluateCommandProtection
6
- } from "./chunk-7BKBAILL.js";
7
- import "./chunk-QFJFKNXF.js";
8
- import "./chunk-IPL36DTU.js";
6
+ } from "./chunk-UMI7BLA3.js";
7
+ import "./chunk-IPOU7CXA.js";
8
+ import "./chunk-HIRAX6GE.js";
9
9
  import {
10
10
  isRecord
11
11
  } from "./chunk-UZSNFLDQ.js";
12
- import "./chunk-HLUAG4YU.js";
12
+ import "./chunk-BOAIMJKK.js";
13
13
  import "./chunk-VEQXQN22.js";
14
14
 
15
15
  // src/command-protection-hook.ts
@@ -7,12 +7,12 @@ import {
7
7
  import {
8
8
  AppServerProcess,
9
9
  buildCodexAgentEnv
10
- } from "./chunk-FOVYFK2H.js";
11
- import "./chunk-IPL36DTU.js";
10
+ } from "./chunk-SMKDV5DS.js";
11
+ import "./chunk-HIRAX6GE.js";
12
12
  import {
13
13
  AGENT,
14
14
  headlessAgentRequestSchema
15
- } from "./chunk-HLUAG4YU.js";
15
+ } from "./chunk-BOAIMJKK.js";
16
16
  import "./chunk-VEQXQN22.js";
17
17
 
18
18
  // src/headless-agent.ts
package/dist/src/index.js CHANGED
@@ -32,7 +32,7 @@ import {
32
32
  reportCommandProtectionBlock,
33
33
  resolveDiffBase,
34
34
  runGitCommand
35
- } from "./chunk-7BKBAILL.js";
35
+ } from "./chunk-UMI7BLA3.js";
36
36
  import {
37
37
  ACCOUNT_RATE_LIMITS_UPDATED_METHOD,
38
38
  AGENT_MESSAGE_DELTA_METHOD,
@@ -57,7 +57,7 @@ import {
57
57
  } from "./chunk-P73LGPQO.js";
58
58
  import {
59
59
  publishedPullRequestUrls
60
- } from "./chunk-X5VJ2FDA.js";
60
+ } from "./chunk-E35XSXJE.js";
61
61
  import {
62
62
  BaseRefreshManager,
63
63
  CodexAspAuthMethodChangedError,
@@ -71,7 +71,7 @@ import {
71
71
  refreshDelayMs,
72
72
  restartCodexAspHost,
73
73
  routineRefreshRequest
74
- } from "./chunk-73446GJ3.js";
74
+ } from "./chunk-DWWWY7HA.js";
75
75
  import {
76
76
  ENGINE_ENV,
77
77
  monolithRequest,
@@ -79,16 +79,16 @@ import {
79
79
  readRuntimeEnvFile,
80
80
  reloadEngineEnv,
81
81
  setAgentCredentialSnapshot
82
- } from "./chunk-QFJFKNXF.js";
82
+ } from "./chunk-IPOU7CXA.js";
83
83
  import {
84
84
  AspClient,
85
85
  getCodexModelProvider
86
- } from "./chunk-FOVYFK2H.js";
86
+ } from "./chunk-SMKDV5DS.js";
87
87
  import {
88
88
  SUBPROCESS_MAX_BUFFER,
89
89
  execAsync,
90
90
  execFileAsync
91
- } from "./chunk-IPL36DTU.js";
91
+ } from "./chunk-HIRAX6GE.js";
92
92
  import {
93
93
  isRecord as isRecord2
94
94
  } from "./chunk-UZSNFLDQ.js";
@@ -218,6 +218,7 @@ import {
218
218
  detectAgentQuotaLimit,
219
219
  detectEnvironmentAuthMethod,
220
220
  detectLanguageByPath,
221
+ engineMcpOAuthTokenResponseSchema,
221
222
  excludeQueuedAcceptedEvents,
222
223
  extractErrorText,
223
224
  extractFinalResponse,
@@ -318,7 +319,7 @@ import {
318
319
  workspaceTokenRequestSchema,
319
320
  workspaceTokenResponseSchema,
320
321
  wrapInTag
321
- } from "./chunk-HLUAG4YU.js";
322
+ } from "./chunk-BOAIMJKK.js";
322
323
  import {
323
324
  cloneMcpConfig,
324
325
  ensureCompatibilityImports,
@@ -9636,6 +9637,41 @@ async function applyInfisicalCredentials(data, paths) {
9636
9637
  }
9637
9638
  var infisicalTokenManager = new InfisicalTokenManager();
9638
9639
 
9640
+ // src/token-cache.ts
9641
+ var TokenCache = class {
9642
+ constructor(maxEntries = Infinity) {
9643
+ this.maxEntries = maxEntries;
9644
+ }
9645
+ maxEntries;
9646
+ cache = /* @__PURE__ */ new Map();
9647
+ inflight = /* @__PURE__ */ new Map();
9648
+ get(key, load, force = false) {
9649
+ const cached = this.cache.get(key);
9650
+ if (!force && cached && cached.refreshAt > Date.now()) return Promise.resolve(createSuccessResult(cached.token));
9651
+ const inflightKey = force ? `${key}\0force` : key;
9652
+ let pending = this.inflight.get(inflightKey);
9653
+ if (!pending) {
9654
+ pending = load().then((result) => {
9655
+ this.cache.delete(key);
9656
+ if (!result.ok) return result;
9657
+ const oldest = this.cache.keys().next();
9658
+ if (this.cache.size >= this.maxEntries && !oldest.done) this.cache.delete(oldest.value);
9659
+ this.cache.set(key, result.data);
9660
+ return createSuccessResult(result.data.token);
9661
+ }).finally(() => this.inflight.delete(inflightKey));
9662
+ this.inflight.set(inflightKey, pending);
9663
+ }
9664
+ return pending;
9665
+ }
9666
+ };
9667
+ async function requestToken(request, path7, body, failureMessage) {
9668
+ try {
9669
+ return createSuccessResult(await request(path7, { body }));
9670
+ } catch (error) {
9671
+ return createErrorResult({ message: error instanceof Error ? error.message : failureMessage, code: 503 });
9672
+ }
9673
+ }
9674
+
9639
9675
  // src/workspace-identity-tokens.ts
9640
9676
  var MIN_REMAINING_MS = 5 * 60 * 1e3;
9641
9677
  var MAX_CACHED_AUDIENCES = 32;
@@ -9644,30 +9680,19 @@ var WorkspaceIdentityTokens = class {
9644
9680
  this.request = request;
9645
9681
  }
9646
9682
  request;
9647
- cache = /* @__PURE__ */ new Map();
9648
- inflight = /* @__PURE__ */ new Map();
9683
+ tokens = new TokenCache(MAX_CACHED_AUDIENCES);
9649
9684
  async get(audience = WORKSPACE_IDENTITY_AUDIENCE, awsSourceIdentity = false) {
9650
9685
  const parsed = workspaceAudienceSchema.safeParse(audience);
9651
9686
  if (!parsed.success) {
9652
9687
  return createErrorResult({ message: parsed.error.issues[0]?.message ?? "Invalid audience", code: 400 });
9653
9688
  }
9654
9689
  const key = awsSourceIdentity ? `${audience} aws_source_identity` : audience;
9655
- const cached = this.cache.get(key);
9656
- if (cached && Date.parse(cached.expires_at) - Date.now() > MIN_REMAINING_MS) return createSuccessResult(cached);
9657
- let pending = this.inflight.get(key);
9658
- if (!pending) {
9659
- pending = this.mint(audience, awsSourceIdentity, key).finally(() => this.inflight.delete(key));
9660
- this.inflight.set(key, pending);
9661
- }
9662
- return pending;
9690
+ return this.tokens.get(key, () => this.mint(audience, awsSourceIdentity));
9663
9691
  }
9664
- async mint(audience, awsSourceIdentity, key) {
9665
- let response;
9666
- try {
9667
- response = await this.request("/v1/engine/workspace-identity/token", { body: { audience, aws_source_identity: awsSourceIdentity } });
9668
- } catch (error) {
9669
- return createErrorResult({ message: error instanceof Error ? error.message : "Workspace identity request failed", code: 503 });
9670
- }
9692
+ async mint(audience, awsSourceIdentity) {
9693
+ const requested = await requestToken(this.request, "/v1/engine/workspace-identity/token", { audience, aws_source_identity: awsSourceIdentity }, "Workspace identity request failed");
9694
+ if (!requested.ok) return requested;
9695
+ const response = requested.data;
9671
9696
  if (!response.ok) {
9672
9697
  return createErrorResult({ message: `Workspace identity request failed: ${response.status} ${await response.text()}`, code: response.status === 400 ? 400 : 503 });
9673
9698
  }
@@ -9687,11 +9712,7 @@ var WorkspaceIdentityTokens = class {
9687
9712
  return createErrorResult({ message: "AWS source identity is unavailable from this Replicas version", code: 503 });
9688
9713
  }
9689
9714
  }
9690
- this.cache.delete(key);
9691
- const oldest = this.cache.keys().next();
9692
- if (this.cache.size >= MAX_CACHED_AUDIENCES && !oldest.done) this.cache.delete(oldest.value);
9693
- this.cache.set(key, token);
9694
- return createSuccessResult(token);
9715
+ return createSuccessResult({ token, refreshAt: Date.parse(token.expires_at) - MIN_REMAINING_MS });
9695
9716
  }
9696
9717
  };
9697
9718
  var workspaceIdentityTokens = new WorkspaceIdentityTokens();
@@ -9729,27 +9750,85 @@ function resolveIdentityRoute(requestUrl) {
9729
9750
  target.search = url3.search;
9730
9751
  return { target, audience: decode(encodedAudience) };
9731
9752
  }
9732
- function readJsonBody(request) {
9753
+ function resolveMcpOAuthRoute(requestUrl) {
9754
+ const url3 = new URL(requestUrl, "http://127.0.0.1");
9755
+ const [, kind, environmentMcpId, ...rest] = url3.pathname.split("/");
9756
+ return kind === "mcp-oauth" && environmentMcpId ? { environmentMcpId, rest, search: url3.search } : null;
9757
+ }
9758
+ var MAX_MCP_OAUTH_BODY_BYTES = 10 * 1024 * 1024;
9759
+ function readBody(request, maxBytes) {
9733
9760
  return new Promise((resolve7) => {
9734
9761
  const chunks = [];
9735
9762
  let size = 0;
9736
9763
  request.on("data", (chunk) => {
9737
9764
  size += chunk.length;
9738
- if (size <= MAX_TOKEN_REQUEST_BYTES) chunks.push(chunk);
9739
- });
9740
- request.on("end", () => {
9741
- if (size > MAX_TOKEN_REQUEST_BYTES) return resolve7(null);
9742
- const text = Buffer.concat(chunks).toString("utf8").trim();
9743
- try {
9744
- resolve7(text ? JSON.parse(text) : {});
9745
- } catch {
9746
- resolve7(null);
9747
- }
9765
+ if (size > maxBytes) resolve7("too_large");
9766
+ else chunks.push(chunk);
9748
9767
  });
9768
+ request.on("end", () => resolve7(Buffer.concat(chunks)));
9749
9769
  request.on("error", () => resolve7(null));
9750
9770
  });
9751
9771
  }
9752
- function createIdentityProxy(tokens) {
9772
+ function forward(request, response, target, token, onResponse = (incoming) => pipeResponse(incoming, response)) {
9773
+ const headers = { host: target.host };
9774
+ for (const [name, value] of Object.entries(request.headers)) {
9775
+ if (!STRIPPED_HEADERS.has(name) && !name.startsWith("x-replicas-") && value !== void 0) headers[name] = value;
9776
+ }
9777
+ headers.authorization = `Bearer ${token}`;
9778
+ const upstream = (target.protocol === "https:" ? https : http).request(target, { method: request.method, headers }, onResponse);
9779
+ upstream.on("error", () => {
9780
+ if (!response.headersSent) response.writeHead(502);
9781
+ response.end();
9782
+ });
9783
+ response.on("close", () => upstream.destroy());
9784
+ return upstream;
9785
+ }
9786
+ function pipeResponse(incoming, response) {
9787
+ response.writeHead(incoming.statusCode ?? 502, incoming.headers);
9788
+ incoming.pipe(response);
9789
+ response.on("close", () => incoming.destroy());
9790
+ }
9791
+ async function proxyMcpOAuth(request, response, tokens, route) {
9792
+ const body = await readBody(request, MAX_MCP_OAUTH_BODY_BYTES);
9793
+ if (body === "too_large") {
9794
+ response.writeHead(413, { connection: "close" }).end();
9795
+ return;
9796
+ }
9797
+ if (!body) {
9798
+ response.writeHead(400).end();
9799
+ return;
9800
+ }
9801
+ const send = async (forceRefresh) => {
9802
+ const token = await tokens.get(route.environmentMcpId, forceRefresh);
9803
+ if (!token.ok) {
9804
+ response.writeHead(token.error.code ?? 503).end(token.error.message);
9805
+ return;
9806
+ }
9807
+ const target = new URL(token.data.url);
9808
+ if (route.rest.length) target.pathname = [target.pathname.replace(/\/$/, ""), ...route.rest].join("/");
9809
+ target.search = route.search;
9810
+ forward(request, response, target, token.data.access_token, (incoming) => {
9811
+ if (incoming.statusCode === 401 && !forceRefresh) {
9812
+ incoming.resume();
9813
+ void send(true);
9814
+ return;
9815
+ }
9816
+ pipeResponse(incoming, response);
9817
+ }).end(body);
9818
+ };
9819
+ await send(false);
9820
+ }
9821
+ async function readJsonBody(request) {
9822
+ const body = await readBody(request, MAX_TOKEN_REQUEST_BYTES);
9823
+ if (!body || body === "too_large") return null;
9824
+ const text = body.toString("utf8").trim();
9825
+ try {
9826
+ return text ? JSON.parse(text) : {};
9827
+ } catch {
9828
+ return null;
9829
+ }
9830
+ }
9831
+ function createIdentityProxy(tokens, mcpOAuthTokens2) {
9753
9832
  return http.createServer(async (request, response) => {
9754
9833
  const url3 = new URL(request.url ?? "/", "http://127.0.0.1");
9755
9834
  if (url3.pathname === IDENTITY_TOKEN_PATH) {
@@ -9767,6 +9846,11 @@ function createIdentityProxy(tokens) {
9767
9846
  response.end(JSON.stringify(result.ok ? result.data : { error: result.error.message }));
9768
9847
  return;
9769
9848
  }
9849
+ const mcpOAuthRoute = resolveMcpOAuthRoute(request.url ?? "/");
9850
+ if (mcpOAuthRoute) {
9851
+ await proxyMcpOAuth(request, response, mcpOAuthTokens2, mcpOAuthRoute);
9852
+ return;
9853
+ }
9770
9854
  const route = resolveIdentityRoute(request.url ?? "/");
9771
9855
  if (!route) {
9772
9856
  response.writeHead(404).end("Unknown identity route");
@@ -9777,29 +9861,46 @@ function createIdentityProxy(tokens) {
9777
9861
  response.writeHead(token.error.code ?? 503).end(token.error.message);
9778
9862
  return;
9779
9863
  }
9780
- const headers = { host: route.target.host };
9781
- for (const [name, value] of Object.entries(request.headers)) {
9782
- if (!STRIPPED_HEADERS.has(name) && !name.startsWith("x-replicas-") && value !== void 0) headers[name] = value;
9783
- }
9784
- headers.authorization = `Bearer ${token.data.token}`;
9785
- const upstream = (route.target.protocol === "https:" ? https : http).request(
9786
- route.target,
9787
- { method: request.method, headers },
9788
- (incoming) => {
9789
- response.writeHead(incoming.statusCode ?? 502, incoming.headers);
9790
- incoming.pipe(response);
9791
- response.on("close", () => incoming.destroy());
9792
- }
9793
- );
9794
- upstream.on("error", () => {
9795
- if (!response.headersSent) response.writeHead(502);
9796
- response.end();
9797
- });
9798
- response.on("close", () => upstream.destroy());
9799
- request.pipe(upstream);
9864
+ request.pipe(forward(request, response, route.target, token.data.token));
9800
9865
  });
9801
9866
  }
9802
9867
 
9868
+ // src/mcp-oauth-tokens.ts
9869
+ var MIN_REMAINING_MS2 = 60 * 1e3;
9870
+ var MAX_CACHE_MS = 5 * 60 * 1e3;
9871
+ var McpOAuthTokens = class {
9872
+ constructor(request = monolithRequest) {
9873
+ this.request = request;
9874
+ }
9875
+ request;
9876
+ tokens = new TokenCache();
9877
+ get(environmentMcpId, forceRefresh = false) {
9878
+ return this.tokens.get(environmentMcpId, () => this.fetch(environmentMcpId, forceRefresh), forceRefresh);
9879
+ }
9880
+ async fetch(environmentMcpId, forceRefresh) {
9881
+ const requested = await requestToken(
9882
+ this.request,
9883
+ "/v1/engine/mcp-oauth/token",
9884
+ { environment_mcp_id: environmentMcpId, force_refresh: forceRefresh },
9885
+ "MCP OAuth token request failed"
9886
+ );
9887
+ if (!requested.ok) return requested;
9888
+ const response = requested.data;
9889
+ const data = await response.json().catch(() => null);
9890
+ const parsed = engineMcpOAuthTokenResponseSchema.safeParse(data);
9891
+ if (!response.ok || !parsed.success) {
9892
+ const message = isRecord(data) && typeof data.error === "string" ? data.error : `MCP OAuth token request failed: ${response.status}`;
9893
+ return createErrorResult({ message, code: response.ok ? 502 : response.status });
9894
+ }
9895
+ const remaining = parsed.data.expires_at ? Date.parse(parsed.data.expires_at) - Date.now() : Infinity;
9896
+ return createSuccessResult({
9897
+ token: parsed.data,
9898
+ refreshAt: Date.now() + Math.min(Math.max(remaining - MIN_REMAINING_MS2, remaining / 2), MAX_CACHE_MS)
9899
+ });
9900
+ }
9901
+ };
9902
+ var mcpOAuthTokens = new McpOAuthTokens();
9903
+
9803
9904
  // src/lifecycle.ts
9804
9905
  import { readFileSync, rmSync, writeFileSync } from "fs";
9805
9906
  var CLAIM_FILE = "/tmp/replicas-engine.claim";
@@ -50655,7 +50756,7 @@ data: ${JSON.stringify(payload)}
50655
50756
  }
50656
50757
  async function parseBody(req, res) {
50657
50758
  try {
50658
- const body = await readBody(req);
50759
+ const body = await readBody2(req);
50659
50760
  if (!body || typeof body !== "object") {
50660
50761
  sendJson(res, 400, { ok: false, error: "Invalid request body" });
50661
50762
  return null;
@@ -50666,7 +50767,7 @@ async function parseBody(req, res) {
50666
50767
  return null;
50667
50768
  }
50668
50769
  }
50669
- function readBody(req) {
50770
+ function readBody2(req) {
50670
50771
  return new Promise((resolve7, reject) => {
50671
50772
  let size = 0;
50672
50773
  const chunks = [];
@@ -64677,7 +64778,7 @@ async function startBackgroundServices(signal) {
64677
64778
  heartbeatService.start(bootTimeMs);
64678
64779
  if (!identityProxyStarted) {
64679
64780
  identityProxyStarted = true;
64680
- createIdentityProxy(workspaceIdentityTokens).on("error", (error) => console.error("[identity-proxy] failed to listen:", error)).listen(IDENTITY_PROXY_PORT, "127.0.0.1");
64781
+ createIdentityProxy(workspaceIdentityTokens, mcpOAuthTokens).on("error", (error) => console.error("[identity-proxy] failed to listen:", error)).listen(IDENTITY_PROXY_PORT, "127.0.0.1");
64681
64782
  }
64682
64783
  for (const manager of tokenManagers.slice(1)) {
64683
64784
  if (signal.aborted) return;
@@ -3,13 +3,13 @@ import { createRequire as __createRequire } from 'node:module';
3
3
  const require = __createRequire(import.meta.url);
4
4
  import {
5
5
  publishedPullRequestUrls
6
- } from "./chunk-X5VJ2FDA.js";
6
+ } from "./chunk-E35XSXJE.js";
7
7
  import {
8
8
  isRecord
9
9
  } from "./chunk-UZSNFLDQ.js";
10
10
  import {
11
11
  shouldRefreshPullRequests
12
- } from "./chunk-HLUAG4YU.js";
12
+ } from "./chunk-BOAIMJKK.js";
13
13
  import "./chunk-VEQXQN22.js";
14
14
 
15
15
  // src/services/post-tool-pr-notifier.ts
@@ -9,7 +9,7 @@ import {
9
9
  spawnSubagentRequestSchema,
10
10
  subagentResponseSchema,
11
11
  waitSubagentRequestSchema
12
- } from "./chunk-HLUAG4YU.js";
12
+ } from "./chunk-BOAIMJKK.js";
13
13
  import "./chunk-VEQXQN22.js";
14
14
 
15
15
  // src/subagent-mcp.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.1027",
3
+ "version": "0.1.1029",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",