replicas-engine 0.1.309 → 0.1.313
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 +322 -169
- package/package.json +1 -1
package/dist/src/index.js
CHANGED
|
@@ -14,21 +14,6 @@ function isRecord(value) {
|
|
|
14
14
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
// ../shared/src/result.ts
|
|
18
|
-
function createSuccessResult(data) {
|
|
19
|
-
return { ok: true, data };
|
|
20
|
-
}
|
|
21
|
-
function createErrorResult(error) {
|
|
22
|
-
return {
|
|
23
|
-
ok: false,
|
|
24
|
-
error: {
|
|
25
|
-
message: error.message,
|
|
26
|
-
code: error.code,
|
|
27
|
-
details: error.details
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
|
|
32
17
|
// ../shared/src/agent.ts
|
|
33
18
|
var CODEX_REASONING_EFFORT_BY_THINKING_LEVEL = {
|
|
34
19
|
low: "low",
|
|
@@ -132,9 +117,9 @@ var EXT_TO_LANGUAGE = {
|
|
|
132
117
|
function detectLanguageByPath(filePath) {
|
|
133
118
|
const dot = filePath.lastIndexOf(".");
|
|
134
119
|
if (dot === -1) {
|
|
135
|
-
const
|
|
136
|
-
if (
|
|
137
|
-
if (
|
|
120
|
+
const basename2 = filePath.split("/").pop() ?? "";
|
|
121
|
+
if (basename2 === "Dockerfile") return "dockerfile";
|
|
122
|
+
if (basename2 === "Makefile") return "makefile";
|
|
138
123
|
return null;
|
|
139
124
|
}
|
|
140
125
|
const ext = filePath.slice(dot).toLowerCase();
|
|
@@ -302,7 +287,7 @@ var WORKSPACE_SIZES = ["small", "large"];
|
|
|
302
287
|
var INVALID_WORKSPACE_SIZE_ERROR = `Invalid size: must be one of ${WORKSPACE_SIZES.join(", ")}`;
|
|
303
288
|
|
|
304
289
|
// ../shared/src/e2b.ts
|
|
305
|
-
var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-06-13-
|
|
290
|
+
var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-06-13-v9";
|
|
306
291
|
|
|
307
292
|
// ../shared/src/runtime-env.ts
|
|
308
293
|
function parsePosixEnvFile(content) {
|
|
@@ -2091,6 +2076,35 @@ var MODEL_LABELS = {
|
|
|
2091
2076
|
"gpt-5.2": "GPT-5.2"
|
|
2092
2077
|
};
|
|
2093
2078
|
var IMAGE_MEDIA_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
|
|
2079
|
+
var MAX_CANVAS_FILE_BYTES = 5 * 1024 * 1024;
|
|
2080
|
+
function isCanvasTextKind(kind) {
|
|
2081
|
+
return kind === "markdown" || kind === "html";
|
|
2082
|
+
}
|
|
2083
|
+
function sanitizeCanvasFilename(filename) {
|
|
2084
|
+
const safe = filename.split(/[\\/]/).pop() ?? "";
|
|
2085
|
+
if (!safe || safe !== filename || safe.startsWith(".")) return null;
|
|
2086
|
+
return safe;
|
|
2087
|
+
}
|
|
2088
|
+
function bytesToBase64(bytes) {
|
|
2089
|
+
let binary = "";
|
|
2090
|
+
const chunkSize = 32768;
|
|
2091
|
+
for (let i = 0; i < bytes.length; i += chunkSize) {
|
|
2092
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + chunkSize));
|
|
2093
|
+
}
|
|
2094
|
+
return btoa(binary);
|
|
2095
|
+
}
|
|
2096
|
+
function serializeCanvasContentResponse(input) {
|
|
2097
|
+
const base = {
|
|
2098
|
+
filename: input.filename,
|
|
2099
|
+
kind: input.kind,
|
|
2100
|
+
sizeBytes: input.sizeBytes,
|
|
2101
|
+
mimeType: input.mimeType
|
|
2102
|
+
};
|
|
2103
|
+
if (isCanvasTextKind(input.kind)) {
|
|
2104
|
+
return { ...base, content: new TextDecoder().decode(input.bytes) };
|
|
2105
|
+
}
|
|
2106
|
+
return { ...base, base64: bytesToBase64(input.bytes) };
|
|
2107
|
+
}
|
|
2094
2108
|
var CANVAS_KIND_BY_EXTENSION = {
|
|
2095
2109
|
".md": { kind: "markdown", mimeType: "text/markdown; charset=utf-8" },
|
|
2096
2110
|
".markdown": { kind: "markdown", mimeType: "text/markdown; charset=utf-8" },
|
|
@@ -2925,6 +2939,32 @@ function upsertCredentialFileLines(filePath, hosts, lines) {
|
|
|
2925
2939
|
credentialFileQueue = task.catch(() => void 0);
|
|
2926
2940
|
return task;
|
|
2927
2941
|
}
|
|
2942
|
+
function removeCredentialFileLines(filePath, shouldRemove) {
|
|
2943
|
+
const task = credentialFileQueue.then(async () => {
|
|
2944
|
+
let existing = [];
|
|
2945
|
+
try {
|
|
2946
|
+
existing = (await readFile(filePath, "utf-8")).split("\n").filter(Boolean);
|
|
2947
|
+
} catch {
|
|
2948
|
+
return;
|
|
2949
|
+
}
|
|
2950
|
+
const kept = existing.filter((line) => !shouldRemove(line));
|
|
2951
|
+
await writeSecureCredentialFile(filePath, [...kept, ""].join("\n"));
|
|
2952
|
+
});
|
|
2953
|
+
credentialFileQueue = task.catch(() => void 0);
|
|
2954
|
+
return task;
|
|
2955
|
+
}
|
|
2956
|
+
|
|
2957
|
+
// src/utils/git-identity.ts
|
|
2958
|
+
async function updateGitIdentity(identity, tag) {
|
|
2959
|
+
try {
|
|
2960
|
+
const [nameArgs, emailArgs] = gitIdentityConfigCommands(identity);
|
|
2961
|
+
await execFileAsync("git", nameArgs);
|
|
2962
|
+
await execFileAsync("git", emailArgs);
|
|
2963
|
+
console.log(`[${tag}] Updated git identity to ${identity.name} <${identity.email}>`);
|
|
2964
|
+
} catch (error) {
|
|
2965
|
+
console.error(`[${tag}] Failed to update git identity:`, error);
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2928
2968
|
|
|
2929
2969
|
// src/managers/github-token-manager.ts
|
|
2930
2970
|
var GitHubTokenManager = class extends BaseRefreshManager {
|
|
@@ -2935,6 +2975,9 @@ var GitHubTokenManager = class extends BaseRefreshManager {
|
|
|
2935
2975
|
console.log("[GitHubTokenManager] Refreshing GitHub token...");
|
|
2936
2976
|
const response = await monolithRequest("/v1/engine/github/refresh-token");
|
|
2937
2977
|
if (!response.ok) {
|
|
2978
|
+
if (response.status === 403) {
|
|
2979
|
+
await this.clearGitHubCredentials();
|
|
2980
|
+
}
|
|
2938
2981
|
const errorText = await response.text();
|
|
2939
2982
|
throw new Error(`Token refresh failed: ${response.status} ${errorText}`);
|
|
2940
2983
|
}
|
|
@@ -2944,7 +2987,7 @@ var GitHubTokenManager = class extends BaseRefreshManager {
|
|
|
2944
2987
|
await this.updateGitCredentials(ghToken);
|
|
2945
2988
|
await this.updateGhHostsFile(ghToken, ghUsername);
|
|
2946
2989
|
if (data.gitIdentity) {
|
|
2947
|
-
await
|
|
2990
|
+
await updateGitIdentity(data.gitIdentity, "GitHubTokenManager");
|
|
2948
2991
|
}
|
|
2949
2992
|
if (data.userToken) {
|
|
2950
2993
|
console.log(`[GitHubTokenManager] Token refreshed with user token for PR attribution, installation token expires at ${data.expiresAt}, user token expires at ${data.userToken.expiresAt}`);
|
|
@@ -2977,15 +3020,12 @@ var GitHubTokenManager = class extends BaseRefreshManager {
|
|
|
2977
3020
|
console.error("[GitHubTokenManager] Failed to update gh hosts file:", error);
|
|
2978
3021
|
}
|
|
2979
3022
|
}
|
|
2980
|
-
async
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
} catch (error) {
|
|
2987
|
-
console.error("[GitHubTokenManager] Failed to update git identity:", error);
|
|
2988
|
-
}
|
|
3023
|
+
async clearGitHubCredentials() {
|
|
3024
|
+
const credentialsPath = path.join(ENGINE_ENV.HOME_DIR, ".git-credentials");
|
|
3025
|
+
await removeCredentialFileLines(
|
|
3026
|
+
credentialsPath,
|
|
3027
|
+
(line) => line.endsWith("@github.com")
|
|
3028
|
+
);
|
|
2989
3029
|
}
|
|
2990
3030
|
};
|
|
2991
3031
|
var githubTokenManager = new GitHubTokenManager();
|
|
@@ -2997,12 +3037,16 @@ var GitLabTokenManager = class extends BaseRefreshManager {
|
|
|
2997
3037
|
super("GitLabTokenManager");
|
|
2998
3038
|
}
|
|
2999
3039
|
async doRefresh(_config) {
|
|
3000
|
-
const
|
|
3001
|
-
if (!
|
|
3002
|
-
|
|
3040
|
+
const response = await monolithRequest("/v1/engine/gitlab/refresh-token");
|
|
3041
|
+
if (!response.ok) {
|
|
3042
|
+
if (response.status === 403) {
|
|
3043
|
+
await this.clearGitLabCredentials();
|
|
3044
|
+
}
|
|
3045
|
+
throw new Error(`Token refresh failed: ${response.status} ${await response.text()}`);
|
|
3003
3046
|
}
|
|
3004
|
-
const data =
|
|
3047
|
+
const data = await response.json();
|
|
3005
3048
|
if (!data.token) {
|
|
3049
|
+
await this.clearGitLabCredentials(data.hosts);
|
|
3006
3050
|
console.log(`[GitLabTokenManager] No GitLab token to install: ${data.reason}`);
|
|
3007
3051
|
return;
|
|
3008
3052
|
}
|
|
@@ -3015,25 +3059,15 @@ var GitLabTokenManager = class extends BaseRefreshManager {
|
|
|
3015
3059
|
);
|
|
3016
3060
|
console.log(`[GitLabTokenManager] Updated ${credentialsPath} for ${hosts.join(", ")}`);
|
|
3017
3061
|
if (data.gitIdentity) {
|
|
3018
|
-
await
|
|
3019
|
-
}
|
|
3020
|
-
}
|
|
3021
|
-
async fetchRefresh() {
|
|
3022
|
-
const response = await monolithRequest("/v1/engine/gitlab/refresh-token");
|
|
3023
|
-
if (!response.ok) {
|
|
3024
|
-
return createErrorResult({ message: `${response.status} ${await response.text()}` });
|
|
3062
|
+
await updateGitIdentity(data.gitIdentity, "GitLabTokenManager");
|
|
3025
3063
|
}
|
|
3026
|
-
return createSuccessResult(await response.json());
|
|
3027
3064
|
}
|
|
3028
|
-
async
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
} catch (error) {
|
|
3035
|
-
console.error("[GitLabTokenManager] Failed to update git identity:", error);
|
|
3036
|
-
}
|
|
3065
|
+
async clearGitLabCredentials(hosts = []) {
|
|
3066
|
+
const credentialsPath = path2.join(ENGINE_ENV.HOME_DIR, ".git-credentials");
|
|
3067
|
+
await removeCredentialFileLines(
|
|
3068
|
+
credentialsPath,
|
|
3069
|
+
(line) => line.startsWith("https://oauth2:") && (hosts.length === 0 || hosts.some((host) => line.endsWith(`@${host}`)))
|
|
3070
|
+
);
|
|
3037
3071
|
}
|
|
3038
3072
|
};
|
|
3039
3073
|
var gitlabTokenManager = new GitLabTokenManager();
|
|
@@ -4899,9 +4933,9 @@ async function registerDesktopPreview() {
|
|
|
4899
4933
|
|
|
4900
4934
|
// src/services/chat/chat-service.ts
|
|
4901
4935
|
import { existsSync as existsSync7 } from "fs";
|
|
4902
|
-
import { appendFile as appendFile4, copyFile, mkdir as mkdir11, readFile as
|
|
4903
|
-
import { homedir as
|
|
4904
|
-
import { join as
|
|
4936
|
+
import { appendFile as appendFile4, copyFile, mkdir as mkdir11, readFile as readFile12, rename as rename2, rm } from "fs/promises";
|
|
4937
|
+
import { homedir as homedir14 } from "os";
|
|
4938
|
+
import { join as join16 } from "path";
|
|
4905
4939
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
4906
4940
|
|
|
4907
4941
|
// src/managers/claude-manager.ts
|
|
@@ -6846,7 +6880,7 @@ var AspClient = class {
|
|
|
6846
6880
|
// src/managers/codex-asp/app-server-process.ts
|
|
6847
6881
|
var DEFAULT_CODEX_BINARY = "codex";
|
|
6848
6882
|
var DEFAULT_CODEX_ARGS = ["app-server", "--listen", "stdio://"];
|
|
6849
|
-
var ENGINE_PACKAGE_VERSION = "0.1.
|
|
6883
|
+
var ENGINE_PACKAGE_VERSION = "0.1.313";
|
|
6850
6884
|
var INITIALIZE_METHOD = "initialize";
|
|
6851
6885
|
var INITIALIZED_NOTIFICATION = "initialized";
|
|
6852
6886
|
var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
|
|
@@ -9149,15 +9183,190 @@ var KeepAliveService = class _KeepAliveService {
|
|
|
9149
9183
|
};
|
|
9150
9184
|
var keepAliveService = new KeepAliveService();
|
|
9151
9185
|
|
|
9152
|
-
// src/services/
|
|
9153
|
-
import { readdir as readdir3, readFile as readFile10 } from "fs/promises";
|
|
9154
|
-
import { basename, join as join14 } from "path";
|
|
9186
|
+
// src/services/canvas-service.ts
|
|
9187
|
+
import { readdir as readdir3, readFile as readFile10, stat as stat2 } from "fs/promises";
|
|
9155
9188
|
import { homedir as homedir12 } from "os";
|
|
9156
|
-
|
|
9189
|
+
import { join as join14 } from "path";
|
|
9190
|
+
var CANVAS_DIRECTORIES = [
|
|
9191
|
+
join14(homedir12(), ".claude", "plans"),
|
|
9192
|
+
join14(homedir12(), ".replicas", "canvas")
|
|
9193
|
+
];
|
|
9194
|
+
var CanvasService = class {
|
|
9195
|
+
async listItems() {
|
|
9196
|
+
const items = /* @__PURE__ */ new Map();
|
|
9197
|
+
for (const directory of CANVAS_DIRECTORIES) {
|
|
9198
|
+
let entries;
|
|
9199
|
+
try {
|
|
9200
|
+
entries = await readdir3(directory, { withFileTypes: true });
|
|
9201
|
+
} catch {
|
|
9202
|
+
continue;
|
|
9203
|
+
}
|
|
9204
|
+
for (const entry of entries) {
|
|
9205
|
+
if (!entry.isFile()) continue;
|
|
9206
|
+
if (entry.name.startsWith(".")) continue;
|
|
9207
|
+
if (items.has(entry.name)) continue;
|
|
9208
|
+
const { kind } = classifyCanvasFilename(entry.name);
|
|
9209
|
+
let sizeBytes = 0;
|
|
9210
|
+
try {
|
|
9211
|
+
const s = await stat2(join14(directory, entry.name));
|
|
9212
|
+
sizeBytes = s.size;
|
|
9213
|
+
} catch {
|
|
9214
|
+
continue;
|
|
9215
|
+
}
|
|
9216
|
+
items.set(entry.name, { filename: entry.name, kind, sizeBytes });
|
|
9217
|
+
}
|
|
9218
|
+
}
|
|
9219
|
+
return Array.from(items.values()).sort((a, b) => a.filename.localeCompare(b.filename));
|
|
9220
|
+
}
|
|
9221
|
+
async getItemFile(filename) {
|
|
9222
|
+
const safe = sanitizeCanvasFilename(filename);
|
|
9223
|
+
if (!safe) return null;
|
|
9224
|
+
const { kind, mimeType } = classifyCanvasFilename(safe);
|
|
9225
|
+
for (const directory of CANVAS_DIRECTORIES) {
|
|
9226
|
+
const filePath = join14(directory, safe);
|
|
9227
|
+
let sizeBytes = 0;
|
|
9228
|
+
let updatedAt = "";
|
|
9229
|
+
try {
|
|
9230
|
+
const s = await stat2(filePath);
|
|
9231
|
+
sizeBytes = s.size;
|
|
9232
|
+
updatedAt = s.mtime.toISOString();
|
|
9233
|
+
} catch {
|
|
9234
|
+
continue;
|
|
9235
|
+
}
|
|
9236
|
+
if (sizeBytes > MAX_CANVAS_FILE_BYTES) {
|
|
9237
|
+
return {
|
|
9238
|
+
filename: safe,
|
|
9239
|
+
kind,
|
|
9240
|
+
sizeBytes,
|
|
9241
|
+
mimeType,
|
|
9242
|
+
updatedAt,
|
|
9243
|
+
tooLarge: true
|
|
9244
|
+
};
|
|
9245
|
+
}
|
|
9246
|
+
try {
|
|
9247
|
+
const bytes = await readFile10(filePath);
|
|
9248
|
+
return { filename: safe, kind, sizeBytes, mimeType, updatedAt, bytes };
|
|
9249
|
+
} catch {
|
|
9250
|
+
continue;
|
|
9251
|
+
}
|
|
9252
|
+
}
|
|
9253
|
+
return null;
|
|
9254
|
+
}
|
|
9255
|
+
async getItem(filename) {
|
|
9256
|
+
const item = await this.getItemFile(filename);
|
|
9257
|
+
if (!item) return null;
|
|
9258
|
+
if (item.tooLarge || !item.bytes) {
|
|
9259
|
+
return {
|
|
9260
|
+
filename: item.filename,
|
|
9261
|
+
kind: item.kind,
|
|
9262
|
+
sizeBytes: item.sizeBytes,
|
|
9263
|
+
mimeType: item.mimeType,
|
|
9264
|
+
tooLarge: true
|
|
9265
|
+
};
|
|
9266
|
+
}
|
|
9267
|
+
return serializeCanvasContentResponse({
|
|
9268
|
+
filename: item.filename,
|
|
9269
|
+
kind: item.kind,
|
|
9270
|
+
sizeBytes: item.sizeBytes,
|
|
9271
|
+
mimeType: item.mimeType,
|
|
9272
|
+
bytes: item.bytes
|
|
9273
|
+
});
|
|
9274
|
+
}
|
|
9275
|
+
};
|
|
9276
|
+
var canvasService = new CanvasService();
|
|
9277
|
+
|
|
9278
|
+
// src/services/upload-canvas-items.ts
|
|
9279
|
+
var uploadedCanvasItems = /* @__PURE__ */ new Map();
|
|
9280
|
+
function isReconcileCanvasItemsResponse(value) {
|
|
9281
|
+
return typeof value === "object" && value !== null && "deleted" in value && typeof value.deleted === "number" && (!("skipped" in value) || typeof value.skipped === "string");
|
|
9282
|
+
}
|
|
9283
|
+
async function flushAllCanvasItems() {
|
|
9284
|
+
let flushed = 0;
|
|
9285
|
+
let skipped = 0;
|
|
9286
|
+
let failed = 0;
|
|
9287
|
+
let deleted = 0;
|
|
9288
|
+
const filenames = [];
|
|
9289
|
+
const items = await canvasService.listItems();
|
|
9290
|
+
await Promise.all(items.map(async (item) => {
|
|
9291
|
+
if (item.sizeBytes > MAX_CANVAS_FILE_BYTES) {
|
|
9292
|
+
skipped++;
|
|
9293
|
+
return;
|
|
9294
|
+
}
|
|
9295
|
+
try {
|
|
9296
|
+
const file = await canvasService.getItemFile(item.filename);
|
|
9297
|
+
if (!file?.bytes || file.tooLarge) {
|
|
9298
|
+
skipped++;
|
|
9299
|
+
return;
|
|
9300
|
+
}
|
|
9301
|
+
const uploaded = uploadedCanvasItems.get(file.filename);
|
|
9302
|
+
if (uploaded?.sizeBytes === file.sizeBytes && uploaded.updatedAt === file.updatedAt) {
|
|
9303
|
+
filenames.push(file.filename);
|
|
9304
|
+
skipped++;
|
|
9305
|
+
return;
|
|
9306
|
+
}
|
|
9307
|
+
await uploadCanvasItem(file);
|
|
9308
|
+
uploadedCanvasItems.set(file.filename, { sizeBytes: file.sizeBytes, updatedAt: file.updatedAt });
|
|
9309
|
+
filenames.push(file.filename);
|
|
9310
|
+
flushed++;
|
|
9311
|
+
} catch (err) {
|
|
9312
|
+
failed++;
|
|
9313
|
+
console.error("[CanvasUploader] upload failed:", { filename: item.filename, err });
|
|
9314
|
+
}
|
|
9315
|
+
}));
|
|
9316
|
+
if (failed === 0) {
|
|
9317
|
+
try {
|
|
9318
|
+
deleted = await reconcileCanvasItems(filenames);
|
|
9319
|
+
const currentFilenames = new Set(filenames);
|
|
9320
|
+
for (const filename of uploadedCanvasItems.keys()) {
|
|
9321
|
+
if (!currentFilenames.has(filename)) uploadedCanvasItems.delete(filename);
|
|
9322
|
+
}
|
|
9323
|
+
} catch (err) {
|
|
9324
|
+
failed++;
|
|
9325
|
+
console.error("[CanvasUploader] reconcile failed:", err);
|
|
9326
|
+
}
|
|
9327
|
+
}
|
|
9328
|
+
return { flushed, skipped, failed, deleted };
|
|
9329
|
+
}
|
|
9330
|
+
async function uploadCanvasItem(item) {
|
|
9331
|
+
if (!item.bytes || item.tooLarge) return;
|
|
9332
|
+
const form = new FormData();
|
|
9333
|
+
form.append("filename", item.filename);
|
|
9334
|
+
form.append("updated_at", item.updatedAt);
|
|
9335
|
+
form.append(
|
|
9336
|
+
"file",
|
|
9337
|
+
new Blob([item.bytes], { type: item.mimeType }),
|
|
9338
|
+
item.filename
|
|
9339
|
+
);
|
|
9340
|
+
const response = await monolithRequest("/v1/engine/canvas", { body: form });
|
|
9341
|
+
if (!response.ok) {
|
|
9342
|
+
const errorText = await response.text();
|
|
9343
|
+
throw new Error(`upload failed: ${response.status} ${errorText}`);
|
|
9344
|
+
}
|
|
9345
|
+
}
|
|
9346
|
+
async function reconcileCanvasItems(filenames) {
|
|
9347
|
+
const response = await monolithRequest("/v1/engine/canvas/reconcile", {
|
|
9348
|
+
body: { filenames }
|
|
9349
|
+
});
|
|
9350
|
+
if (!response.ok) {
|
|
9351
|
+
const errorText = await response.text();
|
|
9352
|
+
throw new Error(`reconcile failed: ${response.status} ${errorText}`);
|
|
9353
|
+
}
|
|
9354
|
+
const data = await response.json();
|
|
9355
|
+
if (!isReconcileCanvasItemsResponse(data)) {
|
|
9356
|
+
throw new Error("Invalid response from reconcile endpoint");
|
|
9357
|
+
}
|
|
9358
|
+
return data.deleted;
|
|
9359
|
+
}
|
|
9360
|
+
|
|
9361
|
+
// src/services/upload-chat-transcripts.ts
|
|
9362
|
+
import { readdir as readdir4, readFile as readFile11 } from "fs/promises";
|
|
9363
|
+
import { basename, join as join15 } from "path";
|
|
9364
|
+
import { homedir as homedir13 } from "os";
|
|
9365
|
+
var ENGINE_DIR2 = join15(homedir13(), ".replicas", "engine");
|
|
9157
9366
|
var HISTORY_DIRS = [
|
|
9158
|
-
|
|
9159
|
-
|
|
9160
|
-
|
|
9367
|
+
join15(ENGINE_DIR2, "claude-histories"),
|
|
9368
|
+
join15(ENGINE_DIR2, "relay-histories"),
|
|
9369
|
+
join15(ENGINE_DIR2, "codex-histories")
|
|
9161
9370
|
];
|
|
9162
9371
|
async function flushAllChatTranscripts(chatsById = /* @__PURE__ */ new Map()) {
|
|
9163
9372
|
let flushed = 0;
|
|
@@ -9166,7 +9375,7 @@ async function flushAllChatTranscripts(chatsById = /* @__PURE__ */ new Map()) {
|
|
|
9166
9375
|
for (const dir of HISTORY_DIRS) {
|
|
9167
9376
|
let entries;
|
|
9168
9377
|
try {
|
|
9169
|
-
entries = await
|
|
9378
|
+
entries = await readdir4(dir);
|
|
9170
9379
|
} catch {
|
|
9171
9380
|
continue;
|
|
9172
9381
|
}
|
|
@@ -9174,7 +9383,7 @@ async function flushAllChatTranscripts(chatsById = /* @__PURE__ */ new Map()) {
|
|
|
9174
9383
|
if (!entry.endsWith(".jsonl")) continue;
|
|
9175
9384
|
const chatId = basename(entry, ".jsonl");
|
|
9176
9385
|
tasks.push(
|
|
9177
|
-
uploadChatTranscript(chatId,
|
|
9386
|
+
uploadChatTranscript(chatId, join15(dir, entry), chatsById.get(chatId)).then(() => {
|
|
9178
9387
|
flushed++;
|
|
9179
9388
|
}).catch((err) => {
|
|
9180
9389
|
failed++;
|
|
@@ -9187,7 +9396,7 @@ async function flushAllChatTranscripts(chatsById = /* @__PURE__ */ new Map()) {
|
|
|
9187
9396
|
return { flushed, failed };
|
|
9188
9397
|
}
|
|
9189
9398
|
async function uploadChatTranscript(chatId, filePath, chat) {
|
|
9190
|
-
const bytes = await
|
|
9399
|
+
const bytes = await readFile11(filePath);
|
|
9191
9400
|
if (bytes.byteLength === 0) return;
|
|
9192
9401
|
const form = new FormData();
|
|
9193
9402
|
form.append("chat_id", chatId);
|
|
@@ -9244,18 +9453,18 @@ var DuplicateDefaultChatError = class extends Error {
|
|
|
9244
9453
|
};
|
|
9245
9454
|
|
|
9246
9455
|
// src/services/chat/chat-service.ts
|
|
9247
|
-
var ENGINE_DIR3 =
|
|
9248
|
-
var CHATS_FILE =
|
|
9249
|
-
var CLAUDE_HISTORY_DIR =
|
|
9250
|
-
var RELAY_HISTORY_DIR =
|
|
9251
|
-
var CODEX_HISTORY_DIR =
|
|
9456
|
+
var ENGINE_DIR3 = join16(homedir14(), ".replicas", "engine");
|
|
9457
|
+
var CHATS_FILE = join16(ENGINE_DIR3, "chats.json");
|
|
9458
|
+
var CLAUDE_HISTORY_DIR = join16(ENGINE_DIR3, "claude-histories");
|
|
9459
|
+
var RELAY_HISTORY_DIR = join16(ENGINE_DIR3, "relay-histories");
|
|
9460
|
+
var CODEX_HISTORY_DIR = join16(ENGINE_DIR3, "codex-histories");
|
|
9252
9461
|
var HISTORY_DIR_BY_PROVIDER = {
|
|
9253
9462
|
claude: CLAUDE_HISTORY_DIR,
|
|
9254
9463
|
relay: RELAY_HISTORY_DIR,
|
|
9255
9464
|
codex: CODEX_HISTORY_DIR
|
|
9256
9465
|
};
|
|
9257
|
-
var CHAT_SENDERS_DIR =
|
|
9258
|
-
var CODEX_AUTH_PATH2 =
|
|
9466
|
+
var CHAT_SENDERS_DIR = join16(ENGINE_DIR3, "chat-senders");
|
|
9467
|
+
var CODEX_AUTH_PATH2 = join16(homedir14(), ".codex", "auth.json");
|
|
9259
9468
|
var CHATS_BACKUP_FILE = `${CHATS_FILE}.bak`;
|
|
9260
9469
|
function isChatMessageSender(value) {
|
|
9261
9470
|
if (!isRecord4(value)) return false;
|
|
@@ -9453,7 +9662,7 @@ var ChatService = class {
|
|
|
9453
9662
|
};
|
|
9454
9663
|
}
|
|
9455
9664
|
senderFilePath(chatId) {
|
|
9456
|
-
return
|
|
9665
|
+
return join16(CHAT_SENDERS_DIR, `${chatId}.jsonl`);
|
|
9457
9666
|
}
|
|
9458
9667
|
async appendSender(chatId, sender) {
|
|
9459
9668
|
try {
|
|
@@ -9464,7 +9673,7 @@ var ChatService = class {
|
|
|
9464
9673
|
}
|
|
9465
9674
|
async readSenders(chatId) {
|
|
9466
9675
|
try {
|
|
9467
|
-
const content = await
|
|
9676
|
+
const content = await readFile12(this.senderFilePath(chatId), "utf-8");
|
|
9468
9677
|
const lines = content.split("\n").filter((line) => line.trim().length > 0);
|
|
9469
9678
|
const senders = [];
|
|
9470
9679
|
for (const line of lines) {
|
|
@@ -9609,7 +9818,7 @@ var ChatService = class {
|
|
|
9609
9818
|
return descendants;
|
|
9610
9819
|
}
|
|
9611
9820
|
async deleteHistoryFile(persisted) {
|
|
9612
|
-
await rm(
|
|
9821
|
+
await rm(join16(HISTORY_DIR_BY_PROVIDER[persisted.provider], `${persisted.id}.jsonl`), { force: true });
|
|
9613
9822
|
await rm(this.senderFilePath(persisted.id), { force: true });
|
|
9614
9823
|
}
|
|
9615
9824
|
async getChatHistory(chatId) {
|
|
@@ -9647,6 +9856,16 @@ var ChatService = class {
|
|
|
9647
9856
|
[...this.chats.entries()].map(([chatId, chat]) => [chatId, this.toSummary(chat)])
|
|
9648
9857
|
));
|
|
9649
9858
|
}
|
|
9859
|
+
async flushAllWorkspaceArtifacts() {
|
|
9860
|
+
const chatsById = new Map(
|
|
9861
|
+
[...this.chats.entries()].map(([chatId, chat]) => [chatId, this.toSummary(chat)])
|
|
9862
|
+
);
|
|
9863
|
+
const [chatTranscripts, canvas] = await Promise.all([
|
|
9864
|
+
flushAllChatTranscripts(chatsById),
|
|
9865
|
+
flushAllCanvasItems()
|
|
9866
|
+
]);
|
|
9867
|
+
return { chatTranscripts, canvas };
|
|
9868
|
+
}
|
|
9650
9869
|
createRuntimeChat(persisted) {
|
|
9651
9870
|
const saveSession = async (sessionId) => {
|
|
9652
9871
|
persisted.providerSessionId = sessionId;
|
|
@@ -9670,7 +9889,7 @@ var ChatService = class {
|
|
|
9670
9889
|
if (persisted.provider === "claude") {
|
|
9671
9890
|
provider = new ClaudeManager({
|
|
9672
9891
|
workingDirectory: this.workingDirectory,
|
|
9673
|
-
historyFilePath:
|
|
9892
|
+
historyFilePath: join16(CLAUDE_HISTORY_DIR, `${persisted.id}.jsonl`),
|
|
9674
9893
|
initialSessionId: persisted.providerSessionId,
|
|
9675
9894
|
onSaveSessionId: saveSession,
|
|
9676
9895
|
onTurnComplete: onProviderTurnComplete,
|
|
@@ -9679,7 +9898,7 @@ var ChatService = class {
|
|
|
9679
9898
|
} else if (persisted.provider === "relay") {
|
|
9680
9899
|
provider = new RelayManager({
|
|
9681
9900
|
workingDirectory: this.workingDirectory,
|
|
9682
|
-
historyFilePath:
|
|
9901
|
+
historyFilePath: join16(RELAY_HISTORY_DIR, `${persisted.id}.jsonl`),
|
|
9683
9902
|
initialSessionId: persisted.providerSessionId,
|
|
9684
9903
|
onSaveSessionId: saveSession,
|
|
9685
9904
|
onTurnComplete: onProviderTurnComplete,
|
|
@@ -9690,7 +9909,7 @@ var ChatService = class {
|
|
|
9690
9909
|
} else {
|
|
9691
9910
|
provider = new CodexAspManager({
|
|
9692
9911
|
workingDirectory: this.workingDirectory,
|
|
9693
|
-
historyFilePath:
|
|
9912
|
+
historyFilePath: join16(CODEX_HISTORY_DIR, `${persisted.id}.jsonl`),
|
|
9694
9913
|
initialSessionId: persisted.providerSessionId,
|
|
9695
9914
|
onSaveSessionId: saveSession,
|
|
9696
9915
|
onTurnComplete: onProviderTurnComplete,
|
|
@@ -9828,11 +10047,14 @@ var ChatService = class {
|
|
|
9828
10047
|
});
|
|
9829
10048
|
uploadChatTranscript(
|
|
9830
10049
|
chatId,
|
|
9831
|
-
|
|
10050
|
+
join16(HISTORY_DIR_BY_PROVIDER[chat.persisted.provider], `${chatId}.jsonl`),
|
|
9832
10051
|
this.toSummary(chat)
|
|
9833
10052
|
).catch((err) => {
|
|
9834
10053
|
console.error("[ChatService] Failed to upload chat transcript:", { chatId, err });
|
|
9835
10054
|
});
|
|
10055
|
+
flushAllCanvasItems().catch((err) => {
|
|
10056
|
+
console.error("[ChatService] Failed to upload canvas items:", { chatId, err });
|
|
10057
|
+
});
|
|
9836
10058
|
await this.publishAgentTurnCompleteWebhook(chat);
|
|
9837
10059
|
}
|
|
9838
10060
|
getRuntimeChat(chatId) {
|
|
@@ -9840,7 +10062,7 @@ var ChatService = class {
|
|
|
9840
10062
|
}
|
|
9841
10063
|
async loadChats() {
|
|
9842
10064
|
try {
|
|
9843
|
-
const content = await
|
|
10065
|
+
const content = await readFile12(CHATS_FILE, "utf-8");
|
|
9844
10066
|
return parsePersistedChatsContent(content);
|
|
9845
10067
|
} catch (error) {
|
|
9846
10068
|
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
@@ -9855,7 +10077,7 @@ var ChatService = class {
|
|
|
9855
10077
|
console.error("[ChatService] Failed to quarantine corrupt chats file:", renameError);
|
|
9856
10078
|
}
|
|
9857
10079
|
try {
|
|
9858
|
-
const backupContent = await
|
|
10080
|
+
const backupContent = await readFile12(CHATS_BACKUP_FILE, "utf-8");
|
|
9859
10081
|
return parsePersistedChatsContent(backupContent);
|
|
9860
10082
|
} catch (backupError) {
|
|
9861
10083
|
if (backupError && typeof backupError === "object" && "code" in backupError && backupError.code === "ENOENT") {
|
|
@@ -9940,8 +10162,8 @@ var ChatService = class {
|
|
|
9940
10162
|
|
|
9941
10163
|
// src/services/repo-file-service.ts
|
|
9942
10164
|
import { execFile as execFile2 } from "child_process";
|
|
9943
|
-
import { readFile as
|
|
9944
|
-
import { join as
|
|
10165
|
+
import { readFile as readFile13, realpath, stat as stat3 } from "fs/promises";
|
|
10166
|
+
import { join as join17, resolve, extname } from "path";
|
|
9945
10167
|
var CACHE_TTL_MS = 3e4;
|
|
9946
10168
|
var SEARCH_TIMEOUT_MS = 15e3;
|
|
9947
10169
|
var MAX_CONTENT_BYTES = 256 * 1024;
|
|
@@ -10001,11 +10223,11 @@ function scoreMatch(query2, filePath) {
|
|
|
10001
10223
|
const lowerQuery = query2.toLowerCase();
|
|
10002
10224
|
const lowerPath = filePath.toLowerCase();
|
|
10003
10225
|
const segments = lowerPath.split("/");
|
|
10004
|
-
const
|
|
10005
|
-
if (
|
|
10006
|
-
if (
|
|
10226
|
+
const basename2 = segments[segments.length - 1] ?? "";
|
|
10227
|
+
if (basename2 === lowerQuery) return 100;
|
|
10228
|
+
if (basename2.startsWith(lowerQuery)) return 90;
|
|
10007
10229
|
if (segments.some((seg) => seg === lowerQuery)) return 80;
|
|
10008
|
-
if (
|
|
10230
|
+
if (basename2.includes(lowerQuery)) return 70;
|
|
10009
10231
|
if (segments.some((seg) => seg.includes(lowerQuery))) return 60;
|
|
10010
10232
|
if (lowerPath.includes(lowerQuery)) return 50;
|
|
10011
10233
|
return 0;
|
|
@@ -10101,11 +10323,11 @@ var RepoFileService = class {
|
|
|
10101
10323
|
const repo = repos.find((r) => r.name === repoName);
|
|
10102
10324
|
if (!repo) return null;
|
|
10103
10325
|
try {
|
|
10104
|
-
const fullPath = await realpath(resolve(
|
|
10326
|
+
const fullPath = await realpath(resolve(join17(repo.path, filePath)));
|
|
10105
10327
|
const repoRoot = await realpath(repo.path);
|
|
10106
10328
|
const repoPrefix = repoRoot.endsWith("/") ? repoRoot : repoRoot + "/";
|
|
10107
10329
|
if (!fullPath.startsWith(repoPrefix) && fullPath !== repoRoot) return null;
|
|
10108
|
-
const fileStat = await
|
|
10330
|
+
const fileStat = await stat3(fullPath);
|
|
10109
10331
|
if (!fileStat.isFile()) return null;
|
|
10110
10332
|
const sizeBytes = fileStat.size;
|
|
10111
10333
|
if (isBinaryExtension(filePath)) {
|
|
@@ -10130,7 +10352,7 @@ var RepoFileService = class {
|
|
|
10130
10352
|
tooLarge: true
|
|
10131
10353
|
};
|
|
10132
10354
|
}
|
|
10133
|
-
const content = await
|
|
10355
|
+
const content = await readFile13(fullPath, "utf-8");
|
|
10134
10356
|
return {
|
|
10135
10357
|
repoName,
|
|
10136
10358
|
path: filePath,
|
|
@@ -10211,86 +10433,6 @@ import { z as z2 } from "zod";
|
|
|
10211
10433
|
import { readdir as readdir6, stat as stat4, readFile as readFile16 } from "fs/promises";
|
|
10212
10434
|
import { join as join20, resolve as resolve2 } from "path";
|
|
10213
10435
|
|
|
10214
|
-
// src/services/canvas-service.ts
|
|
10215
|
-
import { readdir as readdir4, readFile as readFile13, stat as stat3 } from "fs/promises";
|
|
10216
|
-
import { homedir as homedir14 } from "os";
|
|
10217
|
-
import { basename as basename2, join as join17 } from "path";
|
|
10218
|
-
var CANVAS_DIRECTORIES = [
|
|
10219
|
-
join17(homedir14(), ".claude", "plans"),
|
|
10220
|
-
join17(homedir14(), ".replicas", "canvas")
|
|
10221
|
-
];
|
|
10222
|
-
var MAX_CANVAS_FILE_BYTES = 5 * 1024 * 1024;
|
|
10223
|
-
function isTextKind(kind) {
|
|
10224
|
-
return kind === "markdown" || kind === "html";
|
|
10225
|
-
}
|
|
10226
|
-
function sanitizeFilename(filename) {
|
|
10227
|
-
return basename2(filename);
|
|
10228
|
-
}
|
|
10229
|
-
var CanvasService = class {
|
|
10230
|
-
async listItems() {
|
|
10231
|
-
const items = /* @__PURE__ */ new Map();
|
|
10232
|
-
for (const directory of CANVAS_DIRECTORIES) {
|
|
10233
|
-
let entries;
|
|
10234
|
-
try {
|
|
10235
|
-
entries = await readdir4(directory, { withFileTypes: true });
|
|
10236
|
-
} catch {
|
|
10237
|
-
continue;
|
|
10238
|
-
}
|
|
10239
|
-
for (const entry of entries) {
|
|
10240
|
-
if (!entry.isFile()) continue;
|
|
10241
|
-
if (entry.name.startsWith(".")) continue;
|
|
10242
|
-
if (items.has(entry.name)) continue;
|
|
10243
|
-
const { kind } = classifyCanvasFilename(entry.name);
|
|
10244
|
-
let sizeBytes = 0;
|
|
10245
|
-
try {
|
|
10246
|
-
const s = await stat3(join17(directory, entry.name));
|
|
10247
|
-
sizeBytes = s.size;
|
|
10248
|
-
} catch {
|
|
10249
|
-
continue;
|
|
10250
|
-
}
|
|
10251
|
-
items.set(entry.name, { filename: entry.name, kind, sizeBytes });
|
|
10252
|
-
}
|
|
10253
|
-
}
|
|
10254
|
-
return Array.from(items.values()).sort((a, b) => a.filename.localeCompare(b.filename));
|
|
10255
|
-
}
|
|
10256
|
-
async getItem(filename) {
|
|
10257
|
-
const safe = sanitizeFilename(filename);
|
|
10258
|
-
if (!safe || safe !== filename || safe.startsWith(".")) return null;
|
|
10259
|
-
const { kind, mimeType } = classifyCanvasFilename(safe);
|
|
10260
|
-
for (const directory of CANVAS_DIRECTORIES) {
|
|
10261
|
-
const filePath = join17(directory, safe);
|
|
10262
|
-
let sizeBytes = 0;
|
|
10263
|
-
try {
|
|
10264
|
-
const s = await stat3(filePath);
|
|
10265
|
-
sizeBytes = s.size;
|
|
10266
|
-
} catch {
|
|
10267
|
-
continue;
|
|
10268
|
-
}
|
|
10269
|
-
if (sizeBytes > MAX_CANVAS_FILE_BYTES) {
|
|
10270
|
-
return {
|
|
10271
|
-
filename: safe,
|
|
10272
|
-
kind,
|
|
10273
|
-
sizeBytes,
|
|
10274
|
-
mimeType,
|
|
10275
|
-
tooLarge: true
|
|
10276
|
-
};
|
|
10277
|
-
}
|
|
10278
|
-
try {
|
|
10279
|
-
if (isTextKind(kind)) {
|
|
10280
|
-
const content = await readFile13(filePath, "utf-8");
|
|
10281
|
-
return { filename: safe, kind, sizeBytes, mimeType, content };
|
|
10282
|
-
}
|
|
10283
|
-
const buf = await readFile13(filePath);
|
|
10284
|
-
return { filename: safe, kind, sizeBytes, mimeType, base64: buf.toString("base64") };
|
|
10285
|
-
} catch {
|
|
10286
|
-
continue;
|
|
10287
|
-
}
|
|
10288
|
-
}
|
|
10289
|
-
return null;
|
|
10290
|
-
}
|
|
10291
|
-
};
|
|
10292
|
-
var canvasService = new CanvasService();
|
|
10293
|
-
|
|
10294
10436
|
// src/services/warm-hooks-service.ts
|
|
10295
10437
|
import { spawn as spawn4 } from "child_process";
|
|
10296
10438
|
import { readFile as readFile15 } from "fs/promises";
|
|
@@ -10907,6 +11049,17 @@ function createV1Routes(deps) {
|
|
|
10907
11049
|
);
|
|
10908
11050
|
}
|
|
10909
11051
|
});
|
|
11052
|
+
app2.post("/workspace-artifacts/flush-all", async (c) => {
|
|
11053
|
+
try {
|
|
11054
|
+
const result = await deps.chatService.flushAllWorkspaceArtifacts();
|
|
11055
|
+
return c.json(result);
|
|
11056
|
+
} catch (error) {
|
|
11057
|
+
return c.json(
|
|
11058
|
+
jsonError("Failed to flush workspace artifacts", error instanceof Error ? error.message : "Unknown error"),
|
|
11059
|
+
500
|
|
11060
|
+
);
|
|
11061
|
+
}
|
|
11062
|
+
});
|
|
10910
11063
|
app2.get("/chats/:chatId/queue", (c) => {
|
|
10911
11064
|
try {
|
|
10912
11065
|
const result = deps.chatService.getChatQueue(c.req.param("chatId"));
|