replicas-engine 0.1.851 → 0.1.852

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -33,6 +33,17 @@ Chats:
33
33
  - `POST /chats/:chatId/messages`
34
34
  - `POST /chats/:chatId/interrupt`
35
35
 
36
+ Relay subagents:
37
+
38
+ - `GET /chats/:parentChatId/subagents`
39
+ - `POST /chats/:parentChatId/subagents`
40
+ - `POST /chats/:parentChatId/subagents/:subagentId/messages`
41
+ - `GET /chats/:parentChatId/subagents/:subagentId`
42
+ - `GET /chats/:parentChatId/subagents/:subagentId/wait?timeoutMinutes=<minutes>` (SSE)
43
+ - `DELETE /chats/:parentChatId/subagents/:subagentId`
44
+
45
+ The `subagent_relay` MCP server exposes child work through `spawn_agent`, `message_agent`, `get_agent`, and `wait_agent`. Spawn and message calls wait by default; set `blocking: false` to return the child chat ID immediately, then use `get_agent` or `wait_agent` later. A wait timeout returns `status: "working"` with `timedOut: true` without interrupting the child; completed work returns `status: "completed"` and its response.
46
+
36
47
  Canvas:
37
48
 
38
49
  - `GET /canvas`
@@ -5,11 +5,11 @@ import {
5
5
  getCodexAspHost,
6
6
  restartCodexAspHost,
7
7
  restartCodexAspHostIfRunning
8
- } from "./chunk-ZMZB2VED.js";
9
- import "./chunk-Q5TVOQAA.js";
10
- import "./chunk-MKFFAP4D.js";
8
+ } from "./chunk-HPMBSWPV.js";
9
+ import "./chunk-KCTWAJQL.js";
10
+ import "./chunk-WABXMCX5.js";
11
11
  import "./chunk-UZSNFLDQ.js";
12
- import "./chunk-FEV3BCGC.js";
12
+ import "./chunk-ICQTYE55.js";
13
13
  import "./chunk-VEQXQN22.js";
14
14
  export {
15
15
  getCodexAspHost,
@@ -3,12 +3,12 @@ import { createRequire as __createRequire } from 'node:module';
3
3
  const require = __createRequire(import.meta.url);
4
4
  import {
5
5
  monolithRequest
6
- } from "./chunk-Q5TVOQAA.js";
6
+ } from "./chunk-KCTWAJQL.js";
7
7
  import {
8
8
  extractCommandProtectionCommandText,
9
9
  findGitCommitSignals,
10
10
  findPrMergeSignals
11
- } from "./chunk-FEV3BCGC.js";
11
+ } from "./chunk-ICQTYE55.js";
12
12
 
13
13
  // src/services/command-protection-service.ts
14
14
  var DEFAULT_COMMAND_PROTECTION_BLOCK_MESSAGE = "Blocked by Replicas command protection.";
@@ -5,13 +5,13 @@ import {
5
5
  ENGINE_ENV,
6
6
  monolithRequest,
7
7
  setAgentCredentialSnapshot
8
- } from "./chunk-Q5TVOQAA.js";
8
+ } from "./chunk-KCTWAJQL.js";
9
9
  import {
10
10
  AppServerProcess,
11
11
  SUBPROCESS_MAX_BUFFER,
12
12
  buildCodexAgentEnv,
13
13
  execFileAsync
14
- } from "./chunk-MKFFAP4D.js";
14
+ } from "./chunk-WABXMCX5.js";
15
15
  import {
16
16
  isRecord
17
17
  } from "./chunk-UZSNFLDQ.js";
@@ -23,7 +23,7 @@ import {
23
23
  createSuccessResult,
24
24
  listOpenAICompatibleModels,
25
25
  resolveOpenAICompatibleModelsUrl
26
- } from "./chunk-FEV3BCGC.js";
26
+ } from "./chunk-ICQTYE55.js";
27
27
 
28
28
  // src/managers/codex-token-manager.ts
29
29
  import { promises as fs } from "fs";
@@ -244,7 +244,7 @@ var CodexTokenManager = class extends BaseRefreshManager {
244
244
  await this.applyCredentialUpdate(++this.credentialGeneration, async () => {
245
245
  await this.applyCredentialsResponse(data);
246
246
  if (data.scope) setAgentCredentialSnapshot("codex", { method: data.type, scope: data.scope, revision: data.revision });
247
- const { restartCodexAspHostIfRunning: restartCodexAspHostIfRunning2 } = await import("./asp-host-JLTBSIYU.js");
247
+ const { restartCodexAspHostIfRunning: restartCodexAspHostIfRunning2 } = await import("./asp-host-BTHMIOWF.js");
248
248
  await restartCodexAspHostIfRunning2();
249
249
  });
250
250
  await this.start(true);
@@ -264,7 +264,7 @@ var CodexTokenManager = class extends BaseRefreshManager {
264
264
  await this.applyCredentialUpdate(generation, async () => {
265
265
  await this.applyCredentialsResponse(data);
266
266
  if (restartOnChange && CODEX_AUTH_ENV_KEYS.some((key, index) => process.env[key] !== previousEnv[index])) {
267
- const { restartCodexAspHostIfRunning: restartCodexAspHostIfRunning2 } = await import("./asp-host-JLTBSIYU.js");
267
+ const { restartCodexAspHostIfRunning: restartCodexAspHostIfRunning2 } = await import("./asp-host-BTHMIOWF.js");
268
268
  await restartCodexAspHostIfRunning2();
269
269
  }
270
270
  if (data.scope) {
@@ -992,14 +992,21 @@ var spawnRelaySubagentRequestSchema = z3.object({
992
992
  ).default("all"),
993
993
  model: z3.string().min(1).optional(),
994
994
  thinkingLevel: z3.enum(VALID_THINKING_LEVELS).optional(),
995
- title: z3.string().min(1).optional(),
996
- timeoutMinutes: z3.number().positive().max(1440).optional()
995
+ title: z3.string().min(1).optional()
997
996
  });
998
997
  var messageRelaySubagentRequestSchema = z3.object({
999
998
  message: z3.string().min(1),
1000
999
  model: z3.string().min(1).optional(),
1001
- thinkingLevel: z3.enum(VALID_THINKING_LEVELS).optional(),
1002
- timeoutMinutes: z3.number().positive().max(1440).optional()
1000
+ thinkingLevel: z3.enum(VALID_THINKING_LEVELS).optional()
1001
+ });
1002
+ var waitRelaySubagentRequestSchema = z3.object({
1003
+ timeoutMinutes: z3.coerce.number().positive().max(1440).optional()
1004
+ });
1005
+ var relaySubagentResponseSchema = z3.object({
1006
+ chatId: z3.string().uuid(),
1007
+ status: z3.enum(["working", "completed"]),
1008
+ response: z3.string().optional(),
1009
+ timedOut: z3.literal(true).optional()
1003
1010
  });
1004
1011
  var sendChatMessageRequestSchema = z3.object({
1005
1012
  messageId: z3.string().min(1).optional(),
@@ -1555,6 +1562,47 @@ var headlessFilesystemAgentResultSchema = z4.object({
1555
1562
  // ../shared/src/engine/environment.ts
1556
1563
  var DESKTOP_STREAM_PORT = 6080;
1557
1564
 
1565
+ // ../shared/src/sse.ts
1566
+ function parseSseChunk(chunk) {
1567
+ let data = "";
1568
+ let id = null;
1569
+ for (const line of chunk.split("\n")) {
1570
+ if (line.startsWith("data: ")) {
1571
+ data += line.slice(6);
1572
+ } else if (line.startsWith("id: ")) {
1573
+ id = line.slice(4);
1574
+ }
1575
+ }
1576
+ if (!data) return null;
1577
+ try {
1578
+ return { event: JSON.parse(data), id };
1579
+ } catch {
1580
+ return null;
1581
+ }
1582
+ }
1583
+ async function readSseStream(body, onEvent, shouldContinue = () => true) {
1584
+ const reader = body.getReader();
1585
+ const decoder2 = new TextDecoder();
1586
+ let buffer = "";
1587
+ try {
1588
+ while (shouldContinue()) {
1589
+ const { done, value } = await reader.read();
1590
+ if (done) break;
1591
+ buffer += decoder2.decode(value, { stream: true });
1592
+ const chunks = buffer.split("\n\n");
1593
+ buffer = chunks.pop() ?? "";
1594
+ for (const chunk of chunks) {
1595
+ if (!shouldContinue()) break;
1596
+ const parsed = parseSseChunk(chunk);
1597
+ if (!parsed) continue;
1598
+ await onEvent(parsed.event, parsed.id);
1599
+ }
1600
+ }
1601
+ } finally {
1602
+ await reader.cancel().catch(() => void 0);
1603
+ }
1604
+ }
1605
+
1558
1606
  // ../shared/src/json.ts
1559
1607
  function safeJsonParse(str, fallback) {
1560
1608
  try {
@@ -7938,6 +7986,8 @@ export {
7938
7986
  createChatRequestSchema,
7939
7987
  spawnRelaySubagentRequestSchema,
7940
7988
  messageRelaySubagentRequestSchema,
7989
+ waitRelaySubagentRequestSchema,
7990
+ relaySubagentResponseSchema,
7941
7991
  sendChatMessageRequestSchema,
7942
7992
  isChatMessageSender,
7943
7993
  normalizeCodexAspTranscriptStatus,
@@ -8051,5 +8101,6 @@ export {
8051
8101
  getChatTranscriptHistorySize,
8052
8102
  createChatTranscriptPages,
8053
8103
  parseChatTranscriptArtifact,
8104
+ readSseStream,
8054
8105
  isSkillRegistryManifest
8055
8106
  };
@@ -7,7 +7,15 @@ import { existsSync } from "fs";
7
7
  import { dirname, join } from "path";
8
8
  import { fileURLToPath } from "url";
9
9
  import { createRequire } from "module";
10
- var RELAY_MCP_TOOL_NAMES = ["get_providers", "spawn_agent", "message_agent", "delete_agent"];
10
+ var RELAY_MCP_TOOLS = {
11
+ getProviders: "get_providers",
12
+ spawnAgent: "spawn_agent",
13
+ getAgent: "get_agent",
14
+ waitAgent: "wait_agent",
15
+ messageAgent: "message_agent",
16
+ deleteAgent: "delete_agent"
17
+ };
18
+ var RELAY_MCP_TOOL_NAMES = Object.values(RELAY_MCP_TOOLS);
11
19
  var RELAY_MCP_TOOL_TIMEOUT_MS = 24 * 60 * 60 * 1e3 + 6e4;
12
20
  function relayMcpProcessConfig(parentChatId) {
13
21
  const directory = dirname(fileURLToPath(import.meta.url));
@@ -37,6 +45,7 @@ function relayCodexMcpConfig(parentChatId) {
37
45
  }
38
46
 
39
47
  export {
48
+ RELAY_MCP_TOOLS,
40
49
  RELAY_MCP_TOOL_NAMES,
41
50
  RELAY_MCP_TOOL_TIMEOUT_MS,
42
51
  relayMcpProcessConfig,
@@ -15,7 +15,7 @@ import {
15
15
  isValidRelayBaseProvider,
16
16
  parsePosixEnvFile,
17
17
  readReplicasRuntimeEnv
18
- } from "./chunk-FEV3BCGC.js";
18
+ } from "./chunk-ICQTYE55.js";
19
19
 
20
20
  // src/engine-env.ts
21
21
  import { readFileSync as readFileSync2 } from "fs";
@@ -4,7 +4,7 @@ const require = __createRequire(import.meta.url);
4
4
  import {
5
5
  HOOK_EXEC_MAX_BUFFER_BYTES,
6
6
  isVersionBelow
7
- } from "./chunk-FEV3BCGC.js";
7
+ } from "./chunk-ICQTYE55.js";
8
8
 
9
9
  // src/utils/codex-agent-env.ts
10
10
  function buildCodexAgentEnv(source = process.env) {
@@ -241,7 +241,7 @@ var DEFAULT_CODEX_ARGS = [
241
241
  var MIN_CODEX_CLI_VERSION = "0.153.3";
242
242
  var CODEX_UPGRADE_TIMEOUT_MS = 12e4;
243
243
  var codexCliVersionEnsured = null;
244
- var ENGINE_PACKAGE_VERSION = "0.1.851";
244
+ var ENGINE_PACKAGE_VERSION = "0.1.852";
245
245
  var INITIALIZE_METHOD = "initialize";
246
246
  var INITIALIZED_NOTIFICATION = "initialized";
247
247
  var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
@@ -3,7 +3,7 @@ import { createRequire as __createRequire } from 'node:module';
3
3
  const require = __createRequire(import.meta.url);
4
4
  import {
5
5
  shouldRefreshPullRequests
6
- } from "./chunk-FEV3BCGC.js";
6
+ } from "./chunk-ICQTYE55.js";
7
7
 
8
8
  // src/services/post-tool-pr-notifier.ts
9
9
  async function notifyPostToolUse(toolName, toolInput) {
@@ -3,12 +3,12 @@ import { createRequire as __createRequire } from 'node:module';
3
3
  const require = __createRequire(import.meta.url);
4
4
  import {
5
5
  evaluateCommandProtection
6
- } from "./chunk-IVALOOVU.js";
7
- import "./chunk-Q5TVOQAA.js";
6
+ } from "./chunk-FCGE52IS.js";
7
+ import "./chunk-KCTWAJQL.js";
8
8
  import {
9
9
  isRecord
10
10
  } from "./chunk-UZSNFLDQ.js";
11
- import "./chunk-FEV3BCGC.js";
11
+ import "./chunk-ICQTYE55.js";
12
12
  import "./chunk-VEQXQN22.js";
13
13
 
14
14
  // src/command-protection-hook.ts
@@ -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-IVALOOVU.js";
6
+ } from "./chunk-FCGE52IS.js";
7
7
  import {
8
8
  notifyPostToolUse
9
- } from "./chunk-MTSI4DXY.js";
10
- import "./chunk-Q5TVOQAA.js";
9
+ } from "./chunk-XZ56DHQ7.js";
10
+ import "./chunk-KCTWAJQL.js";
11
11
  import "./chunk-UZSNFLDQ.js";
12
- import "./chunk-FEV3BCGC.js";
12
+ import "./chunk-ICQTYE55.js";
13
13
  import "./chunk-VEQXQN22.js";
14
14
 
15
15
  // src/deepseek-command-protection-plugin.ts
@@ -8,12 +8,12 @@ import {
8
8
  import {
9
9
  AppServerProcess,
10
10
  buildCodexAgentEnv
11
- } from "./chunk-MKFFAP4D.js";
11
+ } from "./chunk-WABXMCX5.js";
12
12
  import {
13
13
  AGENT,
14
14
  getMemoryOutputSafetyViolation,
15
15
  headlessAgentRequestSchema
16
- } from "./chunk-FEV3BCGC.js";
16
+ } from "./chunk-ICQTYE55.js";
17
17
  import "./chunk-VEQXQN22.js";
18
18
 
19
19
  // src/headless-agent.ts
package/dist/src/index.js CHANGED
@@ -91,7 +91,7 @@ import {
91
91
  evaluateCommandProtection,
92
92
  extractToolCommand,
93
93
  reportCommandProtectionBlock
94
- } from "./chunk-IVALOOVU.js";
94
+ } from "./chunk-FCGE52IS.js";
95
95
  import {
96
96
  ACCOUNT_RATE_LIMITS_UPDATED_METHOD,
97
97
  AGENT_MESSAGE_DELTA_METHOD,
@@ -115,11 +115,12 @@ import {
115
115
  recoverCompletedTurn
116
116
  } from "./chunk-FLDA7BX3.js";
117
117
  import {
118
+ RELAY_MCP_TOOLS,
118
119
  RELAY_MCP_TOOL_NAMES,
119
120
  RELAY_MCP_TOOL_TIMEOUT_MS,
120
121
  relayCodexMcpConfig,
121
122
  relayMcpProcessConfig
122
- } from "./chunk-UOCB4U67.js";
123
+ } from "./chunk-JQDFBEAW.js";
123
124
  import {
124
125
  BaseRefreshManager,
125
126
  CodexAspAuthMethodChangedError,
@@ -131,20 +132,20 @@ import {
131
132
  recordCredentialFallback,
132
133
  recordExhaustedCredential,
133
134
  restartCodexAspHost
134
- } from "./chunk-ZMZB2VED.js";
135
+ } from "./chunk-HPMBSWPV.js";
135
136
  import {
136
137
  ENGINE_ENV,
137
138
  IS_WARMING_MODE,
138
139
  monolithRequest,
139
140
  monolithService,
140
141
  setAgentCredentialSnapshot
141
- } from "./chunk-Q5TVOQAA.js";
142
+ } from "./chunk-KCTWAJQL.js";
142
143
  import {
143
144
  AspClient,
144
145
  SUBPROCESS_MAX_BUFFER,
145
146
  execAsync,
146
147
  execFileAsync
147
- } from "./chunk-MKFFAP4D.js";
148
+ } from "./chunk-WABXMCX5.js";
148
149
  import {
149
150
  isRecord as isRecord2
150
151
  } from "./chunk-UZSNFLDQ.js";
@@ -355,9 +356,10 @@ import {
355
356
  shouldRefreshPullRequests,
356
357
  spawnRelaySubagentRequestSchema,
357
358
  stripAgentDiagnosticErrors,
359
+ waitRelaySubagentRequestSchema,
358
360
  withTimeout,
359
361
  workspaceCredentialProviderSchema
360
- } from "./chunk-FEV3BCGC.js";
362
+ } from "./chunk-ICQTYE55.js";
361
363
  import {
362
364
  __commonJS,
363
365
  __export,
@@ -59912,9 +59914,9 @@ Go straight to the point. Lead with outcomes and decisions, keep updates concise
59912
59914
 
59913
59915
  Your primary constraint is context. Work directly when the task is small, tightly coupled to context you already hold, or cheaper to complete than explain. Delegate independent work that would consume substantial context, including broad investigation, multi-file implementation, iterative debugging, test-and-fix loops, and unbiased review or verification. Do not delegate trivial work or work whose full context would cost more to transfer than doing it yourself.
59914
59916
 
59915
- Use the \`${RELAY_MCP_SERVER_NAME}\` MCP server exclusively for delegation; never use terminal commands or the base harness's native agent-spawning, delegation, or collaboration tools. It exposes \`get_providers\`, \`spawn_agent\`, \`message_agent\`, and \`delete_agent\`; your harness may show them with an MCP server prefix. In Pi, call them through the \`mcp\` gateway for the \`${RELAY_MCP_SERVER_NAME}\` server if direct tools are not yet visible. Check \`get_providers\` before the first delegation instead of guessing. The default timeout is 10 minutes; increase it only when the task reasonably needs longer.
59917
+ Use the \`${RELAY_MCP_SERVER_NAME}\` MCP server exclusively for delegation; never use terminal commands or the base harness's native agent-spawning, delegation, or collaboration tools. It exposes ${RELAY_MCP_TOOL_NAMES.map((name) => `\`${name}\``).join(", ")}; your harness may show them with an MCP server prefix. In Pi, call them through the \`mcp\` gateway for the \`${RELAY_MCP_SERVER_NAME}\` server if direct tools are not yet visible. Check \`${RELAY_MCP_TOOLS.getProviders}\` before the first delegation instead of guessing. Spawning and messaging block by default; use \`blocking: false\` only when you intend to continue independently, then inspect or join the child with \`${RELAY_MCP_TOOLS.getAgent}\` or \`${RELAY_MCP_TOOLS.waitAgent}\`.
59916
59918
 
59917
- Choose how much parent context each child inherits with \`spawn_agent.fork_turns\`:
59919
+ Choose how much parent context each child inherits with \`${RELAY_MCP_TOOLS.spawnAgent}.fork_turns\`:
59918
59920
 
59919
59921
  - \`"all"\` (default): inherit the conversation as an extension of yourself when the task depends on prior decisions and constraints.
59920
59922
  - \`"none"\`: start with only the task prompt to reduce input tokens or get an independent assessment without your prior reasoning. Include all necessary requirements, paths, constraints, and evidence in the prompt; the child cannot infer omitted context.
@@ -61627,7 +61629,6 @@ var LAST_MESSAGE_PREVIEW_MAX = 200;
61627
61629
  var MAX_ACCEPTED_SEND_RESPONSES = 50;
61628
61630
  var MAX_REMOVED_MESSAGE_IDS = 6e3;
61629
61631
  var RELAY_SUBAGENT_POLL_INTERVAL_MS = 2e3;
61630
- var DEFAULT_RELAY_SUBAGENT_TIMEOUT_MS = 6e5;
61631
61632
  async function resolveRelayBaseProvider(provider, requested) {
61632
61633
  if (provider !== "relay") return null;
61633
61634
  return requested ?? (await monolithService.getRelayConfig())?.baseProvider ?? (!ENGINE_ENV.REPLICAS_WORKSPACE_ID ? DEFAULT_RELAY_BASE_PROVIDER : null);
@@ -61845,7 +61846,7 @@ var ChatService = class {
61845
61846
  });
61846
61847
  throw error;
61847
61848
  }
61848
- return this.waitForRelaySubagent(chat.id, request.timeoutMinutes);
61849
+ return this.getRelaySubagent(parentChatId, chat.id);
61849
61850
  }
61850
61851
  async getRelaySubagentProviders(parentChatId) {
61851
61852
  const parent = this.requireChat(parentChatId);
@@ -61861,7 +61862,17 @@ var ChatService = class {
61861
61862
  ...request.model ? { model: request.model } : {},
61862
61863
  ...request.thinkingLevel ? { thinkingLevel: request.thinkingLevel } : {}
61863
61864
  });
61864
- return this.waitForRelaySubagent(chatId, request.timeoutMinutes);
61865
+ return this.getRelaySubagent(parentChatId, chatId);
61866
+ }
61867
+ async getRelaySubagent(parentChatId, chatId) {
61868
+ const chat = this.requireRelayChild(parentChatId, chatId);
61869
+ if (hasActiveChatTurn(chat)) return { chatId, status: "working" };
61870
+ const history = await chat.provider.getHistory();
61871
+ return {
61872
+ chatId,
61873
+ status: "completed",
61874
+ response: extractFinalResponse(history, getChatExecutionProvider(chat.persisted)) ?? "[No response from subagent]"
61875
+ };
61865
61876
  }
61866
61877
  async deleteRelaySubagent(parentChatId, chatId) {
61867
61878
  this.requireRelayChild(parentChatId, chatId);
@@ -61874,24 +61885,19 @@ var ChatService = class {
61874
61885
  if (child.persisted.parentChatId !== parentChatId) throw new Error("Chat is not a child of this Relay chat");
61875
61886
  return child;
61876
61887
  }
61877
- async waitForRelaySubagent(chatId, timeoutMinutes) {
61878
- const timeoutMs = timeoutMinutes ? timeoutMinutes * 6e4 : DEFAULT_RELAY_SUBAGENT_TIMEOUT_MS;
61888
+ async waitForRelaySubagent(parentChatId, chatId, timeoutMinutes, signal) {
61889
+ const timeoutMs = timeoutMinutes ? timeoutMinutes * 6e4 : null;
61879
61890
  const startedAt = Date.now();
61880
61891
  while (true) {
61881
- const chat = this.requireChat(chatId);
61882
- if (!hasActiveChatTurn(chat)) {
61883
- const history = await chat.provider.getHistory();
61884
- return {
61885
- chatId,
61886
- response: extractFinalResponse(history, getChatExecutionProvider(chat.persisted)) ?? "[No response from subagent]"
61887
- };
61888
- }
61889
- const remainingMs = timeoutMs - (Date.now() - startedAt);
61890
- if (remainingMs <= 0) {
61891
- await this.interrupt(chatId);
61892
- throw new Error(`Subagent chat ${chatId} timed out after ${timeoutMs}ms`);
61893
- }
61894
- await new Promise((resolve7) => setTimeout(resolve7, Math.min(RELAY_SUBAGENT_POLL_INTERVAL_MS, remainingMs)));
61892
+ if (signal?.aborted) throw new Error("Relay wait was cancelled");
61893
+ const result = await this.getRelaySubagent(parentChatId, chatId);
61894
+ if (result.status === "completed") return result;
61895
+ const remainingMs = timeoutMs === null ? null : timeoutMs - (Date.now() - startedAt);
61896
+ if (remainingMs !== null && remainingMs <= 0) return { ...result, timedOut: true };
61897
+ await new Promise((resolve7) => setTimeout(
61898
+ resolve7,
61899
+ remainingMs === null ? RELAY_SUBAGENT_POLL_INTERVAL_MS : Math.min(RELAY_SUBAGENT_POLL_INTERVAL_MS, remainingMs)
61900
+ ));
61895
61901
  }
61896
61902
  }
61897
61903
  async sendMessage(chatId, request) {
@@ -63919,6 +63925,47 @@ var updateTextFileSchema = (maxLength) => z6.object({
63919
63925
  function jsonError(message, details) {
63920
63926
  return { error: message, details };
63921
63927
  }
63928
+ function createJsonEventStream(run, onError) {
63929
+ const encoder2 = new TextEncoder();
63930
+ const stream = new ReadableStream({
63931
+ start(controller) {
63932
+ let closed = false;
63933
+ const enqueue = (chunk) => {
63934
+ if (closed) return false;
63935
+ try {
63936
+ controller.enqueue(encoder2.encode(chunk));
63937
+ return true;
63938
+ } catch {
63939
+ closed = true;
63940
+ return false;
63941
+ }
63942
+ };
63943
+ const send = (data, event) => {
63944
+ enqueue(`${event ? `event: ${event}
63945
+ ` : ""}data: ${JSON.stringify(data)}
63946
+
63947
+ `);
63948
+ };
63949
+ const heartbeat = setInterval(() => {
63950
+ if (!enqueue(": ping\n\n")) clearInterval(heartbeat);
63951
+ }, 15e3);
63952
+ enqueue(": connected\n\n");
63953
+ run(send).catch((error) => {
63954
+ const failure = onError(error);
63955
+ send(failure.data, failure.event);
63956
+ }).finally(() => {
63957
+ clearInterval(heartbeat);
63958
+ if (!closed) {
63959
+ closed = true;
63960
+ controller.close();
63961
+ }
63962
+ });
63963
+ }
63964
+ });
63965
+ return new Response(stream, {
63966
+ headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" }
63967
+ });
63968
+ }
63922
63969
  function createV1Routes(deps) {
63923
63970
  const app2 = new Hono();
63924
63971
  app2.post("/pull-requests/check", async (c) => {
@@ -64054,6 +64101,42 @@ function createV1Routes(deps) {
64054
64101
  ), 400);
64055
64102
  }
64056
64103
  });
64104
+ app2.get("/chats/:chatId/subagents/:subagentId", async (c) => {
64105
+ try {
64106
+ return c.json(await deps.chatService.getRelaySubagent(
64107
+ c.req.param("chatId"),
64108
+ c.req.param("subagentId")
64109
+ ));
64110
+ } catch (error) {
64111
+ return c.json(jsonError(
64112
+ "Failed to load Relay subagent",
64113
+ error instanceof Error ? error.message : "Unknown error"
64114
+ ), 400);
64115
+ }
64116
+ });
64117
+ app2.get("/chats/:chatId/subagents/:subagentId/wait", async (c) => {
64118
+ const parsed = waitRelaySubagentRequestSchema.safeParse(c.req.query());
64119
+ if (!parsed.success) {
64120
+ return c.json(jsonError("Failed to wait for Relay subagent", parsed.error.message), 400);
64121
+ }
64122
+ return createJsonEventStream(
64123
+ async (send) => {
64124
+ send(await deps.chatService.waitForRelaySubagent(
64125
+ c.req.param("chatId"),
64126
+ c.req.param("subagentId"),
64127
+ parsed.data.timeoutMinutes,
64128
+ c.req.raw.signal
64129
+ ));
64130
+ },
64131
+ (error) => ({
64132
+ event: "error",
64133
+ data: {
64134
+ error: "Failed to wait for Relay subagent",
64135
+ details: error instanceof Error ? error.message : "Unknown error"
64136
+ }
64137
+ })
64138
+ );
64139
+ });
64057
64140
  app2.delete("/chats/:chatId/subagents/:subagentId", async (c) => {
64058
64141
  try {
64059
64142
  await deps.chatService.deleteRelaySubagent(c.req.param("chatId"), c.req.param("subagentId"));
@@ -64604,56 +64687,18 @@ data: ${JSON.stringify("Terminal session not found")}
64604
64687
  app2.post("/warm-hooks/run/stream", async (c) => {
64605
64688
  try {
64606
64689
  const body = await c.req.json();
64607
- const encoder2 = new TextEncoder();
64608
- const stream = new ReadableStream({
64609
- start: (controller) => {
64610
- let closed = false;
64611
- const safeEnqueue = (chunk) => {
64612
- if (closed) return false;
64613
- try {
64614
- controller.enqueue(encoder2.encode(chunk));
64615
- return true;
64616
- } catch {
64617
- closed = true;
64618
- return false;
64619
- }
64620
- };
64621
- const heartbeat = setInterval(() => {
64622
- if (!safeEnqueue(": ping\n\n")) clearInterval(heartbeat);
64623
- }, 15e3);
64624
- safeEnqueue(": connected\n\n");
64625
- runWarmHooksStreaming({
64626
- globalWarmHook: body.globalWarmHook,
64627
- environmentWarmHook: body.environmentWarmHook,
64628
- organizationWarmHook: body.organizationWarmHook,
64629
- timeoutMs: body.timeoutMs,
64630
- onEvent: (event) => {
64631
- safeEnqueue(`data: ${JSON.stringify(event)}
64632
-
64633
- `);
64634
- }
64635
- }).then(() => {
64636
- clearInterval(heartbeat);
64637
- if (!closed) {
64638
- closed = true;
64639
- controller.close();
64640
- }
64641
- }).catch((err) => {
64642
- const message = err instanceof Error ? err.message : "Unknown error";
64643
- safeEnqueue(`data: ${JSON.stringify({ type: "error", data: message })}
64644
-
64645
- `);
64646
- clearInterval(heartbeat);
64647
- if (!closed) {
64648
- closed = true;
64649
- controller.close();
64650
- }
64651
- });
64652
- }
64653
- });
64654
- return new Response(stream, {
64655
- headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" }
64656
- });
64690
+ return createJsonEventStream(
64691
+ (send) => runWarmHooksStreaming({
64692
+ globalWarmHook: body.globalWarmHook,
64693
+ environmentWarmHook: body.environmentWarmHook,
64694
+ organizationWarmHook: body.organizationWarmHook,
64695
+ timeoutMs: body.timeoutMs,
64696
+ onEvent: (event) => {
64697
+ send(event);
64698
+ }
64699
+ }),
64700
+ (error) => ({ data: { type: "error", data: error instanceof Error ? error.message : "Unknown error" } })
64701
+ );
64657
64702
  } catch (error) {
64658
64703
  return c.json(
64659
64704
  jsonError("Failed to run warm hooks", error instanceof Error ? error.message : "Unknown error"),
@@ -64664,54 +64709,16 @@ data: ${JSON.stringify("Terminal session not found")}
64664
64709
  app2.post("/start-hooks/run/stream", async (c) => {
64665
64710
  try {
64666
64711
  const body = await c.req.json();
64667
- const encoder2 = new TextEncoder();
64668
- const stream = new ReadableStream({
64669
- start: (controller) => {
64670
- let closed = false;
64671
- const safeEnqueue = (chunk) => {
64672
- if (closed) return false;
64673
- try {
64674
- controller.enqueue(encoder2.encode(chunk));
64675
- return true;
64676
- } catch {
64677
- closed = true;
64678
- return false;
64679
- }
64680
- };
64681
- const heartbeat = setInterval(() => {
64682
- if (!safeEnqueue(": ping\n\n")) clearInterval(heartbeat);
64683
- }, 15e3);
64684
- safeEnqueue(": connected\n\n");
64685
- replicasConfigService.runStartHooksStreaming({
64686
- environmentStartHook: body.environmentStartHook,
64687
- timeoutMs: body.timeoutMs,
64688
- onEvent: (event) => {
64689
- safeEnqueue(`data: ${JSON.stringify(event)}
64690
-
64691
- `);
64692
- }
64693
- }).then(() => {
64694
- clearInterval(heartbeat);
64695
- if (!closed) {
64696
- closed = true;
64697
- controller.close();
64698
- }
64699
- }).catch((err) => {
64700
- const message = err instanceof Error ? err.message : "Unknown error";
64701
- safeEnqueue(`data: ${JSON.stringify({ type: "error", data: message })}
64702
-
64703
- `);
64704
- clearInterval(heartbeat);
64705
- if (!closed) {
64706
- closed = true;
64707
- controller.close();
64708
- }
64709
- });
64710
- }
64711
- });
64712
- return new Response(stream, {
64713
- headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" }
64714
- });
64712
+ return createJsonEventStream(
64713
+ (send) => replicasConfigService.runStartHooksStreaming({
64714
+ environmentStartHook: body.environmentStartHook,
64715
+ timeoutMs: body.timeoutMs,
64716
+ onEvent: (event) => {
64717
+ send(event);
64718
+ }
64719
+ }),
64720
+ (error) => ({ data: { type: "error", data: error instanceof Error ? error.message : "Unknown error" } })
64721
+ );
64715
64722
  } catch (error) {
64716
64723
  return c.json(
64717
64724
  jsonError("Failed to run start hooks", error instanceof Error ? error.message : "Unknown error"),
@@ -3,11 +3,11 @@ import { createRequire as __createRequire } from 'node:module';
3
3
  const require = __createRequire(import.meta.url);
4
4
  import {
5
5
  notifyPostToolUse
6
- } from "./chunk-MTSI4DXY.js";
6
+ } from "./chunk-XZ56DHQ7.js";
7
7
  import {
8
8
  isRecord
9
9
  } from "./chunk-UZSNFLDQ.js";
10
- import "./chunk-FEV3BCGC.js";
10
+ import "./chunk-ICQTYE55.js";
11
11
  import "./chunk-VEQXQN22.js";
12
12
 
13
13
  // src/post-tool-pr-hook.ts
@@ -1,12 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire as __createRequire } from 'node:module';
3
3
  const require = __createRequire(import.meta.url);
4
- import "./chunk-UOCB4U67.js";
4
+ import {
5
+ RELAY_MCP_TOOLS
6
+ } from "./chunk-JQDFBEAW.js";
5
7
  import {
6
8
  RELAY_MCP_SERVER_NAME,
7
9
  messageRelaySubagentRequestSchema,
8
- spawnRelaySubagentRequestSchema
9
- } from "./chunk-FEV3BCGC.js";
10
+ readSseStream,
11
+ relaySubagentResponseSchema,
12
+ spawnRelaySubagentRequestSchema,
13
+ waitRelaySubagentRequestSchema
14
+ } from "./chunk-ICQTYE55.js";
10
15
  import "./chunk-VEQXQN22.js";
11
16
 
12
17
  // src/relay-mcp.ts
@@ -38,14 +43,37 @@ async function request(path, method, body) {
38
43
  if (!response.ok) throw new Error(text || `Relay request failed (${response.status})`);
39
44
  return text || JSON.stringify({ success: true });
40
45
  }
46
+ async function waitForAgent(chatId, timeoutMinutes) {
47
+ const query = timeoutMinutes ? `?timeoutMinutes=${timeoutMinutes}` : "";
48
+ const response = await fetch(`http://localhost:${enginePort}/chats/${parentChatId}/subagents/${chatId}/wait${query}`, {
49
+ headers: {
50
+ Accept: "text/event-stream",
51
+ "X-Replicas-Engine-Secret": engineSecret
52
+ }
53
+ });
54
+ if (!response.ok || !response.body) throw new Error(await response.text() || `Relay wait failed (${response.status})`);
55
+ let result;
56
+ await readSseStream(response.body, (event) => {
57
+ const parsed = relaySubagentResponseSchema.safeParse(event);
58
+ if (parsed.success) {
59
+ result = JSON.stringify(parsed.data);
60
+ return;
61
+ }
62
+ const failure = z.object({ error: z.string(), details: z.string().optional() }).safeParse(event);
63
+ if (failure.success) throw new Error(failure.data.details || failure.data.error);
64
+ throw new Error("Relay wait stream returned an invalid response");
65
+ });
66
+ if (!result) throw new Error("Relay wait stream ended without a result");
67
+ return result;
68
+ }
41
69
  var server = new McpServer({ name: RELAY_MCP_SERVER_NAME, version: "1.0.0" });
42
- server.registerTool("get_providers", {
70
+ server.registerTool(RELAY_MCP_TOOLS.getProviders, {
43
71
  description: "List the agent providers Relay is allowed to use and the configured Relay base provider."
44
72
  }, async () => ({
45
73
  content: [{ type: "text", text: await request(`/chats/${parentChatId}/subagents`, "GET") }]
46
74
  }));
47
- server.registerTool("spawn_agent", {
48
- description: "Spawn a child agent with all, none, or the latest N turns of parent context, wait for it to finish, and return its chat ID and final response.",
75
+ server.registerTool(RELAY_MCP_TOOLS.spawnAgent, {
76
+ description: "Spawn a child agent with all, none, or the latest N turns of parent context. Blocks by default; set blocking to false to return its chat ID immediately.",
49
77
  inputSchema: {
50
78
  provider: spawnRelaySubagentRequestSchema.shape.provider.describe("Provider returned by get_providers."),
51
79
  prompt: spawnRelaySubagentRequestSchema.shape.prompt.describe("The child\u2019s assigned task, scope, and expected result. Include all necessary context when fork_turns is none."),
@@ -53,43 +81,55 @@ server.registerTool("spawn_agent", {
53
81
  model: spawnRelaySubagentRequestSchema.shape.model,
54
82
  thinking_level: spawnRelaySubagentRequestSchema.shape.thinkingLevel,
55
83
  title: spawnRelaySubagentRequestSchema.shape.title,
56
- timeout_minutes: spawnRelaySubagentRequestSchema.shape.timeoutMinutes
84
+ blocking: z.boolean().optional().describe("Wait for the child to finish. Defaults to true."),
85
+ timeout_minutes: waitRelaySubagentRequestSchema.shape.timeoutMinutes.describe("Maximum time to wait without interrupting the child.")
57
86
  }
58
- }, async ({ provider, prompt, fork_turns, model, thinking_level, title, timeout_minutes }) => ({
59
- content: [{
60
- type: "text",
61
- text: await request(`/chats/${parentChatId}/subagents`, "POST", {
62
- provider,
63
- prompt,
64
- forkTurns: fork_turns,
65
- ...model ? { model } : {},
66
- ...thinking_level ? { thinkingLevel: thinking_level } : {},
67
- ...title ? { title } : {},
68
- ...timeout_minutes ? { timeoutMinutes: timeout_minutes } : {}
69
- })
70
- }]
87
+ }, async ({ provider, prompt, fork_turns, model, thinking_level, title, blocking, timeout_minutes }) => {
88
+ const created = await request(`/chats/${parentChatId}/subagents`, "POST", {
89
+ provider,
90
+ prompt,
91
+ forkTurns: fork_turns,
92
+ ...model ? { model } : {},
93
+ ...thinking_level ? { thinkingLevel: thinking_level } : {},
94
+ ...title ? { title } : {}
95
+ });
96
+ const createdAgent = relaySubagentResponseSchema.parse(JSON.parse(created));
97
+ return { content: [{ type: "text", text: blocking === false ? created : await waitForAgent(createdAgent.chatId, timeout_minutes) }] };
98
+ });
99
+ server.registerTool(RELAY_MCP_TOOLS.getAgent, {
100
+ description: "Get the current status and final response, if available, for one of this Relay chat\u2019s child agents.",
101
+ inputSchema: { chat_id: z.string().uuid() }
102
+ }, async ({ chat_id }) => ({
103
+ content: [{ type: "text", text: await request(`/chats/${parentChatId}/subagents/${chat_id}`, "GET") }]
104
+ }));
105
+ server.registerTool(RELAY_MCP_TOOLS.waitAgent, {
106
+ description: "Wait for one of this Relay chat\u2019s child agents to finish over a heartbeat stream. A timeout stops waiting without interrupting the child.",
107
+ inputSchema: {
108
+ chat_id: z.string().uuid(),
109
+ timeout_minutes: waitRelaySubagentRequestSchema.shape.timeoutMinutes
110
+ }
111
+ }, async ({ chat_id, timeout_minutes }) => ({
112
+ content: [{ type: "text", text: await waitForAgent(chat_id, timeout_minutes) }]
71
113
  }));
72
- server.registerTool("message_agent", {
73
- description: "Send a follow-up to one of this Relay chat\u2019s child agents, wait for it to finish, and return its final response.",
114
+ server.registerTool(RELAY_MCP_TOOLS.messageAgent, {
115
+ description: "Send a follow-up to one of this Relay chat\u2019s child agents. Blocks by default; set blocking to false to return immediately.",
74
116
  inputSchema: {
75
117
  chat_id: z.string().uuid(),
76
118
  message: messageRelaySubagentRequestSchema.shape.message,
77
119
  model: messageRelaySubagentRequestSchema.shape.model,
78
120
  thinking_level: messageRelaySubagentRequestSchema.shape.thinkingLevel,
79
- timeout_minutes: messageRelaySubagentRequestSchema.shape.timeoutMinutes
121
+ blocking: z.boolean().optional().describe("Wait for the child to finish. Defaults to true."),
122
+ timeout_minutes: waitRelaySubagentRequestSchema.shape.timeoutMinutes.describe("Maximum time to wait without interrupting the child.")
80
123
  }
81
- }, async ({ chat_id, message, model, thinking_level, timeout_minutes }) => ({
82
- content: [{
83
- type: "text",
84
- text: await request(`/chats/${parentChatId}/subagents/${chat_id}/messages`, "POST", {
85
- message,
86
- ...model ? { model } : {},
87
- ...thinking_level ? { thinkingLevel: thinking_level } : {},
88
- ...timeout_minutes ? { timeoutMinutes: timeout_minutes } : {}
89
- })
90
- }]
91
- }));
92
- server.registerTool("delete_agent", {
124
+ }, async ({ chat_id, message, model, thinking_level, blocking, timeout_minutes }) => {
125
+ const accepted = await request(`/chats/${parentChatId}/subagents/${chat_id}/messages`, "POST", {
126
+ message,
127
+ ...model ? { model } : {},
128
+ ...thinking_level ? { thinkingLevel: thinking_level } : {}
129
+ });
130
+ return { content: [{ type: "text", text: blocking === false ? accepted : await waitForAgent(chat_id, timeout_minutes) }] };
131
+ });
132
+ server.registerTool(RELAY_MCP_TOOLS.deleteAgent, {
93
133
  description: "Delete one of this Relay chat\u2019s child agents.",
94
134
  inputSchema: { chat_id: z.string().uuid() }
95
135
  }, async ({ chat_id }) => ({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.851",
3
+ "version": "0.1.852",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",