replicas-engine 0.1.985 → 0.1.987

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-C3CEF7DO.js";
9
- import "./chunk-VNL2VYM4.js";
10
- import "./chunk-DAVKTMQK.js";
11
- import "./chunk-QVK2OVNX.js";
8
+ } from "./chunk-D5B72SUV.js";
9
+ import "./chunk-TYH3IEXZ.js";
10
+ import "./chunk-KAJYMTYA.js";
11
+ import "./chunk-2PMHC6IL.js";
12
12
  import "./chunk-UZSNFLDQ.js";
13
- import "./chunk-LKTX2CHU.js";
13
+ import "./chunk-QU4FXL4X.js";
14
14
  import "./chunk-VEQXQN22.js";
15
15
  export {
16
16
  getCodexAspHost,
@@ -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-LKTX2CHU.js";
6
+ } from "./chunk-QU4FXL4X.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-LKTX2CHU.js";
8
+ } from "./chunk-QU4FXL4X.js";
9
9
 
10
10
  // src/services/published-pull-request-urls.ts
11
11
  function publishedPullRequestUrls(result) {
@@ -5,15 +5,15 @@ import {
5
5
  ENGINE_ENV,
6
6
  monolithRequest,
7
7
  setAgentCredentialSnapshot
8
- } from "./chunk-VNL2VYM4.js";
8
+ } from "./chunk-TYH3IEXZ.js";
9
9
  import {
10
10
  AppServerProcess,
11
11
  buildCodexAgentEnv
12
- } from "./chunk-DAVKTMQK.js";
12
+ } from "./chunk-KAJYMTYA.js";
13
13
  import {
14
14
  SUBPROCESS_MAX_BUFFER,
15
15
  execFileAsync
16
- } from "./chunk-QVK2OVNX.js";
16
+ } from "./chunk-2PMHC6IL.js";
17
17
  import {
18
18
  isRecord
19
19
  } from "./chunk-UZSNFLDQ.js";
@@ -27,7 +27,7 @@ import {
27
27
  listOpenAICompatibleModels,
28
28
  readOllamaAuthEnv,
29
29
  resolveOpenAICompatibleModelsUrl
30
- } from "./chunk-LKTX2CHU.js";
30
+ } from "./chunk-QU4FXL4X.js";
31
31
 
32
32
  // src/managers/codex-token-manager.ts
33
33
  import { promises as fs } from "fs";
@@ -260,7 +260,7 @@ var CodexTokenManager = class extends BaseRefreshManager {
260
260
  await this.applyCredentialUpdate(++this.credentialGeneration, async () => {
261
261
  await this.applyCredentialsResponse(data);
262
262
  setAgentCredentialSnapshot("codex", data);
263
- const { restartCodexAspHostIfRunning: restartCodexAspHostIfRunning2 } = await import("./asp-host-WFNJQSDE.js");
263
+ const { restartCodexAspHostIfRunning: restartCodexAspHostIfRunning2 } = await import("./asp-host-LOTCGOVD.js");
264
264
  await restartCodexAspHostIfRunning2();
265
265
  });
266
266
  await this.start(true);
@@ -280,7 +280,7 @@ var CodexTokenManager = class extends BaseRefreshManager {
280
280
  await this.applyCredentialUpdate(generation, async () => {
281
281
  await this.applyCredentialsResponse(data);
282
282
  if (restartOnChange && CODEX_AUTH_ENV_KEYS.some((key, index) => process.env[key] !== previousEnv[index])) {
283
- const { restartCodexAspHostIfRunning: restartCodexAspHostIfRunning2 } = await import("./asp-host-WFNJQSDE.js");
283
+ const { restartCodexAspHostIfRunning: restartCodexAspHostIfRunning2 } = await import("./asp-host-LOTCGOVD.js");
284
284
  await restartCodexAspHostIfRunning2();
285
285
  }
286
286
  setAgentCredentialSnapshot("codex", data);
@@ -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-VNL2VYM4.js";
6
+ } from "./chunk-TYH3IEXZ.js";
7
7
  import {
8
8
  SUBPROCESS_MAX_BUFFER,
9
9
  execFileAsync
10
- } from "./chunk-QVK2OVNX.js";
10
+ } from "./chunk-2PMHC6IL.js";
11
11
  import {
12
12
  extractCommandProtectionCommandText,
13
13
  findCommandProtectionGitLookups,
14
14
  findGitCommitSignals,
15
15
  findPrMergeSignals,
16
16
  parseCommandProtectionRequest
17
- } from "./chunk-LKTX2CHU.js";
17
+ } from "./chunk-QU4FXL4X.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-QVK2OVNX.js";
6
+ } from "./chunk-2PMHC6IL.js";
7
7
  import {
8
8
  isVersionBelow
9
- } from "./chunk-LKTX2CHU.js";
9
+ } from "./chunk-QU4FXL4X.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.985";
243
+ var ENGINE_PACKAGE_VERSION = "0.1.987";
244
244
  var INITIALIZE_METHOD = "initialize";
245
245
  var INITIALIZED_NOTIFICATION = "initialized";
246
246
  var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
@@ -1083,7 +1083,6 @@ function getEventStreamId(event) {
1083
1083
  return null;
1084
1084
  }
1085
1085
  var CODEX_QUOTA_STATUS_EVENT_TYPE = "codex-quota-status";
1086
- var COMPACTION_STATUS_EVENT_TYPE = "compaction-status";
1087
1086
  var CHAT_INTERRUPTED_EVENT_TYPE = "replicas-interrupted";
1088
1087
  var CHAT_GOAL_EVENT_TYPE = "chat-goal";
1089
1088
  var AUTH_RETRY_STATUS_EVENT_TYPE = "auth-retry-status";
@@ -1195,7 +1194,8 @@ var subagentResponseSchema = z3.object({
1195
1194
  chatId: z3.string().uuid(),
1196
1195
  status: z3.enum(["working", "completed"]),
1197
1196
  response: z3.string().optional(),
1198
- timedOut: z3.literal(true).optional()
1197
+ timedOut: z3.literal(true).optional(),
1198
+ interruptedByUserMessage: z3.literal(true).optional()
1199
1199
  });
1200
1200
  var integrationErrorNotificationTargetSchema = z3.discriminatedUnion("type", [
1201
1201
  z3.object({
@@ -1322,6 +1322,7 @@ var CODEX_ITEM_FIELD_CHECKS = {
1322
1322
  function isCodexAspTranscriptItem(value) {
1323
1323
  if (!isRecord(value) || typeof value.id !== "string" || typeof value.timestamp !== "string") return false;
1324
1324
  if (value.sequence !== void 0 && typeof value.sequence !== "number") return false;
1325
+ if (value.completedAt !== void 0 && typeof value.completedAt !== "string") return false;
1325
1326
  if (typeof value.type !== "string" || !Object.hasOwn(CODEX_ITEM_FIELD_CHECKS, value.type)) return false;
1326
1327
  return CODEX_ITEM_FIELD_CHECKS[value.type](value);
1327
1328
  }
@@ -2046,6 +2047,7 @@ function normalizeBackgroundTaskStatus(status) {
2046
2047
 
2047
2048
  // ../shared/src/display-message/constants.ts
2048
2049
  var USER_MESSAGE_MATCH_GRACE_PERIOD_MS = 3e4;
2050
+ var COMPACTION_CONTENT = "Context compacted";
2049
2051
 
2050
2052
  // ../shared/src/shell-commands.ts
2051
2053
  var SHELL_OPAQUE = "\0";
@@ -2554,6 +2556,15 @@ function skillNamesFromCommand(command) {
2554
2556
  })));
2555
2557
  }
2556
2558
 
2559
+ // ../shared/src/user-message-matching.ts
2560
+ function parseTimestampMs(timestamp) {
2561
+ const value = typeof timestamp === "number" ? timestamp : Date.parse(timestamp);
2562
+ return Number.isFinite(value) ? value : 0;
2563
+ }
2564
+ function areUserMessagesWithinMatchWindow(a, b) {
2565
+ return a.content === b.content && Math.abs(parseTimestampMs(a.timestamp) - parseTimestampMs(b.timestamp)) <= USER_MESSAGE_MATCH_GRACE_PERIOD_MS;
2566
+ }
2567
+
2557
2568
  // ../shared/src/urls.ts
2558
2569
  var DASHBOARD_PATH = "/dashboard";
2559
2570
  var WEB_APP_PATHS = {
@@ -4911,15 +4922,6 @@ function stripAttachmentNote(text2) {
4911
4922
  };
4912
4923
  }
4913
4924
 
4914
- // ../shared/src/user-message-matching.ts
4915
- function parseTimestampMs(timestamp) {
4916
- const value = typeof timestamp === "number" ? timestamp : Date.parse(timestamp);
4917
- return Number.isFinite(value) ? value : 0;
4918
- }
4919
- function areUserMessagesWithinMatchWindow(a, b) {
4920
- return a.content === b.content && Math.abs(parseTimestampMs(a.timestamp) - parseTimestampMs(b.timestamp)) <= USER_MESSAGE_MATCH_GRACE_PERIOD_MS;
4921
- }
4922
-
4923
4925
  // ../shared/src/display-message/parsers/codex-asp-parser.ts
4924
4926
  function collectCodexTurns(events) {
4925
4927
  const turns = /* @__PURE__ */ new Map();
@@ -5092,9 +5094,10 @@ function messageForItem(item) {
5092
5094
  return {
5093
5095
  id: `reasoning-${item.id}`,
5094
5096
  type: "reasoning",
5095
- content: "Context compacted",
5097
+ content: COMPACTION_CONTENT,
5096
5098
  status: normalizeCodexAspTranscriptStatus(item.status),
5097
- timestamp: item.timestamp
5099
+ timestamp: item.timestamp,
5100
+ completedAt: item.completedAt
5098
5101
  };
5099
5102
  }
5100
5103
  return {
@@ -5919,6 +5922,15 @@ function parseMuseEvents(events) {
5919
5922
  status: terminal ? "completed" : "in_progress",
5920
5923
  timestamp: typeof item.recordedAt === "string" ? item.recordedAt : event.timestamp
5921
5924
  });
5925
+ } else if (item.kind === "compaction") {
5926
+ upsertDisplayMessage(messages, {
5927
+ id: `muse-compaction-${item.itemId}`,
5928
+ type: "reasoning",
5929
+ content: COMPACTION_CONTENT,
5930
+ status: item.status === "failed" ? "failed" : terminal ? "completed" : "in_progress",
5931
+ timestamp: typeof item.recordedAt === "string" ? item.recordedAt : event.timestamp,
5932
+ completedAt: terminal ? event.timestamp : void 0
5933
+ });
5922
5934
  } else if (item.kind === "toolCall") {
5923
5935
  let input = item.args;
5924
5936
  if (typeof item.args === "string") {
@@ -6252,6 +6264,7 @@ function parseClaudeEvents(events, parentToolUseId) {
6252
6264
  const completedStreamIds = /* @__PURE__ */ new Set();
6253
6265
  const supersededStreamIds = /* @__PURE__ */ new Set();
6254
6266
  let liveStreamId = null;
6267
+ let compaction;
6255
6268
  const assistantThinking = /* @__PURE__ */ new Map();
6256
6269
  const assistantTextCounts = /* @__PURE__ */ new Map();
6257
6270
  const acceptedUserMessageIndexes = /* @__PURE__ */ new Set();
@@ -6544,6 +6557,11 @@ function parseClaudeEvents(events, parentToolUseId) {
6544
6557
  }
6545
6558
  });
6546
6559
  }
6560
+ if (compaction && (event.type === "claude-result" || event.type === CHAT_INTERRUPTED_EVENT_TYPE)) {
6561
+ compaction.status = "failed";
6562
+ compaction.completedAt = event.timestamp;
6563
+ compaction = void 0;
6564
+ }
6547
6565
  if (event.type === "claude-result") {
6548
6566
  const payload = coerceClaudeResultPayload(event.payload);
6549
6567
  if (isClaudeResultError(payload)) {
@@ -6557,6 +6575,19 @@ function parseClaudeEvents(events, parentToolUseId) {
6557
6575
  }
6558
6576
  }
6559
6577
  if (event.type === "claude-system") {
6578
+ const { subtype, status, compact_result: compactResult } = event.payload;
6579
+ const compactionEnded = subtype === "compact_boundary" || compactResult === "failed";
6580
+ if (compactionEnded || subtype === "status" && status === "compacting") {
6581
+ if (!compaction) {
6582
+ compaction = { id: `compaction-${event.timestamp}`, type: "reasoning", content: COMPACTION_CONTENT, timestamp: event.timestamp };
6583
+ messages.push(compaction);
6584
+ }
6585
+ compaction.status = !compactionEnded ? "in_progress" : compactResult === "failed" ? "failed" : "completed";
6586
+ if (compactionEnded) {
6587
+ compaction.completedAt = event.timestamp;
6588
+ compaction = void 0;
6589
+ }
6590
+ }
6560
6591
  const payload = coerceBackgroundTaskPayload(event.payload);
6561
6592
  if (payload?.ambient) ambientTaskIds.add(payload.taskId);
6562
6593
  if (payload && !ambientTaskIds.has(payload.taskId)) {
@@ -9093,7 +9124,6 @@ export {
9093
9124
  CODEX_HISTORY_FORMAT_EVENT_TYPE,
9094
9125
  getEventStreamId,
9095
9126
  CODEX_QUOTA_STATUS_EVENT_TYPE,
9096
- COMPACTION_STATUS_EVENT_TYPE,
9097
9127
  CHAT_INTERRUPTED_EVENT_TYPE,
9098
9128
  CHAT_GOAL_EVENT_TYPE,
9099
9129
  AUTH_RETRY_STATUS_EVENT_TYPE,
@@ -9131,6 +9161,7 @@ export {
9131
9161
  paginateChatHistory,
9132
9162
  coerceBackgroundTaskPayload,
9133
9163
  fileContentDiff,
9164
+ areUserMessagesWithinMatchWindow,
9134
9165
  isGitHubUrl,
9135
9166
  parseCodeHostPrUrl,
9136
9167
  decodePathSegments,
@@ -9138,7 +9169,6 @@ export {
9138
9169
  createUuidFromHex,
9139
9170
  HISTORY_ROOT,
9140
9171
  HISTORY_INDEX_FILENAME,
9141
- areUserMessagesWithinMatchWindow,
9142
9172
  extractErrorText,
9143
9173
  isTransientErrorText,
9144
9174
  extractToolResultText,
@@ -14,7 +14,7 @@ import {
14
14
  isValidAgentProvider,
15
15
  parsePosixEnvFile,
16
16
  readReplicasRuntimeEnv
17
- } from "./chunk-LKTX2CHU.js";
17
+ } from "./chunk-QU4FXL4X.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-6ZHOSG2F.js";
7
- import "./chunk-VNL2VYM4.js";
8
- import "./chunk-QVK2OVNX.js";
6
+ } from "./chunk-G6QBYUZR.js";
7
+ import "./chunk-TYH3IEXZ.js";
8
+ import "./chunk-2PMHC6IL.js";
9
9
  import {
10
10
  isRecord
11
11
  } from "./chunk-UZSNFLDQ.js";
12
- import "./chunk-LKTX2CHU.js";
12
+ import "./chunk-QU4FXL4X.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-DAVKTMQK.js";
11
- import "./chunk-QVK2OVNX.js";
10
+ } from "./chunk-KAJYMTYA.js";
11
+ import "./chunk-2PMHC6IL.js";
12
12
  import {
13
13
  AGENT,
14
14
  headlessAgentRequestSchema
15
- } from "./chunk-LKTX2CHU.js";
15
+ } from "./chunk-QU4FXL4X.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-6ZHOSG2F.js";
35
+ } from "./chunk-G6QBYUZR.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-MWCBGGWF.js";
60
+ } from "./chunk-5EHMJ4OT.js";
61
61
  import {
62
62
  BaseRefreshManager,
63
63
  CodexAspAuthMethodChangedError,
@@ -72,7 +72,7 @@ import {
72
72
  refreshDelayMs,
73
73
  restartCodexAspHost,
74
74
  routineRefreshExclusions
75
- } from "./chunk-C3CEF7DO.js";
75
+ } from "./chunk-D5B72SUV.js";
76
76
  import {
77
77
  ENGINE_ENV,
78
78
  monolithRequest,
@@ -80,16 +80,16 @@ import {
80
80
  readRuntimeEnvFile,
81
81
  reloadEngineEnv,
82
82
  setAgentCredentialSnapshot
83
- } from "./chunk-VNL2VYM4.js";
83
+ } from "./chunk-TYH3IEXZ.js";
84
84
  import {
85
85
  AspClient,
86
86
  getCodexModelProvider
87
- } from "./chunk-DAVKTMQK.js";
87
+ } from "./chunk-KAJYMTYA.js";
88
88
  import {
89
89
  SUBPROCESS_MAX_BUFFER,
90
90
  execAsync,
91
91
  execFileAsync
92
- } from "./chunk-QVK2OVNX.js";
92
+ } from "./chunk-2PMHC6IL.js";
93
93
  import {
94
94
  isRecord as isRecord2
95
95
  } from "./chunk-UZSNFLDQ.js";
@@ -119,7 +119,6 @@ import {
119
119
  CODEX_HISTORY_FORMAT_EVENT_TYPE,
120
120
  CODEX_ITEM_EVENT_TYPE,
121
121
  CODEX_QUOTA_STATUS_EVENT_TYPE,
122
- COMPACTION_STATUS_EVENT_TYPE,
123
122
  CONTEXT_USAGE_EVENT_TYPE,
124
123
  CREDENTIAL_METHOD,
125
124
  CURSOR_CONNECTION_STATUS_EVENT_TYPE,
@@ -316,7 +315,7 @@ import {
316
315
  workspaceCredentialProviderSchema,
317
316
  workspaceTokenRequestSchema,
318
317
  workspaceTokenResponseSchema
319
- } from "./chunk-LKTX2CHU.js";
318
+ } from "./chunk-QU4FXL4X.js";
320
319
  import {
321
320
  cloneMcpConfig,
322
321
  ensureCompatibilityImports,
@@ -10167,6 +10166,7 @@ var EnvironmentDetailsService = class {
10167
10166
  details.engineVersion = ENGINE_ENV.REPLICAS_SANDBOX_IMAGE_VERSION;
10168
10167
  details.supportsMidTurnSteering = true;
10169
10168
  details.supportsClaudeMidTurnSteering = true;
10169
+ details.supportsSendNow = true;
10170
10170
  details.supportsClaudeGoals = true;
10171
10171
  details.supportsGoalUpdates = true;
10172
10172
  details.supportsQueuedMessageEditing = true;
@@ -11770,6 +11770,7 @@ var MessageQueueService = class {
11770
11770
  // version below.
11771
11771
  acceptanceWithdrawals = /* @__PURE__ */ new WeakSet();
11772
11772
  queueCancellationVersion = 0;
11773
+ activeTurn = null;
11773
11774
  constructor(processMessage, onProcessingChanged = () => {
11774
11775
  }, onMessageStarted = async () => {
11775
11776
  }) {
@@ -11864,6 +11865,8 @@ var MessageQueueService = class {
11864
11865
  return tail.merge === true && tail.type === request.type;
11865
11866
  }
11866
11867
  async startProcessing(queuedMessage) {
11868
+ const turn = new AbortController();
11869
+ this.activeTurn = turn;
11867
11870
  this.processing = true;
11868
11871
  this.onProcessingChanged(true);
11869
11872
  try {
@@ -11875,11 +11878,14 @@ var MessageQueueService = class {
11875
11878
  this.acceptanceByMessage.delete(queuedMessage);
11876
11879
  break;
11877
11880
  }
11878
- await this.onMessageStarted(queuedMessage);
11879
- await this.processMessage(queuedMessage);
11881
+ if (!turn.signal.aborted) {
11882
+ await this.onMessageStarted(queuedMessage);
11883
+ await this.processMessage(queuedMessage, turn.signal);
11884
+ }
11880
11885
  } catch (error) {
11881
11886
  console.error("[MessageQueue] Error processing message:", error);
11882
11887
  } finally {
11888
+ this.activeTurn = null;
11883
11889
  this.processing = false;
11884
11890
  if (this.queue.length === 0) {
11885
11891
  this.onProcessingChanged(false);
@@ -11899,22 +11905,15 @@ var MessageQueueService = class {
11899
11905
  isProcessing() {
11900
11906
  return this.processing;
11901
11907
  }
11902
- drainQueue(options) {
11903
- const maxItems = options?.maxItems ?? this.queue.length;
11904
- const maxChars = options?.maxChars ?? Number.POSITIVE_INFINITY;
11905
- const drained = [];
11906
- let totalChars = 0;
11907
- for (const message of this.queue) {
11908
- if (drained.length >= maxItems) break;
11909
- const text = message.message;
11910
- if (totalChars + text.length > maxChars) break;
11911
- drained.push(text);
11912
- totalChars += text.length;
11913
- }
11908
+ drainQueue() {
11909
+ const drained = this.queue;
11914
11910
  this.queueCancellationVersion++;
11915
11911
  this.queue = [];
11916
11912
  return drained;
11917
11913
  }
11914
+ cancelActiveTurn() {
11915
+ this.activeTurn?.abort();
11916
+ }
11918
11917
  /**
11919
11918
  * Get all queued messages (does not include the currently processing message)
11920
11919
  */
@@ -11974,6 +11973,16 @@ Native subagent tools are disabled in this workspace. To delegate work to a suba
11974
11973
  // src/managers/coding-agent-manager.ts
11975
11974
  var MAX_INTERRUPT_QUEUE_ITEMS = 1e3;
11976
11975
  var MAX_INTERRUPT_QUEUE_CHARS = 2e5;
11976
+ function toQueuedMessageInfo(message) {
11977
+ return {
11978
+ id: message.id,
11979
+ message: message.message,
11980
+ queuedAt: message.queuedAt,
11981
+ ...message.senderUserId ? { senderUserId: message.senderUserId } : {},
11982
+ ...message.senderEmail ? { senderEmail: message.senderEmail } : {},
11983
+ ...message.senderDisplayName ? { senderDisplayName: message.senderDisplayName } : {}
11984
+ };
11985
+ }
11977
11986
  var subagentMcpConfigRequest = null;
11978
11987
  var CodingAgentManager = class {
11979
11988
  workingDirectory;
@@ -11992,7 +12001,7 @@ var CodingAgentManager = class {
11992
12001
  authFallback;
11993
12002
  subagentMcpChatId;
11994
12003
  subagentMcpEnabledForTurn = false;
11995
- compacting = false;
12004
+ unreadSteers = /* @__PURE__ */ new Map();
11996
12005
  constructor(options) {
11997
12006
  this.workingDirectory = options.workingDirectory ?? ENGINE_ENV.WORKSPACE_ROOT;
11998
12007
  this.initialSessionId = options.initialSessionId;
@@ -12022,22 +12031,9 @@ var CodingAgentManager = class {
12022
12031
  return result;
12023
12032
  }
12024
12033
  onTurnComplete = async () => {
12025
- if (this.compacting) {
12026
- this.setCompacting(false);
12027
- }
12034
+ this.requeueUnreadSteers();
12028
12035
  await this.hostOnTurnComplete();
12029
12036
  };
12030
- isCompacting() {
12031
- return this.compacting;
12032
- }
12033
- // Dedup so a noisy stream of native compaction events doesn't spam the UI with redundant status flips.
12034
- setCompacting(active) {
12035
- if (this.compacting === active) return;
12036
- this.compacting = active;
12037
- this.emitSyntheticEvent(COMPACTION_STATUS_EVENT_TYPE, {
12038
- state: active ? "in_progress" : "completed"
12039
- });
12040
- }
12041
12037
  emitSyntheticEvent(type, payload) {
12042
12038
  this.onEvent({
12043
12039
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -12080,9 +12076,10 @@ var CodingAgentManager = class {
12080
12076
  }
12081
12077
  initializeManager(processMessage) {
12082
12078
  this.messageQueue = new MessageQueueService(
12083
- async (request) => {
12079
+ async (request, signal) => {
12084
12080
  await this.refreshSubagentMcp();
12085
- await processMessage(request);
12081
+ if (signal.aborted) return;
12082
+ await processMessage(request, signal);
12086
12083
  },
12087
12084
  this.onProcessingChanged,
12088
12085
  this.onMessageStarted
@@ -12110,46 +12107,55 @@ var CodingAgentManager = class {
12110
12107
  if (!history?.appendDurably) throw new Error("Chat history is unavailable");
12111
12108
  await history.appendDurably(event);
12112
12109
  }
12113
- async interrupt() {
12114
- const queuedMessages = this.messageQueue.getQueue();
12115
- const queue = this.messageQueue.drainQueue({
12116
- maxItems: MAX_INTERRUPT_QUEUE_ITEMS,
12117
- maxChars: MAX_INTERRUPT_QUEUE_CHARS
12118
- });
12110
+ async interrupt(persistRemoval) {
12111
+ const queuedMessages = this.messageQueue.drainQueue();
12112
+ const unreadSteers = [...this.unreadSteers.values()];
12113
+ this.unreadSteers.clear();
12114
+ this.messageQueue.cancelActiveTurn();
12119
12115
  const isProcessing = this.isProcessing();
12120
12116
  if (isProcessing) {
12121
12117
  try {
12122
12118
  await this.interruptActiveTurn();
12123
12119
  } catch (error) {
12124
- this.restoreQueuedMessages(queuedMessages.slice(0, queue.length).map((message) => ({ message, index: 0 })));
12120
+ for (const steer of unreadSteers) this.unreadSteers.set(steer.message.id, steer);
12121
+ this.restoreQueuedMessages(queuedMessages.map((message, index) => ({ message, index })));
12125
12122
  throw error;
12126
12123
  }
12127
- const event = {
12128
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
12129
- type: CHAT_INTERRUPTED_EVENT_TYPE,
12130
- payload: {}
12131
- };
12132
- this.onEvent(event);
12133
- this.getHistorySink()?.append(event);
12124
+ }
12125
+ const removed = [...unreadSteers.map(({ message }) => message), ...queuedMessages];
12126
+ if (removed.length > 0) await persistRemoval(removed.map(({ id }) => id));
12127
+ if (isProcessing) this.recordInterrupted();
12128
+ const queue = [];
12129
+ let queueChars = 0;
12130
+ for (const { message } of removed) {
12131
+ if (queue.length >= MAX_INTERRUPT_QUEUE_ITEMS || queueChars + message.length > MAX_INTERRUPT_QUEUE_CHARS) break;
12132
+ queue.push(message);
12133
+ queueChars += message.length;
12134
12134
  }
12135
12135
  this.emitInterruptedQueueEvent(queue);
12136
12136
  return {
12137
- interrupted: isProcessing || queue.length > 0,
12138
- queue
12137
+ interrupted: isProcessing || removed.length > 0,
12138
+ queue,
12139
+ removedMessageIds: removed.map(({ id }) => id)
12139
12140
  };
12140
12141
  }
12142
+ recordInterrupted() {
12143
+ const event = {
12144
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
12145
+ type: CHAT_INTERRUPTED_EVENT_TYPE,
12146
+ payload: {}
12147
+ };
12148
+ this.onEvent(event);
12149
+ this.getHistorySink()?.append(event);
12150
+ }
12141
12151
  isProcessing() {
12142
12152
  return this.messageQueue.isProcessing();
12143
12153
  }
12144
12154
  getQueue() {
12145
- return this.messageQueue.getQueue().map((m) => ({
12146
- id: m.id,
12147
- message: m.message,
12148
- queuedAt: m.queuedAt,
12149
- ...m.senderUserId ? { senderUserId: m.senderUserId } : {},
12150
- ...m.senderEmail ? { senderEmail: m.senderEmail } : {},
12151
- ...m.senderDisplayName ? { senderDisplayName: m.senderDisplayName } : {}
12152
- }));
12155
+ return [
12156
+ ...[...this.unreadSteers.values()].map(({ message, steeredAt }) => ({ ...toQueuedMessageInfo(message), steeredAt })),
12157
+ ...this.messageQueue.getQueue().map(toQueuedMessageInfo)
12158
+ ];
12153
12159
  }
12154
12160
  async removeFromQueue(messageId, persistRemoval) {
12155
12161
  return this.removeQueuedMessages([messageId], persistRemoval);
@@ -12195,33 +12201,54 @@ var CodingAgentManager = class {
12195
12201
  if (!taken) {
12196
12202
  return { success: false, queue: this.getQueue() };
12197
12203
  }
12198
- const keepQueued = this.keepSteeredMessageQueued();
12199
- if (keepQueued) {
12200
- this.messageQueue.restoreToQueue(taken.message, 0);
12201
- }
12202
12204
  let steered = false;
12203
12205
  try {
12204
12206
  steered = await this.steerRequest(taken.message);
12205
12207
  } catch (error) {
12206
12208
  console.error("[CodingAgentManager] steerRequest threw, keeping message queued:", error);
12207
12209
  }
12208
- if (!steered) {
12209
- if (keepQueued) {
12210
- const prioritized = this.messageQueue.takeFromQueue(messageId);
12211
- if (prioritized) this.restoreQueuedMessages([{ message: prioritized.message, index: taken.index }]);
12212
- } else {
12213
- this.restoreQueuedMessages([taken]);
12210
+ if (steered) this.unreadSteers.set(messageId, { message: taken.message, steeredAt: (/* @__PURE__ */ new Date()).toISOString() });
12211
+ else this.restoreQueuedMessages([{ message: taken.message, index: 0 }]);
12212
+ return { success: steered, queue: this.getQueue() };
12213
+ }
12214
+ async sendNow(messageId) {
12215
+ await this.initialized;
12216
+ const queued = this.messageQueue.getQueue().some(({ id }) => id === messageId);
12217
+ if (!queued && !this.unreadSteers.has(messageId)) return { success: false, queue: this.getQueue() };
12218
+ const turnRunning = this.messageQueue.isProcessing();
12219
+ if (turnRunning && this.canDeliverSteersNow()) {
12220
+ if (queued) await this.steerFromQueue(messageId);
12221
+ if (this.unreadSteers.has(messageId)) {
12222
+ await this.deliverSteersNow();
12223
+ return { success: true, queue: this.getQueue() };
12214
12224
  }
12215
12225
  }
12216
- return {
12217
- success: steered,
12218
- queue: this.getQueue(),
12219
- ...steered ? { steeredAt: (/* @__PURE__ */ new Date()).toISOString() } : {}
12220
- };
12226
+ this.requeueUnreadSteers();
12227
+ const taken = this.messageQueue.takeFromQueue(messageId);
12228
+ if (taken) this.messageQueue.restoreToQueue(taken.message, 0);
12229
+ if (turnRunning) {
12230
+ this.recordInterrupted();
12231
+ this.messageQueue.cancelActiveTurn();
12232
+ await this.interruptActiveTurn();
12233
+ }
12234
+ return { success: true, queue: this.getQueue() };
12221
12235
  }
12222
- keepSteeredMessageQueued() {
12236
+ requeueUnreadSteers() {
12237
+ const steers = [...this.unreadSteers.values()].map(({ message }) => message);
12238
+ this.unreadSteers.clear();
12239
+ this.restoreQueuedMessages(steers.map((message) => ({ message, index: 0 })));
12240
+ }
12241
+ markSteerRead(messageId) {
12242
+ const steer = this.unreadSteers.get(messageId);
12243
+ this.unreadSteers.delete(messageId);
12244
+ return steer?.message ?? this.messageQueue.takeFromQueue(messageId)?.message;
12245
+ }
12246
+ canDeliverSteersNow() {
12223
12247
  return false;
12224
12248
  }
12249
+ deliverSteersNow() {
12250
+ return Promise.resolve();
12251
+ }
12225
12252
  steerRequest(_request) {
12226
12253
  return Promise.resolve(false);
12227
12254
  }
@@ -13054,11 +13081,19 @@ function isClaudeSessionActive(activity) {
13054
13081
  // src/managers/claude-background-tasks.ts
13055
13082
  var ClaudeBackgroundTasks = class {
13056
13083
  active = /* @__PURE__ */ new Set();
13084
+ foreground = /* @__PURE__ */ new Set();
13057
13085
  reset() {
13058
13086
  this.active.clear();
13087
+ this.foreground.clear();
13059
13088
  }
13060
13089
  update(message) {
13061
- if (message.type !== "system" || message.subtype !== "background_tasks_changed") return;
13090
+ if (message.type !== "system") return;
13091
+ if (message.subtype === "task_started" && message.is_backgrounded === false) this.foreground.add(message.task_id);
13092
+ if (message.subtype === "task_notification") this.foreground.delete(message.task_id);
13093
+ if (message.subtype === "task_updated" && (message.patch.is_backgrounded || message.patch.status && message.patch.status !== "running")) {
13094
+ this.foreground.delete(message.task_id);
13095
+ }
13096
+ if (message.subtype !== "background_tasks_changed") return;
13062
13097
  this.active.clear();
13063
13098
  for (const task of message.tasks) {
13064
13099
  if (!task.ambient) this.active.add(task.task_id);
@@ -13378,25 +13413,31 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
13378
13413
  await this.abortAllPendingToolInputs();
13379
13414
  await this.tearDownSession(true);
13380
13415
  }
13416
+ canDeliverSteersNow() {
13417
+ return this.activeQuery !== null && this.backgroundTasks.foreground.size > 0;
13418
+ }
13419
+ async deliverSteersNow() {
13420
+ await this.activeQuery?.backgroundTasks();
13421
+ }
13381
13422
  // Claude Code folds a `next`-priority prompt into the running turn at its next tool boundary.
13382
13423
  async steerRequest(request) {
13383
13424
  const pendingTurn = this.pendingTurn;
13384
13425
  const promptStream = this.activePromptStream;
13385
13426
  if (!pendingTurn || !promptStream) return false;
13386
- if (getGoalCommand(request.message, request.goalMode)) {
13387
- this.messageQueue.restoreToQueue(request, 0);
13388
- await this.interruptActiveTurn();
13389
- return true;
13390
- }
13427
+ if (getGoalCommand(request.message, request.goalMode)) return false;
13391
13428
  const userMessage = await this.buildUserMessage(request);
13392
13429
  if (this.pendingTurn !== pendingTurn || this.activePromptStream !== promptStream) return false;
13393
13430
  pendingTurn.userMessageUuids.add(userMessage.uuid);
13394
- const steer = { ...userMessage, priority: "next" };
13395
- pendingTurn.steered.push(steer);
13396
- await this.recordEvent(userMessage, request.messageId);
13397
- promptStream.push(steer);
13431
+ pendingTurn.steers.set(userMessage.uuid, { messageId: request.id, userMessage });
13432
+ promptStream.push({ ...userMessage, priority: "next" });
13398
13433
  return true;
13399
13434
  }
13435
+ async recordSteerRead(uuid) {
13436
+ const steer = this.pendingTurn?.steers.get(uuid);
13437
+ if (!steer) return;
13438
+ this.pendingTurn?.steers.delete(uuid);
13439
+ if (this.markSteerRead(steer.messageId)) await this.recordEvent(steer.userMessage, steer.messageId);
13440
+ }
13400
13441
  async abortAllPendingToolInputs() {
13401
13442
  const pending = Array.from(this.pendingToolInputs.values());
13402
13443
  this.pendingToolInputs.clear();
@@ -13652,7 +13693,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
13652
13693
  }
13653
13694
  }
13654
13695
  }
13655
- async processMessageInternal(request) {
13696
+ async processMessageInternal(request, signal) {
13656
13697
  let lastError;
13657
13698
  let authRetryExhausted = false;
13658
13699
  let quotaFallbacks = 0;
@@ -13662,7 +13703,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
13662
13703
  let midTurnContinues = 0;
13663
13704
  let missingSessionReset = false;
13664
13705
  let currentRequest = request;
13665
- let carriedSteers = [];
13706
+ let carriedSteers = /* @__PURE__ */ new Map();
13666
13707
  await this.goalClear?.catch(() => {
13667
13708
  });
13668
13709
  try {
@@ -13670,10 +13711,10 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
13670
13711
  await this.turnCredentials?.refresh();
13671
13712
  while (true) {
13672
13713
  const attemptedCredential = this.turnCredentials ? this.turnCredentials.snapshot() : ENGINE_ENV.REPLICAS_AGENT_CREDENTIALS.claude;
13673
- const steered = carriedSteers;
13674
- carriedSteers = [];
13714
+ const steers = carriedSteers;
13715
+ carriedSteers = /* @__PURE__ */ new Map();
13675
13716
  try {
13676
- await this.executeQuery(currentRequest, { skipUserMessageRecord: attempt > 0, steered });
13717
+ await this.executeQuery(currentRequest, { skipUserMessageRecord: attempt > 0, signal, steers });
13677
13718
  return;
13678
13719
  } catch (error) {
13679
13720
  lastError = error;
@@ -13718,7 +13759,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
13718
13759
  missingSessionReset = true;
13719
13760
  console.warn(`[ClaudeManager] Session ${this.sessionId} has no transcript; starting a fresh session:`, error.message);
13720
13761
  this.sessionId = null;
13721
- carriedSteers = steered;
13762
+ carriedSteers = steers;
13722
13763
  await this.onSaveSessionId(null);
13723
13764
  await this.tearDownSession();
13724
13765
  attempt++;
@@ -13901,21 +13942,21 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
13901
13942
  if (!promptStream || !activeQuery) {
13902
13943
  throw new Error("Claude session unavailable after ensureSession");
13903
13944
  }
13904
- if (this.pendingInterrupt) {
13945
+ if (this.pendingInterrupt || options.signal?.aborted) {
13905
13946
  this.pendingInterrupt = false;
13906
13947
  return;
13907
13948
  }
13908
13949
  await new Promise((resolve7, reject) => {
13909
- const steered = options.steered ?? [];
13950
+ const steers = options.steers ?? /* @__PURE__ */ new Map();
13910
13951
  this.pendingTurn = {
13911
13952
  resolve: resolve7,
13912
13953
  reject,
13913
13954
  sawActivity: false,
13914
- steered,
13915
- userMessageUuids: /* @__PURE__ */ new Set([userMessage.uuid, ...steered.flatMap((steer) => steer.uuid ?? [])])
13955
+ steers,
13956
+ userMessageUuids: /* @__PURE__ */ new Set([userMessage.uuid, ...steers.keys()])
13916
13957
  };
13917
13958
  promptStream.push(goalCommand ? { ...userMessage, message: { role: "user", content: `/goal ${goalObjective ?? "clear"}` } } : userMessage);
13918
- for (const steer of steered) promptStream.push(steer);
13959
+ for (const { userMessage: steer } of steers.values()) promptStream.push({ ...steer, priority: "next" });
13919
13960
  });
13920
13961
  }
13921
13962
  async buildUserMessage({ message, images }) {
@@ -14138,6 +14179,10 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
14138
14179
  }
14139
14180
  const wasProcessing = this.isProcessing();
14140
14181
  this.setAuthRetrying(false);
14182
+ const lifecycle2 = msg;
14183
+ if (isRecord(lifecycle2) && lifecycle2.type === "command_lifecycle" && lifecycle2.state === "started" && typeof lifecycle2.command_uuid === "string") {
14184
+ await this.recordSteerRead(lifecycle2.command_uuid);
14185
+ }
14141
14186
  if (this.pendingTurn && _ClaudeManager.isTurnActivity(msg)) {
14142
14187
  this.pendingTurn.sawActivity = true;
14143
14188
  }
@@ -14164,6 +14209,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
14164
14209
  if (this.activeQuery !== response) break;
14165
14210
  this.sessionLinearForwarder?.flushThoughtAsResponse();
14166
14211
  const pendingUuids = this.pendingTurn?.userMessageUuids;
14212
+ for (const uuid of msg.user_message_uuids ?? []) await this.recordSteerRead(uuid);
14167
14213
  if (msg.user_message_uuid) {
14168
14214
  for (const uuid of msg.user_message_uuids ?? [msg.user_message_uuid]) pendingUuids?.delete(uuid);
14169
14215
  if (pendingUuids?.size === 0) this.resolvePendingTurn();
@@ -14579,7 +14625,6 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
14579
14625
  console.error("[ClaudeManager] Failed to record published PR:", error);
14580
14626
  });
14581
14627
  }
14582
- this.trackNativeCompaction(message);
14583
14628
  if (message.type === "system" && message.subtype === "task_started" && message.ambient) {
14584
14629
  this.ambientTaskIds.add(message.task_id);
14585
14630
  }
@@ -14609,17 +14654,6 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
14609
14654
  this.recordGoalChange(null);
14610
14655
  }
14611
14656
  }
14612
- // Claude Code intercepts /compact and auto-compaction inside the SDK process; we just observe the status messages to mirror state to the UI.
14613
- trackNativeCompaction(message) {
14614
- if (message.type !== "system") return;
14615
- if (message.subtype === "status" && message.status === "compacting") {
14616
- this.setCompacting(true);
14617
- return;
14618
- }
14619
- if (message.subtype === "compact_boundary") {
14620
- this.setCompacting(false);
14621
- }
14622
- }
14623
14657
  async recordEvent(event, replicasMessageId) {
14624
14658
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
14625
14659
  const restampAmbient = event.type === "system" && "task_id" in event && this.ambientTaskIds.has(event.task_id);
@@ -15300,6 +15334,7 @@ var CodexAspManager = class extends CodingAgentManager {
15300
15334
  slashCommandsCache = null;
15301
15335
  slashCommandsRequest = null;
15302
15336
  steeredTempImagePaths = [];
15337
+ compacting = false;
15303
15338
  steerLock = new AsyncLock();
15304
15339
  onCodexTurnStarted;
15305
15340
  onCodexTokenUsage;
@@ -15338,10 +15373,13 @@ var CodexAspManager = class extends CodingAgentManager {
15338
15373
  async subagentMcpForTurn(enabled) {
15339
15374
  return this.currentThreadId ? existsSync9(join17(SUBAGENT_MCP_THREADS_DIR, this.currentThreadId)) : enabled;
15340
15375
  }
15341
- async interruptActiveTurn() {
15376
+ async interrupt(persistRemoval) {
15342
15377
  if (this.currentGoal?.status === "active") {
15343
15378
  await this.updateGoal({ status: "paused" });
15344
15379
  }
15380
+ return super.interrupt(persistRemoval);
15381
+ }
15382
+ async interruptActiveTurn() {
15345
15383
  if (!this.currentThreadId || !this.activeTurnId) {
15346
15384
  return;
15347
15385
  }
@@ -15392,7 +15430,7 @@ var CodexAspManager = class extends CodingAgentManager {
15392
15430
  const threadId = this.currentThreadId;
15393
15431
  const expectedTurnId = this.activeTurnId;
15394
15432
  if (!threadId || !expectedTurnId || !this.messageQueue.isProcessing()) return false;
15395
- if (this.isCompacting()) return false;
15433
+ if (this.compacting) return false;
15396
15434
  if (getGoalCommand(request.message, request.goalMode) || isCompactCommand(request.message)) return false;
15397
15435
  const { input, tempImagePaths } = await buildTurnInput(request);
15398
15436
  const result = await this.requestActiveTurnWithReconciliation(
@@ -15400,7 +15438,7 @@ var CodexAspManager = class extends CodingAgentManager {
15400
15438
  expectedTurnId,
15401
15439
  (host, turnId) => host.client.request(
15402
15440
  TURN_STEER_METHOD,
15403
- { threadId, expectedTurnId: turnId, input }
15441
+ { threadId, expectedTurnId: turnId, input, clientUserMessageId: request.id }
15404
15442
  )
15405
15443
  );
15406
15444
  if (!result.ok) {
@@ -15416,7 +15454,6 @@ var CodexAspManager = class extends CodingAgentManager {
15416
15454
  } else {
15417
15455
  await removeTempImageFiles(tempImagePaths);
15418
15456
  }
15419
- this.recordUserMessageEvent(request);
15420
15457
  return true;
15421
15458
  }
15422
15459
  async getHistory(page2 = {}) {
@@ -15507,7 +15544,7 @@ var CodexAspManager = class extends CodingAgentManager {
15507
15544
  ...extraPayload
15508
15545
  }));
15509
15546
  }
15510
- async processMessageInternal(request) {
15547
+ async processMessageInternal(request, signal) {
15511
15548
  let userMessageRecorded = false;
15512
15549
  let quotaFallbackAttempted = false;
15513
15550
  const recordUserMessage = (extraPayload = {}) => {
@@ -15518,8 +15555,8 @@ var CodexAspManager = class extends CodingAgentManager {
15518
15555
  const goalCommand = getGoalCommand(request.message, request.goalMode);
15519
15556
  const dispatch = async () => {
15520
15557
  if (isCompactCommand(request.message)) {
15521
- await this.executeAspTurn(request, recordUserMessage, {
15522
- runTurn: (host, threadId) => this.observeTurn(host, threadId, request, async () => {
15558
+ await this.executeAspTurn(request, recordUserMessage, signal, {
15559
+ runTurn: (host, threadId) => this.observeTurn(host, threadId, request, signal, async () => {
15523
15560
  await host.client.request(
15524
15561
  THREAD_COMPACT_START_METHOD,
15525
15562
  { threadId }
@@ -15543,13 +15580,13 @@ var CodexAspManager = class extends CodingAgentManager {
15543
15580
  return;
15544
15581
  }
15545
15582
  if (goalCommand?.type === "set") {
15546
- await this.executeAspTurn(request, recordUserMessage, {
15547
- runTurn: (host, threadId) => this.runGoalTurn(host, threadId, request, goalCommand.objective),
15583
+ await this.executeAspTurn(request, recordUserMessage, signal, {
15584
+ runTurn: (host, threadId) => this.runGoalTurn(host, threadId, request, goalCommand.objective, signal),
15548
15585
  userMessagePayload: { command: "goal" }
15549
15586
  });
15550
15587
  return;
15551
15588
  }
15552
- await this.executeAspTurn(request, recordUserMessage);
15589
+ await this.executeAspTurn(request, recordUserMessage, signal);
15553
15590
  };
15554
15591
  let attemptedCredential = ENGINE_ENV.REPLICAS_AGENT_CREDENTIALS.codex;
15555
15592
  try {
@@ -15612,6 +15649,7 @@ var CodexAspManager = class extends CodingAgentManager {
15612
15649
  this.itemUpdateCoalescer.flushPending();
15613
15650
  this.itemUpdateCoalescer.dispose();
15614
15651
  this.activeTurnId = null;
15652
+ this.compacting = false;
15615
15653
  await removeTempImageFiles(this.steeredTempImagePaths.splice(0));
15616
15654
  await this.historyFile?.flush();
15617
15655
  await this.onTurnComplete();
@@ -15640,14 +15678,15 @@ var CodexAspManager = class extends CodingAgentManager {
15640
15678
  const threadId = await this.ensureThread(host, request, developerInstructions);
15641
15679
  return { host, threadId };
15642
15680
  }
15643
- async executeAspTurn(request, recordUserMessage, options = {}) {
15681
+ async executeAspTurn(request, recordUserMessage, signal, options = {}) {
15644
15682
  const host = await getCodexAspHost();
15645
15683
  const developerInstructions = this.buildCombinedInstructions(request.customInstructions, true);
15646
15684
  recordUserMessage(options.userMessagePayload);
15647
15685
  const threadId = await this.ensureThread(host, request, developerInstructions);
15648
15686
  const serviceTier = await this.resolveRequestedServiceTier(host, request);
15649
15687
  await this.applyThreadServiceTier(host, threadId, serviceTier);
15650
- const runTurn = options.runTurn ?? ((aspHost, aspThreadId) => this.runTurn(aspHost, aspThreadId, request, serviceTier));
15688
+ if (signal.aborted) return;
15689
+ const runTurn = options.runTurn ?? ((aspHost, aspThreadId) => this.runTurn(aspHost, aspThreadId, request, serviceTier, signal));
15651
15690
  let completedTurn;
15652
15691
  try {
15653
15692
  completedTurn = await runTurn(host, threadId);
@@ -15787,9 +15826,9 @@ ${text.slice(-24e3)}`
15787
15826
  console.warn("[CodexAspManager] Failed to apply skill registries:", error);
15788
15827
  }
15789
15828
  }
15790
- async runTurn(host, threadId, request, serviceTier) {
15829
+ async runTurn(host, threadId, request, serviceTier, signal) {
15791
15830
  const { params, tempImagePaths } = await buildTurnStartParams(threadId, request, serviceTier);
15792
- return this.observeTurn(host, threadId, request, async () => {
15831
+ return this.observeTurn(host, threadId, request, signal, async () => {
15793
15832
  const turnStartResponse = await host.client.request(
15794
15833
  TURN_START_METHOD,
15795
15834
  params
@@ -15797,8 +15836,8 @@ ${text.slice(-24e3)}`
15797
15836
  return { turn: turnStartResponse.turn, tempImagePaths };
15798
15837
  });
15799
15838
  }
15800
- async runGoalTurn(host, threadId, request, objective) {
15801
- return this.observeTurn(host, threadId, request, async () => {
15839
+ async runGoalTurn(host, threadId, request, objective, signal) {
15840
+ return this.observeTurn(host, threadId, request, signal, async () => {
15802
15841
  const response = await host.client.request(
15803
15842
  THREAD_GOAL_SET_METHOD,
15804
15843
  { threadId, objective, status: "active" }
@@ -15807,7 +15846,15 @@ ${text.slice(-24e3)}`
15807
15846
  return { turn: null, tempImagePaths: [] };
15808
15847
  });
15809
15848
  }
15810
- async observeTurn(host, threadId, request, startTurn) {
15849
+ async observeTurn(host, threadId, request, signal, startTurn) {
15850
+ let stopSent = false;
15851
+ const interruptIfStopped = () => {
15852
+ if (!signal.aborted || stopSent) return;
15853
+ stopSent = true;
15854
+ void this.interruptActiveTurn().catch((error) => {
15855
+ console.error("[CodexAspManager] Failed to interrupt a turn stopped before it started:", error);
15856
+ });
15857
+ };
15811
15858
  let resolveCompleted;
15812
15859
  const completed = new Promise((resolve7) => {
15813
15860
  resolveCompleted = resolve7;
@@ -15861,7 +15908,7 @@ ${text.slice(-24e3)}`
15861
15908
  },
15862
15909
  [THREAD_COMPACTED_METHOD]: (notification) => {
15863
15910
  if (!matchesTurn(notification.params.threadId, notification.params.turnId)) return;
15864
- this.setCompacting(false);
15911
+ this.compacting = false;
15865
15912
  },
15866
15913
  [TURN_STARTED_METHOD]: (notification) => {
15867
15914
  if (notification.params.threadId !== threadId) return;
@@ -15873,6 +15920,7 @@ ${text.slice(-24e3)}`
15873
15920
  completedItems.length = 0;
15874
15921
  agentMessageDeltas.clear();
15875
15922
  this.startTurn(notification.params.turn);
15923
+ interruptIfStopped();
15876
15924
  linearForwarder.sendEvent(convertCodexAspNotification(notification, linearSessionId ?? ""));
15877
15925
  },
15878
15926
  [TURN_PLAN_UPDATED_METHOD]: (notification) => {
@@ -15881,8 +15929,10 @@ ${text.slice(-24e3)}`
15881
15929
  },
15882
15930
  [ITEM_STARTED_METHOD]: (notification) => {
15883
15931
  if (!matchesTurn(notification.params.threadId, notification.params.turnId)) return;
15884
- if (notification.params.item.type === "contextCompaction") {
15885
- this.setCompacting(true);
15932
+ if (notification.params.item.type === "contextCompaction") this.compacting = true;
15933
+ if (notification.params.item.type === "userMessage" && notification.params.item.clientId) {
15934
+ const steer = this.markSteerRead(notification.params.item.clientId);
15935
+ if (steer) this.recordUserMessageEvent(steer);
15886
15936
  }
15887
15937
  this.startItem(
15888
15938
  notification.params.turnId,
@@ -15893,9 +15943,7 @@ ${text.slice(-24e3)}`
15893
15943
  [ITEM_COMPLETED_METHOD]: (notification) => {
15894
15944
  if (!matchesTurn(notification.params.threadId, notification.params.turnId)) return;
15895
15945
  completedItems.push(notification.params.item);
15896
- if (notification.params.item.type === "contextCompaction") {
15897
- this.setCompacting(false);
15898
- }
15946
+ if (notification.params.item.type === "contextCompaction") this.compacting = false;
15899
15947
  const itemFailed = notification.params.item.type === "commandExecution" && typeof notification.params.item.exitCode === "number" && notification.params.item.exitCode !== 0;
15900
15948
  this.completeItem(
15901
15949
  notification.params.turnId,
@@ -15989,6 +16037,7 @@ ${text.slice(-24e3)}`
15989
16037
  this.activeTurnId = started.turn.id;
15990
16038
  this.onCodexTurnStarted?.(threadId, started.turn.id);
15991
16039
  this.startTurn(started.turn);
16040
+ interruptIfStopped();
15992
16041
  }
15993
16042
  const turn = await Promise.race([completed, disposed]);
15994
16043
  linearForwarder.flushThoughtAsResponse();
@@ -16197,8 +16246,8 @@ ${text.slice(-24e3)}`
16197
16246
  const live = turn.items.get(item.id);
16198
16247
  turn.items.delete(item.id);
16199
16248
  turn.persistedItemIds.add(item.id);
16200
- const timestamp = live?.timestamp ?? item.timestamp;
16201
- const persisted = item.type === "subagent" && item.status === "completed" ? { ...item, timestamp, status: "in_progress" } : { ...item, timestamp };
16249
+ const completed = live ? { ...item, timestamp: live.timestamp, completedAt: item.timestamp } : item;
16250
+ const persisted = completed.type === "subagent" && completed.status === "completed" ? { ...completed, status: "in_progress" } : completed;
16202
16251
  if (persisted.type === "subagent" && persisted.status === "in_progress") {
16203
16252
  const subagent = { turnId, item: persisted, active: new Set(persisted.receiverThreadIds), failed: false };
16204
16253
  for (const threadId of subagent.active) this.codexSubagents.set(threadId, subagent);
@@ -17163,7 +17212,7 @@ ${typeof pending.subject.command === "string" ? pending.subject.command : JSON.s
17163
17212
  }, this.historyFile);
17164
17213
  }
17165
17214
  }
17166
- async processMessageInternal(request) {
17215
+ async processMessageInternal(request, signal) {
17167
17216
  let userMessageRecorded = false;
17168
17217
  const dispatch = async () => {
17169
17218
  const model = request.model ?? DEFAULT_MUSE_MODEL;
@@ -17186,7 +17235,6 @@ ${typeof pending.subject.command === "string" ? pending.subject.command : JSON.s
17186
17235
  sessionId: this.sessionId
17187
17236
  });
17188
17237
  if (isRecord(result2) && result2.status === "accepted") {
17189
- this.setCompacting(true);
17190
17238
  await completed2;
17191
17239
  } else {
17192
17240
  this.compactionComplete = null;
@@ -17210,6 +17258,7 @@ ${pending}
17210
17258
  </workspace-instructions>
17211
17259
 
17212
17260
  ` : ""}${request.planMode ? "Plan the requested work without modifying files or running mutating commands.\n\n" : ""}${request.message}`;
17261
+ if (signal.aborted) return;
17213
17262
  this.turnError = null;
17214
17263
  const completed = new Promise((resolve7) => {
17215
17264
  this.turnComplete = { resolve: resolve7 };
@@ -17275,17 +17324,6 @@ ${pending}
17275
17324
  turnId: this.activeTurnId
17276
17325
  });
17277
17326
  }
17278
- async steerRequest(request) {
17279
- if (!this.sessionId || !this.activeTurnId) return false;
17280
- await this.request("turn/steer", {
17281
- commandId: uuidV7(),
17282
- sessionId: this.sessionId,
17283
- expectedTurnId: this.activeTurnId,
17284
- input: this.createTurnInput(request.message, request.images),
17285
- reasoningEffort: codexReasoningEffortForThinkingLevel(request.thinkingLevel)
17286
- });
17287
- return true;
17288
- }
17289
17327
  async respondToToolInput(requestId, selectionId) {
17290
17328
  const approval = [...this.pendingApprovals.values()].find((pending2) => pending2.requestId === requestId);
17291
17329
  if (approval) {
@@ -58712,13 +58750,6 @@ var PiManager = class extends CodingAgentManager {
58712
58750
  async interruptActiveTurn() {
58713
58751
  await this.session?.abort();
58714
58752
  }
58715
- async steerRequest(request) {
58716
- const session = this.session;
58717
- if (!session?.isStreaming) return false;
58718
- await session.steer(request.message);
58719
- this.recordUserMessage(session, request);
58720
- return true;
58721
- }
58722
58753
  async getHistory(page2 = {}) {
58723
58754
  await this.historyFile.flush();
58724
58755
  const history = await this.historyFile.loadEventsPage(page2);
@@ -58903,7 +58934,7 @@ var PiManager = class extends CodingAgentManager {
58903
58934
  skillName ? { skillName } : {}
58904
58935
  );
58905
58936
  }
58906
- async processMessageInternal(request) {
58937
+ async processMessageInternal(request, signal) {
58907
58938
  try {
58908
58939
  const session = await this.ensureSession(request);
58909
58940
  this.recordUserMessage(session, request);
@@ -58912,6 +58943,7 @@ var PiManager = class extends CodingAgentManager {
58912
58943
  data: image.source.data,
58913
58944
  mimeType: image.source.media_type
58914
58945
  })) : void 0;
58946
+ if (signal.aborted) return;
58915
58947
  await session.prompt(request.message, images ? { images } : void 0);
58916
58948
  } catch (error) {
58917
58949
  this.recordHistoryEvent("pi-error", { message: error instanceof Error ? error.message : String(error) }, this.historyFile);
@@ -60872,6 +60904,9 @@ var ChatService = class {
60872
60904
  if (signal?.aborted) throw new Error("Subagent wait was cancelled");
60873
60905
  const result = await this.getSubagent(parentChatId, chatId);
60874
60906
  if (result.status === "completed") return result;
60907
+ if (this.requireChat(parentChatId).provider.getQueue().some(({ steeredAt }) => steeredAt && Date.parse(steeredAt) >= startedAt)) {
60908
+ return { ...result, interruptedByUserMessage: true };
60909
+ }
60875
60910
  const remainingMs = timeoutMs === null ? null : timeoutMs - (Date.now() - startedAt);
60876
60911
  if (remainingMs !== null && remainingMs <= 0) return { ...result, timedOut: true };
60877
60912
  await new Promise((resolve7) => setTimeout(
@@ -61054,7 +61089,7 @@ var ChatService = class {
61054
61089
  }
61055
61090
  async interrupt(chatId) {
61056
61091
  const chat = this.requireChat(chatId);
61057
- const result = await chat.provider.interrupt();
61092
+ const result = await chat.provider.interrupt((messageIds) => this.forgetPendingMessages(chat, messageIds));
61058
61093
  this.endTrackedTurn(chatId);
61059
61094
  chat.hasActiveTurn = false;
61060
61095
  chat.activeMessageId = null;
@@ -61076,7 +61111,7 @@ var ChatService = class {
61076
61111
  if (!chat.provider.clearGoal) {
61077
61112
  return { interrupted: false, queue: [], goal: null };
61078
61113
  }
61079
- const interruptResult = await chat.provider.interrupt();
61114
+ const interruptResult = await chat.provider.interrupt((messageIds) => this.forgetPendingMessages(chat, messageIds));
61080
61115
  this.endTrackedTurn(chatId);
61081
61116
  chat.hasActiveTurn = false;
61082
61117
  chat.activeMessageId = null;
@@ -61224,6 +61259,14 @@ var ChatService = class {
61224
61259
  }
61225
61260
  return result;
61226
61261
  }
61262
+ async sendNow(chatId, messageId) {
61263
+ const chat = this.requireChat(chatId);
61264
+ const result = await chat.provider.sendNow(messageId);
61265
+ if (result.success && !result.queue.some(({ id, steeredAt }) => id === messageId && !steeredAt)) {
61266
+ chat.pendingMessageIds = chat.pendingMessageIds.filter((pendingMessageId) => pendingMessageId !== messageId);
61267
+ }
61268
+ return result;
61269
+ }
61227
61270
  async respondToToolInput(chatId, requestId, selectionId) {
61228
61271
  const chat = this.requireChat(chatId);
61229
61272
  if (!chat.provider.respondToToolInput) {
@@ -61239,7 +61282,7 @@ var ChatService = class {
61239
61282
  const target = this.chats.get(id);
61240
61283
  if (!target || target.persisted.deletedAt) continue;
61241
61284
  if (target.provider.isProcessing()) {
61242
- await target.provider.interrupt().catch(() => {
61285
+ await target.provider.interrupt((messageIds) => this.forgetPendingMessages(target, messageIds)).catch(() => {
61243
61286
  });
61244
61287
  target.hasActiveTurn = false;
61245
61288
  target.activeMessageId = null;
@@ -61681,7 +61724,6 @@ var ChatService = class {
61681
61724
  processing: hasActiveChatTurn(chat),
61682
61725
  activityTrackingVersion: 1,
61683
61726
  awaitingInput: chat.provider.isAwaitingInput?.() ?? false,
61684
- isCompacting: chat.provider.isCompacting?.() ?? false,
61685
61727
  isAuthRetrying: chat.provider.isAuthRetrying?.() ?? false,
61686
61728
  mayBeReconnecting: chat.provider.mayBeReconnecting?.() ?? false,
61687
61729
  goal: chat.provider.getGoal?.() ?? null,
@@ -61809,7 +61851,7 @@ var ChatService = class {
61809
61851
  }
61810
61852
  }).catch(() => {
61811
61853
  });
61812
- if (event.type === "replicas-tool-input-request" || event.type === "replicas-tool-input-resolved" || event.type === "compaction-status" || event.type === AUTH_RETRY_STATUS_EVENT_TYPE || event.type === AUTH_FALLBACK_EVENT_TYPE || event.type === CURSOR_CONNECTION_STATUS_EVENT_TYPE || event.type === CHAT_GOAL_EVENT_TYPE || event.type === "claude-result" || event.type === "claude-system" && (event.payload.subtype === "init" || event.payload.subtype === "session_state_changed" || event.payload.subtype === "background_tasks_changed" || coerceBackgroundTaskPayload(event.payload) !== null)) {
61854
+ if (event.type === "replicas-tool-input-request" || event.type === "replicas-tool-input-resolved" || event.type === AUTH_RETRY_STATUS_EVENT_TYPE || event.type === AUTH_FALLBACK_EVENT_TYPE || event.type === CURSOR_CONNECTION_STATUS_EVENT_TYPE || event.type === CHAT_GOAL_EVENT_TYPE || event.type === "claude-result" || event.type === "claude-system" && (event.payload.subtype === "init" || event.payload.subtype === "session_state_changed" || event.payload.subtype === "background_tasks_changed" || coerceBackgroundTaskPayload(event.payload) !== null)) {
61813
61855
  this.publish({
61814
61856
  type: "chat.updated",
61815
61857
  payload: { chat: this.toSummary(chat) }
@@ -63525,17 +63567,21 @@ function createV1Routes(deps) {
63525
63567
  return c.json(jsonError("Failed to update queued message", error instanceof Error ? error.message : "Unknown error"), 500);
63526
63568
  }
63527
63569
  });
63528
- app2.post("/chats/:chatId/queue/:messageId/steer", async (c) => {
63529
- try {
63530
- const result = await deps.chatService.steerFromQueue(c.req.param("chatId"), c.req.param("messageId"));
63531
- return c.json(result);
63532
- } catch (error) {
63533
- if (error instanceof ChatNotFoundError) {
63534
- return c.json(jsonError("Chat not found", error.message), 404);
63570
+ for (const { action, failure, run } of [
63571
+ { action: "steer", failure: "Failed to steer queued message", run: (chatId, messageId) => deps.chatService.steerFromQueue(chatId, messageId) },
63572
+ { action: "send-now", failure: "Failed to send queued message now", run: (chatId, messageId) => deps.chatService.sendNow(chatId, messageId) }
63573
+ ]) {
63574
+ app2.post(`/chats/:chatId/queue/:messageId/${action}`, async (c) => {
63575
+ try {
63576
+ return c.json(await run(c.req.param("chatId"), c.req.param("messageId")));
63577
+ } catch (error) {
63578
+ if (error instanceof ChatNotFoundError) {
63579
+ return c.json(jsonError("Chat not found", error.message), 404);
63580
+ }
63581
+ return c.json(jsonError(failure, error instanceof Error ? error.message : "Unknown error"), 500);
63535
63582
  }
63536
- return c.json(jsonError("Failed to steer queued message", error instanceof Error ? error.message : "Unknown error"), 500);
63537
- }
63538
- });
63583
+ });
63584
+ }
63539
63585
  app2.post("/chats/:chatId/tool-input", async (c) => {
63540
63586
  try {
63541
63587
  const body = respondToolInputSchema.parse(await c.req.json());
@@ -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-MWCBGGWF.js";
6
+ } from "./chunk-5EHMJ4OT.js";
7
7
  import {
8
8
  isRecord
9
9
  } from "./chunk-UZSNFLDQ.js";
10
10
  import {
11
11
  shouldRefreshPullRequests
12
- } from "./chunk-LKTX2CHU.js";
12
+ } from "./chunk-QU4FXL4X.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-LKTX2CHU.js";
12
+ } from "./chunk-QU4FXL4X.js";
13
13
  import "./chunk-VEQXQN22.js";
14
14
 
15
15
  // src/subagent-mcp.ts
@@ -101,7 +101,7 @@ server.registerTool(SUBAGENT_MCP_TOOLS.getAgent, {
101
101
  content: [{ type: "text", text: await request(`/chats/${parentChatId}/subagents/${chat_id}`, "GET") }]
102
102
  }));
103
103
  server.registerTool(SUBAGENT_MCP_TOOLS.waitAgent, {
104
- description: "Wait for one of this chat\u2019s subagents to finish over a heartbeat stream. A timeout stops waiting without interrupting the child.",
104
+ description: "Wait for one of this chat\u2019s subagents to finish over a heartbeat stream. A timeout, or a new message from the user, stops waiting without interrupting the child.",
105
105
  inputSchema: {
106
106
  chat_id: z.string().uuid(),
107
107
  timeout_minutes: waitSubagentRequestSchema.shape.timeoutMinutes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.985",
3
+ "version": "0.1.987",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",