replicas-engine 0.1.49 → 0.1.51

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
@@ -5,7 +5,7 @@ import "./chunk-ZXMDA7VB.js";
5
5
  import "dotenv/config";
6
6
  import { serve } from "@hono/node-server";
7
7
  import { Hono as Hono2 } from "hono";
8
- import { readFile as readFile8 } from "fs/promises";
8
+ import { readFile as readFile9 } from "fs/promises";
9
9
  import { execSync } from "child_process";
10
10
  import { randomUUID as randomUUID4 } from "crypto";
11
11
 
@@ -829,13 +829,11 @@ var EngineLogger = class {
829
829
  };
830
830
  var engineLogger = new EngineLogger();
831
831
 
832
- // src/services/replicas-config-service.ts
833
- import { readFile as readFile2, appendFile as appendFile2, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
832
+ // src/services/environment-details-service.ts
833
+ import { mkdir as mkdir3, readFile as readFile2, writeFile as writeFile3 } from "fs/promises";
834
834
  import { existsSync as existsSync3 } from "fs";
835
- import { join as join5 } from "path";
836
835
  import { homedir as homedir4 } from "os";
837
- import { exec } from "child_process";
838
- import { promisify } from "util";
836
+ import { join as join5 } from "path";
839
837
 
840
838
  // ../shared/src/sandbox.ts
841
839
  var SANDBOX_LIFECYCLE = {
@@ -898,6 +896,9 @@ function resolveWarmHookConfig(value) {
898
896
  };
899
897
  }
900
898
 
899
+ // ../shared/src/engine/environment.ts
900
+ var REPLICAS_ENGINE_VERSION = "09-03-2026-kipling";
901
+
901
902
  // ../shared/src/engine/types.ts
902
903
  var DEFAULT_CHAT_TITLES = {
903
904
  claude: "Claude Code",
@@ -909,9 +910,183 @@ var IMAGE_MEDIA_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
909
910
  var WORKSPACE_FILE_UPLOAD_MAX_SIZE_BYTES = 20 * 1024 * 1024;
910
911
  var WORKSPACE_FILE_CONTENT_MAX_SIZE_BYTES = 1 * 1024 * 1024;
911
912
 
913
+ // src/services/environment-details-service.ts
914
+ var REPLICAS_DIR = join5(homedir4(), ".replicas");
915
+ var ENVIRONMENT_DETAILS_FILE = join5(REPLICAS_DIR, "environment-details.json");
916
+ var CLAUDE_CREDENTIALS_PATH = join5(homedir4(), ".claude", ".credentials.json");
917
+ var CODEX_AUTH_PATH = join5(homedir4(), ".codex", "auth.json");
918
+ function createExecutionItem(status, details) {
919
+ return { status, details: details ?? null };
920
+ }
921
+ function createDefaultDetails() {
922
+ return {
923
+ engineVersion: REPLICAS_ENGINE_VERSION,
924
+ globalWarmHookCompleted: createExecutionItem("n/a"),
925
+ repositories: [],
926
+ filesUploaded: [],
927
+ envVarsSet: [],
928
+ skillsInstalled: [],
929
+ runtimeEnvVarsSet: [],
930
+ repositoriesCloned: [],
931
+ gitIdentityConfigured: false,
932
+ githubCredentialsConfigured: false,
933
+ linearAccessConfigured: false,
934
+ slackAccessConfigured: false,
935
+ githubAccessConfigured: false,
936
+ claudeAuthMethod: "none",
937
+ codexAuthMethod: "none",
938
+ lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
939
+ };
940
+ }
941
+ function getClaudeAuthMethod() {
942
+ if (existsSync3(CLAUDE_CREDENTIALS_PATH)) {
943
+ return "oauth";
944
+ }
945
+ if (ENGINE_ENV.CLAUDE_CODE_USE_BEDROCK && ENGINE_ENV.AWS_ACCESS_KEY_ID && ENGINE_ENV.AWS_SECRET_ACCESS_KEY) {
946
+ return "bedrock";
947
+ }
948
+ if (ENGINE_ENV.ANTHROPIC_API_KEY) {
949
+ return "api_key";
950
+ }
951
+ return "none";
952
+ }
953
+ function getCodexAuthMethod() {
954
+ if (existsSync3(CODEX_AUTH_PATH)) {
955
+ return "oauth";
956
+ }
957
+ if (ENGINE_ENV.OPENAI_API_KEY) {
958
+ return "api_key";
959
+ }
960
+ return "none";
961
+ }
962
+ function getLiveDetails(current) {
963
+ const claudeAuthMethod = getClaudeAuthMethod();
964
+ const codexAuthMethod = getCodexAuthMethod();
965
+ return {
966
+ ...current,
967
+ engineVersion: REPLICAS_ENGINE_VERSION,
968
+ linearAccessConfigured: Boolean(ENGINE_ENV.LINEAR_SESSION_ID || ENGINE_ENV.LINEAR_ACCESS_TOKEN),
969
+ slackAccessConfigured: Boolean(ENGINE_ENV.SLACK_BOT_TOKEN),
970
+ githubAccessConfigured: Boolean(ENGINE_ENV.GH_TOKEN),
971
+ githubCredentialsConfigured: Boolean(ENGINE_ENV.GH_TOKEN),
972
+ claudeAuthMethod,
973
+ codexAuthMethod
974
+ };
975
+ }
976
+ function mergeUnique(current, incoming) {
977
+ if (!incoming || incoming.length === 0) {
978
+ return current;
979
+ }
980
+ return Array.from(/* @__PURE__ */ new Set([...current, ...incoming]));
981
+ }
982
+ function upsertRepositoryStatus(current, incoming) {
983
+ const byName = new Map(current.map((repository) => [repository.repositoryName, repository]));
984
+ for (const repository of incoming) {
985
+ const existing = byName.get(repository.repositoryName);
986
+ byName.set(repository.repositoryName, {
987
+ repositoryName: repository.repositoryName,
988
+ warmHookCompleted: repository.warmHookCompleted !== "n/a" ? repository.warmHookCompleted : existing?.warmHookCompleted ?? "n/a",
989
+ startHookCompleted: repository.startHookCompleted !== "n/a" ? repository.startHookCompleted : existing?.startHookCompleted ?? "n/a"
990
+ });
991
+ }
992
+ return [...byName.values()].sort((a, b) => a.repositoryName.localeCompare(b.repositoryName));
993
+ }
994
+ var EnvironmentDetailsService = class {
995
+ async initialize() {
996
+ const current = await this.readDetails();
997
+ const repositories = await gitService.listRepositories();
998
+ const merged = upsertRepositoryStatus(
999
+ current.repositories,
1000
+ repositories.map((repository) => ({
1001
+ repositoryName: repository.name,
1002
+ warmHookCompleted: "n/a",
1003
+ startHookCompleted: "n/a"
1004
+ }))
1005
+ );
1006
+ await this.writeDetails({
1007
+ ...current,
1008
+ engineVersion: REPLICAS_ENGINE_VERSION,
1009
+ repositories: merged,
1010
+ lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
1011
+ });
1012
+ }
1013
+ async getDetails() {
1014
+ const current = await this.readDetails();
1015
+ return getLiveDetails(current);
1016
+ }
1017
+ async track(update) {
1018
+ const current = await this.readDetails();
1019
+ const next = {
1020
+ ...current,
1021
+ engineVersion: REPLICAS_ENGINE_VERSION,
1022
+ globalWarmHookCompleted: update.globalWarmHookCompleted ?? current.globalWarmHookCompleted,
1023
+ repositories: update.repositories ? upsertRepositoryStatus(current.repositories, update.repositories) : current.repositories,
1024
+ filesUploaded: mergeUnique(current.filesUploaded, update.filesUploaded),
1025
+ envVarsSet: mergeUnique(current.envVarsSet, update.envVarsSet),
1026
+ skillsInstalled: mergeUnique(current.skillsInstalled, update.skillsInstalled),
1027
+ runtimeEnvVarsSet: mergeUnique(current.runtimeEnvVarsSet, update.runtimeEnvVarsSet),
1028
+ repositoriesCloned: mergeUnique(current.repositoriesCloned, update.repositoriesCloned),
1029
+ gitIdentityConfigured: update.gitIdentityConfigured ?? current.gitIdentityConfigured,
1030
+ githubCredentialsConfigured: update.githubCredentialsConfigured ?? current.githubCredentialsConfigured,
1031
+ lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
1032
+ };
1033
+ await this.writeDetails(next);
1034
+ return next;
1035
+ }
1036
+ async setGlobalWarmHook(status, details) {
1037
+ await this.track({
1038
+ globalWarmHookCompleted: createExecutionItem(status, details)
1039
+ });
1040
+ }
1041
+ async setRepositoryWarmHook(repositoryName, status) {
1042
+ await this.track({
1043
+ repositories: [{
1044
+ repositoryName,
1045
+ warmHookCompleted: status,
1046
+ startHookCompleted: "n/a"
1047
+ }]
1048
+ });
1049
+ }
1050
+ async setRepositoryStartHook(repositoryName, status) {
1051
+ await this.track({
1052
+ repositories: [{
1053
+ repositoryName,
1054
+ warmHookCompleted: "n/a",
1055
+ startHookCompleted: status
1056
+ }]
1057
+ });
1058
+ }
1059
+ async readDetails() {
1060
+ try {
1061
+ if (!existsSync3(ENVIRONMENT_DETAILS_FILE)) {
1062
+ return createDefaultDetails();
1063
+ }
1064
+ const raw = await readFile2(ENVIRONMENT_DETAILS_FILE, "utf-8");
1065
+ return getLiveDetails({
1066
+ ...createDefaultDetails(),
1067
+ ...JSON.parse(raw)
1068
+ });
1069
+ } catch {
1070
+ return createDefaultDetails();
1071
+ }
1072
+ }
1073
+ async writeDetails(details) {
1074
+ await mkdir3(REPLICAS_DIR, { recursive: true });
1075
+ await writeFile3(ENVIRONMENT_DETAILS_FILE, `${JSON.stringify(details, null, 2)}
1076
+ `, "utf-8");
1077
+ }
1078
+ };
1079
+ var environmentDetailsService = new EnvironmentDetailsService();
1080
+
912
1081
  // src/services/replicas-config-service.ts
1082
+ import { readFile as readFile3, appendFile as appendFile2, writeFile as writeFile4, mkdir as mkdir4 } from "fs/promises";
1083
+ import { existsSync as existsSync4 } from "fs";
1084
+ import { join as join6 } from "path";
1085
+ import { homedir as homedir5 } from "os";
1086
+ import { exec } from "child_process";
1087
+ import { promisify } from "util";
913
1088
  var execAsync = promisify(exec);
914
- var START_HOOKS_LOG = join5(homedir4(), ".replicas", "startHooks.log");
1089
+ var START_HOOKS_LOG = join6(homedir5(), ".replicas", "startHooks.log");
915
1090
  var START_HOOKS_RUNNING_PROMPT = `IMPORTANT - Start Hooks Running:
916
1091
  Start hooks are shell commands/scripts set by repository owners that run on workspace startup.
917
1092
  These hooks are currently executing in the background. You can:
@@ -991,12 +1166,12 @@ var ReplicasConfigService = class {
991
1166
  const repos = await gitService.listRepositories();
992
1167
  const configs = [];
993
1168
  for (const repo of repos) {
994
- const configPath = join5(repo.path, "replicas.json");
995
- if (!existsSync3(configPath)) {
1169
+ const configPath = join6(repo.path, "replicas.json");
1170
+ if (!existsSync4(configPath)) {
996
1171
  continue;
997
1172
  }
998
1173
  try {
999
- const data = await readFile2(configPath, "utf-8");
1174
+ const data = await readFile3(configPath, "utf-8");
1000
1175
  const config = parseReplicasConfig(JSON.parse(data));
1001
1176
  configs.push({
1002
1177
  repoName: repo.name,
@@ -1022,7 +1197,7 @@ var ReplicasConfigService = class {
1022
1197
  const logLine = `[${timestamp}] ${message}
1023
1198
  `;
1024
1199
  try {
1025
- await mkdir3(join5(homedir4(), ".replicas"), { recursive: true });
1200
+ await mkdir4(join6(homedir5(), ".replicas"), { recursive: true });
1026
1201
  await appendFile2(START_HOOKS_LOG, logLine, "utf-8");
1027
1202
  } catch (error) {
1028
1203
  console.error("Failed to write to start hooks log:", error);
@@ -1037,13 +1212,17 @@ var ReplicasConfigService = class {
1037
1212
  this.hooksRunning = false;
1038
1213
  this.hooksCompleted = true;
1039
1214
  this.hooksFailed = false;
1215
+ const repos = await gitService.listRepositories();
1216
+ for (const repo of repos) {
1217
+ await environmentDetailsService.setRepositoryStartHook(repo.name, "n/a");
1218
+ }
1040
1219
  return;
1041
1220
  }
1042
1221
  this.hooksRunning = true;
1043
1222
  this.hooksCompleted = false;
1044
1223
  try {
1045
- await mkdir3(join5(homedir4(), ".replicas"), { recursive: true });
1046
- await writeFile3(
1224
+ await mkdir4(join6(homedir5(), ".replicas"), { recursive: true });
1225
+ await writeFile4(
1047
1226
  START_HOOKS_LOG,
1048
1227
  `=== Start Hooks Execution Log ===
1049
1228
  Started: ${(/* @__PURE__ */ new Date()).toISOString()}
@@ -1052,6 +1231,13 @@ Repositories: ${hookEntries.length}
1052
1231
  `,
1053
1232
  "utf-8"
1054
1233
  );
1234
+ const repos = await gitService.listRepositories();
1235
+ const reposWithHooks = new Set(hookEntries.map((entry) => entry.repoName));
1236
+ for (const repo of repos) {
1237
+ if (!reposWithHooks.has(repo.name)) {
1238
+ await environmentDetailsService.setRepositoryStartHook(repo.name, "n/a");
1239
+ }
1240
+ }
1055
1241
  for (const entry of hookEntries) {
1056
1242
  const startHookConfig = entry.config.startHook;
1057
1243
  if (!startHookConfig) {
@@ -1060,9 +1246,11 @@ Repositories: ${hookEntries.length}
1060
1246
  const persistedRepoState = await loadRepoState(entry.repoName);
1061
1247
  if (persistedRepoState?.startHooksCompleted) {
1062
1248
  await this.logToFile(`[${entry.repoName}] Start hooks already completed in this workspace lifecycle, skipping`);
1249
+ await environmentDetailsService.setRepositoryStartHook(entry.repoName, "yes");
1063
1250
  continue;
1064
1251
  }
1065
1252
  const timeout = startHookConfig.timeout ?? 3e5;
1253
+ let repoFailed = false;
1066
1254
  await this.logToFile(`[${entry.repoName}] Executing ${startHookConfig.commands.length} hook(s) with timeout ${timeout}ms`);
1067
1255
  for (const hook of startHookConfig.commands) {
1068
1256
  try {
@@ -1082,7 +1270,9 @@ Repositories: ${hookEntries.length}
1082
1270
  } catch (error) {
1083
1271
  const errorMessage = error instanceof Error ? error.message : "Unknown error";
1084
1272
  this.hooksFailed = true;
1273
+ repoFailed = true;
1085
1274
  await this.logToFile(`[${entry.repoName}] [ERROR] ${hook} failed: ${errorMessage}`);
1275
+ await environmentDetailsService.setRepositoryStartHook(entry.repoName, "no");
1086
1276
  }
1087
1277
  }
1088
1278
  const fallbackRepoState = persistedRepoState ?? {
@@ -1095,6 +1285,7 @@ Repositories: ${hookEntries.length}
1095
1285
  startHooksCompleted: false
1096
1286
  };
1097
1287
  await saveRepoState(entry.repoName, { startHooksCompleted: true }, fallbackRepoState);
1288
+ await environmentDetailsService.setRepositoryStartHook(entry.repoName, repoFailed ? "no" : "yes");
1098
1289
  }
1099
1290
  this.hooksCompleted = true;
1100
1291
  await this.logToFile(`=== All start hooks completed at ${(/* @__PURE__ */ new Date()).toISOString()} ===`);
@@ -1152,17 +1343,17 @@ Repositories: ${hookEntries.length}
1152
1343
  var replicasConfigService = new ReplicasConfigService();
1153
1344
 
1154
1345
  // src/services/event-service.ts
1155
- import { appendFile as appendFile3, mkdir as mkdir4 } from "fs/promises";
1156
- import { homedir as homedir5 } from "os";
1157
- import { join as join6 } from "path";
1346
+ import { appendFile as appendFile3, mkdir as mkdir5 } from "fs/promises";
1347
+ import { homedir as homedir6 } from "os";
1348
+ import { join as join7 } from "path";
1158
1349
  import { randomUUID } from "crypto";
1159
- var ENGINE_DIR = join6(homedir5(), ".replicas", "engine");
1160
- var EVENTS_FILE = join6(ENGINE_DIR, "events.jsonl");
1350
+ var ENGINE_DIR = join7(homedir6(), ".replicas", "engine");
1351
+ var EVENTS_FILE = join7(ENGINE_DIR, "events.jsonl");
1161
1352
  var EventService = class {
1162
1353
  subscribers = /* @__PURE__ */ new Map();
1163
1354
  writeChain = Promise.resolve();
1164
1355
  async initialize() {
1165
- await mkdir4(ENGINE_DIR, { recursive: true });
1356
+ await mkdir5(ENGINE_DIR, { recursive: true });
1166
1357
  }
1167
1358
  subscribe(subscriber) {
1168
1359
  const id = randomUUID();
@@ -1192,21 +1383,21 @@ var EventService = class {
1192
1383
  var eventService = new EventService();
1193
1384
 
1194
1385
  // src/services/chat/chat-service.ts
1195
- import { mkdir as mkdir7, readFile as readFile5, rm, writeFile as writeFile5 } from "fs/promises";
1196
- import { homedir as homedir8 } from "os";
1197
- import { join as join9 } from "path";
1386
+ import { mkdir as mkdir8, readFile as readFile6, rm, writeFile as writeFile6 } from "fs/promises";
1387
+ import { homedir as homedir9 } from "os";
1388
+ import { join as join10 } from "path";
1198
1389
  import { randomUUID as randomUUID3 } from "crypto";
1199
1390
 
1200
1391
  // src/managers/claude-manager.ts
1201
1392
  import {
1202
1393
  query
1203
1394
  } from "@anthropic-ai/claude-agent-sdk";
1204
- import { join as join7 } from "path";
1205
- import { mkdir as mkdir5, appendFile as appendFile4 } from "fs/promises";
1206
- import { homedir as homedir6 } from "os";
1395
+ import { join as join8 } from "path";
1396
+ import { mkdir as mkdir6, appendFile as appendFile4 } from "fs/promises";
1397
+ import { homedir as homedir7 } from "os";
1207
1398
 
1208
1399
  // src/utils/jsonl-reader.ts
1209
- import { readFile as readFile3 } from "fs/promises";
1400
+ import { readFile as readFile4 } from "fs/promises";
1210
1401
  function isJsonlEvent(value) {
1211
1402
  if (!isRecord(value)) {
1212
1403
  return false;
@@ -1228,7 +1419,7 @@ function parseJsonlEvents(lines) {
1228
1419
  }
1229
1420
  async function readJSONL(filePath) {
1230
1421
  try {
1231
- const content = await readFile3(filePath, "utf-8");
1422
+ const content = await readFile4(filePath, "utf-8");
1232
1423
  const lines = content.split("\n").filter((line) => line.trim());
1233
1424
  return parseJsonlEvents(lines);
1234
1425
  } catch (error) {
@@ -1962,7 +2153,7 @@ var ClaudeManager = class extends CodingAgentManager {
1962
2153
  pendingInterrupt = false;
1963
2154
  constructor(options) {
1964
2155
  super(options);
1965
- this.historyFile = options.historyFilePath ?? join7(homedir6(), ".replicas", "claude", "history.jsonl");
2156
+ this.historyFile = options.historyFilePath ?? join8(homedir7(), ".replicas", "claude", "history.jsonl");
1966
2157
  this.initializeManager(this.processMessageInternal.bind(this));
1967
2158
  }
1968
2159
  async interruptActiveTurn() {
@@ -2100,8 +2291,8 @@ var ClaudeManager = class extends CodingAgentManager {
2100
2291
  };
2101
2292
  }
2102
2293
  async initialize() {
2103
- const historyDir = join7(homedir6(), ".replicas", "claude");
2104
- await mkdir5(historyDir, { recursive: true });
2294
+ const historyDir = join8(homedir7(), ".replicas", "claude");
2295
+ await mkdir6(historyDir, { recursive: true });
2105
2296
  if (this.initialSessionId) {
2106
2297
  this.sessionId = this.initialSessionId;
2107
2298
  console.log(`[ClaudeManager] Restored session ID from persisted state: ${this.sessionId}`);
@@ -2130,13 +2321,13 @@ var ClaudeManager = class extends CodingAgentManager {
2130
2321
  // src/managers/codex-manager.ts
2131
2322
  import { Codex } from "@openai/codex-sdk";
2132
2323
  import { randomUUID as randomUUID2 } from "crypto";
2133
- import { readdir as readdir2, stat as stat2, writeFile as writeFile4, mkdir as mkdir6, readFile as readFile4 } from "fs/promises";
2134
- import { existsSync as existsSync4 } from "fs";
2135
- import { join as join8 } from "path";
2136
- import { homedir as homedir7 } from "os";
2324
+ import { readdir as readdir2, stat as stat2, writeFile as writeFile5, mkdir as mkdir7, readFile as readFile5 } from "fs/promises";
2325
+ import { existsSync as existsSync5 } from "fs";
2326
+ import { join as join9 } from "path";
2327
+ import { homedir as homedir8 } from "os";
2137
2328
  import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
2138
2329
  var DEFAULT_MODEL = "gpt-5.4";
2139
- var CODEX_CONFIG_PATH = join8(homedir7(), ".codex", "config.toml");
2330
+ var CODEX_CONFIG_PATH = join9(homedir8(), ".codex", "config.toml");
2140
2331
  function isLinearThoughtEvent2(event) {
2141
2332
  return event.content.type === "thought";
2142
2333
  }
@@ -2158,7 +2349,7 @@ var CodexManager = class extends CodingAgentManager {
2158
2349
  constructor(options) {
2159
2350
  super(options);
2160
2351
  this.codex = new Codex();
2161
- this.tempImageDir = join8(homedir7(), ".replicas", "codex", "temp-images");
2352
+ this.tempImageDir = join9(homedir8(), ".replicas", "codex", "temp-images");
2162
2353
  this.initializeManager(this.processMessageInternal.bind(this));
2163
2354
  }
2164
2355
  async initialize() {
@@ -2178,12 +2369,12 @@ var CodexManager = class extends CodingAgentManager {
2178
2369
  */
2179
2370
  async updateCodexConfig(developerInstructions) {
2180
2371
  try {
2181
- const codexDir = join8(homedir7(), ".codex");
2182
- await mkdir6(codexDir, { recursive: true });
2372
+ const codexDir = join9(homedir8(), ".codex");
2373
+ await mkdir7(codexDir, { recursive: true });
2183
2374
  let config = {};
2184
- if (existsSync4(CODEX_CONFIG_PATH)) {
2375
+ if (existsSync5(CODEX_CONFIG_PATH)) {
2185
2376
  try {
2186
- const existingContent = await readFile4(CODEX_CONFIG_PATH, "utf-8");
2377
+ const existingContent = await readFile5(CODEX_CONFIG_PATH, "utf-8");
2187
2378
  const parsed = parseToml(existingContent);
2188
2379
  if (isRecord(parsed)) {
2189
2380
  config = parsed;
@@ -2198,7 +2389,7 @@ var CodexManager = class extends CodingAgentManager {
2198
2389
  delete config.developer_instructions;
2199
2390
  }
2200
2391
  const tomlContent = stringifyToml(config);
2201
- await writeFile4(CODEX_CONFIG_PATH, tomlContent, "utf-8");
2392
+ await writeFile5(CODEX_CONFIG_PATH, tomlContent, "utf-8");
2202
2393
  console.log("[CodexManager] Updated config.toml with developer_instructions");
2203
2394
  } catch (error) {
2204
2395
  console.error("[CodexManager] Failed to update config.toml:", error);
@@ -2209,14 +2400,14 @@ var CodexManager = class extends CodingAgentManager {
2209
2400
  * @returns Array of temp file paths
2210
2401
  */
2211
2402
  async saveImagesToTempFiles(images) {
2212
- await mkdir6(this.tempImageDir, { recursive: true });
2403
+ await mkdir7(this.tempImageDir, { recursive: true });
2213
2404
  const tempPaths = [];
2214
2405
  for (const image of images) {
2215
2406
  const ext = image.source.media_type.split("/")[1] || "png";
2216
2407
  const filename = `img_${randomUUID2()}.${ext}`;
2217
- const filepath = join8(this.tempImageDir, filename);
2408
+ const filepath = join9(this.tempImageDir, filename);
2218
2409
  const buffer = Buffer.from(image.source.data, "base64");
2219
- await writeFile4(filepath, buffer);
2410
+ await writeFile5(filepath, buffer);
2220
2411
  tempPaths.push(filepath);
2221
2412
  }
2222
2413
  return tempPaths;
@@ -2346,13 +2537,13 @@ var CodexManager = class extends CodingAgentManager {
2346
2537
  }
2347
2538
  // Helper methods for finding session files
2348
2539
  async findSessionFile(threadId) {
2349
- const sessionsDir = join8(homedir7(), ".codex", "sessions");
2540
+ const sessionsDir = join9(homedir8(), ".codex", "sessions");
2350
2541
  try {
2351
2542
  const now = /* @__PURE__ */ new Date();
2352
2543
  const year = now.getFullYear();
2353
2544
  const month = String(now.getMonth() + 1).padStart(2, "0");
2354
2545
  const day = String(now.getDate()).padStart(2, "0");
2355
- const todayDir = join8(sessionsDir, String(year), month, day);
2546
+ const todayDir = join9(sessionsDir, String(year), month, day);
2356
2547
  const file = await this.findFileInDirectory(todayDir, threadId);
2357
2548
  if (file) return file;
2358
2549
  for (let daysAgo = 1; daysAgo <= 7; daysAgo++) {
@@ -2361,7 +2552,7 @@ var CodexManager = class extends CodingAgentManager {
2361
2552
  const searchYear = date.getFullYear();
2362
2553
  const searchMonth = String(date.getMonth() + 1).padStart(2, "0");
2363
2554
  const searchDay = String(date.getDate()).padStart(2, "0");
2364
- const searchDir = join8(sessionsDir, String(searchYear), searchMonth, searchDay);
2555
+ const searchDir = join9(sessionsDir, String(searchYear), searchMonth, searchDay);
2365
2556
  const file2 = await this.findFileInDirectory(searchDir, threadId);
2366
2557
  if (file2) return file2;
2367
2558
  }
@@ -2375,7 +2566,7 @@ var CodexManager = class extends CodingAgentManager {
2375
2566
  const files = await readdir2(directory);
2376
2567
  for (const file of files) {
2377
2568
  if (file.endsWith(".jsonl") && file.includes(threadId)) {
2378
- const fullPath = join8(directory, file);
2569
+ const fullPath = join9(directory, file);
2379
2570
  const stats = await stat2(fullPath);
2380
2571
  if (stats.isFile()) {
2381
2572
  return fullPath;
@@ -2408,7 +2599,7 @@ var CodexManager = class extends CodingAgentManager {
2408
2599
  const seenLines = /* @__PURE__ */ new Set();
2409
2600
  const seedSeenLines = async () => {
2410
2601
  try {
2411
- const content = await readFile4(sessionFile, "utf-8");
2602
+ const content = await readFile5(sessionFile, "utf-8");
2412
2603
  const lines = content.split("\n").map((line) => line.trim()).filter(Boolean);
2413
2604
  for (const line of lines) {
2414
2605
  seenLines.add(line);
@@ -2420,7 +2611,7 @@ var CodexManager = class extends CodingAgentManager {
2420
2611
  const pump = async () => {
2421
2612
  let emitted = 0;
2422
2613
  try {
2423
- const content = await readFile4(sessionFile, "utf-8");
2614
+ const content = await readFile5(sessionFile, "utf-8");
2424
2615
  const lines = content.split("\n");
2425
2616
  const completeLines = content.endsWith("\n") ? lines : lines.slice(0, -1);
2426
2617
  for (const line of completeLines) {
@@ -2491,9 +2682,9 @@ var DuplicateDefaultChatError = class extends Error {
2491
2682
  };
2492
2683
 
2493
2684
  // src/services/chat/chat-service.ts
2494
- var ENGINE_DIR2 = join9(homedir8(), ".replicas", "engine");
2495
- var CHATS_FILE = join9(ENGINE_DIR2, "chats.json");
2496
- var CLAUDE_HISTORY_DIR = join9(ENGINE_DIR2, "claude-histories");
2685
+ var ENGINE_DIR2 = join10(homedir9(), ".replicas", "engine");
2686
+ var CHATS_FILE = join10(ENGINE_DIR2, "chats.json");
2687
+ var CLAUDE_HISTORY_DIR = join10(ENGINE_DIR2, "claude-histories");
2497
2688
  function isPersistedChat(value) {
2498
2689
  if (!isRecord(value)) {
2499
2690
  return false;
@@ -2508,8 +2699,8 @@ var ChatService = class {
2508
2699
  chats = /* @__PURE__ */ new Map();
2509
2700
  writeChain = Promise.resolve();
2510
2701
  async initialize() {
2511
- await mkdir7(ENGINE_DIR2, { recursive: true });
2512
- await mkdir7(CLAUDE_HISTORY_DIR, { recursive: true });
2702
+ await mkdir8(ENGINE_DIR2, { recursive: true });
2703
+ await mkdir8(CLAUDE_HISTORY_DIR, { recursive: true });
2513
2704
  const persisted = await this.loadChats();
2514
2705
  for (const chat of persisted) {
2515
2706
  const runtime = this.createRuntimeChat(chat);
@@ -2593,7 +2784,7 @@ var ChatService = class {
2593
2784
  this.chats.delete(chatId);
2594
2785
  await this.persistAllChats();
2595
2786
  if (chat.persisted.provider === "claude") {
2596
- const historyFilePath = join9(CLAUDE_HISTORY_DIR, `${chatId}.jsonl`);
2787
+ const historyFilePath = join10(CLAUDE_HISTORY_DIR, `${chatId}.jsonl`);
2597
2788
  await rm(historyFilePath, { force: true });
2598
2789
  }
2599
2790
  await this.publish({
@@ -2636,7 +2827,7 @@ var ChatService = class {
2636
2827
  };
2637
2828
  const provider = persisted.provider === "claude" ? new ClaudeManager({
2638
2829
  workingDirectory: this.workingDirectory,
2639
- historyFilePath: join9(CLAUDE_HISTORY_DIR, `${persisted.id}.jsonl`),
2830
+ historyFilePath: join10(CLAUDE_HISTORY_DIR, `${persisted.id}.jsonl`),
2640
2831
  initialSessionId: persisted.providerSessionId,
2641
2832
  onSaveSessionId: saveSession,
2642
2833
  onTurnComplete: onProviderTurnComplete,
@@ -2732,7 +2923,7 @@ var ChatService = class {
2732
2923
  }
2733
2924
  async loadChats() {
2734
2925
  try {
2735
- const content = await readFile5(CHATS_FILE, "utf-8");
2926
+ const content = await readFile6(CHATS_FILE, "utf-8");
2736
2927
  const parsed = JSON.parse(content);
2737
2928
  if (!Array.isArray(parsed)) {
2738
2929
  return [];
@@ -2749,7 +2940,7 @@ var ChatService = class {
2749
2940
  this.writeChain = this.writeChain.catch(() => {
2750
2941
  }).then(async () => {
2751
2942
  const payload = Array.from(this.chats.values()).map((chat) => chat.persisted);
2752
- await writeFile5(CHATS_FILE, JSON.stringify(payload, null, 2), "utf-8");
2943
+ await writeFile6(CHATS_FILE, JSON.stringify(payload, null, 2), "utf-8");
2753
2944
  });
2754
2945
  await this.writeChain;
2755
2946
  }
@@ -2774,12 +2965,12 @@ import { Hono } from "hono";
2774
2965
  import { z } from "zod";
2775
2966
 
2776
2967
  // src/services/plan-service.ts
2777
- import { readdir as readdir3, readFile as readFile6 } from "fs/promises";
2778
- import { homedir as homedir9 } from "os";
2779
- import { basename, join as join10 } from "path";
2968
+ import { readdir as readdir3, readFile as readFile7 } from "fs/promises";
2969
+ import { homedir as homedir10 } from "os";
2970
+ import { basename, join as join11 } from "path";
2780
2971
  var PLAN_DIRECTORIES = [
2781
- join10(homedir9(), ".claude", "plans"),
2782
- join10(homedir9(), ".replicas", "plans")
2972
+ join11(homedir10(), ".claude", "plans"),
2973
+ join11(homedir10(), ".replicas", "plans")
2783
2974
  ];
2784
2975
  function isMarkdownFile(filename) {
2785
2976
  return filename.toLowerCase().endsWith(".md");
@@ -2813,9 +3004,9 @@ var PlanService = class {
2813
3004
  return null;
2814
3005
  }
2815
3006
  for (const directory of PLAN_DIRECTORIES) {
2816
- const filePath = join10(directory, safeFilename);
3007
+ const filePath = join11(directory, safeFilename);
2817
3008
  try {
2818
- const content = await readFile6(filePath, "utf-8");
3009
+ const content = await readFile7(filePath, "utf-8");
2819
3010
  return { filename: safeFilename, content };
2820
3011
  } catch {
2821
3012
  }
@@ -2828,8 +3019,8 @@ var planService = new PlanService();
2828
3019
  // src/services/warm-hooks-service.ts
2829
3020
  import { execFile } from "child_process";
2830
3021
  import { promisify as promisify2 } from "util";
2831
- import { readFile as readFile7 } from "fs/promises";
2832
- import { join as join11 } from "path";
3022
+ import { readFile as readFile8 } from "fs/promises";
3023
+ import { join as join12 } from "path";
2833
3024
  var execFileAsync = promisify2(execFile);
2834
3025
  async function installSkill(params) {
2835
3026
  const timeout = clampWarmHookTimeoutMs(params.timeoutMs);
@@ -2895,9 +3086,9 @@ async function executeHookScript(params) {
2895
3086
  }
2896
3087
  }
2897
3088
  async function readRepoWarmHook(repoPath) {
2898
- const configPath = join11(repoPath, "replicas.json");
3089
+ const configPath = join12(repoPath, "replicas.json");
2899
3090
  try {
2900
- const raw = await readFile7(configPath, "utf-8");
3091
+ const raw = await readFile8(configPath, "utf-8");
2901
3092
  const parsed = JSON.parse(raw);
2902
3093
  if (!isRecord(parsed) || !("warmHook" in parsed)) {
2903
3094
  return null;
@@ -2940,7 +3131,11 @@ async function runWarmHooks(params) {
2940
3131
  timeoutMs: params.timeoutMs
2941
3132
  });
2942
3133
  outputBlocks.push(installResult.output);
3134
+ if (installResult.exitCode === 0) {
3135
+ await environmentDetailsService.track({ skillsInstalled: [source] });
3136
+ }
2943
3137
  if (installResult.exitCode !== 0) {
3138
+ await environmentDetailsService.setGlobalWarmHook("no", installResult.output);
2944
3139
  return {
2945
3140
  exitCode: installResult.exitCode,
2946
3141
  output: outputBlocks.join("\n\n"),
@@ -2956,6 +3151,7 @@ async function runWarmHooks(params) {
2956
3151
  timeoutMs: params.timeoutMs
2957
3152
  });
2958
3153
  outputBlocks.push(orgResult.output);
3154
+ await environmentDetailsService.setGlobalWarmHook(orgResult.exitCode === 0 ? "yes" : "no", orgResult.output);
2959
3155
  if (orgResult.exitCode !== 0) {
2960
3156
  return {
2961
3157
  exitCode: orgResult.exitCode,
@@ -2963,9 +3159,18 @@ async function runWarmHooks(params) {
2963
3159
  timedOut: orgResult.timedOut
2964
3160
  };
2965
3161
  }
3162
+ } else {
3163
+ await environmentDetailsService.setGlobalWarmHook("n/a");
2966
3164
  }
2967
3165
  if (params.includeRepoHooks !== false) {
2968
3166
  const repoHooks = await collectRepoWarmHooks();
3167
+ const repoHookNames = new Set(repoHooks.map((repoHook) => repoHook.repoName));
3168
+ const discoveredRepos = await gitService.listRepositories();
3169
+ for (const repo of discoveredRepos) {
3170
+ if (!repoHookNames.has(repo.name)) {
3171
+ await environmentDetailsService.setRepositoryWarmHook(repo.name, "n/a");
3172
+ }
3173
+ }
2969
3174
  for (const repoHook of repoHooks) {
2970
3175
  for (const command of repoHook.commands) {
2971
3176
  const repoResult = await executeHookScript({
@@ -2975,6 +3180,10 @@ async function runWarmHooks(params) {
2975
3180
  timeoutMs: repoHook.timeoutMs
2976
3181
  });
2977
3182
  outputBlocks.push(repoResult.output);
3183
+ await environmentDetailsService.setRepositoryWarmHook(
3184
+ repoHook.repoName,
3185
+ repoResult.exitCode === 0 ? "yes" : "no"
3186
+ );
2978
3187
  if (repoResult.exitCode !== 0) {
2979
3188
  return {
2980
3189
  exitCode: repoResult.exitCode,
@@ -2984,6 +3193,11 @@ async function runWarmHooks(params) {
2984
3193
  }
2985
3194
  }
2986
3195
  }
3196
+ } else {
3197
+ const repos = await gitService.listRepositories();
3198
+ for (const repo of repos) {
3199
+ await environmentDetailsService.setRepositoryWarmHook(repo.name, "n/a");
3200
+ }
2987
3201
  }
2988
3202
  if (outputBlocks.length === 0) {
2989
3203
  return {
@@ -3219,6 +3433,35 @@ function createV1Routes(deps) {
3219
3433
  };
3220
3434
  return c.json(response);
3221
3435
  });
3436
+ app2.get("/version", (c) => {
3437
+ const response = {
3438
+ version: REPLICAS_ENGINE_VERSION
3439
+ };
3440
+ return c.json(response);
3441
+ });
3442
+ app2.get("/environment", async (c) => {
3443
+ try {
3444
+ const details = await environmentDetailsService.getDetails();
3445
+ return c.json(details);
3446
+ } catch (error) {
3447
+ return c.json(
3448
+ jsonError("Failed to load environment details", error instanceof Error ? error.message : "Unknown error"),
3449
+ 500
3450
+ );
3451
+ }
3452
+ });
3453
+ app2.post("/environment/track", async (c) => {
3454
+ try {
3455
+ const body = await c.req.json();
3456
+ const details = await environmentDetailsService.track(body);
3457
+ return c.json(details);
3458
+ } catch (error) {
3459
+ return c.json(
3460
+ jsonError("Failed to track environment details", error instanceof Error ? error.message : "Unknown error"),
3461
+ 500
3462
+ );
3463
+ }
3464
+ });
3222
3465
  app2.post("/warm-hooks/run", async (c) => {
3223
3466
  try {
3224
3467
  const body = await c.req.json();
@@ -3246,6 +3489,7 @@ function createV1Routes(deps) {
3246
3489
  // src/index.ts
3247
3490
  await engineLogger.initialize();
3248
3491
  await eventService.initialize();
3492
+ await environmentDetailsService.initialize();
3249
3493
  var READY_MESSAGE = "========= REPLICAS WORKSPACE READY ==========";
3250
3494
  var COMPLETION_MESSAGE = "========= REPLICAS WORKSPACE INITIALIZATION COMPLETE ==========";
3251
3495
  function checkActiveSSHSessions() {
@@ -3280,7 +3524,7 @@ app.get("/health", async (c) => {
3280
3524
  return c.json({ status: "initializing", timestamp: (/* @__PURE__ */ new Date()).toISOString() }, 503);
3281
3525
  }
3282
3526
  try {
3283
- const logContent = await readFile8("/var/log/cloud-init-output.log", "utf-8");
3527
+ const logContent = await readFile9("/var/log/cloud-init-output.log", "utf-8");
3284
3528
  let status;
3285
3529
  if (logContent.includes(COMPLETION_MESSAGE)) {
3286
3530
  status = "active";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.49",
3
+ "version": "0.1.51",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",
@@ -1,28 +0,0 @@
1
- import { defineConfig } from 'tsup';
2
- export default defineConfig({
3
- entry: ['src/index.ts'],
4
- format: ['esm'],
5
- bundle: true,
6
- // Bundle @replicas/shared inline, all other dependencies are automatically external
7
- noExternal: ['@replicas/shared'],
8
- dts: false, // We don't need type definitions for the published package
9
- clean: true,
10
- // Output to dist/src to match existing structure
11
- outDir: 'dist/src',
12
- // Add shebang for the bin script
13
- banner: {
14
- js: '#!/usr/bin/env node',
15
- },
16
- // Preserve the directory structure
17
- outExtension() {
18
- return {
19
- js: '.js',
20
- };
21
- },
22
- // Resolve path mappings from tsconfig
23
- esbuildOptions(options) {
24
- options.alias = {
25
- '@replicas/shared': '../shared/src',
26
- };
27
- },
28
- });