replicas-engine 0.1.533 → 0.1.536

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -134,3 +134,4 @@ Outgoing endpoints:
134
134
  - `POST /v1/engine/claude/refresh-credentials`
135
135
  - `POST /v1/engine/codex/refresh-credentials`
136
136
  - `POST /v1/engine/infisical/refresh-token`
137
+ - `POST /v1/engine/chat-turn-usage`
package/dist/src/index.js CHANGED
@@ -203,6 +203,11 @@ function detectLanguageByPath(filePath) {
203
203
  return EXT_TO_LANGUAGE[ext] ?? null;
204
204
  }
205
205
 
206
+ // ../shared/src/chat-turn-usage.ts
207
+ function isChatTurnUsageRecord(value) {
208
+ return isRecord(value) && typeof value.turnId === "string" && typeof value.chatId === "string" && typeof value.provider === "string" && isValidAgentProvider(value.provider) && typeof value.model === "string" && (value.senderUserId === void 0 || typeof value.senderUserId === "string") && typeof value.occurredAt === "string" && typeof value.seconds === "number" && Number.isFinite(value.seconds) && value.seconds >= 0;
209
+ }
210
+
206
211
  // ../shared/src/aster.ts
207
212
  var ASTER_PROVIDER = "aster";
208
213
  var ASTER_BASE_URL = "https://api.asterlab.ai/v1";
@@ -258,6 +263,17 @@ var OPENROUTER_MODELS = [
258
263
  "xiaomi/mimo-v2.5-pro",
259
264
  "moonshotai/kimi-k2.6"
260
265
  ];
266
+ var FALLBACK_AGENT_MODEL = {
267
+ claude: DEFAULT_CLAUDE_MODEL,
268
+ codex: DEFAULT_CODEX_MODEL,
269
+ cursor: DEFAULT_CURSOR_MODEL,
270
+ opencode: DEFAULT_OPENCODE_MODEL,
271
+ pi: DEFAULT_PI_MODEL,
272
+ relay: DEFAULT_CLAUDE_MODEL
273
+ };
274
+ function getDefaultAgentModel(provider, overrides) {
275
+ return overrides?.[provider]?.[0] ?? FALLBACK_AGENT_MODEL[provider];
276
+ }
261
277
  function normalizeClaudeModel(model) {
262
278
  if (model === "opus" || model === CLAUDE_OPUS_1M_MODEL || model === LEGACY_CLAUDE_OPUS_1M_MODEL) {
263
279
  return DEFAULT_CLAUDE_MODEL;
@@ -623,7 +639,7 @@ var WORKSPACE_SIZES = ["small", "large"];
623
639
  var INVALID_WORKSPACE_SIZE_ERROR = `Invalid size: must be one of ${WORKSPACE_SIZES.join(", ")}`;
624
640
 
625
641
  // ../shared/src/e2b.ts
626
- var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-07-31-v4";
642
+ var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-07-31-v7";
627
643
 
628
644
  // ../shared/src/runtime-env.ts
629
645
  function shellQuotePosix(value) {
@@ -4949,7 +4965,7 @@ var MAX_ENGINE_LOG_BYTES = 50 * 1024 * 1024;
4949
4965
  var SKILL_REGISTRY_MANIFEST_VERSION = 1;
4950
4966
 
4951
4967
  // src/index.ts
4952
- import { randomUUID as randomUUID7 } from "crypto";
4968
+ import { randomUUID as randomUUID8 } from "crypto";
4953
4969
  import { connect } from "net";
4954
4970
 
4955
4971
  // src/utils/exec.ts
@@ -7401,10 +7417,10 @@ async function registerDesktopPreview() {
7401
7417
 
7402
7418
  // src/services/chat/chat-service.ts
7403
7419
  import { existsSync as existsSync7 } from "fs";
7404
- import { appendFile as appendFile3, copyFile, mkdir as mkdir14, readFile as readFile14, rename as rename2, rm as rm2 } from "fs/promises";
7420
+ import { appendFile as appendFile4, copyFile, mkdir as mkdir15, readFile as readFile15, rename as rename3, rm as rm2 } from "fs/promises";
7405
7421
  import { homedir as homedir15 } from "os";
7406
- import { join as join26 } from "path";
7407
- import { randomUUID as randomUUID5 } from "crypto";
7422
+ import { join as join27 } from "path";
7423
+ import { randomUUID as randomUUID6 } from "crypto";
7408
7424
 
7409
7425
  // src/managers/claude-manager.ts
7410
7426
  import {
@@ -10162,7 +10178,7 @@ var DEFAULT_CODEX_ARGS = ["app-server", "--listen", "stdio://"];
10162
10178
  var MIN_CODEX_CLI_VERSION = "0.144.6";
10163
10179
  var CODEX_UPGRADE_TIMEOUT_MS = 12e4;
10164
10180
  var codexCliVersionEnsured = null;
10165
- var ENGINE_PACKAGE_VERSION = "0.1.533";
10181
+ var ENGINE_PACKAGE_VERSION = "0.1.536";
10166
10182
  var INITIALIZE_METHOD = "initialize";
10167
10183
  var INITIALIZED_NOTIFICATION = "initialized";
10168
10184
  var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
@@ -14348,6 +14364,146 @@ var RelayManager = class {
14348
14364
  }
14349
14365
  };
14350
14366
 
14367
+ // src/services/chat/turn-usage.ts
14368
+ import { randomUUID as randomUUID5 } from "crypto";
14369
+ import { appendFile as appendFile3, mkdir as mkdir14, readFile as readFile12, readdir as readdir6, rename as rename2, unlink as unlink3 } from "fs/promises";
14370
+ import { join as join22 } from "path";
14371
+ var LIVE_FILE = join22(ENGINE_DIR2, "turn-usage.jsonl");
14372
+ var SEGMENT_FILE_RE = /^turn-usage\.(\d+)\.jsonl$/;
14373
+ var UPLOADED_SUFFIX = ".uploaded";
14374
+ var MAX_TRACKED_MESSAGES = 500;
14375
+ var MAX_FAILED_RECORDS = 500;
14376
+ var FLUSH_DEBOUNCE_MS = 15e3;
14377
+ var MAX_UPLOADED_SEGMENTS = 50;
14378
+ var messageAttrs = /* @__PURE__ */ new Map();
14379
+ var activeTurns = /* @__PURE__ */ new Map();
14380
+ var flushTimer = null;
14381
+ var activeFlush = Promise.resolve({ flushed: 0, failed: 0 });
14382
+ var pendingAppends = /* @__PURE__ */ new Set();
14383
+ var failedRecords = [];
14384
+ function noteMessageAccepted(messageId, request) {
14385
+ messageAttrs.set(messageId, {
14386
+ ...request.model ? { model: request.model } : {},
14387
+ ...request.senderUserId ? { senderUserId: request.senderUserId } : {}
14388
+ });
14389
+ for (const key of messageAttrs.keys()) {
14390
+ if (messageAttrs.size <= MAX_TRACKED_MESSAGES) break;
14391
+ messageAttrs.delete(key);
14392
+ }
14393
+ }
14394
+ function noteTurnStarted(chatId, messageId, provider) {
14395
+ activeTurns.set(chatId, { messageId, startedAtMs: Date.now(), provider });
14396
+ }
14397
+ function noteTurnEnded(chatId) {
14398
+ const turn = activeTurns.get(chatId);
14399
+ if (!turn) return;
14400
+ activeTurns.delete(chatId);
14401
+ recordTurn(chatId, turn);
14402
+ scheduleFlush();
14403
+ }
14404
+ function scheduleFlush() {
14405
+ if (flushTimer) return;
14406
+ flushTimer = setTimeout(() => {
14407
+ flushTimer = null;
14408
+ void flushAllTurnUsage(false).catch((err) => {
14409
+ console.error("[TurnUsage] Scheduled flush failed, retrying on the next turn:", err);
14410
+ });
14411
+ }, FLUSH_DEBOUNCE_MS);
14412
+ flushTimer.unref?.();
14413
+ }
14414
+ function recordTurn(chatId, turn) {
14415
+ const attrs = messageAttrs.get(turn.messageId) ?? {};
14416
+ messageAttrs.delete(turn.messageId);
14417
+ writeRecord({
14418
+ turnId: randomUUID5(),
14419
+ chatId,
14420
+ provider: turn.provider,
14421
+ ...attrs,
14422
+ // A request without a model runs the agent's default, so record that rather than nothing.
14423
+ model: attrs.model ?? getDefaultAgentModel(turn.provider),
14424
+ occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
14425
+ seconds: Math.round((Date.now() - turn.startedAtMs) / 1e3)
14426
+ });
14427
+ }
14428
+ function writeRecord(record) {
14429
+ const pending = mkdir14(ENGINE_DIR2, { recursive: true }).then(() => appendFile3(LIVE_FILE, JSON.stringify(record) + "\n", "utf-8")).catch((err) => {
14430
+ console.error("[TurnUsage] Append failed, will retry on flush:", err);
14431
+ if (failedRecords.length < MAX_FAILED_RECORDS) failedRecords.push(record);
14432
+ });
14433
+ pendingAppends.add(pending);
14434
+ void pending.finally(() => pendingAppends.delete(pending));
14435
+ }
14436
+ function flushAllTurnUsage(finalizeInFlight = true) {
14437
+ if (flushTimer) {
14438
+ clearTimeout(flushTimer);
14439
+ flushTimer = null;
14440
+ }
14441
+ activeFlush = activeFlush.catch(() => {
14442
+ }).then(() => runFlush(finalizeInFlight));
14443
+ return activeFlush;
14444
+ }
14445
+ async function runFlush(finalizeInFlight) {
14446
+ for (const record of failedRecords.splice(0)) writeRecord(record);
14447
+ if (finalizeInFlight) {
14448
+ for (const [chatId, turn] of activeTurns) recordTurn(chatId, turn);
14449
+ activeTurns.clear();
14450
+ }
14451
+ while (pendingAppends.size > 0) await Promise.allSettled([...pendingAppends]);
14452
+ await rename2(LIVE_FILE, join22(ENGINE_DIR2, `turn-usage.${Date.now()}.jsonl`)).catch(() => {
14453
+ });
14454
+ const entries = await readdir6(ENGINE_DIR2).catch(() => []);
14455
+ let flushed = 0;
14456
+ let failed = 0;
14457
+ for (const entry of entries) {
14458
+ if (!SEGMENT_FILE_RE.test(entry)) continue;
14459
+ try {
14460
+ await uploadSegment(join22(ENGINE_DIR2, entry));
14461
+ flushed++;
14462
+ } catch (err) {
14463
+ failed++;
14464
+ console.error("[TurnUsage] Segment upload failed, retained for retry:", { entry, err });
14465
+ }
14466
+ }
14467
+ const stranded = failedRecords.splice(0);
14468
+ if (stranded.length > 0) {
14469
+ try {
14470
+ await uploadTurns(stranded);
14471
+ flushed++;
14472
+ } catch (err) {
14473
+ failed++;
14474
+ failedRecords.unshift(...stranded.slice(0, MAX_FAILED_RECORDS));
14475
+ console.error("[TurnUsage] Stranded-record upload failed, retained for retry:", err);
14476
+ }
14477
+ }
14478
+ const uploaded = entries.filter((entry) => entry.endsWith(UPLOADED_SUFFIX)).sort();
14479
+ for (const entry of uploaded.slice(0, -MAX_UPLOADED_SEGMENTS)) {
14480
+ await unlink3(join22(ENGINE_DIR2, entry)).catch(() => {
14481
+ });
14482
+ }
14483
+ return { flushed, failed };
14484
+ }
14485
+ async function uploadSegment(filePath) {
14486
+ const turns = (await readFile12(filePath, "utf-8")).split("\n").flatMap((line) => {
14487
+ try {
14488
+ const parsed = JSON.parse(line);
14489
+ return isChatTurnUsageRecord(parsed) ? [parsed] : [];
14490
+ } catch {
14491
+ return [];
14492
+ }
14493
+ });
14494
+ await uploadTurns(turns);
14495
+ await rename2(filePath, `${filePath}${UPLOADED_SUFFIX}`).catch(() => {
14496
+ });
14497
+ }
14498
+ async function uploadTurns(turns) {
14499
+ if (turns.length === 0) return;
14500
+ const body = { turns };
14501
+ const response = await monolithRequest("/v1/engine/chat-turn-usage", { body });
14502
+ if (!response.ok) {
14503
+ throw new Error(`upload failed: ${response.status} ${await response.text()}`);
14504
+ }
14505
+ }
14506
+
14351
14507
  // src/services/keep-alive-service.ts
14352
14508
  var KeepAliveService = class _KeepAliveService {
14353
14509
  interval = null;
@@ -14392,19 +14548,19 @@ var KeepAliveService = class _KeepAliveService {
14392
14548
  var keepAliveService = new KeepAliveService();
14393
14549
 
14394
14550
  // src/services/canvas-service.ts
14395
- import { readdir as readdir6, readFile as readFile12, stat as stat3 } from "fs/promises";
14551
+ import { readdir as readdir7, readFile as readFile13, stat as stat3 } from "fs/promises";
14396
14552
  import { homedir as homedir14 } from "os";
14397
- import { join as join22 } from "path";
14553
+ import { join as join23 } from "path";
14398
14554
  var GLOBAL_CANVAS_DIRECTORIES = [
14399
- join22(homedir14(), ".claude", "plans"),
14400
- join22(process.env.XDG_DATA_HOME ?? join22(homedir14(), ".local", "share"), "opencode", "plans"),
14401
- join22(homedir14(), ".replicas", "canvas")
14555
+ join23(homedir14(), ".claude", "plans"),
14556
+ join23(process.env.XDG_DATA_HOME ?? join23(homedir14(), ".local", "share"), "opencode", "plans"),
14557
+ join23(homedir14(), ".replicas", "canvas")
14402
14558
  ];
14403
14559
  async function canvasDirectories() {
14404
14560
  const repositories = await gitService.listRepositories().catch(() => []);
14405
14561
  return [
14406
14562
  ...GLOBAL_CANVAS_DIRECTORIES,
14407
- ...repositories.map((repository) => join22(repository.path, ".opencode", "plans"))
14563
+ ...repositories.map((repository) => join23(repository.path, ".opencode", "plans"))
14408
14564
  ];
14409
14565
  }
14410
14566
  var CanvasService = class {
@@ -14413,7 +14569,7 @@ var CanvasService = class {
14413
14569
  for (const directory of await canvasDirectories()) {
14414
14570
  let entries;
14415
14571
  try {
14416
- entries = await readdir6(directory, { withFileTypes: true });
14572
+ entries = await readdir7(directory, { withFileTypes: true });
14417
14573
  } catch {
14418
14574
  continue;
14419
14575
  }
@@ -14424,7 +14580,7 @@ var CanvasService = class {
14424
14580
  const { kind } = classifyCanvasFilename(entry.name);
14425
14581
  let sizeBytes = 0;
14426
14582
  try {
14427
- const s = await stat3(join22(directory, entry.name));
14583
+ const s = await stat3(join23(directory, entry.name));
14428
14584
  sizeBytes = s.size;
14429
14585
  } catch {
14430
14586
  continue;
@@ -14439,7 +14595,7 @@ var CanvasService = class {
14439
14595
  if (!safe) return null;
14440
14596
  const { kind, mimeType } = classifyCanvasFilename(safe);
14441
14597
  for (const directory of await canvasDirectories()) {
14442
- const filePath = join22(directory, safe);
14598
+ const filePath = join23(directory, safe);
14443
14599
  let sizeBytes = 0;
14444
14600
  let updatedAt = "";
14445
14601
  try {
@@ -14460,7 +14616,7 @@ var CanvasService = class {
14460
14616
  };
14461
14617
  }
14462
14618
  try {
14463
- const bytes = await readFile12(filePath);
14619
+ const bytes = await readFile13(filePath);
14464
14620
  return { filename: safe, kind, sizeBytes, mimeType, updatedAt, bytes };
14465
14621
  } catch {
14466
14622
  continue;
@@ -14576,16 +14732,16 @@ async function reconcileCanvasItems(filenames) {
14576
14732
 
14577
14733
  // src/services/upload-chat-transcripts.ts
14578
14734
  import { createReadStream } from "fs";
14579
- import { readdir as readdir7, readFile as readFile13, stat as stat4 } from "fs/promises";
14735
+ import { readdir as readdir8, readFile as readFile14, stat as stat4 } from "fs/promises";
14580
14736
  import { request as httpRequest } from "http";
14581
14737
  import { request as httpsRequest } from "https";
14582
- import { basename as basename2, join as join24 } from "path";
14738
+ import { basename as basename2, join as join25 } from "path";
14583
14739
 
14584
14740
  // src/services/chat/chat-senders.ts
14585
- import { join as join23 } from "path";
14586
- var CHAT_SENDERS_DIR = join23(ENGINE_DIR2, "chat-senders");
14741
+ import { join as join24 } from "path";
14742
+ var CHAT_SENDERS_DIR = join24(ENGINE_DIR2, "chat-senders");
14587
14743
  function chatMessageSendersFilePath(chatId) {
14588
- return join23(CHAT_SENDERS_DIR, `${chatId}.jsonl`);
14744
+ return join24(CHAT_SENDERS_DIR, `${chatId}.jsonl`);
14589
14745
  }
14590
14746
  function parseChatMessageSendersJsonl(content) {
14591
14747
  return content.split("\n").flatMap((line) => {
@@ -14601,9 +14757,9 @@ function parseChatMessageSendersJsonl(content) {
14601
14757
 
14602
14758
  // src/services/upload-chat-transcripts.ts
14603
14759
  var HISTORY_DIRS = [
14604
- join24(ENGINE_DIR2, "claude-histories"),
14605
- join24(ENGINE_DIR2, "relay-histories"),
14606
- join24(ENGINE_DIR2, "codex-histories")
14760
+ join25(ENGINE_DIR2, "claude-histories"),
14761
+ join25(ENGINE_DIR2, "relay-histories"),
14762
+ join25(ENGINE_DIR2, "codex-histories")
14607
14763
  ];
14608
14764
  async function putTranscript(uploadUrl, filePath, size) {
14609
14765
  await new Promise((resolve4, reject) => {
@@ -14640,7 +14796,7 @@ async function flushAllChatTranscripts(chatsById = /* @__PURE__ */ new Map()) {
14640
14796
  for (const dir of HISTORY_DIRS) {
14641
14797
  let entries;
14642
14798
  try {
14643
- entries = await readdir7(dir);
14799
+ entries = await readdir8(dir);
14644
14800
  } catch {
14645
14801
  continue;
14646
14802
  }
@@ -14648,7 +14804,7 @@ async function flushAllChatTranscripts(chatsById = /* @__PURE__ */ new Map()) {
14648
14804
  if (!entry.endsWith(".jsonl")) continue;
14649
14805
  const chatId = basename2(entry, ".jsonl");
14650
14806
  tasks.push(
14651
- uploadChatTranscript(chatId, join24(dir, entry), chatsById.get(chatId)).then(() => {
14807
+ uploadChatTranscript(chatId, join25(dir, entry), chatsById.get(chatId)).then(() => {
14652
14808
  flushed++;
14653
14809
  }).catch((err) => {
14654
14810
  failed++;
@@ -14673,7 +14829,7 @@ async function uploadChatTranscript(chatId, filePath, chat) {
14673
14829
  } : {};
14674
14830
  try {
14675
14831
  metadata.senders = parseChatMessageSendersJsonl(
14676
- await readFile13(chatMessageSendersFilePath(chatId), "utf-8")
14832
+ await readFile14(chatMessageSendersFilePath(chatId), "utf-8")
14677
14833
  );
14678
14834
  } catch (error) {
14679
14835
  if (!(error && typeof error === "object" && "code" in error && error.code === "ENOENT")) throw error;
@@ -14734,8 +14890,8 @@ async function flushRepoState() {
14734
14890
 
14735
14891
  // src/services/upload-engine-logs.ts
14736
14892
  import { createReadStream as createReadStream2 } from "fs";
14737
- import { readdir as readdir8, stat as stat5 } from "fs/promises";
14738
- import { join as join25 } from "path";
14893
+ import { readdir as readdir9, stat as stat5 } from "fs/promises";
14894
+ import { join as join26 } from "path";
14739
14895
  var MAX_ENGINE_LOG_FLUSH_SESSIONS = 10;
14740
14896
  var MAX_ENGINE_LOG_FLUSH_BYTES = 5 * 1024 * 1024;
14741
14897
  var ENGINE_LOG_FLUSH_TIMEOUT_MS = 2e4;
@@ -14745,7 +14901,7 @@ async function flushAllEngineLogs() {
14745
14901
  let failed = 0;
14746
14902
  const deadline = Date.now() + ENGINE_LOG_FLUSH_TIMEOUT_MS;
14747
14903
  await runBeforeDeadline((signal) => engineLogger.flush(signal), deadline);
14748
- const files = await runBeforeDeadline(() => readdir8(LOG_DIR), deadline).catch(() => []);
14904
+ const files = await runBeforeDeadline(() => readdir9(LOG_DIR), deadline).catch(() => []);
14749
14905
  const currentFilename = engineLogger.sessionId ? `${engineLogger.sessionId}.log` : null;
14750
14906
  const filenames = files.filter((filename) => {
14751
14907
  if (!filename.endsWith(".log")) return false;
@@ -14764,7 +14920,7 @@ async function flushAllEngineLogs() {
14764
14920
  const candidates = (await Promise.all(filenames.slice(0, MAX_ENGINE_LOG_FLUSH_SESSIONS).map(async (filename) => {
14765
14921
  try {
14766
14922
  const sessionId = filename.slice(0, -".log".length);
14767
- const filePath = join25(LOG_DIR, filename);
14923
+ const filePath = join26(LOG_DIR, filename);
14768
14924
  const fileStat = await runBeforeDeadline(() => stat5(filePath), deadline);
14769
14925
  if (!fileStat.isFile()) {
14770
14926
  skipped++;
@@ -14860,7 +15016,7 @@ async function uploadEngineLog(input, timeoutMs) {
14860
15016
  }
14861
15017
 
14862
15018
  // src/services/chat/chat-service.ts
14863
- var CODEX_AUTH_PATH2 = join26(homedir15(), ".codex", "auth.json");
15019
+ var CODEX_AUTH_PATH2 = join27(homedir15(), ".codex", "auth.json");
14864
15020
  var CHATS_BACKUP_FILE = `${CHATS_FILE}.bak`;
14865
15021
  function isCodexAvailable() {
14866
15022
  return existsSync7(CODEX_AUTH_PATH2) || Boolean(ENGINE_ENV.OPENAI_API_KEY);
@@ -14988,13 +15144,13 @@ var ChatService = class {
14988
15144
  persistInFlight = false;
14989
15145
  persistQueued = false;
14990
15146
  async initialize() {
14991
- await mkdir14(ENGINE_DIR2, { recursive: true });
14992
- await mkdir14(CLAUDE_HISTORY_DIR, { recursive: true });
14993
- await mkdir14(RELAY_HISTORY_DIR, { recursive: true });
14994
- await mkdir14(CODEX_HISTORY_DIR, { recursive: true });
14995
- await mkdir14(CURSOR_HISTORY_DIR, { recursive: true });
14996
- await mkdir14(OPENCODE_HISTORY_DIR, { recursive: true });
14997
- await mkdir14(CHAT_SENDERS_DIR, { recursive: true });
15147
+ await mkdir15(ENGINE_DIR2, { recursive: true });
15148
+ await mkdir15(CLAUDE_HISTORY_DIR, { recursive: true });
15149
+ await mkdir15(RELAY_HISTORY_DIR, { recursive: true });
15150
+ await mkdir15(CODEX_HISTORY_DIR, { recursive: true });
15151
+ await mkdir15(CURSOR_HISTORY_DIR, { recursive: true });
15152
+ await mkdir15(OPENCODE_HISTORY_DIR, { recursive: true });
15153
+ await mkdir15(CHAT_SENDERS_DIR, { recursive: true });
14998
15154
  const persisted = await this.loadChats();
14999
15155
  for (const chat of persisted) {
15000
15156
  const runtime = this.createRuntimeChat(chat);
@@ -15061,7 +15217,7 @@ var ChatService = class {
15061
15217
  throw new ChatNotFoundError(parentChatId);
15062
15218
  }
15063
15219
  const persisted = {
15064
- id: request.id ?? randomUUID5(),
15220
+ id: request.id ?? randomUUID6(),
15065
15221
  provider: request.provider,
15066
15222
  title,
15067
15223
  createdAt: now,
@@ -15105,6 +15261,7 @@ var ChatService = class {
15105
15261
  request.images
15106
15262
  );
15107
15263
  chat.pendingMessageIds.push(result.messageId);
15264
+ noteMessageAccepted(result.messageId, request);
15108
15265
  if (request.errorNotificationTarget) {
15109
15266
  chat.errorNotificationTargets.set(result.messageId, request.errorNotificationTarget);
15110
15267
  }
@@ -15141,14 +15298,14 @@ var ChatService = class {
15141
15298
  }
15142
15299
  async appendSender(chatId, sender) {
15143
15300
  try {
15144
- await appendFile3(chatMessageSendersFilePath(chatId), JSON.stringify(sender) + "\n", "utf-8");
15301
+ await appendFile4(chatMessageSendersFilePath(chatId), JSON.stringify(sender) + "\n", "utf-8");
15145
15302
  } catch (error) {
15146
15303
  console.error("[ChatService] Failed to append sender record:", error);
15147
15304
  }
15148
15305
  }
15149
15306
  async readSenders(chatId) {
15150
15307
  try {
15151
- return parseChatMessageSendersJsonl(await readFile14(chatMessageSendersFilePath(chatId), "utf-8"));
15308
+ return parseChatMessageSendersJsonl(await readFile15(chatMessageSendersFilePath(chatId), "utf-8"));
15152
15309
  } catch (error) {
15153
15310
  if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
15154
15311
  return [];
@@ -15160,6 +15317,7 @@ var ChatService = class {
15160
15317
  async interrupt(chatId) {
15161
15318
  const chat = this.requireChat(chatId);
15162
15319
  const result = await chat.provider.interrupt();
15320
+ noteTurnEnded(chatId);
15163
15321
  chat.hasActiveTurn = false;
15164
15322
  chat.activeMessageId = null;
15165
15323
  chat.pendingMessageIds = [];
@@ -15181,6 +15339,7 @@ var ChatService = class {
15181
15339
  return { interrupted: false, queue: [], goal: null };
15182
15340
  }
15183
15341
  const interruptResult = await chat.provider.interrupt();
15342
+ noteTurnEnded(chatId);
15184
15343
  chat.hasActiveTurn = false;
15185
15344
  chat.activeMessageId = null;
15186
15345
  chat.pendingMessageIds = [];
@@ -15327,7 +15486,7 @@ var ChatService = class {
15327
15486
  return descendants;
15328
15487
  }
15329
15488
  async deleteHistoryFile(persisted) {
15330
- await rm2(join26(HISTORY_DIR_BY_PROVIDER[persisted.provider], `${persisted.id}.jsonl`), { force: true });
15489
+ await rm2(join27(HISTORY_DIR_BY_PROVIDER[persisted.provider], `${persisted.id}.jsonl`), { force: true });
15331
15490
  await rm2(chatMessageSendersFilePath(persisted.id), { force: true });
15332
15491
  }
15333
15492
  async getChatHistory(chatId, page = {}) {
@@ -15370,13 +15529,14 @@ var ChatService = class {
15370
15529
  const chatsById = new Map(
15371
15530
  [...this.chats.entries()].map(([chatId, chat]) => [chatId, this.toSummary(chat)])
15372
15531
  );
15373
- const [chatTranscripts, canvas, repoState, engineLogs] = await Promise.all([
15532
+ const [chatTranscripts, canvas, repoState, engineLogs, turnUsage] = await Promise.all([
15374
15533
  flushAllChatTranscripts(chatsById),
15375
15534
  flushAllCanvasItems(),
15376
15535
  flushRepoState(),
15377
- flushAllEngineLogs()
15536
+ flushAllEngineLogs(),
15537
+ flushAllTurnUsage()
15378
15538
  ]);
15379
- return { chatTranscripts, canvas, repoState, engineLogs };
15539
+ return { chatTranscripts, canvas, repoState, engineLogs, turnUsage };
15380
15540
  }
15381
15541
  createRuntimeChat(persisted) {
15382
15542
  const saveSession = async (sessionId) => {
@@ -15403,7 +15563,7 @@ var ChatService = class {
15403
15563
  if (persisted.provider === "claude") {
15404
15564
  provider = new ClaudeManager({
15405
15565
  workingDirectory: this.workingDirectory,
15406
- historyFilePath: join26(CLAUDE_HISTORY_DIR, `${persisted.id}.jsonl`),
15566
+ historyFilePath: join27(CLAUDE_HISTORY_DIR, `${persisted.id}.jsonl`),
15407
15567
  initialSessionId: persisted.providerSessionId,
15408
15568
  onSaveSessionId: saveSession,
15409
15569
  onTurnComplete: onProviderTurnComplete,
@@ -15412,7 +15572,7 @@ var ChatService = class {
15412
15572
  } else if (persisted.provider === "relay") {
15413
15573
  provider = new RelayManager({
15414
15574
  workingDirectory: this.workingDirectory,
15415
- historyFilePath: join26(RELAY_HISTORY_DIR, `${persisted.id}.jsonl`),
15575
+ historyFilePath: join27(RELAY_HISTORY_DIR, `${persisted.id}.jsonl`),
15416
15576
  initialSessionId: persisted.providerSessionId,
15417
15577
  onSaveSessionId: saveSession,
15418
15578
  onTurnComplete: onProviderTurnComplete,
@@ -15426,7 +15586,7 @@ var ChatService = class {
15426
15586
  } else if (persisted.provider === "cursor") {
15427
15587
  provider = new CursorManager({
15428
15588
  workingDirectory: this.workingDirectory,
15429
- historyFilePath: join26(CURSOR_HISTORY_DIR, `${persisted.id}.jsonl`),
15589
+ historyFilePath: join27(CURSOR_HISTORY_DIR, `${persisted.id}.jsonl`),
15430
15590
  initialSessionId: persisted.providerSessionId,
15431
15591
  onSaveSessionId: saveSession,
15432
15592
  onTurnComplete: onProviderTurnComplete,
@@ -15435,7 +15595,7 @@ var ChatService = class {
15435
15595
  } else if (persisted.provider === "opencode") {
15436
15596
  provider = new OpencodeManager({
15437
15597
  workingDirectory: this.workingDirectory,
15438
- historyFilePath: join26(OPENCODE_HISTORY_DIR, `${persisted.id}.jsonl`),
15598
+ historyFilePath: join27(OPENCODE_HISTORY_DIR, `${persisted.id}.jsonl`),
15439
15599
  initialSessionId: persisted.providerSessionId,
15440
15600
  onSaveSessionId: saveSession,
15441
15601
  onTurnComplete: onProviderTurnComplete,
@@ -15444,7 +15604,7 @@ var ChatService = class {
15444
15604
  } else if (persisted.provider === "pi") {
15445
15605
  provider = new PiManager({
15446
15606
  workingDirectory: this.workingDirectory,
15447
- historyFilePath: join26(PI_HISTORY_DIR, `${persisted.id}.jsonl`),
15607
+ historyFilePath: join27(PI_HISTORY_DIR, `${persisted.id}.jsonl`),
15448
15608
  initialSessionId: persisted.providerSessionId,
15449
15609
  onSaveSessionId: saveSession,
15450
15610
  onTurnComplete: onProviderTurnComplete,
@@ -15453,7 +15613,7 @@ var ChatService = class {
15453
15613
  } else {
15454
15614
  provider = new CodexAspManager({
15455
15615
  workingDirectory: this.workingDirectory,
15456
- historyFilePath: join26(CODEX_HISTORY_DIR, `${persisted.id}.jsonl`),
15616
+ historyFilePath: join27(CODEX_HISTORY_DIR, `${persisted.id}.jsonl`),
15457
15617
  initialSessionId: persisted.providerSessionId,
15458
15618
  onSaveSessionId: saveSession,
15459
15619
  onTurnComplete: onProviderTurnComplete,
@@ -15516,6 +15676,7 @@ var ChatService = class {
15516
15676
  }
15517
15677
  chat.hasActiveTurn = true;
15518
15678
  chat.activeMessageId = messageId;
15679
+ noteTurnStarted(chat.persisted.id, messageId, chat.persisted.provider);
15519
15680
  chat.activeErrorNotificationTarget = chat.errorNotificationTargets.get(messageId) ?? null;
15520
15681
  chat.errorNotificationTargets.delete(messageId);
15521
15682
  this.publish({
@@ -15590,6 +15751,7 @@ var ChatService = class {
15590
15751
  }
15591
15752
  chat.hasActiveTurn = false;
15592
15753
  chat.activeMessageId = null;
15754
+ noteTurnEnded(chatId);
15593
15755
  this.publish({
15594
15756
  type: "chat.turn.completed",
15595
15757
  payload: {
@@ -15603,7 +15765,7 @@ var ChatService = class {
15603
15765
  });
15604
15766
  uploadChatTranscript(
15605
15767
  chatId,
15606
- join26(HISTORY_DIR_BY_PROVIDER[chat.persisted.provider], `${chatId}.jsonl`),
15768
+ join27(HISTORY_DIR_BY_PROVIDER[chat.persisted.provider], `${chatId}.jsonl`),
15607
15769
  this.toSummary(chat)
15608
15770
  ).catch((err) => {
15609
15771
  console.error("[ChatService] Failed to upload chat transcript:", { chatId, err });
@@ -15619,7 +15781,7 @@ var ChatService = class {
15619
15781
  }
15620
15782
  async loadChats() {
15621
15783
  try {
15622
- const content = await readFile14(CHATS_FILE, "utf-8");
15784
+ const content = await readFile15(CHATS_FILE, "utf-8");
15623
15785
  return parsePersistedChatsContent(content);
15624
15786
  } catch (error) {
15625
15787
  if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
@@ -15628,13 +15790,13 @@ var ChatService = class {
15628
15790
  const quarantinePath = corruptChatsFilePath();
15629
15791
  console.error(`[ChatService] Failed to load ${CHATS_FILE}; quarantining and trying backup:`, error);
15630
15792
  try {
15631
- await rename2(CHATS_FILE, quarantinePath);
15793
+ await rename3(CHATS_FILE, quarantinePath);
15632
15794
  console.error(`[ChatService] Quarantined corrupt chats file at ${quarantinePath}`);
15633
15795
  } catch (renameError) {
15634
15796
  console.error("[ChatService] Failed to quarantine corrupt chats file:", renameError);
15635
15797
  }
15636
15798
  try {
15637
- const backupContent = await readFile14(CHATS_BACKUP_FILE, "utf-8");
15799
+ const backupContent = await readFile15(CHATS_BACKUP_FILE, "utf-8");
15638
15800
  return parsePersistedChatsContent(backupContent);
15639
15801
  } catch (backupError) {
15640
15802
  if (backupError && typeof backupError === "object" && "code" in backupError && backupError.code === "ENOENT") {
@@ -15673,7 +15835,7 @@ var ChatService = class {
15673
15835
  }
15674
15836
  async publish(input) {
15675
15837
  const event = {
15676
- id: randomUUID5(),
15838
+ id: randomUUID6(),
15677
15839
  ts: (/* @__PURE__ */ new Date()).toISOString(),
15678
15840
  ...input
15679
15841
  };
@@ -15738,8 +15900,8 @@ var ChatService = class {
15738
15900
 
15739
15901
  // src/services/repo-file-service.ts
15740
15902
  import { execFile as execFile2 } from "child_process";
15741
- import { readFile as readFile15, realpath, stat as stat6 } from "fs/promises";
15742
- import { join as join27, resolve as resolve2, extname as extname2 } from "path";
15903
+ import { readFile as readFile16, realpath, stat as stat6 } from "fs/promises";
15904
+ import { join as join28, resolve as resolve2, extname as extname2 } from "path";
15743
15905
  var CACHE_TTL_MS = 3e4;
15744
15906
  var SEARCH_TIMEOUT_MS = 15e3;
15745
15907
  var MAX_CONTENT_BYTES = 256 * 1024;
@@ -15899,7 +16061,7 @@ var RepoFileService = class {
15899
16061
  const repo = repos.find((r) => r.name === repoName);
15900
16062
  if (!repo) return null;
15901
16063
  try {
15902
- const fullPath = await realpath(resolve2(join27(repo.path, filePath)));
16064
+ const fullPath = await realpath(resolve2(join28(repo.path, filePath)));
15903
16065
  const repoRoot = await realpath(repo.path);
15904
16066
  const repoPrefix = repoRoot.endsWith("/") ? repoRoot : repoRoot + "/";
15905
16067
  if (!fullPath.startsWith(repoPrefix) && fullPath !== repoRoot) return null;
@@ -15928,7 +16090,7 @@ var RepoFileService = class {
15928
16090
  tooLarge: true
15929
16091
  };
15930
16092
  }
15931
- const content = await readFile15(fullPath, "utf-8");
16093
+ const content = await readFile16(fullPath, "utf-8");
15932
16094
  return {
15933
16095
  repoName,
15934
16096
  path: filePath,
@@ -16006,21 +16168,21 @@ var RepoFileService = class {
16006
16168
  // src/v1-routes.ts
16007
16169
  import { Hono } from "hono";
16008
16170
  import { z as z7 } from "zod";
16009
- import { readdir as readdir10, stat as stat7, readFile as readFile18 } from "fs/promises";
16010
- import { join as join30, resolve as resolve3 } from "path";
16171
+ import { readdir as readdir11, stat as stat7, readFile as readFile19 } from "fs/promises";
16172
+ import { join as join31, resolve as resolve3 } from "path";
16011
16173
 
16012
16174
  // src/services/warm-hooks-service.ts
16013
16175
  import { spawn as spawn4 } from "child_process";
16014
- import { readFile as readFile17 } from "fs/promises";
16176
+ import { readFile as readFile18 } from "fs/promises";
16015
16177
  import { existsSync as existsSync8 } from "fs";
16016
- import { join as join29 } from "path";
16178
+ import { join as join30 } from "path";
16017
16179
 
16018
16180
  // src/services/warm-hook-logs-service.ts
16019
- import { mkdir as mkdir15, readFile as readFile16, writeFile as writeFile6, readdir as readdir9, appendFile as appendFile4, unlink as unlink3 } from "fs/promises";
16181
+ import { mkdir as mkdir16, readFile as readFile17, writeFile as writeFile6, readdir as readdir10, appendFile as appendFile5, unlink as unlink4 } from "fs/promises";
16020
16182
  import { homedir as homedir16 } from "os";
16021
- import { join as join28 } from "path";
16022
- var LOGS_DIR2 = join28(homedir16(), ".replicas", "warm-hook-logs");
16023
- var CURRENT_RUN_LOG = join28(LOGS_DIR2, "current-run.log");
16183
+ import { join as join29 } from "path";
16184
+ var LOGS_DIR2 = join29(homedir16(), ".replicas", "warm-hook-logs");
16185
+ var CURRENT_RUN_LOG = join29(LOGS_DIR2, "current-run.log");
16024
16186
  var GLOBAL_FILENAME = "global.json";
16025
16187
  function withPreview2(stored) {
16026
16188
  const preview = buildHookOutputPreview(stored.output);
@@ -16028,7 +16190,7 @@ function withPreview2(stored) {
16028
16190
  }
16029
16191
  var WarmHookLogsService = class {
16030
16192
  async ensureDir() {
16031
- await mkdir15(LOGS_DIR2, { recursive: true });
16193
+ await mkdir16(LOGS_DIR2, { recursive: true });
16032
16194
  }
16033
16195
  async saveGlobalHookLog(entry) {
16034
16196
  await this.ensureDir();
@@ -16037,7 +16199,7 @@ var WarmHookLogsService = class {
16037
16199
  hookName: "organization",
16038
16200
  ...entry
16039
16201
  };
16040
- await writeFile6(join28(LOGS_DIR2, GLOBAL_FILENAME), `${JSON.stringify(log, null, 2)}
16202
+ await writeFile6(join29(LOGS_DIR2, GLOBAL_FILENAME), `${JSON.stringify(log, null, 2)}
16041
16203
  `, "utf-8");
16042
16204
  }
16043
16205
  async saveEnvironmentHookLog(entry) {
@@ -16047,7 +16209,7 @@ var WarmHookLogsService = class {
16047
16209
  hookName: "environment",
16048
16210
  ...entry
16049
16211
  };
16050
- await writeFile6(join28(LOGS_DIR2, ENVIRONMENT_HOOK_LOG_FILENAME), `${JSON.stringify(log, null, 2)}
16212
+ await writeFile6(join29(LOGS_DIR2, ENVIRONMENT_HOOK_LOG_FILENAME), `${JSON.stringify(log, null, 2)}
16051
16213
  `, "utf-8");
16052
16214
  }
16053
16215
  async saveRepoHookLog(repoName, entry) {
@@ -16057,13 +16219,13 @@ var WarmHookLogsService = class {
16057
16219
  hookName: repoName,
16058
16220
  ...entry
16059
16221
  };
16060
- await writeFile6(join28(LOGS_DIR2, repoHookLogFilename(repoName)), `${JSON.stringify(log, null, 2)}
16222
+ await writeFile6(join29(LOGS_DIR2, repoHookLogFilename(repoName)), `${JSON.stringify(log, null, 2)}
16061
16223
  `, "utf-8");
16062
16224
  }
16063
16225
  async getAllLogs() {
16064
16226
  let files;
16065
16227
  try {
16066
- files = await readdir9(LOGS_DIR2);
16228
+ files = await readdir10(LOGS_DIR2);
16067
16229
  } catch (err) {
16068
16230
  if (err.code === "ENOENT") {
16069
16231
  return [];
@@ -16076,7 +16238,7 @@ var WarmHookLogsService = class {
16076
16238
  continue;
16077
16239
  }
16078
16240
  try {
16079
- const raw = await readFile16(join28(LOGS_DIR2, file), "utf-8");
16241
+ const raw = await readFile17(join29(LOGS_DIR2, file), "utf-8");
16080
16242
  const stored = JSON.parse(raw);
16081
16243
  logs.push(withPreview2(stored));
16082
16244
  } catch {
@@ -16094,18 +16256,18 @@ var WarmHookLogsService = class {
16094
16256
  async resetCurrentRunLog() {
16095
16257
  await this.ensureDir();
16096
16258
  try {
16097
- await unlink3(CURRENT_RUN_LOG);
16259
+ await unlink4(CURRENT_RUN_LOG);
16098
16260
  } catch (err) {
16099
16261
  if (err.code !== "ENOENT") throw err;
16100
16262
  }
16101
16263
  }
16102
16264
  async appendCurrentRunLog(chunk) {
16103
16265
  if (!chunk) return;
16104
- await appendFile4(CURRENT_RUN_LOG, chunk, "utf-8");
16266
+ await appendFile5(CURRENT_RUN_LOG, chunk, "utf-8");
16105
16267
  }
16106
16268
  async getCurrentRunLog() {
16107
16269
  try {
16108
- return await readFile16(CURRENT_RUN_LOG, "utf-8");
16270
+ return await readFile17(CURRENT_RUN_LOG, "utf-8");
16109
16271
  } catch (err) {
16110
16272
  if (err.code === "ENOENT") return null;
16111
16273
  throw err;
@@ -16114,7 +16276,7 @@ var WarmHookLogsService = class {
16114
16276
  async getFullOutput(hookType, hookName) {
16115
16277
  const filename = hookType === "global" ? GLOBAL_FILENAME : hookType === "environment" ? ENVIRONMENT_HOOK_LOG_FILENAME : repoHookLogFilename(hookName);
16116
16278
  try {
16117
- const raw = await readFile16(join28(LOGS_DIR2, filename), "utf-8");
16279
+ const raw = await readFile17(join29(LOGS_DIR2, filename), "utf-8");
16118
16280
  const stored = JSON.parse(raw);
16119
16281
  if (stored.hookType !== hookType || stored.hookName !== hookName) {
16120
16282
  return null;
@@ -16133,12 +16295,12 @@ var warmHookLogsService = new WarmHookLogsService();
16133
16295
  // src/services/warm-hooks-service.ts
16134
16296
  async function readRepoWarmHook(repoPath) {
16135
16297
  for (const filename of REPLICAS_CONFIG_FILENAMES) {
16136
- const configPath = join29(repoPath, filename);
16298
+ const configPath = join30(repoPath, filename);
16137
16299
  if (!existsSync8(configPath)) {
16138
16300
  continue;
16139
16301
  }
16140
16302
  try {
16141
- const raw = await readFile17(configPath, "utf-8");
16303
+ const raw = await readFile18(configPath, "utf-8");
16142
16304
  const config = parseReplicasConfigString(raw, filename);
16143
16305
  if (!config.warmHook) {
16144
16306
  return null;
@@ -16394,7 +16556,7 @@ ${combinedScript}` : combinedScript;
16394
16556
  }
16395
16557
 
16396
16558
  // src/services/terminal-service.ts
16397
- import { randomUUID as randomUUID6 } from "crypto";
16559
+ import { randomUUID as randomUUID7 } from "crypto";
16398
16560
  import { existsSync as existsSync9 } from "fs";
16399
16561
  import { spawn as spawn5 } from "node-pty";
16400
16562
  var MAX_REPLAY_CHARS = 1024 * 1024;
@@ -16413,7 +16575,7 @@ var TerminalService = class {
16413
16575
  code: "limit"
16414
16576
  });
16415
16577
  }
16416
- const id = randomUUID6();
16578
+ const id = randomUUID7();
16417
16579
  const shell = process.env.SHELL && existsSync9(process.env.SHELL) ? process.env.SHELL : "/bin/bash";
16418
16580
  const pty = spawn5(shell, ["-l"], {
16419
16581
  name: "xterm-256color",
@@ -17391,11 +17553,11 @@ data: ${JSON.stringify("Terminal session not found")}
17391
17553
  });
17392
17554
  app2.get("/logs", async (c) => {
17393
17555
  try {
17394
- const files = await readdir10(LOG_DIR).catch(() => []);
17556
+ const files = await readdir11(LOG_DIR).catch(() => []);
17395
17557
  const logFiles = files.filter((f) => f.endsWith(".log"));
17396
17558
  const sessions = await Promise.all(
17397
17559
  logFiles.map(async (filename) => {
17398
- const filePath = join30(LOG_DIR, filename);
17560
+ const filePath = join31(LOG_DIR, filename);
17399
17561
  const fileStat = await stat7(filePath);
17400
17562
  const sessionId = filename.replace(/\.log$/, "");
17401
17563
  return {
@@ -17430,7 +17592,7 @@ data: ${JSON.stringify("Terminal session not found")}
17430
17592
  }
17431
17593
  let content;
17432
17594
  try {
17433
- content = await readFile18(filePath, "utf-8");
17595
+ content = await readFile19(filePath, "utf-8");
17434
17596
  } catch {
17435
17597
  return c.json(jsonError("Log session not found"), 404);
17436
17598
  }
@@ -17669,7 +17831,7 @@ function startStatusBroadcaster() {
17669
17831
  if (serialized !== previousRepoStatus) {
17670
17832
  previousRepoStatus = serialized;
17671
17833
  eventService.publish({
17672
- id: randomUUID7(),
17834
+ id: randomUUID8(),
17673
17835
  ts: (/* @__PURE__ */ new Date()).toISOString(),
17674
17836
  type: "repo.status.changed",
17675
17837
  payload: { repos }
@@ -17690,7 +17852,7 @@ function startStatusBroadcaster() {
17690
17852
  if (engineStatusJson !== previousEngineStatus) {
17691
17853
  previousEngineStatus = engineStatusJson;
17692
17854
  eventService.publish({
17693
- id: randomUUID7(),
17855
+ id: randomUUID8(),
17694
17856
  ts: (/* @__PURE__ */ new Date()).toISOString(),
17695
17857
  type: "engine.status.changed",
17696
17858
  payload: { status: engineStatus }
@@ -17709,7 +17871,7 @@ function startStatusBroadcaster() {
17709
17871
  previousHookStatus = hookSnapshot;
17710
17872
  if (!lastHooksRunning && hooksRunning) {
17711
17873
  eventService.publish({
17712
- id: randomUUID7(),
17874
+ id: randomUUID8(),
17713
17875
  ts: (/* @__PURE__ */ new Date()).toISOString(),
17714
17876
  type: "hooks.started",
17715
17877
  payload: { running: true, completed: false }
@@ -17718,7 +17880,7 @@ function startStatusBroadcaster() {
17718
17880
  }
17719
17881
  if (hooksRunning) {
17720
17882
  eventService.publish({
17721
- id: randomUUID7(),
17883
+ id: randomUUID8(),
17722
17884
  ts: (/* @__PURE__ */ new Date()).toISOString(),
17723
17885
  type: "hooks.progress",
17724
17886
  payload: { running: true, completed: false }
@@ -17727,7 +17889,7 @@ function startStatusBroadcaster() {
17727
17889
  }
17728
17890
  if (lastHooksRunning && !hooksRunning && hooksCompleted && !hooksFailed) {
17729
17891
  eventService.publish({
17730
- id: randomUUID7(),
17892
+ id: randomUUID8(),
17731
17893
  ts: (/* @__PURE__ */ new Date()).toISOString(),
17732
17894
  type: "hooks.completed",
17733
17895
  payload: { running: false, completed: true }
@@ -17736,7 +17898,7 @@ function startStatusBroadcaster() {
17736
17898
  }
17737
17899
  if (lastHooksRunning && !hooksRunning && hooksFailed) {
17738
17900
  eventService.publish({
17739
- id: randomUUID7(),
17901
+ id: randomUUID8(),
17740
17902
  ts: (/* @__PURE__ */ new Date()).toISOString(),
17741
17903
  type: "hooks.failed",
17742
17904
  payload: { running: false, completed: hooksCompleted }
@@ -17744,7 +17906,7 @@ function startStatusBroadcaster() {
17744
17906
  });
17745
17907
  }
17746
17908
  eventService.publish({
17747
- id: randomUUID7(),
17909
+ id: randomUUID8(),
17748
17910
  ts: (/* @__PURE__ */ new Date()).toISOString(),
17749
17911
  type: "hooks.status",
17750
17912
  payload: {
@@ -17798,20 +17960,20 @@ serve(
17798
17960
  }
17799
17961
  const repos = await gitService.listRepos();
17800
17962
  await eventService.publish({
17801
- id: randomUUID7(),
17963
+ id: randomUUID8(),
17802
17964
  ts: (/* @__PURE__ */ new Date()).toISOString(),
17803
17965
  type: "repo.discovered",
17804
17966
  payload: { repos }
17805
17967
  });
17806
17968
  const repoStatuses = await gitService.listRepos();
17807
17969
  await eventService.publish({
17808
- id: randomUUID7(),
17970
+ id: randomUUID8(),
17809
17971
  ts: (/* @__PURE__ */ new Date()).toISOString(),
17810
17972
  type: "repo.status.changed",
17811
17973
  payload: { repos: repoStatuses }
17812
17974
  });
17813
17975
  await eventService.publish({
17814
- id: randomUUID7(),
17976
+ id: randomUUID8(),
17815
17977
  ts: (/* @__PURE__ */ new Date()).toISOString(),
17816
17978
  type: "engine.ready",
17817
17979
  payload: { version: "v1" }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.533",
3
+ "version": "0.1.536",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",