replicas-engine 0.1.1028 → 0.1.1030

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-DU4QTHOX.js";
9
- import "./chunk-OK37MIB6.js";
10
- import "./chunk-LY4WTFXS.js";
11
- import "./chunk-CFLUSFBP.js";
8
+ } from "./chunk-27OEXGGC.js";
9
+ import "./chunk-RVXD24D4.js";
10
+ import "./chunk-NGZ6IR76.js";
11
+ import "./chunk-7PXLG7IJ.js";
12
12
  import "./chunk-UZSNFLDQ.js";
13
- import "./chunk-BFHGBJO2.js";
13
+ import "./chunk-5HLMUJTJ.js";
14
14
  import "./chunk-VEQXQN22.js";
15
15
  export {
16
16
  getCodexAspHost,
@@ -5,15 +5,15 @@ import {
5
5
  ENGINE_ENV,
6
6
  monolithRequest,
7
7
  setAgentCredentialSnapshot
8
- } from "./chunk-OK37MIB6.js";
8
+ } from "./chunk-RVXD24D4.js";
9
9
  import {
10
10
  AppServerProcess,
11
11
  buildCodexAgentEnv
12
- } from "./chunk-LY4WTFXS.js";
12
+ } from "./chunk-NGZ6IR76.js";
13
13
  import {
14
14
  SUBPROCESS_MAX_BUFFER,
15
15
  execFileAsync
16
- } from "./chunk-CFLUSFBP.js";
16
+ } from "./chunk-7PXLG7IJ.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-BFHGBJO2.js";
31
+ } from "./chunk-5HLMUJTJ.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-A57BJZPM.js");
267
+ const { restartCodexAspHostIfRunning: restartCodexAspHostIfRunning2 } = await import("./asp-host-5HX6D4TL.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-A57BJZPM.js");
288
+ const { restartCodexAspHostIfRunning: restartCodexAspHostIfRunning2 } = await import("./asp-host-5HX6D4TL.js");
289
289
  await restartCodexAspHostIfRunning2();
290
290
  }
291
291
  setAgentCredentialSnapshot("codex", data);
@@ -1258,7 +1258,8 @@ var sendChatMessageRequestSchema = z3.object({
1258
1258
  senderDisplayName: z3.string().optional(),
1259
1259
  senderAvatarUrl: z3.string().optional(),
1260
1260
  errorNotificationTarget: integrationErrorNotificationTargetSchema.optional(),
1261
- errorNotificationTargets: z3.array(integrationErrorNotificationTargetSchema).optional()
1261
+ errorNotificationTargets: z3.array(integrationErrorNotificationTargetSchema).optional(),
1262
+ streamSlackActivity: z3.boolean().optional()
1262
1263
  }).passthrough();
1263
1264
  function isChatMessageSender(value) {
1264
1265
  return isRecord(value) && (value.messageId === void 0 || typeof value.messageId === "string") && typeof value.senderUserId === "string" && typeof value.senderEmail === "string" && typeof value.recordedAt === "string";
@@ -1548,6 +1549,19 @@ var mcpWorkloadAuthSchema = z4.strictObject({
1548
1549
  type: z4.literal("workspace_identity"),
1549
1550
  audience: workspaceAudienceSchema
1550
1551
  });
1552
+ var mcpAuthSchema = z4.discriminatedUnion("type", [
1553
+ mcpWorkloadAuthSchema,
1554
+ z4.strictObject({ type: z4.literal("oauth") })
1555
+ ]);
1556
+ var engineMcpOAuthTokenRequestSchema = z4.object({
1557
+ environment_mcp_id: z4.string().uuid(),
1558
+ force_refresh: z4.boolean().optional()
1559
+ });
1560
+ var engineMcpOAuthTokenResponseSchema = z4.object({
1561
+ url: z4.string(),
1562
+ access_token: z4.string(),
1563
+ expires_at: z4.string().nullable()
1564
+ });
1551
1565
 
1552
1566
  // ../shared/src/runtime-env.ts
1553
1567
  var REPLICAS_RUNTIME_ENV_ALIASES = {
@@ -2748,15 +2762,30 @@ function createUuidFromHex(value) {
2748
2762
  }
2749
2763
  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
2764
 
2765
+ // ../shared/src/automations/github/index.ts
2766
+ var REVIEW_REQUESTED_EVENT = "pull_request.review_requested";
2767
+
2751
2768
  // ../shared/src/automations/trigger-schemas.ts
2769
+ function getReviewerLoginsError(event, logins) {
2770
+ const isReviewRequested = event === REVIEW_REQUESTED_EVENT;
2771
+ if (isReviewRequested === !!logins?.some((login) => login.trim().replace(/^@+/, ""))) return null;
2772
+ return isReviewRequested ? "The PR review requested trigger needs at least one GitHub username" : "Requested reviewers only apply to the PR review requested trigger";
2773
+ }
2752
2774
  var repositoryTriggerConfigSchema = z10.object({
2753
2775
  event: z10.string(),
2754
2776
  repository_ids: z10.array(z10.string().regex(UUID_RE, "must be a repository id")).optional(),
2755
2777
  group_pr_events: z10.boolean().optional(),
2756
- excluded_actors: z10.array(z10.string()).optional()
2778
+ excluded_actors: z10.array(z10.string()).optional(),
2779
+ reviewer_logins: z10.array(z10.string()).optional()
2757
2780
  });
2758
2781
  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 });
2782
+ var githubTriggerSchema = z10.object({
2783
+ type: z10.literal("github"),
2784
+ config: repositoryTriggerConfigSchema.superRefine((config, ctx) => {
2785
+ const message2 = getReviewerLoginsError(config.event, config.reviewer_logins);
2786
+ if (message2) ctx.addIssue({ code: "custom", message: message2, path: ["reviewer_logins"] });
2787
+ })
2788
+ });
2760
2789
  var gitlabTriggerSchema = z10.object({ type: z10.literal("gitlab"), config: repositoryTriggerConfigSchema });
2761
2790
  var automationTriggerSchema = z10.discriminatedUnion("type", [
2762
2791
  cronTriggerSchema,
@@ -2776,7 +2805,7 @@ var createEnvironmentMcpRequestSchema = z11.object({
2776
2805
  transport: z11.enum(ENVIRONMENT_MCP_TRANSPORTS),
2777
2806
  config: z11.union([
2778
2807
  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() })
2808
+ z11.object({ url: z11.url(), headers: stringMap.optional(), auth: mcpAuthSchema.optional() })
2780
2809
  ]).describe("stdio: `{ command, args, env }`. http/sse: `{ url, headers }`.")
2781
2810
  });
2782
2811
  var updateEnvironmentMcpRequestSchema = createEnvironmentMcpRequestSchema.partial();
@@ -3377,7 +3406,7 @@ Trigger shapes:
3377
3406
  { "type": "cron", "config": { "schedule": "0 9 * * 1-5", "timezone": "UTC" } }
3378
3407
  \`\`\`
3379
3408
 
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.
3409
+ 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
3410
 
3382
3411
  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
3412
 
@@ -6456,6 +6485,11 @@ function parseClaudeEvents(events, parentToolUseId) {
6456
6485
  const completedStreamIds = /* @__PURE__ */ new Set();
6457
6486
  const supersededStreamIds = /* @__PURE__ */ new Set();
6458
6487
  let liveStreamId = null;
6488
+ const endStreamingTools = (exceptStreamId) => {
6489
+ for (const streamId of streamingTools.keys()) {
6490
+ if (streamId !== exceptStreamId) streamingTools.delete(streamId);
6491
+ }
6492
+ };
6459
6493
  let compaction;
6460
6494
  const assistantThinking = /* @__PURE__ */ new Map();
6461
6495
  const assistantTextCounts = /* @__PURE__ */ new Map();
@@ -6509,6 +6543,7 @@ function parseClaudeEvents(events, parentToolUseId) {
6509
6543
  supersededStreamIds.add(liveStreamId);
6510
6544
  }
6511
6545
  liveStreamId = payload.streamId;
6546
+ endStreamingTools(payload.streamId);
6512
6547
  const existing = partialIndexes.get(payload.streamId) ?? {};
6513
6548
  if (payload.thinking && !assistantThinking.has(payload.streamId)) {
6514
6549
  const reasoningMessage = {
@@ -6606,6 +6641,7 @@ function parseClaudeEvents(events, parentToolUseId) {
6606
6641
  }
6607
6642
  const streamRefs = messageId ? partialIndexes.get(messageId) : void 0;
6608
6643
  if (messageId) {
6644
+ endStreamingTools(messageId);
6609
6645
  if (liveStreamId !== null && liveStreamId !== messageId) {
6610
6646
  supersededStreamIds.add(liveStreamId);
6611
6647
  }
@@ -6720,6 +6756,7 @@ function parseClaudeEvents(events, parentToolUseId) {
6720
6756
  }
6721
6757
  });
6722
6758
  }
6759
+ if (event.type === "claude-result" || event.type === CHAT_INTERRUPTED_EVENT_TYPE) endStreamingTools();
6723
6760
  if (compaction && (event.type === "claude-result" || event.type === CHAT_INTERRUPTED_EVENT_TYPE)) {
6724
6761
  compaction.status = "failed";
6725
6762
  compaction.completedAt = event.timestamp;
@@ -9339,6 +9376,8 @@ export {
9339
9376
  chatHistoryPageParamsFromQuery,
9340
9377
  paginateChatHistory,
9341
9378
  coerceBackgroundTaskPayload,
9379
+ planItemsFromMessage,
9380
+ getDisplayMessageActivity,
9342
9381
  fileContentDiff,
9343
9382
  skillNamesFromCommand,
9344
9383
  areUserMessagesWithinMatchWindow,
@@ -9358,6 +9397,7 @@ export {
9358
9397
  workspaceTokenRequestSchema,
9359
9398
  workspaceTokenResponseSchema,
9360
9399
  legacyWorkspaceTokenBundleSchema,
9400
+ engineMcpOAuthTokenResponseSchema,
9361
9401
  SANDBOX_SKILL_DIRS,
9362
9402
  HISTORY_ROOT,
9363
9403
  HISTORY_INDEX_FILENAME,
@@ -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-BFHGBJO2.js";
6
+ } from "./chunk-5HLMUJTJ.js";
7
7
 
8
8
  // src/utils/exec.ts
9
9
  import { exec, execFile } from "child_process";
@@ -5,7 +5,7 @@ import {
5
5
  extractCommandProtectionCommandText,
6
6
  isRecord,
7
7
  parseCodeHostPrUrl
8
- } from "./chunk-BFHGBJO2.js";
8
+ } from "./chunk-5HLMUJTJ.js";
9
9
 
10
10
  // src/services/published-pull-request-urls.ts
11
11
  function publishedPullRequestUrls(result) {
@@ -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-OK37MIB6.js";
6
+ } from "./chunk-RVXD24D4.js";
7
7
  import {
8
8
  SUBPROCESS_MAX_BUFFER,
9
9
  execFileAsync
10
- } from "./chunk-CFLUSFBP.js";
10
+ } from "./chunk-7PXLG7IJ.js";
11
11
  import {
12
12
  extractCommandProtectionCommandText,
13
13
  findCommandProtectionGitLookups,
14
14
  findGitCommitSignals,
15
15
  findPrMergeSignals,
16
16
  parseCommandProtectionRequest
17
- } from "./chunk-BFHGBJO2.js";
17
+ } from "./chunk-5HLMUJTJ.js";
18
18
 
19
19
  // src/services/command-protection-service.ts
20
20
  import { readFile, stat } from "fs/promises";
@@ -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-CFLUSFBP.js";
6
+ } from "./chunk-7PXLG7IJ.js";
7
7
  import {
8
8
  isVersionBelow
9
- } from "./chunk-BFHGBJO2.js";
9
+ } from "./chunk-5HLMUJTJ.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.1028";
243
+ var ENGINE_PACKAGE_VERSION = "0.1.1030";
244
244
  var INITIALIZE_METHOD = "initialize";
245
245
  var INITIALIZED_NOTIFICATION = "initialized";
246
246
  var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
@@ -14,7 +14,7 @@ import {
14
14
  isValidAgentProvider,
15
15
  parsePosixEnvFile,
16
16
  readReplicasRuntimeEnv
17
- } from "./chunk-BFHGBJO2.js";
17
+ } from "./chunk-5HLMUJTJ.js";
18
18
 
19
19
  // src/runtime-env-loader.ts
20
20
  import { readFileSync } from "fs";
@@ -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-AWQZGS2I.js";
7
- import "./chunk-OK37MIB6.js";
8
- import "./chunk-CFLUSFBP.js";
6
+ } from "./chunk-ADUHQPYR.js";
7
+ import "./chunk-RVXD24D4.js";
8
+ import "./chunk-7PXLG7IJ.js";
9
9
  import {
10
10
  isRecord
11
11
  } from "./chunk-UZSNFLDQ.js";
12
- import "./chunk-BFHGBJO2.js";
12
+ import "./chunk-5HLMUJTJ.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-LY4WTFXS.js";
11
- import "./chunk-CFLUSFBP.js";
10
+ } from "./chunk-NGZ6IR76.js";
11
+ import "./chunk-7PXLG7IJ.js";
12
12
  import {
13
13
  AGENT,
14
14
  headlessAgentRequestSchema
15
- } from "./chunk-BFHGBJO2.js";
15
+ } from "./chunk-5HLMUJTJ.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-AWQZGS2I.js";
35
+ } from "./chunk-ADUHQPYR.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-3OGTHVR4.js";
60
+ } from "./chunk-ABUMJRPU.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-DU4QTHOX.js";
74
+ } from "./chunk-27OEXGGC.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-OK37MIB6.js";
82
+ } from "./chunk-RVXD24D4.js";
83
83
  import {
84
84
  AspClient,
85
85
  getCodexModelProvider
86
- } from "./chunk-LY4WTFXS.js";
86
+ } from "./chunk-NGZ6IR76.js";
87
87
  import {
88
88
  SUBPROCESS_MAX_BUFFER,
89
89
  execAsync,
90
90
  execFileAsync
91
- } from "./chunk-CFLUSFBP.js";
91
+ } from "./chunk-7PXLG7IJ.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,
@@ -232,6 +233,7 @@ import {
232
233
  getClaudeModelContextWindow,
233
234
  getDefaultAgentModel,
234
235
  getDefaultOllamaModel,
236
+ getDisplayMessageActivity,
235
237
  getEventStreamId,
236
238
  getEventTimestampMs,
237
239
  getGitHubAccessToken,
@@ -293,6 +295,7 @@ import {
293
295
  parseMcpToolName,
294
296
  parseReplicasConfigString,
295
297
  percentage,
298
+ planItemsFromMessage,
296
299
  raceWithTimeout,
297
300
  readJsonlPage,
298
301
  readOllamaAuthEnv,
@@ -318,7 +321,7 @@ import {
318
321
  workspaceTokenRequestSchema,
319
322
  workspaceTokenResponseSchema,
320
323
  wrapInTag
321
- } from "./chunk-BFHGBJO2.js";
324
+ } from "./chunk-5HLMUJTJ.js";
322
325
  import {
323
326
  cloneMcpConfig,
324
327
  ensureCompatibilityImports,
@@ -9636,6 +9639,41 @@ async function applyInfisicalCredentials(data, paths) {
9636
9639
  }
9637
9640
  var infisicalTokenManager = new InfisicalTokenManager();
9638
9641
 
9642
+ // src/token-cache.ts
9643
+ var TokenCache = class {
9644
+ constructor(maxEntries = Infinity) {
9645
+ this.maxEntries = maxEntries;
9646
+ }
9647
+ maxEntries;
9648
+ cache = /* @__PURE__ */ new Map();
9649
+ inflight = /* @__PURE__ */ new Map();
9650
+ get(key, load, force = false) {
9651
+ const cached = this.cache.get(key);
9652
+ if (!force && cached && cached.refreshAt > Date.now()) return Promise.resolve(createSuccessResult(cached.token));
9653
+ const inflightKey = force ? `${key}\0force` : key;
9654
+ let pending = this.inflight.get(inflightKey);
9655
+ if (!pending) {
9656
+ pending = load().then((result) => {
9657
+ this.cache.delete(key);
9658
+ if (!result.ok) return result;
9659
+ const oldest = this.cache.keys().next();
9660
+ if (this.cache.size >= this.maxEntries && !oldest.done) this.cache.delete(oldest.value);
9661
+ this.cache.set(key, result.data);
9662
+ return createSuccessResult(result.data.token);
9663
+ }).finally(() => this.inflight.delete(inflightKey));
9664
+ this.inflight.set(inflightKey, pending);
9665
+ }
9666
+ return pending;
9667
+ }
9668
+ };
9669
+ async function requestToken(request, path7, body, failureMessage) {
9670
+ try {
9671
+ return createSuccessResult(await request(path7, { body }));
9672
+ } catch (error) {
9673
+ return createErrorResult({ message: error instanceof Error ? error.message : failureMessage, code: 503 });
9674
+ }
9675
+ }
9676
+
9639
9677
  // src/workspace-identity-tokens.ts
9640
9678
  var MIN_REMAINING_MS = 5 * 60 * 1e3;
9641
9679
  var MAX_CACHED_AUDIENCES = 32;
@@ -9644,30 +9682,19 @@ var WorkspaceIdentityTokens = class {
9644
9682
  this.request = request;
9645
9683
  }
9646
9684
  request;
9647
- cache = /* @__PURE__ */ new Map();
9648
- inflight = /* @__PURE__ */ new Map();
9685
+ tokens = new TokenCache(MAX_CACHED_AUDIENCES);
9649
9686
  async get(audience = WORKSPACE_IDENTITY_AUDIENCE, awsSourceIdentity = false) {
9650
9687
  const parsed = workspaceAudienceSchema.safeParse(audience);
9651
9688
  if (!parsed.success) {
9652
9689
  return createErrorResult({ message: parsed.error.issues[0]?.message ?? "Invalid audience", code: 400 });
9653
9690
  }
9654
9691
  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;
9692
+ return this.tokens.get(key, () => this.mint(audience, awsSourceIdentity));
9663
9693
  }
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
- }
9694
+ async mint(audience, awsSourceIdentity) {
9695
+ const requested = await requestToken(this.request, "/v1/engine/workspace-identity/token", { audience, aws_source_identity: awsSourceIdentity }, "Workspace identity request failed");
9696
+ if (!requested.ok) return requested;
9697
+ const response = requested.data;
9671
9698
  if (!response.ok) {
9672
9699
  return createErrorResult({ message: `Workspace identity request failed: ${response.status} ${await response.text()}`, code: response.status === 400 ? 400 : 503 });
9673
9700
  }
@@ -9687,11 +9714,7 @@ var WorkspaceIdentityTokens = class {
9687
9714
  return createErrorResult({ message: "AWS source identity is unavailable from this Replicas version", code: 503 });
9688
9715
  }
9689
9716
  }
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);
9717
+ return createSuccessResult({ token, refreshAt: Date.parse(token.expires_at) - MIN_REMAINING_MS });
9695
9718
  }
9696
9719
  };
9697
9720
  var workspaceIdentityTokens = new WorkspaceIdentityTokens();
@@ -9729,27 +9752,85 @@ function resolveIdentityRoute(requestUrl) {
9729
9752
  target.search = url3.search;
9730
9753
  return { target, audience: decode(encodedAudience) };
9731
9754
  }
9732
- function readJsonBody(request) {
9755
+ function resolveMcpOAuthRoute(requestUrl) {
9756
+ const url3 = new URL(requestUrl, "http://127.0.0.1");
9757
+ const [, kind, environmentMcpId, ...rest] = url3.pathname.split("/");
9758
+ return kind === "mcp-oauth" && environmentMcpId ? { environmentMcpId, rest, search: url3.search } : null;
9759
+ }
9760
+ var MAX_MCP_OAUTH_BODY_BYTES = 10 * 1024 * 1024;
9761
+ function readBody(request, maxBytes) {
9733
9762
  return new Promise((resolve7) => {
9734
9763
  const chunks = [];
9735
9764
  let size = 0;
9736
9765
  request.on("data", (chunk) => {
9737
9766
  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
- }
9767
+ if (size > maxBytes) resolve7("too_large");
9768
+ else chunks.push(chunk);
9748
9769
  });
9770
+ request.on("end", () => resolve7(Buffer.concat(chunks)));
9749
9771
  request.on("error", () => resolve7(null));
9750
9772
  });
9751
9773
  }
9752
- function createIdentityProxy(tokens) {
9774
+ function forward(request, response, target, token, onResponse = (incoming) => pipeResponse(incoming, response)) {
9775
+ const headers = { host: target.host };
9776
+ for (const [name, value] of Object.entries(request.headers)) {
9777
+ if (!STRIPPED_HEADERS.has(name) && !name.startsWith("x-replicas-") && value !== void 0) headers[name] = value;
9778
+ }
9779
+ headers.authorization = `Bearer ${token}`;
9780
+ const upstream = (target.protocol === "https:" ? https : http).request(target, { method: request.method, headers }, onResponse);
9781
+ upstream.on("error", () => {
9782
+ if (!response.headersSent) response.writeHead(502);
9783
+ response.end();
9784
+ });
9785
+ response.on("close", () => upstream.destroy());
9786
+ return upstream;
9787
+ }
9788
+ function pipeResponse(incoming, response) {
9789
+ response.writeHead(incoming.statusCode ?? 502, incoming.headers);
9790
+ incoming.pipe(response);
9791
+ response.on("close", () => incoming.destroy());
9792
+ }
9793
+ async function proxyMcpOAuth(request, response, tokens, route) {
9794
+ const body = await readBody(request, MAX_MCP_OAUTH_BODY_BYTES);
9795
+ if (body === "too_large") {
9796
+ response.writeHead(413, { connection: "close" }).end();
9797
+ return;
9798
+ }
9799
+ if (!body) {
9800
+ response.writeHead(400).end();
9801
+ return;
9802
+ }
9803
+ const send = async (forceRefresh) => {
9804
+ const token = await tokens.get(route.environmentMcpId, forceRefresh);
9805
+ if (!token.ok) {
9806
+ response.writeHead(token.error.code ?? 503).end(token.error.message);
9807
+ return;
9808
+ }
9809
+ const target = new URL(token.data.url);
9810
+ if (route.rest.length) target.pathname = [target.pathname.replace(/\/$/, ""), ...route.rest].join("/");
9811
+ target.search = route.search;
9812
+ forward(request, response, target, token.data.access_token, (incoming) => {
9813
+ if (incoming.statusCode === 401 && !forceRefresh) {
9814
+ incoming.resume();
9815
+ void send(true);
9816
+ return;
9817
+ }
9818
+ pipeResponse(incoming, response);
9819
+ }).end(body);
9820
+ };
9821
+ await send(false);
9822
+ }
9823
+ async function readJsonBody(request) {
9824
+ const body = await readBody(request, MAX_TOKEN_REQUEST_BYTES);
9825
+ if (!body || body === "too_large") return null;
9826
+ const text = body.toString("utf8").trim();
9827
+ try {
9828
+ return text ? JSON.parse(text) : {};
9829
+ } catch {
9830
+ return null;
9831
+ }
9832
+ }
9833
+ function createIdentityProxy(tokens, mcpOAuthTokens2) {
9753
9834
  return http.createServer(async (request, response) => {
9754
9835
  const url3 = new URL(request.url ?? "/", "http://127.0.0.1");
9755
9836
  if (url3.pathname === IDENTITY_TOKEN_PATH) {
@@ -9767,6 +9848,11 @@ function createIdentityProxy(tokens) {
9767
9848
  response.end(JSON.stringify(result.ok ? result.data : { error: result.error.message }));
9768
9849
  return;
9769
9850
  }
9851
+ const mcpOAuthRoute = resolveMcpOAuthRoute(request.url ?? "/");
9852
+ if (mcpOAuthRoute) {
9853
+ await proxyMcpOAuth(request, response, mcpOAuthTokens2, mcpOAuthRoute);
9854
+ return;
9855
+ }
9770
9856
  const route = resolveIdentityRoute(request.url ?? "/");
9771
9857
  if (!route) {
9772
9858
  response.writeHead(404).end("Unknown identity route");
@@ -9777,29 +9863,46 @@ function createIdentityProxy(tokens) {
9777
9863
  response.writeHead(token.error.code ?? 503).end(token.error.message);
9778
9864
  return;
9779
9865
  }
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);
9866
+ request.pipe(forward(request, response, route.target, token.data.token));
9800
9867
  });
9801
9868
  }
9802
9869
 
9870
+ // src/mcp-oauth-tokens.ts
9871
+ var MIN_REMAINING_MS2 = 60 * 1e3;
9872
+ var MAX_CACHE_MS = 5 * 60 * 1e3;
9873
+ var McpOAuthTokens = class {
9874
+ constructor(request = monolithRequest) {
9875
+ this.request = request;
9876
+ }
9877
+ request;
9878
+ tokens = new TokenCache();
9879
+ get(environmentMcpId, forceRefresh = false) {
9880
+ return this.tokens.get(environmentMcpId, () => this.fetch(environmentMcpId, forceRefresh), forceRefresh);
9881
+ }
9882
+ async fetch(environmentMcpId, forceRefresh) {
9883
+ const requested = await requestToken(
9884
+ this.request,
9885
+ "/v1/engine/mcp-oauth/token",
9886
+ { environment_mcp_id: environmentMcpId, force_refresh: forceRefresh },
9887
+ "MCP OAuth token request failed"
9888
+ );
9889
+ if (!requested.ok) return requested;
9890
+ const response = requested.data;
9891
+ const data = await response.json().catch(() => null);
9892
+ const parsed = engineMcpOAuthTokenResponseSchema.safeParse(data);
9893
+ if (!response.ok || !parsed.success) {
9894
+ const message = isRecord(data) && typeof data.error === "string" ? data.error : `MCP OAuth token request failed: ${response.status}`;
9895
+ return createErrorResult({ message, code: response.ok ? 502 : response.status });
9896
+ }
9897
+ const remaining = parsed.data.expires_at ? Date.parse(parsed.data.expires_at) - Date.now() : Infinity;
9898
+ return createSuccessResult({
9899
+ token: parsed.data,
9900
+ refreshAt: Date.now() + Math.min(Math.max(remaining - MIN_REMAINING_MS2, remaining / 2), MAX_CACHE_MS)
9901
+ });
9902
+ }
9903
+ };
9904
+ var mcpOAuthTokens = new McpOAuthTokens();
9905
+
9803
9906
  // src/lifecycle.ts
9804
9907
  import { readFileSync, rmSync, writeFileSync } from "fs";
9805
9908
  var CLAIM_FILE = "/tmp/replicas-engine.claim";
@@ -50655,7 +50758,7 @@ data: ${JSON.stringify(payload)}
50655
50758
  }
50656
50759
  async function parseBody(req, res) {
50657
50760
  try {
50658
- const body = await readBody(req);
50761
+ const body = await readBody2(req);
50659
50762
  if (!body || typeof body !== "object") {
50660
50763
  sendJson(res, 400, { ok: false, error: "Invalid request body" });
50661
50764
  return null;
@@ -50666,7 +50769,7 @@ async function parseBody(req, res) {
50666
50769
  return null;
50667
50770
  }
50668
50771
  }
50669
- function readBody(req) {
50772
+ function readBody2(req) {
50670
50773
  return new Promise((resolve7, reject) => {
50671
50774
  let size = 0;
50672
50775
  const chunks = [];
@@ -53623,9 +53726,9 @@ function Clone2(value) {
53623
53726
  // ../node_modules/.bun/typebox@1.3.7/node_modules/typebox/build/value/clean/from_union.mjs
53624
53727
  function FromUnion9(context, type, value) {
53625
53728
  for (const schema of type.anyOf) {
53626
- const clean = FromType19(context, schema, Clone2(value));
53627
- if (Check2(context, schema, clean))
53628
- return clean;
53729
+ const clean2 = FromType19(context, schema, Clone2(value));
53730
+ if (Check2(context, schema, clean2))
53731
+ return clean2;
53629
53732
  }
53630
53733
  return value;
53631
53734
  }
@@ -60531,6 +60634,106 @@ async function buildForkHandoff(input) {
60531
60634
  };
60532
60635
  }
60533
60636
 
60637
+ // src/services/chat/slack-activity-stream.ts
60638
+ var FLUSH_INTERVAL_MS = 3e3;
60639
+ var MAX_ACTIVITY_LENGTH = 300;
60640
+ var MAX_DONE_ATTEMPTS = 3;
60641
+ var sendQueue = Promise.resolve();
60642
+ var truncate = (text, max) => text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
60643
+ var clean = (text) => truncate(text.replace(/\s+/g, " ").trim(), MAX_ACTIVITY_LENGTH);
60644
+ function describeStep(message) {
60645
+ const plan = planItemsFromMessage(message);
60646
+ if (plan) {
60647
+ const current = plan.find((item) => item.status === "in_progress");
60648
+ return plan.length ? `Plan ${plan.filter((item) => item.status === "completed").length}/${plan.length}${current ? ` \xB7 ${current.text}` : ""}` : null;
60649
+ }
60650
+ if (message.type === "tool_call") {
60651
+ const input = summarizeInput(message.input);
60652
+ return input ? `${toolAction(message.tool)}: ${input}` : toolAction(message.tool);
60653
+ }
60654
+ const activity = getDisplayMessageActivity(message);
60655
+ if (!activity || activity.kind === "error") return null;
60656
+ return (activity.summary ? `${activity.title}: ${activity.summary}` : activity.title).trim().split("\n")[0];
60657
+ }
60658
+ var SlackActivityStream = class {
60659
+ constructor(turnId, provider) {
60660
+ this.turnId = turnId;
60661
+ this.provider = provider;
60662
+ this.timer = setInterval(() => this.enqueue(false), FLUSH_INTERVAL_MS);
60663
+ this.timer.unref?.();
60664
+ }
60665
+ turnId;
60666
+ provider;
60667
+ events = [];
60668
+ timer;
60669
+ sent = null;
60670
+ attempted = false;
60671
+ dirty = false;
60672
+ stopped = false;
60673
+ queued = false;
60674
+ noteEvent(event) {
60675
+ if (event.type === CLAUDE_PARTIAL_MESSAGE_EVENT_TYPE) return;
60676
+ const partialType = event.type === CODEX_ITEM_PARTIAL_EVENT_TYPE ? codexItemFromEvent(event)?.item.type : void 0;
60677
+ if (partialType === "agentMessage" || partialType === "reasoning") return;
60678
+ const streamId = getEventStreamId(event);
60679
+ const index = streamId ? this.events.findLastIndex((previous) => getEventStreamId(previous) === streamId) : -1;
60680
+ if (index >= 0) this.events[index] = event;
60681
+ else this.events.push(event);
60682
+ this.dirty = true;
60683
+ }
60684
+ finish() {
60685
+ clearInterval(this.timer);
60686
+ this.enqueue(true);
60687
+ }
60688
+ enqueue(done) {
60689
+ if (!done && this.queued) return;
60690
+ this.queued = true;
60691
+ sendQueue = sendQueue.then(async () => {
60692
+ this.queued = false;
60693
+ for (let attempt = 1; !await this.flush(done) && done && attempt < MAX_DONE_ATTEMPTS; attempt++) {
60694
+ await new Promise((resolve7) => setTimeout(resolve7, FLUSH_INTERVAL_MS));
60695
+ }
60696
+ }).catch((error) => console.error("[SlackActivityStream] Flush failed:", error));
60697
+ }
60698
+ async flush(done) {
60699
+ if (this.stopped) return true;
60700
+ if (done && !this.attempted) {
60701
+ this.stopped = true;
60702
+ return true;
60703
+ }
60704
+ let activity = {};
60705
+ if (!done) {
60706
+ if (!this.dirty) return true;
60707
+ this.dirty = false;
60708
+ const messages = parseDisplayMessages(this.events, this.provider);
60709
+ const thought = messages.map((message) => message.type === "agent" || message.type === "reasoning" ? message.content : "").findLast((text) => text.trim());
60710
+ const step = messages.map(describeStep).findLast(Boolean);
60711
+ activity = {
60712
+ ...thought ? { thought: clean(thought) } : {},
60713
+ ...step ? { step: clean(step) } : {}
60714
+ };
60715
+ if (!activity.thought && !activity.step || JSON.stringify(activity) === this.sent) return true;
60716
+ }
60717
+ this.attempted = true;
60718
+ const event = {
60719
+ type: "slack_activity",
60720
+ payload: { turnId: this.turnId, ...done ? { done } : activity }
60721
+ };
60722
+ const response = await monolithRequest("/v1/engine/webhook", { body: event, signal: AbortSignal.timeout(15e3) }).catch(() => null);
60723
+ if (response?.status === 409) {
60724
+ this.stopped = true;
60725
+ return true;
60726
+ }
60727
+ if (!response?.ok) {
60728
+ this.dirty = true;
60729
+ return false;
60730
+ }
60731
+ this.sent = JSON.stringify(activity);
60732
+ if (done) this.stopped = true;
60733
+ return true;
60734
+ }
60735
+ };
60736
+
60534
60737
  // src/services/chat/chat-service.ts
60535
60738
  var CHATS_BACKUP_FILE = `${CHATS_FILE}.bak`;
60536
60739
  function createTurnMetadata(message, messageId, recordedAt) {
@@ -60993,6 +61196,9 @@ var ChatService = class {
60993
61196
  ...request.errorNotificationTargets ?? [],
60994
61197
  ...request.errorNotificationTarget ? [request.errorNotificationTarget] : []
60995
61198
  ];
61199
+ if (request.streamSlackActivity && chat.persisted.parentChatId === null) {
61200
+ chat.slackActivityMessageIds.add(result.messageId);
61201
+ }
60996
61202
  if (notificationTargets.length > 0) {
60997
61203
  const existingTargets = chat.errorNotificationTargets.get(result.messageId) ?? [];
60998
61204
  chat.errorNotificationTargets.set(result.messageId, [
@@ -61135,6 +61341,7 @@ var ChatService = class {
61135
61341
  for (const messageId of removed) {
61136
61342
  chat.acceptedUserEvents.delete(messageId);
61137
61343
  chat.errorNotificationTargets.delete(messageId);
61344
+ chat.slackActivityMessageIds.delete(messageId);
61138
61345
  }
61139
61346
  await this.persistAllChats().catch((error) => {
61140
61347
  console.error("[ChatService] Failed to persist queued-message removals:", error);
@@ -61240,6 +61447,9 @@ var ChatService = class {
61240
61447
  target.pendingMessageIds = [];
61241
61448
  target.acceptedUserEvents.clear();
61242
61449
  }
61450
+ target.slackActivity?.finish();
61451
+ target.slackActivity = null;
61452
+ target.slackActivityMessageIds.clear();
61243
61453
  if (hasChatStarted(this.toSummary(target))) {
61244
61454
  target.persisted.deletedAt = deletedAt;
61245
61455
  } else {
@@ -61661,6 +61871,8 @@ var ChatService = class {
61661
61871
  settledProcessing: null,
61662
61872
  errorNotificationTargets: /* @__PURE__ */ new Map(),
61663
61873
  activeErrorNotificationTargets: [],
61874
+ slackActivityMessageIds: /* @__PURE__ */ new Set(),
61875
+ slackActivity: null,
61664
61876
  quotaExhaustion: null,
61665
61877
  lastSendRequest: null
61666
61878
  };
@@ -61715,6 +61927,8 @@ var ChatService = class {
61715
61927
  this.agentTokenUsageTracker.startTurn(context);
61716
61928
  chat.activeErrorNotificationTargets = chat.errorNotificationTargets.get(messageId) ?? [];
61717
61929
  chat.errorNotificationTargets.delete(messageId);
61930
+ chat.slackActivity?.finish();
61931
+ chat.slackActivity = chat.slackActivityMessageIds.delete(messageId) ? new SlackActivityStream(messageId, chat.persisted.provider) : null;
61718
61932
  }
61719
61933
  async handleTurnStarted(chatId, message) {
61720
61934
  const chat = this.getRuntimeChat(chatId);
@@ -61764,6 +61978,7 @@ var ChatService = class {
61764
61978
  });
61765
61979
  }
61766
61980
  this.agentTokenUsageTracker.noteAgentEvent(chatId, event);
61981
+ chat.slackActivity?.noteEvent(event);
61767
61982
  const parentToolUseId = typeof event.payload.parent_tool_use_id === "string" ? event.payload.parent_tool_use_id : null;
61768
61983
  const messages = parseDisplayMessages([event], chat.persisted.provider, { filter: false, parentToolUseId });
61769
61984
  if (chat.hasActiveTurn) this.agentChatActivityTrackerService.noteSkillMcpCalls(chatId, messages);
@@ -61854,6 +62069,8 @@ var ChatService = class {
61854
62069
  }
61855
62070
  chat.hasActiveTurn = false;
61856
62071
  chat.activeMessageId = null;
62072
+ chat.slackActivity?.finish();
62073
+ chat.slackActivity = null;
61857
62074
  this.endTrackedTurn(chatId);
61858
62075
  const completedAt = (/* @__PURE__ */ new Date()).toISOString();
61859
62076
  const processing = chat.provider.getQueue().length > 0 || (chat.provider.hasActiveBackgroundTasks?.() ?? false);
@@ -64677,7 +64894,7 @@ async function startBackgroundServices(signal) {
64677
64894
  heartbeatService.start(bootTimeMs);
64678
64895
  if (!identityProxyStarted) {
64679
64896
  identityProxyStarted = true;
64680
- createIdentityProxy(workspaceIdentityTokens).on("error", (error) => console.error("[identity-proxy] failed to listen:", error)).listen(IDENTITY_PROXY_PORT, "127.0.0.1");
64897
+ createIdentityProxy(workspaceIdentityTokens, mcpOAuthTokens).on("error", (error) => console.error("[identity-proxy] failed to listen:", error)).listen(IDENTITY_PROXY_PORT, "127.0.0.1");
64681
64898
  }
64682
64899
  for (const manager of tokenManagers.slice(1)) {
64683
64900
  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-3OGTHVR4.js";
6
+ } from "./chunk-ABUMJRPU.js";
7
7
  import {
8
8
  isRecord
9
9
  } from "./chunk-UZSNFLDQ.js";
10
10
  import {
11
11
  shouldRefreshPullRequests
12
- } from "./chunk-BFHGBJO2.js";
12
+ } from "./chunk-5HLMUJTJ.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-BFHGBJO2.js";
12
+ } from "./chunk-5HLMUJTJ.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.1028",
3
+ "version": "0.1.1030",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",