@codelia/runtime 0.1.3 → 0.1.10
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/index.cjs +2082 -331
- package/dist/index.js +2139 -374
- package/package.json +9 -9
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// src/runtime.ts
|
|
2
|
-
import
|
|
2
|
+
import path15 from "path";
|
|
3
|
+
import { SessionStateStoreImpl as SessionStateStoreImpl2 } from "@codelia/storage";
|
|
3
4
|
|
|
4
5
|
// src/agent-factory.ts
|
|
6
|
+
import { promises as fs14 } from "fs";
|
|
5
7
|
import {
|
|
6
8
|
Agent,
|
|
7
9
|
ChatAnthropic,
|
|
@@ -840,10 +842,13 @@ var describeRpcMessage = (msg) => {
|
|
|
840
842
|
}
|
|
841
843
|
return "unknown";
|
|
842
844
|
};
|
|
845
|
+
var serializeMessage = (msg) => ({
|
|
846
|
+
payload: `${JSON.stringify(msg)}
|
|
847
|
+
`,
|
|
848
|
+
label: describeRpcMessage(msg)
|
|
849
|
+
});
|
|
843
850
|
var send = (msg) => {
|
|
844
|
-
const payload =
|
|
845
|
-
`;
|
|
846
|
-
const label = describeRpcMessage(msg);
|
|
851
|
+
const { payload, label } = serializeMessage(msg);
|
|
847
852
|
const writable = process.stdout.write(payload, (error) => {
|
|
848
853
|
if (error) {
|
|
849
854
|
debugLog(`transport.write.error label=${label} message=${error.message}`);
|
|
@@ -853,6 +858,20 @@ var send = (msg) => {
|
|
|
853
858
|
debugLog(`transport.backpressure label=${label} bytes=${payload.length}`);
|
|
854
859
|
}
|
|
855
860
|
};
|
|
861
|
+
var sendAsync = async (msg) => new Promise((resolve) => {
|
|
862
|
+
const { payload, label } = serializeMessage(msg);
|
|
863
|
+
const writable = process.stdout.write(payload, (error) => {
|
|
864
|
+
if (error) {
|
|
865
|
+
debugLog(
|
|
866
|
+
`transport.write.error label=${label} message=${error.message}`
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
resolve();
|
|
870
|
+
});
|
|
871
|
+
if (!writable) {
|
|
872
|
+
debugLog(`transport.backpressure label=${label} bytes=${payload.length}`);
|
|
873
|
+
}
|
|
874
|
+
});
|
|
856
875
|
var sendError = (id, error) => {
|
|
857
876
|
const response = { jsonrpc: "2.0", id, error };
|
|
858
877
|
send(response);
|
|
@@ -876,6 +895,21 @@ var sendAgentEvent = (state, runId, event) => {
|
|
|
876
895
|
send(notify);
|
|
877
896
|
return seq;
|
|
878
897
|
};
|
|
898
|
+
var sendAgentEventAsync = async (state, runId, event) => {
|
|
899
|
+
if (state.shouldSuppressEvent(runId)) return null;
|
|
900
|
+
const seq = state.nextSequence(runId);
|
|
901
|
+
const notify = {
|
|
902
|
+
jsonrpc: "2.0",
|
|
903
|
+
method: "agent.event",
|
|
904
|
+
params: {
|
|
905
|
+
run_id: runId,
|
|
906
|
+
seq,
|
|
907
|
+
event
|
|
908
|
+
}
|
|
909
|
+
};
|
|
910
|
+
await sendAsync(notify);
|
|
911
|
+
return seq;
|
|
912
|
+
};
|
|
879
913
|
var sendRunStatus = (runId, status, message) => {
|
|
880
914
|
const notify = {
|
|
881
915
|
jsonrpc: "2.0",
|
|
@@ -888,6 +922,18 @@ var sendRunStatus = (runId, status, message) => {
|
|
|
888
922
|
};
|
|
889
923
|
send(notify);
|
|
890
924
|
};
|
|
925
|
+
var sendRunStatusAsync = async (runId, status, message) => {
|
|
926
|
+
const notify = {
|
|
927
|
+
jsonrpc: "2.0",
|
|
928
|
+
method: "run.status",
|
|
929
|
+
params: {
|
|
930
|
+
run_id: runId,
|
|
931
|
+
status,
|
|
932
|
+
message
|
|
933
|
+
}
|
|
934
|
+
};
|
|
935
|
+
await sendAsync(notify);
|
|
936
|
+
};
|
|
891
937
|
var sendRunContext = (runId, contextLeftPercent) => {
|
|
892
938
|
const notify = {
|
|
893
939
|
jsonrpc: "2.0",
|
|
@@ -966,10 +1012,15 @@ var AuthStore = class {
|
|
|
966
1012
|
};
|
|
967
1013
|
|
|
968
1014
|
// src/auth/resolver.ts
|
|
969
|
-
var SUPPORTED_PROVIDERS = [
|
|
1015
|
+
var SUPPORTED_PROVIDERS = [
|
|
1016
|
+
"openai",
|
|
1017
|
+
"anthropic",
|
|
1018
|
+
"openrouter"
|
|
1019
|
+
];
|
|
970
1020
|
var API_KEY_ENV = {
|
|
971
1021
|
openai: "OPENAI_API_KEY",
|
|
972
|
-
anthropic: "ANTHROPIC_API_KEY"
|
|
1022
|
+
anthropic: "ANTHROPIC_API_KEY",
|
|
1023
|
+
openrouter: "OPENROUTER_API_KEY"
|
|
973
1024
|
};
|
|
974
1025
|
var AuthResolver = class _AuthResolver {
|
|
975
1026
|
auth;
|
|
@@ -987,6 +1038,12 @@ var AuthResolver = class _AuthResolver {
|
|
|
987
1038
|
const auth = await store.load();
|
|
988
1039
|
return new _AuthResolver(state, log2, store, auth);
|
|
989
1040
|
}
|
|
1041
|
+
hasAnyAvailableAuth() {
|
|
1042
|
+
return SUPPORTED_PROVIDERS.some((provider) => {
|
|
1043
|
+
if (this.auth.providers[provider]) return true;
|
|
1044
|
+
return Boolean(readEnvValue(API_KEY_ENV[provider]));
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
990
1047
|
async resolveProvider(preferred) {
|
|
991
1048
|
const preferredProvider = preferred && SUPPORTED_PROVIDERS.includes(preferred) ? preferred : null;
|
|
992
1049
|
if (preferredProvider) return preferredProvider;
|
|
@@ -1005,10 +1062,11 @@ var AuthResolver = class _AuthResolver {
|
|
|
1005
1062
|
return "openai";
|
|
1006
1063
|
}
|
|
1007
1064
|
const result = await requestUiPick(this.state, {
|
|
1008
|
-
title: "
|
|
1065
|
+
title: "Welcome! Choose your provider to get started.",
|
|
1009
1066
|
items: SUPPORTED_PROVIDERS.map((provider) => ({
|
|
1010
1067
|
id: provider,
|
|
1011
|
-
label: provider
|
|
1068
|
+
label: provider,
|
|
1069
|
+
detail: provider === "openai" ? "OAuth (ChatGPT Plus/Pro) or API key" : provider === "openrouter" ? "API key (OpenRouter)" : "API key"
|
|
1012
1070
|
})),
|
|
1013
1071
|
multi: false
|
|
1014
1072
|
});
|
|
@@ -1031,7 +1089,17 @@ var AuthResolver = class _AuthResolver {
|
|
|
1031
1089
|
if (provider === "openai") {
|
|
1032
1090
|
return this.promptOpenAiAuth();
|
|
1033
1091
|
}
|
|
1034
|
-
return this.promptApiKey(provider,
|
|
1092
|
+
return this.promptApiKey(provider, this.getApiKeyPromptLabel(provider));
|
|
1093
|
+
}
|
|
1094
|
+
getApiKeyPromptLabel(provider) {
|
|
1095
|
+
switch (provider) {
|
|
1096
|
+
case "anthropic":
|
|
1097
|
+
return "Anthropic API key";
|
|
1098
|
+
case "openrouter":
|
|
1099
|
+
return "OpenRouter API key";
|
|
1100
|
+
default:
|
|
1101
|
+
return "API key";
|
|
1102
|
+
}
|
|
1035
1103
|
}
|
|
1036
1104
|
async getOpenAiAccessToken() {
|
|
1037
1105
|
const entry = this.auth.providers.openai;
|
|
@@ -1060,10 +1128,18 @@ var AuthResolver = class _AuthResolver {
|
|
|
1060
1128
|
throw new Error("UI does not support auth prompts");
|
|
1061
1129
|
}
|
|
1062
1130
|
const pick = await requestUiPick(this.state, {
|
|
1063
|
-
title: "
|
|
1131
|
+
title: "How would you like to connect OpenAI?",
|
|
1064
1132
|
items: [
|
|
1065
|
-
{
|
|
1066
|
-
|
|
1133
|
+
{
|
|
1134
|
+
id: "oauth",
|
|
1135
|
+
label: "ChatGPT Plus/Pro (OAuth)",
|
|
1136
|
+
detail: "Recommended if you use ChatGPT subscription access"
|
|
1137
|
+
},
|
|
1138
|
+
{
|
|
1139
|
+
id: "api_key",
|
|
1140
|
+
label: "OpenAI API key",
|
|
1141
|
+
detail: "Use a standard OpenAI API key from platform settings"
|
|
1142
|
+
}
|
|
1067
1143
|
],
|
|
1068
1144
|
multi: false
|
|
1069
1145
|
});
|
|
@@ -1087,7 +1163,7 @@ var AuthResolver = class _AuthResolver {
|
|
|
1087
1163
|
const session = await createOAuthSession();
|
|
1088
1164
|
const confirm = await requestUiConfirm(this.state, {
|
|
1089
1165
|
title: "OpenAI OAuth",
|
|
1090
|
-
message: `Open your browser to
|
|
1166
|
+
message: `You're almost done. Open your browser to continue sign in.
|
|
1091
1167
|
|
|
1092
1168
|
${session.authUrl}`,
|
|
1093
1169
|
confirm_label: "Open browser",
|
|
@@ -1126,7 +1202,7 @@ ${session.authUrl}`,
|
|
|
1126
1202
|
}
|
|
1127
1203
|
const prompt = await requestUiPrompt(this.state, {
|
|
1128
1204
|
title: label,
|
|
1129
|
-
message: "
|
|
1205
|
+
message: "Paste your API key to continue. You can change it later with /logout.",
|
|
1130
1206
|
secret: true
|
|
1131
1207
|
});
|
|
1132
1208
|
const value = prompt?.value?.trim() ?? "";
|
|
@@ -1327,7 +1403,8 @@ var createMcpOAuthSession = async (params) => {
|
|
|
1327
1403
|
import { applyModelMetadata, DEFAULT_MODEL_REGISTRY } from "@codelia/core";
|
|
1328
1404
|
import { ModelMetadataServiceImpl } from "@codelia/model-metadata";
|
|
1329
1405
|
import { StoragePathServiceImpl as StoragePathServiceImpl2 } from "@codelia/storage";
|
|
1330
|
-
var buildModelRegistry = async (llm) => {
|
|
1406
|
+
var buildModelRegistry = async (llm, options = {}) => {
|
|
1407
|
+
const strict = options.strict ?? true;
|
|
1331
1408
|
const metadataService = new ModelMetadataServiceImpl({
|
|
1332
1409
|
storagePathService: new StoragePathServiceImpl2()
|
|
1333
1410
|
});
|
|
@@ -1342,9 +1419,11 @@ var buildModelRegistry = async (llm) => {
|
|
|
1342
1419
|
fullIdEntry = providerEntries?.[`${llm.provider}/${llm.model}`];
|
|
1343
1420
|
}
|
|
1344
1421
|
if (!directEntry && !fullIdEntry) {
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1422
|
+
if (strict) {
|
|
1423
|
+
throw new Error(
|
|
1424
|
+
`Model metadata not found for ${llm.provider}/${llm.model} after refresh`
|
|
1425
|
+
);
|
|
1426
|
+
}
|
|
1348
1427
|
}
|
|
1349
1428
|
return applyModelMetadata(DEFAULT_MODEL_REGISTRY, { models: entries });
|
|
1350
1429
|
};
|
|
@@ -1701,6 +1780,8 @@ var SYSTEM_TOOL_ALLOWLIST = [
|
|
|
1701
1780
|
"agents_resolve",
|
|
1702
1781
|
"skill_search",
|
|
1703
1782
|
"skill_load",
|
|
1783
|
+
"lane_list",
|
|
1784
|
+
"lane_status",
|
|
1704
1785
|
"done"
|
|
1705
1786
|
];
|
|
1706
1787
|
var SYSTEM_BASH_ALLOWLIST = [
|
|
@@ -1792,7 +1873,27 @@ var PermissionService = class {
|
|
|
1792
1873
|
message: `${skillName ? skillName : explicitPath || `skill_load ${rawArgs}`}${rememberPreview}`
|
|
1793
1874
|
};
|
|
1794
1875
|
}
|
|
1795
|
-
|
|
1876
|
+
if (toolName === "write") {
|
|
1877
|
+
const parsed = parseRawArgsForPrompt(rawArgs);
|
|
1878
|
+
const filePath = parsed && typeof parsed.file_path === "string" ? parsed.file_path.trim() : "";
|
|
1879
|
+
const content = parsed && typeof parsed.content === "string" ? parsed.content : "";
|
|
1880
|
+
const target = filePath || "(unknown path)";
|
|
1881
|
+
return {
|
|
1882
|
+
title: "Run tool?",
|
|
1883
|
+
message: `write ${target} (${content.length} bytes)${rememberPreview}`
|
|
1884
|
+
};
|
|
1885
|
+
}
|
|
1886
|
+
if (toolName === "edit") {
|
|
1887
|
+
const parsed = parseRawArgsForPrompt(rawArgs);
|
|
1888
|
+
const filePath = parsed && typeof parsed.file_path === "string" ? parsed.file_path.trim() : "";
|
|
1889
|
+
const target = filePath || "(unknown path)";
|
|
1890
|
+
const mode = parsed && typeof parsed.match_mode === "string" ? parsed.match_mode : "auto";
|
|
1891
|
+
return {
|
|
1892
|
+
title: "Run tool?",
|
|
1893
|
+
message: `edit ${target} (match=${mode})${rememberPreview}`
|
|
1894
|
+
};
|
|
1895
|
+
}
|
|
1896
|
+
return { title: "Run tool?", message: toolName };
|
|
1796
1897
|
}
|
|
1797
1898
|
evaluate(toolName, rawArgs) {
|
|
1798
1899
|
if (toolName === "bash") {
|
|
@@ -3288,151 +3389,877 @@ var createGrepTool = (sandboxKey) => defineTool6({
|
|
|
3288
3389
|
}
|
|
3289
3390
|
});
|
|
3290
3391
|
|
|
3291
|
-
// src/tools/
|
|
3292
|
-
import { promises as fs10 } from "fs";
|
|
3392
|
+
// src/tools/lane.ts
|
|
3293
3393
|
import { defineTool as defineTool7 } from "@codelia/core";
|
|
3294
3394
|
import { z as z8 } from "zod";
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3395
|
+
|
|
3396
|
+
// src/lanes/manager.ts
|
|
3397
|
+
import crypto4 from "crypto";
|
|
3398
|
+
import { promises as fs11 } from "fs";
|
|
3399
|
+
import os2 from "os";
|
|
3400
|
+
import path11 from "path";
|
|
3401
|
+
|
|
3402
|
+
// src/lanes/command.ts
|
|
3403
|
+
import { spawn as spawn3 } from "child_process";
|
|
3404
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
3405
|
+
var MAX_CAPTURE_BYTES = 512 * 1024;
|
|
3406
|
+
var truncate2 = (value) => {
|
|
3407
|
+
if (value.length <= 8e3) return value;
|
|
3408
|
+
return `${value.slice(0, 8e3)}...[truncated]`;
|
|
3409
|
+
};
|
|
3410
|
+
var runCommand = async (command, args, options = {}) => {
|
|
3411
|
+
const timeoutMs = Math.max(1e3, options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
3412
|
+
return new Promise((resolve, reject) => {
|
|
3413
|
+
const child = spawn3(command, args, {
|
|
3414
|
+
cwd: options.cwd,
|
|
3415
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
3416
|
+
});
|
|
3417
|
+
let stdout = "";
|
|
3418
|
+
let stderr = "";
|
|
3419
|
+
let bytes = 0;
|
|
3420
|
+
let done = false;
|
|
3421
|
+
const finish = (fn) => {
|
|
3422
|
+
if (done) return;
|
|
3423
|
+
done = true;
|
|
3424
|
+
clearTimeout(timer);
|
|
3425
|
+
fn();
|
|
3426
|
+
};
|
|
3427
|
+
const consume = (chunk, kind) => {
|
|
3428
|
+
const text = chunk.toString("utf8");
|
|
3429
|
+
bytes += Buffer.byteLength(text, "utf8");
|
|
3430
|
+
if (bytes > MAX_CAPTURE_BYTES) {
|
|
3431
|
+
finish(
|
|
3432
|
+
() => reject(
|
|
3433
|
+
new Error(
|
|
3434
|
+
`${command} output exceeded ${MAX_CAPTURE_BYTES} bytes while running ${args.join(" ")}`
|
|
3435
|
+
)
|
|
3436
|
+
)
|
|
3437
|
+
);
|
|
3438
|
+
try {
|
|
3439
|
+
child.kill("SIGTERM");
|
|
3440
|
+
} catch {
|
|
3441
|
+
}
|
|
3442
|
+
return;
|
|
3318
3443
|
}
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
const content = await fs10.readFile(resolved, "utf8");
|
|
3324
|
-
const lines = content.split(/\r?\n/);
|
|
3325
|
-
if (lines.length === 0) {
|
|
3326
|
-
return "";
|
|
3444
|
+
if (kind === "stdout") {
|
|
3445
|
+
stdout += text;
|
|
3446
|
+
} else {
|
|
3447
|
+
stderr += text;
|
|
3327
3448
|
}
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3449
|
+
};
|
|
3450
|
+
child.stdout?.on("data", (chunk) => consume(chunk, "stdout"));
|
|
3451
|
+
child.stderr?.on("data", (chunk) => consume(chunk, "stderr"));
|
|
3452
|
+
child.on("error", (error) => {
|
|
3453
|
+
finish(() => reject(error));
|
|
3454
|
+
});
|
|
3455
|
+
child.on("close", (code) => {
|
|
3456
|
+
if (code === 0) {
|
|
3457
|
+
finish(() => resolve({ stdout, stderr }));
|
|
3458
|
+
return;
|
|
3332
3459
|
}
|
|
3333
|
-
const
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
}
|
|
3343
|
-
raw.push(line);
|
|
3344
|
-
bytes += size;
|
|
3460
|
+
const detail = `${stderr}
|
|
3461
|
+
${stdout}`.trim();
|
|
3462
|
+
const message = detail ? `${command} ${args.join(" ")} failed (exit ${String(code)}): ${truncate2(detail)}` : `${command} ${args.join(" ")} failed (exit ${String(code)})`;
|
|
3463
|
+
finish(() => reject(new Error(message)));
|
|
3464
|
+
});
|
|
3465
|
+
const timer = setTimeout(() => {
|
|
3466
|
+
try {
|
|
3467
|
+
child.kill("SIGTERM");
|
|
3468
|
+
} catch {
|
|
3345
3469
|
}
|
|
3346
|
-
|
|
3347
|
-
(
|
|
3470
|
+
finish(
|
|
3471
|
+
() => reject(
|
|
3472
|
+
new Error(
|
|
3473
|
+
`${command} ${args.join(" ")} timed out after ${Math.floor(timeoutMs / 1e3)}s`
|
|
3474
|
+
)
|
|
3475
|
+
)
|
|
3348
3476
|
);
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
let output = numbered.join("\n");
|
|
3353
|
-
if (truncated) {
|
|
3354
|
-
const reason = truncatedByBytes ? `Output truncated at ${MAX_BYTES} bytes.` : "File has more lines.";
|
|
3355
|
-
output += `
|
|
3477
|
+
}, timeoutMs);
|
|
3478
|
+
});
|
|
3479
|
+
};
|
|
3356
3480
|
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3481
|
+
// src/lanes/registry.ts
|
|
3482
|
+
import { promises as fs10 } from "fs";
|
|
3483
|
+
import path10 from "path";
|
|
3484
|
+
import { resolveStoragePaths as resolveStoragePaths2 } from "@codelia/storage";
|
|
3485
|
+
var REGISTRY_DIRNAME = "lanes";
|
|
3486
|
+
var REGISTRY_FILENAME = "registry.json";
|
|
3487
|
+
var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
3488
|
+
var sortByUpdatedDesc = (lanes) => [...lanes].sort((a, b) => b.updated_at.localeCompare(a.updated_at));
|
|
3489
|
+
var readJsonFile = async (filePath) => {
|
|
3490
|
+
try {
|
|
3491
|
+
const raw = await fs10.readFile(filePath, "utf8");
|
|
3492
|
+
const parsed = JSON.parse(raw);
|
|
3493
|
+
if (!parsed || parsed.version !== 1 || !Array.isArray(parsed.lanes)) {
|
|
3494
|
+
return null;
|
|
3495
|
+
}
|
|
3496
|
+
return {
|
|
3497
|
+
version: 1,
|
|
3498
|
+
lanes: parsed.lanes
|
|
3499
|
+
};
|
|
3500
|
+
} catch {
|
|
3501
|
+
return null;
|
|
3502
|
+
}
|
|
3503
|
+
};
|
|
3504
|
+
var atomicWrite = async (filePath, payload) => {
|
|
3505
|
+
const dir = path10.dirname(filePath);
|
|
3506
|
+
const base = path10.basename(filePath);
|
|
3507
|
+
const tmp = path10.join(
|
|
3508
|
+
dir,
|
|
3509
|
+
`${base}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`
|
|
3510
|
+
);
|
|
3511
|
+
await fs10.writeFile(tmp, payload, "utf8");
|
|
3512
|
+
await fs10.rename(tmp, filePath);
|
|
3513
|
+
};
|
|
3514
|
+
var LaneRegistryStore = class {
|
|
3515
|
+
registryPath;
|
|
3516
|
+
constructor(registryPath) {
|
|
3517
|
+
if (registryPath) {
|
|
3518
|
+
this.registryPath = registryPath;
|
|
3519
|
+
return;
|
|
3362
3520
|
}
|
|
3521
|
+
const root = resolveStoragePaths2().root;
|
|
3522
|
+
this.registryPath = path10.join(root, REGISTRY_DIRNAME, REGISTRY_FILENAME);
|
|
3363
3523
|
}
|
|
3364
|
-
|
|
3524
|
+
async ensureDir() {
|
|
3525
|
+
await fs10.mkdir(path10.dirname(this.registryPath), { recursive: true });
|
|
3526
|
+
}
|
|
3527
|
+
async readAll() {
|
|
3528
|
+
await this.ensureDir();
|
|
3529
|
+
const data = await readJsonFile(this.registryPath);
|
|
3530
|
+
if (!data) return [];
|
|
3531
|
+
return sortByUpdatedDesc(data.lanes);
|
|
3532
|
+
}
|
|
3533
|
+
async writeAll(lanes) {
|
|
3534
|
+
await this.ensureDir();
|
|
3535
|
+
const payload = {
|
|
3536
|
+
version: 1,
|
|
3537
|
+
lanes: sortByUpdatedDesc(lanes)
|
|
3538
|
+
};
|
|
3539
|
+
await atomicWrite(
|
|
3540
|
+
this.registryPath,
|
|
3541
|
+
`${JSON.stringify(payload, null, 2)}
|
|
3542
|
+
`
|
|
3543
|
+
);
|
|
3544
|
+
}
|
|
3545
|
+
async list() {
|
|
3546
|
+
return this.readAll();
|
|
3547
|
+
}
|
|
3548
|
+
async get(laneId) {
|
|
3549
|
+
const lanes = await this.readAll();
|
|
3550
|
+
return lanes.find((lane) => lane.lane_id === laneId) ?? null;
|
|
3551
|
+
}
|
|
3552
|
+
async upsert(record) {
|
|
3553
|
+
const lanes = await this.readAll();
|
|
3554
|
+
const idx = lanes.findIndex((lane) => lane.lane_id === record.lane_id);
|
|
3555
|
+
const next = {
|
|
3556
|
+
...record,
|
|
3557
|
+
updated_at: nowIso()
|
|
3558
|
+
};
|
|
3559
|
+
if (idx >= 0) {
|
|
3560
|
+
lanes[idx] = next;
|
|
3561
|
+
} else {
|
|
3562
|
+
lanes.push(next);
|
|
3563
|
+
}
|
|
3564
|
+
await this.writeAll(lanes);
|
|
3565
|
+
}
|
|
3566
|
+
async patch(laneId, patch) {
|
|
3567
|
+
const lanes = await this.readAll();
|
|
3568
|
+
const idx = lanes.findIndex((lane) => lane.lane_id === laneId);
|
|
3569
|
+
if (idx < 0) return null;
|
|
3570
|
+
const current = lanes[idx];
|
|
3571
|
+
const next = {
|
|
3572
|
+
...current,
|
|
3573
|
+
...patch,
|
|
3574
|
+
lane_id: current.lane_id,
|
|
3575
|
+
created_at: current.created_at,
|
|
3576
|
+
updated_at: nowIso()
|
|
3577
|
+
};
|
|
3578
|
+
lanes[idx] = next;
|
|
3579
|
+
await this.writeAll(lanes);
|
|
3580
|
+
return next;
|
|
3581
|
+
}
|
|
3582
|
+
};
|
|
3365
3583
|
|
|
3366
|
-
// src/
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
var
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
}
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3584
|
+
// src/lanes/manager.ts
|
|
3585
|
+
var nowIso2 = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
3586
|
+
var INITIAL_MESSAGE_FLAG = "--initial-message";
|
|
3587
|
+
var toSlug = (value) => {
|
|
3588
|
+
const normalized = value.trim().toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replaceAll(/^-+|-+$/g, "");
|
|
3589
|
+
return normalized || "task";
|
|
3590
|
+
};
|
|
3591
|
+
var parseDate = (value) => {
|
|
3592
|
+
const ts = Date.parse(value);
|
|
3593
|
+
if (Number.isNaN(ts)) return 0;
|
|
3594
|
+
return ts;
|
|
3595
|
+
};
|
|
3596
|
+
var isNotFoundError = (error) => {
|
|
3597
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3598
|
+
return message.includes("command not found") || message.includes("ENOENT") || message.includes("not found");
|
|
3599
|
+
};
|
|
3600
|
+
var isExitFailure = (error) => {
|
|
3601
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3602
|
+
return message.includes("failed (exit");
|
|
3603
|
+
};
|
|
3604
|
+
var LaneManagerError = class extends Error {
|
|
3605
|
+
code;
|
|
3606
|
+
constructor(code, message) {
|
|
3607
|
+
super(message);
|
|
3608
|
+
this.code = code;
|
|
3609
|
+
this.name = "LaneManagerError";
|
|
3610
|
+
}
|
|
3611
|
+
};
|
|
3612
|
+
var LaneManager = class {
|
|
3613
|
+
runner;
|
|
3614
|
+
registry;
|
|
3615
|
+
now;
|
|
3616
|
+
randomId;
|
|
3617
|
+
launchCommand;
|
|
3618
|
+
defaultWorktreeRoot;
|
|
3619
|
+
constructor(options = {}) {
|
|
3620
|
+
this.runner = options.runner ?? runCommand;
|
|
3621
|
+
this.registry = options.registry ?? new LaneRegistryStore();
|
|
3622
|
+
this.now = options.now ?? nowIso2;
|
|
3623
|
+
this.randomId = options.randomId ?? (() => crypto4.randomUUID());
|
|
3624
|
+
this.launchCommand = options.launchCommand ?? process.env.CODELIA_LANE_LAUNCH_COMMAND?.trim() ?? "codelia";
|
|
3625
|
+
this.defaultWorktreeRoot = options.defaultWorktreeRoot ?? path11.join(os2.homedir(), ".codelia", "worktrees");
|
|
3626
|
+
}
|
|
3627
|
+
async cmd(command, args, options) {
|
|
3628
|
+
debugLog(`lane.cmd ${command} ${args.join(" ")}`);
|
|
3629
|
+
return this.runner(command, args, options);
|
|
3630
|
+
}
|
|
3631
|
+
async resolveRepoRoot(workingDir) {
|
|
3632
|
+
const { stdout } = await this.cmd(
|
|
3633
|
+
"git",
|
|
3634
|
+
["-C", workingDir, "rev-parse", "--show-toplevel"],
|
|
3635
|
+
{ cwd: workingDir }
|
|
3636
|
+
);
|
|
3637
|
+
const root = stdout.trim();
|
|
3638
|
+
if (!root) {
|
|
3639
|
+
throw new LaneManagerError(
|
|
3640
|
+
"backend_command_failed",
|
|
3641
|
+
"Could not resolve git repository root."
|
|
3642
|
+
);
|
|
3643
|
+
}
|
|
3644
|
+
return root;
|
|
3645
|
+
}
|
|
3646
|
+
async preflightBackend(backend) {
|
|
3647
|
+
if (backend === "tmux") {
|
|
3648
|
+
try {
|
|
3649
|
+
await this.cmd("tmux", ["-V"]);
|
|
3650
|
+
return;
|
|
3651
|
+
} catch (error) {
|
|
3652
|
+
if (isNotFoundError(error)) {
|
|
3653
|
+
throw new LaneManagerError(
|
|
3654
|
+
"backend_not_found",
|
|
3655
|
+
"tmux not found in PATH."
|
|
3656
|
+
);
|
|
3657
|
+
}
|
|
3658
|
+
throw new LaneManagerError(
|
|
3659
|
+
"backend_command_failed",
|
|
3660
|
+
`tmux preflight failed: ${String(error)}`
|
|
3661
|
+
);
|
|
3662
|
+
}
|
|
3663
|
+
}
|
|
3381
3664
|
try {
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
if (
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
already_loaded: null,
|
|
3390
|
-
skill: null,
|
|
3391
|
-
content: null,
|
|
3392
|
-
files: [],
|
|
3393
|
-
files_truncated: false
|
|
3394
|
-
};
|
|
3665
|
+
await this.cmd("zellij", ["--version"]);
|
|
3666
|
+
} catch (error) {
|
|
3667
|
+
if (isNotFoundError(error)) {
|
|
3668
|
+
throw new LaneManagerError(
|
|
3669
|
+
"backend_not_found",
|
|
3670
|
+
"zellij not found in PATH."
|
|
3671
|
+
);
|
|
3395
3672
|
}
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3673
|
+
throw new LaneManagerError(
|
|
3674
|
+
"backend_command_failed",
|
|
3675
|
+
`zellij preflight failed: ${String(error)}`
|
|
3676
|
+
);
|
|
3677
|
+
}
|
|
3678
|
+
throw new LaneManagerError(
|
|
3679
|
+
"backend_command_failed",
|
|
3680
|
+
"zellij backend is not supported yet in lane MVP. Use tmux."
|
|
3681
|
+
);
|
|
3682
|
+
}
|
|
3683
|
+
async startTmuxLane(target, worktreePath, seedContext) {
|
|
3684
|
+
const launchCommand = this.buildLaunchCommand(seedContext);
|
|
3685
|
+
await this.cmd("tmux", [
|
|
3686
|
+
"new-session",
|
|
3687
|
+
"-d",
|
|
3688
|
+
"-s",
|
|
3689
|
+
target,
|
|
3690
|
+
"-c",
|
|
3691
|
+
worktreePath
|
|
3692
|
+
]);
|
|
3693
|
+
const paneTarget = `${target}:0.0`;
|
|
3694
|
+
await this.cmd("tmux", [
|
|
3695
|
+
"send-keys",
|
|
3696
|
+
"-t",
|
|
3697
|
+
paneTarget,
|
|
3698
|
+
"-l",
|
|
3699
|
+
launchCommand
|
|
3700
|
+
]);
|
|
3701
|
+
await this.cmd("tmux", ["send-keys", "-t", paneTarget, "Enter"]);
|
|
3702
|
+
}
|
|
3703
|
+
shellQuote(value) {
|
|
3704
|
+
if (!value) return "''";
|
|
3705
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
3706
|
+
}
|
|
3707
|
+
buildLaunchCommand(seedContext) {
|
|
3708
|
+
const seed = seedContext?.trim();
|
|
3709
|
+
if (!seed) {
|
|
3710
|
+
return this.launchCommand;
|
|
3711
|
+
}
|
|
3712
|
+
return `${this.launchCommand} ${INITIAL_MESSAGE_FLAG} ${this.shellQuote(seed)}`;
|
|
3713
|
+
}
|
|
3714
|
+
async isTmuxAlive(target) {
|
|
3715
|
+
try {
|
|
3716
|
+
await this.cmd("tmux", ["has-session", "-t", target], {
|
|
3717
|
+
timeoutMs: 5e3
|
|
3718
|
+
});
|
|
3719
|
+
return true;
|
|
3406
3720
|
} catch (error) {
|
|
3407
|
-
return
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
skill: null,
|
|
3413
|
-
content: null,
|
|
3414
|
-
files: [],
|
|
3415
|
-
files_truncated: false
|
|
3416
|
-
};
|
|
3721
|
+
if (isExitFailure(error)) return false;
|
|
3722
|
+
throw new LaneManagerError(
|
|
3723
|
+
"backend_command_failed",
|
|
3724
|
+
`tmux status failed: ${String(error)}`
|
|
3725
|
+
);
|
|
3417
3726
|
}
|
|
3418
3727
|
}
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
// src/tools/skill-search.ts
|
|
3422
|
-
import { defineTool as defineTool9 } from "@codelia/core";
|
|
3423
|
-
import { z as z10 } from "zod";
|
|
3424
|
-
var createSkillSearchTool = (skillsResolverKey) => defineTool9({
|
|
3425
|
-
name: "skill_search",
|
|
3426
|
-
description: "Search installed local skills by name, description, or path.",
|
|
3427
|
-
input: z10.object({
|
|
3428
|
-
query: z10.string().min(1).describe("Search query text."),
|
|
3429
|
-
limit: z10.number().int().positive().optional().describe("Optional max result count (clamped by config)."),
|
|
3430
|
-
scope: z10.enum(["repo", "user"]).optional().describe("Optional scope filter.")
|
|
3431
|
-
}),
|
|
3432
|
-
execute: async (input, ctx) => {
|
|
3728
|
+
async stopTmuxLane(target) {
|
|
3433
3729
|
try {
|
|
3434
|
-
|
|
3435
|
-
|
|
3730
|
+
await this.cmd("tmux", ["kill-session", "-t", target], {
|
|
3731
|
+
timeoutMs: 1e4
|
|
3732
|
+
});
|
|
3733
|
+
} catch (error) {
|
|
3734
|
+
if (isExitFailure(error)) return;
|
|
3735
|
+
throw new LaneManagerError(
|
|
3736
|
+
"backend_command_failed",
|
|
3737
|
+
`tmux close failed: ${String(error)}`
|
|
3738
|
+
);
|
|
3739
|
+
}
|
|
3740
|
+
}
|
|
3741
|
+
async resolveWorktreeMainRoot(worktreePath) {
|
|
3742
|
+
const { stdout } = await this.cmd("git", [
|
|
3743
|
+
"-C",
|
|
3744
|
+
worktreePath,
|
|
3745
|
+
"rev-parse",
|
|
3746
|
+
"--git-common-dir"
|
|
3747
|
+
]);
|
|
3748
|
+
const commonDir = path11.resolve(worktreePath, stdout.trim());
|
|
3749
|
+
if (path11.basename(commonDir) === ".git") {
|
|
3750
|
+
return path11.dirname(commonDir);
|
|
3751
|
+
}
|
|
3752
|
+
return await this.resolveRepoRoot(worktreePath);
|
|
3753
|
+
}
|
|
3754
|
+
async isWorktreeDirty(worktreePath) {
|
|
3755
|
+
try {
|
|
3756
|
+
await fs11.access(worktreePath);
|
|
3757
|
+
} catch {
|
|
3758
|
+
return false;
|
|
3759
|
+
}
|
|
3760
|
+
const { stdout } = await this.cmd("git", [
|
|
3761
|
+
"-C",
|
|
3762
|
+
worktreePath,
|
|
3763
|
+
"status",
|
|
3764
|
+
"--porcelain"
|
|
3765
|
+
]);
|
|
3766
|
+
return stdout.trim().length > 0;
|
|
3767
|
+
}
|
|
3768
|
+
async removeWorktree(worktreePath, force) {
|
|
3769
|
+
try {
|
|
3770
|
+
await fs11.access(worktreePath);
|
|
3771
|
+
} catch {
|
|
3772
|
+
return;
|
|
3773
|
+
}
|
|
3774
|
+
const repoRoot = await this.resolveWorktreeMainRoot(worktreePath);
|
|
3775
|
+
const args = ["-C", repoRoot, "worktree", "remove"];
|
|
3776
|
+
if (force) args.push("--force");
|
|
3777
|
+
args.push(worktreePath);
|
|
3778
|
+
await this.cmd("git", args);
|
|
3779
|
+
}
|
|
3780
|
+
async create(input, options) {
|
|
3781
|
+
const taskId = input.task_id.trim();
|
|
3782
|
+
if (!taskId) {
|
|
3783
|
+
throw new LaneManagerError(
|
|
3784
|
+
"backend_command_failed",
|
|
3785
|
+
"task_id is required."
|
|
3786
|
+
);
|
|
3787
|
+
}
|
|
3788
|
+
const backend = input.mux_backend ?? "tmux";
|
|
3789
|
+
await this.preflightBackend(backend);
|
|
3790
|
+
const repoRoot = await this.resolveRepoRoot(options.workingDir);
|
|
3791
|
+
const laneId = this.randomId();
|
|
3792
|
+
const slug = toSlug(taskId);
|
|
3793
|
+
const shortId = laneId.slice(0, 8);
|
|
3794
|
+
const branchName = `lane/${slug}-${shortId}`;
|
|
3795
|
+
const worktreePath = input.worktree_path ? path11.resolve(options.workingDir, input.worktree_path) : path11.join(this.defaultWorktreeRoot, `${slug}-${shortId}`);
|
|
3796
|
+
const muxTarget = `codelia-lane-${shortId}`;
|
|
3797
|
+
const createdAt = this.now();
|
|
3798
|
+
const record = {
|
|
3799
|
+
lane_id: laneId,
|
|
3800
|
+
task_id: taskId,
|
|
3801
|
+
state: "creating",
|
|
3802
|
+
mux_backend: backend,
|
|
3803
|
+
mux_target: muxTarget,
|
|
3804
|
+
worktree_path: worktreePath,
|
|
3805
|
+
branch_name: branchName,
|
|
3806
|
+
session_id: crypto4.randomUUID(),
|
|
3807
|
+
created_at: createdAt,
|
|
3808
|
+
updated_at: createdAt,
|
|
3809
|
+
last_activity_at: createdAt
|
|
3810
|
+
};
|
|
3811
|
+
await this.registry.upsert(record);
|
|
3812
|
+
try {
|
|
3813
|
+
await fs11.mkdir(path11.dirname(worktreePath), { recursive: true });
|
|
3814
|
+
await this.cmd("git", [
|
|
3815
|
+
"-C",
|
|
3816
|
+
repoRoot,
|
|
3817
|
+
"worktree",
|
|
3818
|
+
"add",
|
|
3819
|
+
"-b",
|
|
3820
|
+
branchName,
|
|
3821
|
+
worktreePath,
|
|
3822
|
+
input.base_ref?.trim() || "HEAD"
|
|
3823
|
+
]);
|
|
3824
|
+
if (backend === "tmux") {
|
|
3825
|
+
await this.startTmuxLane(muxTarget, worktreePath, input.seed_context);
|
|
3826
|
+
}
|
|
3827
|
+
const running = await this.registry.patch(laneId, {
|
|
3828
|
+
state: "running",
|
|
3829
|
+
last_activity_at: this.now(),
|
|
3830
|
+
last_error: void 0
|
|
3831
|
+
});
|
|
3832
|
+
if (!running) {
|
|
3833
|
+
throw new Error("lane record disappeared during create");
|
|
3834
|
+
}
|
|
3835
|
+
return running;
|
|
3836
|
+
} catch (error) {
|
|
3837
|
+
await this.registry.patch(laneId, {
|
|
3838
|
+
state: "error",
|
|
3839
|
+
last_error: String(error),
|
|
3840
|
+
last_activity_at: this.now()
|
|
3841
|
+
});
|
|
3842
|
+
throw new LaneManagerError(
|
|
3843
|
+
"backend_command_failed",
|
|
3844
|
+
`lane.create failed (${laneId}): ${String(error)}`
|
|
3845
|
+
);
|
|
3846
|
+
}
|
|
3847
|
+
}
|
|
3848
|
+
async list(input) {
|
|
3849
|
+
const all = await this.registry.list();
|
|
3850
|
+
if (input?.include_closed) return all;
|
|
3851
|
+
return all.filter((lane) => lane.state !== "closed");
|
|
3852
|
+
}
|
|
3853
|
+
async status(laneId) {
|
|
3854
|
+
const lane = await this.registry.get(laneId);
|
|
3855
|
+
if (!lane) {
|
|
3856
|
+
throw new LaneManagerError("lane_not_found", `Lane not found: ${laneId}`);
|
|
3857
|
+
}
|
|
3858
|
+
let alive = false;
|
|
3859
|
+
if (lane.state !== "closed") {
|
|
3860
|
+
if (lane.mux_backend === "tmux") {
|
|
3861
|
+
alive = await this.isTmuxAlive(lane.mux_target);
|
|
3862
|
+
}
|
|
3863
|
+
if (lane.state === "running" && !alive) {
|
|
3864
|
+
const patched = await this.registry.patch(lane.lane_id, {
|
|
3865
|
+
state: "finished",
|
|
3866
|
+
last_activity_at: this.now()
|
|
3867
|
+
});
|
|
3868
|
+
if (patched) {
|
|
3869
|
+
return { lane: patched, backend_alive: false };
|
|
3870
|
+
}
|
|
3871
|
+
}
|
|
3872
|
+
}
|
|
3873
|
+
return { lane, backend_alive: alive };
|
|
3874
|
+
}
|
|
3875
|
+
async close(input) {
|
|
3876
|
+
const lane = await this.registry.get(input.lane_id);
|
|
3877
|
+
if (!lane) {
|
|
3878
|
+
throw new LaneManagerError(
|
|
3879
|
+
"lane_not_found",
|
|
3880
|
+
`Lane not found: ${input.lane_id}`
|
|
3881
|
+
);
|
|
3882
|
+
}
|
|
3883
|
+
if (lane.state === "closed") {
|
|
3884
|
+
return lane;
|
|
3885
|
+
}
|
|
3886
|
+
const force = input.force === true;
|
|
3887
|
+
const removeWorktree = input.remove_worktree !== false;
|
|
3888
|
+
const status = await this.status(lane.lane_id);
|
|
3889
|
+
const refreshed = status.lane;
|
|
3890
|
+
if (status.backend_alive && !force) {
|
|
3891
|
+
throw new LaneManagerError(
|
|
3892
|
+
"lane_running",
|
|
3893
|
+
`Lane is still running: ${lane.lane_id}`
|
|
3894
|
+
);
|
|
3895
|
+
}
|
|
3896
|
+
if (status.backend_alive && refreshed.mux_backend === "tmux") {
|
|
3897
|
+
await this.stopTmuxLane(refreshed.mux_target);
|
|
3898
|
+
}
|
|
3899
|
+
if (removeWorktree) {
|
|
3900
|
+
const dirty = await this.isWorktreeDirty(refreshed.worktree_path);
|
|
3901
|
+
if (dirty && !force) {
|
|
3902
|
+
throw new LaneManagerError(
|
|
3903
|
+
"worktree_dirty",
|
|
3904
|
+
`Worktree has uncommitted changes: ${refreshed.worktree_path}`
|
|
3905
|
+
);
|
|
3906
|
+
}
|
|
3907
|
+
await this.removeWorktree(refreshed.worktree_path, force);
|
|
3908
|
+
}
|
|
3909
|
+
const closed = await this.registry.patch(refreshed.lane_id, {
|
|
3910
|
+
state: "closed",
|
|
3911
|
+
last_activity_at: this.now()
|
|
3912
|
+
});
|
|
3913
|
+
if (!closed) {
|
|
3914
|
+
throw new LaneManagerError(
|
|
3915
|
+
"backend_command_failed",
|
|
3916
|
+
"Failed to persist lane close state."
|
|
3917
|
+
);
|
|
3918
|
+
}
|
|
3919
|
+
return closed;
|
|
3920
|
+
}
|
|
3921
|
+
async gc(input) {
|
|
3922
|
+
const ttlMs = Math.max(1, input.idle_ttl_minutes) * 6e4;
|
|
3923
|
+
const now = Date.now();
|
|
3924
|
+
const lanes = await this.registry.list();
|
|
3925
|
+
let closed = 0;
|
|
3926
|
+
let skipped = 0;
|
|
3927
|
+
const errors = [];
|
|
3928
|
+
for (const lane of lanes) {
|
|
3929
|
+
try {
|
|
3930
|
+
const status = await this.status(lane.lane_id);
|
|
3931
|
+
const current = status.lane;
|
|
3932
|
+
if (current.state === "running" || current.state === "closed") {
|
|
3933
|
+
skipped += 1;
|
|
3934
|
+
continue;
|
|
3935
|
+
}
|
|
3936
|
+
if (current.state !== "finished" && current.state !== "error") {
|
|
3937
|
+
skipped += 1;
|
|
3938
|
+
continue;
|
|
3939
|
+
}
|
|
3940
|
+
const idleSince = parseDate(current.last_activity_at) || parseDate(current.updated_at);
|
|
3941
|
+
if (idleSince <= 0 || now - idleSince < ttlMs) {
|
|
3942
|
+
skipped += 1;
|
|
3943
|
+
continue;
|
|
3944
|
+
}
|
|
3945
|
+
await this.close({
|
|
3946
|
+
lane_id: current.lane_id,
|
|
3947
|
+
remove_worktree: input.remove_worktree,
|
|
3948
|
+
force: input.force
|
|
3949
|
+
});
|
|
3950
|
+
closed += 1;
|
|
3951
|
+
} catch (error) {
|
|
3952
|
+
errors.push(`${lane.lane_id}: ${String(error)}`);
|
|
3953
|
+
}
|
|
3954
|
+
}
|
|
3955
|
+
return {
|
|
3956
|
+
checked: lanes.length,
|
|
3957
|
+
closed,
|
|
3958
|
+
skipped,
|
|
3959
|
+
errors
|
|
3960
|
+
};
|
|
3961
|
+
}
|
|
3962
|
+
};
|
|
3963
|
+
|
|
3964
|
+
// src/tools/lane.ts
|
|
3965
|
+
var laneManager = new LaneManager();
|
|
3966
|
+
var backendSchema = z8.enum(["tmux", "zellij"]);
|
|
3967
|
+
var formatError = (error) => {
|
|
3968
|
+
if (error instanceof LaneManagerError) {
|
|
3969
|
+
return new Error(`${error.code}: ${error.message}`);
|
|
3970
|
+
}
|
|
3971
|
+
if (error instanceof Error) return error;
|
|
3972
|
+
return new Error(String(error));
|
|
3973
|
+
};
|
|
3974
|
+
var shQuote = (value) => {
|
|
3975
|
+
if (!value) return "''";
|
|
3976
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
3977
|
+
};
|
|
3978
|
+
var buildLaneHints = (lane) => {
|
|
3979
|
+
const attachCommand = lane.mux_backend === "tmux" ? `tmux attach -t ${shQuote(lane.mux_target)}` : lane.mux_backend === "zellij" ? `zellij attach ${shQuote(lane.mux_target)}` : null;
|
|
3980
|
+
return {
|
|
3981
|
+
attach_command: attachCommand,
|
|
3982
|
+
enter_worktree_command: `cd ${shQuote(lane.worktree_path)}`,
|
|
3983
|
+
status_tool: {
|
|
3984
|
+
name: "lane_status",
|
|
3985
|
+
args: { lane_id: lane.lane_id }
|
|
3986
|
+
},
|
|
3987
|
+
close_tool: {
|
|
3988
|
+
name: "lane_close",
|
|
3989
|
+
args: { lane_id: lane.lane_id }
|
|
3990
|
+
}
|
|
3991
|
+
};
|
|
3992
|
+
};
|
|
3993
|
+
var createLaneCreateTool = (sandboxKey) => defineTool7({
|
|
3994
|
+
name: "lane_create",
|
|
3995
|
+
description: "Create a Task lane (worktree slot) and start autonomous execution in tmux/zellij.",
|
|
3996
|
+
input: z8.object({
|
|
3997
|
+
task_id: z8.string().describe("Task identifier."),
|
|
3998
|
+
base_ref: z8.string().optional().describe("Base git ref. Default: HEAD."),
|
|
3999
|
+
worktree_path: z8.string().optional().describe(
|
|
4000
|
+
"Optional worktree path override. Default: ~/.codelia/worktrees/<task-slug>-<lane-id8>."
|
|
4001
|
+
),
|
|
4002
|
+
mux_backend: backendSchema.optional().describe("Multiplexer backend. Default: tmux."),
|
|
4003
|
+
seed_context: z8.string().optional().describe("Optional initial text context.")
|
|
4004
|
+
}),
|
|
4005
|
+
execute: async (input, ctx) => {
|
|
4006
|
+
const sandbox = await getSandboxContext(ctx, sandboxKey);
|
|
4007
|
+
try {
|
|
4008
|
+
const lane = await laneManager.create(
|
|
4009
|
+
{
|
|
4010
|
+
task_id: input.task_id,
|
|
4011
|
+
base_ref: input.base_ref,
|
|
4012
|
+
worktree_path: input.worktree_path,
|
|
4013
|
+
mux_backend: input.mux_backend,
|
|
4014
|
+
seed_context: input.seed_context
|
|
4015
|
+
},
|
|
4016
|
+
{ workingDir: sandbox.workingDir }
|
|
4017
|
+
);
|
|
4018
|
+
return {
|
|
4019
|
+
ok: true,
|
|
4020
|
+
lane,
|
|
4021
|
+
hints: buildLaneHints(lane)
|
|
4022
|
+
};
|
|
4023
|
+
} catch (error) {
|
|
4024
|
+
throw formatError(error);
|
|
4025
|
+
}
|
|
4026
|
+
}
|
|
4027
|
+
});
|
|
4028
|
+
var createLaneListTool = (sandboxKey) => defineTool7({
|
|
4029
|
+
name: "lane_list",
|
|
4030
|
+
description: "List Task lanes (worktree slots) and current states.",
|
|
4031
|
+
input: z8.object({
|
|
4032
|
+
include_closed: z8.boolean().optional().describe("Include closed lanes. Default: false.")
|
|
4033
|
+
}),
|
|
4034
|
+
execute: async (input, ctx) => {
|
|
4035
|
+
await getSandboxContext(ctx, sandboxKey);
|
|
4036
|
+
try {
|
|
4037
|
+
const lanes = await laneManager.list({
|
|
4038
|
+
include_closed: input.include_closed
|
|
4039
|
+
});
|
|
4040
|
+
return {
|
|
4041
|
+
lanes,
|
|
4042
|
+
hints: lanes.map((lane) => ({
|
|
4043
|
+
lane_id: lane.lane_id,
|
|
4044
|
+
...buildLaneHints(lane)
|
|
4045
|
+
}))
|
|
4046
|
+
};
|
|
4047
|
+
} catch (error) {
|
|
4048
|
+
throw formatError(error);
|
|
4049
|
+
}
|
|
4050
|
+
}
|
|
4051
|
+
});
|
|
4052
|
+
var createLaneStatusTool = (sandboxKey) => defineTool7({
|
|
4053
|
+
name: "lane_status",
|
|
4054
|
+
description: "Get Task lane status and backend liveness.",
|
|
4055
|
+
input: z8.object({
|
|
4056
|
+
lane_id: z8.string().describe("Lane id.")
|
|
4057
|
+
}),
|
|
4058
|
+
execute: async (input, ctx) => {
|
|
4059
|
+
await getSandboxContext(ctx, sandboxKey);
|
|
4060
|
+
try {
|
|
4061
|
+
const result = await laneManager.status(input.lane_id);
|
|
4062
|
+
return {
|
|
4063
|
+
...result,
|
|
4064
|
+
hints: buildLaneHints(result.lane)
|
|
4065
|
+
};
|
|
4066
|
+
} catch (error) {
|
|
4067
|
+
throw formatError(error);
|
|
4068
|
+
}
|
|
4069
|
+
}
|
|
4070
|
+
});
|
|
4071
|
+
var createLaneCloseTool = (sandboxKey) => defineTool7({
|
|
4072
|
+
name: "lane_close",
|
|
4073
|
+
description: "Close a Task lane and optionally remove its worktree.",
|
|
4074
|
+
input: z8.object({
|
|
4075
|
+
lane_id: z8.string().describe("Lane id."),
|
|
4076
|
+
remove_worktree: z8.boolean().optional().describe("Remove worktree. Default: true."),
|
|
4077
|
+
force: z8.boolean().optional().describe("Force close/cleanup. Default: false.")
|
|
4078
|
+
}),
|
|
4079
|
+
execute: async (input, ctx) => {
|
|
4080
|
+
await getSandboxContext(ctx, sandboxKey);
|
|
4081
|
+
try {
|
|
4082
|
+
const lane = await laneManager.close({
|
|
4083
|
+
lane_id: input.lane_id,
|
|
4084
|
+
remove_worktree: input.remove_worktree,
|
|
4085
|
+
force: input.force
|
|
4086
|
+
});
|
|
4087
|
+
return {
|
|
4088
|
+
ok: true,
|
|
4089
|
+
lane
|
|
4090
|
+
};
|
|
4091
|
+
} catch (error) {
|
|
4092
|
+
throw formatError(error);
|
|
4093
|
+
}
|
|
4094
|
+
}
|
|
4095
|
+
});
|
|
4096
|
+
var createLaneGcTool = (sandboxKey) => defineTool7({
|
|
4097
|
+
name: "lane_gc",
|
|
4098
|
+
description: "Close stale finished/error lanes after idle TTL.",
|
|
4099
|
+
input: z8.object({
|
|
4100
|
+
idle_ttl_minutes: z8.number().int().positive().describe("Idle TTL in minutes."),
|
|
4101
|
+
remove_worktree: z8.boolean().optional().describe("Remove worktree on close. Default: false."),
|
|
4102
|
+
force: z8.boolean().optional().describe("Force cleanup for dirty worktrees. Default: false.")
|
|
4103
|
+
}),
|
|
4104
|
+
execute: async (input, ctx) => {
|
|
4105
|
+
await getSandboxContext(ctx, sandboxKey);
|
|
4106
|
+
try {
|
|
4107
|
+
return await laneManager.gc({
|
|
4108
|
+
idle_ttl_minutes: input.idle_ttl_minutes,
|
|
4109
|
+
remove_worktree: input.remove_worktree,
|
|
4110
|
+
force: input.force
|
|
4111
|
+
});
|
|
4112
|
+
} catch (error) {
|
|
4113
|
+
throw formatError(error);
|
|
4114
|
+
}
|
|
4115
|
+
}
|
|
4116
|
+
});
|
|
4117
|
+
|
|
4118
|
+
// src/tools/read.ts
|
|
4119
|
+
import { promises as fs12 } from "fs";
|
|
4120
|
+
import { defineTool as defineTool8 } from "@codelia/core";
|
|
4121
|
+
import { z as z9 } from "zod";
|
|
4122
|
+
var DEFAULT_READ_LIMIT = 2e3;
|
|
4123
|
+
var MAX_LINE_LENGTH = 2e3;
|
|
4124
|
+
var MAX_BYTES = 50 * 1024;
|
|
4125
|
+
var createReadTool = (sandboxKey) => defineTool8({
|
|
4126
|
+
name: "read",
|
|
4127
|
+
description: "Read a text file with optional 0-based line offset and line limit.",
|
|
4128
|
+
input: z9.object({
|
|
4129
|
+
file_path: z9.string().describe("File path under the sandbox root."),
|
|
4130
|
+
offset: z9.number().int().nonnegative().optional().describe("0-based start line. Default 0."),
|
|
4131
|
+
limit: z9.number().int().positive().optional().describe("Max lines to read. Default 2000.")
|
|
4132
|
+
}),
|
|
4133
|
+
execute: async (input, ctx) => {
|
|
4134
|
+
let resolved;
|
|
4135
|
+
try {
|
|
4136
|
+
const sandbox = await getSandboxContext(ctx, sandboxKey);
|
|
4137
|
+
resolved = sandbox.resolvePath(input.file_path);
|
|
4138
|
+
} catch (error) {
|
|
4139
|
+
return `Security error: ${String(error)}`;
|
|
4140
|
+
}
|
|
4141
|
+
try {
|
|
4142
|
+
const stat = await fs12.stat(resolved);
|
|
4143
|
+
if (stat.isDirectory()) {
|
|
4144
|
+
return `Path is a directory: ${input.file_path}`;
|
|
4145
|
+
}
|
|
4146
|
+
} catch {
|
|
4147
|
+
return `File not found: ${input.file_path}`;
|
|
4148
|
+
}
|
|
4149
|
+
try {
|
|
4150
|
+
const content = await fs12.readFile(resolved, "utf8");
|
|
4151
|
+
const lines = content.split(/\r?\n/);
|
|
4152
|
+
if (lines.length === 0) {
|
|
4153
|
+
return "";
|
|
4154
|
+
}
|
|
4155
|
+
const offset = input.offset ?? 0;
|
|
4156
|
+
const limit = input.limit ?? DEFAULT_READ_LIMIT;
|
|
4157
|
+
if (offset >= lines.length) {
|
|
4158
|
+
return `Offset exceeds file length: ${input.file_path}`;
|
|
4159
|
+
}
|
|
4160
|
+
const raw = [];
|
|
4161
|
+
let bytes = 0;
|
|
4162
|
+
let truncatedByBytes = false;
|
|
4163
|
+
for (let index = offset; index < Math.min(lines.length, offset + limit); index += 1) {
|
|
4164
|
+
const line = lines[index].length > MAX_LINE_LENGTH ? `${lines[index].slice(0, MAX_LINE_LENGTH)}...` : lines[index];
|
|
4165
|
+
const size = Buffer.byteLength(line, "utf8") + (raw.length > 0 ? 1 : 0);
|
|
4166
|
+
if (bytes + size > MAX_BYTES) {
|
|
4167
|
+
truncatedByBytes = true;
|
|
4168
|
+
break;
|
|
4169
|
+
}
|
|
4170
|
+
raw.push(line);
|
|
4171
|
+
bytes += size;
|
|
4172
|
+
}
|
|
4173
|
+
const numbered = raw.map(
|
|
4174
|
+
(line, index) => `${String(index + offset + 1).padStart(5, " ")} ${line}`
|
|
4175
|
+
);
|
|
4176
|
+
const lastReadLine = offset + raw.length;
|
|
4177
|
+
const hasMoreLines = lines.length > lastReadLine;
|
|
4178
|
+
const truncated = truncatedByBytes || hasMoreLines;
|
|
4179
|
+
let output = numbered.join("\n");
|
|
4180
|
+
if (truncated) {
|
|
4181
|
+
const reason = truncatedByBytes ? `Output truncated at ${MAX_BYTES} bytes.` : "File has more lines.";
|
|
4182
|
+
output += `
|
|
4183
|
+
|
|
4184
|
+
${reason} Use offset to read beyond line ${lastReadLine}.`;
|
|
4185
|
+
}
|
|
4186
|
+
return output;
|
|
4187
|
+
} catch (error) {
|
|
4188
|
+
return `Error reading file: ${String(error)}`;
|
|
4189
|
+
}
|
|
4190
|
+
}
|
|
4191
|
+
});
|
|
4192
|
+
|
|
4193
|
+
// src/tools/skill-load.ts
|
|
4194
|
+
import { defineTool as defineTool9 } from "@codelia/core";
|
|
4195
|
+
import { z as z10 } from "zod";
|
|
4196
|
+
var SkillLoadInputSchema = z10.object({
|
|
4197
|
+
name: z10.string().min(1).optional().describe("Exact skill name to load."),
|
|
4198
|
+
path: z10.string().min(1).optional().describe("Absolute or workspace-relative path to SKILL.md.")
|
|
4199
|
+
}).refine((value) => !!value.name || !!value.path, {
|
|
4200
|
+
message: "name or path is required",
|
|
4201
|
+
path: ["name"]
|
|
4202
|
+
});
|
|
4203
|
+
var createSkillLoadTool = (skillsResolverKey) => defineTool9({
|
|
4204
|
+
name: "skill_load",
|
|
4205
|
+
description: "Load full SKILL.md content by exact name or path.",
|
|
4206
|
+
input: SkillLoadInputSchema,
|
|
4207
|
+
execute: async (input, ctx) => {
|
|
4208
|
+
try {
|
|
4209
|
+
const resolver = await getSkillsResolver(ctx, skillsResolverKey);
|
|
4210
|
+
const result = await resolver.load(input);
|
|
4211
|
+
if (!result.ok) {
|
|
4212
|
+
return {
|
|
4213
|
+
ok: false,
|
|
4214
|
+
error: result.message,
|
|
4215
|
+
ambiguous_paths: result.ambiguous_paths ?? [],
|
|
4216
|
+
already_loaded: null,
|
|
4217
|
+
skill: null,
|
|
4218
|
+
content: null,
|
|
4219
|
+
files: [],
|
|
4220
|
+
files_truncated: false
|
|
4221
|
+
};
|
|
4222
|
+
}
|
|
4223
|
+
return {
|
|
4224
|
+
ok: true,
|
|
4225
|
+
error: null,
|
|
4226
|
+
ambiguous_paths: [],
|
|
4227
|
+
already_loaded: result.already_loaded,
|
|
4228
|
+
skill: result.skill,
|
|
4229
|
+
content: result.content,
|
|
4230
|
+
files: result.files,
|
|
4231
|
+
files_truncated: result.files_truncated
|
|
4232
|
+
};
|
|
4233
|
+
} catch (error) {
|
|
4234
|
+
return {
|
|
4235
|
+
ok: false,
|
|
4236
|
+
error: `Error loading skill: ${String(error)}`,
|
|
4237
|
+
ambiguous_paths: [],
|
|
4238
|
+
already_loaded: null,
|
|
4239
|
+
skill: null,
|
|
4240
|
+
content: null,
|
|
4241
|
+
files: [],
|
|
4242
|
+
files_truncated: false
|
|
4243
|
+
};
|
|
4244
|
+
}
|
|
4245
|
+
}
|
|
4246
|
+
});
|
|
4247
|
+
|
|
4248
|
+
// src/tools/skill-search.ts
|
|
4249
|
+
import { defineTool as defineTool10 } from "@codelia/core";
|
|
4250
|
+
import { z as z11 } from "zod";
|
|
4251
|
+
var createSkillSearchTool = (skillsResolverKey) => defineTool10({
|
|
4252
|
+
name: "skill_search",
|
|
4253
|
+
description: "Search installed local skills by name, description, or path.",
|
|
4254
|
+
input: z11.object({
|
|
4255
|
+
query: z11.string().min(1).describe("Search query text."),
|
|
4256
|
+
limit: z11.number().int().positive().optional().describe("Optional max result count (clamped by config)."),
|
|
4257
|
+
scope: z11.enum(["repo", "user"]).optional().describe("Optional scope filter.")
|
|
4258
|
+
}),
|
|
4259
|
+
execute: async (input, ctx) => {
|
|
4260
|
+
try {
|
|
4261
|
+
const resolver = await getSkillsResolver(ctx, skillsResolverKey);
|
|
4262
|
+
const result = await resolver.search(input);
|
|
3436
4263
|
return {
|
|
3437
4264
|
query: input.query,
|
|
3438
4265
|
count: result.results.length,
|
|
@@ -3450,17 +4277,17 @@ var createSkillSearchTool = (skillsResolverKey) => defineTool9({
|
|
|
3450
4277
|
});
|
|
3451
4278
|
|
|
3452
4279
|
// src/tools/todo-read.ts
|
|
3453
|
-
import { defineTool as
|
|
3454
|
-
import { z as
|
|
4280
|
+
import { defineTool as defineTool11 } from "@codelia/core";
|
|
4281
|
+
import { z as z12 } from "zod";
|
|
3455
4282
|
|
|
3456
4283
|
// src/tools/todo-store.ts
|
|
3457
4284
|
var todoStore = /* @__PURE__ */ new Map();
|
|
3458
4285
|
|
|
3459
4286
|
// src/tools/todo-read.ts
|
|
3460
|
-
var createTodoReadTool = (sandboxKey) =>
|
|
4287
|
+
var createTodoReadTool = (sandboxKey) => defineTool11({
|
|
3461
4288
|
name: "todo_read",
|
|
3462
4289
|
description: "Read the in-session todo list.",
|
|
3463
|
-
input:
|
|
4290
|
+
input: z12.object({}),
|
|
3464
4291
|
execute: async (_input, ctx) => {
|
|
3465
4292
|
const sandbox = await getSandboxContext(ctx, sandboxKey);
|
|
3466
4293
|
const todos = todoStore.get(sandbox.sessionId) ?? [];
|
|
@@ -3473,17 +4300,17 @@ var createTodoReadTool = (sandboxKey) => defineTool10({
|
|
|
3473
4300
|
});
|
|
3474
4301
|
|
|
3475
4302
|
// src/tools/todo-write.ts
|
|
3476
|
-
import { defineTool as
|
|
3477
|
-
import { z as
|
|
3478
|
-
var createTodoWriteTool = (sandboxKey) =>
|
|
4303
|
+
import { defineTool as defineTool12 } from "@codelia/core";
|
|
4304
|
+
import { z as z13 } from "zod";
|
|
4305
|
+
var createTodoWriteTool = (sandboxKey) => defineTool12({
|
|
3479
4306
|
name: "todo_write",
|
|
3480
4307
|
description: "Replace the in-session todo list.",
|
|
3481
|
-
input:
|
|
3482
|
-
todos:
|
|
3483
|
-
|
|
3484
|
-
content:
|
|
3485
|
-
status:
|
|
3486
|
-
activeForm:
|
|
4308
|
+
input: z13.object({
|
|
4309
|
+
todos: z13.array(
|
|
4310
|
+
z13.object({
|
|
4311
|
+
content: z13.string().describe("Todo item text."),
|
|
4312
|
+
status: z13.enum(["pending", "in_progress", "completed"]).describe("Todo status."),
|
|
4313
|
+
activeForm: z13.string().optional().describe("Optional in-progress phrasing for UI display.")
|
|
3487
4314
|
})
|
|
3488
4315
|
)
|
|
3489
4316
|
}),
|
|
@@ -3500,15 +4327,15 @@ var createTodoWriteTool = (sandboxKey) => defineTool11({
|
|
|
3500
4327
|
});
|
|
3501
4328
|
|
|
3502
4329
|
// src/tools/tool-output-cache.ts
|
|
3503
|
-
import { defineTool as
|
|
3504
|
-
import { z as
|
|
3505
|
-
var createToolOutputCacheTool = (store) =>
|
|
4330
|
+
import { defineTool as defineTool13 } from "@codelia/core";
|
|
4331
|
+
import { z as z14 } from "zod";
|
|
4332
|
+
var createToolOutputCacheTool = (store) => defineTool13({
|
|
3506
4333
|
name: "tool_output_cache",
|
|
3507
4334
|
description: "Read cached tool output by ref_id.",
|
|
3508
|
-
input:
|
|
3509
|
-
ref_id:
|
|
3510
|
-
offset:
|
|
3511
|
-
limit:
|
|
4335
|
+
input: z14.object({
|
|
4336
|
+
ref_id: z14.string().describe("Tool output reference ID."),
|
|
4337
|
+
offset: z14.number().int().nonnegative().optional().describe("Optional 0-based line offset."),
|
|
4338
|
+
limit: z14.number().int().positive().optional().describe("Optional max number of lines to return.")
|
|
3512
4339
|
}),
|
|
3513
4340
|
execute: async (input) => {
|
|
3514
4341
|
if (!store.read) {
|
|
@@ -3524,16 +4351,16 @@ var createToolOutputCacheTool = (store) => defineTool12({
|
|
|
3524
4351
|
}
|
|
3525
4352
|
}
|
|
3526
4353
|
});
|
|
3527
|
-
var createToolOutputCacheGrepTool = (store) =>
|
|
4354
|
+
var createToolOutputCacheGrepTool = (store) => defineTool13({
|
|
3528
4355
|
name: "tool_output_cache_grep",
|
|
3529
4356
|
description: "Search cached tool output by ref_id.",
|
|
3530
|
-
input:
|
|
3531
|
-
ref_id:
|
|
3532
|
-
pattern:
|
|
3533
|
-
regex:
|
|
3534
|
-
before:
|
|
3535
|
-
after:
|
|
3536
|
-
max_matches:
|
|
4357
|
+
input: z14.object({
|
|
4358
|
+
ref_id: z14.string().describe("Tool output reference ID."),
|
|
4359
|
+
pattern: z14.string().describe("Text or regex pattern to search for."),
|
|
4360
|
+
regex: z14.boolean().optional().describe("Interpret pattern as regex when true. Default false."),
|
|
4361
|
+
before: z14.number().int().nonnegative().optional().describe("Context lines before each match. Default 0."),
|
|
4362
|
+
after: z14.number().int().nonnegative().optional().describe("Context lines after each match. Default 0."),
|
|
4363
|
+
max_matches: z14.number().int().positive().optional().describe("Maximum number of matches to return.")
|
|
3537
4364
|
}),
|
|
3538
4365
|
execute: async (input) => {
|
|
3539
4366
|
if (!store.grep) {
|
|
@@ -3554,16 +4381,16 @@ var createToolOutputCacheGrepTool = (store) => defineTool12({
|
|
|
3554
4381
|
});
|
|
3555
4382
|
|
|
3556
4383
|
// src/tools/write.ts
|
|
3557
|
-
import { promises as
|
|
3558
|
-
import
|
|
3559
|
-
import { defineTool as
|
|
3560
|
-
import { z as
|
|
3561
|
-
var createWriteTool = (sandboxKey) =>
|
|
4384
|
+
import { promises as fs13 } from "fs";
|
|
4385
|
+
import path12 from "path";
|
|
4386
|
+
import { defineTool as defineTool14 } from "@codelia/core";
|
|
4387
|
+
import { z as z15 } from "zod";
|
|
4388
|
+
var createWriteTool = (sandboxKey) => defineTool14({
|
|
3562
4389
|
name: "write",
|
|
3563
4390
|
description: "Write text to a file, creating parent directories if needed.",
|
|
3564
|
-
input:
|
|
3565
|
-
file_path:
|
|
3566
|
-
content:
|
|
4391
|
+
input: z15.object({
|
|
4392
|
+
file_path: z15.string().describe("File path under the sandbox root."),
|
|
4393
|
+
content: z15.string().describe("UTF-8 text content to write.")
|
|
3567
4394
|
}),
|
|
3568
4395
|
execute: async (input, ctx) => {
|
|
3569
4396
|
let resolved;
|
|
@@ -3574,8 +4401,8 @@ var createWriteTool = (sandboxKey) => defineTool13({
|
|
|
3574
4401
|
return `Security error: ${String(error)}`;
|
|
3575
4402
|
}
|
|
3576
4403
|
try {
|
|
3577
|
-
await
|
|
3578
|
-
await
|
|
4404
|
+
await fs13.mkdir(path12.dirname(resolved), { recursive: true });
|
|
4405
|
+
await fs13.writeFile(resolved, input.content, "utf8");
|
|
3579
4406
|
return `Wrote ${input.content.length} bytes to ${input.file_path}`;
|
|
3580
4407
|
} catch (error) {
|
|
3581
4408
|
return `Error writing file: ${String(error)}`;
|
|
@@ -3600,9 +4427,106 @@ var createTools = (sandboxKey, agentsResolverKey, skillsResolverKey, options = {
|
|
|
3600
4427
|
] : [],
|
|
3601
4428
|
createTodoReadTool(sandboxKey),
|
|
3602
4429
|
createTodoWriteTool(sandboxKey),
|
|
4430
|
+
createLaneCreateTool(sandboxKey),
|
|
4431
|
+
createLaneListTool(sandboxKey),
|
|
4432
|
+
createLaneStatusTool(sandboxKey),
|
|
4433
|
+
createLaneCloseTool(sandboxKey),
|
|
4434
|
+
createLaneGcTool(sandboxKey),
|
|
3603
4435
|
createDoneTool()
|
|
3604
4436
|
];
|
|
3605
4437
|
|
|
4438
|
+
// src/utils/language.ts
|
|
4439
|
+
var normalizeLanguage = (value) => {
|
|
4440
|
+
const token = value.trim().toLowerCase();
|
|
4441
|
+
if (!token) return "";
|
|
4442
|
+
switch (token) {
|
|
4443
|
+
case "yml":
|
|
4444
|
+
return "yaml";
|
|
4445
|
+
case "mjs":
|
|
4446
|
+
case "cjs":
|
|
4447
|
+
case "node":
|
|
4448
|
+
return "javascript";
|
|
4449
|
+
case "mts":
|
|
4450
|
+
case "cts":
|
|
4451
|
+
case "ts-node":
|
|
4452
|
+
case "deno":
|
|
4453
|
+
return "typescript";
|
|
4454
|
+
case "py":
|
|
4455
|
+
case "python3":
|
|
4456
|
+
return "python";
|
|
4457
|
+
case "rb":
|
|
4458
|
+
return "ruby";
|
|
4459
|
+
case "zsh":
|
|
4460
|
+
case "sh":
|
|
4461
|
+
return "bash";
|
|
4462
|
+
case "ps1":
|
|
4463
|
+
return "powershell";
|
|
4464
|
+
default:
|
|
4465
|
+
return token;
|
|
4466
|
+
}
|
|
4467
|
+
};
|
|
4468
|
+
var languageFromFilePath = (filePath) => {
|
|
4469
|
+
if (!filePath) return void 0;
|
|
4470
|
+
const path16 = filePath.trim().replace(/^["']|["']$/g, "");
|
|
4471
|
+
if (!path16 || path16 === "/dev/null") return void 0;
|
|
4472
|
+
const normalizedPath = path16.replace(/^a\//, "").replace(/^b\//, "");
|
|
4473
|
+
const lastSlash = Math.max(
|
|
4474
|
+
normalizedPath.lastIndexOf("/"),
|
|
4475
|
+
normalizedPath.lastIndexOf("\\")
|
|
4476
|
+
);
|
|
4477
|
+
const basename = lastSlash >= 0 ? normalizedPath.slice(lastSlash + 1) : normalizedPath;
|
|
4478
|
+
if (!basename) return void 0;
|
|
4479
|
+
const dotIndex = basename.lastIndexOf(".");
|
|
4480
|
+
if (dotIndex > 0 && dotIndex < basename.length - 1) {
|
|
4481
|
+
return normalizeLanguage(basename.slice(dotIndex + 1));
|
|
4482
|
+
}
|
|
4483
|
+
if (basename === "Dockerfile") return "dockerfile";
|
|
4484
|
+
if (/^Makefile(\..+)?$/i.test(basename)) return "makefile";
|
|
4485
|
+
return void 0;
|
|
4486
|
+
};
|
|
4487
|
+
var languageFromDiffHeaders = (diff) => {
|
|
4488
|
+
if (!diff) return void 0;
|
|
4489
|
+
for (const line of diff.split("\n")) {
|
|
4490
|
+
if (!line.startsWith("--- ") && !line.startsWith("+++ ")) continue;
|
|
4491
|
+
const rawPath = line.slice(4).trim().split(/\s+/)[0] ?? "";
|
|
4492
|
+
const language = languageFromFilePath(rawPath);
|
|
4493
|
+
if (language) return language;
|
|
4494
|
+
}
|
|
4495
|
+
return void 0;
|
|
4496
|
+
};
|
|
4497
|
+
var languageFromShebang = (content) => {
|
|
4498
|
+
if (!content) return void 0;
|
|
4499
|
+
const firstLine = content.split("\n", 1)[0]?.trim() ?? "";
|
|
4500
|
+
if (!firstLine.startsWith("#!")) return void 0;
|
|
4501
|
+
const body = firstLine.slice(2).trim();
|
|
4502
|
+
if (!body) return void 0;
|
|
4503
|
+
const parts = body.split(/\s+/).filter(Boolean);
|
|
4504
|
+
if (!parts.length) return void 0;
|
|
4505
|
+
let command = parts[0];
|
|
4506
|
+
if (command.endsWith("/env")) {
|
|
4507
|
+
let index = 1;
|
|
4508
|
+
if (parts[index] === "-S") index += 1;
|
|
4509
|
+
while (index < parts.length && parts[index].includes("=")) {
|
|
4510
|
+
index += 1;
|
|
4511
|
+
}
|
|
4512
|
+
command = parts[index] ?? "";
|
|
4513
|
+
}
|
|
4514
|
+
if (!command) return void 0;
|
|
4515
|
+
const last = command.split("/").pop() ?? command;
|
|
4516
|
+
const simplified = last.replace(/[0-9.]+$/g, "");
|
|
4517
|
+
const normalized = normalizeLanguage(simplified);
|
|
4518
|
+
return normalized || void 0;
|
|
4519
|
+
};
|
|
4520
|
+
var resolvePreviewLanguageHint = (input) => {
|
|
4521
|
+
const explicit = input.language ? normalizeLanguage(input.language) : "";
|
|
4522
|
+
if (explicit) return explicit;
|
|
4523
|
+
const shebang = languageFromShebang(input.content);
|
|
4524
|
+
if (shebang) return shebang;
|
|
4525
|
+
const fromDiff = languageFromDiffHeaders(input.diff);
|
|
4526
|
+
if (fromDiff) return fromDiff;
|
|
4527
|
+
return languageFromFilePath(input.filePath);
|
|
4528
|
+
};
|
|
4529
|
+
|
|
3606
4530
|
// src/agent-factory.ts
|
|
3607
4531
|
var requireApiKeyAuth = (provider, auth) => {
|
|
3608
4532
|
if (auth.method !== "api_key") {
|
|
@@ -3615,6 +4539,7 @@ var envTruthy = (value) => {
|
|
|
3615
4539
|
const normalized = value.trim().toLowerCase();
|
|
3616
4540
|
return normalized === "1" || normalized === "true";
|
|
3617
4541
|
};
|
|
4542
|
+
var OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
|
|
3618
4543
|
var buildOpenAiClientOptions = (authResolver, auth) => {
|
|
3619
4544
|
if (auth.method === "api_key") {
|
|
3620
4545
|
return { apiKey: auth.api_key };
|
|
@@ -3658,6 +4583,22 @@ var buildOpenAiClientOptions = (authResolver, auth) => {
|
|
|
3658
4583
|
fetch: fetchWithAccount
|
|
3659
4584
|
};
|
|
3660
4585
|
};
|
|
4586
|
+
var buildOpenRouterClientOptions = (auth) => {
|
|
4587
|
+
const headers = {};
|
|
4588
|
+
const referer = readEnvValue("OPENROUTER_HTTP_REFERER");
|
|
4589
|
+
if (referer) {
|
|
4590
|
+
headers["HTTP-Referer"] = referer;
|
|
4591
|
+
}
|
|
4592
|
+
const title = readEnvValue("OPENROUTER_X_TITLE");
|
|
4593
|
+
if (title) {
|
|
4594
|
+
headers["X-Title"] = title;
|
|
4595
|
+
}
|
|
4596
|
+
return {
|
|
4597
|
+
apiKey: requireApiKeyAuth("OpenRouter", auth),
|
|
4598
|
+
baseURL: OPENROUTER_BASE_URL,
|
|
4599
|
+
...Object.keys(headers).length ? { defaultHeaders: headers } : {}
|
|
4600
|
+
};
|
|
4601
|
+
};
|
|
3661
4602
|
var sleep = (ms) => new Promise((resolve) => {
|
|
3662
4603
|
setTimeout(resolve, ms);
|
|
3663
4604
|
});
|
|
@@ -3671,6 +4612,41 @@ var waitForUiConfirmSupport = async (state, timeoutMs = 5e3) => {
|
|
|
3671
4612
|
}
|
|
3672
4613
|
return !!state.uiCapabilities?.supports_confirm;
|
|
3673
4614
|
};
|
|
4615
|
+
var MAX_CONFIRM_PREVIEW_LINES = 120;
|
|
4616
|
+
var splitLines = (value) => value.split("\n").map((line) => line.replace(/\r$/, ""));
|
|
4617
|
+
var buildBoundedDiffPreview = (diff, maxLines = MAX_CONFIRM_PREVIEW_LINES) => {
|
|
4618
|
+
if (!diff.trim()) return { diff: null, truncated: false };
|
|
4619
|
+
const lines = splitLines(diff);
|
|
4620
|
+
if (!lines.length) return { diff: null, truncated: false };
|
|
4621
|
+
if (lines.length <= maxLines) {
|
|
4622
|
+
return { diff: lines.join("\n"), truncated: false };
|
|
4623
|
+
}
|
|
4624
|
+
return {
|
|
4625
|
+
diff: lines.slice(0, maxLines).join("\n"),
|
|
4626
|
+
truncated: true
|
|
4627
|
+
};
|
|
4628
|
+
};
|
|
4629
|
+
var parseToolArgsObject = (rawArgs) => {
|
|
4630
|
+
try {
|
|
4631
|
+
const parsed = JSON.parse(rawArgs);
|
|
4632
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
4633
|
+
return null;
|
|
4634
|
+
}
|
|
4635
|
+
return parsed;
|
|
4636
|
+
} catch {
|
|
4637
|
+
return null;
|
|
4638
|
+
}
|
|
4639
|
+
};
|
|
4640
|
+
var unwrapToolJsonObject = (result) => {
|
|
4641
|
+
if (!result || typeof result !== "object") return null;
|
|
4642
|
+
const typed = result;
|
|
4643
|
+
if (typed.type !== "json") return null;
|
|
4644
|
+
const value = typed.value;
|
|
4645
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
4646
|
+
return null;
|
|
4647
|
+
}
|
|
4648
|
+
return value;
|
|
4649
|
+
};
|
|
3674
4650
|
var requestMcpOAuthTokens = async (state, serverId, oauth2, errorMessage) => {
|
|
3675
4651
|
const canConfirm = state.uiCapabilities?.supports_confirm ? true : await waitForUiConfirmSupport(state);
|
|
3676
4652
|
if (!canConfirm) {
|
|
@@ -3808,6 +4784,9 @@ var createAgentFactory = (state, options = {}) => {
|
|
|
3808
4784
|
toolOutputCacheStore
|
|
3809
4785
|
}
|
|
3810
4786
|
);
|
|
4787
|
+
const editTool = localTools.find(
|
|
4788
|
+
(tool) => tool.definition.name === "edit"
|
|
4789
|
+
);
|
|
3811
4790
|
let mcpTools = [];
|
|
3812
4791
|
if (options.mcpManager) {
|
|
3813
4792
|
try {
|
|
@@ -3829,6 +4808,7 @@ var createAgentFactory = (state, options = {}) => {
|
|
|
3829
4808
|
}
|
|
3830
4809
|
}
|
|
3831
4810
|
const tools = [...localTools, ...mcpTools];
|
|
4811
|
+
state.tools = tools;
|
|
3832
4812
|
state.toolDefinitions = tools.map((tool) => tool.definition);
|
|
3833
4813
|
const baseSystemPrompt = await loadSystemPrompt(ctx.workingDir);
|
|
3834
4814
|
const withAgentsContext = appendInitialAgentsContext(
|
|
@@ -3878,6 +4858,17 @@ var createAgentFactory = (state, options = {}) => {
|
|
|
3878
4858
|
});
|
|
3879
4859
|
break;
|
|
3880
4860
|
}
|
|
4861
|
+
case "openrouter": {
|
|
4862
|
+
const reasoningEffort = resolveReasoningEffort(modelConfig.reasoning);
|
|
4863
|
+
const textVerbosity = resolveTextVerbosity(modelConfig.verbosity);
|
|
4864
|
+
llm = new ChatOpenAI({
|
|
4865
|
+
clientOptions: buildOpenRouterClientOptions(providerAuth),
|
|
4866
|
+
...modelConfig.name ? { model: modelConfig.name } : {},
|
|
4867
|
+
...reasoningEffort ? { reasoningEffort } : {},
|
|
4868
|
+
...textVerbosity ? { textVerbosity } : {}
|
|
4869
|
+
});
|
|
4870
|
+
break;
|
|
4871
|
+
}
|
|
3881
4872
|
case "anthropic": {
|
|
3882
4873
|
llm = new ChatAnthropic({
|
|
3883
4874
|
clientOptions: {
|
|
@@ -3890,14 +4881,16 @@ var createAgentFactory = (state, options = {}) => {
|
|
|
3890
4881
|
default:
|
|
3891
4882
|
throw new Error(`Unsupported model.provider: ${provider}`);
|
|
3892
4883
|
}
|
|
3893
|
-
const modelRegistry = await buildModelRegistry(llm
|
|
4884
|
+
const modelRegistry = await buildModelRegistry(llm, {
|
|
4885
|
+
strict: provider !== "openrouter"
|
|
4886
|
+
});
|
|
3894
4887
|
const agent = new Agent({
|
|
3895
4888
|
llm,
|
|
3896
4889
|
tools,
|
|
3897
4890
|
systemPrompt,
|
|
3898
4891
|
modelRegistry: modelRegistry ?? DEFAULT_MODEL_REGISTRY2,
|
|
3899
4892
|
services: { toolOutputCacheStore },
|
|
3900
|
-
canExecuteTool: async (call, rawArgs) => {
|
|
4893
|
+
canExecuteTool: async (call, rawArgs, toolCtx) => {
|
|
3901
4894
|
const decision = permissionService.evaluate(
|
|
3902
4895
|
call.function.name,
|
|
3903
4896
|
rawArgs
|
|
@@ -3919,12 +4912,129 @@ var createAgentFactory = (state, options = {}) => {
|
|
|
3919
4912
|
};
|
|
3920
4913
|
}
|
|
3921
4914
|
const runId = state.activeRunId ?? void 0;
|
|
4915
|
+
debugLog(
|
|
4916
|
+
`permission.request tool=${call.function.name} args=${rawArgs}`
|
|
4917
|
+
);
|
|
3922
4918
|
const prompt = permissionService.getConfirmPrompt(
|
|
3923
4919
|
call.function.name,
|
|
3924
4920
|
rawArgs
|
|
3925
4921
|
);
|
|
4922
|
+
let previewDiff = null;
|
|
4923
|
+
let previewSummary = null;
|
|
4924
|
+
let previewTruncated = false;
|
|
4925
|
+
let previewFilePath = null;
|
|
4926
|
+
let previewLanguage = null;
|
|
4927
|
+
if (call.function.name === "write") {
|
|
4928
|
+
const parsed = parseToolArgsObject(rawArgs);
|
|
4929
|
+
const filePath = typeof parsed?.file_path === "string" ? parsed.file_path : "";
|
|
4930
|
+
const content = typeof parsed?.content === "string" ? parsed.content : "";
|
|
4931
|
+
const language = typeof parsed?.language === "string" ? parsed.language : "";
|
|
4932
|
+
if (filePath) {
|
|
4933
|
+
previewFilePath = filePath;
|
|
4934
|
+
previewLanguage = resolvePreviewLanguageHint({
|
|
4935
|
+
language,
|
|
4936
|
+
filePath,
|
|
4937
|
+
content
|
|
4938
|
+
}) ?? previewLanguage;
|
|
4939
|
+
try {
|
|
4940
|
+
const sandbox = await getSandboxContext(toolCtx, sandboxKey);
|
|
4941
|
+
const resolved = sandbox.resolvePath(filePath);
|
|
4942
|
+
let before = "";
|
|
4943
|
+
try {
|
|
4944
|
+
const stat = await fs14.stat(resolved);
|
|
4945
|
+
if (!stat.isDirectory()) {
|
|
4946
|
+
before = await fs14.readFile(resolved, "utf8");
|
|
4947
|
+
}
|
|
4948
|
+
} catch {
|
|
4949
|
+
before = "";
|
|
4950
|
+
}
|
|
4951
|
+
const preview = buildBoundedDiffPreview(
|
|
4952
|
+
createUnifiedDiff(filePath, before, content)
|
|
4953
|
+
);
|
|
4954
|
+
previewDiff = preview.diff;
|
|
4955
|
+
previewTruncated = preview.truncated;
|
|
4956
|
+
} catch {
|
|
4957
|
+
previewDiff = null;
|
|
4958
|
+
previewSummary = null;
|
|
4959
|
+
previewTruncated = false;
|
|
4960
|
+
}
|
|
4961
|
+
}
|
|
4962
|
+
} else if (call.function.name === "edit" && editTool) {
|
|
4963
|
+
const parsed = parseToolArgsObject(rawArgs);
|
|
4964
|
+
if (parsed) {
|
|
4965
|
+
const filePath = typeof parsed.file_path === "string" ? parsed.file_path : "";
|
|
4966
|
+
const language = typeof parsed.language === "string" ? parsed.language : "";
|
|
4967
|
+
if (filePath) {
|
|
4968
|
+
previewFilePath = filePath;
|
|
4969
|
+
}
|
|
4970
|
+
previewLanguage = resolvePreviewLanguageHint({
|
|
4971
|
+
language,
|
|
4972
|
+
filePath
|
|
4973
|
+
}) ?? previewLanguage;
|
|
4974
|
+
const dryRunInput = { ...parsed, dry_run: true };
|
|
4975
|
+
try {
|
|
4976
|
+
const result = await editTool.executeRaw(
|
|
4977
|
+
JSON.stringify(dryRunInput),
|
|
4978
|
+
toolCtx
|
|
4979
|
+
);
|
|
4980
|
+
if (result && typeof result === "object") {
|
|
4981
|
+
const obj = unwrapToolJsonObject(result);
|
|
4982
|
+
if (!obj) {
|
|
4983
|
+
previewSummary = "Preview unavailable: unexpected dry-run output";
|
|
4984
|
+
} else {
|
|
4985
|
+
const resultFilePath = typeof obj.file_path === "string" ? obj.file_path : "";
|
|
4986
|
+
const resultLanguage = typeof obj.language === "string" ? obj.language : "";
|
|
4987
|
+
if (!previewFilePath && resultFilePath) {
|
|
4988
|
+
previewFilePath = resultFilePath;
|
|
4989
|
+
}
|
|
4990
|
+
const diff = typeof obj.diff === "string" ? obj.diff : "";
|
|
4991
|
+
const summary = typeof obj.summary === "string" ? obj.summary : "";
|
|
4992
|
+
previewLanguage = resolvePreviewLanguageHint({
|
|
4993
|
+
language: resultLanguage || previewLanguage,
|
|
4994
|
+
filePath: previewFilePath || resultFilePath,
|
|
4995
|
+
diff
|
|
4996
|
+
}) ?? previewLanguage;
|
|
4997
|
+
const preview = buildBoundedDiffPreview(diff);
|
|
4998
|
+
previewDiff = preview.diff;
|
|
4999
|
+
previewTruncated = preview.truncated;
|
|
5000
|
+
if (!previewDiff) {
|
|
5001
|
+
previewSummary = summary || "Preview: no diff content";
|
|
5002
|
+
}
|
|
5003
|
+
}
|
|
5004
|
+
}
|
|
5005
|
+
} catch {
|
|
5006
|
+
previewSummary = "Preview unavailable: dry-run failed";
|
|
5007
|
+
}
|
|
5008
|
+
}
|
|
5009
|
+
}
|
|
3926
5010
|
if (runId) {
|
|
3927
|
-
|
|
5011
|
+
if (previewDiff || previewSummary) {
|
|
5012
|
+
previewLanguage = resolvePreviewLanguageHint({
|
|
5013
|
+
language: previewLanguage,
|
|
5014
|
+
filePath: previewFilePath,
|
|
5015
|
+
diff: previewDiff
|
|
5016
|
+
}) ?? previewLanguage;
|
|
5017
|
+
await sendAgentEventAsync(state, runId, {
|
|
5018
|
+
type: "permission.preview",
|
|
5019
|
+
tool: call.function.name,
|
|
5020
|
+
tool_call_id: call.id,
|
|
5021
|
+
...previewFilePath ? { file_path: previewFilePath } : {},
|
|
5022
|
+
...previewLanguage ? { language: previewLanguage } : {},
|
|
5023
|
+
...previewDiff ? { diff: previewDiff } : {},
|
|
5024
|
+
...previewSummary ? { summary: previewSummary } : {},
|
|
5025
|
+
...previewTruncated ? { truncated: true } : {}
|
|
5026
|
+
});
|
|
5027
|
+
}
|
|
5028
|
+
await sendAgentEventAsync(state, runId, {
|
|
5029
|
+
type: "permission.ready",
|
|
5030
|
+
tool: call.function.name,
|
|
5031
|
+
tool_call_id: call.id
|
|
5032
|
+
});
|
|
5033
|
+
await sendRunStatusAsync(
|
|
5034
|
+
runId,
|
|
5035
|
+
"awaiting_ui",
|
|
5036
|
+
"waiting for confirmation"
|
|
5037
|
+
);
|
|
3928
5038
|
}
|
|
3929
5039
|
const confirmResult = await requestUiConfirm(state, {
|
|
3930
5040
|
run_id: runId,
|
|
@@ -4270,7 +5380,7 @@ var HttpMcpClient = class {
|
|
|
4270
5380
|
};
|
|
4271
5381
|
|
|
4272
5382
|
// src/mcp/stdio-client.ts
|
|
4273
|
-
import { spawn as
|
|
5383
|
+
import { spawn as spawn4 } from "child_process";
|
|
4274
5384
|
import { createInterface } from "readline";
|
|
4275
5385
|
var StdioMcpClient = class {
|
|
4276
5386
|
constructor(options) {
|
|
@@ -4279,7 +5389,7 @@ var StdioMcpClient = class {
|
|
|
4279
5389
|
...process.env,
|
|
4280
5390
|
...options.env ?? {}
|
|
4281
5391
|
};
|
|
4282
|
-
this.child =
|
|
5392
|
+
this.child = spawn4(options.command, options.args, {
|
|
4283
5393
|
cwd: options.cwd,
|
|
4284
5394
|
env,
|
|
4285
5395
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -4489,8 +5599,8 @@ var parseAuthorizationServerMetadata = (value) => {
|
|
|
4489
5599
|
var buildAuthorizationServerMetadataUrl = (issuer) => {
|
|
4490
5600
|
try {
|
|
4491
5601
|
const parsed = new URL(issuer);
|
|
4492
|
-
const
|
|
4493
|
-
const basePath =
|
|
5602
|
+
const path16 = parsed.pathname === "/" ? "" : parsed.pathname;
|
|
5603
|
+
const basePath = path16.startsWith("/") ? path16 : `/${path16}`;
|
|
4494
5604
|
const metadataPath = `/.well-known/oauth-authorization-server${basePath}`;
|
|
4495
5605
|
return new URL(metadataPath, `${parsed.origin}/`).toString();
|
|
4496
5606
|
} catch {
|
|
@@ -4587,24 +5697,24 @@ var fetchDiscoveredOAuthConfig = async (serverUrl) => {
|
|
|
4587
5697
|
};
|
|
4588
5698
|
|
|
4589
5699
|
// src/mcp/tooling.ts
|
|
4590
|
-
import
|
|
5700
|
+
import crypto5 from "crypto";
|
|
4591
5701
|
var MAX_SCHEMA_SIZE_BYTES = 64 * 1024;
|
|
4592
5702
|
var MAX_TOOL_OUTPUT_CHARS = 1e5;
|
|
4593
5703
|
var MAX_TOOLS_LIST_PAGES = 100;
|
|
4594
5704
|
var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4595
5705
|
var isMcpToolDescriptor = (value) => isRecord4(value) && typeof value.name === "string";
|
|
4596
|
-
var
|
|
5706
|
+
var toSlug2 = (value) => {
|
|
4597
5707
|
const slug = value.toLowerCase().replace(/[^a-z0-9_]+/g, "_").replace(/^_+|_+$/g, "");
|
|
4598
5708
|
return slug || "tool";
|
|
4599
5709
|
};
|
|
4600
|
-
var hash8 = (value) =>
|
|
5710
|
+
var hash8 = (value) => crypto5.createHash("sha256").update(value).digest("hex").slice(0, 8);
|
|
4601
5711
|
var clampToolName = (value) => {
|
|
4602
5712
|
if (value.length <= 63) return value;
|
|
4603
5713
|
return value.slice(0, 63);
|
|
4604
5714
|
};
|
|
4605
5715
|
var buildToolName = (serverId, toolName) => {
|
|
4606
|
-
const serverSlug =
|
|
4607
|
-
const toolSlug =
|
|
5716
|
+
const serverSlug = toSlug2(serverId);
|
|
5717
|
+
const toolSlug = toSlug2(toolName);
|
|
4608
5718
|
const fingerprint = hash8(`${serverId}:${toolName}`);
|
|
4609
5719
|
const maxPrefixLength = 63 - (fingerprint.length + 1);
|
|
4610
5720
|
const prefix = `mcp_${serverSlug}_${toolSlug}`.slice(0, maxPrefixLength);
|
|
@@ -4762,7 +5872,7 @@ var createMcpToolAdapter = (params) => {
|
|
|
4762
5872
|
|
|
4763
5873
|
// src/mcp/manager.ts
|
|
4764
5874
|
var OAUTH_TOKEN_EXPIRY_SKEW_MS = 6e4;
|
|
4765
|
-
var
|
|
5875
|
+
var nowIso3 = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
4766
5876
|
var describeError2 = (error) => error instanceof Error ? error.message : String(error);
|
|
4767
5877
|
var toListServer = (id, runtime) => ({
|
|
4768
5878
|
id,
|
|
@@ -4894,7 +6004,7 @@ var McpManager = class {
|
|
|
4894
6004
|
runtime.tools_count = runtime.toolAdapters.length;
|
|
4895
6005
|
runtime.state = "ready";
|
|
4896
6006
|
runtime.last_error = void 0;
|
|
4897
|
-
runtime.last_connected_at =
|
|
6007
|
+
runtime.last_connected_at = nowIso3();
|
|
4898
6008
|
connectOptions?.onStatus?.(
|
|
4899
6009
|
`MCP server ready: ${serverId} (${runtime.tools_count ?? 0} tools)`
|
|
4900
6010
|
);
|
|
@@ -5147,6 +6257,10 @@ var McpManager = class {
|
|
|
5147
6257
|
};
|
|
5148
6258
|
|
|
5149
6259
|
// src/rpc/handlers.ts
|
|
6260
|
+
import { updateModelConfig as updateModelConfig2 } from "@codelia/config-loader";
|
|
6261
|
+
import {
|
|
6262
|
+
RPC_ERROR_CODE as RPC_ERROR_CODE7
|
|
6263
|
+
} from "@codelia/protocol";
|
|
5150
6264
|
import {
|
|
5151
6265
|
RunEventStoreFactoryImpl,
|
|
5152
6266
|
SessionStateStoreImpl
|
|
@@ -5158,6 +6272,9 @@ var SERVER_VERSION = "0.1.0";
|
|
|
5158
6272
|
var PROTOCOL_VERSION = "0";
|
|
5159
6273
|
|
|
5160
6274
|
// src/rpc/context.ts
|
|
6275
|
+
import {
|
|
6276
|
+
RPC_ERROR_CODE
|
|
6277
|
+
} from "@codelia/protocol";
|
|
5161
6278
|
var createContextHandlers = ({
|
|
5162
6279
|
state,
|
|
5163
6280
|
log: log2
|
|
@@ -5247,7 +6364,7 @@ var createContextHandlers = ({
|
|
|
5247
6364
|
sendResult(id, result);
|
|
5248
6365
|
} catch (error) {
|
|
5249
6366
|
sendError(id, {
|
|
5250
|
-
code:
|
|
6367
|
+
code: RPC_ERROR_CODE.RUNTIME_INTERNAL,
|
|
5251
6368
|
message: `context.inspect failed: ${String(error)}`
|
|
5252
6369
|
});
|
|
5253
6370
|
}
|
|
@@ -5256,12 +6373,50 @@ var createContextHandlers = ({
|
|
|
5256
6373
|
};
|
|
5257
6374
|
|
|
5258
6375
|
// src/rpc/history.ts
|
|
5259
|
-
import { createReadStream, promises as
|
|
5260
|
-
import
|
|
6376
|
+
import { createReadStream, promises as fs15 } from "fs";
|
|
6377
|
+
import path13 from "path";
|
|
5261
6378
|
import { createInterface as createInterface2 } from "readline";
|
|
5262
|
-
import {
|
|
6379
|
+
import {
|
|
6380
|
+
RPC_ERROR_CODE as RPC_ERROR_CODE2
|
|
6381
|
+
} from "@codelia/protocol";
|
|
6382
|
+
import { resolveStoragePaths as resolveStoragePaths3 } from "@codelia/storage";
|
|
5263
6383
|
var DEFAULT_HISTORY_RUNS = 20;
|
|
5264
6384
|
var DEFAULT_HISTORY_EVENTS = 1500;
|
|
6385
|
+
var runStartInputToHiddenMessage = (input) => {
|
|
6386
|
+
if (!input) {
|
|
6387
|
+
return "";
|
|
6388
|
+
}
|
|
6389
|
+
if (input.type === "text") {
|
|
6390
|
+
return input.text;
|
|
6391
|
+
}
|
|
6392
|
+
if (!Array.isArray(input.parts)) {
|
|
6393
|
+
return "";
|
|
6394
|
+
}
|
|
6395
|
+
let message = "";
|
|
6396
|
+
for (const part of input.parts) {
|
|
6397
|
+
if (!part || typeof part !== "object") {
|
|
6398
|
+
continue;
|
|
6399
|
+
}
|
|
6400
|
+
if (part.type === "text") {
|
|
6401
|
+
message += part.text;
|
|
6402
|
+
continue;
|
|
6403
|
+
}
|
|
6404
|
+
if (part.type === "image_url") {
|
|
6405
|
+
message += "[image]";
|
|
6406
|
+
}
|
|
6407
|
+
}
|
|
6408
|
+
return message;
|
|
6409
|
+
};
|
|
6410
|
+
var parseRunHeader = (headerLine) => {
|
|
6411
|
+
try {
|
|
6412
|
+
const header = JSON.parse(headerLine);
|
|
6413
|
+
if (!header || typeof header !== "object") return null;
|
|
6414
|
+
if (header.type !== "header") return null;
|
|
6415
|
+
return header;
|
|
6416
|
+
} catch {
|
|
6417
|
+
return null;
|
|
6418
|
+
}
|
|
6419
|
+
};
|
|
5265
6420
|
var readFirstLine = async (filePath) => {
|
|
5266
6421
|
const stream = createReadStream(filePath, { encoding: "utf8" });
|
|
5267
6422
|
const reader = createInterface2({
|
|
@@ -5278,12 +6433,12 @@ var readFirstLine = async (filePath) => {
|
|
|
5278
6433
|
stream.destroy();
|
|
5279
6434
|
}
|
|
5280
6435
|
};
|
|
5281
|
-
var
|
|
5282
|
-
const paths =
|
|
6436
|
+
var collectRunCandidates = async (log2) => {
|
|
6437
|
+
const paths = resolveStoragePaths3();
|
|
5283
6438
|
const sessionsDir = paths.sessionsDir;
|
|
5284
6439
|
let yearEntries;
|
|
5285
6440
|
try {
|
|
5286
|
-
yearEntries = await
|
|
6441
|
+
yearEntries = await fs15.readdir(sessionsDir, {
|
|
5287
6442
|
withFileTypes: true,
|
|
5288
6443
|
encoding: "utf8"
|
|
5289
6444
|
});
|
|
@@ -5295,10 +6450,10 @@ var collectSessionRuns = async (sessionId, maxRuns, log2) => {
|
|
|
5295
6450
|
for (const yearEntry of yearEntries) {
|
|
5296
6451
|
if (!yearEntry.isDirectory()) continue;
|
|
5297
6452
|
if (!/^\d{4}$/.test(yearEntry.name)) continue;
|
|
5298
|
-
const yearPath =
|
|
6453
|
+
const yearPath = path13.join(sessionsDir, yearEntry.name);
|
|
5299
6454
|
let monthEntries;
|
|
5300
6455
|
try {
|
|
5301
|
-
monthEntries = await
|
|
6456
|
+
monthEntries = await fs15.readdir(yearPath, {
|
|
5302
6457
|
withFileTypes: true,
|
|
5303
6458
|
encoding: "utf8"
|
|
5304
6459
|
});
|
|
@@ -5308,10 +6463,10 @@ var collectSessionRuns = async (sessionId, maxRuns, log2) => {
|
|
|
5308
6463
|
for (const monthEntry of monthEntries) {
|
|
5309
6464
|
if (!monthEntry.isDirectory()) continue;
|
|
5310
6465
|
if (!/^\d{2}$/.test(monthEntry.name)) continue;
|
|
5311
|
-
const monthPath =
|
|
6466
|
+
const monthPath = path13.join(yearPath, monthEntry.name);
|
|
5312
6467
|
let dayEntries;
|
|
5313
6468
|
try {
|
|
5314
|
-
dayEntries = await
|
|
6469
|
+
dayEntries = await fs15.readdir(monthPath, {
|
|
5315
6470
|
withFileTypes: true,
|
|
5316
6471
|
encoding: "utf8"
|
|
5317
6472
|
});
|
|
@@ -5321,10 +6476,10 @@ var collectSessionRuns = async (sessionId, maxRuns, log2) => {
|
|
|
5321
6476
|
for (const dayEntry of dayEntries) {
|
|
5322
6477
|
if (!dayEntry.isDirectory()) continue;
|
|
5323
6478
|
if (!/^\d{2}$/.test(dayEntry.name)) continue;
|
|
5324
|
-
const dayPath =
|
|
6479
|
+
const dayPath = path13.join(monthPath, dayEntry.name);
|
|
5325
6480
|
let files;
|
|
5326
6481
|
try {
|
|
5327
|
-
files = await
|
|
6482
|
+
files = await fs15.readdir(dayPath, {
|
|
5328
6483
|
withFileTypes: true,
|
|
5329
6484
|
encoding: "utf8"
|
|
5330
6485
|
});
|
|
@@ -5334,9 +6489,9 @@ var collectSessionRuns = async (sessionId, maxRuns, log2) => {
|
|
|
5334
6489
|
for (const file of files) {
|
|
5335
6490
|
if (!file.isFile()) continue;
|
|
5336
6491
|
if (!file.name.endsWith(".jsonl")) continue;
|
|
5337
|
-
const filePath =
|
|
6492
|
+
const filePath = path13.join(dayPath, file.name);
|
|
5338
6493
|
try {
|
|
5339
|
-
const stat = await
|
|
6494
|
+
const stat = await fs15.stat(filePath);
|
|
5340
6495
|
candidates.push({ path: filePath, mtimeMs: stat.mtimeMs });
|
|
5341
6496
|
} catch {
|
|
5342
6497
|
}
|
|
@@ -5345,6 +6500,10 @@ var collectSessionRuns = async (sessionId, maxRuns, log2) => {
|
|
|
5345
6500
|
}
|
|
5346
6501
|
}
|
|
5347
6502
|
candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
6503
|
+
return candidates;
|
|
6504
|
+
};
|
|
6505
|
+
var collectSessionRuns = async (sessionId, maxRuns, log2) => {
|
|
6506
|
+
const candidates = await collectRunCandidates(log2);
|
|
5348
6507
|
const runs = [];
|
|
5349
6508
|
for (const candidate of candidates) {
|
|
5350
6509
|
if (runs.length >= maxRuns) break;
|
|
@@ -5355,19 +6514,16 @@ var collectSessionRuns = async (sessionId, maxRuns, log2) => {
|
|
|
5355
6514
|
continue;
|
|
5356
6515
|
}
|
|
5357
6516
|
if (!headerLine) continue;
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
});
|
|
5369
|
-
} catch {
|
|
5370
|
-
}
|
|
6517
|
+
const header = parseRunHeader(headerLine);
|
|
6518
|
+
if (!header) continue;
|
|
6519
|
+
if (header.session_id !== sessionId) continue;
|
|
6520
|
+
const runId = header.run_id ?? path13.basename(candidate.path, ".jsonl");
|
|
6521
|
+
const startedAt = header.started_at ?? new Date(candidate.mtimeMs).toISOString();
|
|
6522
|
+
runs.push({
|
|
6523
|
+
path: candidate.path,
|
|
6524
|
+
run_id: runId,
|
|
6525
|
+
started_at: startedAt
|
|
6526
|
+
});
|
|
5371
6527
|
}
|
|
5372
6528
|
runs.sort((a, b) => a.started_at.localeCompare(b.started_at));
|
|
5373
6529
|
return runs;
|
|
@@ -5415,7 +6571,7 @@ var createHistoryHandlers = ({
|
|
|
5415
6571
|
sessions = await sessionStateStore.list();
|
|
5416
6572
|
} catch (error) {
|
|
5417
6573
|
sendError(id, {
|
|
5418
|
-
code:
|
|
6574
|
+
code: RPC_ERROR_CODE2.SESSION_LIST_FAILED,
|
|
5419
6575
|
message: `session list failed: ${String(error)}`
|
|
5420
6576
|
});
|
|
5421
6577
|
return;
|
|
@@ -5430,7 +6586,7 @@ var createHistoryHandlers = ({
|
|
|
5430
6586
|
const handleSessionHistory = async (id, params) => {
|
|
5431
6587
|
const sessionId = params?.session_id?.trim();
|
|
5432
6588
|
if (!sessionId) {
|
|
5433
|
-
sendError(id, { code:
|
|
6589
|
+
sendError(id, { code: RPC_ERROR_CODE2.INVALID_PARAMS, message: "session_id is required" });
|
|
5434
6590
|
return;
|
|
5435
6591
|
}
|
|
5436
6592
|
const maxRuns = Math.max(0, params?.max_runs ?? DEFAULT_HISTORY_RUNS);
|
|
@@ -5460,7 +6616,7 @@ var createHistoryHandlers = ({
|
|
|
5460
6616
|
}
|
|
5461
6617
|
if (!record || typeof record !== "object") continue;
|
|
5462
6618
|
if (record.type === "run.start") {
|
|
5463
|
-
const input = record.input
|
|
6619
|
+
const input = runStartInputToHiddenMessage(record.input);
|
|
5464
6620
|
if (input) {
|
|
5465
6621
|
sendHistoryEvent(record.run_id, -1, {
|
|
5466
6622
|
type: "hidden_user_message",
|
|
@@ -5484,17 +6640,279 @@ var createHistoryHandlers = ({
|
|
|
5484
6640
|
};
|
|
5485
6641
|
sendResult(id, result);
|
|
5486
6642
|
};
|
|
5487
|
-
return { handleSessionList, handleSessionHistory };
|
|
6643
|
+
return { handleSessionList, handleSessionHistory };
|
|
6644
|
+
};
|
|
6645
|
+
|
|
6646
|
+
// src/rpc/model.ts
|
|
6647
|
+
import { updateModelConfig } from "@codelia/config-loader";
|
|
6648
|
+
import {
|
|
6649
|
+
DEFAULT_MODEL_REGISTRY as DEFAULT_MODEL_REGISTRY3,
|
|
6650
|
+
listModels,
|
|
6651
|
+
resolveModel
|
|
6652
|
+
} from "@codelia/core";
|
|
6653
|
+
import { ModelMetadataServiceImpl as ModelMetadataServiceImpl2 } from "@codelia/model-metadata";
|
|
6654
|
+
import {
|
|
6655
|
+
RPC_ERROR_CODE as RPC_ERROR_CODE3
|
|
6656
|
+
} from "@codelia/protocol";
|
|
6657
|
+
var isSupportedProvider = (provider) => provider === "openai" || provider === "anthropic" || provider === "openrouter";
|
|
6658
|
+
var resolveProviderModelEntry = (providerEntries, provider, model) => {
|
|
6659
|
+
if (!providerEntries) return null;
|
|
6660
|
+
return providerEntries[model] ?? providerEntries[`${provider}/${model}`] ?? null;
|
|
6661
|
+
};
|
|
6662
|
+
var parseReleaseTimestamp = (entry) => {
|
|
6663
|
+
const releaseDate = entry?.releaseDate?.trim();
|
|
6664
|
+
if (!releaseDate) return null;
|
|
6665
|
+
const timestamp = Date.parse(releaseDate);
|
|
6666
|
+
if (Number.isNaN(timestamp)) return null;
|
|
6667
|
+
return timestamp;
|
|
6668
|
+
};
|
|
6669
|
+
var normalizeUsdPer1M = (value) => {
|
|
6670
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
6671
|
+
return void 0;
|
|
6672
|
+
}
|
|
6673
|
+
return value;
|
|
6674
|
+
};
|
|
6675
|
+
var buildModelListDetail = (entry) => {
|
|
6676
|
+
if (!entry) return null;
|
|
6677
|
+
const detail = {};
|
|
6678
|
+
if (entry.releaseDate?.trim()) {
|
|
6679
|
+
detail.release_date = entry.releaseDate.trim();
|
|
6680
|
+
}
|
|
6681
|
+
const limits = entry.limits;
|
|
6682
|
+
if (typeof limits?.contextWindow === "number" && Number.isFinite(limits.contextWindow) && limits.contextWindow > 0) {
|
|
6683
|
+
detail.context_window = limits.contextWindow;
|
|
6684
|
+
}
|
|
6685
|
+
if (typeof limits?.inputTokens === "number" && Number.isFinite(limits.inputTokens) && limits.inputTokens > 0) {
|
|
6686
|
+
detail.max_input_tokens = limits.inputTokens;
|
|
6687
|
+
}
|
|
6688
|
+
if (typeof limits?.outputTokens === "number" && Number.isFinite(limits.outputTokens) && limits.outputTokens > 0) {
|
|
6689
|
+
detail.max_output_tokens = limits.outputTokens;
|
|
6690
|
+
}
|
|
6691
|
+
const inputCost = normalizeUsdPer1M(entry.cost?.input);
|
|
6692
|
+
if (inputCost !== void 0) {
|
|
6693
|
+
detail.cost_per_1m_input_tokens_usd = inputCost;
|
|
6694
|
+
}
|
|
6695
|
+
const outputCost = normalizeUsdPer1M(entry.cost?.output);
|
|
6696
|
+
if (outputCost !== void 0) {
|
|
6697
|
+
detail.cost_per_1m_output_tokens_usd = outputCost;
|
|
6698
|
+
}
|
|
6699
|
+
return Object.keys(detail).length ? detail : null;
|
|
6700
|
+
};
|
|
6701
|
+
var sortModelsByReleaseDate = (models, provider, providerEntries) => {
|
|
6702
|
+
const sortable = models.map((model, index) => ({
|
|
6703
|
+
model,
|
|
6704
|
+
index,
|
|
6705
|
+
releaseTimestamp: parseReleaseTimestamp(
|
|
6706
|
+
resolveProviderModelEntry(providerEntries, provider, model)
|
|
6707
|
+
)
|
|
6708
|
+
}));
|
|
6709
|
+
sortable.sort((left, right) => {
|
|
6710
|
+
if (left.releaseTimestamp !== null && right.releaseTimestamp !== null && left.releaseTimestamp !== right.releaseTimestamp) {
|
|
6711
|
+
return right.releaseTimestamp - left.releaseTimestamp;
|
|
6712
|
+
}
|
|
6713
|
+
if (left.releaseTimestamp !== null && right.releaseTimestamp === null) {
|
|
6714
|
+
return -1;
|
|
6715
|
+
}
|
|
6716
|
+
if (left.releaseTimestamp === null && right.releaseTimestamp !== null) {
|
|
6717
|
+
return 1;
|
|
6718
|
+
}
|
|
6719
|
+
return left.index - right.index;
|
|
6720
|
+
});
|
|
6721
|
+
return sortable.map((item) => item.model);
|
|
6722
|
+
};
|
|
6723
|
+
var loadProviderModelEntries = async (provider) => {
|
|
6724
|
+
const metadataService = new ModelMetadataServiceImpl2();
|
|
6725
|
+
const allEntries = await metadataService.getAllModelEntries();
|
|
6726
|
+
return allEntries[provider] ?? null;
|
|
6727
|
+
};
|
|
6728
|
+
var OPENROUTER_BASE_URL2 = "https://openrouter.ai/api/v1";
|
|
6729
|
+
var parseUnixSecondsToDate = (value) => {
|
|
6730
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
6731
|
+
return void 0;
|
|
6732
|
+
}
|
|
6733
|
+
const date = new Date(Math.floor(value) * 1e3);
|
|
6734
|
+
const timestamp = date.getTime();
|
|
6735
|
+
if (!Number.isFinite(timestamp)) {
|
|
6736
|
+
return void 0;
|
|
6737
|
+
}
|
|
6738
|
+
return date.toISOString().slice(0, 10);
|
|
6739
|
+
};
|
|
6740
|
+
var parsePositiveNumber = (value) => {
|
|
6741
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
6742
|
+
return void 0;
|
|
6743
|
+
}
|
|
6744
|
+
return value;
|
|
6745
|
+
};
|
|
6746
|
+
var buildOpenRouterHeaders = (apiKey) => {
|
|
6747
|
+
const headers = new Headers();
|
|
6748
|
+
headers.set("Authorization", `Bearer ${apiKey}`);
|
|
6749
|
+
const referer = readEnvValue("OPENROUTER_HTTP_REFERER");
|
|
6750
|
+
if (referer) {
|
|
6751
|
+
headers.set("HTTP-Referer", referer);
|
|
6752
|
+
}
|
|
6753
|
+
const title = readEnvValue("OPENROUTER_X_TITLE");
|
|
6754
|
+
if (title) {
|
|
6755
|
+
headers.set("X-Title", title);
|
|
6756
|
+
}
|
|
6757
|
+
return headers;
|
|
6758
|
+
};
|
|
6759
|
+
var resolveOpenRouterApiKey = async () => {
|
|
6760
|
+
const envKey = readEnvValue("OPENROUTER_API_KEY");
|
|
6761
|
+
if (envKey) {
|
|
6762
|
+
return envKey;
|
|
6763
|
+
}
|
|
6764
|
+
const store = new AuthStore();
|
|
6765
|
+
const auth = await store.load();
|
|
6766
|
+
const providerAuth = auth.providers.openrouter;
|
|
6767
|
+
if (providerAuth?.method !== "api_key") {
|
|
6768
|
+
return null;
|
|
6769
|
+
}
|
|
6770
|
+
const value = providerAuth.api_key.trim();
|
|
6771
|
+
return value ? value : null;
|
|
6772
|
+
};
|
|
6773
|
+
var resolveOpenRouterApiKeyWithPrompt = async ({
|
|
6774
|
+
state,
|
|
6775
|
+
log: log2
|
|
6776
|
+
}) => {
|
|
6777
|
+
const existing = await resolveOpenRouterApiKey();
|
|
6778
|
+
if (existing) {
|
|
6779
|
+
return existing;
|
|
6780
|
+
}
|
|
6781
|
+
if (!state) {
|
|
6782
|
+
throw new Error("OpenRouter API key is required");
|
|
6783
|
+
}
|
|
6784
|
+
const authResolver = await AuthResolver.create(state, log2);
|
|
6785
|
+
const auth = await authResolver.resolveProviderAuth("openrouter");
|
|
6786
|
+
if (auth.method !== "api_key") {
|
|
6787
|
+
throw new Error("OpenRouter API key is required");
|
|
6788
|
+
}
|
|
6789
|
+
const value = auth.api_key.trim();
|
|
6790
|
+
if (!value) {
|
|
6791
|
+
throw new Error("OpenRouter API key is required");
|
|
6792
|
+
}
|
|
6793
|
+
return value;
|
|
6794
|
+
};
|
|
6795
|
+
var parseOpenRouterModel = (value) => {
|
|
6796
|
+
if (!value || typeof value !== "object") {
|
|
6797
|
+
return null;
|
|
6798
|
+
}
|
|
6799
|
+
const entry = value;
|
|
6800
|
+
const id = typeof entry.id === "string" ? entry.id.trim() : "";
|
|
6801
|
+
if (!id) {
|
|
6802
|
+
return null;
|
|
6803
|
+
}
|
|
6804
|
+
const topProvider = entry.top_provider && typeof entry.top_provider === "object" ? entry.top_provider : null;
|
|
6805
|
+
return {
|
|
6806
|
+
id,
|
|
6807
|
+
created: typeof entry.created === "number" && Number.isFinite(entry.created) ? entry.created : void 0,
|
|
6808
|
+
context_length: parsePositiveNumber(entry.context_length),
|
|
6809
|
+
top_provider: topProvider ? {
|
|
6810
|
+
context_length: parsePositiveNumber(topProvider.context_length),
|
|
6811
|
+
max_completion_tokens: parsePositiveNumber(
|
|
6812
|
+
topProvider.max_completion_tokens
|
|
6813
|
+
)
|
|
6814
|
+
} : void 0
|
|
6815
|
+
};
|
|
6816
|
+
};
|
|
6817
|
+
var fetchOpenRouterModels = async (apiKey) => {
|
|
6818
|
+
const response = await fetch(`${OPENROUTER_BASE_URL2}/models`, {
|
|
6819
|
+
headers: buildOpenRouterHeaders(apiKey)
|
|
6820
|
+
});
|
|
6821
|
+
if (!response.ok) {
|
|
6822
|
+
const body = await response.text().catch(() => "");
|
|
6823
|
+
const snippet = body ? body.slice(0, 300) : "(empty)";
|
|
6824
|
+
throw new Error(
|
|
6825
|
+
`OpenRouter models request failed (${response.status}): ${snippet}`
|
|
6826
|
+
);
|
|
6827
|
+
}
|
|
6828
|
+
const payload = await response.json();
|
|
6829
|
+
if (!payload || typeof payload !== "object") {
|
|
6830
|
+
throw new Error("OpenRouter models response is not an object");
|
|
6831
|
+
}
|
|
6832
|
+
const data = payload.data;
|
|
6833
|
+
if (!Array.isArray(data)) {
|
|
6834
|
+
throw new Error("OpenRouter models response has no data array");
|
|
6835
|
+
}
|
|
6836
|
+
const models = data.map((entry) => parseOpenRouterModel(entry)).filter((entry) => !!entry);
|
|
6837
|
+
models.sort((left, right) => {
|
|
6838
|
+
const leftCreated = left.created ?? 0;
|
|
6839
|
+
const rightCreated = right.created ?? 0;
|
|
6840
|
+
if (leftCreated !== rightCreated) {
|
|
6841
|
+
return rightCreated - leftCreated;
|
|
6842
|
+
}
|
|
6843
|
+
return left.id.localeCompare(right.id);
|
|
6844
|
+
});
|
|
6845
|
+
return models;
|
|
6846
|
+
};
|
|
6847
|
+
var buildOpenRouterModelList = async ({
|
|
6848
|
+
includeDetails,
|
|
6849
|
+
state,
|
|
6850
|
+
log: log2
|
|
6851
|
+
}) => {
|
|
6852
|
+
const apiKey = await resolveOpenRouterApiKeyWithPrompt({ state, log: log2 });
|
|
6853
|
+
const models = await fetchOpenRouterModels(apiKey);
|
|
6854
|
+
const ids = models.map((model) => model.id);
|
|
6855
|
+
if (!includeDetails) {
|
|
6856
|
+
return { models: ids };
|
|
6857
|
+
}
|
|
6858
|
+
const details = {};
|
|
6859
|
+
for (const model of models) {
|
|
6860
|
+
const detail = {};
|
|
6861
|
+
const releaseDate = parseUnixSecondsToDate(model.created);
|
|
6862
|
+
if (releaseDate) {
|
|
6863
|
+
detail.release_date = releaseDate;
|
|
6864
|
+
}
|
|
6865
|
+
const contextWindow = model.context_length ?? model.top_provider?.context_length;
|
|
6866
|
+
if (contextWindow && contextWindow > 0) {
|
|
6867
|
+
detail.context_window = contextWindow;
|
|
6868
|
+
detail.max_input_tokens = contextWindow;
|
|
6869
|
+
}
|
|
6870
|
+
const maxOutputTokens = model.top_provider?.max_completion_tokens;
|
|
6871
|
+
if (maxOutputTokens && maxOutputTokens > 0) {
|
|
6872
|
+
detail.max_output_tokens = maxOutputTokens;
|
|
6873
|
+
}
|
|
6874
|
+
if (Object.keys(detail).length) {
|
|
6875
|
+
details[model.id] = detail;
|
|
6876
|
+
}
|
|
6877
|
+
}
|
|
6878
|
+
return Object.keys(details).length ? { models: ids, details } : { models: ids };
|
|
6879
|
+
};
|
|
6880
|
+
var buildProviderModelList = async ({
|
|
6881
|
+
provider,
|
|
6882
|
+
includeDetails,
|
|
6883
|
+
state,
|
|
6884
|
+
log: log2
|
|
6885
|
+
}) => {
|
|
6886
|
+
if (provider === "openrouter") {
|
|
6887
|
+
return buildOpenRouterModelList({ includeDetails, state, log: log2 });
|
|
6888
|
+
}
|
|
6889
|
+
let providerEntries = null;
|
|
6890
|
+
try {
|
|
6891
|
+
providerEntries = await loadProviderModelEntries(provider);
|
|
6892
|
+
} catch (error) {
|
|
6893
|
+
if (includeDetails) {
|
|
6894
|
+
log2(`model.list details error: ${error}`);
|
|
6895
|
+
}
|
|
6896
|
+
}
|
|
6897
|
+
const models = sortModelsByReleaseDate(
|
|
6898
|
+
listModels(DEFAULT_MODEL_REGISTRY3, provider).map((model) => model.id),
|
|
6899
|
+
provider,
|
|
6900
|
+
providerEntries
|
|
6901
|
+
);
|
|
6902
|
+
if (!includeDetails || !providerEntries) {
|
|
6903
|
+
return { models };
|
|
6904
|
+
}
|
|
6905
|
+
const details = {};
|
|
6906
|
+
for (const model of models) {
|
|
6907
|
+
const detail = buildModelListDetail(
|
|
6908
|
+
resolveProviderModelEntry(providerEntries, provider, model)
|
|
6909
|
+
);
|
|
6910
|
+
if (detail) {
|
|
6911
|
+
details[model] = detail;
|
|
6912
|
+
}
|
|
6913
|
+
}
|
|
6914
|
+
return Object.keys(details).length ? { models, details } : { models };
|
|
5488
6915
|
};
|
|
5489
|
-
|
|
5490
|
-
// src/rpc/model.ts
|
|
5491
|
-
import { updateModelConfig } from "@codelia/config-loader";
|
|
5492
|
-
import {
|
|
5493
|
-
DEFAULT_MODEL_REGISTRY as DEFAULT_MODEL_REGISTRY3,
|
|
5494
|
-
listModels,
|
|
5495
|
-
resolveModel
|
|
5496
|
-
} from "@codelia/core";
|
|
5497
|
-
import { ModelMetadataServiceImpl as ModelMetadataServiceImpl2 } from "@codelia/model-metadata";
|
|
5498
6916
|
var createModelHandlers = ({
|
|
5499
6917
|
state,
|
|
5500
6918
|
log: log2
|
|
@@ -5502,9 +6920,9 @@ var createModelHandlers = ({
|
|
|
5502
6920
|
const handleModelList = async (id, params) => {
|
|
5503
6921
|
const requestedProvider = params?.provider;
|
|
5504
6922
|
const includeDetails = params?.include_details ?? false;
|
|
5505
|
-
if (requestedProvider && requestedProvider
|
|
6923
|
+
if (requestedProvider && !isSupportedProvider(requestedProvider)) {
|
|
5506
6924
|
sendError(id, {
|
|
5507
|
-
code:
|
|
6925
|
+
code: RPC_ERROR_CODE3.INVALID_PARAMS,
|
|
5508
6926
|
message: `unsupported provider: ${requestedProvider}`
|
|
5509
6927
|
});
|
|
5510
6928
|
return;
|
|
@@ -5518,54 +6936,35 @@ var createModelHandlers = ({
|
|
|
5518
6936
|
current = config.name;
|
|
5519
6937
|
}
|
|
5520
6938
|
} catch (error) {
|
|
5521
|
-
sendError(id, { code:
|
|
6939
|
+
sendError(id, { code: RPC_ERROR_CODE3.RUNTIME_INTERNAL, message: String(error) });
|
|
5522
6940
|
return;
|
|
5523
6941
|
}
|
|
5524
6942
|
const provider = requestedProvider ?? configuredProvider ?? "openai";
|
|
5525
|
-
if (provider
|
|
6943
|
+
if (!isSupportedProvider(provider)) {
|
|
5526
6944
|
sendError(id, {
|
|
5527
|
-
code:
|
|
6945
|
+
code: RPC_ERROR_CODE3.INVALID_PARAMS,
|
|
5528
6946
|
message: `unsupported provider: ${provider}`
|
|
5529
6947
|
});
|
|
5530
6948
|
return;
|
|
5531
6949
|
}
|
|
5532
|
-
|
|
6950
|
+
let models;
|
|
6951
|
+
let details;
|
|
6952
|
+
try {
|
|
6953
|
+
const result2 = await buildProviderModelList({
|
|
6954
|
+
provider,
|
|
6955
|
+
includeDetails,
|
|
6956
|
+
state,
|
|
6957
|
+
log: log2
|
|
6958
|
+
});
|
|
6959
|
+
models = result2.models;
|
|
6960
|
+
details = result2.details;
|
|
6961
|
+
} catch (error) {
|
|
6962
|
+
sendError(id, { code: RPC_ERROR_CODE3.RUNTIME_INTERNAL, message: String(error) });
|
|
6963
|
+
return;
|
|
6964
|
+
}
|
|
5533
6965
|
if (current && !models.includes(current)) {
|
|
5534
6966
|
current = void 0;
|
|
5535
6967
|
}
|
|
5536
|
-
let details;
|
|
5537
|
-
if (includeDetails) {
|
|
5538
|
-
try {
|
|
5539
|
-
const metadataService = new ModelMetadataServiceImpl2();
|
|
5540
|
-
const entries = await metadataService.getAllModelEntries();
|
|
5541
|
-
const providerEntries = entries[provider];
|
|
5542
|
-
if (providerEntries) {
|
|
5543
|
-
const nextDetails = {};
|
|
5544
|
-
for (const model of models) {
|
|
5545
|
-
const limits = providerEntries[model]?.limits;
|
|
5546
|
-
if (!limits) continue;
|
|
5547
|
-
const detail = {};
|
|
5548
|
-
if (typeof limits.contextWindow === "number" && limits.contextWindow > 0) {
|
|
5549
|
-
detail.context_window = limits.contextWindow;
|
|
5550
|
-
}
|
|
5551
|
-
if (typeof limits.inputTokens === "number" && limits.inputTokens > 0) {
|
|
5552
|
-
detail.max_input_tokens = limits.inputTokens;
|
|
5553
|
-
}
|
|
5554
|
-
if (typeof limits.outputTokens === "number" && limits.outputTokens > 0) {
|
|
5555
|
-
detail.max_output_tokens = limits.outputTokens;
|
|
5556
|
-
}
|
|
5557
|
-
if (Object.keys(detail).length > 0) {
|
|
5558
|
-
nextDetails[model] = detail;
|
|
5559
|
-
}
|
|
5560
|
-
}
|
|
5561
|
-
if (Object.keys(nextDetails).length > 0) {
|
|
5562
|
-
details = nextDetails;
|
|
5563
|
-
}
|
|
5564
|
-
}
|
|
5565
|
-
} catch (error) {
|
|
5566
|
-
log2(`model.list details error: ${error}`);
|
|
5567
|
-
}
|
|
5568
|
-
}
|
|
5569
6968
|
const result = {
|
|
5570
6969
|
provider,
|
|
5571
6970
|
models,
|
|
@@ -5576,26 +6975,28 @@ var createModelHandlers = ({
|
|
|
5576
6975
|
};
|
|
5577
6976
|
const handleModelSet = async (id, params) => {
|
|
5578
6977
|
if (state.activeRunId) {
|
|
5579
|
-
sendError(id, { code:
|
|
6978
|
+
sendError(id, { code: RPC_ERROR_CODE3.RUNTIME_BUSY, message: "runtime busy" });
|
|
5580
6979
|
return;
|
|
5581
6980
|
}
|
|
5582
6981
|
const provider = params?.provider ?? "openai";
|
|
5583
6982
|
const name = params?.name?.trim();
|
|
5584
6983
|
if (!name) {
|
|
5585
|
-
sendError(id, { code:
|
|
6984
|
+
sendError(id, { code: RPC_ERROR_CODE3.INVALID_PARAMS, message: "model name is required" });
|
|
5586
6985
|
return;
|
|
5587
6986
|
}
|
|
5588
|
-
if (provider !== "openai" && provider !== "anthropic") {
|
|
6987
|
+
if (provider !== "openai" && provider !== "anthropic" && provider !== "openrouter") {
|
|
5589
6988
|
sendError(id, {
|
|
5590
|
-
code:
|
|
6989
|
+
code: RPC_ERROR_CODE3.INVALID_PARAMS,
|
|
5591
6990
|
message: `unsupported provider: ${provider}`
|
|
5592
6991
|
});
|
|
5593
6992
|
return;
|
|
5594
6993
|
}
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
6994
|
+
if (provider !== "openrouter") {
|
|
6995
|
+
const spec = resolveModel(DEFAULT_MODEL_REGISTRY3, name, provider);
|
|
6996
|
+
if (!spec) {
|
|
6997
|
+
sendError(id, { code: RPC_ERROR_CODE3.INVALID_PARAMS, message: `unknown model: ${name}` });
|
|
6998
|
+
return;
|
|
6999
|
+
}
|
|
5599
7000
|
}
|
|
5600
7001
|
try {
|
|
5601
7002
|
const configPath = resolveConfigPath();
|
|
@@ -5605,17 +7006,23 @@ var createModelHandlers = ({
|
|
|
5605
7006
|
sendResult(id, result);
|
|
5606
7007
|
log2(`model.set ${provider}/${name}`);
|
|
5607
7008
|
} catch (error) {
|
|
5608
|
-
sendError(id, { code:
|
|
7009
|
+
sendError(id, { code: RPC_ERROR_CODE3.RUNTIME_INTERNAL, message: String(error) });
|
|
5609
7010
|
}
|
|
5610
7011
|
};
|
|
5611
7012
|
return { handleModelList, handleModelSet };
|
|
5612
7013
|
};
|
|
5613
7014
|
|
|
7015
|
+
// src/rpc/run.ts
|
|
7016
|
+
import {
|
|
7017
|
+
RPC_ERROR_CODE as RPC_ERROR_CODE4
|
|
7018
|
+
} from "@codelia/protocol";
|
|
7019
|
+
|
|
5614
7020
|
// src/rpc/run-debug.ts
|
|
5615
7021
|
import { stringifyContent } from "@codelia/core";
|
|
5616
7022
|
var DEBUG_MAX_CONTENT_CHARS = 2e3;
|
|
5617
7023
|
var DEBUG_MAX_LOG_CHARS = 2e4;
|
|
5618
7024
|
var DEBUG_MAX_EVENT_RESULT_CHARS = 500;
|
|
7025
|
+
var DEBUG_MAX_EVENT_ARGS_CHARS = 2e3;
|
|
5619
7026
|
var truncateText2 = (value, maxChars) => {
|
|
5620
7027
|
if (value.length <= maxChars) return value;
|
|
5621
7028
|
return `${value.slice(0, maxChars)}...[truncated]`;
|
|
@@ -5687,7 +7094,9 @@ var summarizeRunEvent = (event) => {
|
|
|
5687
7094
|
case "tool_call": {
|
|
5688
7095
|
const tool = typeof event.tool === "string" ? event.tool : "unknown";
|
|
5689
7096
|
const toolCallId = typeof event.tool_call_id === "string" ? event.tool_call_id : "unknown";
|
|
5690
|
-
|
|
7097
|
+
const rawArgs = typeof event.raw_args === "string" ? event.raw_args : stringifyUnknown(event.args);
|
|
7098
|
+
const rawArgsSnippet = truncateText2(rawArgs, DEBUG_MAX_EVENT_ARGS_CHARS);
|
|
7099
|
+
return `type=tool_call tool=${tool} tool_call_id=${toolCallId} raw_args=${stringifyUnknown(rawArgsSnippet)}`;
|
|
5691
7100
|
}
|
|
5692
7101
|
case "tool_result": {
|
|
5693
7102
|
const tool = typeof event.tool === "string" ? event.tool : "unknown";
|
|
@@ -5722,7 +7131,7 @@ var logRunDebug = (log2, runId, message) => {
|
|
|
5722
7131
|
if (!isDebugEnabled()) return;
|
|
5723
7132
|
log2(`run debug run_id=${runId} ${message}`);
|
|
5724
7133
|
};
|
|
5725
|
-
var
|
|
7134
|
+
var normalizeToolCallHistory = (messages) => {
|
|
5726
7135
|
const assistantCallIds = /* @__PURE__ */ new Set();
|
|
5727
7136
|
const toolOutputCallIds = /* @__PURE__ */ new Set();
|
|
5728
7137
|
for (const message of messages) {
|
|
@@ -5806,8 +7215,92 @@ var prepareRunInputText = (inputText) => {
|
|
|
5806
7215
|
return `${inputText}${buildSkillMentionHint(mentions)}`;
|
|
5807
7216
|
};
|
|
5808
7217
|
|
|
7218
|
+
// src/rpc/run-input.ts
|
|
7219
|
+
var isRunInputImageMediaType = (value) => value === "image/png" || value === "image/jpeg" || value === "image/webp" || value === "image/gif";
|
|
7220
|
+
var isRunInputImageDetail = (value) => value === "auto" || value === "low" || value === "high";
|
|
7221
|
+
var normalizeRunInputTextPart = (part) => {
|
|
7222
|
+
if (typeof part.text !== "string") {
|
|
7223
|
+
throw new Error("run.start input.parts[text].text must be a string");
|
|
7224
|
+
}
|
|
7225
|
+
return {
|
|
7226
|
+
type: "text",
|
|
7227
|
+
text: prepareRunInputText(part.text)
|
|
7228
|
+
};
|
|
7229
|
+
};
|
|
7230
|
+
var normalizeRunInputImagePart = (part) => {
|
|
7231
|
+
const imageUrl = part.image_url;
|
|
7232
|
+
if (!imageUrl || typeof imageUrl !== "object") {
|
|
7233
|
+
throw new Error("run.start input.parts[image_url].image_url is required");
|
|
7234
|
+
}
|
|
7235
|
+
if (typeof imageUrl.url !== "string" || imageUrl.url.length === 0) {
|
|
7236
|
+
throw new Error(
|
|
7237
|
+
"run.start input.parts[image_url].image_url.url must be a non-empty string"
|
|
7238
|
+
);
|
|
7239
|
+
}
|
|
7240
|
+
if (imageUrl.media_type !== void 0 && !isRunInputImageMediaType(imageUrl.media_type)) {
|
|
7241
|
+
throw new Error(
|
|
7242
|
+
"run.start input.parts[image_url].image_url.media_type must be png/jpeg/webp/gif"
|
|
7243
|
+
);
|
|
7244
|
+
}
|
|
7245
|
+
if (imageUrl.detail !== void 0 && !isRunInputImageDetail(imageUrl.detail)) {
|
|
7246
|
+
throw new Error(
|
|
7247
|
+
"run.start input.parts[image_url].image_url.detail must be auto/low/high"
|
|
7248
|
+
);
|
|
7249
|
+
}
|
|
7250
|
+
return {
|
|
7251
|
+
type: "image_url",
|
|
7252
|
+
image_url: {
|
|
7253
|
+
url: imageUrl.url,
|
|
7254
|
+
...imageUrl.media_type ? { media_type: imageUrl.media_type } : {},
|
|
7255
|
+
...imageUrl.detail ? { detail: imageUrl.detail } : {}
|
|
7256
|
+
}
|
|
7257
|
+
};
|
|
7258
|
+
};
|
|
7259
|
+
var normalizeRunInput = (input) => {
|
|
7260
|
+
if (input.type === "text") {
|
|
7261
|
+
if (typeof input.text !== "string") {
|
|
7262
|
+
throw new Error("run.start input.text must be a string");
|
|
7263
|
+
}
|
|
7264
|
+
return prepareRunInputText(input.text);
|
|
7265
|
+
}
|
|
7266
|
+
if (!Array.isArray(input.parts)) {
|
|
7267
|
+
throw new Error("run.start input.parts must be an array");
|
|
7268
|
+
}
|
|
7269
|
+
return input.parts.map((part) => {
|
|
7270
|
+
if (!part || typeof part !== "object") {
|
|
7271
|
+
throw new Error("run.start input.parts entry must be an object");
|
|
7272
|
+
}
|
|
7273
|
+
if (part.type === "text") {
|
|
7274
|
+
return normalizeRunInputTextPart(part);
|
|
7275
|
+
}
|
|
7276
|
+
if (part.type === "image_url") {
|
|
7277
|
+
return normalizeRunInputImagePart(part);
|
|
7278
|
+
}
|
|
7279
|
+
throw new Error(
|
|
7280
|
+
`run.start input.parts type is not supported: ${String(part.type)}`
|
|
7281
|
+
);
|
|
7282
|
+
});
|
|
7283
|
+
};
|
|
7284
|
+
var runInputLengthForDebug = (input) => {
|
|
7285
|
+
if (typeof input === "string") {
|
|
7286
|
+
return input.length;
|
|
7287
|
+
}
|
|
7288
|
+
let total = 0;
|
|
7289
|
+
for (const part of input) {
|
|
7290
|
+
if (part.type === "text") {
|
|
7291
|
+
total += part.text.length;
|
|
7292
|
+
continue;
|
|
7293
|
+
}
|
|
7294
|
+
if (part.type === "image_url") {
|
|
7295
|
+
total += part.image_url.url.length;
|
|
7296
|
+
}
|
|
7297
|
+
}
|
|
7298
|
+
return total;
|
|
7299
|
+
};
|
|
7300
|
+
|
|
5809
7301
|
// src/rpc/run.ts
|
|
5810
|
-
var
|
|
7302
|
+
var nowIso4 = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
7303
|
+
var SESSION_STATE_SAVE_DEBOUNCE_MS = 1500;
|
|
5811
7304
|
var createSessionAppender = (store, onError) => {
|
|
5812
7305
|
let chain = Promise.resolve();
|
|
5813
7306
|
return (record) => {
|
|
@@ -5819,7 +7312,7 @@ var createSessionAppender = (store, onError) => {
|
|
|
5819
7312
|
var buildSessionState = (sessionId, runId, messages, invokeSeq) => ({
|
|
5820
7313
|
schema_version: 1,
|
|
5821
7314
|
session_id: sessionId,
|
|
5822
|
-
updated_at:
|
|
7315
|
+
updated_at: nowIso4(),
|
|
5823
7316
|
run_id: runId,
|
|
5824
7317
|
invoke_seq: invokeSeq,
|
|
5825
7318
|
messages
|
|
@@ -5830,18 +7323,26 @@ var createRunHandlers = ({
|
|
|
5830
7323
|
log: log2,
|
|
5831
7324
|
runEventStoreFactory,
|
|
5832
7325
|
sessionStateStore,
|
|
5833
|
-
appendSession
|
|
7326
|
+
appendSession,
|
|
7327
|
+
beforeRunStart
|
|
5834
7328
|
}) => {
|
|
5835
7329
|
let activeRunAbort = null;
|
|
5836
7330
|
let runStartQueue = Promise.resolve();
|
|
5837
7331
|
const normalizeRunHistoryAfterCancel = (runId, runtimeAgent) => {
|
|
5838
7332
|
const currentMessages = runtimeAgent.getHistoryMessages();
|
|
5839
|
-
const normalizedMessages =
|
|
7333
|
+
const normalizedMessages = normalizeToolCallHistory(currentMessages);
|
|
5840
7334
|
if (normalizedMessages !== currentMessages) {
|
|
5841
7335
|
runtimeAgent.replaceHistoryMessages(normalizedMessages);
|
|
5842
7336
|
log2(`run.cancel normalized history ${runId}`);
|
|
5843
7337
|
}
|
|
5844
7338
|
};
|
|
7339
|
+
const normalizeRestoredSessionMessages = (messages, sessionId) => {
|
|
7340
|
+
const normalizedMessages = normalizeToolCallHistory(messages);
|
|
7341
|
+
if (normalizedMessages !== messages) {
|
|
7342
|
+
log2(`session restore normalized history ${sessionId}`);
|
|
7343
|
+
}
|
|
7344
|
+
return normalizedMessages;
|
|
7345
|
+
};
|
|
5845
7346
|
const emitRunStatus = (runId, status, message) => {
|
|
5846
7347
|
sendRunStatus(runId, status, message);
|
|
5847
7348
|
const suffix = message ? ` message=${message}` : "";
|
|
@@ -5849,7 +7350,7 @@ var createRunHandlers = ({
|
|
|
5849
7350
|
appendSession({
|
|
5850
7351
|
type: "run.status",
|
|
5851
7352
|
run_id: runId,
|
|
5852
|
-
ts:
|
|
7353
|
+
ts: nowIso4(),
|
|
5853
7354
|
status,
|
|
5854
7355
|
...message ? { message } : {}
|
|
5855
7356
|
});
|
|
@@ -5858,22 +7359,43 @@ var createRunHandlers = ({
|
|
|
5858
7359
|
appendSession({
|
|
5859
7360
|
type: "run.end",
|
|
5860
7361
|
run_id: runId,
|
|
5861
|
-
ts:
|
|
7362
|
+
ts: nowIso4(),
|
|
5862
7363
|
outcome,
|
|
5863
7364
|
...final !== void 0 ? { final } : {}
|
|
5864
7365
|
});
|
|
5865
7366
|
};
|
|
5866
7367
|
const handleRunStart = (id, params) => {
|
|
5867
7368
|
const run = async () => {
|
|
7369
|
+
let normalizedInput;
|
|
7370
|
+
try {
|
|
7371
|
+
normalizedInput = normalizeRunInput(params.input);
|
|
7372
|
+
} catch (error) {
|
|
7373
|
+
sendError(id, {
|
|
7374
|
+
code: RPC_ERROR_CODE4.INVALID_PARAMS,
|
|
7375
|
+
message: String(error)
|
|
7376
|
+
});
|
|
7377
|
+
return;
|
|
7378
|
+
}
|
|
7379
|
+
if (beforeRunStart) {
|
|
7380
|
+
try {
|
|
7381
|
+
await beforeRunStart();
|
|
7382
|
+
} catch (error) {
|
|
7383
|
+
sendError(id, {
|
|
7384
|
+
code: RPC_ERROR_CODE4.RUNTIME_INTERNAL,
|
|
7385
|
+
message: `startup onboarding failed: ${String(error)}`
|
|
7386
|
+
});
|
|
7387
|
+
return;
|
|
7388
|
+
}
|
|
7389
|
+
}
|
|
5868
7390
|
if (state.activeRunId) {
|
|
5869
|
-
sendError(id, { code:
|
|
7391
|
+
sendError(id, { code: RPC_ERROR_CODE4.RUNTIME_BUSY, message: "runtime busy" });
|
|
5870
7392
|
return;
|
|
5871
7393
|
}
|
|
5872
7394
|
let runtimeAgent;
|
|
5873
7395
|
try {
|
|
5874
7396
|
runtimeAgent = await getAgent();
|
|
5875
7397
|
} catch (error) {
|
|
5876
|
-
sendError(id, { code:
|
|
7398
|
+
sendError(id, { code: RPC_ERROR_CODE4.RUNTIME_INTERNAL, message: String(error) });
|
|
5877
7399
|
return;
|
|
5878
7400
|
}
|
|
5879
7401
|
const requestedSessionId = params.session_id?.trim() || void 0;
|
|
@@ -5884,16 +7406,20 @@ var createRunHandlers = ({
|
|
|
5884
7406
|
resumeState = await sessionStateStore.load(requestedSessionId);
|
|
5885
7407
|
} catch (error) {
|
|
5886
7408
|
sendError(id, {
|
|
5887
|
-
code:
|
|
7409
|
+
code: RPC_ERROR_CODE4.SESSION_LOAD_FAILED,
|
|
5888
7410
|
message: `session load failed: ${String(error)}`
|
|
5889
7411
|
});
|
|
5890
7412
|
return;
|
|
5891
7413
|
}
|
|
5892
7414
|
if (!resumeState) {
|
|
5893
|
-
sendError(id, { code:
|
|
7415
|
+
sendError(id, { code: RPC_ERROR_CODE4.SESSION_NOT_FOUND, message: "session not found" });
|
|
5894
7416
|
return;
|
|
5895
7417
|
}
|
|
5896
|
-
const
|
|
7418
|
+
const restoredMessages = Array.isArray(resumeState.messages) ? resumeState.messages : [];
|
|
7419
|
+
const messages = normalizeRestoredSessionMessages(
|
|
7420
|
+
restoredMessages,
|
|
7421
|
+
requestedSessionId
|
|
7422
|
+
);
|
|
5897
7423
|
runtimeAgent.replaceHistoryMessages(messages);
|
|
5898
7424
|
sessionId = requestedSessionId;
|
|
5899
7425
|
state.sessionId = sessionId;
|
|
@@ -5901,17 +7427,25 @@ var createRunHandlers = ({
|
|
|
5901
7427
|
try {
|
|
5902
7428
|
resumeState = await sessionStateStore.load(state.sessionId);
|
|
5903
7429
|
} catch (error) {
|
|
5904
|
-
|
|
7430
|
+
sendError(id, {
|
|
7431
|
+
code: RPC_ERROR_CODE4.SESSION_LOAD_FAILED,
|
|
7432
|
+
message: `session reload failed: ${String(error)}`
|
|
7433
|
+
});
|
|
7434
|
+
return;
|
|
5905
7435
|
}
|
|
5906
7436
|
if (resumeState) {
|
|
5907
|
-
const
|
|
7437
|
+
const restoredMessages = Array.isArray(resumeState.messages) ? resumeState.messages : [];
|
|
7438
|
+
const messages = normalizeRestoredSessionMessages(
|
|
7439
|
+
restoredMessages,
|
|
7440
|
+
state.sessionId
|
|
7441
|
+
);
|
|
5908
7442
|
runtimeAgent.replaceHistoryMessages(messages);
|
|
5909
7443
|
}
|
|
5910
7444
|
} else if (!state.sessionId) {
|
|
5911
7445
|
state.sessionId = sessionId;
|
|
5912
7446
|
}
|
|
5913
7447
|
const runId = state.nextRunId();
|
|
5914
|
-
const startedAt =
|
|
7448
|
+
const startedAt = nowIso4();
|
|
5915
7449
|
state.beginRun(runId, params.ui_context ?? state.lastUiContext);
|
|
5916
7450
|
const runAbortController = new AbortController();
|
|
5917
7451
|
activeRunAbort = { runId, controller: runAbortController };
|
|
@@ -5961,7 +7495,7 @@ var createRunHandlers = ({
|
|
|
5961
7495
|
type: "run.start",
|
|
5962
7496
|
run_id: runId,
|
|
5963
7497
|
session_id: sessionId,
|
|
5964
|
-
ts:
|
|
7498
|
+
ts: nowIso4(),
|
|
5965
7499
|
input: params.input,
|
|
5966
7500
|
ui_context: params.ui_context,
|
|
5967
7501
|
meta: params.meta
|
|
@@ -5972,25 +7506,58 @@ var createRunHandlers = ({
|
|
|
5972
7506
|
emitRunStatus(runId, "running");
|
|
5973
7507
|
void (async () => {
|
|
5974
7508
|
let finalResponse;
|
|
5975
|
-
let
|
|
5976
|
-
|
|
7509
|
+
let sessionSaveChain = Promise.resolve();
|
|
7510
|
+
let lastSessionSaveAt = 0;
|
|
7511
|
+
const queueSessionSave = async (reason) => {
|
|
7512
|
+
sessionSaveChain = sessionSaveChain.then(async () => {
|
|
7513
|
+
if (!sessionId) return;
|
|
7514
|
+
const messages = runtimeAgent.getHistoryMessages();
|
|
7515
|
+
const snapshotMessages = normalizeToolCallHistory(messages);
|
|
7516
|
+
if (snapshotMessages !== messages) {
|
|
7517
|
+
logRunDebug(
|
|
7518
|
+
log2,
|
|
7519
|
+
runId,
|
|
7520
|
+
`session.save normalized reason=${reason}`
|
|
7521
|
+
);
|
|
7522
|
+
}
|
|
7523
|
+
const snapshot = buildSessionState(
|
|
7524
|
+
sessionId,
|
|
7525
|
+
runId,
|
|
7526
|
+
snapshotMessages,
|
|
7527
|
+
session.invoke_seq
|
|
7528
|
+
);
|
|
7529
|
+
await sessionStateStore.save(snapshot);
|
|
7530
|
+
logRunDebug(log2, runId, `session.save ${reason}`);
|
|
7531
|
+
}).catch((error) => {
|
|
7532
|
+
log2(`Error: session-state save failed: ${String(error)}`);
|
|
7533
|
+
});
|
|
7534
|
+
await sessionSaveChain;
|
|
7535
|
+
};
|
|
7536
|
+
const maybeDebouncedSessionSave = () => {
|
|
7537
|
+
const now = Date.now();
|
|
7538
|
+
if (now - lastSessionSaveAt < SESSION_STATE_SAVE_DEBOUNCE_MS) {
|
|
7539
|
+
return;
|
|
7540
|
+
}
|
|
7541
|
+
lastSessionSaveAt = now;
|
|
7542
|
+
void queueSessionSave("debounced");
|
|
7543
|
+
};
|
|
5977
7544
|
try {
|
|
5978
7545
|
logRunDebug(
|
|
5979
7546
|
log2,
|
|
5980
7547
|
runId,
|
|
5981
|
-
`stream.start input_chars=${
|
|
7548
|
+
`stream.start input_chars=${runInputLengthForDebug(normalizedInput)}`
|
|
5982
7549
|
);
|
|
5983
|
-
for await (const event of runtimeAgent.runStream(
|
|
7550
|
+
for await (const event of runtimeAgent.runStream(normalizedInput, {
|
|
5984
7551
|
session,
|
|
5985
7552
|
signal: runAbortController.signal,
|
|
5986
7553
|
forceCompaction: params.force_compaction
|
|
5987
7554
|
})) {
|
|
5988
7555
|
if (state.cancelRequested) {
|
|
5989
|
-
cancelledWhileStreaming = true;
|
|
5990
7556
|
break;
|
|
5991
7557
|
}
|
|
5992
7558
|
if (event.type === "final") {
|
|
5993
7559
|
finalResponse = event.content;
|
|
7560
|
+
await queueSessionSave("final");
|
|
5994
7561
|
}
|
|
5995
7562
|
if (event.type === "compaction_complete") {
|
|
5996
7563
|
logCompactionSnapshot(log2, runId, runtimeAgent, event.compacted);
|
|
@@ -6014,7 +7581,7 @@ var createRunHandlers = ({
|
|
|
6014
7581
|
appendSession({
|
|
6015
7582
|
type: "agent.event",
|
|
6016
7583
|
run_id: runId,
|
|
6017
|
-
ts:
|
|
7584
|
+
ts: nowIso4(),
|
|
6018
7585
|
seq,
|
|
6019
7586
|
event
|
|
6020
7587
|
});
|
|
@@ -6025,14 +7592,16 @@ var createRunHandlers = ({
|
|
|
6025
7592
|
appendSession({
|
|
6026
7593
|
type: "run.context",
|
|
6027
7594
|
run_id: runId,
|
|
6028
|
-
ts:
|
|
7595
|
+
ts: nowIso4(),
|
|
6029
7596
|
context_left_percent: contextLeftPercent
|
|
6030
7597
|
});
|
|
6031
7598
|
}
|
|
7599
|
+
maybeDebouncedSessionSave();
|
|
6032
7600
|
}
|
|
6033
|
-
if (
|
|
7601
|
+
if (state.cancelRequested) {
|
|
6034
7602
|
normalizeRunHistoryAfterCancel(runId, runtimeAgent);
|
|
6035
7603
|
}
|
|
7604
|
+
await queueSessionSave("terminal");
|
|
6036
7605
|
const status = state.cancelRequested ? "cancelled" : "completed";
|
|
6037
7606
|
emitRunStatus(runId, status);
|
|
6038
7607
|
emitRunEnd(
|
|
@@ -6044,15 +7613,17 @@ var createRunHandlers = ({
|
|
|
6044
7613
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
6045
7614
|
if (state.cancelRequested || isAbortLikeError(err)) {
|
|
6046
7615
|
normalizeRunHistoryAfterCancel(runId, runtimeAgent);
|
|
7616
|
+
await queueSessionSave("cancelled");
|
|
6047
7617
|
emitRunStatus(runId, "cancelled", err.message || "cancelled");
|
|
6048
7618
|
emitRunEnd(runId, "cancelled", finalResponse);
|
|
6049
7619
|
return;
|
|
6050
7620
|
}
|
|
7621
|
+
await queueSessionSave("error");
|
|
6051
7622
|
emitRunStatus(runId, "error", err.message);
|
|
6052
7623
|
appendSession({
|
|
6053
7624
|
type: "run.error",
|
|
6054
7625
|
run_id: runId,
|
|
6055
|
-
ts:
|
|
7626
|
+
ts: nowIso4(),
|
|
6056
7627
|
error: {
|
|
6057
7628
|
name: err.name,
|
|
6058
7629
|
message: err.message,
|
|
@@ -6062,18 +7633,10 @@ var createRunHandlers = ({
|
|
|
6062
7633
|
emitRunEnd(runId, "error");
|
|
6063
7634
|
} finally {
|
|
6064
7635
|
logRunDebug(log2, runId, "stream.finally");
|
|
6065
|
-
if (state.
|
|
6066
|
-
|
|
6067
|
-
const snapshot = buildSessionState(
|
|
6068
|
-
sessionId,
|
|
6069
|
-
runId,
|
|
6070
|
-
messages,
|
|
6071
|
-
session.invoke_seq
|
|
6072
|
-
);
|
|
6073
|
-
void sessionStateStore.save(snapshot).catch((error) => {
|
|
6074
|
-
log2(`session-state error: ${String(error)}`);
|
|
6075
|
-
});
|
|
7636
|
+
if (state.cancelRequested) {
|
|
7637
|
+
normalizeRunHistoryAfterCancel(runId, runtimeAgent);
|
|
6076
7638
|
}
|
|
7639
|
+
await queueSessionSave("finally");
|
|
6077
7640
|
if (activeRunAbort?.runId === runId) {
|
|
6078
7641
|
activeRunAbort = null;
|
|
6079
7642
|
}
|
|
@@ -6099,19 +7662,22 @@ var createRunHandlers = ({
|
|
|
6099
7662
|
log2(`run.cancel ${params.run_id} (${params.reason ?? "no reason"})`);
|
|
6100
7663
|
return;
|
|
6101
7664
|
}
|
|
6102
|
-
sendError(id, { code:
|
|
7665
|
+
sendError(id, { code: RPC_ERROR_CODE4.RUN_NOT_FOUND, message: "run not found" });
|
|
6103
7666
|
};
|
|
6104
7667
|
return { handleRunStart, handleRunCancel };
|
|
6105
7668
|
};
|
|
6106
7669
|
|
|
6107
7670
|
// src/rpc/skills.ts
|
|
6108
|
-
import
|
|
7671
|
+
import path14 from "path";
|
|
7672
|
+
import {
|
|
7673
|
+
RPC_ERROR_CODE as RPC_ERROR_CODE5
|
|
7674
|
+
} from "@codelia/protocol";
|
|
6109
7675
|
var createSkillsHandlers = ({
|
|
6110
7676
|
state,
|
|
6111
7677
|
log: log2
|
|
6112
7678
|
}) => {
|
|
6113
7679
|
const handleSkillsList = async (id, params) => {
|
|
6114
|
-
const requestedCwd = params?.cwd ?
|
|
7680
|
+
const requestedCwd = params?.cwd ? path14.resolve(params.cwd) : void 0;
|
|
6115
7681
|
const workingDir = requestedCwd ?? state.runtimeWorkingDir ?? state.lastUiContext?.cwd ?? process.cwd();
|
|
6116
7682
|
const forceReload = params?.force_reload ?? false;
|
|
6117
7683
|
if (!forceReload) {
|
|
@@ -6135,7 +7701,7 @@ var createSkillsHandlers = ({
|
|
|
6135
7701
|
});
|
|
6136
7702
|
} catch (error) {
|
|
6137
7703
|
sendError(id, {
|
|
6138
|
-
code:
|
|
7704
|
+
code: RPC_ERROR_CODE5.RUNTIME_INTERNAL,
|
|
6139
7705
|
message: `skills resolver init failed: ${String(error)}`
|
|
6140
7706
|
});
|
|
6141
7707
|
return;
|
|
@@ -6157,7 +7723,7 @@ var createSkillsHandlers = ({
|
|
|
6157
7723
|
} catch (error) {
|
|
6158
7724
|
log2(`skills.list error: ${String(error)}`);
|
|
6159
7725
|
sendError(id, {
|
|
6160
|
-
code:
|
|
7726
|
+
code: RPC_ERROR_CODE5.RUNTIME_INTERNAL,
|
|
6161
7727
|
message: `skills list failed: ${String(error)}`
|
|
6162
7728
|
});
|
|
6163
7729
|
}
|
|
@@ -6165,6 +7731,77 @@ var createSkillsHandlers = ({
|
|
|
6165
7731
|
return { handleSkillsList };
|
|
6166
7732
|
};
|
|
6167
7733
|
|
|
7734
|
+
// src/rpc/tool.ts
|
|
7735
|
+
import {
|
|
7736
|
+
RPC_ERROR_CODE as RPC_ERROR_CODE6
|
|
7737
|
+
} from "@codelia/protocol";
|
|
7738
|
+
var normalizeToolResult = (result) => {
|
|
7739
|
+
switch (result.type) {
|
|
7740
|
+
case "json":
|
|
7741
|
+
return result.value;
|
|
7742
|
+
case "text":
|
|
7743
|
+
return result.text;
|
|
7744
|
+
case "parts":
|
|
7745
|
+
return result.parts;
|
|
7746
|
+
}
|
|
7747
|
+
};
|
|
7748
|
+
var createToolContext = () => {
|
|
7749
|
+
const deps = /* @__PURE__ */ Object.create(null);
|
|
7750
|
+
const cache = /* @__PURE__ */ new Map();
|
|
7751
|
+
const resolve = async (key) => {
|
|
7752
|
+
if (cache.has(key.id)) {
|
|
7753
|
+
return cache.get(key.id);
|
|
7754
|
+
}
|
|
7755
|
+
const value = await key.create();
|
|
7756
|
+
cache.set(key.id, value);
|
|
7757
|
+
return value;
|
|
7758
|
+
};
|
|
7759
|
+
return {
|
|
7760
|
+
deps,
|
|
7761
|
+
resolve,
|
|
7762
|
+
now: () => /* @__PURE__ */ new Date()
|
|
7763
|
+
};
|
|
7764
|
+
};
|
|
7765
|
+
var createToolHandlers = ({
|
|
7766
|
+
state,
|
|
7767
|
+
getAgent
|
|
7768
|
+
}) => {
|
|
7769
|
+
const handleToolCall = async (id, params) => {
|
|
7770
|
+
const toolName = params?.name;
|
|
7771
|
+
if (!toolName) {
|
|
7772
|
+
sendError(id, { code: RPC_ERROR_CODE6.INVALID_PARAMS, message: "tool name is required" });
|
|
7773
|
+
return;
|
|
7774
|
+
}
|
|
7775
|
+
if (!state.tools) {
|
|
7776
|
+
await getAgent();
|
|
7777
|
+
}
|
|
7778
|
+
const tool = state.tools?.find((entry) => entry.name === toolName);
|
|
7779
|
+
if (!tool) {
|
|
7780
|
+
sendError(id, { code: RPC_ERROR_CODE6.INVALID_PARAMS, message: `unknown tool: ${toolName}` });
|
|
7781
|
+
return;
|
|
7782
|
+
}
|
|
7783
|
+
try {
|
|
7784
|
+
const result = await tool.executeRaw(
|
|
7785
|
+
JSON.stringify(params?.arguments ?? {}),
|
|
7786
|
+
createToolContext()
|
|
7787
|
+
);
|
|
7788
|
+
const response = {
|
|
7789
|
+
ok: true,
|
|
7790
|
+
result: normalizeToolResult(result)
|
|
7791
|
+
};
|
|
7792
|
+
sendResult(id, response);
|
|
7793
|
+
} catch (error) {
|
|
7794
|
+
sendError(id, {
|
|
7795
|
+
code: RPC_ERROR_CODE6.RUNTIME_INTERNAL,
|
|
7796
|
+
message: `tool call failed: ${String(error)}`
|
|
7797
|
+
});
|
|
7798
|
+
}
|
|
7799
|
+
};
|
|
7800
|
+
return {
|
|
7801
|
+
handleToolCall
|
|
7802
|
+
};
|
|
7803
|
+
};
|
|
7804
|
+
|
|
6168
7805
|
// src/rpc/handlers.ts
|
|
6169
7806
|
var createRuntimeHandlers = ({
|
|
6170
7807
|
state,
|
|
@@ -6172,7 +7809,8 @@ var createRuntimeHandlers = ({
|
|
|
6172
7809
|
log: log2,
|
|
6173
7810
|
mcpManager: injectedMcpManager,
|
|
6174
7811
|
sessionStateStore: injectedSessionStateStore,
|
|
6175
|
-
runEventStoreFactory: injectedRunEventStoreFactory
|
|
7812
|
+
runEventStoreFactory: injectedRunEventStoreFactory,
|
|
7813
|
+
buildProviderModelList: injectedBuildProviderModelList
|
|
6176
7814
|
}) => {
|
|
6177
7815
|
const sessionStateStore = injectedSessionStateStore ?? new SessionStateStoreImpl({
|
|
6178
7816
|
onError: (error, context) => {
|
|
@@ -6186,17 +7824,117 @@ var createRuntimeHandlers = ({
|
|
|
6186
7824
|
start: async () => void 0,
|
|
6187
7825
|
list: () => ({ servers: [] })
|
|
6188
7826
|
};
|
|
7827
|
+
const buildProviderModelList2 = injectedBuildProviderModelList ?? buildProviderModelList;
|
|
7828
|
+
let startupOnboardingPromise = null;
|
|
7829
|
+
let startupOnboardingStarted = false;
|
|
6189
7830
|
const appendSession = (record) => {
|
|
6190
7831
|
if (!state.sessionAppend) return;
|
|
6191
7832
|
state.sessionAppend(record);
|
|
6192
7833
|
};
|
|
7834
|
+
const formatUsdPer1M = (value) => {
|
|
7835
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
7836
|
+
return null;
|
|
7837
|
+
}
|
|
7838
|
+
const fixed = value.toFixed(4);
|
|
7839
|
+
return fixed.replace(/\.?0+$/, "");
|
|
7840
|
+
};
|
|
7841
|
+
const buildModelPickDetail = (details) => {
|
|
7842
|
+
const parts = [];
|
|
7843
|
+
if (details?.release_date) {
|
|
7844
|
+
parts.push(`released ${details.release_date}`);
|
|
7845
|
+
}
|
|
7846
|
+
const inputCost = formatUsdPer1M(details?.cost_per_1m_input_tokens_usd);
|
|
7847
|
+
const outputCost = formatUsdPer1M(details?.cost_per_1m_output_tokens_usd);
|
|
7848
|
+
if (inputCost !== null || outputCost !== null) {
|
|
7849
|
+
parts.push(
|
|
7850
|
+
`cost in/out ${inputCost ?? "-"}/${outputCost ?? "-"} USD per 1M`
|
|
7851
|
+
);
|
|
7852
|
+
}
|
|
7853
|
+
return parts.join(" \u2022 ");
|
|
7854
|
+
};
|
|
7855
|
+
const runStartupOnboarding = async () => {
|
|
7856
|
+
const supportsPick = !!state.uiCapabilities?.supports_pick;
|
|
7857
|
+
const supportsPrompt = !!state.uiCapabilities?.supports_prompt;
|
|
7858
|
+
if (!supportsPick || !supportsPrompt) {
|
|
7859
|
+
return;
|
|
7860
|
+
}
|
|
7861
|
+
const authResolver = await AuthResolver.create(state, log2);
|
|
7862
|
+
if (authResolver.hasAnyAvailableAuth()) {
|
|
7863
|
+
return;
|
|
7864
|
+
}
|
|
7865
|
+
const providerPick = await requestUiPick(state, {
|
|
7866
|
+
title: "Let's set up your provider to get started.",
|
|
7867
|
+
items: SUPPORTED_PROVIDERS.map((provider2) => ({
|
|
7868
|
+
id: provider2,
|
|
7869
|
+
label: provider2,
|
|
7870
|
+
detail: provider2 === "openai" ? "OAuth (ChatGPT Plus/Pro) or API key" : provider2 === "openrouter" ? "API key (OpenRouter)" : "API key"
|
|
7871
|
+
})),
|
|
7872
|
+
multi: false
|
|
7873
|
+
});
|
|
7874
|
+
const pickedProvider = providerPick?.ids?.[0];
|
|
7875
|
+
if (!pickedProvider || !SUPPORTED_PROVIDERS.includes(pickedProvider)) {
|
|
7876
|
+
log2("startup onboarding skipped (provider not selected)");
|
|
7877
|
+
return;
|
|
7878
|
+
}
|
|
7879
|
+
const provider = pickedProvider;
|
|
7880
|
+
try {
|
|
7881
|
+
await authResolver.resolveProviderAuth(provider);
|
|
7882
|
+
} catch (error) {
|
|
7883
|
+
log2(`startup onboarding auth failed: ${String(error)}`);
|
|
7884
|
+
return;
|
|
7885
|
+
}
|
|
7886
|
+
const { models, details } = await buildProviderModelList2({
|
|
7887
|
+
provider,
|
|
7888
|
+
includeDetails: true,
|
|
7889
|
+
state,
|
|
7890
|
+
log: log2
|
|
7891
|
+
});
|
|
7892
|
+
if (!models.length) {
|
|
7893
|
+
log2(`startup onboarding model list returned no models for ${provider}`);
|
|
7894
|
+
return;
|
|
7895
|
+
}
|
|
7896
|
+
const modelPick = await requestUiPick(state, {
|
|
7897
|
+
title: `Select model (${provider})`,
|
|
7898
|
+
items: models.map((model) => ({
|
|
7899
|
+
id: model,
|
|
7900
|
+
label: model,
|
|
7901
|
+
detail: buildModelPickDetail(details?.[model]) || void 0
|
|
7902
|
+
})),
|
|
7903
|
+
multi: false
|
|
7904
|
+
});
|
|
7905
|
+
const selectedModel = modelPick?.ids?.[0];
|
|
7906
|
+
if (!selectedModel || !models.includes(selectedModel)) {
|
|
7907
|
+
log2("startup onboarding skipped (model not selected)");
|
|
7908
|
+
return;
|
|
7909
|
+
}
|
|
7910
|
+
const configPath = resolveConfigPath();
|
|
7911
|
+
await updateModelConfig2(configPath, { provider, name: selectedModel });
|
|
7912
|
+
state.agent = null;
|
|
7913
|
+
log2(`startup onboarding completed: ${provider}/${selectedModel}`);
|
|
7914
|
+
};
|
|
7915
|
+
const launchStartupOnboarding = () => {
|
|
7916
|
+
if (startupOnboardingStarted) {
|
|
7917
|
+
return;
|
|
7918
|
+
}
|
|
7919
|
+
startupOnboardingStarted = true;
|
|
7920
|
+
startupOnboardingPromise = runStartupOnboarding().catch((error) => {
|
|
7921
|
+
log2(`startup onboarding failed: ${String(error)}`);
|
|
7922
|
+
}).finally(() => {
|
|
7923
|
+
startupOnboardingPromise = null;
|
|
7924
|
+
});
|
|
7925
|
+
};
|
|
7926
|
+
const waitForStartupOnboarding = async () => {
|
|
7927
|
+
if (!startupOnboardingPromise) return;
|
|
7928
|
+
await startupOnboardingPromise;
|
|
7929
|
+
};
|
|
6193
7930
|
const { handleRunStart, handleRunCancel } = createRunHandlers({
|
|
6194
7931
|
state,
|
|
6195
7932
|
getAgent,
|
|
6196
7933
|
log: log2,
|
|
6197
7934
|
runEventStoreFactory,
|
|
6198
7935
|
sessionStateStore,
|
|
6199
|
-
appendSession
|
|
7936
|
+
appendSession,
|
|
7937
|
+
beforeRunStart: waitForStartupOnboarding
|
|
6200
7938
|
});
|
|
6201
7939
|
const { handleSessionList, handleSessionHistory } = createHistoryHandlers({
|
|
6202
7940
|
sessionStateStore,
|
|
@@ -6214,6 +7952,10 @@ var createRuntimeHandlers = ({
|
|
|
6214
7952
|
state,
|
|
6215
7953
|
log: log2
|
|
6216
7954
|
});
|
|
7955
|
+
const { handleToolCall } = createToolHandlers({
|
|
7956
|
+
state,
|
|
7957
|
+
getAgent
|
|
7958
|
+
});
|
|
6217
7959
|
const handleInitialize = (id, params) => {
|
|
6218
7960
|
const result = {
|
|
6219
7961
|
protocol_version: PROTOCOL_VERSION,
|
|
@@ -6223,13 +7965,16 @@ var createRuntimeHandlers = ({
|
|
|
6223
7965
|
supports_ui_requests: true,
|
|
6224
7966
|
supports_mcp_list: true,
|
|
6225
7967
|
supports_skills_list: true,
|
|
6226
|
-
supports_context_inspect: true
|
|
7968
|
+
supports_context_inspect: true,
|
|
7969
|
+
supports_tool_call: true,
|
|
7970
|
+
supports_permission_preflight_events: true
|
|
6227
7971
|
}
|
|
6228
7972
|
};
|
|
6229
7973
|
sendResult(id, result);
|
|
6230
7974
|
log2(`initialize from ${params.client?.name ?? "unknown"}`);
|
|
6231
7975
|
state.lastClientInfo = params.client ?? null;
|
|
6232
7976
|
state.setUiCapabilities(params.ui_capabilities);
|
|
7977
|
+
launchStartupOnboarding();
|
|
6233
7978
|
};
|
|
6234
7979
|
const handleUiContextUpdate = (params) => {
|
|
6235
7980
|
state.updateUiContext(params);
|
|
@@ -6239,7 +7984,7 @@ var createRuntimeHandlers = ({
|
|
|
6239
7984
|
};
|
|
6240
7985
|
const handleAuthLogout = async (id, params) => {
|
|
6241
7986
|
if (state.activeRunId) {
|
|
6242
|
-
sendError(id, { code:
|
|
7987
|
+
sendError(id, { code: RPC_ERROR_CODE7.RUNTIME_BUSY, message: "runtime busy" });
|
|
6243
7988
|
return;
|
|
6244
7989
|
}
|
|
6245
7990
|
const clearSession = params?.clear_session ?? true;
|
|
@@ -6247,7 +7992,7 @@ var createRuntimeHandlers = ({
|
|
|
6247
7992
|
const supportsConfirm = !!state.uiCapabilities?.supports_confirm;
|
|
6248
7993
|
if (!supportsConfirm) {
|
|
6249
7994
|
sendError(id, {
|
|
6250
|
-
code:
|
|
7995
|
+
code: RPC_ERROR_CODE7.RUNTIME_INTERNAL,
|
|
6251
7996
|
message: "UI confirmation is required for logout"
|
|
6252
7997
|
});
|
|
6253
7998
|
return;
|
|
@@ -6289,7 +8034,7 @@ var createRuntimeHandlers = ({
|
|
|
6289
8034
|
log2(`auth.logout session_cleared=${clearSession}`);
|
|
6290
8035
|
} catch (error) {
|
|
6291
8036
|
sendError(id, {
|
|
6292
|
-
code:
|
|
8037
|
+
code: RPC_ERROR_CODE7.RUNTIME_INTERNAL,
|
|
6293
8038
|
message: `auth logout failed: ${String(error)}`
|
|
6294
8039
|
});
|
|
6295
8040
|
}
|
|
@@ -6312,6 +8057,8 @@ var createRuntimeHandlers = ({
|
|
|
6312
8057
|
return handleModelList(req.id, req.params);
|
|
6313
8058
|
case "model.set":
|
|
6314
8059
|
return handleModelSet(req.id, req.params);
|
|
8060
|
+
case "tool.call":
|
|
8061
|
+
return handleToolCall(req.id, req.params);
|
|
6315
8062
|
case "mcp.list":
|
|
6316
8063
|
await mcpManager.start?.();
|
|
6317
8064
|
return sendResult(
|
|
@@ -6323,7 +8070,7 @@ var createRuntimeHandlers = ({
|
|
|
6323
8070
|
case "context.inspect":
|
|
6324
8071
|
return handleContextInspect(req.id, req.params);
|
|
6325
8072
|
default:
|
|
6326
|
-
return sendError(req.id, { code:
|
|
8073
|
+
return sendError(req.id, { code: RPC_ERROR_CODE7.METHOD_NOT_FOUND, message: "method not found" });
|
|
6327
8074
|
}
|
|
6328
8075
|
};
|
|
6329
8076
|
const handleNotification = (note) => {
|
|
@@ -6350,7 +8097,7 @@ var createRuntimeHandlers = ({
|
|
|
6350
8097
|
};
|
|
6351
8098
|
|
|
6352
8099
|
// src/runtime-state.ts
|
|
6353
|
-
import
|
|
8100
|
+
import crypto6 from "crypto";
|
|
6354
8101
|
var RuntimeState = class {
|
|
6355
8102
|
runSeq = /* @__PURE__ */ new Map();
|
|
6356
8103
|
uiRequestCounter = 0;
|
|
@@ -6363,6 +8110,7 @@ var RuntimeState = class {
|
|
|
6363
8110
|
uiCapabilities = null;
|
|
6364
8111
|
systemPrompt = null;
|
|
6365
8112
|
toolDefinitions = null;
|
|
8113
|
+
tools = null;
|
|
6366
8114
|
sessionId = null;
|
|
6367
8115
|
sessionAppend = null;
|
|
6368
8116
|
agent = null;
|
|
@@ -6373,10 +8121,10 @@ var RuntimeState = class {
|
|
|
6373
8121
|
runtimeWorkingDir = null;
|
|
6374
8122
|
runtimeSandboxRoot = null;
|
|
6375
8123
|
nextRunId() {
|
|
6376
|
-
return
|
|
8124
|
+
return crypto6.randomUUID();
|
|
6377
8125
|
}
|
|
6378
8126
|
nextSessionId() {
|
|
6379
|
-
return
|
|
8127
|
+
return crypto6.randomUUID();
|
|
6380
8128
|
}
|
|
6381
8129
|
beginRun(runId, uiContext) {
|
|
6382
8130
|
this.activeRunId = runId;
|
|
@@ -6466,42 +8214,59 @@ var RuntimeState = class {
|
|
|
6466
8214
|
|
|
6467
8215
|
// src/runtime.ts
|
|
6468
8216
|
var startRuntime = () => {
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
|
|
6472
|
-
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
const { processMessage } = createRuntimeHandlers({
|
|
6480
|
-
state,
|
|
6481
|
-
getAgent,
|
|
6482
|
-
log,
|
|
6483
|
-
mcpManager
|
|
6484
|
-
});
|
|
6485
|
-
log("runtime started");
|
|
6486
|
-
process.stdin.setEncoding("utf8");
|
|
6487
|
-
let buffer = "";
|
|
6488
|
-
process.stdin.on("data", (chunk) => {
|
|
6489
|
-
buffer += chunk;
|
|
6490
|
-
let index = buffer.indexOf("\n");
|
|
6491
|
-
while (index >= 0) {
|
|
6492
|
-
const line = buffer.slice(0, index).trim();
|
|
6493
|
-
buffer = buffer.slice(index + 1);
|
|
6494
|
-
if (line) {
|
|
6495
|
-
try {
|
|
6496
|
-
const msg = JSON.parse(line);
|
|
6497
|
-
processMessage(msg);
|
|
6498
|
-
} catch (error) {
|
|
6499
|
-
log(`invalid json: ${String(error)}`);
|
|
6500
|
-
}
|
|
8217
|
+
void (async () => {
|
|
8218
|
+
const state = new RuntimeState();
|
|
8219
|
+
const workingDir = process.env.CODELIA_SANDBOX_ROOT ? path15.resolve(process.env.CODELIA_SANDBOX_ROOT) : process.cwd();
|
|
8220
|
+
state.runtimeWorkingDir = workingDir;
|
|
8221
|
+
state.runtimeSandboxRoot = workingDir;
|
|
8222
|
+
const sessionStateStore = new SessionStateStoreImpl2({
|
|
8223
|
+
onError: (error, context) => {
|
|
8224
|
+
log(
|
|
8225
|
+
`Error: session-state ${context.action} error${context.detail ? ` (${context.detail})` : ""}: ${String(error)}`
|
|
8226
|
+
);
|
|
6501
8227
|
}
|
|
6502
|
-
|
|
8228
|
+
});
|
|
8229
|
+
try {
|
|
8230
|
+
await sessionStateStore.list();
|
|
8231
|
+
} catch (error) {
|
|
8232
|
+
log(`Error: session index database is not available: ${String(error)}`);
|
|
8233
|
+
process.exit(1);
|
|
8234
|
+
return;
|
|
6503
8235
|
}
|
|
6504
|
-
|
|
8236
|
+
const mcpManager = new McpManager({ workingDir, log });
|
|
8237
|
+
void mcpManager.start({
|
|
8238
|
+
onStatus: (message) => log(`mcp: ${message}`),
|
|
8239
|
+
requestOAuthTokens: ({ server_id, oauth: oauth2, error }) => requestMcpOAuthTokensWithRunStatus(state, server_id, oauth2, error)
|
|
8240
|
+
});
|
|
8241
|
+
const getAgent = createAgentFactory(state, { mcpManager });
|
|
8242
|
+
const { processMessage } = createRuntimeHandlers({
|
|
8243
|
+
state,
|
|
8244
|
+
getAgent,
|
|
8245
|
+
log,
|
|
8246
|
+
mcpManager,
|
|
8247
|
+
sessionStateStore
|
|
8248
|
+
});
|
|
8249
|
+
log("runtime started");
|
|
8250
|
+
process.stdin.setEncoding("utf8");
|
|
8251
|
+
let buffer = "";
|
|
8252
|
+
process.stdin.on("data", (chunk) => {
|
|
8253
|
+
buffer += chunk;
|
|
8254
|
+
let index = buffer.indexOf("\n");
|
|
8255
|
+
while (index >= 0) {
|
|
8256
|
+
const line = buffer.slice(0, index).trim();
|
|
8257
|
+
buffer = buffer.slice(index + 1);
|
|
8258
|
+
if (line) {
|
|
8259
|
+
try {
|
|
8260
|
+
const msg = JSON.parse(line);
|
|
8261
|
+
processMessage(msg);
|
|
8262
|
+
} catch (error) {
|
|
8263
|
+
log(`invalid json: ${String(error)}`);
|
|
8264
|
+
}
|
|
8265
|
+
}
|
|
8266
|
+
index = buffer.indexOf("\n");
|
|
8267
|
+
}
|
|
8268
|
+
});
|
|
8269
|
+
})();
|
|
6505
8270
|
};
|
|
6506
8271
|
|
|
6507
8272
|
// src/index.ts
|