replicas-engine 0.1.338 → 0.1.339
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/{chunk-MHJJJ2VP.js → chunk-6NGTYZPX.js} +377 -102
- package/dist/src/{chunk-B5KEKENG.js → chunk-A6ZHMNK4.js} +12 -12
- package/dist/src/{chunk-C6IOMGXW.js → chunk-B7L4ZPGB.js} +33 -33
- package/dist/src/{chunk-3YYBZ6D5.js → chunk-EB4AJ2V6.js} +11 -11
- package/dist/src/{chunk-YY2DS5UJ.js → chunk-JWM4YXMZ.js} +55 -54
- package/dist/src/{chunk-ATF5CAZW.js → chunk-QE4MMXQA.js} +80 -72
- package/dist/src/{dist-es-37YNNMWK.js → dist-es-4XW324ME.js} +17 -17
- package/dist/src/{dist-es-M4ZCYBRD.js → dist-es-6PMBQ4JI.js} +7 -7
- package/dist/src/{dist-es-YA3RZ3J2.js → dist-es-GB67RX6P.js} +43 -35
- package/dist/src/{dist-es-7IBRAXHQ.js → dist-es-OTROFLRA.js} +9 -9
- package/dist/src/{dist-es-MNGVDEEO.js → dist-es-VF5HSV72.js} +4 -4
- package/dist/src/{dist-es-JSZE2H6Y.js → dist-es-VJJ2WNNG.js} +22 -19
- package/dist/src/{dist-es-AYVDCHRQ.js → dist-es-XMRJV7XN.js} +7 -7
- package/dist/src/{event-streams-7ZLUNSFX.js → event-streams-3JEJVYQR.js} +17 -17
- package/dist/src/index.js +573 -100
- package/dist/src/{loadSso-2A75N7C3.js → loadSso-QDS4L4DG.js} +19 -19
- package/dist/src/{signin-FXO7O6JB.js → signin-EETD6KX2.js} +155 -77
- package/dist/src/{sso-oidc-4IFMUO6A.js → sso-oidc-25HRWVRW.js} +21 -21
- package/dist/src/{sts-IAUEUXEW.js → sts-2O2TZS6K.js} +434 -45
- package/package.json +5 -1
- package/scripts/opencode +36 -0
package/dist/src/index.js
CHANGED
|
@@ -15,7 +15,7 @@ function isRecord(value) {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
// ../shared/src/agent.ts
|
|
18
|
-
var VALID_AGENT_PROVIDERS = ["claude", "codex", "cursor", "relay"];
|
|
18
|
+
var VALID_AGENT_PROVIDERS = ["claude", "codex", "cursor", "opencode", "relay"];
|
|
19
19
|
var VALID_CODING_AGENT_PROVIDERS = VALID_AGENT_PROVIDERS.filter(
|
|
20
20
|
(provider) => provider !== "relay"
|
|
21
21
|
);
|
|
@@ -295,7 +295,7 @@ var WORKSPACE_SIZES = ["small", "large"];
|
|
|
295
295
|
var INVALID_WORKSPACE_SIZE_ERROR = `Invalid size: must be one of ${WORKSPACE_SIZES.join(", ")}`;
|
|
296
296
|
|
|
297
297
|
// ../shared/src/e2b.ts
|
|
298
|
-
var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-06-
|
|
298
|
+
var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-06-23-v1";
|
|
299
299
|
|
|
300
300
|
// ../shared/src/runtime-env.ts
|
|
301
301
|
function parsePosixEnvFile(content) {
|
|
@@ -496,16 +496,13 @@ scrot /tmp/state.png
|
|
|
496
496
|
replicas computer info
|
|
497
497
|
|
|
498
498
|
# 2) Launch a browser on the workspace display.
|
|
499
|
-
replicas computer launch chrome
|
|
499
|
+
replicas computer launch chrome https://news.ycombinator.com
|
|
500
500
|
|
|
501
501
|
# 3) Take a screenshot so you can see what's there.
|
|
502
502
|
replicas computer screenshot /tmp/state.png
|
|
503
503
|
# (Read the PNG yourself before deciding where to click.)
|
|
504
504
|
|
|
505
505
|
# 4) Drive the UI.
|
|
506
|
-
replicas computer key ctrl+l # focus address bar
|
|
507
|
-
replicas computer type "https://news.ycombinator.com"
|
|
508
|
-
replicas computer key Return
|
|
509
506
|
replicas computer click 521 700 # click coordinates from the screenshot
|
|
510
507
|
replicas computer scroll down --amount 5
|
|
511
508
|
|
|
@@ -558,6 +555,7 @@ Spawns an app on the workspace display. Built-in aliases:
|
|
|
558
555
|
- \`notepad\` / \`editor\` - mousepad (lightweight GTK text editor)
|
|
559
556
|
- \`files\` / \`filemanager\` - thunar (file manager)
|
|
560
557
|
Anything else gets \`exec\`'d verbatim, so \`replicas computer launch xeyes\` works if xeyes is installed.
|
|
558
|
+
When opening a known page, prefer passing the URL directly: \`replicas computer launch chrome http://localhost:3000/\`.
|
|
561
559
|
|
|
562
560
|
### \`replicas computer record start <path> [--fps N]\`
|
|
563
561
|
Starts an ffmpeg screen recorder. Output is a fragmented MP4 (still playable if the workspace dies mid-record). Default 60fps; drop to 30 if the workspace is CPU-constrained.
|
|
@@ -583,6 +581,8 @@ replicas computer screenshot /tmp/after-click.png
|
|
|
583
581
|
|
|
584
582
|
### Typing into an address bar
|
|
585
583
|
\`\`\`bash
|
|
584
|
+
replicas computer launch chrome
|
|
585
|
+
sleep 2
|
|
586
586
|
replicas computer key ctrl+l # focus address bar
|
|
587
587
|
replicas computer type "https://example.com"
|
|
588
588
|
replicas computer key Return
|
|
@@ -2177,12 +2177,14 @@ var DEFAULT_CHAT_TITLES = {
|
|
|
2177
2177
|
claude: "Claude Code",
|
|
2178
2178
|
codex: "Codex",
|
|
2179
2179
|
cursor: "Cursor",
|
|
2180
|
+
opencode: "Opencode",
|
|
2180
2181
|
relay: "Relay"
|
|
2181
2182
|
};
|
|
2182
2183
|
var CLAUDE_OPUS_1M_MODEL = "opus[1m]";
|
|
2183
2184
|
var LEGACY_CLAUDE_OPUS_1M_MODEL = "opus-1m";
|
|
2184
2185
|
var DEFAULT_CODEX_MODEL = "gpt-5.5";
|
|
2185
2186
|
var DEFAULT_CURSOR_MODEL = "composer-2";
|
|
2187
|
+
var DEFAULT_OPENCODE_MODEL = "z-ai/glm-5.2";
|
|
2186
2188
|
function normalizeClaudeModel(model) {
|
|
2187
2189
|
if (model === LEGACY_CLAUDE_OPUS_1M_MODEL) {
|
|
2188
2190
|
return CLAUDE_OPUS_1M_MODEL;
|
|
@@ -2193,6 +2195,7 @@ var AGENT_MODELS = {
|
|
|
2193
2195
|
claude: [CLAUDE_OPUS_1M_MODEL, "sonnet", "haiku"],
|
|
2194
2196
|
codex: [DEFAULT_CODEX_MODEL, "gpt-5.4", "gpt-5.4-mini", "gpt-5.3-codex", "gpt-5.2"],
|
|
2195
2197
|
cursor: [DEFAULT_CURSOR_MODEL, "composer-2.5"],
|
|
2198
|
+
opencode: [DEFAULT_OPENCODE_MODEL, "minimax/minimax-m3", "xiaomi/mimo-v2.5-pro", "moonshotai/kimi-k2.6:free"],
|
|
2196
2199
|
relay: [CLAUDE_OPUS_1M_MODEL, "sonnet"]
|
|
2197
2200
|
};
|
|
2198
2201
|
var MODEL_LABELS = {
|
|
@@ -2203,7 +2206,11 @@ var MODEL_LABELS = {
|
|
|
2203
2206
|
haiku: "Haiku 4.5",
|
|
2204
2207
|
[DEFAULT_CODEX_MODEL]: "GPT-5.5",
|
|
2205
2208
|
[DEFAULT_CURSOR_MODEL]: "Composer 2",
|
|
2209
|
+
[DEFAULT_OPENCODE_MODEL]: "GLM 5.2 via OpenRouter",
|
|
2206
2210
|
"composer-2.5": "Composer 2.5",
|
|
2211
|
+
"minimax/minimax-m3": "MiniMax M3 via OpenRouter",
|
|
2212
|
+
"xiaomi/mimo-v2.5-pro": "MiMo V2.5 Pro via OpenRouter",
|
|
2213
|
+
"moonshotai/kimi-k2.6:free": "Kimi K2.6 via OpenRouter",
|
|
2207
2214
|
"gpt-5.4": "GPT-5.4",
|
|
2208
2215
|
"gpt-5.4-mini": "GPT-5.4 Mini",
|
|
2209
2216
|
"gpt-5.3-codex": "GPT-5.3 Codex",
|
|
@@ -2877,6 +2884,7 @@ function loadEngineEnv() {
|
|
|
2877
2884
|
ANTHROPIC_API_KEY: readEnv("ANTHROPIC_API_KEY"),
|
|
2878
2885
|
OPENAI_API_KEY: readEnv("OPENAI_API_KEY"),
|
|
2879
2886
|
CURSOR_API_KEY: readEnv("CURSOR_API_KEY"),
|
|
2887
|
+
OPENROUTER_API_KEY: readEnv("OPENROUTER_API_KEY"),
|
|
2880
2888
|
CLAUDE_CODE_USE_BEDROCK: readEnv("CLAUDE_CODE_USE_BEDROCK"),
|
|
2881
2889
|
AWS_ACCESS_KEY_ID: readEnv("AWS_ACCESS_KEY_ID"),
|
|
2882
2890
|
AWS_SECRET_ACCESS_KEY: readEnv("AWS_SECRET_ACCESS_KEY"),
|
|
@@ -4335,6 +4343,7 @@ var REPLICAS_DIR = join7(homedir5(), ".replicas");
|
|
|
4335
4343
|
var DETAILS_FILE = join7(REPLICAS_DIR, "environment-details.json");
|
|
4336
4344
|
var CLAUDE_CREDENTIALS_PATH = join7(homedir5(), ".claude", ".credentials.json");
|
|
4337
4345
|
var CODEX_AUTH_PATH = join7(homedir5(), ".codex", "auth.json");
|
|
4346
|
+
var OPENCODE_AUTH_PATH = join7(homedir5(), ".local", "share", "opencode", "auth.json");
|
|
4338
4347
|
var GH_HOSTS_PATH = join7(homedir5(), ".config", "gh", "hosts.yml");
|
|
4339
4348
|
function detectClaudeAuthMethod() {
|
|
4340
4349
|
if (existsSync3(CLAUDE_CREDENTIALS_PATH)) {
|
|
@@ -4360,6 +4369,9 @@ function detectCodexAuthMethod() {
|
|
|
4360
4369
|
function detectCursorAuthMethod() {
|
|
4361
4370
|
return ENGINE_ENV.CURSOR_API_KEY ? "api_key" : "none";
|
|
4362
4371
|
}
|
|
4372
|
+
function detectOpencodeAuthMethod() {
|
|
4373
|
+
return existsSync3(OPENCODE_AUTH_PATH) || ENGINE_ENV.OPENROUTER_API_KEY ? "api_key" : "none";
|
|
4374
|
+
}
|
|
4363
4375
|
async function detectGitIdentityConfigured() {
|
|
4364
4376
|
try {
|
|
4365
4377
|
const { stdout } = await execFileAsync("git", ["config", "--global", "user.name"]);
|
|
@@ -4400,6 +4412,7 @@ function createDefaultDetails() {
|
|
|
4400
4412
|
claudeAuthMethod: "none",
|
|
4401
4413
|
codexAuthMethod: "none",
|
|
4402
4414
|
cursorAuthMethod: "none",
|
|
4415
|
+
opencodeAuthMethod: "none",
|
|
4403
4416
|
lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4404
4417
|
};
|
|
4405
4418
|
}
|
|
@@ -4431,6 +4444,7 @@ var EnvironmentDetailsService = class {
|
|
|
4431
4444
|
details.claudeAuthMethod = detectClaudeAuthMethod();
|
|
4432
4445
|
details.codexAuthMethod = detectCodexAuthMethod();
|
|
4433
4446
|
details.cursorAuthMethod = detectCursorAuthMethod();
|
|
4447
|
+
details.opencodeAuthMethod = detectOpencodeAuthMethod();
|
|
4434
4448
|
details.gitIdentityConfigured = gitIdentityConfigured;
|
|
4435
4449
|
const ghConfigured = existsSync3(GH_HOSTS_PATH);
|
|
4436
4450
|
details.githubAccessConfigured = ghConfigured;
|
|
@@ -5179,9 +5193,9 @@ async function registerDesktopPreview() {
|
|
|
5179
5193
|
|
|
5180
5194
|
// src/services/chat/chat-service.ts
|
|
5181
5195
|
import { existsSync as existsSync7 } from "fs";
|
|
5182
|
-
import { appendFile as appendFile3, copyFile, mkdir as
|
|
5196
|
+
import { appendFile as appendFile3, copyFile, mkdir as mkdir13, readFile as readFile13, rename as rename2, rm } from "fs/promises";
|
|
5183
5197
|
import { homedir as homedir14 } from "os";
|
|
5184
|
-
import { join as
|
|
5198
|
+
import { join as join19 } from "path";
|
|
5185
5199
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
5186
5200
|
|
|
5187
5201
|
// src/managers/claude-manager.ts
|
|
@@ -5745,6 +5759,21 @@ var CodingAgentManager = class {
|
|
|
5745
5759
|
payload
|
|
5746
5760
|
});
|
|
5747
5761
|
}
|
|
5762
|
+
recordHistoryEvent(type, payload, historyFile) {
|
|
5763
|
+
const eventPayload = {};
|
|
5764
|
+
if (payload && typeof payload === "object") {
|
|
5765
|
+
Object.assign(eventPayload, payload);
|
|
5766
|
+
} else {
|
|
5767
|
+
eventPayload.value = payload;
|
|
5768
|
+
}
|
|
5769
|
+
const event = {
|
|
5770
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5771
|
+
type,
|
|
5772
|
+
payload: eventPayload
|
|
5773
|
+
};
|
|
5774
|
+
this.onEvent(event);
|
|
5775
|
+
historyFile.append(event);
|
|
5776
|
+
}
|
|
5748
5777
|
initializeManager(processMessage) {
|
|
5749
5778
|
this.messageQueue = new MessageQueueService(processMessage);
|
|
5750
5779
|
this.initialized = this.initialize();
|
|
@@ -7508,7 +7537,7 @@ var AspClient = class {
|
|
|
7508
7537
|
// src/managers/codex-asp/app-server-process.ts
|
|
7509
7538
|
var DEFAULT_CODEX_BINARY = "codex";
|
|
7510
7539
|
var DEFAULT_CODEX_ARGS = ["app-server", "--listen", "stdio://"];
|
|
7511
|
-
var ENGINE_PACKAGE_VERSION = "0.1.
|
|
7540
|
+
var ENGINE_PACKAGE_VERSION = "0.1.339";
|
|
7512
7541
|
var INITIALIZE_METHOD = "initialize";
|
|
7513
7542
|
var INITIALIZED_NOTIFICATION = "initialized";
|
|
7514
7543
|
var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
|
|
@@ -9332,7 +9361,7 @@ var CursorManager = class extends CodingAgentManager {
|
|
|
9332
9361
|
this.recordHistoryEvent("event_msg", {
|
|
9333
9362
|
type: "user_message",
|
|
9334
9363
|
message: request.message
|
|
9335
|
-
});
|
|
9364
|
+
}, this.historyFile);
|
|
9336
9365
|
const run = await agent.send(message, {
|
|
9337
9366
|
model: { id: request.model ?? DEFAULT_CURSOR_MODEL },
|
|
9338
9367
|
mode: request.planMode ? "plan" : "agent"
|
|
@@ -9347,13 +9376,13 @@ var CursorManager = class extends CodingAgentManager {
|
|
|
9347
9376
|
type: "error",
|
|
9348
9377
|
message: result.result || "Cursor run failed",
|
|
9349
9378
|
runId: result.id
|
|
9350
|
-
});
|
|
9379
|
+
}, this.historyFile);
|
|
9351
9380
|
}
|
|
9352
9381
|
} catch (error) {
|
|
9353
9382
|
this.recordHistoryEvent("cursor-error", {
|
|
9354
9383
|
type: "error",
|
|
9355
9384
|
message: error instanceof Error ? error.message : String(error)
|
|
9356
|
-
});
|
|
9385
|
+
}, this.historyFile);
|
|
9357
9386
|
} finally {
|
|
9358
9387
|
this.activeRun = null;
|
|
9359
9388
|
await this.historyFile.flush();
|
|
@@ -9374,22 +9403,431 @@ var CursorManager = class extends CodingAgentManager {
|
|
|
9374
9403
|
};
|
|
9375
9404
|
}
|
|
9376
9405
|
recordCursorEvent(event) {
|
|
9377
|
-
this.recordHistoryEvent(`cursor-${event.type}`, event);
|
|
9406
|
+
this.recordHistoryEvent(`cursor-${event.type}`, event, this.historyFile);
|
|
9378
9407
|
}
|
|
9379
|
-
|
|
9380
|
-
|
|
9381
|
-
|
|
9382
|
-
|
|
9383
|
-
|
|
9384
|
-
|
|
9408
|
+
};
|
|
9409
|
+
|
|
9410
|
+
// src/managers/opencode-manager.ts
|
|
9411
|
+
import { mkdir as mkdir12, readFile as readFile10 } from "fs/promises";
|
|
9412
|
+
import { delimiter, dirname as dirname6, join as join16 } from "path";
|
|
9413
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
9414
|
+
import { fileURLToPath } from "url";
|
|
9415
|
+
import { Agent } from "undici";
|
|
9416
|
+
import {
|
|
9417
|
+
createOpencodeClient,
|
|
9418
|
+
createOpencodeServer
|
|
9419
|
+
} from "@opencode-ai/sdk/v2";
|
|
9420
|
+
var OPENCODE_SHIM_DIR = dirname6(fileURLToPath(new URL("../../scripts/opencode", import.meta.url)));
|
|
9421
|
+
var OPENCODE_CONFIG_PATH = join16(ENGINE_ENV.HOME_DIR, ".config", "opencode", "opencode.json");
|
|
9422
|
+
var OPENCODE_FETCH_DISPATCHER = new Agent({ headersTimeout: 0, bodyTimeout: 0 });
|
|
9423
|
+
var OPENCODE_WORKSPACE_PERMISSION = "allow";
|
|
9424
|
+
var OPENCODE_VARIANT_CANDIDATES_BY_THINKING_LEVEL = {
|
|
9425
|
+
low: ["low"],
|
|
9426
|
+
medium: ["medium"],
|
|
9427
|
+
high: ["high"],
|
|
9428
|
+
max: ["max", "xhigh", "high"]
|
|
9429
|
+
};
|
|
9430
|
+
async function opencodeConfig(model) {
|
|
9431
|
+
const models = getConfiguredOpencodeModels(model);
|
|
9432
|
+
const mcp = await readProvisionedOpencodeMcpConfig();
|
|
9433
|
+
const config = {
|
|
9434
|
+
enabled_providers: ["openrouter"],
|
|
9435
|
+
model: `openrouter/${model}`,
|
|
9436
|
+
provider: {
|
|
9437
|
+
openrouter: {
|
|
9438
|
+
models: Object.fromEntries(models.map((candidate) => [candidate, {}])),
|
|
9439
|
+
options: {
|
|
9440
|
+
apiKey: "{env:OPENROUTER_API_KEY}"
|
|
9441
|
+
}
|
|
9442
|
+
}
|
|
9443
|
+
},
|
|
9444
|
+
permission: OPENCODE_WORKSPACE_PERMISSION,
|
|
9445
|
+
share: "disabled"
|
|
9446
|
+
};
|
|
9447
|
+
if (mcp && Object.keys(mcp).length > 0) {
|
|
9448
|
+
config.mcp = mcp;
|
|
9449
|
+
}
|
|
9450
|
+
return config;
|
|
9451
|
+
}
|
|
9452
|
+
function getConfiguredOpencodeModels(model) {
|
|
9453
|
+
return [.../* @__PURE__ */ new Set([model, ...AGENT_MODELS.opencode])];
|
|
9454
|
+
}
|
|
9455
|
+
function isOpencodePart(value) {
|
|
9456
|
+
return typeof value === "object" && value !== null && "type" in value && typeof value.type === "string";
|
|
9457
|
+
}
|
|
9458
|
+
function isStringRecord(value) {
|
|
9459
|
+
return isRecord4(value) && !Array.isArray(value) && Object.values(value).every((item) => typeof item === "string");
|
|
9460
|
+
}
|
|
9461
|
+
function isStringArray(value) {
|
|
9462
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
9463
|
+
}
|
|
9464
|
+
function isOpencodeMcpEntry(value) {
|
|
9465
|
+
if (!isRecord4(value) || Array.isArray(value)) return false;
|
|
9466
|
+
if (typeof value.enabled === "boolean" && typeof value.type !== "string") return true;
|
|
9467
|
+
if (value.type === "local") {
|
|
9468
|
+
return isStringArray(value.command) && (value.environment === void 0 || isStringRecord(value.environment));
|
|
9469
|
+
}
|
|
9470
|
+
if (value.type === "remote") {
|
|
9471
|
+
return typeof value.url === "string" && (value.headers === void 0 || isStringRecord(value.headers));
|
|
9472
|
+
}
|
|
9473
|
+
return false;
|
|
9474
|
+
}
|
|
9475
|
+
async function readProvisionedOpencodeMcpConfig() {
|
|
9476
|
+
let raw;
|
|
9477
|
+
try {
|
|
9478
|
+
raw = await readFile10(OPENCODE_CONFIG_PATH, "utf8");
|
|
9479
|
+
} catch (error) {
|
|
9480
|
+
if (isRecord4(error) && error.code === "ENOENT") return void 0;
|
|
9481
|
+
console.error("[OpencodeManager] Failed to read Opencode config:", error);
|
|
9482
|
+
return void 0;
|
|
9483
|
+
}
|
|
9484
|
+
try {
|
|
9485
|
+
const parsed = JSON.parse(raw);
|
|
9486
|
+
if (!isRecord4(parsed) || !isRecord4(parsed.mcp) || Array.isArray(parsed.mcp)) return void 0;
|
|
9487
|
+
const mcp = {};
|
|
9488
|
+
for (const [name, entry] of Object.entries(parsed.mcp)) {
|
|
9489
|
+
if (isOpencodeMcpEntry(entry)) mcp[name] = entry;
|
|
9385
9490
|
}
|
|
9386
|
-
|
|
9387
|
-
|
|
9388
|
-
|
|
9389
|
-
|
|
9491
|
+
return mcp;
|
|
9492
|
+
} catch (error) {
|
|
9493
|
+
console.error("[OpencodeManager] Failed to parse Opencode config:", error);
|
|
9494
|
+
return void 0;
|
|
9495
|
+
}
|
|
9496
|
+
}
|
|
9497
|
+
function timestampMs(value) {
|
|
9498
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
9499
|
+
if (typeof value === "string") {
|
|
9500
|
+
const parsed = Date.parse(value);
|
|
9501
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
9502
|
+
}
|
|
9503
|
+
return Date.now();
|
|
9504
|
+
}
|
|
9505
|
+
function isOpencodeRuntimeEvent(value) {
|
|
9506
|
+
return isRecord4(value) && typeof value.type === "string";
|
|
9507
|
+
}
|
|
9508
|
+
function opencodeEventPayload(event) {
|
|
9509
|
+
if ("properties" in event && isRecord4(event.properties)) return event.properties;
|
|
9510
|
+
if ("data" in event && isRecord4(event.data)) return event.data;
|
|
9511
|
+
return null;
|
|
9512
|
+
}
|
|
9513
|
+
function opencodeErrorMessage(error) {
|
|
9514
|
+
if (error instanceof Error) return error.message;
|
|
9515
|
+
if (typeof error === "string") return error;
|
|
9516
|
+
if (!isRecord4(error)) return void 0;
|
|
9517
|
+
if (typeof error.message === "string") return error.message;
|
|
9518
|
+
if (typeof error.name === "string") return error.name;
|
|
9519
|
+
if ("error" in error) return opencodeErrorMessage(error.error);
|
|
9520
|
+
if ("data" in error) return opencodeErrorMessage(error.data);
|
|
9521
|
+
if ("body" in error) return opencodeErrorMessage(error.body);
|
|
9522
|
+
return void 0;
|
|
9523
|
+
}
|
|
9524
|
+
function opencodeErrorPayload(error) {
|
|
9525
|
+
const message = opencodeErrorMessage(error) ?? String(error);
|
|
9526
|
+
const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
9527
|
+
return {
|
|
9528
|
+
message: code === "ENOENT" ? `Failed to start Opencode server binary (${message}).` : message,
|
|
9529
|
+
...code ? { code } : {}
|
|
9530
|
+
};
|
|
9531
|
+
}
|
|
9532
|
+
function opencodeFetch(input, init) {
|
|
9533
|
+
return fetch(input, { ...init, dispatcher: OPENCODE_FETCH_DISPATCHER });
|
|
9534
|
+
}
|
|
9535
|
+
var OpencodeManager = class extends CodingAgentManager {
|
|
9536
|
+
client = null;
|
|
9537
|
+
server = null;
|
|
9538
|
+
sessionId = null;
|
|
9539
|
+
historyFilePath;
|
|
9540
|
+
historyFile;
|
|
9541
|
+
activeAbortController = null;
|
|
9542
|
+
configuredModels = /* @__PURE__ */ new Set();
|
|
9543
|
+
eventAbortController = null;
|
|
9544
|
+
eventSubscriptionReady = Promise.resolve();
|
|
9545
|
+
resolveEventSubscriptionReady = null;
|
|
9546
|
+
textParts = /* @__PURE__ */ new Map();
|
|
9547
|
+
reasoningParts = /* @__PURE__ */ new Map();
|
|
9548
|
+
nonAssistantMessageIds = /* @__PURE__ */ new Set();
|
|
9549
|
+
modelVariants = /* @__PURE__ */ new Map();
|
|
9550
|
+
constructor(options) {
|
|
9551
|
+
super(options);
|
|
9552
|
+
this.sessionId = options.initialSessionId;
|
|
9553
|
+
this.historyFilePath = options.historyFilePath ?? join16(ENGINE_ENV.HOME_DIR, ".replicas", "opencode", "history.jsonl");
|
|
9554
|
+
this.historyFile = new CodexHistoryFile(this.historyFilePath);
|
|
9555
|
+
this.initializeManager(this.processMessageInternal.bind(this));
|
|
9556
|
+
}
|
|
9557
|
+
async initialize() {
|
|
9558
|
+
await mkdir12(dirname6(this.historyFilePath), { recursive: true });
|
|
9559
|
+
}
|
|
9560
|
+
async interruptActiveTurn() {
|
|
9561
|
+
this.activeAbortController?.abort();
|
|
9562
|
+
if (this.client && this.sessionId) {
|
|
9563
|
+
try {
|
|
9564
|
+
await this.client.session.abort(
|
|
9565
|
+
{ sessionID: this.sessionId, directory: this.workingDirectory },
|
|
9566
|
+
{ throwOnError: true }
|
|
9567
|
+
);
|
|
9568
|
+
} catch (error) {
|
|
9569
|
+
console.error("[OpencodeManager] Failed to abort Opencode session:", error);
|
|
9570
|
+
}
|
|
9571
|
+
}
|
|
9572
|
+
}
|
|
9573
|
+
async getHistory() {
|
|
9574
|
+
await this.historyFile.flush();
|
|
9575
|
+
const history = await this.historyFile.load();
|
|
9576
|
+
return {
|
|
9577
|
+
thread_id: this.sessionId ?? this.initialSessionId,
|
|
9578
|
+
events: history.events,
|
|
9579
|
+
goal: null
|
|
9390
9580
|
};
|
|
9391
|
-
|
|
9392
|
-
|
|
9581
|
+
}
|
|
9582
|
+
async ensureClient(model) {
|
|
9583
|
+
if (this.client && this.configuredModels.has(model)) return this.client;
|
|
9584
|
+
if (!ENGINE_ENV.OPENROUTER_API_KEY) {
|
|
9585
|
+
throw new Error("OpenRouter API key is not configured for Opencode in this workspace.");
|
|
9586
|
+
}
|
|
9587
|
+
this.eventAbortController?.abort();
|
|
9588
|
+
this.server?.close();
|
|
9589
|
+
const pathEntries = (process.env.PATH ?? "").split(delimiter).filter(Boolean);
|
|
9590
|
+
if (!pathEntries.includes(OPENCODE_SHIM_DIR)) {
|
|
9591
|
+
process.env.PATH = [OPENCODE_SHIM_DIR, ...pathEntries].join(delimiter);
|
|
9592
|
+
}
|
|
9593
|
+
const password = process.env.OPENCODE_SERVER_PASSWORD || randomBytes2(24).toString("base64url");
|
|
9594
|
+
process.env.OPENCODE_SERVER_PASSWORD = password;
|
|
9595
|
+
const server = await createOpencodeServer({
|
|
9596
|
+
port: 0,
|
|
9597
|
+
config: await opencodeConfig(model)
|
|
9598
|
+
});
|
|
9599
|
+
const client = createOpencodeClient({
|
|
9600
|
+
baseUrl: server.url,
|
|
9601
|
+
fetch: opencodeFetch,
|
|
9602
|
+
headers: {
|
|
9603
|
+
Authorization: `Basic ${Buffer.from(`opencode:${password}`).toString("base64")}`
|
|
9604
|
+
}
|
|
9605
|
+
});
|
|
9606
|
+
this.client = client;
|
|
9607
|
+
this.server = server;
|
|
9608
|
+
this.configuredModels = new Set(getConfiguredOpencodeModels(model));
|
|
9609
|
+
const eventController = new AbortController();
|
|
9610
|
+
this.eventAbortController = eventController;
|
|
9611
|
+
this.eventSubscriptionReady = new Promise((resolve4) => {
|
|
9612
|
+
this.resolveEventSubscriptionReady = resolve4;
|
|
9613
|
+
});
|
|
9614
|
+
this.subscribeToEvents(client, eventController).catch((error) => {
|
|
9615
|
+
this.resolveEventSubscriptionReady?.();
|
|
9616
|
+
this.resolveEventSubscriptionReady = null;
|
|
9617
|
+
console.error("[OpencodeManager] Event subscription failed:", error);
|
|
9618
|
+
this.recordHistoryEvent("opencode-error", opencodeErrorPayload(error), this.historyFile);
|
|
9619
|
+
});
|
|
9620
|
+
await Promise.race([
|
|
9621
|
+
this.eventSubscriptionReady,
|
|
9622
|
+
new Promise((resolve4) => setTimeout(resolve4, 2e3))
|
|
9623
|
+
]);
|
|
9624
|
+
return client;
|
|
9625
|
+
}
|
|
9626
|
+
async ensureSession(client, model, agent, variant) {
|
|
9627
|
+
if (this.sessionId) {
|
|
9628
|
+
try {
|
|
9629
|
+
await client.session.get(
|
|
9630
|
+
{ sessionID: this.sessionId, directory: this.workingDirectory },
|
|
9631
|
+
{ throwOnError: true }
|
|
9632
|
+
);
|
|
9633
|
+
return this.sessionId;
|
|
9634
|
+
} catch {
|
|
9635
|
+
this.sessionId = null;
|
|
9636
|
+
}
|
|
9637
|
+
}
|
|
9638
|
+
const result = await client.session.create({
|
|
9639
|
+
directory: this.workingDirectory,
|
|
9640
|
+
agent,
|
|
9641
|
+
model: { providerID: "openrouter", id: model, ...variant ? { variant } : {} }
|
|
9642
|
+
}, { throwOnError: true });
|
|
9643
|
+
const session = result.data;
|
|
9644
|
+
this.sessionId = session.id;
|
|
9645
|
+
await this.onSaveSessionId(session.id);
|
|
9646
|
+
return session.id;
|
|
9647
|
+
}
|
|
9648
|
+
async getModelVariants(client, model) {
|
|
9649
|
+
const cached = this.modelVariants.get(model);
|
|
9650
|
+
if (cached) return cached;
|
|
9651
|
+
try {
|
|
9652
|
+
const result = await client.v2.model.list(
|
|
9653
|
+
{ location: { directory: this.workingDirectory } },
|
|
9654
|
+
{ throwOnError: true }
|
|
9655
|
+
);
|
|
9656
|
+
for (const candidate of result.data.data) {
|
|
9657
|
+
if (candidate.providerID === "openrouter") {
|
|
9658
|
+
this.modelVariants.set(candidate.id, new Set(candidate.variants.map((variant) => variant.id)));
|
|
9659
|
+
}
|
|
9660
|
+
}
|
|
9661
|
+
} catch {
|
|
9662
|
+
this.modelVariants.set(model, /* @__PURE__ */ new Set());
|
|
9663
|
+
}
|
|
9664
|
+
return this.modelVariants.get(model) ?? /* @__PURE__ */ new Set();
|
|
9665
|
+
}
|
|
9666
|
+
async getThinkingVariant(client, model, thinkingLevel) {
|
|
9667
|
+
if (!thinkingLevel) return void 0;
|
|
9668
|
+
const variants = await this.getModelVariants(client, model);
|
|
9669
|
+
return OPENCODE_VARIANT_CANDIDATES_BY_THINKING_LEVEL[thinkingLevel].find((variant) => variants.has(variant));
|
|
9670
|
+
}
|
|
9671
|
+
async processMessageInternal(request) {
|
|
9672
|
+
const model = request.model ?? DEFAULT_OPENCODE_MODEL;
|
|
9673
|
+
const controller = new AbortController();
|
|
9674
|
+
this.activeAbortController = controller;
|
|
9675
|
+
try {
|
|
9676
|
+
const client = await this.ensureClient(model);
|
|
9677
|
+
const agent = request.planMode ? "plan" : "build";
|
|
9678
|
+
const variant = await this.getThinkingVariant(client, model, request.thinkingLevel);
|
|
9679
|
+
const sessionId = await this.ensureSession(client, model, agent, variant);
|
|
9680
|
+
const system = this.buildCombinedInstructions(request.customInstructions);
|
|
9681
|
+
this.recordHistoryEvent("event_msg", {
|
|
9682
|
+
type: "user_message",
|
|
9683
|
+
message: request.message
|
|
9684
|
+
}, this.historyFile);
|
|
9685
|
+
const result = await client.session.prompt({
|
|
9686
|
+
sessionID: sessionId,
|
|
9687
|
+
directory: this.workingDirectory,
|
|
9688
|
+
agent,
|
|
9689
|
+
model: { providerID: "openrouter", modelID: model },
|
|
9690
|
+
...variant ? { variant } : {},
|
|
9691
|
+
...system ? { system } : {},
|
|
9692
|
+
parts: [{ type: "text", text: request.message }]
|
|
9693
|
+
}, { signal: controller.signal, throwOnError: true });
|
|
9694
|
+
this.recordHistoryEvent("opencode-message.updated", { info: result.data.info }, this.historyFile);
|
|
9695
|
+
for (const part of result.data.parts) this.recordOpencodePart(part);
|
|
9696
|
+
this.recordHistoryEvent("opencode-session.idle", { sessionID: sessionId }, this.historyFile);
|
|
9697
|
+
} catch (error) {
|
|
9698
|
+
if (controller.signal.aborted) {
|
|
9699
|
+
this.recordHistoryEvent("opencode-session.idle", { sessionID: this.sessionId }, this.historyFile);
|
|
9700
|
+
} else {
|
|
9701
|
+
console.error("[OpencodeManager] Turn failed:", error);
|
|
9702
|
+
this.recordHistoryEvent("opencode-error", opencodeErrorPayload(error), this.historyFile);
|
|
9703
|
+
}
|
|
9704
|
+
} finally {
|
|
9705
|
+
this.activeAbortController = null;
|
|
9706
|
+
await this.historyFile.flush();
|
|
9707
|
+
await this.onTurnComplete();
|
|
9708
|
+
}
|
|
9709
|
+
}
|
|
9710
|
+
async subscribeToEvents(client, controller) {
|
|
9711
|
+
let streamError;
|
|
9712
|
+
const result = await client.v2.event.subscribe(
|
|
9713
|
+
{ location: { directory: this.workingDirectory } },
|
|
9714
|
+
{
|
|
9715
|
+
signal: controller.signal,
|
|
9716
|
+
sseMaxRetryAttempts: 1,
|
|
9717
|
+
onSseError: (error) => {
|
|
9718
|
+
streamError = error;
|
|
9719
|
+
}
|
|
9720
|
+
}
|
|
9721
|
+
);
|
|
9722
|
+
for await (const event of result.stream) {
|
|
9723
|
+
this.resolveEventSubscriptionReady?.();
|
|
9724
|
+
this.resolveEventSubscriptionReady = null;
|
|
9725
|
+
if (isOpencodeRuntimeEvent(event)) {
|
|
9726
|
+
this.recordOpencodeEvent(event);
|
|
9727
|
+
}
|
|
9728
|
+
}
|
|
9729
|
+
this.resolveEventSubscriptionReady?.();
|
|
9730
|
+
this.resolveEventSubscriptionReady = null;
|
|
9731
|
+
if (!controller.signal.aborted && streamError) throw streamError;
|
|
9732
|
+
}
|
|
9733
|
+
recordOpencodeEvent(event) {
|
|
9734
|
+
const payload = opencodeEventPayload(event);
|
|
9735
|
+
if (!payload) return;
|
|
9736
|
+
const sessionID = typeof payload.sessionID === "string" ? payload.sessionID : void 0;
|
|
9737
|
+
const part = payload.part;
|
|
9738
|
+
if (typeof sessionID === "string" && this.sessionId && sessionID !== this.sessionId) return;
|
|
9739
|
+
const partSessionID = isRecord4(part) ? part.sessionID : void 0;
|
|
9740
|
+
if (typeof partSessionID === "string" && this.sessionId && partSessionID !== this.sessionId) return;
|
|
9741
|
+
if (event.type === "message.part.updated" && isOpencodePart(part)) {
|
|
9742
|
+
if (this.shouldRecordOpencodePart(part)) this.recordOpencodePart(part);
|
|
9743
|
+
return;
|
|
9744
|
+
}
|
|
9745
|
+
if (event.type === "message.updated") this.recordOpencodeMessageRole(payload);
|
|
9746
|
+
if (this.recordOpencodeNextPart(event.type, payload)) return;
|
|
9747
|
+
this.recordHistoryEvent(`opencode-${event.type}`, payload, this.historyFile);
|
|
9748
|
+
}
|
|
9749
|
+
recordOpencodeMessageRole(payload) {
|
|
9750
|
+
const info = isRecord4(payload.info) ? payload.info : null;
|
|
9751
|
+
const id = typeof info?.id === "string" ? info.id : void 0;
|
|
9752
|
+
const role = typeof info?.role === "string" ? info.role : void 0;
|
|
9753
|
+
if (!id || !role) return;
|
|
9754
|
+
if (role !== "assistant") this.nonAssistantMessageIds.add(id);
|
|
9755
|
+
}
|
|
9756
|
+
shouldRecordOpencodePart(part) {
|
|
9757
|
+
return !this.nonAssistantMessageIds.has(part.messageID);
|
|
9758
|
+
}
|
|
9759
|
+
recordOpencodeNextPart(type, payload) {
|
|
9760
|
+
const sessionID = typeof payload.sessionID === "string" ? payload.sessionID : void 0;
|
|
9761
|
+
const messageID = typeof payload.assistantMessageID === "string" ? payload.assistantMessageID : void 0;
|
|
9762
|
+
if (!sessionID || !messageID) return false;
|
|
9763
|
+
if (type === "session.next.text.started") {
|
|
9764
|
+
const id = typeof payload.textID === "string" ? payload.textID : void 0;
|
|
9765
|
+
if (!id) return false;
|
|
9766
|
+
this.textParts.set(id, {
|
|
9767
|
+
id,
|
|
9768
|
+
sessionID,
|
|
9769
|
+
messageID,
|
|
9770
|
+
type: "text",
|
|
9771
|
+
text: "",
|
|
9772
|
+
time: { start: timestampMs(payload.timestamp) }
|
|
9773
|
+
});
|
|
9774
|
+
return true;
|
|
9775
|
+
}
|
|
9776
|
+
if (type === "session.next.text.delta" || type === "session.next.text.ended") {
|
|
9777
|
+
const id = typeof payload.textID === "string" ? payload.textID : void 0;
|
|
9778
|
+
if (!id) return false;
|
|
9779
|
+
const part = this.textParts.get(id) ?? {
|
|
9780
|
+
id,
|
|
9781
|
+
sessionID,
|
|
9782
|
+
messageID,
|
|
9783
|
+
type: "text",
|
|
9784
|
+
text: "",
|
|
9785
|
+
time: { start: timestampMs(payload.timestamp) }
|
|
9786
|
+
};
|
|
9787
|
+
part.text = type === "session.next.text.ended" && typeof payload.text === "string" ? payload.text : `${part.text}${typeof payload.delta === "string" ? payload.delta : ""}`;
|
|
9788
|
+
if (type === "session.next.text.ended") {
|
|
9789
|
+
part.time = { start: part.time?.start ?? timestampMs(payload.timestamp), end: timestampMs(payload.timestamp) };
|
|
9790
|
+
}
|
|
9791
|
+
this.textParts.set(id, part);
|
|
9792
|
+
this.recordOpencodePart(part);
|
|
9793
|
+
return true;
|
|
9794
|
+
}
|
|
9795
|
+
if (type === "session.next.reasoning.started") {
|
|
9796
|
+
const id = typeof payload.reasoningID === "string" ? payload.reasoningID : void 0;
|
|
9797
|
+
if (!id) return false;
|
|
9798
|
+
this.reasoningParts.set(id, {
|
|
9799
|
+
id,
|
|
9800
|
+
sessionID,
|
|
9801
|
+
messageID,
|
|
9802
|
+
type: "reasoning",
|
|
9803
|
+
text: "",
|
|
9804
|
+
time: { start: timestampMs(payload.timestamp) }
|
|
9805
|
+
});
|
|
9806
|
+
return true;
|
|
9807
|
+
}
|
|
9808
|
+
if (type === "session.next.reasoning.delta" || type === "session.next.reasoning.ended") {
|
|
9809
|
+
const id = typeof payload.reasoningID === "string" ? payload.reasoningID : void 0;
|
|
9810
|
+
if (!id) return false;
|
|
9811
|
+
const part = this.reasoningParts.get(id) ?? {
|
|
9812
|
+
id,
|
|
9813
|
+
sessionID,
|
|
9814
|
+
messageID,
|
|
9815
|
+
type: "reasoning",
|
|
9816
|
+
text: "",
|
|
9817
|
+
time: { start: timestampMs(payload.timestamp) }
|
|
9818
|
+
};
|
|
9819
|
+
part.text = type === "session.next.reasoning.ended" && typeof payload.text === "string" ? payload.text : `${part.text}${typeof payload.delta === "string" ? payload.delta : ""}`;
|
|
9820
|
+
if (type === "session.next.reasoning.ended") {
|
|
9821
|
+
part.time = { start: part.time.start, end: timestampMs(payload.timestamp) };
|
|
9822
|
+
}
|
|
9823
|
+
this.reasoningParts.set(id, part);
|
|
9824
|
+
this.recordOpencodePart(part);
|
|
9825
|
+
return true;
|
|
9826
|
+
}
|
|
9827
|
+
return false;
|
|
9828
|
+
}
|
|
9829
|
+
recordOpencodePart(part) {
|
|
9830
|
+
this.recordHistoryEvent(`opencode-part-${part.type}`, { part }, this.historyFile);
|
|
9393
9831
|
}
|
|
9394
9832
|
};
|
|
9395
9833
|
|
|
@@ -9401,14 +9839,16 @@ import { z } from "zod";
|
|
|
9401
9839
|
function getAvailableRelayProviders(availability) {
|
|
9402
9840
|
const codexAvailable = availability.codexAvailable ?? false;
|
|
9403
9841
|
const cursorAvailable = availability.cursorAvailable ?? false;
|
|
9842
|
+
const opencodeAvailable = availability.opencodeAvailable ?? false;
|
|
9404
9843
|
const providers = ["claude"];
|
|
9405
9844
|
if (codexAvailable) providers.push("codex");
|
|
9406
9845
|
if (cursorAvailable) providers.push("cursor");
|
|
9846
|
+
if (opencodeAvailable) providers.push("opencode");
|
|
9407
9847
|
providers.push("relay");
|
|
9408
9848
|
return providers;
|
|
9409
9849
|
}
|
|
9410
9850
|
function getAvailableCodeProviders(availability) {
|
|
9411
|
-
return getAvailableRelayProviders(availability).filter((provider) => provider === "codex" || provider === "cursor");
|
|
9851
|
+
return getAvailableRelayProviders(availability).filter((provider) => provider === "codex" || provider === "cursor" || provider === "opencode");
|
|
9412
9852
|
}
|
|
9413
9853
|
|
|
9414
9854
|
// src/managers/relay-tools.ts
|
|
@@ -9484,6 +9924,12 @@ async function getChatFinalResponse(chatId) {
|
|
|
9484
9924
|
return text;
|
|
9485
9925
|
}
|
|
9486
9926
|
}
|
|
9927
|
+
if (event.type === "opencode-part-text") {
|
|
9928
|
+
const part = payload.part;
|
|
9929
|
+
if (part && typeof part.text === "string" && part.text) {
|
|
9930
|
+
return part.text;
|
|
9931
|
+
}
|
|
9932
|
+
}
|
|
9487
9933
|
if (event.type === "cursor-assistant") {
|
|
9488
9934
|
const message = payload.message;
|
|
9489
9935
|
const text = extractTextBlocks(message?.content, "text", "");
|
|
@@ -9495,6 +9941,7 @@ async function getChatFinalResponse(chatId) {
|
|
|
9495
9941
|
function buildSpawnAgentTool(parentChatId, availability = {}) {
|
|
9496
9942
|
const codexAvailable = availability.codexAvailable ?? false;
|
|
9497
9943
|
const cursorAvailable = availability.cursorAvailable ?? false;
|
|
9944
|
+
const opencodeAvailable = availability.opencodeAvailable ?? false;
|
|
9498
9945
|
const availableProviders = getAvailableRelayProviders(availability);
|
|
9499
9946
|
const providerEnum = z.enum(availableProviders);
|
|
9500
9947
|
const codeProviders = getAvailableCodeProviders(availability);
|
|
@@ -9519,10 +9966,11 @@ You will also receive the chatId so you can send follow-up messages or clean up
|
|
|
9519
9966
|
model: z.string().optional().describe([
|
|
9520
9967
|
`Model override. Claude: ${AGENT_MODELS.claude.join(", ")} (opus[1m] is the default, 1M context, use for very large codebases or huge context tasks).`,
|
|
9521
9968
|
codexAvailable ? "Codex: gpt-5.5, gpt-5.4, gpt-5.3-codex, etc." : null,
|
|
9522
|
-
cursorAvailable ? `Cursor: ${AGENT_MODELS.cursor.join(", ")}.` : null
|
|
9969
|
+
cursorAvailable ? `Cursor: ${AGENT_MODELS.cursor.join(", ")}.` : null,
|
|
9970
|
+
opencodeAvailable ? `Opencode: ${AGENT_MODELS.opencode.join(", ")}.` : null
|
|
9523
9971
|
].filter(Boolean).join(" ")),
|
|
9524
9972
|
thinking_level: z.enum(["low", "medium", "high", "max"]).optional().describe(
|
|
9525
|
-
"Controls how much thinking/reasoning the subagent applies. low = light thinking, medium = moderate, high = deep reasoning, max = maximum effort. Defaults: Claude = high, Codex = medium, Cursor = medium."
|
|
9973
|
+
"Controls how much thinking/reasoning the subagent applies. low = light thinking, medium = moderate, high = deep reasoning, max = maximum effort. Defaults: Claude = high, Codex = medium, Cursor = medium, Opencode = medium."
|
|
9526
9974
|
),
|
|
9527
9975
|
title: z.string().optional().describe("Optional title for the subagent chat (for identification)."),
|
|
9528
9976
|
timeout_minutes: z.number().positive().optional().describe("Timeout in minutes for the subagent to complete (default: 10). Set higher for large tasks to avoid losing work.")
|
|
@@ -9590,7 +10038,7 @@ The tool blocks until the subagent completes and returns its response.`,
|
|
|
9590
10038
|
message: z.string().describe("The follow-up message to send."),
|
|
9591
10039
|
model: z.string().optional().describe("Optional model override for this message."),
|
|
9592
10040
|
thinking_level: z.enum(["low", "medium", "high", "max"]).optional().describe(
|
|
9593
|
-
"Controls how much thinking/reasoning the subagent applies. low = light thinking, medium = moderate, high = deep reasoning, max = maximum effort. Defaults: Claude = high, Codex = medium, Cursor = medium."
|
|
10041
|
+
"Controls how much thinking/reasoning the subagent applies. low = light thinking, medium = moderate, high = deep reasoning, max = maximum effort. Defaults: Claude = high, Codex = medium, Cursor = medium, Opencode = medium."
|
|
9594
10042
|
),
|
|
9595
10043
|
timeout_minutes: z.number().positive().optional().describe("Timeout in minutes for the subagent to complete (default: 10). Set higher for large tasks to avoid losing work.")
|
|
9596
10044
|
},
|
|
@@ -9754,12 +10202,13 @@ function getUsingToolsSection() {
|
|
|
9754
10202
|
];
|
|
9755
10203
|
return [`# Using your tools`, ...prependBullets(items)].join("\n");
|
|
9756
10204
|
}
|
|
9757
|
-
function getDelegationSection(codexAvailable, cursorAvailable) {
|
|
9758
|
-
const providerList = getAvailableRelayProviders({ codexAvailable, cursorAvailable }).join(", ");
|
|
10205
|
+
function getDelegationSection(codexAvailable, cursorAvailable, opencodeAvailable) {
|
|
10206
|
+
const providerList = getAvailableRelayProviders({ codexAvailable, cursorAvailable, opencodeAvailable }).join(", ");
|
|
9759
10207
|
const spawnDesc = `Create a new subagent with a specific provider (${providerList}), send it a prompt, and wait for its response. Returns the chatId and the agent's final response. You can set a custom timeout via the timeout_minutes parameter (default: 10 minutes).`;
|
|
9760
10208
|
const claudeModelList = AGENT_MODELS.claude.join(", ");
|
|
9761
10209
|
const extraAgentLines = [
|
|
9762
10210
|
codexAvailable ? `Use provider 'codex' for heavy code writing, implementation, and large refactors. Suggested models: gpt-5.5 (default), gpt-5.4, gpt-5.3-codex.` : null,
|
|
10211
|
+
opencodeAvailable ? `Use provider 'opencode' for cheaper routine implementation tasks through OpenRouter-backed open source models. Suggested models: ${AGENT_MODELS.opencode.join(", ")}.` : null,
|
|
9763
10212
|
cursorAvailable ? `Use provider 'cursor' for fast iteration on code changes. Suggested models: ${AGENT_MODELS.cursor.join(", ")}.` : null
|
|
9764
10213
|
].filter(Boolean);
|
|
9765
10214
|
const agentSelectionLines = extraAgentLines.length > 0 ? `${extraAgentLines.join("\n\n")}
|
|
@@ -9882,14 +10331,14 @@ function getEnvironmentSection() {
|
|
|
9882
10331
|
].join("\n");
|
|
9883
10332
|
}
|
|
9884
10333
|
function buildRelaySystemPrompt(options) {
|
|
9885
|
-
const { customInstructions, codexAvailable, cursorAvailable } = options ?? {};
|
|
10334
|
+
const { customInstructions, codexAvailable, cursorAvailable, opencodeAvailable } = options ?? {};
|
|
9886
10335
|
const sections = [
|
|
9887
10336
|
getIntroSection(),
|
|
9888
10337
|
getSystemSection(),
|
|
9889
10338
|
getDoingTasksSection(),
|
|
9890
10339
|
getActionsSection(),
|
|
9891
10340
|
getUsingToolsSection(),
|
|
9892
|
-
getDelegationSection(codexAvailable ?? false, cursorAvailable ?? false),
|
|
10341
|
+
getDelegationSection(codexAvailable ?? false, cursorAvailable ?? false, opencodeAvailable ?? false),
|
|
9893
10342
|
getToneAndStyleSection(),
|
|
9894
10343
|
getOutputEfficiencySection(),
|
|
9895
10344
|
getEnvironmentSection(),
|
|
@@ -9921,12 +10370,13 @@ var RelayManager = class {
|
|
|
9921
10370
|
constructor(options) {
|
|
9922
10371
|
const codexAvailable = options.codexAvailable ?? false;
|
|
9923
10372
|
const cursorAvailable = options.cursorAvailable ?? false;
|
|
10373
|
+
const opencodeAvailable = options.opencodeAvailable ?? false;
|
|
9924
10374
|
this.inner = new ClaudeManager({
|
|
9925
10375
|
...options,
|
|
9926
|
-
systemPromptOverride: (customInstructions) => buildRelaySystemPrompt({ customInstructions, codexAvailable, cursorAvailable }),
|
|
10376
|
+
systemPromptOverride: (customInstructions) => buildRelaySystemPrompt({ customInstructions, codexAvailable, cursorAvailable, opencodeAvailable }),
|
|
9927
10377
|
tools: RELAY_TOOLS,
|
|
9928
10378
|
mcpServers: {
|
|
9929
|
-
"relay-subagent-tools": createRelayMcpServer(options.chatId, { codexAvailable, cursorAvailable })
|
|
10379
|
+
"relay-subagent-tools": createRelayMcpServer(options.chatId, { codexAvailable, cursorAvailable, opencodeAvailable })
|
|
9930
10380
|
},
|
|
9931
10381
|
envOverrides: {
|
|
9932
10382
|
CLAUDE_CODE_STREAM_CLOSE_TIMEOUT: "900000"
|
|
@@ -10002,12 +10452,12 @@ var KeepAliveService = class _KeepAliveService {
|
|
|
10002
10452
|
var keepAliveService = new KeepAliveService();
|
|
10003
10453
|
|
|
10004
10454
|
// src/services/canvas-service.ts
|
|
10005
|
-
import { readdir as readdir4, readFile as
|
|
10455
|
+
import { readdir as readdir4, readFile as readFile11, stat as stat3 } from "fs/promises";
|
|
10006
10456
|
import { homedir as homedir12 } from "os";
|
|
10007
|
-
import { join as
|
|
10457
|
+
import { join as join17 } from "path";
|
|
10008
10458
|
var CANVAS_DIRECTORIES = [
|
|
10009
|
-
|
|
10010
|
-
|
|
10459
|
+
join17(homedir12(), ".claude", "plans"),
|
|
10460
|
+
join17(homedir12(), ".replicas", "canvas")
|
|
10011
10461
|
];
|
|
10012
10462
|
var CanvasService = class {
|
|
10013
10463
|
async listItems() {
|
|
@@ -10026,7 +10476,7 @@ var CanvasService = class {
|
|
|
10026
10476
|
const { kind } = classifyCanvasFilename(entry.name);
|
|
10027
10477
|
let sizeBytes = 0;
|
|
10028
10478
|
try {
|
|
10029
|
-
const s = await stat3(
|
|
10479
|
+
const s = await stat3(join17(directory, entry.name));
|
|
10030
10480
|
sizeBytes = s.size;
|
|
10031
10481
|
} catch {
|
|
10032
10482
|
continue;
|
|
@@ -10041,7 +10491,7 @@ var CanvasService = class {
|
|
|
10041
10491
|
if (!safe) return null;
|
|
10042
10492
|
const { kind, mimeType } = classifyCanvasFilename(safe);
|
|
10043
10493
|
for (const directory of CANVAS_DIRECTORIES) {
|
|
10044
|
-
const filePath =
|
|
10494
|
+
const filePath = join17(directory, safe);
|
|
10045
10495
|
let sizeBytes = 0;
|
|
10046
10496
|
let updatedAt = "";
|
|
10047
10497
|
try {
|
|
@@ -10062,7 +10512,7 @@ var CanvasService = class {
|
|
|
10062
10512
|
};
|
|
10063
10513
|
}
|
|
10064
10514
|
try {
|
|
10065
|
-
const bytes = await
|
|
10515
|
+
const bytes = await readFile11(filePath);
|
|
10066
10516
|
return { filename: safe, kind, sizeBytes, mimeType, updatedAt, bytes };
|
|
10067
10517
|
} catch {
|
|
10068
10518
|
continue;
|
|
@@ -10177,14 +10627,14 @@ async function reconcileCanvasItems(filenames) {
|
|
|
10177
10627
|
}
|
|
10178
10628
|
|
|
10179
10629
|
// src/services/upload-chat-transcripts.ts
|
|
10180
|
-
import { readdir as readdir5, readFile as
|
|
10181
|
-
import { basename, join as
|
|
10630
|
+
import { readdir as readdir5, readFile as readFile12 } from "fs/promises";
|
|
10631
|
+
import { basename, join as join18 } from "path";
|
|
10182
10632
|
import { homedir as homedir13 } from "os";
|
|
10183
|
-
var ENGINE_DIR2 =
|
|
10633
|
+
var ENGINE_DIR2 = join18(homedir13(), ".replicas", "engine");
|
|
10184
10634
|
var HISTORY_DIRS = [
|
|
10185
|
-
|
|
10186
|
-
|
|
10187
|
-
|
|
10635
|
+
join18(ENGINE_DIR2, "claude-histories"),
|
|
10636
|
+
join18(ENGINE_DIR2, "relay-histories"),
|
|
10637
|
+
join18(ENGINE_DIR2, "codex-histories")
|
|
10188
10638
|
];
|
|
10189
10639
|
async function flushAllChatTranscripts(chatsById = /* @__PURE__ */ new Map()) {
|
|
10190
10640
|
let flushed = 0;
|
|
@@ -10201,7 +10651,7 @@ async function flushAllChatTranscripts(chatsById = /* @__PURE__ */ new Map()) {
|
|
|
10201
10651
|
if (!entry.endsWith(".jsonl")) continue;
|
|
10202
10652
|
const chatId = basename(entry, ".jsonl");
|
|
10203
10653
|
tasks.push(
|
|
10204
|
-
uploadChatTranscript(chatId,
|
|
10654
|
+
uploadChatTranscript(chatId, join18(dir, entry), chatsById.get(chatId)).then(() => {
|
|
10205
10655
|
flushed++;
|
|
10206
10656
|
}).catch((err) => {
|
|
10207
10657
|
failed++;
|
|
@@ -10214,7 +10664,7 @@ async function flushAllChatTranscripts(chatsById = /* @__PURE__ */ new Map()) {
|
|
|
10214
10664
|
return { flushed, failed };
|
|
10215
10665
|
}
|
|
10216
10666
|
async function uploadChatTranscript(chatId, filePath, chat) {
|
|
10217
|
-
const bytes = await
|
|
10667
|
+
const bytes = await readFile12(filePath);
|
|
10218
10668
|
if (bytes.byteLength === 0) return;
|
|
10219
10669
|
const form = new FormData();
|
|
10220
10670
|
form.append("chat_id", chatId);
|
|
@@ -10271,20 +10721,23 @@ var DuplicateDefaultChatError = class extends Error {
|
|
|
10271
10721
|
};
|
|
10272
10722
|
|
|
10273
10723
|
// src/services/chat/chat-service.ts
|
|
10274
|
-
var ENGINE_DIR3 =
|
|
10275
|
-
var CHATS_FILE =
|
|
10276
|
-
var CLAUDE_HISTORY_DIR =
|
|
10277
|
-
var RELAY_HISTORY_DIR =
|
|
10278
|
-
var CODEX_HISTORY_DIR =
|
|
10279
|
-
var CURSOR_HISTORY_DIR =
|
|
10724
|
+
var ENGINE_DIR3 = join19(homedir14(), ".replicas", "engine");
|
|
10725
|
+
var CHATS_FILE = join19(ENGINE_DIR3, "chats.json");
|
|
10726
|
+
var CLAUDE_HISTORY_DIR = join19(ENGINE_DIR3, "claude-histories");
|
|
10727
|
+
var RELAY_HISTORY_DIR = join19(ENGINE_DIR3, "relay-histories");
|
|
10728
|
+
var CODEX_HISTORY_DIR = join19(ENGINE_DIR3, "codex-histories");
|
|
10729
|
+
var CURSOR_HISTORY_DIR = join19(ENGINE_DIR3, "cursor-histories");
|
|
10730
|
+
var OPENCODE_HISTORY_DIR = join19(ENGINE_DIR3, "opencode-histories");
|
|
10280
10731
|
var HISTORY_DIR_BY_PROVIDER = {
|
|
10281
10732
|
claude: CLAUDE_HISTORY_DIR,
|
|
10282
10733
|
relay: RELAY_HISTORY_DIR,
|
|
10283
10734
|
codex: CODEX_HISTORY_DIR,
|
|
10284
|
-
cursor: CURSOR_HISTORY_DIR
|
|
10735
|
+
cursor: CURSOR_HISTORY_DIR,
|
|
10736
|
+
opencode: OPENCODE_HISTORY_DIR
|
|
10285
10737
|
};
|
|
10286
|
-
var CHAT_SENDERS_DIR =
|
|
10287
|
-
var CODEX_AUTH_PATH2 =
|
|
10738
|
+
var CHAT_SENDERS_DIR = join19(ENGINE_DIR3, "chat-senders");
|
|
10739
|
+
var CODEX_AUTH_PATH2 = join19(homedir14(), ".codex", "auth.json");
|
|
10740
|
+
var OPENCODE_AUTH_PATH2 = join19(homedir14(), ".local", "share", "opencode", "auth.json");
|
|
10288
10741
|
var CHATS_BACKUP_FILE = `${CHATS_FILE}.bak`;
|
|
10289
10742
|
function isChatMessageSender(value) {
|
|
10290
10743
|
if (!isRecord4(value)) return false;
|
|
@@ -10293,6 +10746,9 @@ function isChatMessageSender(value) {
|
|
|
10293
10746
|
function isCodexAvailable() {
|
|
10294
10747
|
return existsSync7(CODEX_AUTH_PATH2) || Boolean(ENGINE_ENV.OPENAI_API_KEY);
|
|
10295
10748
|
}
|
|
10749
|
+
function isOpencodeAvailable() {
|
|
10750
|
+
return existsSync7(OPENCODE_AUTH_PATH2) || Boolean(ENGINE_ENV.OPENROUTER_API_KEY);
|
|
10751
|
+
}
|
|
10296
10752
|
function isCursorAvailable() {
|
|
10297
10753
|
return Boolean(ENGINE_ENV.CURSOR_API_KEY);
|
|
10298
10754
|
}
|
|
@@ -10337,7 +10793,7 @@ function isPersistedChat(value) {
|
|
|
10337
10793
|
return false;
|
|
10338
10794
|
}
|
|
10339
10795
|
const candidate = value;
|
|
10340
|
-
return typeof candidate.id === "string" && (candidate.provider === "claude" || candidate.provider === "codex" || candidate.provider === "cursor" || candidate.provider === "relay") && typeof candidate.title === "string" && typeof candidate.createdAt === "string" && typeof candidate.updatedAt === "string" && (candidate.providerSessionId === null || typeof candidate.providerSessionId === "string") && (candidate.parentChatId === void 0 || candidate.parentChatId === null || typeof candidate.parentChatId === "string");
|
|
10796
|
+
return typeof candidate.id === "string" && (candidate.provider === "claude" || candidate.provider === "codex" || candidate.provider === "cursor" || candidate.provider === "opencode" || candidate.provider === "relay") && typeof candidate.title === "string" && typeof candidate.createdAt === "string" && typeof candidate.updatedAt === "string" && (candidate.providerSessionId === null || typeof candidate.providerSessionId === "string") && (candidate.parentChatId === void 0 || candidate.parentChatId === null || typeof candidate.parentChatId === "string");
|
|
10341
10797
|
}
|
|
10342
10798
|
function normalizePersistedChat(chat) {
|
|
10343
10799
|
const isLegacyCodexSdkChat = chat.provider === "codex" && (chat.codexBackend === "sdk" || chat.codexBackend === void 0 && chat.providerSessionId !== null);
|
|
@@ -10385,12 +10841,13 @@ var ChatService = class {
|
|
|
10385
10841
|
persistInFlight = false;
|
|
10386
10842
|
persistQueued = false;
|
|
10387
10843
|
async initialize() {
|
|
10388
|
-
await
|
|
10389
|
-
await
|
|
10390
|
-
await
|
|
10391
|
-
await
|
|
10392
|
-
await
|
|
10393
|
-
await
|
|
10844
|
+
await mkdir13(ENGINE_DIR3, { recursive: true });
|
|
10845
|
+
await mkdir13(CLAUDE_HISTORY_DIR, { recursive: true });
|
|
10846
|
+
await mkdir13(RELAY_HISTORY_DIR, { recursive: true });
|
|
10847
|
+
await mkdir13(CODEX_HISTORY_DIR, { recursive: true });
|
|
10848
|
+
await mkdir13(CURSOR_HISTORY_DIR, { recursive: true });
|
|
10849
|
+
await mkdir13(OPENCODE_HISTORY_DIR, { recursive: true });
|
|
10850
|
+
await mkdir13(CHAT_SENDERS_DIR, { recursive: true });
|
|
10394
10851
|
const persisted = await this.loadChats();
|
|
10395
10852
|
for (const chat of persisted) {
|
|
10396
10853
|
const runtime = this.createRuntimeChat(chat);
|
|
@@ -10405,6 +10862,9 @@ var ChatService = class {
|
|
|
10405
10862
|
const hasCursorDefault = [...this.chats.values()].some(
|
|
10406
10863
|
(c) => c.persisted.provider === "cursor" && c.persisted.title === "Cursor"
|
|
10407
10864
|
);
|
|
10865
|
+
const hasOpencodeDefault = [...this.chats.values()].some(
|
|
10866
|
+
(c) => c.persisted.provider === "opencode" && c.persisted.title === "Opencode"
|
|
10867
|
+
);
|
|
10408
10868
|
if (!hasClaudeDefault) {
|
|
10409
10869
|
await this.createChat({ provider: "claude", title: "Claude Code" });
|
|
10410
10870
|
}
|
|
@@ -10414,6 +10874,9 @@ var ChatService = class {
|
|
|
10414
10874
|
if (!hasCursorDefault) {
|
|
10415
10875
|
await this.createChat({ provider: "cursor", title: "Cursor" });
|
|
10416
10876
|
}
|
|
10877
|
+
if (!hasOpencodeDefault) {
|
|
10878
|
+
await this.createChat({ provider: "opencode", title: "Opencode" });
|
|
10879
|
+
}
|
|
10417
10880
|
const hasRelayDefault = [...this.chats.values()].some(
|
|
10418
10881
|
(c) => c.persisted.provider === "relay" && c.persisted.title === "Relay"
|
|
10419
10882
|
);
|
|
@@ -10495,7 +10958,7 @@ var ChatService = class {
|
|
|
10495
10958
|
};
|
|
10496
10959
|
}
|
|
10497
10960
|
senderFilePath(chatId) {
|
|
10498
|
-
return
|
|
10961
|
+
return join19(CHAT_SENDERS_DIR, `${chatId}.jsonl`);
|
|
10499
10962
|
}
|
|
10500
10963
|
async appendSender(chatId, sender) {
|
|
10501
10964
|
try {
|
|
@@ -10506,7 +10969,7 @@ var ChatService = class {
|
|
|
10506
10969
|
}
|
|
10507
10970
|
async readSenders(chatId) {
|
|
10508
10971
|
try {
|
|
10509
|
-
const content = await
|
|
10972
|
+
const content = await readFile13(this.senderFilePath(chatId), "utf-8");
|
|
10510
10973
|
const lines = content.split("\n").filter((line) => line.trim().length > 0);
|
|
10511
10974
|
const senders = [];
|
|
10512
10975
|
for (const line of lines) {
|
|
@@ -10651,7 +11114,7 @@ var ChatService = class {
|
|
|
10651
11114
|
return descendants;
|
|
10652
11115
|
}
|
|
10653
11116
|
async deleteHistoryFile(persisted) {
|
|
10654
|
-
await rm(
|
|
11117
|
+
await rm(join19(HISTORY_DIR_BY_PROVIDER[persisted.provider], `${persisted.id}.jsonl`), { force: true });
|
|
10655
11118
|
await rm(this.senderFilePath(persisted.id), { force: true });
|
|
10656
11119
|
}
|
|
10657
11120
|
async getChatHistory(chatId) {
|
|
@@ -10722,7 +11185,7 @@ var ChatService = class {
|
|
|
10722
11185
|
if (persisted.provider === "claude") {
|
|
10723
11186
|
provider = new ClaudeManager({
|
|
10724
11187
|
workingDirectory: this.workingDirectory,
|
|
10725
|
-
historyFilePath:
|
|
11188
|
+
historyFilePath: join19(CLAUDE_HISTORY_DIR, `${persisted.id}.jsonl`),
|
|
10726
11189
|
initialSessionId: persisted.providerSessionId,
|
|
10727
11190
|
onSaveSessionId: saveSession,
|
|
10728
11191
|
onTurnComplete: onProviderTurnComplete,
|
|
@@ -10731,19 +11194,29 @@ var ChatService = class {
|
|
|
10731
11194
|
} else if (persisted.provider === "relay") {
|
|
10732
11195
|
provider = new RelayManager({
|
|
10733
11196
|
workingDirectory: this.workingDirectory,
|
|
10734
|
-
historyFilePath:
|
|
11197
|
+
historyFilePath: join19(RELAY_HISTORY_DIR, `${persisted.id}.jsonl`),
|
|
10735
11198
|
initialSessionId: persisted.providerSessionId,
|
|
10736
11199
|
onSaveSessionId: saveSession,
|
|
10737
11200
|
onTurnComplete: onProviderTurnComplete,
|
|
10738
11201
|
onEvent: onProviderEvent,
|
|
10739
11202
|
chatId: persisted.id,
|
|
10740
11203
|
codexAvailable: isCodexAvailable(),
|
|
11204
|
+
opencodeAvailable: isOpencodeAvailable(),
|
|
10741
11205
|
cursorAvailable: isCursorAvailable()
|
|
10742
11206
|
});
|
|
10743
11207
|
} else if (persisted.provider === "cursor") {
|
|
10744
11208
|
provider = new CursorManager({
|
|
10745
11209
|
workingDirectory: this.workingDirectory,
|
|
10746
|
-
historyFilePath:
|
|
11210
|
+
historyFilePath: join19(CURSOR_HISTORY_DIR, `${persisted.id}.jsonl`),
|
|
11211
|
+
initialSessionId: persisted.providerSessionId,
|
|
11212
|
+
onSaveSessionId: saveSession,
|
|
11213
|
+
onTurnComplete: onProviderTurnComplete,
|
|
11214
|
+
onEvent: onProviderEvent
|
|
11215
|
+
});
|
|
11216
|
+
} else if (persisted.provider === "opencode") {
|
|
11217
|
+
provider = new OpencodeManager({
|
|
11218
|
+
workingDirectory: this.workingDirectory,
|
|
11219
|
+
historyFilePath: join19(OPENCODE_HISTORY_DIR, `${persisted.id}.jsonl`),
|
|
10747
11220
|
initialSessionId: persisted.providerSessionId,
|
|
10748
11221
|
onSaveSessionId: saveSession,
|
|
10749
11222
|
onTurnComplete: onProviderTurnComplete,
|
|
@@ -10752,7 +11225,7 @@ var ChatService = class {
|
|
|
10752
11225
|
} else {
|
|
10753
11226
|
provider = new CodexAspManager({
|
|
10754
11227
|
workingDirectory: this.workingDirectory,
|
|
10755
|
-
historyFilePath:
|
|
11228
|
+
historyFilePath: join19(CODEX_HISTORY_DIR, `${persisted.id}.jsonl`),
|
|
10756
11229
|
initialSessionId: persisted.providerSessionId,
|
|
10757
11230
|
onSaveSessionId: saveSession,
|
|
10758
11231
|
onTurnComplete: onProviderTurnComplete,
|
|
@@ -10891,7 +11364,7 @@ var ChatService = class {
|
|
|
10891
11364
|
});
|
|
10892
11365
|
uploadChatTranscript(
|
|
10893
11366
|
chatId,
|
|
10894
|
-
|
|
11367
|
+
join19(HISTORY_DIR_BY_PROVIDER[chat.persisted.provider], `${chatId}.jsonl`),
|
|
10895
11368
|
this.toSummary(chat)
|
|
10896
11369
|
).catch((err) => {
|
|
10897
11370
|
console.error("[ChatService] Failed to upload chat transcript:", { chatId, err });
|
|
@@ -10906,7 +11379,7 @@ var ChatService = class {
|
|
|
10906
11379
|
}
|
|
10907
11380
|
async loadChats() {
|
|
10908
11381
|
try {
|
|
10909
|
-
const content = await
|
|
11382
|
+
const content = await readFile13(CHATS_FILE, "utf-8");
|
|
10910
11383
|
return parsePersistedChatsContent(content);
|
|
10911
11384
|
} catch (error) {
|
|
10912
11385
|
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
@@ -10921,7 +11394,7 @@ var ChatService = class {
|
|
|
10921
11394
|
console.error("[ChatService] Failed to quarantine corrupt chats file:", renameError);
|
|
10922
11395
|
}
|
|
10923
11396
|
try {
|
|
10924
|
-
const backupContent = await
|
|
11397
|
+
const backupContent = await readFile13(CHATS_BACKUP_FILE, "utf-8");
|
|
10925
11398
|
return parsePersistedChatsContent(backupContent);
|
|
10926
11399
|
} catch (backupError) {
|
|
10927
11400
|
if (backupError && typeof backupError === "object" && "code" in backupError && backupError.code === "ENOENT") {
|
|
@@ -11008,8 +11481,8 @@ var ChatService = class {
|
|
|
11008
11481
|
|
|
11009
11482
|
// src/services/repo-file-service.ts
|
|
11010
11483
|
import { execFile as execFile2 } from "child_process";
|
|
11011
|
-
import { readFile as
|
|
11012
|
-
import { join as
|
|
11484
|
+
import { readFile as readFile14, realpath, stat as stat4 } from "fs/promises";
|
|
11485
|
+
import { join as join20, resolve as resolve2, extname } from "path";
|
|
11013
11486
|
var CACHE_TTL_MS = 3e4;
|
|
11014
11487
|
var SEARCH_TIMEOUT_MS = 15e3;
|
|
11015
11488
|
var MAX_CONTENT_BYTES = 256 * 1024;
|
|
@@ -11169,7 +11642,7 @@ var RepoFileService = class {
|
|
|
11169
11642
|
const repo = repos.find((r) => r.name === repoName);
|
|
11170
11643
|
if (!repo) return null;
|
|
11171
11644
|
try {
|
|
11172
|
-
const fullPath = await realpath(resolve2(
|
|
11645
|
+
const fullPath = await realpath(resolve2(join20(repo.path, filePath)));
|
|
11173
11646
|
const repoRoot = await realpath(repo.path);
|
|
11174
11647
|
const repoPrefix = repoRoot.endsWith("/") ? repoRoot : repoRoot + "/";
|
|
11175
11648
|
if (!fullPath.startsWith(repoPrefix) && fullPath !== repoRoot) return null;
|
|
@@ -11198,7 +11671,7 @@ var RepoFileService = class {
|
|
|
11198
11671
|
tooLarge: true
|
|
11199
11672
|
};
|
|
11200
11673
|
}
|
|
11201
|
-
const content = await
|
|
11674
|
+
const content = await readFile14(fullPath, "utf-8");
|
|
11202
11675
|
return {
|
|
11203
11676
|
repoName,
|
|
11204
11677
|
path: filePath,
|
|
@@ -11276,21 +11749,21 @@ var RepoFileService = class {
|
|
|
11276
11749
|
// src/v1-routes.ts
|
|
11277
11750
|
import { Hono } from "hono";
|
|
11278
11751
|
import { z as z2 } from "zod";
|
|
11279
|
-
import { readdir as readdir7, stat as stat5, readFile as
|
|
11280
|
-
import { join as
|
|
11752
|
+
import { readdir as readdir7, stat as stat5, readFile as readFile17 } from "fs/promises";
|
|
11753
|
+
import { join as join23, resolve as resolve3 } from "path";
|
|
11281
11754
|
|
|
11282
11755
|
// src/services/warm-hooks-service.ts
|
|
11283
11756
|
import { spawn as spawn4 } from "child_process";
|
|
11284
|
-
import { readFile as
|
|
11757
|
+
import { readFile as readFile16 } from "fs/promises";
|
|
11285
11758
|
import { existsSync as existsSync8 } from "fs";
|
|
11286
|
-
import { join as
|
|
11759
|
+
import { join as join22 } from "path";
|
|
11287
11760
|
|
|
11288
11761
|
// src/services/warm-hook-logs-service.ts
|
|
11289
|
-
import { mkdir as
|
|
11762
|
+
import { mkdir as mkdir14, readFile as readFile15, writeFile as writeFile6, readdir as readdir6, appendFile as appendFile4, unlink as unlink3 } from "fs/promises";
|
|
11290
11763
|
import { homedir as homedir15 } from "os";
|
|
11291
|
-
import { join as
|
|
11292
|
-
var LOGS_DIR2 =
|
|
11293
|
-
var CURRENT_RUN_LOG =
|
|
11764
|
+
import { join as join21 } from "path";
|
|
11765
|
+
var LOGS_DIR2 = join21(homedir15(), ".replicas", "warm-hook-logs");
|
|
11766
|
+
var CURRENT_RUN_LOG = join21(LOGS_DIR2, "current-run.log");
|
|
11294
11767
|
var GLOBAL_FILENAME = "global.json";
|
|
11295
11768
|
function withPreview2(stored) {
|
|
11296
11769
|
const preview = buildHookOutputPreview(stored.output);
|
|
@@ -11298,7 +11771,7 @@ function withPreview2(stored) {
|
|
|
11298
11771
|
}
|
|
11299
11772
|
var WarmHookLogsService = class {
|
|
11300
11773
|
async ensureDir() {
|
|
11301
|
-
await
|
|
11774
|
+
await mkdir14(LOGS_DIR2, { recursive: true });
|
|
11302
11775
|
}
|
|
11303
11776
|
async saveGlobalHookLog(entry) {
|
|
11304
11777
|
await this.ensureDir();
|
|
@@ -11307,7 +11780,7 @@ var WarmHookLogsService = class {
|
|
|
11307
11780
|
hookName: "organization",
|
|
11308
11781
|
...entry
|
|
11309
11782
|
};
|
|
11310
|
-
await writeFile6(
|
|
11783
|
+
await writeFile6(join21(LOGS_DIR2, GLOBAL_FILENAME), `${JSON.stringify(log, null, 2)}
|
|
11311
11784
|
`, "utf-8");
|
|
11312
11785
|
}
|
|
11313
11786
|
async saveEnvironmentHookLog(entry) {
|
|
@@ -11317,7 +11790,7 @@ var WarmHookLogsService = class {
|
|
|
11317
11790
|
hookName: "environment",
|
|
11318
11791
|
...entry
|
|
11319
11792
|
};
|
|
11320
|
-
await writeFile6(
|
|
11793
|
+
await writeFile6(join21(LOGS_DIR2, ENVIRONMENT_HOOK_LOG_FILENAME), `${JSON.stringify(log, null, 2)}
|
|
11321
11794
|
`, "utf-8");
|
|
11322
11795
|
}
|
|
11323
11796
|
async saveRepoHookLog(repoName, entry) {
|
|
@@ -11327,7 +11800,7 @@ var WarmHookLogsService = class {
|
|
|
11327
11800
|
hookName: repoName,
|
|
11328
11801
|
...entry
|
|
11329
11802
|
};
|
|
11330
|
-
await writeFile6(
|
|
11803
|
+
await writeFile6(join21(LOGS_DIR2, repoHookLogFilename(repoName)), `${JSON.stringify(log, null, 2)}
|
|
11331
11804
|
`, "utf-8");
|
|
11332
11805
|
}
|
|
11333
11806
|
async getAllLogs() {
|
|
@@ -11346,7 +11819,7 @@ var WarmHookLogsService = class {
|
|
|
11346
11819
|
continue;
|
|
11347
11820
|
}
|
|
11348
11821
|
try {
|
|
11349
|
-
const raw = await
|
|
11822
|
+
const raw = await readFile15(join21(LOGS_DIR2, file), "utf-8");
|
|
11350
11823
|
const stored = JSON.parse(raw);
|
|
11351
11824
|
logs.push(withPreview2(stored));
|
|
11352
11825
|
} catch {
|
|
@@ -11375,7 +11848,7 @@ var WarmHookLogsService = class {
|
|
|
11375
11848
|
}
|
|
11376
11849
|
async getCurrentRunLog() {
|
|
11377
11850
|
try {
|
|
11378
|
-
return await
|
|
11851
|
+
return await readFile15(CURRENT_RUN_LOG, "utf-8");
|
|
11379
11852
|
} catch (err) {
|
|
11380
11853
|
if (err.code === "ENOENT") return null;
|
|
11381
11854
|
throw err;
|
|
@@ -11384,7 +11857,7 @@ var WarmHookLogsService = class {
|
|
|
11384
11857
|
async getFullOutput(hookType, hookName) {
|
|
11385
11858
|
const filename = hookType === "global" ? GLOBAL_FILENAME : hookType === "environment" ? ENVIRONMENT_HOOK_LOG_FILENAME : repoHookLogFilename(hookName);
|
|
11386
11859
|
try {
|
|
11387
|
-
const raw = await
|
|
11860
|
+
const raw = await readFile15(join21(LOGS_DIR2, filename), "utf-8");
|
|
11388
11861
|
const stored = JSON.parse(raw);
|
|
11389
11862
|
if (stored.hookType !== hookType || stored.hookName !== hookName) {
|
|
11390
11863
|
return null;
|
|
@@ -11403,12 +11876,12 @@ var warmHookLogsService = new WarmHookLogsService();
|
|
|
11403
11876
|
// src/services/warm-hooks-service.ts
|
|
11404
11877
|
async function readRepoWarmHook(repoPath) {
|
|
11405
11878
|
for (const filename of REPLICAS_CONFIG_FILENAMES) {
|
|
11406
|
-
const configPath =
|
|
11879
|
+
const configPath = join22(repoPath, filename);
|
|
11407
11880
|
if (!existsSync8(configPath)) {
|
|
11408
11881
|
continue;
|
|
11409
11882
|
}
|
|
11410
11883
|
try {
|
|
11411
|
-
const raw = await
|
|
11884
|
+
const raw = await readFile16(configPath, "utf-8");
|
|
11412
11885
|
const config = parseReplicasConfigString(raw, filename);
|
|
11413
11886
|
if (!config.warmHook) {
|
|
11414
11887
|
return null;
|
|
@@ -11668,7 +12141,7 @@ var setWorkspaceNameSchema = z2.object({
|
|
|
11668
12141
|
name: z2.string().min(1).max(48)
|
|
11669
12142
|
});
|
|
11670
12143
|
var createChatSchema = z2.object({
|
|
11671
|
-
provider: z2.enum(["claude", "codex", "cursor", "relay"]),
|
|
12144
|
+
provider: z2.enum(["claude", "codex", "cursor", "opencode", "relay"]),
|
|
11672
12145
|
title: z2.string().min(1).optional(),
|
|
11673
12146
|
parentChatId: z2.string().uuid().optional()
|
|
11674
12147
|
});
|
|
@@ -12358,7 +12831,7 @@ function createV1Routes(deps) {
|
|
|
12358
12831
|
const logFiles = files.filter((f) => f.endsWith(".log"));
|
|
12359
12832
|
const sessions = await Promise.all(
|
|
12360
12833
|
logFiles.map(async (filename) => {
|
|
12361
|
-
const filePath =
|
|
12834
|
+
const filePath = join23(LOG_DIR, filename);
|
|
12362
12835
|
const fileStat = await stat5(filePath);
|
|
12363
12836
|
const sessionId = filename.replace(/\.log$/, "");
|
|
12364
12837
|
return {
|
|
@@ -12395,7 +12868,7 @@ function createV1Routes(deps) {
|
|
|
12395
12868
|
const limit = Math.min(parseInt(c.req.query("limit") || "500", 10), 5e3);
|
|
12396
12869
|
let content;
|
|
12397
12870
|
try {
|
|
12398
|
-
content = await
|
|
12871
|
+
content = await readFile17(filePath, "utf-8");
|
|
12399
12872
|
} catch {
|
|
12400
12873
|
return c.json(jsonError("Log session not found"), 404);
|
|
12401
12874
|
}
|