replicas-engine 0.1.286 → 0.1.288
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 +240 -109
- package/package.json +1 -1
- package/scripts/engine-watchdog.sh +2 -2
package/dist/src/index.js
CHANGED
|
@@ -116,9 +116,9 @@ var EXT_TO_LANGUAGE = {
|
|
|
116
116
|
function detectLanguageByPath(filePath) {
|
|
117
117
|
const dot = filePath.lastIndexOf(".");
|
|
118
118
|
if (dot === -1) {
|
|
119
|
-
const
|
|
120
|
-
if (
|
|
121
|
-
if (
|
|
119
|
+
const basename3 = filePath.split("/").pop() ?? "";
|
|
120
|
+
if (basename3 === "Dockerfile") return "dockerfile";
|
|
121
|
+
if (basename3 === "Makefile") return "makefile";
|
|
122
122
|
return null;
|
|
123
123
|
}
|
|
124
124
|
const ext = filePath.slice(dot).toLowerCase();
|
|
@@ -286,7 +286,7 @@ var WORKSPACE_SIZES = ["small", "large"];
|
|
|
286
286
|
var INVALID_WORKSPACE_SIZE_ERROR = `Invalid size: must be one of ${WORKSPACE_SIZES.join(", ")}`;
|
|
287
287
|
|
|
288
288
|
// ../shared/src/e2b.ts
|
|
289
|
-
var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-06-
|
|
289
|
+
var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-06-09-v2";
|
|
290
290
|
|
|
291
291
|
// ../shared/src/runtime-env.ts
|
|
292
292
|
function parsePosixEnvFile(content) {
|
|
@@ -2502,10 +2502,35 @@ function areSameUserMessageEvents(a, b) {
|
|
|
2502
2502
|
if (aItemId || bItemId) return aItemId === bItemId;
|
|
2503
2503
|
return Math.abs(getEventTimestampMs(a) - getEventTimestampMs(b)) <= USER_MESSAGE_MATCH_GRACE_PERIOD_MS;
|
|
2504
2504
|
}
|
|
2505
|
+
function parseAgentEventJsonl(content, options = {}) {
|
|
2506
|
+
const events = [];
|
|
2507
|
+
for (const line of content.split("\n")) {
|
|
2508
|
+
const trimmed = line.trim();
|
|
2509
|
+
if (!trimmed) continue;
|
|
2510
|
+
try {
|
|
2511
|
+
const parsed = JSON.parse(trimmed);
|
|
2512
|
+
if (isAgentBackendEvent(parsed)) {
|
|
2513
|
+
events.push(parsed);
|
|
2514
|
+
} else {
|
|
2515
|
+
options.onInvalidLine?.({ line: trimmed });
|
|
2516
|
+
}
|
|
2517
|
+
} catch (error) {
|
|
2518
|
+
options.onInvalidLine?.({ line: trimmed, error });
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
return events;
|
|
2522
|
+
}
|
|
2505
2523
|
|
|
2506
2524
|
// ../shared/src/display-message/parsers/codex-asp-parser.ts
|
|
2507
2525
|
var DUPLICATE_WINDOW_MS = 5 * 60 * 1e3;
|
|
2508
2526
|
|
|
2527
|
+
// ../shared/src/display-message/parsers/index.ts
|
|
2528
|
+
function isAgentBackendEvent(value) {
|
|
2529
|
+
if (!value || typeof value !== "object") return false;
|
|
2530
|
+
const candidate = value;
|
|
2531
|
+
return typeof candidate.timestamp === "string" && typeof candidate.type === "string" && typeof candidate.payload === "object" && candidate.payload !== null;
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2509
2534
|
// ../shared/src/object-store/types.ts
|
|
2510
2535
|
var MEDIA_KIND = {
|
|
2511
2536
|
IMAGE: "image",
|
|
@@ -2739,14 +2764,16 @@ async function monolithRequest(path4, init = {}) {
|
|
|
2739
2764
|
if (!ENGINE_ENV.WORKSPACE_ID) {
|
|
2740
2765
|
throw new Error("WORKSPACE_ID is not set; cannot call monolith");
|
|
2741
2766
|
}
|
|
2767
|
+
const isFormData = init.body instanceof FormData;
|
|
2768
|
+
const headers = {
|
|
2769
|
+
Authorization: `Bearer ${ENGINE_ENV.REPLICAS_ENGINE_SECRET}`,
|
|
2770
|
+
"X-Workspace-Id": ENGINE_ENV.WORKSPACE_ID
|
|
2771
|
+
};
|
|
2772
|
+
if (!isFormData) headers["Content-Type"] = "application/json";
|
|
2742
2773
|
return fetch(`${ENGINE_ENV.MONOLITH_URL}${path4}`, {
|
|
2743
2774
|
method: init.method ?? "POST",
|
|
2744
|
-
headers
|
|
2745
|
-
|
|
2746
|
-
"X-Workspace-Id": ENGINE_ENV.WORKSPACE_ID,
|
|
2747
|
-
"Content-Type": "application/json"
|
|
2748
|
-
},
|
|
2749
|
-
body: init.body === void 0 ? void 0 : JSON.stringify(init.body),
|
|
2775
|
+
headers,
|
|
2776
|
+
body: init.body === void 0 ? void 0 : isFormData ? init.body : JSON.stringify(init.body),
|
|
2750
2777
|
signal: init.signal
|
|
2751
2778
|
});
|
|
2752
2779
|
}
|
|
@@ -3738,28 +3765,91 @@ var GitService = class {
|
|
|
3738
3765
|
var gitService = new GitService();
|
|
3739
3766
|
|
|
3740
3767
|
// src/utils/logger.ts
|
|
3741
|
-
import {
|
|
3768
|
+
import { mkdir as mkdir3, writeFile as writeFile2 } from "fs/promises";
|
|
3742
3769
|
import { homedir as homedir4 } from "os";
|
|
3743
3770
|
import { join as join6 } from "path";
|
|
3744
3771
|
import { format } from "util";
|
|
3745
3772
|
import { randomBytes } from "crypto";
|
|
3773
|
+
|
|
3774
|
+
// src/utils/stream-writer.ts
|
|
3775
|
+
import { createWriteStream } from "fs";
|
|
3776
|
+
var DEFAULT_HIGH_WATER = 8 * 1024 * 1024;
|
|
3777
|
+
var StreamWriter = class {
|
|
3778
|
+
stream = null;
|
|
3779
|
+
backpressured = false;
|
|
3780
|
+
droppedCount = 0;
|
|
3781
|
+
flushTimer = null;
|
|
3782
|
+
open(path4, highWaterMark = DEFAULT_HIGH_WATER) {
|
|
3783
|
+
this.stream = createWriteStream(path4, { flags: "a", highWaterMark });
|
|
3784
|
+
this.stream.on("error", () => {
|
|
3785
|
+
this.stream = null;
|
|
3786
|
+
});
|
|
3787
|
+
this.stream.on("drain", () => {
|
|
3788
|
+
this.backpressured = false;
|
|
3789
|
+
});
|
|
3790
|
+
}
|
|
3791
|
+
write(line) {
|
|
3792
|
+
if (!this.stream) return false;
|
|
3793
|
+
if (this.backpressured) {
|
|
3794
|
+
this.droppedCount++;
|
|
3795
|
+
this.scheduleDropWarning();
|
|
3796
|
+
return false;
|
|
3797
|
+
}
|
|
3798
|
+
const written = this.stream.write(line);
|
|
3799
|
+
if (!written) {
|
|
3800
|
+
this.backpressured = true;
|
|
3801
|
+
}
|
|
3802
|
+
return true;
|
|
3803
|
+
}
|
|
3804
|
+
flush() {
|
|
3805
|
+
return new Promise((resolve3) => {
|
|
3806
|
+
if (!this.stream) {
|
|
3807
|
+
resolve3();
|
|
3808
|
+
return;
|
|
3809
|
+
}
|
|
3810
|
+
if (this.flushTimer) {
|
|
3811
|
+
clearTimeout(this.flushTimer);
|
|
3812
|
+
this.flushTimer = null;
|
|
3813
|
+
}
|
|
3814
|
+
const s = this.stream;
|
|
3815
|
+
this.stream = null;
|
|
3816
|
+
s.end(() => resolve3());
|
|
3817
|
+
});
|
|
3818
|
+
}
|
|
3819
|
+
scheduleDropWarning() {
|
|
3820
|
+
if (this.flushTimer) return;
|
|
3821
|
+
this.flushTimer = setTimeout(() => {
|
|
3822
|
+
this.flushTimer = null;
|
|
3823
|
+
if (this.droppedCount > 0 && this.stream) {
|
|
3824
|
+
this.stream.write(`[${(/* @__PURE__ */ new Date()).toISOString()}] [WARN] Dropped ${this.droppedCount} log lines due to IO backpressure
|
|
3825
|
+
`);
|
|
3826
|
+
this.droppedCount = 0;
|
|
3827
|
+
}
|
|
3828
|
+
}, 2e3);
|
|
3829
|
+
if (this.flushTimer && typeof this.flushTimer === "object" && "unref" in this.flushTimer) {
|
|
3830
|
+
this.flushTimer.unref();
|
|
3831
|
+
}
|
|
3832
|
+
}
|
|
3833
|
+
};
|
|
3834
|
+
|
|
3835
|
+
// src/utils/logger.ts
|
|
3746
3836
|
var LOG_DIR = join6(homedir4(), ".replicas", "logs");
|
|
3747
3837
|
var EngineLogger = class {
|
|
3748
3838
|
_sessionId = null;
|
|
3749
|
-
filePath = null;
|
|
3750
|
-
writeChain = Promise.resolve();
|
|
3751
3839
|
patched = false;
|
|
3840
|
+
writer = new StreamWriter();
|
|
3752
3841
|
get sessionId() {
|
|
3753
3842
|
return this._sessionId;
|
|
3754
3843
|
}
|
|
3755
3844
|
async initialize() {
|
|
3756
3845
|
await mkdir3(LOG_DIR, { recursive: true });
|
|
3757
3846
|
this._sessionId = this.createSessionId();
|
|
3758
|
-
|
|
3759
|
-
await writeFile2(
|
|
3847
|
+
const logPath = join6(LOG_DIR, `${this._sessionId}.log`);
|
|
3848
|
+
await writeFile2(logPath, `=== Replicas Engine Session ${this._sessionId} ===
|
|
3760
3849
|
`, "utf-8");
|
|
3850
|
+
this.writer.open(logPath);
|
|
3761
3851
|
this.patchConsole();
|
|
3762
|
-
this.log("INFO", `Engine logging initialized at ${
|
|
3852
|
+
this.log("INFO", `Engine logging initialized at ${logPath}`);
|
|
3763
3853
|
}
|
|
3764
3854
|
patchConsole() {
|
|
3765
3855
|
if (this.patched) return;
|
|
@@ -3781,21 +3871,11 @@ var EngineLogger = class {
|
|
|
3781
3871
|
};
|
|
3782
3872
|
}
|
|
3783
3873
|
log(level, message) {
|
|
3784
|
-
|
|
3785
|
-
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [${level}] ${message}
|
|
3786
|
-
`;
|
|
3787
|
-
this.writeChain = this.writeChain.then(() => appendFile(this.filePath, line, "utf-8")).catch((error) => {
|
|
3788
|
-
try {
|
|
3789
|
-
const err = error instanceof Error ? error.message : "Unknown error";
|
|
3790
|
-
process.stderr.write(`[EngineLogger] Failed to append log line: ${err}
|
|
3874
|
+
this.writer.write(`[${(/* @__PURE__ */ new Date()).toISOString()}] [${level}] ${message}
|
|
3791
3875
|
`);
|
|
3792
|
-
} catch {
|
|
3793
|
-
}
|
|
3794
|
-
});
|
|
3795
3876
|
}
|
|
3796
|
-
/** Wait for all queued log writes to complete. */
|
|
3797
3877
|
flush() {
|
|
3798
|
-
return this.
|
|
3878
|
+
return this.writer.flush();
|
|
3799
3879
|
}
|
|
3800
3880
|
createSessionId() {
|
|
3801
3881
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-");
|
|
@@ -3806,7 +3886,7 @@ var EngineLogger = class {
|
|
|
3806
3886
|
var engineLogger = new EngineLogger();
|
|
3807
3887
|
|
|
3808
3888
|
// src/services/replicas-config-service.ts
|
|
3809
|
-
import { readFile as readFile5, appendFile
|
|
3889
|
+
import { readFile as readFile5, appendFile, writeFile as writeFile4, mkdir as mkdir6 } from "fs/promises";
|
|
3810
3890
|
import { existsSync as existsSync4 } from "fs";
|
|
3811
3891
|
import { join as join9 } from "path";
|
|
3812
3892
|
import { homedir as homedir7 } from "os";
|
|
@@ -4180,7 +4260,7 @@ var ReplicasConfigService = class {
|
|
|
4180
4260
|
`;
|
|
4181
4261
|
try {
|
|
4182
4262
|
await mkdir6(join9(homedir7(), ".replicas"), { recursive: true });
|
|
4183
|
-
await
|
|
4263
|
+
await appendFile(START_HOOKS_LOG, logLine, "utf-8");
|
|
4184
4264
|
} catch (error) {
|
|
4185
4265
|
console.error("Failed to write to start hooks log:", error);
|
|
4186
4266
|
}
|
|
@@ -4508,7 +4588,7 @@ ${startHookConfig.commands.join("\n")}`;
|
|
|
4508
4588
|
var replicasConfigService = new ReplicasConfigService();
|
|
4509
4589
|
|
|
4510
4590
|
// src/services/event-service.ts
|
|
4511
|
-
import {
|
|
4591
|
+
import { mkdir as mkdir7 } from "fs/promises";
|
|
4512
4592
|
import { homedir as homedir8 } from "os";
|
|
4513
4593
|
import { join as join10 } from "path";
|
|
4514
4594
|
import { randomUUID } from "crypto";
|
|
@@ -4516,9 +4596,10 @@ var ENGINE_DIR = join10(homedir8(), ".replicas", "engine");
|
|
|
4516
4596
|
var EVENTS_FILE = join10(ENGINE_DIR, "events.jsonl");
|
|
4517
4597
|
var EventService = class {
|
|
4518
4598
|
subscribers = /* @__PURE__ */ new Map();
|
|
4519
|
-
|
|
4599
|
+
writer = new StreamWriter();
|
|
4520
4600
|
async initialize() {
|
|
4521
4601
|
await mkdir7(ENGINE_DIR, { recursive: true });
|
|
4602
|
+
this.writer.open(EVENTS_FILE);
|
|
4522
4603
|
}
|
|
4523
4604
|
subscribe(subscriber) {
|
|
4524
4605
|
const id = randomUUID();
|
|
@@ -4531,18 +4612,13 @@ var EventService = class {
|
|
|
4531
4612
|
return this.subscribers.size;
|
|
4532
4613
|
}
|
|
4533
4614
|
async publish(event) {
|
|
4534
|
-
this.
|
|
4535
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4536
|
-
process.stderr.write(`[EventService] Previous write failed, continuing: ${message}
|
|
4537
|
-
`);
|
|
4538
|
-
}).then(() => appendFile3(EVENTS_FILE, JSON.stringify(event) + "\n", "utf-8"));
|
|
4615
|
+
this.writer.write(JSON.stringify(event) + "\n");
|
|
4539
4616
|
for (const subscriber of this.subscribers.values()) {
|
|
4540
4617
|
try {
|
|
4541
4618
|
subscriber(event);
|
|
4542
4619
|
} catch {
|
|
4543
4620
|
}
|
|
4544
4621
|
}
|
|
4545
|
-
await this.writeChain;
|
|
4546
4622
|
}
|
|
4547
4623
|
};
|
|
4548
4624
|
var eventService = new EventService();
|
|
@@ -4664,7 +4740,7 @@ async function registerDesktopPreview() {
|
|
|
4664
4740
|
|
|
4665
4741
|
// src/services/chat/chat-service.ts
|
|
4666
4742
|
import { existsSync as existsSync7 } from "fs";
|
|
4667
|
-
import { appendFile as
|
|
4743
|
+
import { appendFile as appendFile3, copyFile, mkdir as mkdir11, readFile as readFile8, rename as rename2, rm } from "fs/promises";
|
|
4668
4744
|
import { homedir as homedir12 } from "os";
|
|
4669
4745
|
import { join as join14 } from "path";
|
|
4670
4746
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
@@ -4675,35 +4751,15 @@ import {
|
|
|
4675
4751
|
} from "@anthropic-ai/claude-agent-sdk";
|
|
4676
4752
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
4677
4753
|
import { join as join13 } from "path";
|
|
4678
|
-
import { mkdir as mkdir10, appendFile as
|
|
4754
|
+
import { mkdir as mkdir10, appendFile as appendFile2 } from "fs/promises";
|
|
4679
4755
|
import { homedir as homedir11 } from "os";
|
|
4680
4756
|
|
|
4681
4757
|
// src/utils/jsonl-reader.ts
|
|
4682
4758
|
import { readFile as readFile7 } from "fs/promises";
|
|
4683
|
-
function isJsonlEvent(value) {
|
|
4684
|
-
if (!isRecord4(value)) {
|
|
4685
|
-
return false;
|
|
4686
|
-
}
|
|
4687
|
-
return typeof value.timestamp === "string" && typeof value.type === "string" && isRecord4(value.payload);
|
|
4688
|
-
}
|
|
4689
|
-
function parseJsonlEvents(lines) {
|
|
4690
|
-
const events = [];
|
|
4691
|
-
for (const line of lines) {
|
|
4692
|
-
try {
|
|
4693
|
-
const parsed = JSON.parse(line);
|
|
4694
|
-
if (isJsonlEvent(parsed)) {
|
|
4695
|
-
events.push(parsed);
|
|
4696
|
-
}
|
|
4697
|
-
} catch {
|
|
4698
|
-
}
|
|
4699
|
-
}
|
|
4700
|
-
return events;
|
|
4701
|
-
}
|
|
4702
4759
|
async function readJSONL(filePath) {
|
|
4703
4760
|
try {
|
|
4704
4761
|
const content = await readFile7(filePath, "utf-8");
|
|
4705
|
-
|
|
4706
|
-
return parseJsonlEvents(lines);
|
|
4762
|
+
return parseAgentEventJsonl(content);
|
|
4707
4763
|
} catch (error) {
|
|
4708
4764
|
return [];
|
|
4709
4765
|
}
|
|
@@ -5788,7 +5844,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
|
|
|
5788
5844
|
type,
|
|
5789
5845
|
payload: { ...payload, parent_tool_use_id: null }
|
|
5790
5846
|
};
|
|
5791
|
-
await
|
|
5847
|
+
await appendFile2(this.historyFile, JSON.stringify(event) + "\n", "utf-8");
|
|
5792
5848
|
this.onEvent(event);
|
|
5793
5849
|
}
|
|
5794
5850
|
buildCanUseTool() {
|
|
@@ -5924,7 +5980,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
|
|
|
5924
5980
|
}
|
|
5925
5981
|
};
|
|
5926
5982
|
try {
|
|
5927
|
-
await
|
|
5983
|
+
await appendFile2(this.historyFile, JSON.stringify(event) + "\n", "utf-8");
|
|
5928
5984
|
} catch (writeError) {
|
|
5929
5985
|
console.error("[ClaudeManager] Failed to record auth-retry-exhausted event:", writeError);
|
|
5930
5986
|
}
|
|
@@ -6254,7 +6310,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
|
|
|
6254
6310
|
try {
|
|
6255
6311
|
const usage = await response.getContextUsage();
|
|
6256
6312
|
const event = this.emitContextUsage(this.buildContextUsagePayload(usage));
|
|
6257
|
-
await
|
|
6313
|
+
await appendFile2(this.historyFile, JSON.stringify(event) + "\n", "utf-8");
|
|
6258
6314
|
} catch (error) {
|
|
6259
6315
|
console.warn("[ClaudeManager] Failed to record context usage:", error instanceof Error ? error.message : error);
|
|
6260
6316
|
}
|
|
@@ -6330,7 +6386,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
|
|
|
6330
6386
|
type: `claude-${event.type}`,
|
|
6331
6387
|
payload: event
|
|
6332
6388
|
};
|
|
6333
|
-
await
|
|
6389
|
+
await appendFile2(this.historyFile, JSON.stringify(jsonEvent) + "\n", "utf-8");
|
|
6334
6390
|
this.onEvent(jsonEvent);
|
|
6335
6391
|
}
|
|
6336
6392
|
};
|
|
@@ -6531,7 +6587,7 @@ var AspClient = class {
|
|
|
6531
6587
|
// src/managers/codex-asp/app-server-process.ts
|
|
6532
6588
|
var DEFAULT_CODEX_BINARY = "codex";
|
|
6533
6589
|
var DEFAULT_CODEX_ARGS = ["app-server", "--listen", "stdio://"];
|
|
6534
|
-
var ENGINE_PACKAGE_VERSION = "0.1.
|
|
6590
|
+
var ENGINE_PACKAGE_VERSION = "0.1.288";
|
|
6535
6591
|
var INITIALIZE_METHOD = "initialize";
|
|
6536
6592
|
var INITIALIZED_NOTIFICATION = "initialized";
|
|
6537
6593
|
var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
|
|
@@ -8911,7 +8967,8 @@ var ChatService = class {
|
|
|
8911
8967
|
}
|
|
8912
8968
|
workingDirectory;
|
|
8913
8969
|
chats = /* @__PURE__ */ new Map();
|
|
8914
|
-
|
|
8970
|
+
persistInFlight = false;
|
|
8971
|
+
persistQueued = false;
|
|
8915
8972
|
async initialize() {
|
|
8916
8973
|
await mkdir11(ENGINE_DIR2, { recursive: true });
|
|
8917
8974
|
await mkdir11(CLAUDE_HISTORY_DIR, { recursive: true });
|
|
@@ -9018,7 +9075,7 @@ var ChatService = class {
|
|
|
9018
9075
|
}
|
|
9019
9076
|
async appendSender(chatId, sender) {
|
|
9020
9077
|
try {
|
|
9021
|
-
await
|
|
9078
|
+
await appendFile3(this.senderFilePath(chatId), JSON.stringify(sender) + "\n", "utf-8");
|
|
9022
9079
|
} catch (error) {
|
|
9023
9080
|
console.error("[ChatService] Failed to append sender record:", error);
|
|
9024
9081
|
}
|
|
@@ -9408,8 +9465,12 @@ var ChatService = class {
|
|
|
9408
9465
|
}
|
|
9409
9466
|
}
|
|
9410
9467
|
async persistAllChats() {
|
|
9411
|
-
|
|
9412
|
-
|
|
9468
|
+
if (this.persistInFlight) {
|
|
9469
|
+
this.persistQueued = true;
|
|
9470
|
+
return;
|
|
9471
|
+
}
|
|
9472
|
+
this.persistInFlight = true;
|
|
9473
|
+
try {
|
|
9413
9474
|
const payload = Array.from(this.chats.values()).map((chat) => chat.persisted);
|
|
9414
9475
|
try {
|
|
9415
9476
|
await copyFile(CHATS_FILE, CHATS_BACKUP_FILE);
|
|
@@ -9420,8 +9481,14 @@ var ChatService = class {
|
|
|
9420
9481
|
}
|
|
9421
9482
|
await atomicWriteFile(CHATS_FILE, `${JSON.stringify(payload, null, 2)}
|
|
9422
9483
|
`);
|
|
9423
|
-
}
|
|
9424
|
-
|
|
9484
|
+
} catch {
|
|
9485
|
+
} finally {
|
|
9486
|
+
this.persistInFlight = false;
|
|
9487
|
+
if (this.persistQueued) {
|
|
9488
|
+
this.persistQueued = false;
|
|
9489
|
+
void this.persistAllChats();
|
|
9490
|
+
}
|
|
9491
|
+
}
|
|
9425
9492
|
}
|
|
9426
9493
|
async publish(input) {
|
|
9427
9494
|
const event = {
|
|
@@ -9532,11 +9599,11 @@ function scoreMatch(query2, filePath) {
|
|
|
9532
9599
|
const lowerQuery = query2.toLowerCase();
|
|
9533
9600
|
const lowerPath = filePath.toLowerCase();
|
|
9534
9601
|
const segments = lowerPath.split("/");
|
|
9535
|
-
const
|
|
9536
|
-
if (
|
|
9537
|
-
if (
|
|
9602
|
+
const basename3 = segments[segments.length - 1] ?? "";
|
|
9603
|
+
if (basename3 === lowerQuery) return 100;
|
|
9604
|
+
if (basename3.startsWith(lowerQuery)) return 90;
|
|
9538
9605
|
if (segments.some((seg) => seg === lowerQuery)) return 80;
|
|
9539
|
-
if (
|
|
9606
|
+
if (basename3.includes(lowerQuery)) return 70;
|
|
9540
9607
|
if (segments.some((seg) => seg.includes(lowerQuery))) return 60;
|
|
9541
9608
|
if (lowerPath.includes(lowerQuery)) return 50;
|
|
9542
9609
|
return 0;
|
|
@@ -9739,23 +9806,76 @@ var RepoFileService = class {
|
|
|
9739
9806
|
// src/v1-routes.ts
|
|
9740
9807
|
import { Hono } from "hono";
|
|
9741
9808
|
import { z as z2 } from "zod";
|
|
9742
|
-
import { readdir as
|
|
9743
|
-
import { join as
|
|
9809
|
+
import { readdir as readdir6, stat as stat4, readFile as readFile14 } from "fs/promises";
|
|
9810
|
+
import { join as join20, resolve as resolve2 } from "path";
|
|
9744
9811
|
|
|
9745
|
-
// src/services/
|
|
9746
|
-
import { readdir as readdir3, readFile as readFile10
|
|
9747
|
-
import { homedir as homedir13 } from "os";
|
|
9812
|
+
// src/services/upload-chat-transcripts.ts
|
|
9813
|
+
import { readdir as readdir3, readFile as readFile10 } from "fs/promises";
|
|
9748
9814
|
import { basename, join as join16 } from "path";
|
|
9815
|
+
import { homedir as homedir13 } from "os";
|
|
9816
|
+
var ENGINE_DIR3 = join16(homedir13(), ".replicas", "engine");
|
|
9817
|
+
var HISTORY_DIRS = [
|
|
9818
|
+
join16(ENGINE_DIR3, "claude-histories"),
|
|
9819
|
+
join16(ENGINE_DIR3, "relay-histories")
|
|
9820
|
+
];
|
|
9821
|
+
async function flushAllChatTranscripts() {
|
|
9822
|
+
let flushed = 0;
|
|
9823
|
+
let failed = 0;
|
|
9824
|
+
const tasks = [];
|
|
9825
|
+
for (const dir of HISTORY_DIRS) {
|
|
9826
|
+
let entries;
|
|
9827
|
+
try {
|
|
9828
|
+
entries = await readdir3(dir);
|
|
9829
|
+
} catch {
|
|
9830
|
+
continue;
|
|
9831
|
+
}
|
|
9832
|
+
for (const entry of entries) {
|
|
9833
|
+
if (!entry.endsWith(".jsonl")) continue;
|
|
9834
|
+
const chatId = basename(entry, ".jsonl");
|
|
9835
|
+
tasks.push(
|
|
9836
|
+
uploadOne(chatId, join16(dir, entry)).then(() => {
|
|
9837
|
+
flushed++;
|
|
9838
|
+
}).catch((err) => {
|
|
9839
|
+
failed++;
|
|
9840
|
+
console.error("[ChatTranscriptUploader] upload failed:", { chatId, err });
|
|
9841
|
+
})
|
|
9842
|
+
);
|
|
9843
|
+
}
|
|
9844
|
+
}
|
|
9845
|
+
await Promise.all(tasks);
|
|
9846
|
+
return { flushed, failed };
|
|
9847
|
+
}
|
|
9848
|
+
async function uploadOne(chatId, filePath) {
|
|
9849
|
+
const bytes = await readFile10(filePath);
|
|
9850
|
+
if (bytes.byteLength === 0) return;
|
|
9851
|
+
const form = new FormData();
|
|
9852
|
+
form.append("chat_id", chatId);
|
|
9853
|
+
form.append(
|
|
9854
|
+
"file",
|
|
9855
|
+
new Blob([new Uint8Array(bytes)], { type: "application/x-ndjson" }),
|
|
9856
|
+
"transcript.jsonl"
|
|
9857
|
+
);
|
|
9858
|
+
const response = await monolithRequest("/v1/engine/chat-transcripts", { body: form });
|
|
9859
|
+
if (!response.ok) {
|
|
9860
|
+
const errorText = await response.text();
|
|
9861
|
+
throw new Error(`upload failed: ${response.status} ${errorText}`);
|
|
9862
|
+
}
|
|
9863
|
+
}
|
|
9864
|
+
|
|
9865
|
+
// src/services/canvas-service.ts
|
|
9866
|
+
import { readdir as readdir4, readFile as readFile11, stat as stat3 } from "fs/promises";
|
|
9867
|
+
import { homedir as homedir14 } from "os";
|
|
9868
|
+
import { basename as basename2, join as join17 } from "path";
|
|
9749
9869
|
var CANVAS_DIRECTORIES = [
|
|
9750
|
-
|
|
9751
|
-
|
|
9870
|
+
join17(homedir14(), ".claude", "plans"),
|
|
9871
|
+
join17(homedir14(), ".replicas", "canvas")
|
|
9752
9872
|
];
|
|
9753
9873
|
var MAX_CANVAS_FILE_BYTES = 5 * 1024 * 1024;
|
|
9754
9874
|
function isTextKind(kind) {
|
|
9755
9875
|
return kind === "markdown" || kind === "html";
|
|
9756
9876
|
}
|
|
9757
9877
|
function sanitizeFilename(filename) {
|
|
9758
|
-
return
|
|
9878
|
+
return basename2(filename);
|
|
9759
9879
|
}
|
|
9760
9880
|
var CanvasService = class {
|
|
9761
9881
|
async listItems() {
|
|
@@ -9763,7 +9883,7 @@ var CanvasService = class {
|
|
|
9763
9883
|
for (const directory of CANVAS_DIRECTORIES) {
|
|
9764
9884
|
let entries;
|
|
9765
9885
|
try {
|
|
9766
|
-
entries = await
|
|
9886
|
+
entries = await readdir4(directory, { withFileTypes: true });
|
|
9767
9887
|
} catch {
|
|
9768
9888
|
continue;
|
|
9769
9889
|
}
|
|
@@ -9774,7 +9894,7 @@ var CanvasService = class {
|
|
|
9774
9894
|
const { kind } = classifyCanvasFilename(entry.name);
|
|
9775
9895
|
let sizeBytes = 0;
|
|
9776
9896
|
try {
|
|
9777
|
-
const s = await stat3(
|
|
9897
|
+
const s = await stat3(join17(directory, entry.name));
|
|
9778
9898
|
sizeBytes = s.size;
|
|
9779
9899
|
} catch {
|
|
9780
9900
|
continue;
|
|
@@ -9789,7 +9909,7 @@ var CanvasService = class {
|
|
|
9789
9909
|
if (!safe || safe !== filename || safe.startsWith(".")) return null;
|
|
9790
9910
|
const { kind, mimeType } = classifyCanvasFilename(safe);
|
|
9791
9911
|
for (const directory of CANVAS_DIRECTORIES) {
|
|
9792
|
-
const filePath =
|
|
9912
|
+
const filePath = join17(directory, safe);
|
|
9793
9913
|
let sizeBytes = 0;
|
|
9794
9914
|
try {
|
|
9795
9915
|
const s = await stat3(filePath);
|
|
@@ -9808,10 +9928,10 @@ var CanvasService = class {
|
|
|
9808
9928
|
}
|
|
9809
9929
|
try {
|
|
9810
9930
|
if (isTextKind(kind)) {
|
|
9811
|
-
const content = await
|
|
9931
|
+
const content = await readFile11(filePath, "utf-8");
|
|
9812
9932
|
return { filename: safe, kind, sizeBytes, mimeType, content };
|
|
9813
9933
|
}
|
|
9814
|
-
const buf = await
|
|
9934
|
+
const buf = await readFile11(filePath);
|
|
9815
9935
|
return { filename: safe, kind, sizeBytes, mimeType, base64: buf.toString("base64") };
|
|
9816
9936
|
} catch {
|
|
9817
9937
|
continue;
|
|
@@ -9824,16 +9944,16 @@ var canvasService = new CanvasService();
|
|
|
9824
9944
|
|
|
9825
9945
|
// src/services/warm-hooks-service.ts
|
|
9826
9946
|
import { spawn as spawn4 } from "child_process";
|
|
9827
|
-
import { readFile as
|
|
9947
|
+
import { readFile as readFile13 } from "fs/promises";
|
|
9828
9948
|
import { existsSync as existsSync8 } from "fs";
|
|
9829
|
-
import { join as
|
|
9949
|
+
import { join as join19 } from "path";
|
|
9830
9950
|
|
|
9831
9951
|
// src/services/warm-hook-logs-service.ts
|
|
9832
|
-
import { mkdir as mkdir12, readFile as
|
|
9833
|
-
import { homedir as
|
|
9834
|
-
import { join as
|
|
9835
|
-
var LOGS_DIR2 =
|
|
9836
|
-
var CURRENT_RUN_LOG =
|
|
9952
|
+
import { mkdir as mkdir12, readFile as readFile12, writeFile as writeFile6, readdir as readdir5, appendFile as appendFile4, unlink as unlink3 } from "fs/promises";
|
|
9953
|
+
import { homedir as homedir15 } from "os";
|
|
9954
|
+
import { join as join18 } from "path";
|
|
9955
|
+
var LOGS_DIR2 = join18(homedir15(), ".replicas", "warm-hook-logs");
|
|
9956
|
+
var CURRENT_RUN_LOG = join18(LOGS_DIR2, "current-run.log");
|
|
9837
9957
|
var GLOBAL_FILENAME = "global.json";
|
|
9838
9958
|
function withPreview2(stored) {
|
|
9839
9959
|
const preview = buildHookOutputPreview(stored.output);
|
|
@@ -9850,7 +9970,7 @@ var WarmHookLogsService = class {
|
|
|
9850
9970
|
hookName: "organization",
|
|
9851
9971
|
...entry
|
|
9852
9972
|
};
|
|
9853
|
-
await writeFile6(
|
|
9973
|
+
await writeFile6(join18(LOGS_DIR2, GLOBAL_FILENAME), `${JSON.stringify(log, null, 2)}
|
|
9854
9974
|
`, "utf-8");
|
|
9855
9975
|
}
|
|
9856
9976
|
async saveEnvironmentHookLog(entry) {
|
|
@@ -9860,7 +9980,7 @@ var WarmHookLogsService = class {
|
|
|
9860
9980
|
hookName: "environment",
|
|
9861
9981
|
...entry
|
|
9862
9982
|
};
|
|
9863
|
-
await writeFile6(
|
|
9983
|
+
await writeFile6(join18(LOGS_DIR2, ENVIRONMENT_HOOK_LOG_FILENAME), `${JSON.stringify(log, null, 2)}
|
|
9864
9984
|
`, "utf-8");
|
|
9865
9985
|
}
|
|
9866
9986
|
async saveRepoHookLog(repoName, entry) {
|
|
@@ -9870,13 +9990,13 @@ var WarmHookLogsService = class {
|
|
|
9870
9990
|
hookName: repoName,
|
|
9871
9991
|
...entry
|
|
9872
9992
|
};
|
|
9873
|
-
await writeFile6(
|
|
9993
|
+
await writeFile6(join18(LOGS_DIR2, repoHookLogFilename(repoName)), `${JSON.stringify(log, null, 2)}
|
|
9874
9994
|
`, "utf-8");
|
|
9875
9995
|
}
|
|
9876
9996
|
async getAllLogs() {
|
|
9877
9997
|
let files;
|
|
9878
9998
|
try {
|
|
9879
|
-
files = await
|
|
9999
|
+
files = await readdir5(LOGS_DIR2);
|
|
9880
10000
|
} catch (err) {
|
|
9881
10001
|
if (err.code === "ENOENT") {
|
|
9882
10002
|
return [];
|
|
@@ -9889,7 +10009,7 @@ var WarmHookLogsService = class {
|
|
|
9889
10009
|
continue;
|
|
9890
10010
|
}
|
|
9891
10011
|
try {
|
|
9892
|
-
const raw = await
|
|
10012
|
+
const raw = await readFile12(join18(LOGS_DIR2, file), "utf-8");
|
|
9893
10013
|
const stored = JSON.parse(raw);
|
|
9894
10014
|
logs.push(withPreview2(stored));
|
|
9895
10015
|
} catch {
|
|
@@ -9914,11 +10034,11 @@ var WarmHookLogsService = class {
|
|
|
9914
10034
|
}
|
|
9915
10035
|
async appendCurrentRunLog(chunk) {
|
|
9916
10036
|
if (!chunk) return;
|
|
9917
|
-
await
|
|
10037
|
+
await appendFile4(CURRENT_RUN_LOG, chunk, "utf-8");
|
|
9918
10038
|
}
|
|
9919
10039
|
async getCurrentRunLog() {
|
|
9920
10040
|
try {
|
|
9921
|
-
return await
|
|
10041
|
+
return await readFile12(CURRENT_RUN_LOG, "utf-8");
|
|
9922
10042
|
} catch (err) {
|
|
9923
10043
|
if (err.code === "ENOENT") return null;
|
|
9924
10044
|
throw err;
|
|
@@ -9927,7 +10047,7 @@ var WarmHookLogsService = class {
|
|
|
9927
10047
|
async getFullOutput(hookType, hookName) {
|
|
9928
10048
|
const filename = hookType === "global" ? GLOBAL_FILENAME : hookType === "environment" ? ENVIRONMENT_HOOK_LOG_FILENAME : repoHookLogFilename(hookName);
|
|
9929
10049
|
try {
|
|
9930
|
-
const raw = await
|
|
10050
|
+
const raw = await readFile12(join18(LOGS_DIR2, filename), "utf-8");
|
|
9931
10051
|
const stored = JSON.parse(raw);
|
|
9932
10052
|
if (stored.hookType !== hookType || stored.hookName !== hookName) {
|
|
9933
10053
|
return null;
|
|
@@ -9946,12 +10066,12 @@ var warmHookLogsService = new WarmHookLogsService();
|
|
|
9946
10066
|
// src/services/warm-hooks-service.ts
|
|
9947
10067
|
async function readRepoWarmHook(repoPath) {
|
|
9948
10068
|
for (const filename of REPLICAS_CONFIG_FILENAMES) {
|
|
9949
|
-
const configPath =
|
|
10069
|
+
const configPath = join19(repoPath, filename);
|
|
9950
10070
|
if (!existsSync8(configPath)) {
|
|
9951
10071
|
continue;
|
|
9952
10072
|
}
|
|
9953
10073
|
try {
|
|
9954
|
-
const raw = await
|
|
10074
|
+
const raw = await readFile13(configPath, "utf-8");
|
|
9955
10075
|
const config = parseReplicasConfigString(raw, filename);
|
|
9956
10076
|
if (!config.warmHook) {
|
|
9957
10077
|
return null;
|
|
@@ -10427,6 +10547,17 @@ function createV1Routes(deps) {
|
|
|
10427
10547
|
return c.json(jsonError("Failed to clear goal", error instanceof Error ? error.message : "Unknown error"), 404);
|
|
10428
10548
|
}
|
|
10429
10549
|
});
|
|
10550
|
+
app2.post("/chat-transcripts/flush-all", async (c) => {
|
|
10551
|
+
try {
|
|
10552
|
+
const result = await flushAllChatTranscripts();
|
|
10553
|
+
return c.json(result);
|
|
10554
|
+
} catch (error) {
|
|
10555
|
+
return c.json(
|
|
10556
|
+
jsonError("Failed to flush transcripts", error instanceof Error ? error.message : "Unknown error"),
|
|
10557
|
+
500
|
|
10558
|
+
);
|
|
10559
|
+
}
|
|
10560
|
+
});
|
|
10430
10561
|
app2.get("/chats/:chatId/queue", (c) => {
|
|
10431
10562
|
try {
|
|
10432
10563
|
const result = deps.chatService.getChatQueue(c.req.param("chatId"));
|
|
@@ -10862,11 +10993,11 @@ function createV1Routes(deps) {
|
|
|
10862
10993
|
});
|
|
10863
10994
|
app2.get("/logs", async (c) => {
|
|
10864
10995
|
try {
|
|
10865
|
-
const files = await
|
|
10996
|
+
const files = await readdir6(LOG_DIR).catch(() => []);
|
|
10866
10997
|
const logFiles = files.filter((f) => f.endsWith(".log"));
|
|
10867
10998
|
const sessions = await Promise.all(
|
|
10868
10999
|
logFiles.map(async (filename) => {
|
|
10869
|
-
const filePath =
|
|
11000
|
+
const filePath = join20(LOG_DIR, filename);
|
|
10870
11001
|
const fileStat = await stat4(filePath);
|
|
10871
11002
|
const sessionId = filename.replace(/\.log$/, "");
|
|
10872
11003
|
return {
|
|
@@ -10903,7 +11034,7 @@ function createV1Routes(deps) {
|
|
|
10903
11034
|
const limit = Math.min(parseInt(c.req.query("limit") || "500", 10), 5e3);
|
|
10904
11035
|
let content;
|
|
10905
11036
|
try {
|
|
10906
|
-
content = await
|
|
11037
|
+
content = await readFile14(filePath, "utf-8");
|
|
10907
11038
|
} catch {
|
|
10908
11039
|
return c.json(jsonError("Log session not found"), 404);
|
|
10909
11040
|
}
|
package/package.json
CHANGED
|
@@ -86,9 +86,9 @@ while true; do
|
|
|
86
86
|
|
|
87
87
|
START_TIME=$(date +%s)
|
|
88
88
|
if [ -n "$ENGINE_LD_PRELOAD" ]; then
|
|
89
|
-
LD_PRELOAD="$ENGINE_LD_PRELOAD" "${ENGINE_CMD[@]}" >> "$BOOTSTRAP_LOG" 2>&1 &
|
|
89
|
+
LD_PRELOAD="$ENGINE_LD_PRELOAD" "${ENGINE_CMD[@]}" > >(cat >> "$BOOTSTRAP_LOG") 2>&1 &
|
|
90
90
|
else
|
|
91
|
-
"${ENGINE_CMD[@]}" >> "$BOOTSTRAP_LOG" 2>&1 &
|
|
91
|
+
"${ENGINE_CMD[@]}" > >(cat >> "$BOOTSTRAP_LOG") 2>&1 &
|
|
92
92
|
fi
|
|
93
93
|
ENGINE_PID=$!
|
|
94
94
|
echo "$ENGINE_PID" > "$PIDFILE"
|