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/README.md +3 -2
- package/dist/src/index.js +358 -175
- package/package.json +1 -1
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-
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
5295
|
-
var STATE_FILE =
|
|
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
|
|
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(
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
6498
|
-
var OPENCODE_AUTH_PATH =
|
|
6499
|
-
var PI_AUTH_PATH =
|
|
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 =
|
|
6503
|
-
var DETAILS_FILE =
|
|
6504
|
-
var CLAUDE_CREDENTIALS_PATH =
|
|
6505
|
-
var CODEX_AUTH_PATH =
|
|
6506
|
-
var GH_HOSTS_PATH =
|
|
6507
|
-
var GIT_CREDENTIALS_PATH =
|
|
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
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
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
|
|
7270
|
+
import { join as join12 } from "path";
|
|
7238
7271
|
import { randomUUID } from "crypto";
|
|
7239
|
-
var ENGINE_DIR =
|
|
7240
|
-
var EVENTS_FILE =
|
|
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
|
|
7276
|
-
var PREVIEW_PORTS_FILE =
|
|
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
|
|
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
|
|
7390
|
-
import { randomUUID as
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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:
|
|
8540
|
-
{ source: "agents", root:
|
|
8541
|
-
{ source: "codex", root:
|
|
8542
|
-
{ source: "claude", root:
|
|
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 =
|
|
8589
|
+
const skillDir = join15(root, dirent.name);
|
|
8557
8590
|
if (!await isDirectoryDirent(dirent, skillDir)) continue;
|
|
8558
|
-
if (await fileExists(
|
|
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 =
|
|
8601
|
+
const skillDir = join15(registryDir, dirent.name);
|
|
8569
8602
|
if (!await isDirectoryDirent(dirent, skillDir)) continue;
|
|
8570
|
-
if (await fileExists(
|
|
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 =
|
|
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(
|
|
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(
|
|
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
|
|
8679
|
+
return join15(homeDir, REGISTRY_ROOT_DIR);
|
|
8647
8680
|
}
|
|
8648
8681
|
function getManifestPath(homeDir) {
|
|
8649
|
-
return
|
|
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 ??
|
|
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
|
|
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.
|
|
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
|
|
11073
|
-
var ENGINE_DIR2 =
|
|
11074
|
-
var CHATS_FILE =
|
|
11075
|
-
var CLAUDE_HISTORY_DIR =
|
|
11076
|
-
var RELAY_HISTORY_DIR =
|
|
11077
|
-
var CODEX_HISTORY_DIR =
|
|
11078
|
-
var CURSOR_HISTORY_DIR =
|
|
11079
|
-
var OPENCODE_HISTORY_DIR =
|
|
11080
|
-
var PI_HISTORY_DIR =
|
|
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(
|
|
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
|
|
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(
|
|
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 ??
|
|
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) =>
|
|
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
|
|
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 =
|
|
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 ??
|
|
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
|
|
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 ??
|
|
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:
|
|
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
|
|
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
|
|
14553
|
+
import { join as join23 } from "path";
|
|
14381
14554
|
var GLOBAL_CANVAS_DIRECTORIES = [
|
|
14382
|
-
|
|
14383
|
-
|
|
14384
|
-
|
|
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) =>
|
|
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
|
|
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(
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
14738
|
+
import { basename as basename2, join as join25 } from "path";
|
|
14566
14739
|
|
|
14567
14740
|
// src/services/chat/chat-senders.ts
|
|
14568
|
-
import { join as
|
|
14569
|
-
var CHAT_SENDERS_DIR =
|
|
14741
|
+
import { join as join24 } from "path";
|
|
14742
|
+
var CHAT_SENDERS_DIR = join24(ENGINE_DIR2, "chat-senders");
|
|
14570
14743
|
function chatMessageSendersFilePath(chatId) {
|
|
14571
|
-
return
|
|
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
|
-
|
|
14588
|
-
|
|
14589
|
-
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
14721
|
-
import { join as
|
|
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(() =>
|
|
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 =
|
|
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 =
|
|
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
|
|
14975
|
-
await
|
|
14976
|
-
await
|
|
14977
|
-
await
|
|
14978
|
-
await
|
|
14979
|
-
await
|
|
14980
|
-
await
|
|
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 ??
|
|
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
|
|
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
|
|
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(
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
15725
|
-
import { join as
|
|
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(
|
|
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
|
|
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
|
|
15993
|
-
import { join as
|
|
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
|
|
16176
|
+
import { readFile as readFile18 } from "fs/promises";
|
|
15998
16177
|
import { existsSync as existsSync8 } from "fs";
|
|
15999
|
-
import { join as
|
|
16178
|
+
import { join as join30 } from "path";
|
|
16000
16179
|
|
|
16001
16180
|
// src/services/warm-hook-logs-service.ts
|
|
16002
|
-
import { mkdir as
|
|
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
|
|
16005
|
-
var LOGS_DIR2 =
|
|
16006
|
-
var 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
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
16266
|
+
await appendFile5(CURRENT_RUN_LOG, chunk, "utf-8");
|
|
16088
16267
|
}
|
|
16089
16268
|
async getCurrentRunLog() {
|
|
16090
16269
|
try {
|
|
16091
|
-
return await
|
|
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
|
|
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 =
|
|
16298
|
+
const configPath = join30(repoPath, filename);
|
|
16120
16299
|
if (!existsSync8(configPath)) {
|
|
16121
16300
|
continue;
|
|
16122
16301
|
}
|
|
16123
16302
|
try {
|
|
16124
|
-
const raw = await
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
17976
|
+
id: randomUUID8(),
|
|
17794
17977
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
17795
17978
|
type: "engine.ready",
|
|
17796
17979
|
payload: { version: "v1" }
|