replicas-engine 0.1.648 → 0.1.650

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.
@@ -5922,7 +5922,7 @@ var DEFAULT_CODEX_ARGS = [
5922
5922
  var MIN_CODEX_CLI_VERSION = "0.144.6";
5923
5923
  var CODEX_UPGRADE_TIMEOUT_MS = 12e4;
5924
5924
  var codexCliVersionEnsured = null;
5925
- var ENGINE_PACKAGE_VERSION = "0.1.648";
5925
+ var ENGINE_PACKAGE_VERSION = "0.1.650";
5926
5926
  var INITIALIZE_METHOD = "initialize";
5927
5927
  var INITIALIZED_NOTIFICATION = "initialized";
5928
5928
  var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
@@ -7,7 +7,7 @@ import {
7
7
  isUnsafeMemoryOutput,
8
8
  putPresignedFile,
9
9
  recoverCompletedTurn
10
- } from "./chunk-UOZJPW3V.js";
10
+ } from "./chunk-2TB75JBR.js";
11
11
 
12
12
  // src/headless-agent.ts
13
13
  import { createHash } from "crypto";
package/dist/src/index.js CHANGED
@@ -172,7 +172,7 @@ import {
172
172
  serializeCanvasContentResponse,
173
173
  shellQuotePosix,
174
174
  stripAgentDiagnosticErrors
175
- } from "./chunk-UOZJPW3V.js";
175
+ } from "./chunk-2TB75JBR.js";
176
176
 
177
177
  // src/index.ts
178
178
  import { serve } from "@hono/node-server";
@@ -3518,7 +3518,11 @@ var CodingAgentManager = class {
3518
3518
  this.initialized = this.initialize();
3519
3519
  }
3520
3520
  async interrupt() {
3521
- const { queue, isProcessing } = this.drainQueueForInterrupt();
3521
+ const queue = this.messageQueue.drainQueue({
3522
+ maxItems: MAX_INTERRUPT_QUEUE_ITEMS,
3523
+ maxChars: MAX_INTERRUPT_QUEUE_CHARS
3524
+ });
3525
+ const isProcessing = this.isProcessing();
3522
3526
  if (isProcessing) {
3523
3527
  await this.interruptActiveTurn();
3524
3528
  const event = {
@@ -3638,16 +3642,6 @@ var CodingAgentManager = class {
3638
3642
  }
3639
3643
  return parts.length > 0 ? parts.join("\n\n") : void 0;
3640
3644
  }
3641
- drainQueueForInterrupt() {
3642
- const queue = this.messageQueue.drainQueue({
3643
- maxItems: MAX_INTERRUPT_QUEUE_ITEMS,
3644
- maxChars: MAX_INTERRUPT_QUEUE_CHARS
3645
- });
3646
- return {
3647
- queue,
3648
- isProcessing: this.messageQueue.isProcessing()
3649
- };
3650
- }
3651
3645
  emitInterruptedQueueEvent(queue) {
3652
3646
  const linearSessionId = ENGINE_ENV.REPLICAS_LINEAR_SESSION_ID;
3653
3647
  if (!linearSessionId || queue.length === 0) {
@@ -3744,6 +3738,7 @@ function buildClaudeAgentEnv(overrides) {
3744
3738
  env.GH_CONFIG_DIR = void 0;
3745
3739
  env.CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD = "1";
3746
3740
  env.CLAUDE_CODE_DISABLE_AUTO_MEMORY = "1";
3741
+ env.CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS = "1";
3747
3742
  return env;
3748
3743
  }
3749
3744
  function shouldStripAnthropicApiKey(isBedrock) {
@@ -4513,6 +4508,9 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
4513
4508
  backgroundContinuationTimer = null;
4514
4509
  pendingInterrupt = false;
4515
4510
  activeBackgroundTasks = /* @__PURE__ */ new Set();
4511
+ // Set when background tasks outlive a turn; cleared once their completion
4512
+ // has been reported through onTurnComplete.
4513
+ pendingBackgroundCompletion = false;
4516
4514
  partialMessageStreams = /* @__PURE__ */ new Map();
4517
4515
  partialMessageFlushTimers = /* @__PURE__ */ new Map();
4518
4516
  systemPromptOverride;
@@ -4581,7 +4579,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
4581
4579
  return super.isProcessing() || isClaudeSessionActive(this.sessionActivity) || this.activeBackgroundTasks.size > 0;
4582
4580
  }
4583
4581
  hasActiveBackgroundTasks() {
4584
- return this.activeBackgroundTasks.size > 0;
4582
+ return this.activeBackgroundTasks.size > 0 || this.pendingBackgroundCompletion && isClaudeSessionActive(this.sessionActivity) && !this.messageQueue.isProcessing();
4585
4583
  }
4586
4584
  async listSlashCommands() {
4587
4585
  await this.initialized;
@@ -5189,6 +5187,9 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
5189
5187
  if (hadActiveBackgroundTasks && this.activeBackgroundTasks.size === 0) {
5190
5188
  this.startBackgroundContinuationGrace();
5191
5189
  }
5190
+ if (!this.pendingTurn && this.activeBackgroundTasks.size > 0) {
5191
+ this.pendingBackgroundCompletion = true;
5192
+ }
5192
5193
  await this.handleMessage(msg);
5193
5194
  if (linearSessionId && this.sessionLinearForwarder) {
5194
5195
  this.sessionLinearForwarder.processClaudeEventForPlan(msg);
@@ -5199,6 +5200,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
5199
5200
  this.sessionLinearForwarder?.flushThoughtAsResponse();
5200
5201
  this.resolvePendingTurn();
5201
5202
  }
5203
+ this.maybeCompleteBackgroundTurn();
5202
5204
  }
5203
5205
  } catch (error) {
5204
5206
  loopError = error;
@@ -5219,7 +5221,11 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
5219
5221
  }
5220
5222
  this.failPendingTurn(pendingError);
5221
5223
  if (this.activeQuery === response) {
5224
+ const orphanedBackgroundCompletion = this.pendingBackgroundCompletion && !this.messageQueue.isProcessing();
5222
5225
  this.clearSessionState();
5226
+ if (orphanedBackgroundCompletion) {
5227
+ this.notifyBackgroundTurnComplete();
5228
+ }
5223
5229
  }
5224
5230
  }
5225
5231
  }
@@ -5234,6 +5240,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
5234
5240
  this.clearBackgroundContinuationTimer();
5235
5241
  resetClaudeSessionActivity(this.sessionActivity);
5236
5242
  this.activeBackgroundTasks.clear();
5243
+ this.pendingBackgroundCompletion = false;
5237
5244
  for (const timer of this.partialMessageFlushTimers.values()) {
5238
5245
  clearTimeout(timer);
5239
5246
  }
@@ -5245,8 +5252,19 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
5245
5252
  if (!pending) return;
5246
5253
  this.pendingTurn = null;
5247
5254
  this.pendingInterrupt = false;
5255
+ this.pendingBackgroundCompletion = this.activeBackgroundTasks.size > 0;
5248
5256
  pending.resolve();
5249
5257
  }
5258
+ maybeCompleteBackgroundTurn() {
5259
+ if (!this.pendingBackgroundCompletion || this.pendingTurn || this.messageQueue.isProcessing() || this.activeBackgroundTasks.size > 0 || isClaudeSessionActive(this.sessionActivity)) return;
5260
+ this.pendingBackgroundCompletion = false;
5261
+ this.notifyBackgroundTurnComplete();
5262
+ }
5263
+ notifyBackgroundTurnComplete() {
5264
+ void this.historyFile.flush().then(() => this.onTurnComplete()).catch((error) => {
5265
+ console.error("[ClaudeManager] Background turn completion failed:", error);
5266
+ });
5267
+ }
5250
5268
  failPendingTurn(err) {
5251
5269
  const pending = this.pendingTurn;
5252
5270
  if (!pending) return;
@@ -5261,6 +5279,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
5261
5279
  this.backgroundContinuationTimer = null;
5262
5280
  if (this.activeBackgroundTasks.size > 0 || !expireClaudeSessionFallbackContinuation(this.sessionActivity)) return;
5263
5281
  this.emitSyntheticEvent(CLAUDE_ACTIVITY_STATUS_EVENT_TYPE, { state: "idle" });
5282
+ this.maybeCompleteBackgroundTurn();
5264
5283
  }, CLAUDE_BACKGROUND_CONTINUATION_GRACE_MS);
5265
5284
  }
5266
5285
  clearBackgroundContinuationTimer() {
@@ -5684,6 +5703,11 @@ var ACCOUNT_RATE_LIMITS_READ_METHOD = "account/rateLimits/read";
5684
5703
  var MODEL_LIST_METHOD = "model/list";
5685
5704
  var SKILLS_LIST_METHOD = "skills/list";
5686
5705
  var MAX_CODEX_ASP_TRANSCRIPT_OUTPUT_CHARS = DEFAULT_HOOK_OUTPUT_PREVIEW_CHARS;
5706
+ function getReconciledActiveTurnId(error, expectedTurnId) {
5707
+ const message = error instanceof Error ? error.message : String(error);
5708
+ const match = message.match(/expected active turn id ([0-9a-f-]{36}) but found ([0-9a-f-]{36})/i);
5709
+ return match?.[1]?.toLowerCase() === expectedTurnId.toLowerCase() ? match[2] ?? null : null;
5710
+ }
5687
5711
  function codexApprovalPolicyOverrides() {
5688
5712
  return {
5689
5713
  approvalPolicy: "untrusted",
@@ -6433,15 +6457,43 @@ var CodexAspManager = class extends CodingAgentManager {
6433
6457
  if (!this.currentThreadId || !this.activeTurnId) {
6434
6458
  return;
6435
6459
  }
6460
+ const threadId = this.currentThreadId;
6461
+ const expectedTurnId = this.activeTurnId;
6462
+ const result = await this.requestActiveTurnWithReconciliation(
6463
+ "interrupt",
6464
+ expectedTurnId,
6465
+ (host, turnId) => host.client.request(
6466
+ TURN_INTERRUPT_METHOD,
6467
+ { threadId, turnId }
6468
+ )
6469
+ );
6470
+ if (result.ok) return;
6471
+ console.warn(
6472
+ result.error.code === "retry_failed" ? "[CodexAspManager] Failed to interrupt reconciled active turn; leaving shared Codex app-server running:" : "[CodexAspManager] Failed to interrupt active turn:",
6473
+ result.error.details
6474
+ );
6475
+ }
6476
+ async requestActiveTurnWithReconciliation(operation, expectedTurnId, request) {
6436
6477
  try {
6437
- const host = await getCodexAspHost();
6438
- const params = {
6439
- threadId: this.currentThreadId,
6440
- turnId: this.activeTurnId
6441
- };
6442
- await host.client.request(TURN_INTERRUPT_METHOD, params);
6478
+ await request(await getCodexAspHost(), expectedTurnId);
6479
+ return createSuccessResult(expectedTurnId);
6443
6480
  } catch (error) {
6444
- console.warn("[CodexAspManager] Failed to interrupt active turn:", error);
6481
+ const reconciledTurnId = getReconciledActiveTurnId(error, expectedTurnId);
6482
+ if (!reconciledTurnId) {
6483
+ return createErrorResult({ code: "request_failed", message: `Failed to ${operation} active turn`, details: error });
6484
+ }
6485
+ this.activeTurnId = reconciledTurnId;
6486
+ console.warn(`[CodexAspManager] Active turn changed during ${operation}; retrying with provider turn:`, reconciledTurnId);
6487
+ try {
6488
+ await request(await getCodexAspHost(), reconciledTurnId);
6489
+ return createSuccessResult(reconciledTurnId);
6490
+ } catch (retryError) {
6491
+ return createErrorResult({
6492
+ code: "retry_failed",
6493
+ message: `Failed to ${operation} reconciled active turn`,
6494
+ details: retryError
6495
+ });
6496
+ }
6445
6497
  }
6446
6498
  }
6447
6499
  // Serialize so concurrent steers can't race one `turn/steer` RPC ahead of another.
@@ -6455,18 +6507,23 @@ var CodexAspManager = class extends CodingAgentManager {
6455
6507
  if (this.isCompacting()) return false;
6456
6508
  if (getGoalCommand(request.message, request.goalMode) || isCompactCommand(request.message)) return false;
6457
6509
  const { input, tempImagePaths } = await buildTurnInput(request);
6458
- try {
6459
- const host = await getCodexAspHost();
6460
- await host.client.request(
6510
+ const result = await this.requestActiveTurnWithReconciliation(
6511
+ "steer",
6512
+ expectedTurnId,
6513
+ (host, turnId) => host.client.request(
6461
6514
  TURN_STEER_METHOD,
6462
- { threadId, expectedTurnId, input }
6463
- );
6464
- } catch (error) {
6515
+ { threadId, expectedTurnId: turnId, input }
6516
+ )
6517
+ );
6518
+ if (!result.ok) {
6465
6519
  await removeTempImageFiles(tempImagePaths);
6466
- console.warn("[CodexAspManager] Failed to steer active turn, keeping message queued:", error);
6520
+ console.warn(
6521
+ result.error.code === "retry_failed" ? "[CodexAspManager] Failed to steer reconciled active turn, keeping message queued:" : "[CodexAspManager] Failed to steer active turn, keeping message queued:",
6522
+ result.error.details
6523
+ );
6467
6524
  return false;
6468
6525
  }
6469
- if (this.activeTurnId === expectedTurnId) {
6526
+ if (this.activeTurnId === result.data) {
6470
6527
  this.steeredTempImagePaths.push(...tempImagePaths);
6471
6528
  } else {
6472
6529
  await removeTempImageFiles(tempImagePaths);
@@ -6842,7 +6899,7 @@ var CodexAspManager = class extends CodingAgentManager {
6842
6899
  const model = request.model ?? DEFAULT_MODEL;
6843
6900
  let tempImagePaths = [];
6844
6901
  const linearForwarder = new LinearEventForwarder(linearSessionId);
6845
- const matchesTurn = (notificationThreadId, notificationTurnId) => notificationThreadId === threadId && (!observedTurnId || notificationTurnId === null || notificationTurnId === observedTurnId);
6902
+ const matchesTurn = (notificationThreadId, notificationTurnId) => notificationThreadId === threadId && (!observedTurnId || notificationTurnId === null || notificationTurnId === observedTurnId || notificationTurnId === this.activeTurnId);
6846
6903
  const resolveIfGoalIdle = () => {
6847
6904
  if (!lastCompletedTurn || completedResolved) return;
6848
6905
  if (!options.waitForActiveGoal || this.currentGoal?.status !== "active") {
@@ -6994,7 +7051,8 @@ var CodexAspManager = class extends CodingAgentManager {
6994
7051
  },
6995
7052
  [TURN_COMPLETED_METHOD]: (notification) => {
6996
7053
  if (notification.params.threadId !== threadId) return;
6997
- if (!observedTurnIds.has(notification.params.turn.id)) return;
7054
+ if (!observedTurnIds.has(notification.params.turn.id) && notification.params.turn.id !== this.activeTurnId) return;
7055
+ observedTurnIds.add(notification.params.turn.id);
6998
7056
  observedTurnId = notification.params.turn.id;
6999
7057
  const completedTurn = recoverCompletedTurn(notification.params.turn, completedItems, agentMessageDeltas);
7000
7058
  this.mergeTranscriptTurn(notification.params.threadId, completedTurn);
@@ -10645,7 +10703,7 @@ function hasActiveAgentWork(chat) {
10645
10703
  return chat.provider.isProcessing() || (chat.provider.hasActiveBackgroundTasks?.() ?? false);
10646
10704
  }
10647
10705
  function hasActiveChatTurn(chat) {
10648
- return chat.hasActiveTurn || chat.pendingMessageIds.length > 0;
10706
+ return chat.hasActiveTurn || chat.pendingMessageIds.length > 0 || (chat.provider.hasActiveBackgroundTasks?.() ?? false);
10649
10707
  }
10650
10708
  function isPersistedChat(value) {
10651
10709
  if (!isRecord2(value)) {
@@ -11392,17 +11450,13 @@ var ChatService = class {
11392
11450
  if (!chat.hasActiveTurn) {
11393
11451
  const queuedMessageIds = new Set(chat.provider.getQueue().map((message) => message.id));
11394
11452
  const completedMessageId = chat.pendingMessageIds.find((messageId) => !queuedMessageIds.has(messageId));
11395
- if (!completedMessageId) {
11396
- await this.publishAgentTurnCompleteWebhook(chat);
11397
- return;
11398
- }
11399
11453
  chat.pendingMessageIds = chat.pendingMessageIds.filter((messageId) => messageId !== completedMessageId);
11400
11454
  }
11401
11455
  chat.hasActiveTurn = false;
11402
11456
  chat.activeMessageId = null;
11403
11457
  this.agentChatActivityTrackerService.noteTurnEnded(chatId);
11404
11458
  const completedAt = (/* @__PURE__ */ new Date()).toISOString();
11405
- const processing = chat.provider.getQueue().length > 0;
11459
+ const processing = chat.provider.getQueue().length > 0 || (chat.provider.hasActiveBackgroundTasks?.() ?? false);
11406
11460
  this.publish({
11407
11461
  type: "chat.turn.completed",
11408
11462
  payload: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.648",
3
+ "version": "0.1.650",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",