replicas-engine 0.1.532 → 0.1.535

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/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-v3";
642
+ var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-07-31-v6";
627
643
 
628
644
  // ../shared/src/runtime-env.ts
629
645
  function shellQuotePosix(value) {
@@ -685,6 +701,15 @@ function parsePosixEnvFile(content) {
685
701
  }
686
702
 
687
703
  // ../shared/src/git.ts
704
+ var GIT_CREDENTIAL_HELPER_FILENAME = ".git-credential-replicas";
705
+ function buildGitCredentialHelperScript(credentialsPath) {
706
+ return `#!/bin/sh
707
+ if [ "$1" = "get" ]; then
708
+ exec git credential-store --file=${shellQuotePosix(credentialsPath)} get
709
+ fi
710
+ exit 0
711
+ `;
712
+ }
688
713
  function parseGitRemote(remoteUrl) {
689
714
  const normalized = remoteUrl.trim().replace(/\/+$/, "").replace(/\.git$/, "");
690
715
  try {
@@ -4940,7 +4965,7 @@ var MAX_ENGINE_LOG_BYTES = 50 * 1024 * 1024;
4940
4965
  var SKILL_REGISTRY_MANIFEST_VERSION = 1;
4941
4966
 
4942
4967
  // src/index.ts
4943
- import { randomUUID as randomUUID7 } from "crypto";
4968
+ import { randomUUID as randomUUID8 } from "crypto";
4944
4969
  import { connect } from "net";
4945
4970
 
4946
4971
  // src/utils/exec.ts
@@ -5216,7 +5241,7 @@ var monolithService = new MonolithService();
5216
5241
 
5217
5242
  // src/utils/file.ts
5218
5243
  import { mkdir, readFile, rename, unlink, writeFile } from "fs/promises";
5219
- import { dirname } from "path";
5244
+ import { dirname, join as join3 } from "path";
5220
5245
 
5221
5246
  // src/utils/async-lock.ts
5222
5247
  var AsyncLock = class {
@@ -5248,6 +5273,14 @@ async function writeSecureCredentialFile(filePath, content, options) {
5248
5273
  }
5249
5274
  await atomicWriteFile(filePath, content, { mode: 384 });
5250
5275
  }
5276
+ async function configureManagedGitCredentialHelper(homeDir) {
5277
+ const credentialsPath = join3(homeDir, ".git-credentials");
5278
+ const helperPath = join3(homeDir, GIT_CREDENTIAL_HELPER_FILENAME);
5279
+ await atomicWriteFile(helperPath, buildGitCredentialHelperScript(credentialsPath), { mode: 448 });
5280
+ await execFileAsync("git", ["config", "--global", "credential.helper", helperPath], {
5281
+ env: { ...process.env, HOME: homeDir }
5282
+ });
5283
+ }
5251
5284
  var credentialFileLock = new AsyncLock();
5252
5285
  function upsertCredentialFileLines(filePath, hosts, lines) {
5253
5286
  return credentialFileLock.run(async () => {
@@ -5277,12 +5310,12 @@ function removeCredentialFileLines(filePath, shouldRemove) {
5277
5310
  // src/git/service.ts
5278
5311
  import { readdir, readFile as readFile3, stat } from "fs/promises";
5279
5312
  import { spawn } from "child_process";
5280
- import { join as join5 } from "path";
5313
+ import { join as join6 } from "path";
5281
5314
 
5282
5315
  // src/utils/state.ts
5283
5316
  import { readFile as readFile2, mkdir as mkdir2 } from "fs/promises";
5284
5317
  import { existsSync } from "fs";
5285
- import { join as join3 } from "path";
5318
+ import { join as join4 } from "path";
5286
5319
  import { homedir as homedir3 } from "os";
5287
5320
 
5288
5321
  // src/utils/type-guards.ts
@@ -5291,8 +5324,8 @@ function isRecord4(value) {
5291
5324
  }
5292
5325
 
5293
5326
  // src/utils/state.ts
5294
- var STATE_DIR = join3(homedir3(), ".replicas");
5295
- var STATE_FILE = join3(STATE_DIR, "engine-state.json");
5327
+ var STATE_DIR = join4(homedir3(), ".replicas");
5328
+ var STATE_FILE = join4(STATE_DIR, "engine-state.json");
5296
5329
  var DEFAULT_STATE = {
5297
5330
  repos: {}
5298
5331
  };
@@ -5390,7 +5423,7 @@ async function saveRepoState(repoName, state, fallbackState) {
5390
5423
 
5391
5424
  // src/git/commands.ts
5392
5425
  import { readFileSync as readFileSync2 } from "fs";
5393
- import { join as join4 } from "path";
5426
+ import { join as join5 } from "path";
5394
5427
  async function runGitCommand(args, cwd, options = {}) {
5395
5428
  const { stdout } = await execFileAsync("git", args, {
5396
5429
  cwd,
@@ -5401,7 +5434,7 @@ async function runGitCommand(args, cwd, options = {}) {
5401
5434
  }
5402
5435
  function readRepoHeadBranch(repoPath) {
5403
5436
  try {
5404
- const contents = readFileSync2(join4(repoPath, ".git", "HEAD"), "utf-8").trim();
5437
+ const contents = readFileSync2(join5(repoPath, ".git", "HEAD"), "utf-8").trim();
5405
5438
  const match = contents.match(/^ref:\s+refs\/heads\/(.+)$/);
5406
5439
  return match ? match[1] : null;
5407
5440
  } catch {
@@ -5463,13 +5496,13 @@ var GitService = class {
5463
5496
  const repos = [];
5464
5497
  let complete = true;
5465
5498
  for (const entry of entries) {
5466
- const fullPath = join5(root, entry);
5499
+ const fullPath = join6(root, entry);
5467
5500
  try {
5468
5501
  const entryStat = await stat(fullPath);
5469
5502
  if (!entryStat.isDirectory()) {
5470
5503
  continue;
5471
5504
  }
5472
- const hasGit = Boolean(await this.safeStat(join5(fullPath, ".git")));
5505
+ const hasGit = Boolean(await this.safeStat(join6(fullPath, ".git")));
5473
5506
  if (!hasGit) {
5474
5507
  continue;
5475
5508
  }
@@ -5660,7 +5693,7 @@ var GitService = class {
5660
5693
  let total = 0;
5661
5694
  for (const path6 of paths) {
5662
5695
  try {
5663
- const contents = await readFile3(join5(repoPath, path6));
5696
+ const contents = await readFile3(join6(repoPath, path6));
5664
5697
  if (contents.length === 0 || contents.includes(0)) {
5665
5698
  continue;
5666
5699
  }
@@ -5843,7 +5876,7 @@ var GitService = class {
5843
5876
  }
5844
5877
  async getGitLabAccessToken(host) {
5845
5878
  try {
5846
- const credentials = await readFile3(join5(ENGINE_ENV.HOME_DIR, ".git-credentials"), "utf-8");
5879
+ const credentials = await readFile3(join6(ENGINE_ENV.HOME_DIR, ".git-credentials"), "utf-8");
5847
5880
  for (const line of credentials.split("\n")) {
5848
5881
  const trimmed = line.trim();
5849
5882
  if (!trimmed) continue;
@@ -6349,7 +6382,7 @@ var infisicalTokenManager = new InfisicalTokenManager();
6349
6382
  // src/utils/logger.ts
6350
6383
  import { mkdir as mkdir3, writeFile as writeFile2 } from "fs/promises";
6351
6384
  import { homedir as homedir4 } from "os";
6352
- import { join as join6 } from "path";
6385
+ import { join as join7 } from "path";
6353
6386
  import { format } from "util";
6354
6387
  import { randomBytes } from "crypto";
6355
6388
 
@@ -6424,7 +6457,7 @@ var StreamWriter = class {
6424
6457
  };
6425
6458
 
6426
6459
  // src/utils/logger.ts
6427
- var LOG_DIR = join6(homedir4(), ".replicas", "logs");
6460
+ var LOG_DIR = join7(homedir4(), ".replicas", "logs");
6428
6461
  var EngineLogger = class {
6429
6462
  _sessionId = null;
6430
6463
  patched = false;
@@ -6435,7 +6468,7 @@ var EngineLogger = class {
6435
6468
  async initialize() {
6436
6469
  await mkdir3(LOG_DIR, { recursive: true });
6437
6470
  this._sessionId = this.createSessionId();
6438
- const logPath = join6(LOG_DIR, `${this._sessionId}.log`);
6471
+ const logPath = join7(LOG_DIR, `${this._sessionId}.log`);
6439
6472
  await writeFile2(logPath, `=== Replicas Engine Session ${this._sessionId} ===
6440
6473
  `, "utf-8");
6441
6474
  this.writer.open(logPath);
@@ -6482,7 +6515,7 @@ var engineLogger = new EngineLogger();
6482
6515
  // src/services/replicas-config-service.ts
6483
6516
  import { readFile as readFile6, appendFile, writeFile as writeFile4, mkdir as mkdir6 } from "fs/promises";
6484
6517
  import { existsSync as existsSync3 } from "fs";
6485
- import { join as join10 } from "path";
6518
+ import { join as join11 } from "path";
6486
6519
  import { homedir as homedir8 } from "os";
6487
6520
  import { spawn as spawn2 } from "child_process";
6488
6521
 
@@ -6490,21 +6523,21 @@ import { spawn as spawn2 } from "child_process";
6490
6523
  import { mkdir as mkdir4, readFile as readFile4 } from "fs/promises";
6491
6524
  import { existsSync as existsSync2 } from "fs";
6492
6525
  import { homedir as homedir6 } from "os";
6493
- import { join as join8 } from "path";
6526
+ import { join as join9 } from "path";
6494
6527
 
6495
6528
  // src/managers/agent-auth-paths.ts
6496
6529
  import { homedir as homedir5 } from "os";
6497
- import { join as join7 } from "path";
6498
- var OPENCODE_AUTH_PATH = join7(homedir5(), ".local", "share", "opencode", "auth.json");
6499
- var PI_AUTH_PATH = join7(homedir5(), ".pi", "agent", "auth.json");
6530
+ import { join as join8 } from "path";
6531
+ var OPENCODE_AUTH_PATH = join8(homedir5(), ".local", "share", "opencode", "auth.json");
6532
+ var PI_AUTH_PATH = join8(homedir5(), ".pi", "agent", "auth.json");
6500
6533
 
6501
6534
  // src/services/environment-details-service.ts
6502
- var REPLICAS_DIR = join8(homedir6(), ".replicas");
6503
- var DETAILS_FILE = join8(REPLICAS_DIR, "environment-details.json");
6504
- var CLAUDE_CREDENTIALS_PATH = join8(homedir6(), ".claude", ".credentials.json");
6505
- var CODEX_AUTH_PATH = join8(homedir6(), ".codex", "auth.json");
6506
- var GH_HOSTS_PATH = join8(homedir6(), ".config", "gh", "hosts.yml");
6507
- var GIT_CREDENTIALS_PATH = join8(homedir6(), ".git-credentials");
6535
+ var REPLICAS_DIR = join9(homedir6(), ".replicas");
6536
+ var DETAILS_FILE = join9(REPLICAS_DIR, "environment-details.json");
6537
+ var CLAUDE_CREDENTIALS_PATH = join9(homedir6(), ".claude", ".credentials.json");
6538
+ var CODEX_AUTH_PATH = join9(homedir6(), ".codex", "auth.json");
6539
+ var GH_HOSTS_PATH = join9(homedir6(), ".config", "gh", "hosts.yml");
6540
+ var GIT_CREDENTIALS_PATH = join9(homedir6(), ".git-credentials");
6508
6541
  function detectClaudeAuthMethod() {
6509
6542
  if (existsSync2(CLAUDE_CREDENTIALS_PATH)) {
6510
6543
  return "oauth";
@@ -6711,7 +6744,7 @@ var environmentDetailsService = new EnvironmentDetailsService();
6711
6744
  // src/services/start-hook-logs-service.ts
6712
6745
  import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile3, readdir as readdir2 } from "fs/promises";
6713
6746
  import { homedir as homedir7 } from "os";
6714
- import { join as join9 } from "path";
6747
+ import { join as join10 } from "path";
6715
6748
 
6716
6749
  // src/services/hook-log-files.ts
6717
6750
  import { createHash } from "crypto";
@@ -6723,7 +6756,7 @@ function repoHookLogFilename(repoName) {
6723
6756
  }
6724
6757
 
6725
6758
  // src/services/start-hook-logs-service.ts
6726
- var LOGS_DIR = join9(homedir7(), ".replicas", "start-hook-logs");
6759
+ var LOGS_DIR = join10(homedir7(), ".replicas", "start-hook-logs");
6727
6760
  function withPreview(stored) {
6728
6761
  const preview = buildHookOutputPreview(stored.output);
6729
6762
  return { ...stored, ...preview };
@@ -6761,7 +6794,7 @@ var StartHookLogsService = class {
6761
6794
  await this.ensureDir();
6762
6795
  const log = { hookType, hookName, repoName: hookName, ...entry };
6763
6796
  const filename = hookType === "environment" ? ENVIRONMENT_HOOK_LOG_FILENAME : repoHookLogFilename(hookName);
6764
- await writeFile3(join9(LOGS_DIR, filename), `${JSON.stringify(log, null, 2)}
6797
+ await writeFile3(join10(LOGS_DIR, filename), `${JSON.stringify(log, null, 2)}
6765
6798
  `, "utf-8");
6766
6799
  }
6767
6800
  async saveEnvironmentLog(entry) {
@@ -6786,7 +6819,7 @@ var StartHookLogsService = class {
6786
6819
  continue;
6787
6820
  }
6788
6821
  try {
6789
- const raw = await readFile5(join9(LOGS_DIR, file), "utf-8");
6822
+ const raw = await readFile5(join10(LOGS_DIR, file), "utf-8");
6790
6823
  const stored = normalizeStored(JSON.parse(raw));
6791
6824
  if (stored) {
6792
6825
  logs.push(withPreview(stored));
@@ -6805,7 +6838,7 @@ var StartHookLogsService = class {
6805
6838
  async getFullOutput(hookType, hookName) {
6806
6839
  const filename = hookType === "environment" ? ENVIRONMENT_HOOK_LOG_FILENAME : repoHookLogFilename(hookName);
6807
6840
  try {
6808
- const raw = await readFile5(join9(LOGS_DIR, filename), "utf-8");
6841
+ const raw = await readFile5(join10(LOGS_DIR, filename), "utf-8");
6809
6842
  const stored = normalizeStored(JSON.parse(raw));
6810
6843
  if (!stored || stored.hookType !== hookType || stored.hookName !== hookName) {
6811
6844
  return null;
@@ -6822,7 +6855,7 @@ var StartHookLogsService = class {
6822
6855
  var startHookLogsService = new StartHookLogsService();
6823
6856
 
6824
6857
  // src/services/replicas-config-service.ts
6825
- var START_HOOKS_LOG = join10(homedir8(), ".replicas", "startHooks.log");
6858
+ var START_HOOKS_LOG = join11(homedir8(), ".replicas", "startHooks.log");
6826
6859
  var START_HOOKS_RUNNING_PROMPT = `IMPORTANT - Start Hooks Running:
6827
6860
  Start hooks are shell commands/scripts set by repository owners that run on workspace startup.
6828
6861
  These hooks are currently executing in the background. You can:
@@ -6833,7 +6866,7 @@ The start hooks may install dependencies, build projects, or perform other setup
6833
6866
  If your task depends on setup being complete, check the log file before proceeding.`;
6834
6867
  async function readReplicasConfigFromDir(dirPath) {
6835
6868
  for (const filename of REPLICAS_CONFIG_FILENAMES) {
6836
- const configPath = join10(dirPath, filename);
6869
+ const configPath = join11(dirPath, filename);
6837
6870
  if (!existsSync3(configPath)) {
6838
6871
  continue;
6839
6872
  }
@@ -6903,7 +6936,7 @@ var ReplicasConfigService = class {
6903
6936
  const logLine = `[${timestamp}] ${message}
6904
6937
  `;
6905
6938
  try {
6906
- await mkdir6(join10(homedir8(), ".replicas"), { recursive: true });
6939
+ await mkdir6(join11(homedir8(), ".replicas"), { recursive: true });
6907
6940
  await appendFile(START_HOOKS_LOG, logLine, "utf-8");
6908
6941
  } catch (error) {
6909
6942
  console.error("Failed to write to start hooks log:", error);
@@ -7038,7 +7071,7 @@ var ReplicasConfigService = class {
7038
7071
  this.hooksCompleted = false;
7039
7072
  this.hooksFailed = false;
7040
7073
  try {
7041
- await mkdir6(join10(homedir8(), ".replicas"), { recursive: true });
7074
+ await mkdir6(join11(homedir8(), ".replicas"), { recursive: true });
7042
7075
  await writeFile4(
7043
7076
  START_HOOKS_LOG,
7044
7077
  `=== Start Hooks Execution Log ===
@@ -7234,10 +7267,10 @@ var replicasConfigService = new ReplicasConfigService();
7234
7267
  // src/services/event-service.ts
7235
7268
  import { mkdir as mkdir7 } from "fs/promises";
7236
7269
  import { homedir as homedir9 } from "os";
7237
- import { join as join11 } from "path";
7270
+ import { join as join12 } from "path";
7238
7271
  import { randomUUID } from "crypto";
7239
- var ENGINE_DIR = join11(homedir9(), ".replicas", "engine");
7240
- var EVENTS_FILE = join11(ENGINE_DIR, "events.jsonl");
7272
+ var ENGINE_DIR = join12(homedir9(), ".replicas", "engine");
7273
+ var EVENTS_FILE = join12(ENGINE_DIR, "events.jsonl");
7241
7274
  var EventService = class {
7242
7275
  subscribers = /* @__PURE__ */ new Map();
7243
7276
  writer = new StreamWriter();
@@ -7272,8 +7305,8 @@ import { mkdir as mkdir8, readFile as readFile7 } from "fs/promises";
7272
7305
  import { existsSync as existsSync4 } from "fs";
7273
7306
  import { randomUUID as randomUUID2 } from "crypto";
7274
7307
  import { homedir as homedir10 } from "os";
7275
- import { dirname as dirname2, join as join12 } from "path";
7276
- var PREVIEW_PORTS_FILE = join12(homedir10(), ".replicas", "preview-ports.json");
7308
+ import { dirname as dirname2, join as join13 } from "path";
7309
+ var PREVIEW_PORTS_FILE = join13(homedir10(), ".replicas", "preview-ports.json");
7277
7310
  async function readPreviewsFile() {
7278
7311
  try {
7279
7312
  if (!existsSync4(PREVIEW_PORTS_FILE)) {
@@ -7384,17 +7417,17 @@ async function registerDesktopPreview() {
7384
7417
 
7385
7418
  // src/services/chat/chat-service.ts
7386
7419
  import { existsSync as existsSync7 } from "fs";
7387
- 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";
7388
7421
  import { homedir as homedir15 } from "os";
7389
- import { join as join25 } from "path";
7390
- import { randomUUID as randomUUID5 } from "crypto";
7422
+ import { join as join27 } from "path";
7423
+ import { randomUUID as randomUUID6 } from "crypto";
7391
7424
 
7392
7425
  // src/managers/claude-manager.ts
7393
7426
  import {
7394
7427
  query
7395
7428
  } from "@anthropic-ai/claude-agent-sdk";
7396
7429
  import { randomUUID as randomUUID4 } from "crypto";
7397
- import { dirname as dirname4, join as join15 } from "path";
7430
+ import { dirname as dirname4, join as join16 } from "path";
7398
7431
  import { mkdir as mkdir10 } from "fs/promises";
7399
7432
  import { homedir as homedir12 } from "os";
7400
7433
 
@@ -7783,7 +7816,7 @@ function extractPlanFromCodexAspNotification(notification) {
7783
7816
  import { randomUUID as randomUUID3 } from "crypto";
7784
7817
  import { mkdir as mkdir9, unlink as unlink2, writeFile as writeFile5 } from "fs/promises";
7785
7818
  import { homedir as homedir11 } from "os";
7786
- import { join as join13 } from "path";
7819
+ import { join as join14 } from "path";
7787
7820
  function isImageMediaType(value) {
7788
7821
  return IMAGE_MEDIA_TYPES.includes(value);
7789
7822
  }
@@ -7883,14 +7916,14 @@ async function normalizeImages(images) {
7883
7916
  }
7884
7917
  return normalized;
7885
7918
  }
7886
- async function saveNormalizedImagesToTempFiles(images, tempImageDir = join13(homedir11(), ".replicas", "codex", "temp-images")) {
7919
+ async function saveNormalizedImagesToTempFiles(images, tempImageDir = join14(homedir11(), ".replicas", "codex", "temp-images")) {
7887
7920
  await mkdir9(tempImageDir, { recursive: true });
7888
7921
  const tempPaths = [];
7889
7922
  try {
7890
7923
  for (const image of images) {
7891
7924
  const ext = image.source.media_type.split("/")[1] || "png";
7892
7925
  const filename = `img_${randomUUID3()}.${ext}`;
7893
- const filepath = join13(tempImageDir, filename);
7926
+ const filepath = join14(tempImageDir, filename);
7894
7927
  await writeFile5(filepath, Buffer.from(image.source.data, "base64"));
7895
7928
  tempPaths.push(filepath);
7896
7929
  }
@@ -8475,7 +8508,7 @@ function reportCommandProtectionBlock(options) {
8475
8508
 
8476
8509
  // src/services/skill-registry-service.ts
8477
8510
  import { readFile as readFile8, readdir as readdir3, stat as stat2 } from "fs/promises";
8478
- import { dirname as dirname3, isAbsolute, join as join14, relative, resolve } from "path";
8511
+ import { dirname as dirname3, isAbsolute, join as join15, relative, resolve } from "path";
8479
8512
  var REGISTRY_ROOT_DIR = ".replicas/skill-registries";
8480
8513
  var REGISTRY_MANIFEST = "manifest.json";
8481
8514
  async function getSkillRegistryInventory(homeDir) {
@@ -8536,10 +8569,10 @@ async function scanRegistry(registryDir) {
8536
8569
  }
8537
8570
  async function findSkillCollections(registryDir) {
8538
8571
  const candidateRoots = [
8539
- { source: "skills", root: join14(registryDir, "skills") },
8540
- { source: "agents", root: join14(registryDir, ".agents", "skills") },
8541
- { source: "codex", root: join14(registryDir, ".codex", "skills") },
8542
- { source: "claude", root: join14(registryDir, ".claude", "skills") }
8572
+ { source: "skills", root: join15(registryDir, "skills") },
8573
+ { source: "agents", root: join15(registryDir, ".agents", "skills") },
8574
+ { source: "codex", root: join15(registryDir, ".codex", "skills") },
8575
+ { source: "claude", root: join15(registryDir, ".claude", "skills") }
8543
8576
  ];
8544
8577
  const collections = [];
8545
8578
  for (const { source, root } of candidateRoots) {
@@ -8553,9 +8586,9 @@ async function findSkillCollections(registryDir) {
8553
8586
  async function findSkillDirsInRoot(root) {
8554
8587
  const skillDirs = [];
8555
8588
  for (const dirent of await safeReadDir(root)) {
8556
- const skillDir = join14(root, dirent.name);
8589
+ const skillDir = join15(root, dirent.name);
8557
8590
  if (!await isDirectoryDirent(dirent, skillDir)) continue;
8558
- if (await fileExists(join14(skillDir, "SKILL.md"))) {
8591
+ if (await fileExists(join15(skillDir, "SKILL.md"))) {
8559
8592
  skillDirs.push(skillDir);
8560
8593
  }
8561
8594
  }
@@ -8565,9 +8598,9 @@ async function findTopLevelSkills(registryDir) {
8565
8598
  const skills = [];
8566
8599
  for (const dirent of await safeReadDir(registryDir)) {
8567
8600
  if (dirent.name.startsWith(".") || dirent.name === "skills" || dirent.name === "plugins") continue;
8568
- const skillDir = join14(registryDir, dirent.name);
8601
+ const skillDir = join15(registryDir, dirent.name);
8569
8602
  if (!await isDirectoryDirent(dirent, skillDir)) continue;
8570
- if (await fileExists(join14(skillDir, "SKILL.md"))) {
8603
+ if (await fileExists(join15(skillDir, "SKILL.md"))) {
8571
8604
  skills.push({ source: "root", skillDir });
8572
8605
  }
8573
8606
  }
@@ -8578,10 +8611,10 @@ async function findClaudePluginRoots(registryDir) {
8578
8611
  async function walk(dir) {
8579
8612
  for (const dirent of await safeReadDir(dir)) {
8580
8613
  if (dirent.name === ".git") continue;
8581
- const child = join14(dir, dirent.name);
8614
+ const child = join15(dir, dirent.name);
8582
8615
  if (!await isDirectoryDirent(dirent, child)) continue;
8583
8616
  if (dirent.name === ".claude-plugin") {
8584
- if (await fileExists(join14(child, "plugin.json"))) {
8617
+ if (await fileExists(join15(child, "plugin.json"))) {
8585
8618
  roots.push(dirname3(child));
8586
8619
  }
8587
8620
  continue;
@@ -8593,7 +8626,7 @@ async function findClaudePluginRoots(registryDir) {
8593
8626
  return uniqueStrings(roots);
8594
8627
  }
8595
8628
  async function hasCodexMarketplace(registryDir) {
8596
- return await fileExists(join14(registryDir, ".agents", "plugins", "marketplace.json")) || await fileExists(join14(registryDir, ".codex", "plugins", "marketplace.json"));
8629
+ return await fileExists(join15(registryDir, ".agents", "plugins", "marketplace.json")) || await fileExists(join15(registryDir, ".codex", "plugins", "marketplace.json"));
8597
8630
  }
8598
8631
  async function installCodexRegistryPlugins(client, inventory) {
8599
8632
  const cwds = inventory.codexMarketplaceCwds;
@@ -8643,10 +8676,10 @@ function isSkillRegistryEntry(value) {
8643
8676
  return "index" in value && typeof value.index === "number" && "name" in value && typeof value.name === "string" && "url" in value && typeof value.url === "string" && "checkoutPath" in value && typeof value.checkoutPath === "string";
8644
8677
  }
8645
8678
  function getRegistryRoot(homeDir) {
8646
- return join14(homeDir, REGISTRY_ROOT_DIR);
8679
+ return join15(homeDir, REGISTRY_ROOT_DIR);
8647
8680
  }
8648
8681
  function getManifestPath(homeDir) {
8649
- return join14(getRegistryRoot(homeDir), REGISTRY_MANIFEST);
8682
+ return join15(getRegistryRoot(homeDir), REGISTRY_MANIFEST);
8650
8683
  }
8651
8684
  function emptyInventory(registryRoot) {
8652
8685
  return {
@@ -8994,7 +9027,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
8994
9027
  authRetrying = false;
8995
9028
  constructor(options) {
8996
9029
  super(options);
8997
- this.historyFilePath = options.historyFilePath ?? join15(homedir12(), ".replicas", "claude", "history.jsonl");
9030
+ this.historyFilePath = options.historyFilePath ?? join16(homedir12(), ".replicas", "claude", "history.jsonl");
8998
9031
  this.historyFile = new CodexHistoryFile(this.historyFilePath);
8999
9032
  this.systemPromptOverride = options.systemPromptOverride;
9000
9033
  this.toolsOverride = options.tools;
@@ -9944,7 +9977,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
9944
9977
 
9945
9978
  // src/managers/codex-asp/codex-asp-manager.ts
9946
9979
  import { readdir as readdir4 } from "fs/promises";
9947
- import { join as join17 } from "path";
9980
+ import { join as join18 } from "path";
9948
9981
 
9949
9982
  // src/managers/codex-asp/app-server-process.ts
9950
9983
  import { spawn as spawn3 } from "child_process";
@@ -10145,7 +10178,7 @@ var DEFAULT_CODEX_ARGS = ["app-server", "--listen", "stdio://"];
10145
10178
  var MIN_CODEX_CLI_VERSION = "0.144.6";
10146
10179
  var CODEX_UPGRADE_TIMEOUT_MS = 12e4;
10147
10180
  var codexCliVersionEnsured = null;
10148
- var ENGINE_PACKAGE_VERSION = "0.1.532";
10181
+ var ENGINE_PACKAGE_VERSION = "0.1.535";
10149
10182
  var INITIALIZE_METHOD = "initialize";
10150
10183
  var INITIALIZED_NOTIFICATION = "initialized";
10151
10184
  var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
@@ -11069,15 +11102,15 @@ var TranscriptUpdateCoalescer = class {
11069
11102
 
11070
11103
  // src/services/chat/history-paths.ts
11071
11104
  import { homedir as homedir13 } from "os";
11072
- import { join as join16 } from "path";
11073
- var ENGINE_DIR2 = join16(homedir13(), ".replicas", "engine");
11074
- var CHATS_FILE = join16(ENGINE_DIR2, "chats.json");
11075
- var CLAUDE_HISTORY_DIR = join16(ENGINE_DIR2, "claude-histories");
11076
- var RELAY_HISTORY_DIR = join16(ENGINE_DIR2, "relay-histories");
11077
- var CODEX_HISTORY_DIR = join16(ENGINE_DIR2, "codex-histories");
11078
- var CURSOR_HISTORY_DIR = join16(ENGINE_DIR2, "cursor-histories");
11079
- var OPENCODE_HISTORY_DIR = join16(ENGINE_DIR2, "opencode-histories");
11080
- var PI_HISTORY_DIR = join16(ENGINE_DIR2, "pi-histories");
11105
+ import { join as join17 } from "path";
11106
+ var ENGINE_DIR2 = join17(homedir13(), ".replicas", "engine");
11107
+ var CHATS_FILE = join17(ENGINE_DIR2, "chats.json");
11108
+ var CLAUDE_HISTORY_DIR = join17(ENGINE_DIR2, "claude-histories");
11109
+ var RELAY_HISTORY_DIR = join17(ENGINE_DIR2, "relay-histories");
11110
+ var CODEX_HISTORY_DIR = join17(ENGINE_DIR2, "codex-histories");
11111
+ var CURSOR_HISTORY_DIR = join17(ENGINE_DIR2, "cursor-histories");
11112
+ var OPENCODE_HISTORY_DIR = join17(ENGINE_DIR2, "opencode-histories");
11113
+ var PI_HISTORY_DIR = join17(ENGINE_DIR2, "pi-histories");
11081
11114
  var HISTORY_DIR_BY_PROVIDER = {
11082
11115
  claude: CLAUDE_HISTORY_DIR,
11083
11116
  relay: RELAY_HISTORY_DIR,
@@ -11123,7 +11156,7 @@ async function readCodexAspThreadHistory(threadId) {
11123
11156
  }
11124
11157
  for (const entry of entries) {
11125
11158
  if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
11126
- const history = await new CodexHistoryFile(join17(CODEX_HISTORY_DIR, entry.name)).load();
11159
+ const history = await new CodexHistoryFile(join18(CODEX_HISTORY_DIR, entry.name)).load();
11127
11160
  const transcript = history.transcriptsByThreadId.get(threadId);
11128
11161
  if (transcript) {
11129
11162
  return {
@@ -12350,7 +12383,7 @@ var CodexAspManager = class extends CodingAgentManager {
12350
12383
 
12351
12384
  // src/managers/cursor-manager.ts
12352
12385
  import { mkdir as mkdir11, readFile as readFile10, readdir as readdir5 } from "fs/promises";
12353
- import { basename, dirname as dirname5, extname, join as join18 } from "path";
12386
+ import { basename, dirname as dirname5, extname, join as join19 } from "path";
12354
12387
  import { parse as parseYaml2 } from "yaml";
12355
12388
  import { Agent as CursorAgent } from "@cursor/sdk";
12356
12389
  var CURSOR_SLASH_COMMANDS_CACHE_MS = 3e4;
@@ -12409,7 +12442,7 @@ async function listCursorCommandsInDirectory(directory) {
12409
12442
  const name = basename(entry.name, ".md");
12410
12443
  let description;
12411
12444
  try {
12412
- description = extractCursorCommandDescription(await readFile10(join18(directory, entry.name), "utf8"));
12445
+ description = extractCursorCommandDescription(await readFile10(join19(directory, entry.name), "utf8"));
12413
12446
  } catch (error) {
12414
12447
  console.warn("[CursorManager] Failed to read slash command file:", error);
12415
12448
  }
@@ -12431,7 +12464,7 @@ var CursorManager = class extends CodingAgentManager {
12431
12464
  slashCommandsRequest = null;
12432
12465
  constructor(options) {
12433
12466
  super(options);
12434
- this.historyFilePath = options.historyFilePath ?? join18(ENGINE_ENV.HOME_DIR, ".replicas", "cursor", "history.jsonl");
12467
+ this.historyFilePath = options.historyFilePath ?? join19(ENGINE_ENV.HOME_DIR, ".replicas", "cursor", "history.jsonl");
12435
12468
  this.historyFile = new CodexHistoryFile(this.historyFilePath);
12436
12469
  this.initializeManager(this.processMessageInternal.bind(this));
12437
12470
  }
@@ -12482,7 +12515,7 @@ var CursorManager = class extends CodingAgentManager {
12482
12515
  this.slashCommandsRequest ??= (async () => {
12483
12516
  try {
12484
12517
  const repoDirectories = await getAgentAdditionalDirectories();
12485
- const commandDirectories = [this.workingDirectory, ...repoDirectories, ENGINE_ENV.HOME_DIR].map((directory) => join18(directory, ".cursor", "commands"));
12518
+ const commandDirectories = [this.workingDirectory, ...repoDirectories, ENGINE_ENV.HOME_DIR].map((directory) => join19(directory, ".cursor", "commands"));
12486
12519
  const commands = mergeSlashCommands(
12487
12520
  ...await Promise.all(commandDirectories.map(listCursorCommandsInDirectory))
12488
12521
  );
@@ -12742,7 +12775,7 @@ ${request.message}` : request.message;
12742
12775
  // src/managers/opencode-manager.ts
12743
12776
  import { existsSync as existsSync6 } from "fs";
12744
12777
  import { mkdir as mkdir12, readFile as readFile11 } from "fs/promises";
12745
- import { delimiter, dirname as dirname6, join as join19 } from "path";
12778
+ import { delimiter, dirname as dirname6, join as join20 } from "path";
12746
12779
  import { randomBytes as randomBytes2 } from "crypto";
12747
12780
  import { fileURLToPath } from "url";
12748
12781
  import { Agent } from "undici";
@@ -12770,7 +12803,7 @@ import {
12770
12803
  createOpencodeServer
12771
12804
  } from "@opencode-ai/sdk/v2";
12772
12805
  var OPENCODE_SHIM_DIR = dirname6(fileURLToPath(new URL("../../scripts/opencode", import.meta.url)));
12773
- var OPENCODE_CONFIG_PATH = join19(ENGINE_ENV.HOME_DIR, ".config", "opencode", "opencode.json");
12806
+ var OPENCODE_CONFIG_PATH = join20(ENGINE_ENV.HOME_DIR, ".config", "opencode", "opencode.json");
12774
12807
  var OPENCODE_FETCH_DISPATCHER = new Agent({ headersTimeout: 0, bodyTimeout: 0 });
12775
12808
  var OPENCODE_SERVER_STARTUP_TIMEOUT_MS = 3e4;
12776
12809
  var OPENCODE_WORKSPACE_PERMISSION = {
@@ -13018,7 +13051,7 @@ var OpencodeManager = class extends CodingAgentManager {
13018
13051
  constructor(options) {
13019
13052
  super(options);
13020
13053
  this.sessionId = options.initialSessionId;
13021
- this.historyFilePath = options.historyFilePath ?? join19(ENGINE_ENV.HOME_DIR, ".replicas", "opencode", "history.jsonl");
13054
+ this.historyFilePath = options.historyFilePath ?? join20(ENGINE_ENV.HOME_DIR, ".replicas", "opencode", "history.jsonl");
13022
13055
  this.historyFile = new CodexHistoryFile(this.historyFilePath);
13023
13056
  this.initializeManager(this.processMessageInternal.bind(this));
13024
13057
  }
@@ -13517,7 +13550,7 @@ var OpencodeManager = class extends CodingAgentManager {
13517
13550
 
13518
13551
  // src/managers/pi-manager.ts
13519
13552
  import { mkdir as mkdir13 } from "fs/promises";
13520
- import { dirname as dirname7, join as join20 } from "path";
13553
+ import { dirname as dirname7, join as join21 } from "path";
13521
13554
  import {
13522
13555
  AuthStorage,
13523
13556
  createAgentSession,
@@ -13558,7 +13591,7 @@ var PiManager = class extends CodingAgentManager {
13558
13591
  providerId = "openrouter";
13559
13592
  constructor(options) {
13560
13593
  super(options);
13561
- this.historyFilePath = options.historyFilePath ?? join20(PI_HISTORY_DIR, `${Date.now()}.jsonl`);
13594
+ this.historyFilePath = options.historyFilePath ?? join21(PI_HISTORY_DIR, `${Date.now()}.jsonl`);
13562
13595
  this.historyFile = new CodexHistoryFile(this.historyFilePath);
13563
13596
  this.initializeManager(this.processMessageInternal.bind(this));
13564
13597
  }
@@ -13664,7 +13697,7 @@ var PiManager = class extends CodingAgentManager {
13664
13697
  const sessionManager = this.initialSessionId ? SessionManager.open(this.initialSessionId, PI_HISTORY_DIR, this.workingDirectory) : SessionManager.create(this.workingDirectory, PI_HISTORY_DIR);
13665
13698
  const resourceLoader = new DefaultResourceLoader({
13666
13699
  cwd: this.workingDirectory,
13667
- agentDir: join20(ENGINE_ENV.HOME_DIR, ".pi", "agent"),
13700
+ agentDir: join21(ENGINE_ENV.HOME_DIR, ".pi", "agent"),
13668
13701
  extensionFactories: [registerCommandProtection(this.workingDirectory)],
13669
13702
  appendSystemPrompt: [this.buildCombinedInstructions() ?? ""]
13670
13703
  });
@@ -14331,6 +14364,146 @@ var RelayManager = class {
14331
14364
  }
14332
14365
  };
14333
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
+
14334
14507
  // src/services/keep-alive-service.ts
14335
14508
  var KeepAliveService = class _KeepAliveService {
14336
14509
  interval = null;
@@ -14375,19 +14548,19 @@ var KeepAliveService = class _KeepAliveService {
14375
14548
  var keepAliveService = new KeepAliveService();
14376
14549
 
14377
14550
  // src/services/canvas-service.ts
14378
- 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";
14379
14552
  import { homedir as homedir14 } from "os";
14380
- import { join as join21 } from "path";
14553
+ import { join as join23 } from "path";
14381
14554
  var GLOBAL_CANVAS_DIRECTORIES = [
14382
- join21(homedir14(), ".claude", "plans"),
14383
- join21(process.env.XDG_DATA_HOME ?? join21(homedir14(), ".local", "share"), "opencode", "plans"),
14384
- join21(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")
14385
14558
  ];
14386
14559
  async function canvasDirectories() {
14387
14560
  const repositories = await gitService.listRepositories().catch(() => []);
14388
14561
  return [
14389
14562
  ...GLOBAL_CANVAS_DIRECTORIES,
14390
- ...repositories.map((repository) => join21(repository.path, ".opencode", "plans"))
14563
+ ...repositories.map((repository) => join23(repository.path, ".opencode", "plans"))
14391
14564
  ];
14392
14565
  }
14393
14566
  var CanvasService = class {
@@ -14396,7 +14569,7 @@ var CanvasService = class {
14396
14569
  for (const directory of await canvasDirectories()) {
14397
14570
  let entries;
14398
14571
  try {
14399
- entries = await readdir6(directory, { withFileTypes: true });
14572
+ entries = await readdir7(directory, { withFileTypes: true });
14400
14573
  } catch {
14401
14574
  continue;
14402
14575
  }
@@ -14407,7 +14580,7 @@ var CanvasService = class {
14407
14580
  const { kind } = classifyCanvasFilename(entry.name);
14408
14581
  let sizeBytes = 0;
14409
14582
  try {
14410
- const s = await stat3(join21(directory, entry.name));
14583
+ const s = await stat3(join23(directory, entry.name));
14411
14584
  sizeBytes = s.size;
14412
14585
  } catch {
14413
14586
  continue;
@@ -14422,7 +14595,7 @@ var CanvasService = class {
14422
14595
  if (!safe) return null;
14423
14596
  const { kind, mimeType } = classifyCanvasFilename(safe);
14424
14597
  for (const directory of await canvasDirectories()) {
14425
- const filePath = join21(directory, safe);
14598
+ const filePath = join23(directory, safe);
14426
14599
  let sizeBytes = 0;
14427
14600
  let updatedAt = "";
14428
14601
  try {
@@ -14443,7 +14616,7 @@ var CanvasService = class {
14443
14616
  };
14444
14617
  }
14445
14618
  try {
14446
- const bytes = await readFile12(filePath);
14619
+ const bytes = await readFile13(filePath);
14447
14620
  return { filename: safe, kind, sizeBytes, mimeType, updatedAt, bytes };
14448
14621
  } catch {
14449
14622
  continue;
@@ -14559,16 +14732,16 @@ async function reconcileCanvasItems(filenames) {
14559
14732
 
14560
14733
  // src/services/upload-chat-transcripts.ts
14561
14734
  import { createReadStream } from "fs";
14562
- 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";
14563
14736
  import { request as httpRequest } from "http";
14564
14737
  import { request as httpsRequest } from "https";
14565
- import { basename as basename2, join as join23 } from "path";
14738
+ import { basename as basename2, join as join25 } from "path";
14566
14739
 
14567
14740
  // src/services/chat/chat-senders.ts
14568
- import { join as join22 } from "path";
14569
- var CHAT_SENDERS_DIR = join22(ENGINE_DIR2, "chat-senders");
14741
+ import { join as join24 } from "path";
14742
+ var CHAT_SENDERS_DIR = join24(ENGINE_DIR2, "chat-senders");
14570
14743
  function chatMessageSendersFilePath(chatId) {
14571
- return join22(CHAT_SENDERS_DIR, `${chatId}.jsonl`);
14744
+ return join24(CHAT_SENDERS_DIR, `${chatId}.jsonl`);
14572
14745
  }
14573
14746
  function parseChatMessageSendersJsonl(content) {
14574
14747
  return content.split("\n").flatMap((line) => {
@@ -14584,9 +14757,9 @@ function parseChatMessageSendersJsonl(content) {
14584
14757
 
14585
14758
  // src/services/upload-chat-transcripts.ts
14586
14759
  var HISTORY_DIRS = [
14587
- join23(ENGINE_DIR2, "claude-histories"),
14588
- join23(ENGINE_DIR2, "relay-histories"),
14589
- join23(ENGINE_DIR2, "codex-histories")
14760
+ join25(ENGINE_DIR2, "claude-histories"),
14761
+ join25(ENGINE_DIR2, "relay-histories"),
14762
+ join25(ENGINE_DIR2, "codex-histories")
14590
14763
  ];
14591
14764
  async function putTranscript(uploadUrl, filePath, size) {
14592
14765
  await new Promise((resolve4, reject) => {
@@ -14623,7 +14796,7 @@ async function flushAllChatTranscripts(chatsById = /* @__PURE__ */ new Map()) {
14623
14796
  for (const dir of HISTORY_DIRS) {
14624
14797
  let entries;
14625
14798
  try {
14626
- entries = await readdir7(dir);
14799
+ entries = await readdir8(dir);
14627
14800
  } catch {
14628
14801
  continue;
14629
14802
  }
@@ -14631,7 +14804,7 @@ async function flushAllChatTranscripts(chatsById = /* @__PURE__ */ new Map()) {
14631
14804
  if (!entry.endsWith(".jsonl")) continue;
14632
14805
  const chatId = basename2(entry, ".jsonl");
14633
14806
  tasks.push(
14634
- uploadChatTranscript(chatId, join23(dir, entry), chatsById.get(chatId)).then(() => {
14807
+ uploadChatTranscript(chatId, join25(dir, entry), chatsById.get(chatId)).then(() => {
14635
14808
  flushed++;
14636
14809
  }).catch((err) => {
14637
14810
  failed++;
@@ -14656,7 +14829,7 @@ async function uploadChatTranscript(chatId, filePath, chat) {
14656
14829
  } : {};
14657
14830
  try {
14658
14831
  metadata.senders = parseChatMessageSendersJsonl(
14659
- await readFile13(chatMessageSendersFilePath(chatId), "utf-8")
14832
+ await readFile14(chatMessageSendersFilePath(chatId), "utf-8")
14660
14833
  );
14661
14834
  } catch (error) {
14662
14835
  if (!(error && typeof error === "object" && "code" in error && error.code === "ENOENT")) throw error;
@@ -14717,8 +14890,8 @@ async function flushRepoState() {
14717
14890
 
14718
14891
  // src/services/upload-engine-logs.ts
14719
14892
  import { createReadStream as createReadStream2 } from "fs";
14720
- import { readdir as readdir8, stat as stat5 } from "fs/promises";
14721
- import { join as join24 } from "path";
14893
+ import { readdir as readdir9, stat as stat5 } from "fs/promises";
14894
+ import { join as join26 } from "path";
14722
14895
  var MAX_ENGINE_LOG_FLUSH_SESSIONS = 10;
14723
14896
  var MAX_ENGINE_LOG_FLUSH_BYTES = 5 * 1024 * 1024;
14724
14897
  var ENGINE_LOG_FLUSH_TIMEOUT_MS = 2e4;
@@ -14728,7 +14901,7 @@ async function flushAllEngineLogs() {
14728
14901
  let failed = 0;
14729
14902
  const deadline = Date.now() + ENGINE_LOG_FLUSH_TIMEOUT_MS;
14730
14903
  await runBeforeDeadline((signal) => engineLogger.flush(signal), deadline);
14731
- const files = await runBeforeDeadline(() => readdir8(LOG_DIR), deadline).catch(() => []);
14904
+ const files = await runBeforeDeadline(() => readdir9(LOG_DIR), deadline).catch(() => []);
14732
14905
  const currentFilename = engineLogger.sessionId ? `${engineLogger.sessionId}.log` : null;
14733
14906
  const filenames = files.filter((filename) => {
14734
14907
  if (!filename.endsWith(".log")) return false;
@@ -14747,7 +14920,7 @@ async function flushAllEngineLogs() {
14747
14920
  const candidates = (await Promise.all(filenames.slice(0, MAX_ENGINE_LOG_FLUSH_SESSIONS).map(async (filename) => {
14748
14921
  try {
14749
14922
  const sessionId = filename.slice(0, -".log".length);
14750
- const filePath = join24(LOG_DIR, filename);
14923
+ const filePath = join26(LOG_DIR, filename);
14751
14924
  const fileStat = await runBeforeDeadline(() => stat5(filePath), deadline);
14752
14925
  if (!fileStat.isFile()) {
14753
14926
  skipped++;
@@ -14843,7 +15016,7 @@ async function uploadEngineLog(input, timeoutMs) {
14843
15016
  }
14844
15017
 
14845
15018
  // src/services/chat/chat-service.ts
14846
- var CODEX_AUTH_PATH2 = join25(homedir15(), ".codex", "auth.json");
15019
+ var CODEX_AUTH_PATH2 = join27(homedir15(), ".codex", "auth.json");
14847
15020
  var CHATS_BACKUP_FILE = `${CHATS_FILE}.bak`;
14848
15021
  function isCodexAvailable() {
14849
15022
  return existsSync7(CODEX_AUTH_PATH2) || Boolean(ENGINE_ENV.OPENAI_API_KEY);
@@ -14971,13 +15144,13 @@ var ChatService = class {
14971
15144
  persistInFlight = false;
14972
15145
  persistQueued = false;
14973
15146
  async initialize() {
14974
- await mkdir14(ENGINE_DIR2, { recursive: true });
14975
- await mkdir14(CLAUDE_HISTORY_DIR, { recursive: true });
14976
- await mkdir14(RELAY_HISTORY_DIR, { recursive: true });
14977
- await mkdir14(CODEX_HISTORY_DIR, { recursive: true });
14978
- await mkdir14(CURSOR_HISTORY_DIR, { recursive: true });
14979
- await mkdir14(OPENCODE_HISTORY_DIR, { recursive: true });
14980
- 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 });
14981
15154
  const persisted = await this.loadChats();
14982
15155
  for (const chat of persisted) {
14983
15156
  const runtime = this.createRuntimeChat(chat);
@@ -15044,7 +15217,7 @@ var ChatService = class {
15044
15217
  throw new ChatNotFoundError(parentChatId);
15045
15218
  }
15046
15219
  const persisted = {
15047
- id: request.id ?? randomUUID5(),
15220
+ id: request.id ?? randomUUID6(),
15048
15221
  provider: request.provider,
15049
15222
  title,
15050
15223
  createdAt: now,
@@ -15088,6 +15261,7 @@ var ChatService = class {
15088
15261
  request.images
15089
15262
  );
15090
15263
  chat.pendingMessageIds.push(result.messageId);
15264
+ noteMessageAccepted(result.messageId, request);
15091
15265
  if (request.errorNotificationTarget) {
15092
15266
  chat.errorNotificationTargets.set(result.messageId, request.errorNotificationTarget);
15093
15267
  }
@@ -15124,14 +15298,14 @@ var ChatService = class {
15124
15298
  }
15125
15299
  async appendSender(chatId, sender) {
15126
15300
  try {
15127
- await appendFile3(chatMessageSendersFilePath(chatId), JSON.stringify(sender) + "\n", "utf-8");
15301
+ await appendFile4(chatMessageSendersFilePath(chatId), JSON.stringify(sender) + "\n", "utf-8");
15128
15302
  } catch (error) {
15129
15303
  console.error("[ChatService] Failed to append sender record:", error);
15130
15304
  }
15131
15305
  }
15132
15306
  async readSenders(chatId) {
15133
15307
  try {
15134
- return parseChatMessageSendersJsonl(await readFile14(chatMessageSendersFilePath(chatId), "utf-8"));
15308
+ return parseChatMessageSendersJsonl(await readFile15(chatMessageSendersFilePath(chatId), "utf-8"));
15135
15309
  } catch (error) {
15136
15310
  if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
15137
15311
  return [];
@@ -15143,6 +15317,7 @@ var ChatService = class {
15143
15317
  async interrupt(chatId) {
15144
15318
  const chat = this.requireChat(chatId);
15145
15319
  const result = await chat.provider.interrupt();
15320
+ noteTurnEnded(chatId);
15146
15321
  chat.hasActiveTurn = false;
15147
15322
  chat.activeMessageId = null;
15148
15323
  chat.pendingMessageIds = [];
@@ -15164,6 +15339,7 @@ var ChatService = class {
15164
15339
  return { interrupted: false, queue: [], goal: null };
15165
15340
  }
15166
15341
  const interruptResult = await chat.provider.interrupt();
15342
+ noteTurnEnded(chatId);
15167
15343
  chat.hasActiveTurn = false;
15168
15344
  chat.activeMessageId = null;
15169
15345
  chat.pendingMessageIds = [];
@@ -15310,7 +15486,7 @@ var ChatService = class {
15310
15486
  return descendants;
15311
15487
  }
15312
15488
  async deleteHistoryFile(persisted) {
15313
- await rm2(join25(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 });
15314
15490
  await rm2(chatMessageSendersFilePath(persisted.id), { force: true });
15315
15491
  }
15316
15492
  async getChatHistory(chatId, page = {}) {
@@ -15353,13 +15529,14 @@ var ChatService = class {
15353
15529
  const chatsById = new Map(
15354
15530
  [...this.chats.entries()].map(([chatId, chat]) => [chatId, this.toSummary(chat)])
15355
15531
  );
15356
- const [chatTranscripts, canvas, repoState, engineLogs] = await Promise.all([
15532
+ const [chatTranscripts, canvas, repoState, engineLogs, turnUsage] = await Promise.all([
15357
15533
  flushAllChatTranscripts(chatsById),
15358
15534
  flushAllCanvasItems(),
15359
15535
  flushRepoState(),
15360
- flushAllEngineLogs()
15536
+ flushAllEngineLogs(),
15537
+ flushAllTurnUsage()
15361
15538
  ]);
15362
- return { chatTranscripts, canvas, repoState, engineLogs };
15539
+ return { chatTranscripts, canvas, repoState, engineLogs, turnUsage };
15363
15540
  }
15364
15541
  createRuntimeChat(persisted) {
15365
15542
  const saveSession = async (sessionId) => {
@@ -15386,7 +15563,7 @@ var ChatService = class {
15386
15563
  if (persisted.provider === "claude") {
15387
15564
  provider = new ClaudeManager({
15388
15565
  workingDirectory: this.workingDirectory,
15389
- historyFilePath: join25(CLAUDE_HISTORY_DIR, `${persisted.id}.jsonl`),
15566
+ historyFilePath: join27(CLAUDE_HISTORY_DIR, `${persisted.id}.jsonl`),
15390
15567
  initialSessionId: persisted.providerSessionId,
15391
15568
  onSaveSessionId: saveSession,
15392
15569
  onTurnComplete: onProviderTurnComplete,
@@ -15395,7 +15572,7 @@ var ChatService = class {
15395
15572
  } else if (persisted.provider === "relay") {
15396
15573
  provider = new RelayManager({
15397
15574
  workingDirectory: this.workingDirectory,
15398
- historyFilePath: join25(RELAY_HISTORY_DIR, `${persisted.id}.jsonl`),
15575
+ historyFilePath: join27(RELAY_HISTORY_DIR, `${persisted.id}.jsonl`),
15399
15576
  initialSessionId: persisted.providerSessionId,
15400
15577
  onSaveSessionId: saveSession,
15401
15578
  onTurnComplete: onProviderTurnComplete,
@@ -15409,7 +15586,7 @@ var ChatService = class {
15409
15586
  } else if (persisted.provider === "cursor") {
15410
15587
  provider = new CursorManager({
15411
15588
  workingDirectory: this.workingDirectory,
15412
- historyFilePath: join25(CURSOR_HISTORY_DIR, `${persisted.id}.jsonl`),
15589
+ historyFilePath: join27(CURSOR_HISTORY_DIR, `${persisted.id}.jsonl`),
15413
15590
  initialSessionId: persisted.providerSessionId,
15414
15591
  onSaveSessionId: saveSession,
15415
15592
  onTurnComplete: onProviderTurnComplete,
@@ -15418,7 +15595,7 @@ var ChatService = class {
15418
15595
  } else if (persisted.provider === "opencode") {
15419
15596
  provider = new OpencodeManager({
15420
15597
  workingDirectory: this.workingDirectory,
15421
- historyFilePath: join25(OPENCODE_HISTORY_DIR, `${persisted.id}.jsonl`),
15598
+ historyFilePath: join27(OPENCODE_HISTORY_DIR, `${persisted.id}.jsonl`),
15422
15599
  initialSessionId: persisted.providerSessionId,
15423
15600
  onSaveSessionId: saveSession,
15424
15601
  onTurnComplete: onProviderTurnComplete,
@@ -15427,7 +15604,7 @@ var ChatService = class {
15427
15604
  } else if (persisted.provider === "pi") {
15428
15605
  provider = new PiManager({
15429
15606
  workingDirectory: this.workingDirectory,
15430
- historyFilePath: join25(PI_HISTORY_DIR, `${persisted.id}.jsonl`),
15607
+ historyFilePath: join27(PI_HISTORY_DIR, `${persisted.id}.jsonl`),
15431
15608
  initialSessionId: persisted.providerSessionId,
15432
15609
  onSaveSessionId: saveSession,
15433
15610
  onTurnComplete: onProviderTurnComplete,
@@ -15436,7 +15613,7 @@ var ChatService = class {
15436
15613
  } else {
15437
15614
  provider = new CodexAspManager({
15438
15615
  workingDirectory: this.workingDirectory,
15439
- historyFilePath: join25(CODEX_HISTORY_DIR, `${persisted.id}.jsonl`),
15616
+ historyFilePath: join27(CODEX_HISTORY_DIR, `${persisted.id}.jsonl`),
15440
15617
  initialSessionId: persisted.providerSessionId,
15441
15618
  onSaveSessionId: saveSession,
15442
15619
  onTurnComplete: onProviderTurnComplete,
@@ -15499,6 +15676,7 @@ var ChatService = class {
15499
15676
  }
15500
15677
  chat.hasActiveTurn = true;
15501
15678
  chat.activeMessageId = messageId;
15679
+ noteTurnStarted(chat.persisted.id, messageId, chat.persisted.provider);
15502
15680
  chat.activeErrorNotificationTarget = chat.errorNotificationTargets.get(messageId) ?? null;
15503
15681
  chat.errorNotificationTargets.delete(messageId);
15504
15682
  this.publish({
@@ -15573,6 +15751,7 @@ var ChatService = class {
15573
15751
  }
15574
15752
  chat.hasActiveTurn = false;
15575
15753
  chat.activeMessageId = null;
15754
+ noteTurnEnded(chatId);
15576
15755
  this.publish({
15577
15756
  type: "chat.turn.completed",
15578
15757
  payload: {
@@ -15586,7 +15765,7 @@ var ChatService = class {
15586
15765
  });
15587
15766
  uploadChatTranscript(
15588
15767
  chatId,
15589
- join25(HISTORY_DIR_BY_PROVIDER[chat.persisted.provider], `${chatId}.jsonl`),
15768
+ join27(HISTORY_DIR_BY_PROVIDER[chat.persisted.provider], `${chatId}.jsonl`),
15590
15769
  this.toSummary(chat)
15591
15770
  ).catch((err) => {
15592
15771
  console.error("[ChatService] Failed to upload chat transcript:", { chatId, err });
@@ -15602,7 +15781,7 @@ var ChatService = class {
15602
15781
  }
15603
15782
  async loadChats() {
15604
15783
  try {
15605
- const content = await readFile14(CHATS_FILE, "utf-8");
15784
+ const content = await readFile15(CHATS_FILE, "utf-8");
15606
15785
  return parsePersistedChatsContent(content);
15607
15786
  } catch (error) {
15608
15787
  if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
@@ -15611,13 +15790,13 @@ var ChatService = class {
15611
15790
  const quarantinePath = corruptChatsFilePath();
15612
15791
  console.error(`[ChatService] Failed to load ${CHATS_FILE}; quarantining and trying backup:`, error);
15613
15792
  try {
15614
- await rename2(CHATS_FILE, quarantinePath);
15793
+ await rename3(CHATS_FILE, quarantinePath);
15615
15794
  console.error(`[ChatService] Quarantined corrupt chats file at ${quarantinePath}`);
15616
15795
  } catch (renameError) {
15617
15796
  console.error("[ChatService] Failed to quarantine corrupt chats file:", renameError);
15618
15797
  }
15619
15798
  try {
15620
- const backupContent = await readFile14(CHATS_BACKUP_FILE, "utf-8");
15799
+ const backupContent = await readFile15(CHATS_BACKUP_FILE, "utf-8");
15621
15800
  return parsePersistedChatsContent(backupContent);
15622
15801
  } catch (backupError) {
15623
15802
  if (backupError && typeof backupError === "object" && "code" in backupError && backupError.code === "ENOENT") {
@@ -15656,7 +15835,7 @@ var ChatService = class {
15656
15835
  }
15657
15836
  async publish(input) {
15658
15837
  const event = {
15659
- id: randomUUID5(),
15838
+ id: randomUUID6(),
15660
15839
  ts: (/* @__PURE__ */ new Date()).toISOString(),
15661
15840
  ...input
15662
15841
  };
@@ -15721,8 +15900,8 @@ var ChatService = class {
15721
15900
 
15722
15901
  // src/services/repo-file-service.ts
15723
15902
  import { execFile as execFile2 } from "child_process";
15724
- import { readFile as readFile15, realpath, stat as stat6 } from "fs/promises";
15725
- import { join as join26, 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";
15726
15905
  var CACHE_TTL_MS = 3e4;
15727
15906
  var SEARCH_TIMEOUT_MS = 15e3;
15728
15907
  var MAX_CONTENT_BYTES = 256 * 1024;
@@ -15882,7 +16061,7 @@ var RepoFileService = class {
15882
16061
  const repo = repos.find((r) => r.name === repoName);
15883
16062
  if (!repo) return null;
15884
16063
  try {
15885
- const fullPath = await realpath(resolve2(join26(repo.path, filePath)));
16064
+ const fullPath = await realpath(resolve2(join28(repo.path, filePath)));
15886
16065
  const repoRoot = await realpath(repo.path);
15887
16066
  const repoPrefix = repoRoot.endsWith("/") ? repoRoot : repoRoot + "/";
15888
16067
  if (!fullPath.startsWith(repoPrefix) && fullPath !== repoRoot) return null;
@@ -15911,7 +16090,7 @@ var RepoFileService = class {
15911
16090
  tooLarge: true
15912
16091
  };
15913
16092
  }
15914
- const content = await readFile15(fullPath, "utf-8");
16093
+ const content = await readFile16(fullPath, "utf-8");
15915
16094
  return {
15916
16095
  repoName,
15917
16096
  path: filePath,
@@ -15989,21 +16168,21 @@ var RepoFileService = class {
15989
16168
  // src/v1-routes.ts
15990
16169
  import { Hono } from "hono";
15991
16170
  import { z as z7 } from "zod";
15992
- import { readdir as readdir10, stat as stat7, readFile as readFile18 } from "fs/promises";
15993
- import { join as join29, 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";
15994
16173
 
15995
16174
  // src/services/warm-hooks-service.ts
15996
16175
  import { spawn as spawn4 } from "child_process";
15997
- import { readFile as readFile17 } from "fs/promises";
16176
+ import { readFile as readFile18 } from "fs/promises";
15998
16177
  import { existsSync as existsSync8 } from "fs";
15999
- import { join as join28 } from "path";
16178
+ import { join as join30 } from "path";
16000
16179
 
16001
16180
  // src/services/warm-hook-logs-service.ts
16002
- 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";
16003
16182
  import { homedir as homedir16 } from "os";
16004
- import { join as join27 } from "path";
16005
- var LOGS_DIR2 = join27(homedir16(), ".replicas", "warm-hook-logs");
16006
- var CURRENT_RUN_LOG = join27(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");
16007
16186
  var GLOBAL_FILENAME = "global.json";
16008
16187
  function withPreview2(stored) {
16009
16188
  const preview = buildHookOutputPreview(stored.output);
@@ -16011,7 +16190,7 @@ function withPreview2(stored) {
16011
16190
  }
16012
16191
  var WarmHookLogsService = class {
16013
16192
  async ensureDir() {
16014
- await mkdir15(LOGS_DIR2, { recursive: true });
16193
+ await mkdir16(LOGS_DIR2, { recursive: true });
16015
16194
  }
16016
16195
  async saveGlobalHookLog(entry) {
16017
16196
  await this.ensureDir();
@@ -16020,7 +16199,7 @@ var WarmHookLogsService = class {
16020
16199
  hookName: "organization",
16021
16200
  ...entry
16022
16201
  };
16023
- await writeFile6(join27(LOGS_DIR2, GLOBAL_FILENAME), `${JSON.stringify(log, null, 2)}
16202
+ await writeFile6(join29(LOGS_DIR2, GLOBAL_FILENAME), `${JSON.stringify(log, null, 2)}
16024
16203
  `, "utf-8");
16025
16204
  }
16026
16205
  async saveEnvironmentHookLog(entry) {
@@ -16030,7 +16209,7 @@ var WarmHookLogsService = class {
16030
16209
  hookName: "environment",
16031
16210
  ...entry
16032
16211
  };
16033
- await writeFile6(join27(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)}
16034
16213
  `, "utf-8");
16035
16214
  }
16036
16215
  async saveRepoHookLog(repoName, entry) {
@@ -16040,13 +16219,13 @@ var WarmHookLogsService = class {
16040
16219
  hookName: repoName,
16041
16220
  ...entry
16042
16221
  };
16043
- await writeFile6(join27(LOGS_DIR2, repoHookLogFilename(repoName)), `${JSON.stringify(log, null, 2)}
16222
+ await writeFile6(join29(LOGS_DIR2, repoHookLogFilename(repoName)), `${JSON.stringify(log, null, 2)}
16044
16223
  `, "utf-8");
16045
16224
  }
16046
16225
  async getAllLogs() {
16047
16226
  let files;
16048
16227
  try {
16049
- files = await readdir9(LOGS_DIR2);
16228
+ files = await readdir10(LOGS_DIR2);
16050
16229
  } catch (err) {
16051
16230
  if (err.code === "ENOENT") {
16052
16231
  return [];
@@ -16059,7 +16238,7 @@ var WarmHookLogsService = class {
16059
16238
  continue;
16060
16239
  }
16061
16240
  try {
16062
- const raw = await readFile16(join27(LOGS_DIR2, file), "utf-8");
16241
+ const raw = await readFile17(join29(LOGS_DIR2, file), "utf-8");
16063
16242
  const stored = JSON.parse(raw);
16064
16243
  logs.push(withPreview2(stored));
16065
16244
  } catch {
@@ -16077,18 +16256,18 @@ var WarmHookLogsService = class {
16077
16256
  async resetCurrentRunLog() {
16078
16257
  await this.ensureDir();
16079
16258
  try {
16080
- await unlink3(CURRENT_RUN_LOG);
16259
+ await unlink4(CURRENT_RUN_LOG);
16081
16260
  } catch (err) {
16082
16261
  if (err.code !== "ENOENT") throw err;
16083
16262
  }
16084
16263
  }
16085
16264
  async appendCurrentRunLog(chunk) {
16086
16265
  if (!chunk) return;
16087
- await appendFile4(CURRENT_RUN_LOG, chunk, "utf-8");
16266
+ await appendFile5(CURRENT_RUN_LOG, chunk, "utf-8");
16088
16267
  }
16089
16268
  async getCurrentRunLog() {
16090
16269
  try {
16091
- return await readFile16(CURRENT_RUN_LOG, "utf-8");
16270
+ return await readFile17(CURRENT_RUN_LOG, "utf-8");
16092
16271
  } catch (err) {
16093
16272
  if (err.code === "ENOENT") return null;
16094
16273
  throw err;
@@ -16097,7 +16276,7 @@ var WarmHookLogsService = class {
16097
16276
  async getFullOutput(hookType, hookName) {
16098
16277
  const filename = hookType === "global" ? GLOBAL_FILENAME : hookType === "environment" ? ENVIRONMENT_HOOK_LOG_FILENAME : repoHookLogFilename(hookName);
16099
16278
  try {
16100
- const raw = await readFile16(join27(LOGS_DIR2, filename), "utf-8");
16279
+ const raw = await readFile17(join29(LOGS_DIR2, filename), "utf-8");
16101
16280
  const stored = JSON.parse(raw);
16102
16281
  if (stored.hookType !== hookType || stored.hookName !== hookName) {
16103
16282
  return null;
@@ -16116,12 +16295,12 @@ var warmHookLogsService = new WarmHookLogsService();
16116
16295
  // src/services/warm-hooks-service.ts
16117
16296
  async function readRepoWarmHook(repoPath) {
16118
16297
  for (const filename of REPLICAS_CONFIG_FILENAMES) {
16119
- const configPath = join28(repoPath, filename);
16298
+ const configPath = join30(repoPath, filename);
16120
16299
  if (!existsSync8(configPath)) {
16121
16300
  continue;
16122
16301
  }
16123
16302
  try {
16124
- const raw = await readFile17(configPath, "utf-8");
16303
+ const raw = await readFile18(configPath, "utf-8");
16125
16304
  const config = parseReplicasConfigString(raw, filename);
16126
16305
  if (!config.warmHook) {
16127
16306
  return null;
@@ -16377,7 +16556,7 @@ ${combinedScript}` : combinedScript;
16377
16556
  }
16378
16557
 
16379
16558
  // src/services/terminal-service.ts
16380
- import { randomUUID as randomUUID6 } from "crypto";
16559
+ import { randomUUID as randomUUID7 } from "crypto";
16381
16560
  import { existsSync as existsSync9 } from "fs";
16382
16561
  import { spawn as spawn5 } from "node-pty";
16383
16562
  var MAX_REPLAY_CHARS = 1024 * 1024;
@@ -16396,7 +16575,7 @@ var TerminalService = class {
16396
16575
  code: "limit"
16397
16576
  });
16398
16577
  }
16399
- const id = randomUUID6();
16578
+ const id = randomUUID7();
16400
16579
  const shell = process.env.SHELL && existsSync9(process.env.SHELL) ? process.env.SHELL : "/bin/bash";
16401
16580
  const pty = spawn5(shell, ["-l"], {
16402
16581
  name: "xterm-256color",
@@ -17374,11 +17553,11 @@ data: ${JSON.stringify("Terminal session not found")}
17374
17553
  });
17375
17554
  app2.get("/logs", async (c) => {
17376
17555
  try {
17377
- const files = await readdir10(LOG_DIR).catch(() => []);
17556
+ const files = await readdir11(LOG_DIR).catch(() => []);
17378
17557
  const logFiles = files.filter((f) => f.endsWith(".log"));
17379
17558
  const sessions = await Promise.all(
17380
17559
  logFiles.map(async (filename) => {
17381
- const filePath = join29(LOG_DIR, filename);
17560
+ const filePath = join31(LOG_DIR, filename);
17382
17561
  const fileStat = await stat7(filePath);
17383
17562
  const sessionId = filename.replace(/\.log$/, "");
17384
17563
  return {
@@ -17413,7 +17592,7 @@ data: ${JSON.stringify("Terminal session not found")}
17413
17592
  }
17414
17593
  let content;
17415
17594
  try {
17416
- content = await readFile18(filePath, "utf-8");
17595
+ content = await readFile19(filePath, "utf-8");
17417
17596
  } catch {
17418
17597
  return c.json(jsonError("Log session not found"), 404);
17419
17598
  }
@@ -17652,7 +17831,7 @@ function startStatusBroadcaster() {
17652
17831
  if (serialized !== previousRepoStatus) {
17653
17832
  previousRepoStatus = serialized;
17654
17833
  eventService.publish({
17655
- id: randomUUID7(),
17834
+ id: randomUUID8(),
17656
17835
  ts: (/* @__PURE__ */ new Date()).toISOString(),
17657
17836
  type: "repo.status.changed",
17658
17837
  payload: { repos }
@@ -17673,7 +17852,7 @@ function startStatusBroadcaster() {
17673
17852
  if (engineStatusJson !== previousEngineStatus) {
17674
17853
  previousEngineStatus = engineStatusJson;
17675
17854
  eventService.publish({
17676
- id: randomUUID7(),
17855
+ id: randomUUID8(),
17677
17856
  ts: (/* @__PURE__ */ new Date()).toISOString(),
17678
17857
  type: "engine.status.changed",
17679
17858
  payload: { status: engineStatus }
@@ -17692,7 +17871,7 @@ function startStatusBroadcaster() {
17692
17871
  previousHookStatus = hookSnapshot;
17693
17872
  if (!lastHooksRunning && hooksRunning) {
17694
17873
  eventService.publish({
17695
- id: randomUUID7(),
17874
+ id: randomUUID8(),
17696
17875
  ts: (/* @__PURE__ */ new Date()).toISOString(),
17697
17876
  type: "hooks.started",
17698
17877
  payload: { running: true, completed: false }
@@ -17701,7 +17880,7 @@ function startStatusBroadcaster() {
17701
17880
  }
17702
17881
  if (hooksRunning) {
17703
17882
  eventService.publish({
17704
- id: randomUUID7(),
17883
+ id: randomUUID8(),
17705
17884
  ts: (/* @__PURE__ */ new Date()).toISOString(),
17706
17885
  type: "hooks.progress",
17707
17886
  payload: { running: true, completed: false }
@@ -17710,7 +17889,7 @@ function startStatusBroadcaster() {
17710
17889
  }
17711
17890
  if (lastHooksRunning && !hooksRunning && hooksCompleted && !hooksFailed) {
17712
17891
  eventService.publish({
17713
- id: randomUUID7(),
17892
+ id: randomUUID8(),
17714
17893
  ts: (/* @__PURE__ */ new Date()).toISOString(),
17715
17894
  type: "hooks.completed",
17716
17895
  payload: { running: false, completed: true }
@@ -17719,7 +17898,7 @@ function startStatusBroadcaster() {
17719
17898
  }
17720
17899
  if (lastHooksRunning && !hooksRunning && hooksFailed) {
17721
17900
  eventService.publish({
17722
- id: randomUUID7(),
17901
+ id: randomUUID8(),
17723
17902
  ts: (/* @__PURE__ */ new Date()).toISOString(),
17724
17903
  type: "hooks.failed",
17725
17904
  payload: { running: false, completed: hooksCompleted }
@@ -17727,7 +17906,7 @@ function startStatusBroadcaster() {
17727
17906
  });
17728
17907
  }
17729
17908
  eventService.publish({
17730
- id: randomUUID7(),
17909
+ id: randomUUID8(),
17731
17910
  ts: (/* @__PURE__ */ new Date()).toISOString(),
17732
17911
  type: "hooks.status",
17733
17912
  payload: {
@@ -17763,6 +17942,10 @@ serve(
17763
17942
  await timeStartupStep("chat_initialize", () => chatService.initialize());
17764
17943
  await timeStartupStep("preview_initialize", () => previewService.initialize());
17765
17944
  if (!IS_WARMING_MODE) {
17945
+ await timeStartupStep(
17946
+ "git_credential_helper_initialize",
17947
+ () => configureManagedGitCredentialHelper(ENGINE_ENV.HOME_DIR)
17948
+ );
17766
17949
  await timeStartupStep("github_token_initialize", () => githubTokenManager.start());
17767
17950
  }
17768
17951
  engineReady = true;
@@ -17777,20 +17960,20 @@ serve(
17777
17960
  }
17778
17961
  const repos = await gitService.listRepos();
17779
17962
  await eventService.publish({
17780
- id: randomUUID7(),
17963
+ id: randomUUID8(),
17781
17964
  ts: (/* @__PURE__ */ new Date()).toISOString(),
17782
17965
  type: "repo.discovered",
17783
17966
  payload: { repos }
17784
17967
  });
17785
17968
  const repoStatuses = await gitService.listRepos();
17786
17969
  await eventService.publish({
17787
- id: randomUUID7(),
17970
+ id: randomUUID8(),
17788
17971
  ts: (/* @__PURE__ */ new Date()).toISOString(),
17789
17972
  type: "repo.status.changed",
17790
17973
  payload: { repos: repoStatuses }
17791
17974
  });
17792
17975
  await eventService.publish({
17793
- id: randomUUID7(),
17976
+ id: randomUUID8(),
17794
17977
  ts: (/* @__PURE__ */ new Date()).toISOString(),
17795
17978
  type: "engine.ready",
17796
17979
  payload: { version: "v1" }