replicas-engine 0.1.1066 → 0.1.1070

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-Q7BYKZR6.js";
12
- import "./chunk-W347RMUG.js";
13
- import "./chunk-RYGN5ENQ.js";
14
- import "./chunk-DIKPGEYN.js";
11
+ } from "./chunk-KKAF3IGR.js";
12
+ import "./chunk-SNB36JQS.js";
13
+ import "./chunk-K72ZN4MS.js";
14
+ import "./chunk-3Q7NCMIF.js";
15
15
  import "./chunk-UZSNFLDQ.js";
16
- import "./chunk-PBDIRFDJ.js";
16
+ import "./chunk-T6JO4V4V.js";
17
17
  import "./chunk-VEQXQN22.js";
18
18
  export {
19
19
  CODEX_ULTRAFAST_SERVICE_TIER,
@@ -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-PBDIRFDJ.js";
6
+ } from "./chunk-T6JO4V4V.js";
7
7
 
8
8
  // src/utils/exec.ts
9
9
  import { exec, execFile } from "child_process";
@@ -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-RYGN5ENQ.js";
6
+ } from "./chunk-K72ZN4MS.js";
7
7
  import {
8
8
  ASTER_BASE_URL,
9
9
  ASTER_PROVIDER,
@@ -11,7 +11,7 @@ import {
11
11
  codexReasoningEffortForThinkingLevel,
12
12
  isRecord,
13
13
  readOllamaAuthEnv
14
- } from "./chunk-PBDIRFDJ.js";
14
+ } from "./chunk-T6JO4V4V.js";
15
15
 
16
16
  // src/managers/codex-asp/notification-dispatch.ts
17
17
  var TURN_STARTED_METHOD = "turn/started";
@@ -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-DIKPGEYN.js";
6
+ } from "./chunk-3Q7NCMIF.js";
7
7
  import {
8
8
  isVersionBelow
9
- } from "./chunk-PBDIRFDJ.js";
9
+ } from "./chunk-T6JO4V4V.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.1066";
243
+ var ENGINE_PACKAGE_VERSION = "0.1.1070";
244
244
  var INITIALIZE_METHOD = "initialize";
245
245
  var INITIALIZED_NOTIFICATION = "initialized";
246
246
  var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
@@ -10,15 +10,15 @@ import {
10
10
  resolveDiffBase,
11
11
  runGitCommand,
12
12
  setAgentCredentialSnapshot
13
- } from "./chunk-W347RMUG.js";
13
+ } from "./chunk-SNB36JQS.js";
14
14
  import {
15
15
  AppServerProcess,
16
16
  buildCodexAgentEnv
17
- } from "./chunk-RYGN5ENQ.js";
17
+ } from "./chunk-K72ZN4MS.js";
18
18
  import {
19
19
  SUBPROCESS_MAX_BUFFER,
20
20
  execFileAsync
21
- } from "./chunk-DIKPGEYN.js";
21
+ } from "./chunk-3Q7NCMIF.js";
22
22
  import {
23
23
  isRecord
24
24
  } from "./chunk-UZSNFLDQ.js";
@@ -55,7 +55,7 @@ import {
55
55
  resolveOpenAICompatibleModelsUrl,
56
56
  splitMultitaskInstructions,
57
57
  withTimeout
58
- } from "./chunk-PBDIRFDJ.js";
58
+ } from "./chunk-T6JO4V4V.js";
59
59
 
60
60
  // src/managers/codex-token-manager.ts
61
61
  import { promises as fs } from "fs";
@@ -291,7 +291,7 @@ var CodexTokenManager = class extends BaseRefreshManager {
291
291
  await this.applyCredentialUpdate(++this.credentialGeneration, async () => {
292
292
  await this.applyCredentialsResponse(data);
293
293
  setAgentCredentialSnapshot("codex", data);
294
- const { restartCodexAspHostIfRunning: restartCodexAspHostIfRunning2 } = await import("./asp-host-MF2K6N3H.js");
294
+ const { restartCodexAspHostIfRunning: restartCodexAspHostIfRunning2 } = await import("./asp-host-QQ7NEGG4.js");
295
295
  await restartCodexAspHostIfRunning2();
296
296
  });
297
297
  await this.start(true);
@@ -312,7 +312,7 @@ var CodexTokenManager = class extends BaseRefreshManager {
312
312
  const accountChanged = ENGINE_ENV.REPLICAS_AGENT_CREDENTIALS.codex?.credentialId !== data.credentialId;
313
313
  await this.applyCredentialsResponse(data);
314
314
  if (restartOnChange && (accountChanged || CODEX_AUTH_ENV_KEYS.some((key, index) => process.env[key] !== previousEnv[index]))) {
315
- const { restartCodexAspHostIfRunning: restartCodexAspHostIfRunning2 } = await import("./asp-host-MF2K6N3H.js");
315
+ const { restartCodexAspHostIfRunning: restartCodexAspHostIfRunning2 } = await import("./asp-host-QQ7NEGG4.js");
316
316
  await restartCodexAspHostIfRunning2();
317
317
  }
318
318
  setAgentCredentialSnapshot("codex", data);
@@ -763,13 +763,9 @@ var GitService = class {
763
763
  }
764
764
  return { repos: [...states, ...discovered.deleted], complete };
765
765
  }
766
- async refreshRepos(observedBranchesByRepo, options) {
767
- return (await this.refreshReposWithCompleteness(observedBranchesByRepo, options)).repos;
768
- }
769
- async refreshReposWithCompleteness(observedBranchesByRepo, options) {
766
+ async refreshRepos(observedBranchesByRepo) {
770
767
  const discovered = await this.listTrackedRepositories();
771
768
  const states = [];
772
- let complete = discovered.complete;
773
769
  for (const repo of discovered.repos) {
774
770
  try {
775
771
  const persistedState = await loadRepoState(repo.name);
@@ -784,18 +780,14 @@ var GitService = class {
784
780
  currentBranch,
785
781
  startHooksCompleted,
786
782
  persistedState,
787
- observed,
788
- options?.includeDiffs === true
783
+ observed
789
784
  );
790
- if (state.gitDiff === null || options?.includeDiffs === true && state.gitDiff.fullDiff === void 0) {
791
- complete = false;
792
- }
793
785
  states.push(state);
794
786
  } catch {
795
- complete = false;
787
+ continue;
796
788
  }
797
789
  }
798
- return { repos: [...states, ...discovered.deleted], complete };
790
+ return [...states, ...discovered.deleted];
799
791
  }
800
792
  async snapshotCurrentBranches() {
801
793
  const { repos } = await this.listTrackedRepositories();
@@ -1158,7 +1150,7 @@ Binary files /dev/null and ${quote(`b/${path2}`)} differ
1158
1150
  }
1159
1151
  return "main";
1160
1152
  }
1161
- async refreshRepoMetadata(repo, currentBranch, startHooksCompleted, persistedState, observedBranches, includeDiffs = false) {
1153
+ async refreshRepoMetadata(repo, currentBranch, startHooksCompleted, persistedState, observedBranches) {
1162
1154
  const notFound = { status: "not_found" };
1163
1155
  const prResult = currentBranch === repo.defaultBranch ? notFound : await this.getPullRequestUrl(repo.name, repo.path, currentBranch, persistedState);
1164
1156
  let prUrls = persistedState?.prUrls ?? [];
@@ -1174,15 +1166,13 @@ Binary files /dev/null and ${quote(`b/${path2}`)} differ
1174
1166
  }
1175
1167
  }
1176
1168
  }
1177
- const gitDiff = await this.getGitDiffStats(repo.path, repo.defaultBranch);
1178
- const fullDiff = includeDiffs && gitDiff ? await this.getFullGitDiff(repo.path, repo.defaultBranch) : null;
1179
1169
  const state = {
1180
1170
  name: repo.name,
1181
1171
  path: repo.path,
1182
1172
  defaultBranch: repo.defaultBranch,
1183
1173
  currentBranch,
1184
1174
  prUrls,
1185
- gitDiff: includeDiffs && gitDiff ? { ...gitDiff, ...fullDiff === null ? {} : { fullDiff } } : gitDiff,
1175
+ gitDiff: await this.getGitDiffStats(repo.path, repo.defaultBranch),
1186
1176
  startHooksCompleted,
1187
1177
  ...await this.resolveCodeHost(repo.path)
1188
1178
  };
@@ -1404,6 +1394,7 @@ var THREAD_GOAL_CLEAR_METHOD = "thread/goal/clear";
1404
1394
  var THREAD_COMPACT_START_METHOD = "thread/compact/start";
1405
1395
  var THREAD_INJECT_ITEMS_METHOD = "thread/inject_items";
1406
1396
  var THREAD_SETTINGS_UPDATE_METHOD = "thread/settings/update";
1397
+ var THREAD_BACKGROUND_TERMINALS_CLEAN_METHOD = "thread/backgroundTerminals/clean";
1407
1398
  var TURN_START_METHOD = "turn/start";
1408
1399
  var TURN_INTERRUPT_METHOD = "turn/interrupt";
1409
1400
  var TURN_STEER_METHOD = "turn/steer";
@@ -2031,6 +2022,7 @@ export {
2031
2022
  THREAD_COMPACT_START_METHOD,
2032
2023
  THREAD_INJECT_ITEMS_METHOD,
2033
2024
  THREAD_SETTINGS_UPDATE_METHOD,
2025
+ THREAD_BACKGROUND_TERMINALS_CLEAN_METHOD,
2034
2026
  TURN_START_METHOD,
2035
2027
  TURN_INTERRUPT_METHOD,
2036
2028
  TURN_STEER_METHOD,
@@ -4,7 +4,7 @@ const require = __createRequire(import.meta.url);
4
4
  import {
5
5
  SUBPROCESS_MAX_BUFFER,
6
6
  execFileAsync
7
- } from "./chunk-DIKPGEYN.js";
7
+ } from "./chunk-3Q7NCMIF.js";
8
8
  import {
9
9
  isRecord as isRecord2
10
10
  } from "./chunk-UZSNFLDQ.js";
@@ -18,7 +18,7 @@ import {
18
18
  isValidAgentProvider,
19
19
  parsePosixEnvFile,
20
20
  readReplicasRuntimeEnv
21
- } from "./chunk-PBDIRFDJ.js";
21
+ } from "./chunk-T6JO4V4V.js";
22
22
 
23
23
  // src/runtime-env-loader.ts
24
24
  import { readFileSync } from "fs";
@@ -1174,6 +1174,7 @@ function getEventStreamId(event) {
1174
1174
  var CODEX_QUOTA_STATUS_EVENT_TYPE = "codex-quota-status";
1175
1175
  var CHAT_INTERRUPTED_EVENT_TYPE = "replicas-interrupted";
1176
1176
  var CHAT_MESSAGE_REJECTED_EVENT_TYPE = "replicas-message-rejected";
1177
+ var CHAT_TURN_LOST_EVENT_TYPE = "replicas-turn-lost";
1177
1178
  var CHAT_GOAL_EVENT_TYPE = "chat-goal";
1178
1179
  var AUTH_RETRY_STATUS_EVENT_TYPE = "auth-retry-status";
1179
1180
  var CURSOR_CONNECTION_STATUS_EVENT_TYPE = "cursor-connection-status";
@@ -7550,6 +7551,15 @@ function applyEngineNotices(messages, events) {
7550
7551
  });
7551
7552
  return;
7552
7553
  }
7554
+ if (event.type === CHAT_TURN_LOST_EVENT_TYPE) {
7555
+ notices.push({
7556
+ id: `turn-lost-${event.timestamp}-${index}`,
7557
+ type: "error",
7558
+ message: "The agent stopped because the workspace restarted. Send a message to continue.",
7559
+ timestamp: event.timestamp
7560
+ });
7561
+ return;
7562
+ }
7553
7563
  if (event.type !== AUTH_FALLBACK_EVENT_TYPE) return;
7554
7564
  const payload = coerceAuthFallbackPayload(event.payload);
7555
7565
  if (!payload) return;
@@ -9897,6 +9907,7 @@ export {
9897
9907
  getEventStreamId,
9898
9908
  CODEX_QUOTA_STATUS_EVENT_TYPE,
9899
9909
  CHAT_INTERRUPTED_EVENT_TYPE,
9910
+ CHAT_TURN_LOST_EVENT_TYPE,
9900
9911
  CHAT_GOAL_EVENT_TYPE,
9901
9912
  AUTH_RETRY_STATUS_EVENT_TYPE,
9902
9913
  CURSOR_CONNECTION_STATUS_EVENT_TYPE,
@@ -5,7 +5,7 @@ import {
5
5
  extractCommandProtectionCommandText,
6
6
  isRecord,
7
7
  parseCodeHostPrUrl
8
- } from "./chunk-PBDIRFDJ.js";
8
+ } from "./chunk-T6JO4V4V.js";
9
9
 
10
10
  // src/services/published-pull-request-urls.ts
11
11
  function publishedPullRequestUrls(result) {
@@ -5,14 +5,14 @@ import {
5
5
  getOriginDefaultBranch,
6
6
  monolithRequest,
7
7
  runGitCommand
8
- } from "./chunk-W347RMUG.js";
8
+ } from "./chunk-SNB36JQS.js";
9
9
  import {
10
10
  extractCommandProtectionCommandText,
11
11
  findCommandProtectionGitLookups,
12
12
  findGitCommitSignals,
13
13
  findPrMergeSignals,
14
14
  parseCommandProtectionRequest
15
- } from "./chunk-PBDIRFDJ.js";
15
+ } from "./chunk-T6JO4V4V.js";
16
16
 
17
17
  // src/services/command-protection-service.ts
18
18
  import { readFile, stat } from "fs/promises";
@@ -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-NPPQ2SNN.js";
7
- import "./chunk-W347RMUG.js";
8
- import "./chunk-DIKPGEYN.js";
6
+ } from "./chunk-XV3XBNZR.js";
7
+ import "./chunk-SNB36JQS.js";
8
+ import "./chunk-3Q7NCMIF.js";
9
9
  import {
10
10
  isRecord
11
11
  } from "./chunk-UZSNFLDQ.js";
12
- import "./chunk-PBDIRFDJ.js";
12
+ import "./chunk-T6JO4V4V.js";
13
13
  import "./chunk-VEQXQN22.js";
14
14
 
15
15
  // src/command-protection-hook.ts
@@ -13,12 +13,12 @@ import {
13
13
  recoverCompletedTurn,
14
14
  spawnMuseMsp,
15
15
  startMuseSession
16
- } from "./chunk-I46BKVTL.js";
16
+ } from "./chunk-IZSM723M.js";
17
17
  import {
18
18
  AppServerProcess,
19
19
  buildCodexAgentEnv
20
- } from "./chunk-RYGN5ENQ.js";
21
- import "./chunk-DIKPGEYN.js";
20
+ } from "./chunk-K72ZN4MS.js";
21
+ import "./chunk-3Q7NCMIF.js";
22
22
  import {
23
23
  AGENT,
24
24
  OPENCODE_GO_PROVIDER,
@@ -28,7 +28,7 @@ import {
28
28
  headlessAgentUsagePath,
29
29
  isRecord,
30
30
  withTimeout
31
- } from "./chunk-PBDIRFDJ.js";
31
+ } from "./chunk-T6JO4V4V.js";
32
32
  import "./chunk-VEQXQN22.js";
33
33
 
34
34
  // 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-NPPQ2SNN.js";
30
+ } from "./chunk-XV3XBNZR.js";
31
31
  import {
32
32
  ACCOUNT_RATE_LIMITS_UPDATED_METHOD,
33
33
  AGENT_MESSAGE_DELTA_METHOD,
@@ -64,10 +64,10 @@ import {
64
64
  spawnMuseMsp,
65
65
  startMuseSession,
66
66
  uuidV7
67
- } from "./chunk-I46BKVTL.js";
67
+ } from "./chunk-IZSM723M.js";
68
68
  import {
69
69
  publishedPullRequestUrls
70
- } from "./chunk-G647LINI.js";
70
+ } from "./chunk-WJCC4O2M.js";
71
71
  import {
72
72
  ACCOUNT_RATE_LIMITS_READ_METHOD,
73
73
  AsyncLock,
@@ -78,6 +78,7 @@ import {
78
78
  DEFAULT_MODEL,
79
79
  SKILLS_LIST_METHOD,
80
80
  SUBAGENT_MCP_TOOL_TIMEOUT_MS,
81
+ THREAD_BACKGROUND_TERMINALS_CLEAN_METHOD,
81
82
  THREAD_COMPACT_START_METHOD,
82
83
  THREAD_GOAL_CLEAR_METHOD,
83
84
  THREAD_GOAL_GET_METHOD,
@@ -129,7 +130,7 @@ import {
129
130
  timestampFromSeconds,
130
131
  upsertCredentialFileLines,
131
132
  writeSecureCredentialFile
132
- } from "./chunk-Q7BYKZR6.js";
133
+ } from "./chunk-KKAF3IGR.js";
133
134
  import {
134
135
  ENGINE_ENV,
135
136
  monolithRequest,
@@ -138,15 +139,15 @@ import {
138
139
  reloadEngineEnv,
139
140
  resolveDiffBase,
140
141
  setAgentCredentialSnapshot
141
- } from "./chunk-W347RMUG.js";
142
+ } from "./chunk-SNB36JQS.js";
142
143
  import {
143
144
  getCodexModelProvider
144
- } from "./chunk-RYGN5ENQ.js";
145
+ } from "./chunk-K72ZN4MS.js";
145
146
  import {
146
147
  SUBPROCESS_MAX_BUFFER,
147
148
  execAsync,
148
149
  execFileAsync
149
- } from "./chunk-DIKPGEYN.js";
150
+ } from "./chunk-3Q7NCMIF.js";
150
151
  import {
151
152
  isRecord as isRecord2
152
153
  } from "./chunk-UZSNFLDQ.js";
@@ -166,6 +167,7 @@ import {
166
167
  CHAT_FORK_EXCERPT_OMITTED_NOTICE,
167
168
  CHAT_GOAL_EVENT_TYPE,
168
169
  CHAT_INTERRUPTED_EVENT_TYPE,
170
+ CHAT_TURN_LOST_EVENT_TYPE,
169
171
  CLAUDE_AUTH_ENV_KEYS,
170
172
  CLAUDE_AUTH_ENV_KEYS_BY_METHOD,
171
173
  CLAUDE_HAIKU_4_5_BEDROCK_MODEL,
@@ -374,7 +376,7 @@ import {
374
376
  workspaceTokenRequestSchema,
375
377
  workspaceTokenResponseSchema,
376
378
  wrapInTag
377
- } from "./chunk-PBDIRFDJ.js";
379
+ } from "./chunk-T6JO4V4V.js";
378
380
  import {
379
381
  cloneMcpConfig,
380
382
  ensureCompatibilityImports,
@@ -11841,57 +11843,15 @@ var LinearEventForwarder = class {
11841
11843
  }
11842
11844
  };
11843
11845
 
11844
- // src/services/upload-repo-state.ts
11845
- var uploadQueue = Promise.resolve();
11846
- async function sendRepoState(repos) {
11847
- const form = new FormData();
11848
- form.append(
11849
- "file",
11850
- new Blob([JSON.stringify({ repos })], { type: "application/json" }),
11851
- "repo-state.json"
11852
- );
11853
- const response = await monolithRequest("/v1/engine/repo-state", { body: form });
11854
- if (!response.ok) {
11855
- const errorText = await response.text();
11856
- throw new Error(`upload failed: ${response.status} ${errorText}`);
11857
- }
11858
- }
11859
- function uploadRepoState(repos) {
11860
- const upload = uploadQueue.then(() => sendRepoState(repos));
11861
- uploadQueue = upload.catch(() => {
11862
- });
11863
- return upload;
11864
- }
11865
- async function flushRepoState() {
11866
- try {
11867
- const { repos, complete: complete2 } = await gitService.listReposWithCompleteness({ includeDiffs: true });
11868
- if (!complete2) return { flushed: 0, skipped: 1, failed: 0 };
11869
- await uploadRepoState(repos);
11870
- return { flushed: 1, skipped: 0, failed: 0 };
11871
- } catch (err) {
11872
- console.error("[RepoStateUploader] upload failed:", err);
11873
- return { flushed: 0, skipped: 0, failed: 1 };
11874
- }
11875
- }
11876
-
11877
11846
  // src/services/refresh-repo-state.ts
11878
- async function refreshRepoStatuses(observedBranchesByRepo, includeDiffs = false) {
11847
+ async function refreshRepoStatuses(observedBranchesByRepo) {
11879
11848
  await Promise.all([
11880
11849
  githubTokenManager.refreshOnce().catch(() => {
11881
11850
  }),
11882
11851
  gitlabTokenManager.refreshOnce().catch(() => {
11883
11852
  })
11884
11853
  ]);
11885
- const { repos, complete: complete2 } = await gitService.refreshReposWithCompleteness(
11886
- observedBranchesByRepo,
11887
- { includeDiffs }
11888
- );
11889
- if (complete2) {
11890
- uploadRepoState(repos).catch((error) => {
11891
- console.error("[RefreshRepoState] Failed to upload repository state:", error);
11892
- });
11893
- }
11894
- return repos;
11854
+ return gitService.refreshRepos(observedBranchesByRepo);
11895
11855
  }
11896
11856
 
11897
11857
  // src/services/pull-request-check-service.ts
@@ -14125,6 +14085,14 @@ var CodexAspManager = class extends CodingAgentManager {
14125
14085
  }
14126
14086
  const threadId = this.currentThreadId;
14127
14087
  const expectedTurnId = this.activeTurnId;
14088
+ try {
14089
+ await (await getCodexAspHost()).client.request(
14090
+ THREAD_BACKGROUND_TERMINALS_CLEAN_METHOD,
14091
+ { threadId }
14092
+ );
14093
+ } catch (error) {
14094
+ console.warn("[CodexAspManager] Failed to stop the turn's running commands:", error);
14095
+ }
14128
14096
  const result = await this.requestActiveTurnWithReconciliation(
14129
14097
  "interrupt",
14130
14098
  expectedTurnId,
@@ -59021,6 +58989,39 @@ async function uploadChatTranscriptNow(chatId, filePath, chat, capture) {
59021
58989
  return artifact;
59022
58990
  }
59023
58991
 
58992
+ // src/services/upload-repo-state.ts
58993
+ var uploadQueue = Promise.resolve();
58994
+ async function sendRepoState(repos) {
58995
+ const form = new FormData();
58996
+ form.append(
58997
+ "file",
58998
+ new Blob([JSON.stringify({ repos })], { type: "application/json" }),
58999
+ "repo-state.json"
59000
+ );
59001
+ const response = await monolithRequest("/v1/engine/repo-state", { body: form });
59002
+ if (!response.ok) {
59003
+ const errorText = await response.text();
59004
+ throw new Error(`upload failed: ${response.status} ${errorText}`);
59005
+ }
59006
+ }
59007
+ function uploadRepoState(repos) {
59008
+ const upload = uploadQueue.then(() => sendRepoState(repos));
59009
+ uploadQueue = upload.catch(() => {
59010
+ });
59011
+ return upload;
59012
+ }
59013
+ async function flushRepoState() {
59014
+ try {
59015
+ const { repos, complete: complete2 } = await gitService.listReposWithCompleteness({ includeDiffs: true });
59016
+ if (!complete2) return { flushed: 0, skipped: 1, failed: 0 };
59017
+ await uploadRepoState(repos);
59018
+ return { flushed: 1, skipped: 0, failed: 0 };
59019
+ } catch (err) {
59020
+ console.error("[RepoStateUploader] upload failed:", err);
59021
+ return { flushed: 0, skipped: 0, failed: 1 };
59022
+ }
59023
+ }
59024
+
59024
59025
  // src/services/upload-engine-logs.ts
59025
59026
  import { createReadStream as createReadStream4 } from "fs";
59026
59027
  import { readdir as readdir8, stat as stat5 } from "fs/promises";
@@ -59417,10 +59418,10 @@ function hasActiveBackgroundWork(chat) {
59417
59418
  return (chat.provider.hasActiveBackgroundTasks?.() ?? false) || isForkPreparing(chat);
59418
59419
  }
59419
59420
  function hasActiveAgentWork(chat) {
59420
- return chat.provider.isProcessing() || hasActiveBackgroundWork(chat);
59421
+ return chat.provider.isProcessing() || hasActiveBackgroundWork(chat) || chat.turnCompletion !== null;
59421
59422
  }
59422
59423
  function hasActiveChatTurn(chat) {
59423
- return chat.hasActiveTurn || chat.pendingMessageIds.length > 0 || hasActiveBackgroundWork(chat) || (chat.settledProcessing ?? chat.provider.isProcessing());
59424
+ return chat.hasActiveTurn || chat.pendingMessageIds.length > 0 || hasActiveBackgroundWork(chat) || chat.provider.isProcessing();
59424
59425
  }
59425
59426
  function isPersistedChat(value) {
59426
59427
  if (!isRecord2(value)) {
@@ -59447,6 +59448,7 @@ function normalizePersistedChat(chat) {
59447
59448
  ...chat.chatCredential === "workspace" || agentCredentialSnapshotSchema.safeParse(chat.chatCredential).success ? { chatCredential: chat.chatCredential } : {},
59448
59449
  transcriptAuth: normalizeTranscriptAuth(chat.transcriptAuth),
59449
59450
  ...typeof chat.lastSenderKey === "string" ? { lastSenderKey: chat.lastSenderKey } : {},
59451
+ ...typeof chat.turnStartedAt === "string" ? { turnStartedAt: chat.turnStartedAt } : {},
59450
59452
  ...isRecord2(chat.uploadedHistory) && typeof chat.uploadedHistory.version === "string" && typeof chat.uploadedHistory.size === "number" ? { uploadedHistory: { version: chat.uploadedHistory.version, size: chat.uploadedHistory.size } } : {},
59451
59453
  removedMessageIds: Array.isArray(chat.removedMessageIds) ? chat.removedMessageIds.filter((id) => typeof id === "string").slice(-MAX_REMOVED_MESSAGE_IDS) : [],
59452
59454
  acceptedSendResponses: isRecord2(chat.acceptedSendResponses) ? Object.fromEntries(Object.entries(chat.acceptedSendResponses).filter((entry) => {
@@ -59489,20 +59491,29 @@ var ChatService = class {
59489
59491
  await mkdir17(OPENCODE_HISTORY_DIR, { recursive: true });
59490
59492
  await mkdir17(CHAT_SENDERS_DIR, { recursive: true });
59491
59493
  const persisted = await this.loadChats();
59494
+ const lostTurn = persisted.some((chat) => chat.turnStartedAt);
59492
59495
  for (const chat of persisted) {
59493
59496
  const runtime = this.createRuntimeChat(chat);
59497
+ if (chat.turnStartedAt && !chat.deletedAt) {
59498
+ await runtime.provider.persistHistoryEvent({ timestamp: (/* @__PURE__ */ new Date()).toISOString(), type: CHAT_TURN_LOST_EVENT_TYPE, payload: {} }).then(() => {
59499
+ delete chat.turnStartedAt;
59500
+ }).catch((error) => console.error("[ChatService] Failed to record a turn lost to an engine restart:", { chatId: chat.id, error }));
59501
+ } else {
59502
+ delete chat.turnStartedAt;
59503
+ }
59494
59504
  if (chat.uploadedHistory) {
59495
59505
  const size = await stat6(this.historyPath(chat)).then(({ size: size2 }) => size2, () => null);
59496
59506
  if (size === chat.uploadedHistory.size) runtime.historyVersion = chat.uploadedHistory.version;
59497
59507
  }
59498
59508
  this.chats.set(chat.id, runtime);
59499
59509
  }
59510
+ if (lostTurn) await this.persistAllChats();
59500
59511
  }
59501
59512
  listChats(includeChildren = false) {
59502
59513
  return Array.from(this.chats.values()).filter((chat) => !chat.persisted.deletedAt && (includeChildren || chat.persisted.parentChatId === null)).map((chat) => this.toSummary(chat)).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
59503
59514
  }
59504
59515
  hasActiveAgentWork(provider) {
59505
- return [...this.chats.values()].some((chat) => chat.persisted.provider === provider && (chat.provider.isProcessing() || chat.acceptingMessages > 0 || chat.pendingMessageIds.length > 0));
59516
+ return [...this.chats.values()].some((chat) => chat.persisted.provider === provider && (hasActiveAgentWork(chat) || chat.acceptingMessages > 0 || chat.pendingMessageIds.length > 0));
59506
59517
  }
59507
59518
  listDeletedChats(includeChildren = false) {
59508
59519
  return Array.from(this.chats.values()).filter((chat) => chat.persisted.deletedAt && (includeChildren || chat.persisted.parentChatId === null)).map((chat) => this.toSummary(chat)).sort((a, b) => (b.deletedAt ?? "").localeCompare(a.deletedAt ?? ""));
@@ -59862,7 +59873,7 @@ var ChatService = class {
59862
59873
  async interrupt(chatId) {
59863
59874
  const chat = this.requireChat(chatId);
59864
59875
  const result = await chat.provider.interrupt((messageIds) => this.forgetPendingMessages(chat, messageIds));
59865
- this.endTrackedTurn(chatId);
59876
+ this.endTrackedTurn(chat);
59866
59877
  chat.hasActiveTurn = false;
59867
59878
  chat.activeMessageId = null;
59868
59879
  chat.pendingMessageIds = [];
@@ -59885,7 +59896,7 @@ var ChatService = class {
59885
59896
  return { interrupted: false, queue: [], goal: null };
59886
59897
  }
59887
59898
  const interruptResult = await chat.provider.interrupt((messageIds) => this.forgetPendingMessages(chat, messageIds));
59888
- this.endTrackedTurn(chatId);
59899
+ this.endTrackedTurn(chat);
59889
59900
  chat.hasActiveTurn = false;
59890
59901
  chat.activeMessageId = null;
59891
59902
  chat.pendingMessageIds = [];
@@ -60504,7 +60515,7 @@ var ChatService = class {
60504
60515
  lastTurnErrors: null,
60505
60516
  turnRequest: null,
60506
60517
  turnAnswer: null,
60507
- settledProcessing: null,
60518
+ turnCompletion: null,
60508
60519
  errorNotificationTargets: /* @__PURE__ */ new Map(),
60509
60520
  activeErrorNotificationTargets: [],
60510
60521
  slackActivityMessageIds: /* @__PURE__ */ new Set(),
@@ -60551,10 +60562,11 @@ var ChatService = class {
60551
60562
  activateTurn(chat, messageId) {
60552
60563
  chat.hasActiveTurn = true;
60553
60564
  chat.activeMessageId = messageId;
60565
+ chat.persisted.turnStartedAt = (/* @__PURE__ */ new Date()).toISOString();
60566
+ this.touch(chat);
60554
60567
  const request = chat.acceptedUserEvents.get(messageId)?.payload.message;
60555
60568
  chat.turnRequest = typeof request === "string" ? request.trim().slice(0, LAST_MESSAGE_PREVIEW_MAX) : null;
60556
60569
  chat.turnAnswer = null;
60557
- chat.settledProcessing = null;
60558
60570
  const context = this.agentChatActivityTrackerService.noteTurnStarted(
60559
60571
  chat.persisted.id,
60560
60572
  messageId,
@@ -60683,14 +60695,15 @@ var ChatService = class {
60683
60695
  handleProcessingStopped(chatId) {
60684
60696
  const chat = this.getRuntimeChat(chatId);
60685
60697
  if (!chat) return;
60686
- chat.settledProcessing = null;
60687
60698
  this.publish({ type: "chat.updated", payload: { chat: this.toSummary(chat) } }).catch(() => {
60688
60699
  });
60689
60700
  this.notifyWorkspaceAgentIdle();
60690
60701
  }
60691
- endTrackedTurn(chatId) {
60692
- this.agentTokenUsageTracker.endTurn(chatId);
60693
- this.agentChatActivityTrackerService.noteTurnEnded(chatId);
60702
+ endTrackedTurn(chat) {
60703
+ this.agentTokenUsageTracker.endTurn(chat.persisted.id);
60704
+ this.agentChatActivityTrackerService.noteTurnEnded(chat.persisted.id);
60705
+ delete chat.persisted.turnStartedAt;
60706
+ this.touch(chat);
60694
60707
  }
60695
60708
  async handleTurnFinished(chatId) {
60696
60709
  const chat = this.getRuntimeChat(chatId);
@@ -60707,7 +60720,7 @@ var ChatService = class {
60707
60720
  chat.activeMessageId = null;
60708
60721
  chat.slackActivity?.finish();
60709
60722
  chat.slackActivity = null;
60710
- this.endTrackedTurn(chatId);
60723
+ this.endTrackedTurn(chat);
60711
60724
  const completedAt = (/* @__PURE__ */ new Date()).toISOString();
60712
60725
  const processing = chat.provider.getQueue().length > 0 || (chat.provider.hasActiveBackgroundTasks?.() ?? false);
60713
60726
  const exhaustion = chat.quotaExhaustion;
@@ -60722,12 +60735,14 @@ var ChatService = class {
60722
60735
  }
60723
60736
  }
60724
60737
  const stillProcessing = processing || forked;
60725
- chat.settledProcessing = stillProcessing;
60726
60738
  const answer = chat.turnAnswer;
60727
60739
  chat.turnAnswer = null;
60728
60740
  const responseComplete = this.publishTurnCompleted(chat, completedAt, stillProcessing, answer, userTurn);
60729
- await this.publishAgentTurnCompleteWebhook(chat, completedAt, stillProcessing, forked ? null : exhaustion, responseComplete);
60730
- this.notifyWorkspaceAgentIdle();
60741
+ const completion = this.publishAgentTurnCompleteWebhook(chat, completedAt, stillProcessing, forked ? null : exhaustion, responseComplete, chat.turnCompletion).catch((error) => console.error("[ChatService] Failed to report turn completion:", { chatId, error })).finally(() => {
60742
+ if (chat.turnCompletion === completion) chat.turnCompletion = null;
60743
+ this.notifyWorkspaceAgentIdle();
60744
+ });
60745
+ chat.turnCompletion = completion;
60731
60746
  }
60732
60747
  async publishTurnCompleted(chat, completedAt, processing, answer, userTurn) {
60733
60748
  if (chat.persisted.parentChatId) {
@@ -60846,13 +60861,16 @@ var ChatService = class {
60846
60861
  } catch {
60847
60862
  }
60848
60863
  }
60849
- async publishAgentTurnCompleteWebhook(chat, completedAt, processing, quotaExhaustion, responseComplete) {
60864
+ async publishAgentTurnCompleteWebhook(chat, completedAt, processing, quotaExhaustion, responseComplete, previousCompletion) {
60850
60865
  const linearSessionId = ENGINE_ENV.REPLICAS_LINEAR_SESSION_ID;
60851
60866
  const errors = chat.persisted.parentChatId === null ? chat.lastTurnErrors : null;
60852
60867
  chat.lastTurnErrors = null;
60853
60868
  const errorNotificationTargets = chat.activeErrorNotificationTargets;
60854
60869
  chat.activeErrorNotificationTargets = [];
60855
- const refreshedRepoStatuses = await this.refreshTurnRepos(chat);
60870
+ const observedBranches = chat.observedBranchesByRepo;
60871
+ chat.observedBranchesByRepo = /* @__PURE__ */ new Map();
60872
+ await previousCompletion;
60873
+ const refreshedRepoStatuses = await this.refreshTurnRepos(observedBranches);
60856
60874
  const repoStatuses = refreshedRepoStatuses ?? [];
60857
60875
  await responseComplete;
60858
60876
  if (chat.persisted.parentChatId) return;
@@ -60864,7 +60882,8 @@ var ChatService = class {
60864
60882
  const payload = {
60865
60883
  ...linearSessionId ? { linearSessionId } : {},
60866
60884
  completedAt,
60867
- processing,
60885
+ // A newer turn may have started while this completion ran; reporting idle would clear its Slack status.
60886
+ processing: processing || hasActiveChatTurn(chat),
60868
60887
  chatId: chat.persisted.id,
60869
60888
  repoStatuses,
60870
60889
  ...unlinkedPullRequestUrls.length > 0 ? { unlinkedPullRequestUrls } : {},
@@ -60877,11 +60896,9 @@ var ChatService = class {
60877
60896
  console.error("[ChatService] Failed to send agent_turn_complete event:", error);
60878
60897
  }
60879
60898
  }
60880
- async refreshTurnRepos(chat) {
60881
- const observedBranches = chat.observedBranchesByRepo;
60882
- chat.observedBranchesByRepo = /* @__PURE__ */ new Map();
60899
+ async refreshTurnRepos(observedBranches) {
60883
60900
  try {
60884
- const repoStatuses = await refreshRepoStatuses(observedBranches, true);
60901
+ const repoStatuses = await refreshRepoStatuses(observedBranches);
60885
60902
  console.log(`Repository Statuses Refreshed: `, repoStatuses);
60886
60903
  return repoStatuses;
60887
60904
  } catch (error) {
@@ -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-G647LINI.js";
6
+ } from "./chunk-WJCC4O2M.js";
7
7
  import {
8
8
  isRecord
9
9
  } from "./chunk-UZSNFLDQ.js";
10
10
  import {
11
11
  shouldRefreshPullRequests
12
- } from "./chunk-PBDIRFDJ.js";
12
+ } from "./chunk-T6JO4V4V.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-PBDIRFDJ.js";
12
+ } from "./chunk-T6JO4V4V.js";
13
13
  import "./chunk-VEQXQN22.js";
14
14
 
15
15
  // src/subagent-mcp.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.1066",
3
+ "version": "0.1.1070",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",