replicas-engine 0.1.1301 → 0.1.1303

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.
@@ -8,12 +8,12 @@ import {
8
8
  listRunningCodexModels,
9
9
  restartCodexAspHost,
10
10
  restartCodexAspHostIfRunning
11
- } from "./chunk-QMAXILOV.js";
12
- import "./chunk-YQ3SNL2U.js";
13
- import "./chunk-2ZL2LU6M.js";
14
- import "./chunk-2EH7FP6W.js";
11
+ } from "./chunk-5OA7WBDC.js";
12
+ import "./chunk-5ERKSVWK.js";
13
+ import "./chunk-VQQPKWKF.js";
14
+ import "./chunk-5MKQOOKZ.js";
15
15
  import "./chunk-UZSNFLDQ.js";
16
- import "./chunk-UGHPDBZE.js";
16
+ import "./chunk-MXHSZLHO.js";
17
17
  import "./chunk-VEQXQN22.js";
18
18
  export {
19
19
  CODEX_ULTRAFAST_SERVICE_TIER,
@@ -4,7 +4,7 @@ const require = __createRequire(import.meta.url);
4
4
  import {
5
5
  SUBPROCESS_MAX_BUFFER,
6
6
  execFileAsync
7
- } from "./chunk-2EH7FP6W.js";
7
+ } from "./chunk-5MKQOOKZ.js";
8
8
  import {
9
9
  isRecord as isRecord2
10
10
  } from "./chunk-UZSNFLDQ.js";
@@ -22,7 +22,7 @@ import {
22
22
  provisionedDeviceSchema,
23
23
  readReplicasRuntimeEnv,
24
24
  serializePosixEnvFile
25
- } from "./chunk-UGHPDBZE.js";
25
+ } from "./chunk-MXHSZLHO.js";
26
26
 
27
27
  // src/runtime-env-loader.ts
28
28
  import { readFileSync } from "fs";
@@ -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-UGHPDBZE.js";
6
+ } from "./chunk-MXHSZLHO.js";
7
7
 
8
8
  // src/utils/exec.ts
9
9
  import { exec, execFile } from "child_process";
@@ -10,15 +10,15 @@ import {
10
10
  resolveDiffBase,
11
11
  runGitCommand,
12
12
  setAgentCredentialSnapshot
13
- } from "./chunk-YQ3SNL2U.js";
13
+ } from "./chunk-5ERKSVWK.js";
14
14
  import {
15
15
  AppServerProcess,
16
16
  buildCodexAgentEnv
17
- } from "./chunk-2ZL2LU6M.js";
17
+ } from "./chunk-VQQPKWKF.js";
18
18
  import {
19
19
  SUBPROCESS_MAX_BUFFER,
20
20
  execFileAsync
21
- } from "./chunk-2EH7FP6W.js";
21
+ } from "./chunk-5MKQOOKZ.js";
22
22
  import {
23
23
  isRecord
24
24
  } from "./chunk-UZSNFLDQ.js";
@@ -56,7 +56,7 @@ import {
56
56
  resolveOpenAICompatibleModelsUrl,
57
57
  splitMothershipInstructions,
58
58
  withTimeout
59
- } from "./chunk-UGHPDBZE.js";
59
+ } from "./chunk-MXHSZLHO.js";
60
60
 
61
61
  // src/managers/codex-token-manager.ts
62
62
  import { promises as fs } from "fs";
@@ -306,7 +306,7 @@ var CodexTokenManager = class extends BaseRefreshManager {
306
306
  await this.applyCredentialUpdate(++this.credentialGeneration, async () => {
307
307
  await this.applyCredentialsResponse(data);
308
308
  setAgentCredentialSnapshot("codex", data);
309
- const { restartCodexAspHostIfRunning: restartCodexAspHostIfRunning2 } = await import("./asp-host-LDPEGGTO.js");
309
+ const { restartCodexAspHostIfRunning: restartCodexAspHostIfRunning2 } = await import("./asp-host-SNQAHZ4O.js");
310
310
  await restartCodexAspHostIfRunning2();
311
311
  });
312
312
  await this.start(true);
@@ -328,7 +328,7 @@ var CodexTokenManager = class extends BaseRefreshManager {
328
328
  const accountChanged = ENGINE_ENV.REPLICAS_AGENT_CREDENTIALS.codex?.credentialId !== data.credentialId;
329
329
  await this.applyCredentialsResponse(data);
330
330
  if (restartOnChange && (accountChanged || CODEX_AUTH_ENV_KEYS.some((key, index) => process.env[key] !== previousEnv[index]))) {
331
- const { restartCodexAspHostIfRunning: restartCodexAspHostIfRunning2 } = await import("./asp-host-LDPEGGTO.js");
331
+ const { restartCodexAspHostIfRunning: restartCodexAspHostIfRunning2 } = await import("./asp-host-SNQAHZ4O.js");
332
332
  await restartCodexAspHostIfRunning2();
333
333
  }
334
334
  setAgentCredentialSnapshot("codex", data);
@@ -3,7 +3,7 @@ import { createRequire as __createRequire } from 'node:module';
3
3
  const require = __createRequire(import.meta.url);
4
4
  import {
5
5
  AspClient
6
- } from "./chunk-2ZL2LU6M.js";
6
+ } from "./chunk-VQQPKWKF.js";
7
7
  import {
8
8
  ASTER_BASE_URL,
9
9
  ASTER_PROVIDER,
@@ -15,7 +15,7 @@ import {
15
15
  isRecord,
16
16
  readOllamaAuthEnv,
17
17
  readOpenAICompatibleAuthEnv
18
- } from "./chunk-UGHPDBZE.js";
18
+ } from "./chunk-MXHSZLHO.js";
19
19
 
20
20
  // src/managers/codex-asp/notification-dispatch.ts
21
21
  var TURN_STARTED_METHOD = "turn/started";
@@ -5185,7 +5185,9 @@ function splitMothershipInstructions(customInstructions) {
5185
5185
 
5186
5186
  // ../shared/src/mothership-events.ts
5187
5187
  var MOTHERSHIP_EVENT_KINDS = ["finished", "ready", "needs_attention", "merged", "closed", "archived"];
5188
+ var MOTHERSHIP_TURN_ERROR_SUMMARY = "Turn ended with an error.";
5188
5189
  var MOTHERSHIP_EVENT_TYPE = "multitask-event";
5190
+ var MOTHERSHIP_EVENT_MESSAGE_TYPE = "multitask_event";
5189
5191
  var mothershipEventSchema = z20.object({
5190
5192
  id: z20.string(),
5191
5193
  at: z20.string(),
@@ -5194,13 +5196,31 @@ var mothershipEventSchema = z20.object({
5194
5196
  agentName: z20.string(),
5195
5197
  summary: z20.string(),
5196
5198
  detail: z20.string().optional(),
5199
+ // Set when the subagent is a chat in this workspace rather than a ship; agentId is then that chat's id.
5200
+ chatId: z20.string().optional(),
5197
5201
  // Set on events recorded before every kind woke the orchestrator, which were shown as a row when they happened.
5198
5202
  noticed: z20.boolean().optional()
5199
5203
  });
5200
5204
  var TAG = "multitask_event";
5201
5205
  var BLOCK = new RegExp(`<${TAG}((?:\\s+\\w+="[^"]*")*)>\\n?([\\s\\S]*?)\\n?</${TAG}>`, "g");
5202
5206
  var ATTRIBUTE = /(\w+)="([^"]*)"/g;
5207
+ var escapeAttribute = (value) => value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
5203
5208
  var unescapeAttribute = (value) => value.replace(/&lt;/g, "<").replace(/&quot;/g, '"').replace(/&amp;/g, "&");
5209
+ var EVENT_RESPONSE_MAX_CHARS = 8e3;
5210
+ function truncateMothershipEventResponse(response, readTool) {
5211
+ return response.length > EVENT_RESPONSE_MAX_CHARS ? `${response.slice(0, EVENT_RESPONSE_MAX_CHARS)}\u2026
5212
+
5213
+ (Truncated; ${readTool} returns the full response.)` : response;
5214
+ }
5215
+ function formatMothershipEvents(events) {
5216
+ return events.map((event) => {
5217
+ const attributes = { id: event.id, at: event.at, kind: event.kind, agent: event.agentName, agent_id: event.agentId, ...event.chatId ? { chat_id: event.chatId } : {}, ...event.noticed ? { noticed: "true" } : {} };
5218
+ const body = [event.summary, event.detail].filter(Boolean).join("\n\n").replaceAll(`</${TAG}`, `<\\/${TAG}`);
5219
+ return `<${TAG}${Object.entries(attributes).map(([key, value]) => ` ${key}="${escapeAttribute(value)}"`).join("")}>
5220
+ ${body}
5221
+ </${TAG}>`;
5222
+ }).join("\n\n");
5223
+ }
5204
5224
  function extractMothershipEvents(text3) {
5205
5225
  return [...text3.matchAll(BLOCK)].flatMap((match) => {
5206
5226
  const attributes = Object.fromEntries([...match[1].matchAll(ATTRIBUTE)].map(([, key, value]) => [key, unescapeAttribute(value)]));
@@ -5216,6 +5236,7 @@ function extractMothershipEvents(text3) {
5216
5236
  agentName: attributes.agent ?? "ship",
5217
5237
  summary: summary.trim(),
5218
5238
  ...detail ? { detail } : {},
5239
+ ...attributes.chat_id ? { chatId: attributes.chat_id } : {},
5219
5240
  ...attributes.noticed === "true" ? { noticed: true } : {}
5220
5241
  }];
5221
5242
  });
@@ -10770,6 +10791,10 @@ export {
10770
10791
  MOTHERSHIP_INSTRUCTIONS_TAG,
10771
10792
  wrapInTag,
10772
10793
  splitMothershipInstructions,
10794
+ MOTHERSHIP_TURN_ERROR_SUMMARY,
10795
+ MOTHERSHIP_EVENT_MESSAGE_TYPE,
10796
+ truncateMothershipEventResponse,
10797
+ formatMothershipEvents,
10773
10798
  coerceBackgroundTaskPayload,
10774
10799
  CHAT_FORK_EXCERPT_OMITTED_NOTICE,
10775
10800
  formatChatForkHandoffMessage,
@@ -5,14 +5,14 @@ import {
5
5
  getOriginDefaultBranch,
6
6
  monolithRequest,
7
7
  runGitCommand
8
- } from "./chunk-YQ3SNL2U.js";
8
+ } from "./chunk-5ERKSVWK.js";
9
9
  import {
10
10
  extractCommandProtectionCommandText,
11
11
  findCommandProtectionGitLookups,
12
12
  findGitCommitSignals,
13
13
  findPrMergeSignals,
14
14
  parseCommandProtectionRequest
15
- } from "./chunk-UGHPDBZE.js";
15
+ } from "./chunk-MXHSZLHO.js";
16
16
 
17
17
  // src/services/command-protection-service.ts
18
18
  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-2EH7FP6W.js";
6
+ } from "./chunk-5MKQOOKZ.js";
7
7
  import {
8
8
  isVersionBelow
9
- } from "./chunk-UGHPDBZE.js";
9
+ } from "./chunk-MXHSZLHO.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.1301";
243
+ var ENGINE_PACKAGE_VERSION = "0.1.1303";
244
244
  var INITIALIZE_METHOD = "initialize";
245
245
  var INITIALIZED_NOTIFICATION = "initialized";
246
246
  var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
@@ -5,7 +5,7 @@ import {
5
5
  extractCommandProtectionCommandText,
6
6
  isRecord,
7
7
  parseCodeHostPrUrl
8
- } from "./chunk-UGHPDBZE.js";
8
+ } from "./chunk-MXHSZLHO.js";
9
9
 
10
10
  // src/services/published-pull-request-urls.ts
11
11
  function publishedPullRequestUrls(result) {
@@ -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-Z7RUG7OO.js";
7
- import "./chunk-YQ3SNL2U.js";
8
- import "./chunk-2EH7FP6W.js";
6
+ } from "./chunk-RCJP3KY5.js";
7
+ import "./chunk-5ERKSVWK.js";
8
+ import "./chunk-5MKQOOKZ.js";
9
9
  import {
10
10
  isRecord
11
11
  } from "./chunk-UZSNFLDQ.js";
12
- import "./chunk-UGHPDBZE.js";
12
+ import "./chunk-MXHSZLHO.js";
13
13
  import "./chunk-VEQXQN22.js";
14
14
 
15
15
  // src/command-protection-hook.ts
@@ -14,12 +14,12 @@ import {
14
14
  recoverCompletedTurn,
15
15
  spawnMuseMsp,
16
16
  startMuseSession
17
- } from "./chunk-4SXUPNQE.js";
17
+ } from "./chunk-HWYNJQUU.js";
18
18
  import {
19
19
  AppServerProcess,
20
20
  buildCodexAgentEnv
21
- } from "./chunk-2ZL2LU6M.js";
22
- import "./chunk-2EH7FP6W.js";
21
+ } from "./chunk-VQQPKWKF.js";
22
+ import "./chunk-5MKQOOKZ.js";
23
23
  import {
24
24
  AGENT,
25
25
  OPENCODE_GO_PROVIDER,
@@ -29,7 +29,7 @@ import {
29
29
  headlessAgentUsagePath,
30
30
  isRecord,
31
31
  withTimeout
32
- } from "./chunk-UGHPDBZE.js";
32
+ } from "./chunk-MXHSZLHO.js";
33
33
  import "./chunk-VEQXQN22.js";
34
34
 
35
35
  // src/headless-agent.ts
package/dist/src/index.js CHANGED
@@ -27,7 +27,7 @@ import {
27
27
  evaluateCommandProtection,
28
28
  extractToolCommand,
29
29
  reportCommandProtectionBlock
30
- } from "./chunk-Z7RUG7OO.js";
30
+ } from "./chunk-RCJP3KY5.js";
31
31
  import {
32
32
  ACCOUNT_RATE_LIMITS_UPDATED_METHOD,
33
33
  AGENT_MESSAGE_DELTA_METHOD,
@@ -65,10 +65,10 @@ import {
65
65
  spawnMuseMsp,
66
66
  startMuseSession,
67
67
  uuidV7
68
- } from "./chunk-4SXUPNQE.js";
68
+ } from "./chunk-HWYNJQUU.js";
69
69
  import {
70
70
  publishedPullRequestUrls
71
- } from "./chunk-Z7R3XFPJ.js";
71
+ } from "./chunk-XHKAQ3OC.js";
72
72
  import {
73
73
  ACCOUNT_RATE_LIMITS_READ_METHOD,
74
74
  AsyncLock,
@@ -143,7 +143,7 @@ import {
143
143
  timestampFromSeconds,
144
144
  upsertCredentialFileLines,
145
145
  writeSecureCredentialFile
146
- } from "./chunk-QMAXILOV.js";
146
+ } from "./chunk-5OA7WBDC.js";
147
147
  import {
148
148
  ENGINE_ENV,
149
149
  monolithRequest,
@@ -153,15 +153,15 @@ import {
153
153
  resolveDiffBase,
154
154
  setAgentCredentialSnapshot,
155
155
  setLinearSessionId
156
- } from "./chunk-YQ3SNL2U.js";
156
+ } from "./chunk-5ERKSVWK.js";
157
157
  import {
158
158
  getCodexModelProvider
159
- } from "./chunk-2ZL2LU6M.js";
159
+ } from "./chunk-VQQPKWKF.js";
160
160
  import {
161
161
  SUBPROCESS_MAX_BUFFER,
162
162
  execAsync,
163
163
  execFileAsync
164
- } from "./chunk-2EH7FP6W.js";
164
+ } from "./chunk-5MKQOOKZ.js";
165
165
  import {
166
166
  isRecord as isRecord2
167
167
  } from "./chunk-UZSNFLDQ.js";
@@ -236,7 +236,9 @@ import {
236
236
  MAX_CODEX_GOAL_OBJECTIVE_CHARS,
237
237
  MAX_REPO_FILE_CONTENT_BYTES,
238
238
  MERGED_MESSAGE_SEPARATOR,
239
+ MOTHERSHIP_EVENT_MESSAGE_TYPE,
239
240
  MOTHERSHIP_INSTRUCTIONS_TAG,
241
+ MOTHERSHIP_TURN_ERROR_SUMMARY,
240
242
  OLLAMA_AGENTS,
241
243
  OPENCODE_GO_BASE_URL,
242
244
  OPENCODE_GO_PROVIDER,
@@ -309,6 +311,7 @@ import {
309
311
  forkChatRequestSchema,
310
312
  formatChatForkHandoffMessage,
311
313
  formatChatForkUpdateMessage,
314
+ formatMothershipEvents,
312
315
  getChatContinuation,
313
316
  getChatHistoryPageSenders,
314
317
  getChatKind,
@@ -403,6 +406,7 @@ import {
403
406
  stripAgentDiagnosticErrors,
404
407
  threadHandoffText,
405
408
  toClaudeCodeModel,
409
+ truncateMothershipEventResponse,
406
410
  updateChatDisplayNameRequestSchema,
407
411
  waitSubagentRequestSchema,
408
412
  withTimeout,
@@ -411,7 +415,7 @@ import {
411
415
  workspaceTokenRequestSchema,
412
416
  workspaceTokenResponseSchema,
413
417
  wrapInTag
414
- } from "./chunk-UGHPDBZE.js";
418
+ } from "./chunk-MXHSZLHO.js";
415
419
  import {
416
420
  cloneMcpConfig,
417
421
  ensureCompatibilityImports,
@@ -11650,7 +11654,7 @@ var MessageQueueService = class {
11650
11654
  // src/managers/subagent-mcp-prompt.ts
11651
11655
  var SUBAGENT_MCP_INSTRUCTIONS = `# Subagents
11652
11656
 
11653
- Native subagent tools are disabled in this workspace. To delegate work to a subagent, use the \`${SUBAGENT_MCP_SERVER_NAME}\` MCP server (\`${SUBAGENT_MCP_TOOLS.getProviders}\`, \`${SUBAGENT_MCP_TOOLS.spawnAgent}\`, \`${SUBAGENT_MCP_TOOLS.getAgent}\`, \`${SUBAGENT_MCP_TOOLS.waitAgent}\`, \`${SUBAGENT_MCP_TOOLS.messageAgent}\`, \`${SUBAGENT_MCP_TOOLS.deleteAgent}\`); your harness may prefix them, and Pi exposes them through its \`mcp\` gateway. Call \`${SUBAGENT_MCP_TOOLS.getProviders}\` before the first spawn. \`fork_turns\` sets inherited context: "all" (default), "none" (include every needed detail in the prompt), or "N" for the latest N user turns. \`${SUBAGENT_MCP_TOOLS.spawnAgent}\` and \`${SUBAGENT_MCP_TOOLS.messageAgent}\` block by default; pass \`blocking: false\` to keep working, then use \`${SUBAGENT_MCP_TOOLS.getAgent}\` or \`${SUBAGENT_MCP_TOOLS.waitAgent}\`. Subagents share this workspace.`;
11657
+ Native subagent tools are disabled in this workspace. To delegate work to a subagent, use the \`${SUBAGENT_MCP_SERVER_NAME}\` MCP server (\`${SUBAGENT_MCP_TOOLS.getProviders}\`, \`${SUBAGENT_MCP_TOOLS.spawnAgent}\`, \`${SUBAGENT_MCP_TOOLS.getAgent}\`, \`${SUBAGENT_MCP_TOOLS.waitAgent}\`, \`${SUBAGENT_MCP_TOOLS.messageAgent}\`, \`${SUBAGENT_MCP_TOOLS.deleteAgent}\`); your harness may prefix them, and Pi exposes them through its \`mcp\` gateway. Call \`${SUBAGENT_MCP_TOOLS.getProviders}\` before the first spawn. \`fork_turns\` sets inherited context: "all" (default), "none" (include every needed detail in the prompt), or "N" for the latest N user turns. \`${SUBAGENT_MCP_TOOLS.spawnAgent}\` and \`${SUBAGENT_MCP_TOOLS.messageAgent}\` block by default; pass \`blocking: false\` to keep working. A subagent you are not waiting on reports back when it finishes: its final response arrives as a \`<multitask_event>\` message once your turn ends, so end your turn instead of polling. Subagents share this workspace.`;
11654
11658
 
11655
11659
  // src/managers/coding-agent-manager.ts
11656
11660
  var MAX_INTERRUPT_QUEUE_ITEMS = 1e3;
@@ -60262,6 +60266,8 @@ var ChatService = class {
60262
60266
  agentChatActivityTrackerService;
60263
60267
  agentTokenUsageTracker;
60264
60268
  chats = /* @__PURE__ */ new Map();
60269
+ awaitedSubagents = /* @__PURE__ */ new Map();
60270
+ heldSubagentReports = /* @__PURE__ */ new Map();
60265
60271
  forkSourceSeenBlocks = /* @__PURE__ */ new Map();
60266
60272
  persistPromise = null;
60267
60273
  persistQueued = false;
@@ -60468,19 +60474,68 @@ var ChatService = class {
60468
60474
  async waitForSubagent(parentChatId, chatId, timeoutMinutes, signal) {
60469
60475
  const timeoutMs = timeoutMinutes ? timeoutMinutes * 6e4 : null;
60470
60476
  const startedAt = Date.now();
60471
- while (true) {
60472
- if (signal?.aborted) throw new Error("Subagent wait was cancelled");
60473
- const result = await this.getSubagent(parentChatId, chatId);
60474
- if (result.status === "completed") return result;
60475
- if (this.requireChat(parentChatId).provider.getQueue().some(({ steeredAt }) => steeredAt && Date.parse(steeredAt) >= startedAt)) {
60476
- return { ...result, interruptedByUserMessage: true };
60477
- }
60478
- const remainingMs = timeoutMs === null ? null : timeoutMs - (Date.now() - startedAt);
60479
- if (remainingMs !== null && remainingMs <= 0) return { ...result, timedOut: true };
60480
- await new Promise((resolve7) => setTimeout(
60481
- resolve7,
60482
- remainingMs === null ? SUBAGENT_POLL_INTERVAL_MS : Math.min(SUBAGENT_POLL_INTERVAL_MS, remainingMs)
60483
- ));
60477
+ this.awaitedSubagents.set(chatId, (this.awaitedSubagents.get(chatId) ?? 0) + 1);
60478
+ try {
60479
+ while (true) {
60480
+ if (signal?.aborted) throw new Error("Subagent wait was cancelled");
60481
+ const result = await this.getSubagent(parentChatId, chatId);
60482
+ if (result.status === "completed" && !signal?.aborted) {
60483
+ this.heldSubagentReports.delete(chatId);
60484
+ return result;
60485
+ }
60486
+ if (this.requireChat(parentChatId).provider.getQueue().some(({ steeredAt }) => steeredAt && Date.parse(steeredAt) >= startedAt)) {
60487
+ return { ...result, interruptedByUserMessage: true };
60488
+ }
60489
+ const remainingMs = timeoutMs === null ? null : timeoutMs - (Date.now() - startedAt);
60490
+ if (remainingMs !== null && remainingMs <= 0) return { ...result, timedOut: true };
60491
+ await new Promise((resolve7) => setTimeout(
60492
+ resolve7,
60493
+ remainingMs === null ? SUBAGENT_POLL_INTERVAL_MS : Math.min(SUBAGENT_POLL_INTERVAL_MS, remainingMs)
60494
+ ));
60495
+ }
60496
+ } finally {
60497
+ const waits = (this.awaitedSubagents.get(chatId) ?? 1) - 1;
60498
+ if (waits > 0) this.awaitedSubagents.set(chatId, waits);
60499
+ else {
60500
+ this.awaitedSubagents.delete(chatId);
60501
+ const held = this.heldSubagentReports.get(chatId);
60502
+ const child = this.getRuntimeChat(chatId);
60503
+ this.heldSubagentReports.delete(chatId);
60504
+ if (held && child) void this.reportSubagentToParent(child, held.completedAt, held.errors);
60505
+ }
60506
+ }
60507
+ }
60508
+ async reportSubagentToParent(chat, completedAt, errors) {
60509
+ const parentChatId = chat.persisted.parentChatId;
60510
+ if (!parentChatId) return;
60511
+ if (this.awaitedSubagents.has(chat.persisted.id)) {
60512
+ this.heldSubagentReports.set(chat.persisted.id, { completedAt, errors });
60513
+ return;
60514
+ }
60515
+ const last = this.getRuntimeChat(parentChatId)?.lastSendRequest;
60516
+ if (!last) return;
60517
+ try {
60518
+ const detail = errors?.length ? errors.join("\n") : extractFinalResponse(await chat.provider.getHistory(), chat.persisted.provider) ?? "[No response from subagent]";
60519
+ await this.sendMessage(parentChatId, {
60520
+ message: formatMothershipEvents([{
60521
+ id: randomUUID10(),
60522
+ at: completedAt,
60523
+ kind: "finished",
60524
+ agentId: chat.persisted.id,
60525
+ agentName: chat.persisted.title,
60526
+ chatId: chat.persisted.id,
60527
+ summary: `${errors?.length ? MOTHERSHIP_TURN_ERROR_SUMMARY : "Finished its turn."} It is a subagent in this workspace: reach it with the ${SUBAGENT_MCP_TOOLS.messageAgent} and ${SUBAGENT_MCP_TOOLS.getAgent} tools and this chat_id.`,
60528
+ detail: truncateMothershipEventResponse(detail, SUBAGENT_MCP_TOOLS.getAgent)
60529
+ }]),
60530
+ ...last.customInstructions ? { customInstructions: last.customInstructions } : {},
60531
+ ...last.enableInteractiveTools !== void 0 ? { enableInteractiveTools: last.enableInteractiveTools } : {},
60532
+ type: MOTHERSHIP_EVENT_MESSAGE_TYPE,
60533
+ merge: true,
60534
+ inheritThinkingLevel: true,
60535
+ idempotencyKey: `subagent-finished:${chat.persisted.id}:${completedAt}`
60536
+ });
60537
+ } catch (error) {
60538
+ console.error("[ChatService] Failed to report subagent to its parent:", { chatId: chat.persisted.id, error });
60484
60539
  }
60485
60540
  }
60486
60541
  async sendMessage(chatId, request) {
@@ -61779,6 +61834,7 @@ ${note}`;
61779
61834
  if (chat.persisted.parentChatId) {
61780
61835
  this.publish({ type: "chat.updated", payload: { chat: this.toSummary(chat) } }).catch(() => {
61781
61836
  });
61837
+ if (!processing) void this.reportSubagentToParent(chat, completedAt, chat.lastTurnErrors);
61782
61838
  return;
61783
61839
  }
61784
61840
  this.publish({
@@ -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-Z7R3XFPJ.js";
6
+ } from "./chunk-XHKAQ3OC.js";
7
7
  import {
8
8
  isRecord
9
9
  } from "./chunk-UZSNFLDQ.js";
10
10
  import {
11
11
  shouldRefreshPullRequests
12
- } from "./chunk-UGHPDBZE.js";
12
+ } from "./chunk-MXHSZLHO.js";
13
13
  import "./chunk-VEQXQN22.js";
14
14
 
15
15
  // src/services/post-tool-pr-notifier.ts
@@ -11,7 +11,7 @@ import {
11
11
  spawnSubagentRequestSchema,
12
12
  subagentResponseSchema,
13
13
  waitSubagentRequestSchema
14
- } from "./chunk-UGHPDBZE.js";
14
+ } from "./chunk-MXHSZLHO.js";
15
15
  import "./chunk-VEQXQN22.js";
16
16
 
17
17
  // src/subagent-mcp.ts
@@ -43,13 +43,14 @@ async function request(path, method, body) {
43
43
  if (!response.ok) throw new Error(text || `Subagent request failed (${response.status})`);
44
44
  return text || JSON.stringify({ success: true });
45
45
  }
46
- async function waitForAgent(chatId, timeoutMinutes) {
46
+ async function waitForAgent(chatId, timeoutMinutes, signal) {
47
47
  const query = timeoutMinutes ? `?timeoutMinutes=${timeoutMinutes}` : "";
48
48
  const response = await fetch(`http://localhost:${enginePort}/chats/${parentChatId}/subagents/${chatId}/wait${query}`, {
49
49
  headers: {
50
50
  Accept: "text/event-stream",
51
51
  "X-Replicas-Engine-Secret": engineSecret
52
- }
52
+ },
53
+ signal
53
54
  });
54
55
  if (!response.ok || !response.body) throw new Error(await response.text() || `Subagent wait failed (${response.status})`);
55
56
  let result;
@@ -79,7 +80,7 @@ server.registerTool(SUBAGENT_MCP_TOOLS.getProviders, {
79
80
  content: [{ type: "text", text: await request(`/chats/${parentChatId}/subagents`, "GET") }]
80
81
  }));
81
82
  server.registerTool(SUBAGENT_MCP_TOOLS.spawnAgent, {
82
- 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.",
83
+ 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, and its final response reaches you as a message once your turn ends.",
83
84
  inputSchema: {
84
85
  provider: spawnSubagentRequestSchema.shape.provider.describe("Provider returned by get_providers."),
85
86
  prompt: spawnSubagentRequestSchema.shape.prompt.describe("The child\u2019s assigned task, scope, and expected result. Include all necessary context when fork_turns is none."),
@@ -91,7 +92,7 @@ server.registerTool(SUBAGENT_MCP_TOOLS.spawnAgent, {
91
92
  blocking: z.boolean().optional().describe("Wait for the child to finish. Defaults to true."),
92
93
  timeout_minutes: waitSubagentRequestSchema.shape.timeoutMinutes.describe("Maximum time to wait without interrupting the child.")
93
94
  }
94
- }, async ({ provider, prompt, fork_turns, model, thinking_level, service_tier, title, blocking, timeout_minutes }) => {
95
+ }, async ({ provider, prompt, fork_turns, model, thinking_level, service_tier, title, blocking, timeout_minutes }, { signal }) => {
95
96
  const created = await request(`/chats/${parentChatId}/subagents`, "POST", {
96
97
  provider,
97
98
  prompt,
@@ -102,7 +103,7 @@ server.registerTool(SUBAGENT_MCP_TOOLS.spawnAgent, {
102
103
  ...title ? { title } : {}
103
104
  });
104
105
  const createdAgent = subagentResponseSchema.parse(JSON.parse(created));
105
- return { content: [{ type: "text", text: blocking === false ? created : await waitForAgent(createdAgent.chatId, timeout_minutes) }] };
106
+ return { content: [{ type: "text", text: blocking === false ? created : await waitForAgent(createdAgent.chatId, timeout_minutes, signal) }] };
106
107
  });
107
108
  server.registerTool(SUBAGENT_MCP_TOOLS.getAgent, {
108
109
  description: "Get the current status and final response, if available, for one of this chat\u2019s subagents.",
@@ -116,11 +117,11 @@ server.registerTool(SUBAGENT_MCP_TOOLS.waitAgent, {
116
117
  chat_id: z.string().uuid(),
117
118
  timeout_minutes: waitSubagentRequestSchema.shape.timeoutMinutes
118
119
  }
119
- }, async ({ chat_id, timeout_minutes }) => ({
120
- content: [{ type: "text", text: await waitForAgent(chat_id, timeout_minutes) }]
120
+ }, async ({ chat_id, timeout_minutes }, { signal }) => ({
121
+ content: [{ type: "text", text: await waitForAgent(chat_id, timeout_minutes, signal) }]
121
122
  }));
122
123
  server.registerTool(SUBAGENT_MCP_TOOLS.messageAgent, {
123
- description: "Send a follow-up to one of this chat\u2019s subagents. Blocks by default; set blocking to false to return immediately.",
124
+ description: "Send a follow-up to one of this chat\u2019s subagents. Blocks by default; set blocking to false to return immediately, and its reply reaches you as a message once your turn ends.",
124
125
  inputSchema: {
125
126
  chat_id: z.string().uuid(),
126
127
  message: messageSubagentRequestSchema.shape.message,
@@ -130,14 +131,14 @@ server.registerTool(SUBAGENT_MCP_TOOLS.messageAgent, {
130
131
  blocking: z.boolean().optional().describe("Wait for the child to finish. Defaults to true."),
131
132
  timeout_minutes: waitSubagentRequestSchema.shape.timeoutMinutes.describe("Maximum time to wait without interrupting the child.")
132
133
  }
133
- }, async ({ chat_id, message, model, thinking_level, service_tier, blocking, timeout_minutes }) => {
134
+ }, async ({ chat_id, message, model, thinking_level, service_tier, blocking, timeout_minutes }, { signal }) => {
134
135
  const accepted = await request(`/chats/${parentChatId}/subagents/${chat_id}/messages`, "POST", {
135
136
  message,
136
137
  ...model ? { model } : {},
137
138
  ...thinking_level ? { thinkingLevel: thinking_level } : {},
138
139
  ...serviceTierFields(service_tier)
139
140
  });
140
- return { content: [{ type: "text", text: blocking === false ? accepted : await waitForAgent(chat_id, timeout_minutes) }] };
141
+ return { content: [{ type: "text", text: blocking === false ? accepted : await waitForAgent(chat_id, timeout_minutes, signal) }] };
141
142
  });
142
143
  server.registerTool(SUBAGENT_MCP_TOOLS.deleteAgent, {
143
144
  description: "Delete one of this chat\u2019s subagents.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.1301",
3
+ "version": "0.1.1303",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",