@codelia/runtime 0.1.2 → 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 +12 -9
package/dist/index.cjs
CHANGED
|
@@ -23,11 +23,13 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
23
|
));
|
|
24
24
|
|
|
25
25
|
// src/runtime.ts
|
|
26
|
-
var
|
|
26
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
27
|
+
var import_storage9 = require("@codelia/storage");
|
|
27
28
|
|
|
28
29
|
// src/agent-factory.ts
|
|
29
|
-
var
|
|
30
|
-
var
|
|
30
|
+
var import_node_fs14 = require("fs");
|
|
31
|
+
var import_core17 = require("@codelia/core");
|
|
32
|
+
var import_storage5 = require("@codelia/storage");
|
|
31
33
|
|
|
32
34
|
// src/agents/context.ts
|
|
33
35
|
var createAgentsResolverKey = (resolver) => ({
|
|
@@ -856,10 +858,13 @@ var describeRpcMessage = (msg) => {
|
|
|
856
858
|
}
|
|
857
859
|
return "unknown";
|
|
858
860
|
};
|
|
861
|
+
var serializeMessage = (msg) => ({
|
|
862
|
+
payload: `${JSON.stringify(msg)}
|
|
863
|
+
`,
|
|
864
|
+
label: describeRpcMessage(msg)
|
|
865
|
+
});
|
|
859
866
|
var send = (msg) => {
|
|
860
|
-
const payload =
|
|
861
|
-
`;
|
|
862
|
-
const label = describeRpcMessage(msg);
|
|
867
|
+
const { payload, label } = serializeMessage(msg);
|
|
863
868
|
const writable = process.stdout.write(payload, (error) => {
|
|
864
869
|
if (error) {
|
|
865
870
|
(0, import_logger.debugLog)(`transport.write.error label=${label} message=${error.message}`);
|
|
@@ -869,6 +874,20 @@ var send = (msg) => {
|
|
|
869
874
|
(0, import_logger.debugLog)(`transport.backpressure label=${label} bytes=${payload.length}`);
|
|
870
875
|
}
|
|
871
876
|
};
|
|
877
|
+
var sendAsync = async (msg) => new Promise((resolve) => {
|
|
878
|
+
const { payload, label } = serializeMessage(msg);
|
|
879
|
+
const writable = process.stdout.write(payload, (error) => {
|
|
880
|
+
if (error) {
|
|
881
|
+
(0, import_logger.debugLog)(
|
|
882
|
+
`transport.write.error label=${label} message=${error.message}`
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
resolve();
|
|
886
|
+
});
|
|
887
|
+
if (!writable) {
|
|
888
|
+
(0, import_logger.debugLog)(`transport.backpressure label=${label} bytes=${payload.length}`);
|
|
889
|
+
}
|
|
890
|
+
});
|
|
872
891
|
var sendError = (id, error) => {
|
|
873
892
|
const response = { jsonrpc: "2.0", id, error };
|
|
874
893
|
send(response);
|
|
@@ -892,6 +911,21 @@ var sendAgentEvent = (state, runId, event) => {
|
|
|
892
911
|
send(notify);
|
|
893
912
|
return seq;
|
|
894
913
|
};
|
|
914
|
+
var sendAgentEventAsync = async (state, runId, event) => {
|
|
915
|
+
if (state.shouldSuppressEvent(runId)) return null;
|
|
916
|
+
const seq = state.nextSequence(runId);
|
|
917
|
+
const notify = {
|
|
918
|
+
jsonrpc: "2.0",
|
|
919
|
+
method: "agent.event",
|
|
920
|
+
params: {
|
|
921
|
+
run_id: runId,
|
|
922
|
+
seq,
|
|
923
|
+
event
|
|
924
|
+
}
|
|
925
|
+
};
|
|
926
|
+
await sendAsync(notify);
|
|
927
|
+
return seq;
|
|
928
|
+
};
|
|
895
929
|
var sendRunStatus = (runId, status, message) => {
|
|
896
930
|
const notify = {
|
|
897
931
|
jsonrpc: "2.0",
|
|
@@ -904,6 +938,18 @@ var sendRunStatus = (runId, status, message) => {
|
|
|
904
938
|
};
|
|
905
939
|
send(notify);
|
|
906
940
|
};
|
|
941
|
+
var sendRunStatusAsync = async (runId, status, message) => {
|
|
942
|
+
const notify = {
|
|
943
|
+
jsonrpc: "2.0",
|
|
944
|
+
method: "run.status",
|
|
945
|
+
params: {
|
|
946
|
+
run_id: runId,
|
|
947
|
+
status,
|
|
948
|
+
message
|
|
949
|
+
}
|
|
950
|
+
};
|
|
951
|
+
await sendAsync(notify);
|
|
952
|
+
};
|
|
907
953
|
var sendRunContext = (runId, contextLeftPercent) => {
|
|
908
954
|
const notify = {
|
|
909
955
|
jsonrpc: "2.0",
|
|
@@ -982,10 +1028,15 @@ var AuthStore = class {
|
|
|
982
1028
|
};
|
|
983
1029
|
|
|
984
1030
|
// src/auth/resolver.ts
|
|
985
|
-
var SUPPORTED_PROVIDERS = [
|
|
1031
|
+
var SUPPORTED_PROVIDERS = [
|
|
1032
|
+
"openai",
|
|
1033
|
+
"anthropic",
|
|
1034
|
+
"openrouter"
|
|
1035
|
+
];
|
|
986
1036
|
var API_KEY_ENV = {
|
|
987
1037
|
openai: "OPENAI_API_KEY",
|
|
988
|
-
anthropic: "ANTHROPIC_API_KEY"
|
|
1038
|
+
anthropic: "ANTHROPIC_API_KEY",
|
|
1039
|
+
openrouter: "OPENROUTER_API_KEY"
|
|
989
1040
|
};
|
|
990
1041
|
var AuthResolver = class _AuthResolver {
|
|
991
1042
|
auth;
|
|
@@ -1003,6 +1054,12 @@ var AuthResolver = class _AuthResolver {
|
|
|
1003
1054
|
const auth = await store.load();
|
|
1004
1055
|
return new _AuthResolver(state, log2, store, auth);
|
|
1005
1056
|
}
|
|
1057
|
+
hasAnyAvailableAuth() {
|
|
1058
|
+
return SUPPORTED_PROVIDERS.some((provider) => {
|
|
1059
|
+
if (this.auth.providers[provider]) return true;
|
|
1060
|
+
return Boolean(readEnvValue(API_KEY_ENV[provider]));
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1006
1063
|
async resolveProvider(preferred) {
|
|
1007
1064
|
const preferredProvider = preferred && SUPPORTED_PROVIDERS.includes(preferred) ? preferred : null;
|
|
1008
1065
|
if (preferredProvider) return preferredProvider;
|
|
@@ -1021,10 +1078,11 @@ var AuthResolver = class _AuthResolver {
|
|
|
1021
1078
|
return "openai";
|
|
1022
1079
|
}
|
|
1023
1080
|
const result = await requestUiPick(this.state, {
|
|
1024
|
-
title: "
|
|
1081
|
+
title: "Welcome! Choose your provider to get started.",
|
|
1025
1082
|
items: SUPPORTED_PROVIDERS.map((provider) => ({
|
|
1026
1083
|
id: provider,
|
|
1027
|
-
label: provider
|
|
1084
|
+
label: provider,
|
|
1085
|
+
detail: provider === "openai" ? "OAuth (ChatGPT Plus/Pro) or API key" : provider === "openrouter" ? "API key (OpenRouter)" : "API key"
|
|
1028
1086
|
})),
|
|
1029
1087
|
multi: false
|
|
1030
1088
|
});
|
|
@@ -1047,7 +1105,17 @@ var AuthResolver = class _AuthResolver {
|
|
|
1047
1105
|
if (provider === "openai") {
|
|
1048
1106
|
return this.promptOpenAiAuth();
|
|
1049
1107
|
}
|
|
1050
|
-
return this.promptApiKey(provider,
|
|
1108
|
+
return this.promptApiKey(provider, this.getApiKeyPromptLabel(provider));
|
|
1109
|
+
}
|
|
1110
|
+
getApiKeyPromptLabel(provider) {
|
|
1111
|
+
switch (provider) {
|
|
1112
|
+
case "anthropic":
|
|
1113
|
+
return "Anthropic API key";
|
|
1114
|
+
case "openrouter":
|
|
1115
|
+
return "OpenRouter API key";
|
|
1116
|
+
default:
|
|
1117
|
+
return "API key";
|
|
1118
|
+
}
|
|
1051
1119
|
}
|
|
1052
1120
|
async getOpenAiAccessToken() {
|
|
1053
1121
|
const entry = this.auth.providers.openai;
|
|
@@ -1076,10 +1144,18 @@ var AuthResolver = class _AuthResolver {
|
|
|
1076
1144
|
throw new Error("UI does not support auth prompts");
|
|
1077
1145
|
}
|
|
1078
1146
|
const pick = await requestUiPick(this.state, {
|
|
1079
|
-
title: "
|
|
1147
|
+
title: "How would you like to connect OpenAI?",
|
|
1080
1148
|
items: [
|
|
1081
|
-
{
|
|
1082
|
-
|
|
1149
|
+
{
|
|
1150
|
+
id: "oauth",
|
|
1151
|
+
label: "ChatGPT Plus/Pro (OAuth)",
|
|
1152
|
+
detail: "Recommended if you use ChatGPT subscription access"
|
|
1153
|
+
},
|
|
1154
|
+
{
|
|
1155
|
+
id: "api_key",
|
|
1156
|
+
label: "OpenAI API key",
|
|
1157
|
+
detail: "Use a standard OpenAI API key from platform settings"
|
|
1158
|
+
}
|
|
1083
1159
|
],
|
|
1084
1160
|
multi: false
|
|
1085
1161
|
});
|
|
@@ -1103,7 +1179,7 @@ var AuthResolver = class _AuthResolver {
|
|
|
1103
1179
|
const session = await createOAuthSession();
|
|
1104
1180
|
const confirm = await requestUiConfirm(this.state, {
|
|
1105
1181
|
title: "OpenAI OAuth",
|
|
1106
|
-
message: `Open your browser to
|
|
1182
|
+
message: `You're almost done. Open your browser to continue sign in.
|
|
1107
1183
|
|
|
1108
1184
|
${session.authUrl}`,
|
|
1109
1185
|
confirm_label: "Open browser",
|
|
@@ -1142,7 +1218,7 @@ ${session.authUrl}`,
|
|
|
1142
1218
|
}
|
|
1143
1219
|
const prompt = await requestUiPrompt(this.state, {
|
|
1144
1220
|
title: label,
|
|
1145
|
-
message: "
|
|
1221
|
+
message: "Paste your API key to continue. You can change it later with /logout.",
|
|
1146
1222
|
secret: true
|
|
1147
1223
|
});
|
|
1148
1224
|
const value = prompt?.value?.trim() ?? "";
|
|
@@ -1343,7 +1419,8 @@ var createMcpOAuthSession = async (params) => {
|
|
|
1343
1419
|
var import_core2 = require("@codelia/core");
|
|
1344
1420
|
var import_model_metadata = require("@codelia/model-metadata");
|
|
1345
1421
|
var import_storage3 = require("@codelia/storage");
|
|
1346
|
-
var buildModelRegistry = async (llm) => {
|
|
1422
|
+
var buildModelRegistry = async (llm, options = {}) => {
|
|
1423
|
+
const strict = options.strict ?? true;
|
|
1347
1424
|
const metadataService = new import_model_metadata.ModelMetadataServiceImpl({
|
|
1348
1425
|
storagePathService: new import_storage3.StoragePathServiceImpl()
|
|
1349
1426
|
});
|
|
@@ -1358,9 +1435,11 @@ var buildModelRegistry = async (llm) => {
|
|
|
1358
1435
|
fullIdEntry = providerEntries?.[`${llm.provider}/${llm.model}`];
|
|
1359
1436
|
}
|
|
1360
1437
|
if (!directEntry && !fullIdEntry) {
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1438
|
+
if (strict) {
|
|
1439
|
+
throw new Error(
|
|
1440
|
+
`Model metadata not found for ${llm.provider}/${llm.model} after refresh`
|
|
1441
|
+
);
|
|
1442
|
+
}
|
|
1364
1443
|
}
|
|
1365
1444
|
return (0, import_core2.applyModelMetadata)(import_core2.DEFAULT_MODEL_REGISTRY, { models: entries });
|
|
1366
1445
|
};
|
|
@@ -1717,6 +1796,8 @@ var SYSTEM_TOOL_ALLOWLIST = [
|
|
|
1717
1796
|
"agents_resolve",
|
|
1718
1797
|
"skill_search",
|
|
1719
1798
|
"skill_load",
|
|
1799
|
+
"lane_list",
|
|
1800
|
+
"lane_status",
|
|
1720
1801
|
"done"
|
|
1721
1802
|
];
|
|
1722
1803
|
var SYSTEM_BASH_ALLOWLIST = [
|
|
@@ -1808,7 +1889,27 @@ var PermissionService = class {
|
|
|
1808
1889
|
message: `${skillName ? skillName : explicitPath || `skill_load ${rawArgs}`}${rememberPreview}`
|
|
1809
1890
|
};
|
|
1810
1891
|
}
|
|
1811
|
-
|
|
1892
|
+
if (toolName === "write") {
|
|
1893
|
+
const parsed = parseRawArgsForPrompt(rawArgs);
|
|
1894
|
+
const filePath = parsed && typeof parsed.file_path === "string" ? parsed.file_path.trim() : "";
|
|
1895
|
+
const content = parsed && typeof parsed.content === "string" ? parsed.content : "";
|
|
1896
|
+
const target = filePath || "(unknown path)";
|
|
1897
|
+
return {
|
|
1898
|
+
title: "Run tool?",
|
|
1899
|
+
message: `write ${target} (${content.length} bytes)${rememberPreview}`
|
|
1900
|
+
};
|
|
1901
|
+
}
|
|
1902
|
+
if (toolName === "edit") {
|
|
1903
|
+
const parsed = parseRawArgsForPrompt(rawArgs);
|
|
1904
|
+
const filePath = parsed && typeof parsed.file_path === "string" ? parsed.file_path.trim() : "";
|
|
1905
|
+
const target = filePath || "(unknown path)";
|
|
1906
|
+
const mode = parsed && typeof parsed.match_mode === "string" ? parsed.match_mode : "auto";
|
|
1907
|
+
return {
|
|
1908
|
+
title: "Run tool?",
|
|
1909
|
+
message: `edit ${target} (match=${mode})${rememberPreview}`
|
|
1910
|
+
};
|
|
1911
|
+
}
|
|
1912
|
+
return { title: "Run tool?", message: toolName };
|
|
1812
1913
|
}
|
|
1813
1914
|
evaluate(toolName, rawArgs) {
|
|
1814
1915
|
if (toolName === "bash") {
|
|
@@ -3304,94 +3405,820 @@ var createGrepTool = (sandboxKey) => (0, import_core8.defineTool)({
|
|
|
3304
3405
|
}
|
|
3305
3406
|
});
|
|
3306
3407
|
|
|
3307
|
-
// src/tools/
|
|
3308
|
-
var import_node_fs10 = require("fs");
|
|
3408
|
+
// src/tools/lane.ts
|
|
3309
3409
|
var import_core9 = require("@codelia/core");
|
|
3310
3410
|
var import_zod8 = require("zod");
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
var
|
|
3314
|
-
var
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3411
|
+
|
|
3412
|
+
// src/lanes/manager.ts
|
|
3413
|
+
var import_node_crypto4 = __toESM(require("crypto"), 1);
|
|
3414
|
+
var import_node_fs11 = require("fs");
|
|
3415
|
+
var import_node_os2 = __toESM(require("os"), 1);
|
|
3416
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
3417
|
+
|
|
3418
|
+
// src/lanes/command.ts
|
|
3419
|
+
var import_node_child_process3 = require("child_process");
|
|
3420
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
3421
|
+
var MAX_CAPTURE_BYTES = 512 * 1024;
|
|
3422
|
+
var truncate2 = (value) => {
|
|
3423
|
+
if (value.length <= 8e3) return value;
|
|
3424
|
+
return `${value.slice(0, 8e3)}...[truncated]`;
|
|
3425
|
+
};
|
|
3426
|
+
var runCommand = async (command, args, options = {}) => {
|
|
3427
|
+
const timeoutMs = Math.max(1e3, options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
3428
|
+
return new Promise((resolve, reject) => {
|
|
3429
|
+
const child = (0, import_node_child_process3.spawn)(command, args, {
|
|
3430
|
+
cwd: options.cwd,
|
|
3431
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
3432
|
+
});
|
|
3433
|
+
let stdout = "";
|
|
3434
|
+
let stderr = "";
|
|
3435
|
+
let bytes = 0;
|
|
3436
|
+
let done = false;
|
|
3437
|
+
const finish = (fn) => {
|
|
3438
|
+
if (done) return;
|
|
3439
|
+
done = true;
|
|
3440
|
+
clearTimeout(timer);
|
|
3441
|
+
fn();
|
|
3442
|
+
};
|
|
3443
|
+
const consume = (chunk, kind) => {
|
|
3444
|
+
const text = chunk.toString("utf8");
|
|
3445
|
+
bytes += Buffer.byteLength(text, "utf8");
|
|
3446
|
+
if (bytes > MAX_CAPTURE_BYTES) {
|
|
3447
|
+
finish(
|
|
3448
|
+
() => reject(
|
|
3449
|
+
new Error(
|
|
3450
|
+
`${command} output exceeded ${MAX_CAPTURE_BYTES} bytes while running ${args.join(" ")}`
|
|
3451
|
+
)
|
|
3452
|
+
)
|
|
3453
|
+
);
|
|
3454
|
+
try {
|
|
3455
|
+
child.kill("SIGTERM");
|
|
3456
|
+
} catch {
|
|
3457
|
+
}
|
|
3458
|
+
return;
|
|
3459
|
+
}
|
|
3460
|
+
if (kind === "stdout") {
|
|
3461
|
+
stdout += text;
|
|
3462
|
+
} else {
|
|
3463
|
+
stderr += text;
|
|
3464
|
+
}
|
|
3465
|
+
};
|
|
3466
|
+
child.stdout?.on("data", (chunk) => consume(chunk, "stdout"));
|
|
3467
|
+
child.stderr?.on("data", (chunk) => consume(chunk, "stderr"));
|
|
3468
|
+
child.on("error", (error) => {
|
|
3469
|
+
finish(() => reject(error));
|
|
3470
|
+
});
|
|
3471
|
+
child.on("close", (code) => {
|
|
3472
|
+
if (code === 0) {
|
|
3473
|
+
finish(() => resolve({ stdout, stderr }));
|
|
3474
|
+
return;
|
|
3475
|
+
}
|
|
3476
|
+
const detail = `${stderr}
|
|
3477
|
+
${stdout}`.trim();
|
|
3478
|
+
const message = detail ? `${command} ${args.join(" ")} failed (exit ${String(code)}): ${truncate2(detail)}` : `${command} ${args.join(" ")} failed (exit ${String(code)})`;
|
|
3479
|
+
finish(() => reject(new Error(message)));
|
|
3480
|
+
});
|
|
3481
|
+
const timer = setTimeout(() => {
|
|
3482
|
+
try {
|
|
3483
|
+
child.kill("SIGTERM");
|
|
3484
|
+
} catch {
|
|
3485
|
+
}
|
|
3486
|
+
finish(
|
|
3487
|
+
() => reject(
|
|
3488
|
+
new Error(
|
|
3489
|
+
`${command} ${args.join(" ")} timed out after ${Math.floor(timeoutMs / 1e3)}s`
|
|
3490
|
+
)
|
|
3491
|
+
)
|
|
3492
|
+
);
|
|
3493
|
+
}, timeoutMs);
|
|
3494
|
+
});
|
|
3495
|
+
};
|
|
3496
|
+
|
|
3497
|
+
// src/lanes/registry.ts
|
|
3498
|
+
var import_node_fs10 = require("fs");
|
|
3499
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
3500
|
+
var import_storage4 = require("@codelia/storage");
|
|
3501
|
+
var REGISTRY_DIRNAME = "lanes";
|
|
3502
|
+
var REGISTRY_FILENAME = "registry.json";
|
|
3503
|
+
var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
3504
|
+
var sortByUpdatedDesc = (lanes) => [...lanes].sort((a, b) => b.updated_at.localeCompare(a.updated_at));
|
|
3505
|
+
var readJsonFile = async (filePath) => {
|
|
3506
|
+
try {
|
|
3507
|
+
const raw = await import_node_fs10.promises.readFile(filePath, "utf8");
|
|
3508
|
+
const parsed = JSON.parse(raw);
|
|
3509
|
+
if (!parsed || parsed.version !== 1 || !Array.isArray(parsed.lanes)) {
|
|
3510
|
+
return null;
|
|
3511
|
+
}
|
|
3512
|
+
return {
|
|
3513
|
+
version: 1,
|
|
3514
|
+
lanes: parsed.lanes
|
|
3515
|
+
};
|
|
3516
|
+
} catch {
|
|
3517
|
+
return null;
|
|
3518
|
+
}
|
|
3519
|
+
};
|
|
3520
|
+
var atomicWrite = async (filePath, payload) => {
|
|
3521
|
+
const dir = import_node_path10.default.dirname(filePath);
|
|
3522
|
+
const base = import_node_path10.default.basename(filePath);
|
|
3523
|
+
const tmp = import_node_path10.default.join(
|
|
3524
|
+
dir,
|
|
3525
|
+
`${base}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`
|
|
3526
|
+
);
|
|
3527
|
+
await import_node_fs10.promises.writeFile(tmp, payload, "utf8");
|
|
3528
|
+
await import_node_fs10.promises.rename(tmp, filePath);
|
|
3529
|
+
};
|
|
3530
|
+
var LaneRegistryStore = class {
|
|
3531
|
+
registryPath;
|
|
3532
|
+
constructor(registryPath) {
|
|
3533
|
+
if (registryPath) {
|
|
3534
|
+
this.registryPath = registryPath;
|
|
3535
|
+
return;
|
|
3536
|
+
}
|
|
3537
|
+
const root = (0, import_storage4.resolveStoragePaths)().root;
|
|
3538
|
+
this.registryPath = import_node_path10.default.join(root, REGISTRY_DIRNAME, REGISTRY_FILENAME);
|
|
3539
|
+
}
|
|
3540
|
+
async ensureDir() {
|
|
3541
|
+
await import_node_fs10.promises.mkdir(import_node_path10.default.dirname(this.registryPath), { recursive: true });
|
|
3542
|
+
}
|
|
3543
|
+
async readAll() {
|
|
3544
|
+
await this.ensureDir();
|
|
3545
|
+
const data = await readJsonFile(this.registryPath);
|
|
3546
|
+
if (!data) return [];
|
|
3547
|
+
return sortByUpdatedDesc(data.lanes);
|
|
3548
|
+
}
|
|
3549
|
+
async writeAll(lanes) {
|
|
3550
|
+
await this.ensureDir();
|
|
3551
|
+
const payload = {
|
|
3552
|
+
version: 1,
|
|
3553
|
+
lanes: sortByUpdatedDesc(lanes)
|
|
3554
|
+
};
|
|
3555
|
+
await atomicWrite(
|
|
3556
|
+
this.registryPath,
|
|
3557
|
+
`${JSON.stringify(payload, null, 2)}
|
|
3558
|
+
`
|
|
3559
|
+
);
|
|
3560
|
+
}
|
|
3561
|
+
async list() {
|
|
3562
|
+
return this.readAll();
|
|
3563
|
+
}
|
|
3564
|
+
async get(laneId) {
|
|
3565
|
+
const lanes = await this.readAll();
|
|
3566
|
+
return lanes.find((lane) => lane.lane_id === laneId) ?? null;
|
|
3567
|
+
}
|
|
3568
|
+
async upsert(record) {
|
|
3569
|
+
const lanes = await this.readAll();
|
|
3570
|
+
const idx = lanes.findIndex((lane) => lane.lane_id === record.lane_id);
|
|
3571
|
+
const next = {
|
|
3572
|
+
...record,
|
|
3573
|
+
updated_at: nowIso()
|
|
3574
|
+
};
|
|
3575
|
+
if (idx >= 0) {
|
|
3576
|
+
lanes[idx] = next;
|
|
3577
|
+
} else {
|
|
3578
|
+
lanes.push(next);
|
|
3579
|
+
}
|
|
3580
|
+
await this.writeAll(lanes);
|
|
3581
|
+
}
|
|
3582
|
+
async patch(laneId, patch) {
|
|
3583
|
+
const lanes = await this.readAll();
|
|
3584
|
+
const idx = lanes.findIndex((lane) => lane.lane_id === laneId);
|
|
3585
|
+
if (idx < 0) return null;
|
|
3586
|
+
const current = lanes[idx];
|
|
3587
|
+
const next = {
|
|
3588
|
+
...current,
|
|
3589
|
+
...patch,
|
|
3590
|
+
lane_id: current.lane_id,
|
|
3591
|
+
created_at: current.created_at,
|
|
3592
|
+
updated_at: nowIso()
|
|
3593
|
+
};
|
|
3594
|
+
lanes[idx] = next;
|
|
3595
|
+
await this.writeAll(lanes);
|
|
3596
|
+
return next;
|
|
3597
|
+
}
|
|
3598
|
+
};
|
|
3599
|
+
|
|
3600
|
+
// src/lanes/manager.ts
|
|
3601
|
+
var nowIso2 = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
3602
|
+
var INITIAL_MESSAGE_FLAG = "--initial-message";
|
|
3603
|
+
var toSlug = (value) => {
|
|
3604
|
+
const normalized = value.trim().toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replaceAll(/^-+|-+$/g, "");
|
|
3605
|
+
return normalized || "task";
|
|
3606
|
+
};
|
|
3607
|
+
var parseDate = (value) => {
|
|
3608
|
+
const ts = Date.parse(value);
|
|
3609
|
+
if (Number.isNaN(ts)) return 0;
|
|
3610
|
+
return ts;
|
|
3611
|
+
};
|
|
3612
|
+
var isNotFoundError = (error) => {
|
|
3613
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3614
|
+
return message.includes("command not found") || message.includes("ENOENT") || message.includes("not found");
|
|
3615
|
+
};
|
|
3616
|
+
var isExitFailure = (error) => {
|
|
3617
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3618
|
+
return message.includes("failed (exit");
|
|
3619
|
+
};
|
|
3620
|
+
var LaneManagerError = class extends Error {
|
|
3621
|
+
code;
|
|
3622
|
+
constructor(code, message) {
|
|
3623
|
+
super(message);
|
|
3624
|
+
this.code = code;
|
|
3625
|
+
this.name = "LaneManagerError";
|
|
3626
|
+
}
|
|
3627
|
+
};
|
|
3628
|
+
var LaneManager = class {
|
|
3629
|
+
runner;
|
|
3630
|
+
registry;
|
|
3631
|
+
now;
|
|
3632
|
+
randomId;
|
|
3633
|
+
launchCommand;
|
|
3634
|
+
defaultWorktreeRoot;
|
|
3635
|
+
constructor(options = {}) {
|
|
3636
|
+
this.runner = options.runner ?? runCommand;
|
|
3637
|
+
this.registry = options.registry ?? new LaneRegistryStore();
|
|
3638
|
+
this.now = options.now ?? nowIso2;
|
|
3639
|
+
this.randomId = options.randomId ?? (() => import_node_crypto4.default.randomUUID());
|
|
3640
|
+
this.launchCommand = options.launchCommand ?? process.env.CODELIA_LANE_LAUNCH_COMMAND?.trim() ?? "codelia";
|
|
3641
|
+
this.defaultWorktreeRoot = options.defaultWorktreeRoot ?? import_node_path11.default.join(import_node_os2.default.homedir(), ".codelia", "worktrees");
|
|
3642
|
+
}
|
|
3643
|
+
async cmd(command, args, options) {
|
|
3644
|
+
(0, import_logger.debugLog)(`lane.cmd ${command} ${args.join(" ")}`);
|
|
3645
|
+
return this.runner(command, args, options);
|
|
3646
|
+
}
|
|
3647
|
+
async resolveRepoRoot(workingDir) {
|
|
3648
|
+
const { stdout } = await this.cmd(
|
|
3649
|
+
"git",
|
|
3650
|
+
["-C", workingDir, "rev-parse", "--show-toplevel"],
|
|
3651
|
+
{ cwd: workingDir }
|
|
3652
|
+
);
|
|
3653
|
+
const root = stdout.trim();
|
|
3654
|
+
if (!root) {
|
|
3655
|
+
throw new LaneManagerError(
|
|
3656
|
+
"backend_command_failed",
|
|
3657
|
+
"Could not resolve git repository root."
|
|
3658
|
+
);
|
|
3659
|
+
}
|
|
3660
|
+
return root;
|
|
3661
|
+
}
|
|
3662
|
+
async preflightBackend(backend) {
|
|
3663
|
+
if (backend === "tmux") {
|
|
3664
|
+
try {
|
|
3665
|
+
await this.cmd("tmux", ["-V"]);
|
|
3666
|
+
return;
|
|
3667
|
+
} catch (error) {
|
|
3668
|
+
if (isNotFoundError(error)) {
|
|
3669
|
+
throw new LaneManagerError(
|
|
3670
|
+
"backend_not_found",
|
|
3671
|
+
"tmux not found in PATH."
|
|
3672
|
+
);
|
|
3673
|
+
}
|
|
3674
|
+
throw new LaneManagerError(
|
|
3675
|
+
"backend_command_failed",
|
|
3676
|
+
`tmux preflight failed: ${String(error)}`
|
|
3677
|
+
);
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
3324
3680
|
try {
|
|
3325
|
-
|
|
3326
|
-
resolved = sandbox.resolvePath(input.file_path);
|
|
3681
|
+
await this.cmd("zellij", ["--version"]);
|
|
3327
3682
|
} catch (error) {
|
|
3328
|
-
|
|
3683
|
+
if (isNotFoundError(error)) {
|
|
3684
|
+
throw new LaneManagerError(
|
|
3685
|
+
"backend_not_found",
|
|
3686
|
+
"zellij not found in PATH."
|
|
3687
|
+
);
|
|
3688
|
+
}
|
|
3689
|
+
throw new LaneManagerError(
|
|
3690
|
+
"backend_command_failed",
|
|
3691
|
+
`zellij preflight failed: ${String(error)}`
|
|
3692
|
+
);
|
|
3329
3693
|
}
|
|
3694
|
+
throw new LaneManagerError(
|
|
3695
|
+
"backend_command_failed",
|
|
3696
|
+
"zellij backend is not supported yet in lane MVP. Use tmux."
|
|
3697
|
+
);
|
|
3698
|
+
}
|
|
3699
|
+
async startTmuxLane(target, worktreePath, seedContext) {
|
|
3700
|
+
const launchCommand = this.buildLaunchCommand(seedContext);
|
|
3701
|
+
await this.cmd("tmux", [
|
|
3702
|
+
"new-session",
|
|
3703
|
+
"-d",
|
|
3704
|
+
"-s",
|
|
3705
|
+
target,
|
|
3706
|
+
"-c",
|
|
3707
|
+
worktreePath
|
|
3708
|
+
]);
|
|
3709
|
+
const paneTarget = `${target}:0.0`;
|
|
3710
|
+
await this.cmd("tmux", [
|
|
3711
|
+
"send-keys",
|
|
3712
|
+
"-t",
|
|
3713
|
+
paneTarget,
|
|
3714
|
+
"-l",
|
|
3715
|
+
launchCommand
|
|
3716
|
+
]);
|
|
3717
|
+
await this.cmd("tmux", ["send-keys", "-t", paneTarget, "Enter"]);
|
|
3718
|
+
}
|
|
3719
|
+
shellQuote(value) {
|
|
3720
|
+
if (!value) return "''";
|
|
3721
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
3722
|
+
}
|
|
3723
|
+
buildLaunchCommand(seedContext) {
|
|
3724
|
+
const seed = seedContext?.trim();
|
|
3725
|
+
if (!seed) {
|
|
3726
|
+
return this.launchCommand;
|
|
3727
|
+
}
|
|
3728
|
+
return `${this.launchCommand} ${INITIAL_MESSAGE_FLAG} ${this.shellQuote(seed)}`;
|
|
3729
|
+
}
|
|
3730
|
+
async isTmuxAlive(target) {
|
|
3330
3731
|
try {
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3732
|
+
await this.cmd("tmux", ["has-session", "-t", target], {
|
|
3733
|
+
timeoutMs: 5e3
|
|
3734
|
+
});
|
|
3735
|
+
return true;
|
|
3736
|
+
} catch (error) {
|
|
3737
|
+
if (isExitFailure(error)) return false;
|
|
3738
|
+
throw new LaneManagerError(
|
|
3739
|
+
"backend_command_failed",
|
|
3740
|
+
`tmux status failed: ${String(error)}`
|
|
3741
|
+
);
|
|
3742
|
+
}
|
|
3743
|
+
}
|
|
3744
|
+
async stopTmuxLane(target) {
|
|
3745
|
+
try {
|
|
3746
|
+
await this.cmd("tmux", ["kill-session", "-t", target], {
|
|
3747
|
+
timeoutMs: 1e4
|
|
3748
|
+
});
|
|
3749
|
+
} catch (error) {
|
|
3750
|
+
if (isExitFailure(error)) return;
|
|
3751
|
+
throw new LaneManagerError(
|
|
3752
|
+
"backend_command_failed",
|
|
3753
|
+
`tmux close failed: ${String(error)}`
|
|
3754
|
+
);
|
|
3755
|
+
}
|
|
3756
|
+
}
|
|
3757
|
+
async resolveWorktreeMainRoot(worktreePath) {
|
|
3758
|
+
const { stdout } = await this.cmd("git", [
|
|
3759
|
+
"-C",
|
|
3760
|
+
worktreePath,
|
|
3761
|
+
"rev-parse",
|
|
3762
|
+
"--git-common-dir"
|
|
3763
|
+
]);
|
|
3764
|
+
const commonDir = import_node_path11.default.resolve(worktreePath, stdout.trim());
|
|
3765
|
+
if (import_node_path11.default.basename(commonDir) === ".git") {
|
|
3766
|
+
return import_node_path11.default.dirname(commonDir);
|
|
3767
|
+
}
|
|
3768
|
+
return await this.resolveRepoRoot(worktreePath);
|
|
3769
|
+
}
|
|
3770
|
+
async isWorktreeDirty(worktreePath) {
|
|
3771
|
+
try {
|
|
3772
|
+
await import_node_fs11.promises.access(worktreePath);
|
|
3335
3773
|
} catch {
|
|
3336
|
-
return
|
|
3774
|
+
return false;
|
|
3337
3775
|
}
|
|
3776
|
+
const { stdout } = await this.cmd("git", [
|
|
3777
|
+
"-C",
|
|
3778
|
+
worktreePath,
|
|
3779
|
+
"status",
|
|
3780
|
+
"--porcelain"
|
|
3781
|
+
]);
|
|
3782
|
+
return stdout.trim().length > 0;
|
|
3783
|
+
}
|
|
3784
|
+
async removeWorktree(worktreePath, force) {
|
|
3338
3785
|
try {
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3786
|
+
await import_node_fs11.promises.access(worktreePath);
|
|
3787
|
+
} catch {
|
|
3788
|
+
return;
|
|
3789
|
+
}
|
|
3790
|
+
const repoRoot = await this.resolveWorktreeMainRoot(worktreePath);
|
|
3791
|
+
const args = ["-C", repoRoot, "worktree", "remove"];
|
|
3792
|
+
if (force) args.push("--force");
|
|
3793
|
+
args.push(worktreePath);
|
|
3794
|
+
await this.cmd("git", args);
|
|
3795
|
+
}
|
|
3796
|
+
async create(input, options) {
|
|
3797
|
+
const taskId = input.task_id.trim();
|
|
3798
|
+
if (!taskId) {
|
|
3799
|
+
throw new LaneManagerError(
|
|
3800
|
+
"backend_command_failed",
|
|
3801
|
+
"task_id is required."
|
|
3802
|
+
);
|
|
3803
|
+
}
|
|
3804
|
+
const backend = input.mux_backend ?? "tmux";
|
|
3805
|
+
await this.preflightBackend(backend);
|
|
3806
|
+
const repoRoot = await this.resolveRepoRoot(options.workingDir);
|
|
3807
|
+
const laneId = this.randomId();
|
|
3808
|
+
const slug = toSlug(taskId);
|
|
3809
|
+
const shortId = laneId.slice(0, 8);
|
|
3810
|
+
const branchName = `lane/${slug}-${shortId}`;
|
|
3811
|
+
const worktreePath = input.worktree_path ? import_node_path11.default.resolve(options.workingDir, input.worktree_path) : import_node_path11.default.join(this.defaultWorktreeRoot, `${slug}-${shortId}`);
|
|
3812
|
+
const muxTarget = `codelia-lane-${shortId}`;
|
|
3813
|
+
const createdAt = this.now();
|
|
3814
|
+
const record = {
|
|
3815
|
+
lane_id: laneId,
|
|
3816
|
+
task_id: taskId,
|
|
3817
|
+
state: "creating",
|
|
3818
|
+
mux_backend: backend,
|
|
3819
|
+
mux_target: muxTarget,
|
|
3820
|
+
worktree_path: worktreePath,
|
|
3821
|
+
branch_name: branchName,
|
|
3822
|
+
session_id: import_node_crypto4.default.randomUUID(),
|
|
3823
|
+
created_at: createdAt,
|
|
3824
|
+
updated_at: createdAt,
|
|
3825
|
+
last_activity_at: createdAt
|
|
3826
|
+
};
|
|
3827
|
+
await this.registry.upsert(record);
|
|
3828
|
+
try {
|
|
3829
|
+
await import_node_fs11.promises.mkdir(import_node_path11.default.dirname(worktreePath), { recursive: true });
|
|
3830
|
+
await this.cmd("git", [
|
|
3831
|
+
"-C",
|
|
3832
|
+
repoRoot,
|
|
3833
|
+
"worktree",
|
|
3834
|
+
"add",
|
|
3835
|
+
"-b",
|
|
3836
|
+
branchName,
|
|
3837
|
+
worktreePath,
|
|
3838
|
+
input.base_ref?.trim() || "HEAD"
|
|
3839
|
+
]);
|
|
3840
|
+
if (backend === "tmux") {
|
|
3841
|
+
await this.startTmuxLane(muxTarget, worktreePath, input.seed_context);
|
|
3343
3842
|
}
|
|
3344
|
-
const
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3843
|
+
const running = await this.registry.patch(laneId, {
|
|
3844
|
+
state: "running",
|
|
3845
|
+
last_activity_at: this.now(),
|
|
3846
|
+
last_error: void 0
|
|
3847
|
+
});
|
|
3848
|
+
if (!running) {
|
|
3849
|
+
throw new Error("lane record disappeared during create");
|
|
3348
3850
|
}
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3851
|
+
return running;
|
|
3852
|
+
} catch (error) {
|
|
3853
|
+
await this.registry.patch(laneId, {
|
|
3854
|
+
state: "error",
|
|
3855
|
+
last_error: String(error),
|
|
3856
|
+
last_activity_at: this.now()
|
|
3857
|
+
});
|
|
3858
|
+
throw new LaneManagerError(
|
|
3859
|
+
"backend_command_failed",
|
|
3860
|
+
`lane.create failed (${laneId}): ${String(error)}`
|
|
3861
|
+
);
|
|
3862
|
+
}
|
|
3863
|
+
}
|
|
3864
|
+
async list(input) {
|
|
3865
|
+
const all = await this.registry.list();
|
|
3866
|
+
if (input?.include_closed) return all;
|
|
3867
|
+
return all.filter((lane) => lane.state !== "closed");
|
|
3868
|
+
}
|
|
3869
|
+
async status(laneId) {
|
|
3870
|
+
const lane = await this.registry.get(laneId);
|
|
3871
|
+
if (!lane) {
|
|
3872
|
+
throw new LaneManagerError("lane_not_found", `Lane not found: ${laneId}`);
|
|
3873
|
+
}
|
|
3874
|
+
let alive = false;
|
|
3875
|
+
if (lane.state !== "closed") {
|
|
3876
|
+
if (lane.mux_backend === "tmux") {
|
|
3877
|
+
alive = await this.isTmuxAlive(lane.mux_target);
|
|
3878
|
+
}
|
|
3879
|
+
if (lane.state === "running" && !alive) {
|
|
3880
|
+
const patched = await this.registry.patch(lane.lane_id, {
|
|
3881
|
+
state: "finished",
|
|
3882
|
+
last_activity_at: this.now()
|
|
3883
|
+
});
|
|
3884
|
+
if (patched) {
|
|
3885
|
+
return { lane: patched, backend_alive: false };
|
|
3358
3886
|
}
|
|
3359
|
-
raw.push(line);
|
|
3360
|
-
bytes += size;
|
|
3361
3887
|
}
|
|
3362
|
-
|
|
3363
|
-
|
|
3888
|
+
}
|
|
3889
|
+
return { lane, backend_alive: alive };
|
|
3890
|
+
}
|
|
3891
|
+
async close(input) {
|
|
3892
|
+
const lane = await this.registry.get(input.lane_id);
|
|
3893
|
+
if (!lane) {
|
|
3894
|
+
throw new LaneManagerError(
|
|
3895
|
+
"lane_not_found",
|
|
3896
|
+
`Lane not found: ${input.lane_id}`
|
|
3364
3897
|
);
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3898
|
+
}
|
|
3899
|
+
if (lane.state === "closed") {
|
|
3900
|
+
return lane;
|
|
3901
|
+
}
|
|
3902
|
+
const force = input.force === true;
|
|
3903
|
+
const removeWorktree = input.remove_worktree !== false;
|
|
3904
|
+
const status = await this.status(lane.lane_id);
|
|
3905
|
+
const refreshed = status.lane;
|
|
3906
|
+
if (status.backend_alive && !force) {
|
|
3907
|
+
throw new LaneManagerError(
|
|
3908
|
+
"lane_running",
|
|
3909
|
+
`Lane is still running: ${lane.lane_id}`
|
|
3910
|
+
);
|
|
3911
|
+
}
|
|
3912
|
+
if (status.backend_alive && refreshed.mux_backend === "tmux") {
|
|
3913
|
+
await this.stopTmuxLane(refreshed.mux_target);
|
|
3914
|
+
}
|
|
3915
|
+
if (removeWorktree) {
|
|
3916
|
+
const dirty = await this.isWorktreeDirty(refreshed.worktree_path);
|
|
3917
|
+
if (dirty && !force) {
|
|
3918
|
+
throw new LaneManagerError(
|
|
3919
|
+
"worktree_dirty",
|
|
3920
|
+
`Worktree has uncommitted changes: ${refreshed.worktree_path}`
|
|
3921
|
+
);
|
|
3374
3922
|
}
|
|
3375
|
-
|
|
3376
|
-
}
|
|
3377
|
-
|
|
3923
|
+
await this.removeWorktree(refreshed.worktree_path, force);
|
|
3924
|
+
}
|
|
3925
|
+
const closed = await this.registry.patch(refreshed.lane_id, {
|
|
3926
|
+
state: "closed",
|
|
3927
|
+
last_activity_at: this.now()
|
|
3928
|
+
});
|
|
3929
|
+
if (!closed) {
|
|
3930
|
+
throw new LaneManagerError(
|
|
3931
|
+
"backend_command_failed",
|
|
3932
|
+
"Failed to persist lane close state."
|
|
3933
|
+
);
|
|
3378
3934
|
}
|
|
3935
|
+
return closed;
|
|
3379
3936
|
}
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3937
|
+
async gc(input) {
|
|
3938
|
+
const ttlMs = Math.max(1, input.idle_ttl_minutes) * 6e4;
|
|
3939
|
+
const now = Date.now();
|
|
3940
|
+
const lanes = await this.registry.list();
|
|
3941
|
+
let closed = 0;
|
|
3942
|
+
let skipped = 0;
|
|
3943
|
+
const errors = [];
|
|
3944
|
+
for (const lane of lanes) {
|
|
3945
|
+
try {
|
|
3946
|
+
const status = await this.status(lane.lane_id);
|
|
3947
|
+
const current = status.lane;
|
|
3948
|
+
if (current.state === "running" || current.state === "closed") {
|
|
3949
|
+
skipped += 1;
|
|
3950
|
+
continue;
|
|
3951
|
+
}
|
|
3952
|
+
if (current.state !== "finished" && current.state !== "error") {
|
|
3953
|
+
skipped += 1;
|
|
3954
|
+
continue;
|
|
3955
|
+
}
|
|
3956
|
+
const idleSince = parseDate(current.last_activity_at) || parseDate(current.updated_at);
|
|
3957
|
+
if (idleSince <= 0 || now - idleSince < ttlMs) {
|
|
3958
|
+
skipped += 1;
|
|
3959
|
+
continue;
|
|
3960
|
+
}
|
|
3961
|
+
await this.close({
|
|
3962
|
+
lane_id: current.lane_id,
|
|
3963
|
+
remove_worktree: input.remove_worktree,
|
|
3964
|
+
force: input.force
|
|
3965
|
+
});
|
|
3966
|
+
closed += 1;
|
|
3967
|
+
} catch (error) {
|
|
3968
|
+
errors.push(`${lane.lane_id}: ${String(error)}`);
|
|
3969
|
+
}
|
|
3970
|
+
}
|
|
3971
|
+
return {
|
|
3972
|
+
checked: lanes.length,
|
|
3973
|
+
closed,
|
|
3974
|
+
skipped,
|
|
3975
|
+
errors
|
|
3976
|
+
};
|
|
3977
|
+
}
|
|
3978
|
+
};
|
|
3979
|
+
|
|
3980
|
+
// src/tools/lane.ts
|
|
3981
|
+
var laneManager = new LaneManager();
|
|
3982
|
+
var backendSchema = import_zod8.z.enum(["tmux", "zellij"]);
|
|
3983
|
+
var formatError = (error) => {
|
|
3984
|
+
if (error instanceof LaneManagerError) {
|
|
3985
|
+
return new Error(`${error.code}: ${error.message}`);
|
|
3986
|
+
}
|
|
3987
|
+
if (error instanceof Error) return error;
|
|
3988
|
+
return new Error(String(error));
|
|
3989
|
+
};
|
|
3990
|
+
var shQuote = (value) => {
|
|
3991
|
+
if (!value) return "''";
|
|
3992
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
3993
|
+
};
|
|
3994
|
+
var buildLaneHints = (lane) => {
|
|
3995
|
+
const attachCommand = lane.mux_backend === "tmux" ? `tmux attach -t ${shQuote(lane.mux_target)}` : lane.mux_backend === "zellij" ? `zellij attach ${shQuote(lane.mux_target)}` : null;
|
|
3996
|
+
return {
|
|
3997
|
+
attach_command: attachCommand,
|
|
3998
|
+
enter_worktree_command: `cd ${shQuote(lane.worktree_path)}`,
|
|
3999
|
+
status_tool: {
|
|
4000
|
+
name: "lane_status",
|
|
4001
|
+
args: { lane_id: lane.lane_id }
|
|
4002
|
+
},
|
|
4003
|
+
close_tool: {
|
|
4004
|
+
name: "lane_close",
|
|
4005
|
+
args: { lane_id: lane.lane_id }
|
|
4006
|
+
}
|
|
4007
|
+
};
|
|
4008
|
+
};
|
|
4009
|
+
var createLaneCreateTool = (sandboxKey) => (0, import_core9.defineTool)({
|
|
4010
|
+
name: "lane_create",
|
|
4011
|
+
description: "Create a Task lane (worktree slot) and start autonomous execution in tmux/zellij.",
|
|
4012
|
+
input: import_zod8.z.object({
|
|
4013
|
+
task_id: import_zod8.z.string().describe("Task identifier."),
|
|
4014
|
+
base_ref: import_zod8.z.string().optional().describe("Base git ref. Default: HEAD."),
|
|
4015
|
+
worktree_path: import_zod8.z.string().optional().describe(
|
|
4016
|
+
"Optional worktree path override. Default: ~/.codelia/worktrees/<task-slug>-<lane-id8>."
|
|
4017
|
+
),
|
|
4018
|
+
mux_backend: backendSchema.optional().describe("Multiplexer backend. Default: tmux."),
|
|
4019
|
+
seed_context: import_zod8.z.string().optional().describe("Optional initial text context.")
|
|
4020
|
+
}),
|
|
4021
|
+
execute: async (input, ctx) => {
|
|
4022
|
+
const sandbox = await getSandboxContext(ctx, sandboxKey);
|
|
4023
|
+
try {
|
|
4024
|
+
const lane = await laneManager.create(
|
|
4025
|
+
{
|
|
4026
|
+
task_id: input.task_id,
|
|
4027
|
+
base_ref: input.base_ref,
|
|
4028
|
+
worktree_path: input.worktree_path,
|
|
4029
|
+
mux_backend: input.mux_backend,
|
|
4030
|
+
seed_context: input.seed_context
|
|
4031
|
+
},
|
|
4032
|
+
{ workingDir: sandbox.workingDir }
|
|
4033
|
+
);
|
|
4034
|
+
return {
|
|
4035
|
+
ok: true,
|
|
4036
|
+
lane,
|
|
4037
|
+
hints: buildLaneHints(lane)
|
|
4038
|
+
};
|
|
4039
|
+
} catch (error) {
|
|
4040
|
+
throw formatError(error);
|
|
4041
|
+
}
|
|
4042
|
+
}
|
|
4043
|
+
});
|
|
4044
|
+
var createLaneListTool = (sandboxKey) => (0, import_core9.defineTool)({
|
|
4045
|
+
name: "lane_list",
|
|
4046
|
+
description: "List Task lanes (worktree slots) and current states.",
|
|
4047
|
+
input: import_zod8.z.object({
|
|
4048
|
+
include_closed: import_zod8.z.boolean().optional().describe("Include closed lanes. Default: false.")
|
|
4049
|
+
}),
|
|
4050
|
+
execute: async (input, ctx) => {
|
|
4051
|
+
await getSandboxContext(ctx, sandboxKey);
|
|
4052
|
+
try {
|
|
4053
|
+
const lanes = await laneManager.list({
|
|
4054
|
+
include_closed: input.include_closed
|
|
4055
|
+
});
|
|
4056
|
+
return {
|
|
4057
|
+
lanes,
|
|
4058
|
+
hints: lanes.map((lane) => ({
|
|
4059
|
+
lane_id: lane.lane_id,
|
|
4060
|
+
...buildLaneHints(lane)
|
|
4061
|
+
}))
|
|
4062
|
+
};
|
|
4063
|
+
} catch (error) {
|
|
4064
|
+
throw formatError(error);
|
|
4065
|
+
}
|
|
4066
|
+
}
|
|
4067
|
+
});
|
|
4068
|
+
var createLaneStatusTool = (sandboxKey) => (0, import_core9.defineTool)({
|
|
4069
|
+
name: "lane_status",
|
|
4070
|
+
description: "Get Task lane status and backend liveness.",
|
|
4071
|
+
input: import_zod8.z.object({
|
|
4072
|
+
lane_id: import_zod8.z.string().describe("Lane id.")
|
|
4073
|
+
}),
|
|
4074
|
+
execute: async (input, ctx) => {
|
|
4075
|
+
await getSandboxContext(ctx, sandboxKey);
|
|
4076
|
+
try {
|
|
4077
|
+
const result = await laneManager.status(input.lane_id);
|
|
4078
|
+
return {
|
|
4079
|
+
...result,
|
|
4080
|
+
hints: buildLaneHints(result.lane)
|
|
4081
|
+
};
|
|
4082
|
+
} catch (error) {
|
|
4083
|
+
throw formatError(error);
|
|
4084
|
+
}
|
|
4085
|
+
}
|
|
4086
|
+
});
|
|
4087
|
+
var createLaneCloseTool = (sandboxKey) => (0, import_core9.defineTool)({
|
|
4088
|
+
name: "lane_close",
|
|
4089
|
+
description: "Close a Task lane and optionally remove its worktree.",
|
|
4090
|
+
input: import_zod8.z.object({
|
|
4091
|
+
lane_id: import_zod8.z.string().describe("Lane id."),
|
|
4092
|
+
remove_worktree: import_zod8.z.boolean().optional().describe("Remove worktree. Default: true."),
|
|
4093
|
+
force: import_zod8.z.boolean().optional().describe("Force close/cleanup. Default: false.")
|
|
4094
|
+
}),
|
|
4095
|
+
execute: async (input, ctx) => {
|
|
4096
|
+
await getSandboxContext(ctx, sandboxKey);
|
|
4097
|
+
try {
|
|
4098
|
+
const lane = await laneManager.close({
|
|
4099
|
+
lane_id: input.lane_id,
|
|
4100
|
+
remove_worktree: input.remove_worktree,
|
|
4101
|
+
force: input.force
|
|
4102
|
+
});
|
|
4103
|
+
return {
|
|
4104
|
+
ok: true,
|
|
4105
|
+
lane
|
|
4106
|
+
};
|
|
4107
|
+
} catch (error) {
|
|
4108
|
+
throw formatError(error);
|
|
4109
|
+
}
|
|
4110
|
+
}
|
|
4111
|
+
});
|
|
4112
|
+
var createLaneGcTool = (sandboxKey) => (0, import_core9.defineTool)({
|
|
4113
|
+
name: "lane_gc",
|
|
4114
|
+
description: "Close stale finished/error lanes after idle TTL.",
|
|
4115
|
+
input: import_zod8.z.object({
|
|
4116
|
+
idle_ttl_minutes: import_zod8.z.number().int().positive().describe("Idle TTL in minutes."),
|
|
4117
|
+
remove_worktree: import_zod8.z.boolean().optional().describe("Remove worktree on close. Default: false."),
|
|
4118
|
+
force: import_zod8.z.boolean().optional().describe("Force cleanup for dirty worktrees. Default: false.")
|
|
4119
|
+
}),
|
|
4120
|
+
execute: async (input, ctx) => {
|
|
4121
|
+
await getSandboxContext(ctx, sandboxKey);
|
|
4122
|
+
try {
|
|
4123
|
+
return await laneManager.gc({
|
|
4124
|
+
idle_ttl_minutes: input.idle_ttl_minutes,
|
|
4125
|
+
remove_worktree: input.remove_worktree,
|
|
4126
|
+
force: input.force
|
|
4127
|
+
});
|
|
4128
|
+
} catch (error) {
|
|
4129
|
+
throw formatError(error);
|
|
4130
|
+
}
|
|
4131
|
+
}
|
|
4132
|
+
});
|
|
4133
|
+
|
|
4134
|
+
// src/tools/read.ts
|
|
4135
|
+
var import_node_fs12 = require("fs");
|
|
4136
|
+
var import_core10 = require("@codelia/core");
|
|
4137
|
+
var import_zod9 = require("zod");
|
|
4138
|
+
var DEFAULT_READ_LIMIT = 2e3;
|
|
4139
|
+
var MAX_LINE_LENGTH = 2e3;
|
|
4140
|
+
var MAX_BYTES = 50 * 1024;
|
|
4141
|
+
var createReadTool = (sandboxKey) => (0, import_core10.defineTool)({
|
|
4142
|
+
name: "read",
|
|
4143
|
+
description: "Read a text file with optional 0-based line offset and line limit.",
|
|
4144
|
+
input: import_zod9.z.object({
|
|
4145
|
+
file_path: import_zod9.z.string().describe("File path under the sandbox root."),
|
|
4146
|
+
offset: import_zod9.z.number().int().nonnegative().optional().describe("0-based start line. Default 0."),
|
|
4147
|
+
limit: import_zod9.z.number().int().positive().optional().describe("Max lines to read. Default 2000.")
|
|
4148
|
+
}),
|
|
4149
|
+
execute: async (input, ctx) => {
|
|
4150
|
+
let resolved;
|
|
4151
|
+
try {
|
|
4152
|
+
const sandbox = await getSandboxContext(ctx, sandboxKey);
|
|
4153
|
+
resolved = sandbox.resolvePath(input.file_path);
|
|
4154
|
+
} catch (error) {
|
|
4155
|
+
return `Security error: ${String(error)}`;
|
|
4156
|
+
}
|
|
4157
|
+
try {
|
|
4158
|
+
const stat = await import_node_fs12.promises.stat(resolved);
|
|
4159
|
+
if (stat.isDirectory()) {
|
|
4160
|
+
return `Path is a directory: ${input.file_path}`;
|
|
4161
|
+
}
|
|
4162
|
+
} catch {
|
|
4163
|
+
return `File not found: ${input.file_path}`;
|
|
4164
|
+
}
|
|
4165
|
+
try {
|
|
4166
|
+
const content = await import_node_fs12.promises.readFile(resolved, "utf8");
|
|
4167
|
+
const lines = content.split(/\r?\n/);
|
|
4168
|
+
if (lines.length === 0) {
|
|
4169
|
+
return "";
|
|
4170
|
+
}
|
|
4171
|
+
const offset = input.offset ?? 0;
|
|
4172
|
+
const limit = input.limit ?? DEFAULT_READ_LIMIT;
|
|
4173
|
+
if (offset >= lines.length) {
|
|
4174
|
+
return `Offset exceeds file length: ${input.file_path}`;
|
|
4175
|
+
}
|
|
4176
|
+
const raw = [];
|
|
4177
|
+
let bytes = 0;
|
|
4178
|
+
let truncatedByBytes = false;
|
|
4179
|
+
for (let index = offset; index < Math.min(lines.length, offset + limit); index += 1) {
|
|
4180
|
+
const line = lines[index].length > MAX_LINE_LENGTH ? `${lines[index].slice(0, MAX_LINE_LENGTH)}...` : lines[index];
|
|
4181
|
+
const size = Buffer.byteLength(line, "utf8") + (raw.length > 0 ? 1 : 0);
|
|
4182
|
+
if (bytes + size > MAX_BYTES) {
|
|
4183
|
+
truncatedByBytes = true;
|
|
4184
|
+
break;
|
|
4185
|
+
}
|
|
4186
|
+
raw.push(line);
|
|
4187
|
+
bytes += size;
|
|
4188
|
+
}
|
|
4189
|
+
const numbered = raw.map(
|
|
4190
|
+
(line, index) => `${String(index + offset + 1).padStart(5, " ")} ${line}`
|
|
4191
|
+
);
|
|
4192
|
+
const lastReadLine = offset + raw.length;
|
|
4193
|
+
const hasMoreLines = lines.length > lastReadLine;
|
|
4194
|
+
const truncated = truncatedByBytes || hasMoreLines;
|
|
4195
|
+
let output = numbered.join("\n");
|
|
4196
|
+
if (truncated) {
|
|
4197
|
+
const reason = truncatedByBytes ? `Output truncated at ${MAX_BYTES} bytes.` : "File has more lines.";
|
|
4198
|
+
output += `
|
|
4199
|
+
|
|
4200
|
+
${reason} Use offset to read beyond line ${lastReadLine}.`;
|
|
4201
|
+
}
|
|
4202
|
+
return output;
|
|
4203
|
+
} catch (error) {
|
|
4204
|
+
return `Error reading file: ${String(error)}`;
|
|
4205
|
+
}
|
|
4206
|
+
}
|
|
4207
|
+
});
|
|
4208
|
+
|
|
4209
|
+
// src/tools/skill-load.ts
|
|
4210
|
+
var import_core11 = require("@codelia/core");
|
|
4211
|
+
var import_zod10 = require("zod");
|
|
4212
|
+
var SkillLoadInputSchema = import_zod10.z.object({
|
|
4213
|
+
name: import_zod10.z.string().min(1).optional().describe("Exact skill name to load."),
|
|
4214
|
+
path: import_zod10.z.string().min(1).optional().describe("Absolute or workspace-relative path to SKILL.md.")
|
|
4215
|
+
}).refine((value) => !!value.name || !!value.path, {
|
|
4216
|
+
message: "name or path is required",
|
|
4217
|
+
path: ["name"]
|
|
4218
|
+
});
|
|
4219
|
+
var createSkillLoadTool = (skillsResolverKey) => (0, import_core11.defineTool)({
|
|
4220
|
+
name: "skill_load",
|
|
4221
|
+
description: "Load full SKILL.md content by exact name or path.",
|
|
3395
4222
|
input: SkillLoadInputSchema,
|
|
3396
4223
|
execute: async (input, ctx) => {
|
|
3397
4224
|
try {
|
|
@@ -3435,15 +4262,15 @@ var createSkillLoadTool = (skillsResolverKey) => (0, import_core10.defineTool)({
|
|
|
3435
4262
|
});
|
|
3436
4263
|
|
|
3437
4264
|
// src/tools/skill-search.ts
|
|
3438
|
-
var
|
|
3439
|
-
var
|
|
3440
|
-
var createSkillSearchTool = (skillsResolverKey) => (0,
|
|
4265
|
+
var import_core12 = require("@codelia/core");
|
|
4266
|
+
var import_zod11 = require("zod");
|
|
4267
|
+
var createSkillSearchTool = (skillsResolverKey) => (0, import_core12.defineTool)({
|
|
3441
4268
|
name: "skill_search",
|
|
3442
4269
|
description: "Search installed local skills by name, description, or path.",
|
|
3443
|
-
input:
|
|
3444
|
-
query:
|
|
3445
|
-
limit:
|
|
3446
|
-
scope:
|
|
4270
|
+
input: import_zod11.z.object({
|
|
4271
|
+
query: import_zod11.z.string().min(1).describe("Search query text."),
|
|
4272
|
+
limit: import_zod11.z.number().int().positive().optional().describe("Optional max result count (clamped by config)."),
|
|
4273
|
+
scope: import_zod11.z.enum(["repo", "user"]).optional().describe("Optional scope filter.")
|
|
3447
4274
|
}),
|
|
3448
4275
|
execute: async (input, ctx) => {
|
|
3449
4276
|
try {
|
|
@@ -3466,17 +4293,17 @@ var createSkillSearchTool = (skillsResolverKey) => (0, import_core11.defineTool)
|
|
|
3466
4293
|
});
|
|
3467
4294
|
|
|
3468
4295
|
// src/tools/todo-read.ts
|
|
3469
|
-
var
|
|
3470
|
-
var
|
|
4296
|
+
var import_core13 = require("@codelia/core");
|
|
4297
|
+
var import_zod12 = require("zod");
|
|
3471
4298
|
|
|
3472
4299
|
// src/tools/todo-store.ts
|
|
3473
4300
|
var todoStore = /* @__PURE__ */ new Map();
|
|
3474
4301
|
|
|
3475
4302
|
// src/tools/todo-read.ts
|
|
3476
|
-
var createTodoReadTool = (sandboxKey) => (0,
|
|
4303
|
+
var createTodoReadTool = (sandboxKey) => (0, import_core13.defineTool)({
|
|
3477
4304
|
name: "todo_read",
|
|
3478
4305
|
description: "Read the in-session todo list.",
|
|
3479
|
-
input:
|
|
4306
|
+
input: import_zod12.z.object({}),
|
|
3480
4307
|
execute: async (_input, ctx) => {
|
|
3481
4308
|
const sandbox = await getSandboxContext(ctx, sandboxKey);
|
|
3482
4309
|
const todos = todoStore.get(sandbox.sessionId) ?? [];
|
|
@@ -3489,17 +4316,17 @@ var createTodoReadTool = (sandboxKey) => (0, import_core12.defineTool)({
|
|
|
3489
4316
|
});
|
|
3490
4317
|
|
|
3491
4318
|
// src/tools/todo-write.ts
|
|
3492
|
-
var
|
|
3493
|
-
var
|
|
3494
|
-
var createTodoWriteTool = (sandboxKey) => (0,
|
|
4319
|
+
var import_core14 = require("@codelia/core");
|
|
4320
|
+
var import_zod13 = require("zod");
|
|
4321
|
+
var createTodoWriteTool = (sandboxKey) => (0, import_core14.defineTool)({
|
|
3495
4322
|
name: "todo_write",
|
|
3496
4323
|
description: "Replace the in-session todo list.",
|
|
3497
|
-
input:
|
|
3498
|
-
todos:
|
|
3499
|
-
|
|
3500
|
-
content:
|
|
3501
|
-
status:
|
|
3502
|
-
activeForm:
|
|
4324
|
+
input: import_zod13.z.object({
|
|
4325
|
+
todos: import_zod13.z.array(
|
|
4326
|
+
import_zod13.z.object({
|
|
4327
|
+
content: import_zod13.z.string().describe("Todo item text."),
|
|
4328
|
+
status: import_zod13.z.enum(["pending", "in_progress", "completed"]).describe("Todo status."),
|
|
4329
|
+
activeForm: import_zod13.z.string().optional().describe("Optional in-progress phrasing for UI display.")
|
|
3503
4330
|
})
|
|
3504
4331
|
)
|
|
3505
4332
|
}),
|
|
@@ -3516,15 +4343,15 @@ var createTodoWriteTool = (sandboxKey) => (0, import_core13.defineTool)({
|
|
|
3516
4343
|
});
|
|
3517
4344
|
|
|
3518
4345
|
// src/tools/tool-output-cache.ts
|
|
3519
|
-
var
|
|
3520
|
-
var
|
|
3521
|
-
var createToolOutputCacheTool = (store) => (0,
|
|
4346
|
+
var import_core15 = require("@codelia/core");
|
|
4347
|
+
var import_zod14 = require("zod");
|
|
4348
|
+
var createToolOutputCacheTool = (store) => (0, import_core15.defineTool)({
|
|
3522
4349
|
name: "tool_output_cache",
|
|
3523
4350
|
description: "Read cached tool output by ref_id.",
|
|
3524
|
-
input:
|
|
3525
|
-
ref_id:
|
|
3526
|
-
offset:
|
|
3527
|
-
limit:
|
|
4351
|
+
input: import_zod14.z.object({
|
|
4352
|
+
ref_id: import_zod14.z.string().describe("Tool output reference ID."),
|
|
4353
|
+
offset: import_zod14.z.number().int().nonnegative().optional().describe("Optional 0-based line offset."),
|
|
4354
|
+
limit: import_zod14.z.number().int().positive().optional().describe("Optional max number of lines to return.")
|
|
3528
4355
|
}),
|
|
3529
4356
|
execute: async (input) => {
|
|
3530
4357
|
if (!store.read) {
|
|
@@ -3540,16 +4367,16 @@ var createToolOutputCacheTool = (store) => (0, import_core14.defineTool)({
|
|
|
3540
4367
|
}
|
|
3541
4368
|
}
|
|
3542
4369
|
});
|
|
3543
|
-
var createToolOutputCacheGrepTool = (store) => (0,
|
|
4370
|
+
var createToolOutputCacheGrepTool = (store) => (0, import_core15.defineTool)({
|
|
3544
4371
|
name: "tool_output_cache_grep",
|
|
3545
4372
|
description: "Search cached tool output by ref_id.",
|
|
3546
|
-
input:
|
|
3547
|
-
ref_id:
|
|
3548
|
-
pattern:
|
|
3549
|
-
regex:
|
|
3550
|
-
before:
|
|
3551
|
-
after:
|
|
3552
|
-
max_matches:
|
|
4373
|
+
input: import_zod14.z.object({
|
|
4374
|
+
ref_id: import_zod14.z.string().describe("Tool output reference ID."),
|
|
4375
|
+
pattern: import_zod14.z.string().describe("Text or regex pattern to search for."),
|
|
4376
|
+
regex: import_zod14.z.boolean().optional().describe("Interpret pattern as regex when true. Default false."),
|
|
4377
|
+
before: import_zod14.z.number().int().nonnegative().optional().describe("Context lines before each match. Default 0."),
|
|
4378
|
+
after: import_zod14.z.number().int().nonnegative().optional().describe("Context lines after each match. Default 0."),
|
|
4379
|
+
max_matches: import_zod14.z.number().int().positive().optional().describe("Maximum number of matches to return.")
|
|
3553
4380
|
}),
|
|
3554
4381
|
execute: async (input) => {
|
|
3555
4382
|
if (!store.grep) {
|
|
@@ -3570,16 +4397,16 @@ var createToolOutputCacheGrepTool = (store) => (0, import_core14.defineTool)({
|
|
|
3570
4397
|
});
|
|
3571
4398
|
|
|
3572
4399
|
// src/tools/write.ts
|
|
3573
|
-
var
|
|
3574
|
-
var
|
|
3575
|
-
var
|
|
3576
|
-
var
|
|
3577
|
-
var createWriteTool = (sandboxKey) => (0,
|
|
4400
|
+
var import_node_fs13 = require("fs");
|
|
4401
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
4402
|
+
var import_core16 = require("@codelia/core");
|
|
4403
|
+
var import_zod15 = require("zod");
|
|
4404
|
+
var createWriteTool = (sandboxKey) => (0, import_core16.defineTool)({
|
|
3578
4405
|
name: "write",
|
|
3579
4406
|
description: "Write text to a file, creating parent directories if needed.",
|
|
3580
|
-
input:
|
|
3581
|
-
file_path:
|
|
3582
|
-
content:
|
|
4407
|
+
input: import_zod15.z.object({
|
|
4408
|
+
file_path: import_zod15.z.string().describe("File path under the sandbox root."),
|
|
4409
|
+
content: import_zod15.z.string().describe("UTF-8 text content to write.")
|
|
3583
4410
|
}),
|
|
3584
4411
|
execute: async (input, ctx) => {
|
|
3585
4412
|
let resolved;
|
|
@@ -3590,8 +4417,8 @@ var createWriteTool = (sandboxKey) => (0, import_core15.defineTool)({
|
|
|
3590
4417
|
return `Security error: ${String(error)}`;
|
|
3591
4418
|
}
|
|
3592
4419
|
try {
|
|
3593
|
-
await
|
|
3594
|
-
await
|
|
4420
|
+
await import_node_fs13.promises.mkdir(import_node_path12.default.dirname(resolved), { recursive: true });
|
|
4421
|
+
await import_node_fs13.promises.writeFile(resolved, input.content, "utf8");
|
|
3595
4422
|
return `Wrote ${input.content.length} bytes to ${input.file_path}`;
|
|
3596
4423
|
} catch (error) {
|
|
3597
4424
|
return `Error writing file: ${String(error)}`;
|
|
@@ -3616,9 +4443,106 @@ var createTools = (sandboxKey, agentsResolverKey, skillsResolverKey, options = {
|
|
|
3616
4443
|
] : [],
|
|
3617
4444
|
createTodoReadTool(sandboxKey),
|
|
3618
4445
|
createTodoWriteTool(sandboxKey),
|
|
4446
|
+
createLaneCreateTool(sandboxKey),
|
|
4447
|
+
createLaneListTool(sandboxKey),
|
|
4448
|
+
createLaneStatusTool(sandboxKey),
|
|
4449
|
+
createLaneCloseTool(sandboxKey),
|
|
4450
|
+
createLaneGcTool(sandboxKey),
|
|
3619
4451
|
createDoneTool()
|
|
3620
4452
|
];
|
|
3621
4453
|
|
|
4454
|
+
// src/utils/language.ts
|
|
4455
|
+
var normalizeLanguage = (value) => {
|
|
4456
|
+
const token = value.trim().toLowerCase();
|
|
4457
|
+
if (!token) return "";
|
|
4458
|
+
switch (token) {
|
|
4459
|
+
case "yml":
|
|
4460
|
+
return "yaml";
|
|
4461
|
+
case "mjs":
|
|
4462
|
+
case "cjs":
|
|
4463
|
+
case "node":
|
|
4464
|
+
return "javascript";
|
|
4465
|
+
case "mts":
|
|
4466
|
+
case "cts":
|
|
4467
|
+
case "ts-node":
|
|
4468
|
+
case "deno":
|
|
4469
|
+
return "typescript";
|
|
4470
|
+
case "py":
|
|
4471
|
+
case "python3":
|
|
4472
|
+
return "python";
|
|
4473
|
+
case "rb":
|
|
4474
|
+
return "ruby";
|
|
4475
|
+
case "zsh":
|
|
4476
|
+
case "sh":
|
|
4477
|
+
return "bash";
|
|
4478
|
+
case "ps1":
|
|
4479
|
+
return "powershell";
|
|
4480
|
+
default:
|
|
4481
|
+
return token;
|
|
4482
|
+
}
|
|
4483
|
+
};
|
|
4484
|
+
var languageFromFilePath = (filePath) => {
|
|
4485
|
+
if (!filePath) return void 0;
|
|
4486
|
+
const path16 = filePath.trim().replace(/^["']|["']$/g, "");
|
|
4487
|
+
if (!path16 || path16 === "/dev/null") return void 0;
|
|
4488
|
+
const normalizedPath = path16.replace(/^a\//, "").replace(/^b\//, "");
|
|
4489
|
+
const lastSlash = Math.max(
|
|
4490
|
+
normalizedPath.lastIndexOf("/"),
|
|
4491
|
+
normalizedPath.lastIndexOf("\\")
|
|
4492
|
+
);
|
|
4493
|
+
const basename = lastSlash >= 0 ? normalizedPath.slice(lastSlash + 1) : normalizedPath;
|
|
4494
|
+
if (!basename) return void 0;
|
|
4495
|
+
const dotIndex = basename.lastIndexOf(".");
|
|
4496
|
+
if (dotIndex > 0 && dotIndex < basename.length - 1) {
|
|
4497
|
+
return normalizeLanguage(basename.slice(dotIndex + 1));
|
|
4498
|
+
}
|
|
4499
|
+
if (basename === "Dockerfile") return "dockerfile";
|
|
4500
|
+
if (/^Makefile(\..+)?$/i.test(basename)) return "makefile";
|
|
4501
|
+
return void 0;
|
|
4502
|
+
};
|
|
4503
|
+
var languageFromDiffHeaders = (diff) => {
|
|
4504
|
+
if (!diff) return void 0;
|
|
4505
|
+
for (const line of diff.split("\n")) {
|
|
4506
|
+
if (!line.startsWith("--- ") && !line.startsWith("+++ ")) continue;
|
|
4507
|
+
const rawPath = line.slice(4).trim().split(/\s+/)[0] ?? "";
|
|
4508
|
+
const language = languageFromFilePath(rawPath);
|
|
4509
|
+
if (language) return language;
|
|
4510
|
+
}
|
|
4511
|
+
return void 0;
|
|
4512
|
+
};
|
|
4513
|
+
var languageFromShebang = (content) => {
|
|
4514
|
+
if (!content) return void 0;
|
|
4515
|
+
const firstLine = content.split("\n", 1)[0]?.trim() ?? "";
|
|
4516
|
+
if (!firstLine.startsWith("#!")) return void 0;
|
|
4517
|
+
const body = firstLine.slice(2).trim();
|
|
4518
|
+
if (!body) return void 0;
|
|
4519
|
+
const parts = body.split(/\s+/).filter(Boolean);
|
|
4520
|
+
if (!parts.length) return void 0;
|
|
4521
|
+
let command = parts[0];
|
|
4522
|
+
if (command.endsWith("/env")) {
|
|
4523
|
+
let index = 1;
|
|
4524
|
+
if (parts[index] === "-S") index += 1;
|
|
4525
|
+
while (index < parts.length && parts[index].includes("=")) {
|
|
4526
|
+
index += 1;
|
|
4527
|
+
}
|
|
4528
|
+
command = parts[index] ?? "";
|
|
4529
|
+
}
|
|
4530
|
+
if (!command) return void 0;
|
|
4531
|
+
const last = command.split("/").pop() ?? command;
|
|
4532
|
+
const simplified = last.replace(/[0-9.]+$/g, "");
|
|
4533
|
+
const normalized = normalizeLanguage(simplified);
|
|
4534
|
+
return normalized || void 0;
|
|
4535
|
+
};
|
|
4536
|
+
var resolvePreviewLanguageHint = (input) => {
|
|
4537
|
+
const explicit = input.language ? normalizeLanguage(input.language) : "";
|
|
4538
|
+
if (explicit) return explicit;
|
|
4539
|
+
const shebang = languageFromShebang(input.content);
|
|
4540
|
+
if (shebang) return shebang;
|
|
4541
|
+
const fromDiff = languageFromDiffHeaders(input.diff);
|
|
4542
|
+
if (fromDiff) return fromDiff;
|
|
4543
|
+
return languageFromFilePath(input.filePath);
|
|
4544
|
+
};
|
|
4545
|
+
|
|
3622
4546
|
// src/agent-factory.ts
|
|
3623
4547
|
var requireApiKeyAuth = (provider, auth) => {
|
|
3624
4548
|
if (auth.method !== "api_key") {
|
|
@@ -3631,6 +4555,7 @@ var envTruthy = (value) => {
|
|
|
3631
4555
|
const normalized = value.trim().toLowerCase();
|
|
3632
4556
|
return normalized === "1" || normalized === "true";
|
|
3633
4557
|
};
|
|
4558
|
+
var OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
|
|
3634
4559
|
var buildOpenAiClientOptions = (authResolver, auth) => {
|
|
3635
4560
|
if (auth.method === "api_key") {
|
|
3636
4561
|
return { apiKey: auth.api_key };
|
|
@@ -3674,6 +4599,22 @@ var buildOpenAiClientOptions = (authResolver, auth) => {
|
|
|
3674
4599
|
fetch: fetchWithAccount
|
|
3675
4600
|
};
|
|
3676
4601
|
};
|
|
4602
|
+
var buildOpenRouterClientOptions = (auth) => {
|
|
4603
|
+
const headers = {};
|
|
4604
|
+
const referer = readEnvValue("OPENROUTER_HTTP_REFERER");
|
|
4605
|
+
if (referer) {
|
|
4606
|
+
headers["HTTP-Referer"] = referer;
|
|
4607
|
+
}
|
|
4608
|
+
const title = readEnvValue("OPENROUTER_X_TITLE");
|
|
4609
|
+
if (title) {
|
|
4610
|
+
headers["X-Title"] = title;
|
|
4611
|
+
}
|
|
4612
|
+
return {
|
|
4613
|
+
apiKey: requireApiKeyAuth("OpenRouter", auth),
|
|
4614
|
+
baseURL: OPENROUTER_BASE_URL,
|
|
4615
|
+
...Object.keys(headers).length ? { defaultHeaders: headers } : {}
|
|
4616
|
+
};
|
|
4617
|
+
};
|
|
3677
4618
|
var sleep = (ms) => new Promise((resolve) => {
|
|
3678
4619
|
setTimeout(resolve, ms);
|
|
3679
4620
|
});
|
|
@@ -3687,6 +4628,41 @@ var waitForUiConfirmSupport = async (state, timeoutMs = 5e3) => {
|
|
|
3687
4628
|
}
|
|
3688
4629
|
return !!state.uiCapabilities?.supports_confirm;
|
|
3689
4630
|
};
|
|
4631
|
+
var MAX_CONFIRM_PREVIEW_LINES = 120;
|
|
4632
|
+
var splitLines = (value) => value.split("\n").map((line) => line.replace(/\r$/, ""));
|
|
4633
|
+
var buildBoundedDiffPreview = (diff, maxLines = MAX_CONFIRM_PREVIEW_LINES) => {
|
|
4634
|
+
if (!diff.trim()) return { diff: null, truncated: false };
|
|
4635
|
+
const lines = splitLines(diff);
|
|
4636
|
+
if (!lines.length) return { diff: null, truncated: false };
|
|
4637
|
+
if (lines.length <= maxLines) {
|
|
4638
|
+
return { diff: lines.join("\n"), truncated: false };
|
|
4639
|
+
}
|
|
4640
|
+
return {
|
|
4641
|
+
diff: lines.slice(0, maxLines).join("\n"),
|
|
4642
|
+
truncated: true
|
|
4643
|
+
};
|
|
4644
|
+
};
|
|
4645
|
+
var parseToolArgsObject = (rawArgs) => {
|
|
4646
|
+
try {
|
|
4647
|
+
const parsed = JSON.parse(rawArgs);
|
|
4648
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
4649
|
+
return null;
|
|
4650
|
+
}
|
|
4651
|
+
return parsed;
|
|
4652
|
+
} catch {
|
|
4653
|
+
return null;
|
|
4654
|
+
}
|
|
4655
|
+
};
|
|
4656
|
+
var unwrapToolJsonObject = (result) => {
|
|
4657
|
+
if (!result || typeof result !== "object") return null;
|
|
4658
|
+
const typed = result;
|
|
4659
|
+
if (typed.type !== "json") return null;
|
|
4660
|
+
const value = typed.value;
|
|
4661
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
4662
|
+
return null;
|
|
4663
|
+
}
|
|
4664
|
+
return value;
|
|
4665
|
+
};
|
|
3690
4666
|
var requestMcpOAuthTokens = async (state, serverId, oauth2, errorMessage) => {
|
|
3691
4667
|
const canConfirm = state.uiCapabilities?.supports_confirm ? true : await waitForUiConfirmSupport(state);
|
|
3692
4668
|
if (!canConfirm) {
|
|
@@ -3815,7 +4791,7 @@ var createAgentFactory = (state, options = {}) => {
|
|
|
3815
4791
|
const sandboxKey = createSandboxKey(ctx);
|
|
3816
4792
|
const agentsResolverKey = createAgentsResolverKey(agentsResolver);
|
|
3817
4793
|
const skillsResolverKey = createSkillsResolverKey(skillsResolver);
|
|
3818
|
-
const toolOutputCacheStore = new
|
|
4794
|
+
const toolOutputCacheStore = new import_storage5.ToolOutputCacheStoreImpl();
|
|
3819
4795
|
const localTools = createTools(
|
|
3820
4796
|
sandboxKey,
|
|
3821
4797
|
agentsResolverKey,
|
|
@@ -3824,6 +4800,9 @@ var createAgentFactory = (state, options = {}) => {
|
|
|
3824
4800
|
toolOutputCacheStore
|
|
3825
4801
|
}
|
|
3826
4802
|
);
|
|
4803
|
+
const editTool = localTools.find(
|
|
4804
|
+
(tool) => tool.definition.name === "edit"
|
|
4805
|
+
);
|
|
3827
4806
|
let mcpTools = [];
|
|
3828
4807
|
if (options.mcpManager) {
|
|
3829
4808
|
try {
|
|
@@ -3845,6 +4824,7 @@ var createAgentFactory = (state, options = {}) => {
|
|
|
3845
4824
|
}
|
|
3846
4825
|
}
|
|
3847
4826
|
const tools = [...localTools, ...mcpTools];
|
|
4827
|
+
state.tools = tools;
|
|
3848
4828
|
state.toolDefinitions = tools.map((tool) => tool.definition);
|
|
3849
4829
|
const baseSystemPrompt = await loadSystemPrompt(ctx.workingDir);
|
|
3850
4830
|
const withAgentsContext = appendInitialAgentsContext(
|
|
@@ -3886,7 +4866,7 @@ var createAgentFactory = (state, options = {}) => {
|
|
|
3886
4866
|
case "openai": {
|
|
3887
4867
|
const reasoningEffort = resolveReasoningEffort(modelConfig.reasoning);
|
|
3888
4868
|
const textVerbosity = resolveTextVerbosity(modelConfig.verbosity);
|
|
3889
|
-
llm = new
|
|
4869
|
+
llm = new import_core17.ChatOpenAI({
|
|
3890
4870
|
clientOptions: buildOpenAiClientOptions(authResolver, providerAuth),
|
|
3891
4871
|
...modelConfig.name ? { model: modelConfig.name } : {},
|
|
3892
4872
|
...reasoningEffort ? { reasoningEffort } : {},
|
|
@@ -3894,8 +4874,19 @@ var createAgentFactory = (state, options = {}) => {
|
|
|
3894
4874
|
});
|
|
3895
4875
|
break;
|
|
3896
4876
|
}
|
|
4877
|
+
case "openrouter": {
|
|
4878
|
+
const reasoningEffort = resolveReasoningEffort(modelConfig.reasoning);
|
|
4879
|
+
const textVerbosity = resolveTextVerbosity(modelConfig.verbosity);
|
|
4880
|
+
llm = new import_core17.ChatOpenAI({
|
|
4881
|
+
clientOptions: buildOpenRouterClientOptions(providerAuth),
|
|
4882
|
+
...modelConfig.name ? { model: modelConfig.name } : {},
|
|
4883
|
+
...reasoningEffort ? { reasoningEffort } : {},
|
|
4884
|
+
...textVerbosity ? { textVerbosity } : {}
|
|
4885
|
+
});
|
|
4886
|
+
break;
|
|
4887
|
+
}
|
|
3897
4888
|
case "anthropic": {
|
|
3898
|
-
llm = new
|
|
4889
|
+
llm = new import_core17.ChatAnthropic({
|
|
3899
4890
|
clientOptions: {
|
|
3900
4891
|
apiKey: requireApiKeyAuth("Anthropic", providerAuth)
|
|
3901
4892
|
},
|
|
@@ -3906,14 +4897,16 @@ var createAgentFactory = (state, options = {}) => {
|
|
|
3906
4897
|
default:
|
|
3907
4898
|
throw new Error(`Unsupported model.provider: ${provider}`);
|
|
3908
4899
|
}
|
|
3909
|
-
const modelRegistry = await buildModelRegistry(llm
|
|
3910
|
-
|
|
4900
|
+
const modelRegistry = await buildModelRegistry(llm, {
|
|
4901
|
+
strict: provider !== "openrouter"
|
|
4902
|
+
});
|
|
4903
|
+
const agent = new import_core17.Agent({
|
|
3911
4904
|
llm,
|
|
3912
4905
|
tools,
|
|
3913
4906
|
systemPrompt,
|
|
3914
|
-
modelRegistry: modelRegistry ??
|
|
4907
|
+
modelRegistry: modelRegistry ?? import_core17.DEFAULT_MODEL_REGISTRY,
|
|
3915
4908
|
services: { toolOutputCacheStore },
|
|
3916
|
-
canExecuteTool: async (call, rawArgs) => {
|
|
4909
|
+
canExecuteTool: async (call, rawArgs, toolCtx) => {
|
|
3917
4910
|
const decision = permissionService.evaluate(
|
|
3918
4911
|
call.function.name,
|
|
3919
4912
|
rawArgs
|
|
@@ -3935,12 +4928,129 @@ var createAgentFactory = (state, options = {}) => {
|
|
|
3935
4928
|
};
|
|
3936
4929
|
}
|
|
3937
4930
|
const runId = state.activeRunId ?? void 0;
|
|
4931
|
+
(0, import_logger.debugLog)(
|
|
4932
|
+
`permission.request tool=${call.function.name} args=${rawArgs}`
|
|
4933
|
+
);
|
|
3938
4934
|
const prompt = permissionService.getConfirmPrompt(
|
|
3939
4935
|
call.function.name,
|
|
3940
4936
|
rawArgs
|
|
3941
4937
|
);
|
|
4938
|
+
let previewDiff = null;
|
|
4939
|
+
let previewSummary = null;
|
|
4940
|
+
let previewTruncated = false;
|
|
4941
|
+
let previewFilePath = null;
|
|
4942
|
+
let previewLanguage = null;
|
|
4943
|
+
if (call.function.name === "write") {
|
|
4944
|
+
const parsed = parseToolArgsObject(rawArgs);
|
|
4945
|
+
const filePath = typeof parsed?.file_path === "string" ? parsed.file_path : "";
|
|
4946
|
+
const content = typeof parsed?.content === "string" ? parsed.content : "";
|
|
4947
|
+
const language = typeof parsed?.language === "string" ? parsed.language : "";
|
|
4948
|
+
if (filePath) {
|
|
4949
|
+
previewFilePath = filePath;
|
|
4950
|
+
previewLanguage = resolvePreviewLanguageHint({
|
|
4951
|
+
language,
|
|
4952
|
+
filePath,
|
|
4953
|
+
content
|
|
4954
|
+
}) ?? previewLanguage;
|
|
4955
|
+
try {
|
|
4956
|
+
const sandbox = await getSandboxContext(toolCtx, sandboxKey);
|
|
4957
|
+
const resolved = sandbox.resolvePath(filePath);
|
|
4958
|
+
let before = "";
|
|
4959
|
+
try {
|
|
4960
|
+
const stat = await import_node_fs14.promises.stat(resolved);
|
|
4961
|
+
if (!stat.isDirectory()) {
|
|
4962
|
+
before = await import_node_fs14.promises.readFile(resolved, "utf8");
|
|
4963
|
+
}
|
|
4964
|
+
} catch {
|
|
4965
|
+
before = "";
|
|
4966
|
+
}
|
|
4967
|
+
const preview = buildBoundedDiffPreview(
|
|
4968
|
+
createUnifiedDiff(filePath, before, content)
|
|
4969
|
+
);
|
|
4970
|
+
previewDiff = preview.diff;
|
|
4971
|
+
previewTruncated = preview.truncated;
|
|
4972
|
+
} catch {
|
|
4973
|
+
previewDiff = null;
|
|
4974
|
+
previewSummary = null;
|
|
4975
|
+
previewTruncated = false;
|
|
4976
|
+
}
|
|
4977
|
+
}
|
|
4978
|
+
} else if (call.function.name === "edit" && editTool) {
|
|
4979
|
+
const parsed = parseToolArgsObject(rawArgs);
|
|
4980
|
+
if (parsed) {
|
|
4981
|
+
const filePath = typeof parsed.file_path === "string" ? parsed.file_path : "";
|
|
4982
|
+
const language = typeof parsed.language === "string" ? parsed.language : "";
|
|
4983
|
+
if (filePath) {
|
|
4984
|
+
previewFilePath = filePath;
|
|
4985
|
+
}
|
|
4986
|
+
previewLanguage = resolvePreviewLanguageHint({
|
|
4987
|
+
language,
|
|
4988
|
+
filePath
|
|
4989
|
+
}) ?? previewLanguage;
|
|
4990
|
+
const dryRunInput = { ...parsed, dry_run: true };
|
|
4991
|
+
try {
|
|
4992
|
+
const result = await editTool.executeRaw(
|
|
4993
|
+
JSON.stringify(dryRunInput),
|
|
4994
|
+
toolCtx
|
|
4995
|
+
);
|
|
4996
|
+
if (result && typeof result === "object") {
|
|
4997
|
+
const obj = unwrapToolJsonObject(result);
|
|
4998
|
+
if (!obj) {
|
|
4999
|
+
previewSummary = "Preview unavailable: unexpected dry-run output";
|
|
5000
|
+
} else {
|
|
5001
|
+
const resultFilePath = typeof obj.file_path === "string" ? obj.file_path : "";
|
|
5002
|
+
const resultLanguage = typeof obj.language === "string" ? obj.language : "";
|
|
5003
|
+
if (!previewFilePath && resultFilePath) {
|
|
5004
|
+
previewFilePath = resultFilePath;
|
|
5005
|
+
}
|
|
5006
|
+
const diff = typeof obj.diff === "string" ? obj.diff : "";
|
|
5007
|
+
const summary = typeof obj.summary === "string" ? obj.summary : "";
|
|
5008
|
+
previewLanguage = resolvePreviewLanguageHint({
|
|
5009
|
+
language: resultLanguage || previewLanguage,
|
|
5010
|
+
filePath: previewFilePath || resultFilePath,
|
|
5011
|
+
diff
|
|
5012
|
+
}) ?? previewLanguage;
|
|
5013
|
+
const preview = buildBoundedDiffPreview(diff);
|
|
5014
|
+
previewDiff = preview.diff;
|
|
5015
|
+
previewTruncated = preview.truncated;
|
|
5016
|
+
if (!previewDiff) {
|
|
5017
|
+
previewSummary = summary || "Preview: no diff content";
|
|
5018
|
+
}
|
|
5019
|
+
}
|
|
5020
|
+
}
|
|
5021
|
+
} catch {
|
|
5022
|
+
previewSummary = "Preview unavailable: dry-run failed";
|
|
5023
|
+
}
|
|
5024
|
+
}
|
|
5025
|
+
}
|
|
3942
5026
|
if (runId) {
|
|
3943
|
-
|
|
5027
|
+
if (previewDiff || previewSummary) {
|
|
5028
|
+
previewLanguage = resolvePreviewLanguageHint({
|
|
5029
|
+
language: previewLanguage,
|
|
5030
|
+
filePath: previewFilePath,
|
|
5031
|
+
diff: previewDiff
|
|
5032
|
+
}) ?? previewLanguage;
|
|
5033
|
+
await sendAgentEventAsync(state, runId, {
|
|
5034
|
+
type: "permission.preview",
|
|
5035
|
+
tool: call.function.name,
|
|
5036
|
+
tool_call_id: call.id,
|
|
5037
|
+
...previewFilePath ? { file_path: previewFilePath } : {},
|
|
5038
|
+
...previewLanguage ? { language: previewLanguage } : {},
|
|
5039
|
+
...previewDiff ? { diff: previewDiff } : {},
|
|
5040
|
+
...previewSummary ? { summary: previewSummary } : {},
|
|
5041
|
+
...previewTruncated ? { truncated: true } : {}
|
|
5042
|
+
});
|
|
5043
|
+
}
|
|
5044
|
+
await sendAgentEventAsync(state, runId, {
|
|
5045
|
+
type: "permission.ready",
|
|
5046
|
+
tool: call.function.name,
|
|
5047
|
+
tool_call_id: call.id
|
|
5048
|
+
});
|
|
5049
|
+
await sendRunStatusAsync(
|
|
5050
|
+
runId,
|
|
5051
|
+
"awaiting_ui",
|
|
5052
|
+
"waiting for confirmation"
|
|
5053
|
+
);
|
|
3944
5054
|
}
|
|
3945
5055
|
const confirmResult = await requestUiConfirm(state, {
|
|
3946
5056
|
run_id: runId,
|
|
@@ -3994,7 +5104,7 @@ var createAgentFactory = (state, options = {}) => {
|
|
|
3994
5104
|
};
|
|
3995
5105
|
|
|
3996
5106
|
// src/mcp/auth-store.ts
|
|
3997
|
-
var
|
|
5107
|
+
var import_storage6 = require("@codelia/storage");
|
|
3998
5108
|
|
|
3999
5109
|
// src/mcp/manager.ts
|
|
4000
5110
|
var import_protocol = require("@codelia/protocol");
|
|
@@ -4277,7 +5387,7 @@ var HttpMcpClient = class {
|
|
|
4277
5387
|
};
|
|
4278
5388
|
|
|
4279
5389
|
// src/mcp/stdio-client.ts
|
|
4280
|
-
var
|
|
5390
|
+
var import_node_child_process4 = require("child_process");
|
|
4281
5391
|
var import_node_readline = require("readline");
|
|
4282
5392
|
var StdioMcpClient = class {
|
|
4283
5393
|
constructor(options) {
|
|
@@ -4286,7 +5396,7 @@ var StdioMcpClient = class {
|
|
|
4286
5396
|
...process.env,
|
|
4287
5397
|
...options.env ?? {}
|
|
4288
5398
|
};
|
|
4289
|
-
this.child = (0,
|
|
5399
|
+
this.child = (0, import_node_child_process4.spawn)(options.command, options.args, {
|
|
4290
5400
|
cwd: options.cwd,
|
|
4291
5401
|
env,
|
|
4292
5402
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -4496,8 +5606,8 @@ var parseAuthorizationServerMetadata = (value) => {
|
|
|
4496
5606
|
var buildAuthorizationServerMetadataUrl = (issuer) => {
|
|
4497
5607
|
try {
|
|
4498
5608
|
const parsed = new URL(issuer);
|
|
4499
|
-
const
|
|
4500
|
-
const basePath =
|
|
5609
|
+
const path16 = parsed.pathname === "/" ? "" : parsed.pathname;
|
|
5610
|
+
const basePath = path16.startsWith("/") ? path16 : `/${path16}`;
|
|
4501
5611
|
const metadataPath = `/.well-known/oauth-authorization-server${basePath}`;
|
|
4502
5612
|
return new URL(metadataPath, `${parsed.origin}/`).toString();
|
|
4503
5613
|
} catch {
|
|
@@ -4594,24 +5704,24 @@ var fetchDiscoveredOAuthConfig = async (serverUrl) => {
|
|
|
4594
5704
|
};
|
|
4595
5705
|
|
|
4596
5706
|
// src/mcp/tooling.ts
|
|
4597
|
-
var
|
|
5707
|
+
var import_node_crypto5 = __toESM(require("crypto"), 1);
|
|
4598
5708
|
var MAX_SCHEMA_SIZE_BYTES = 64 * 1024;
|
|
4599
5709
|
var MAX_TOOL_OUTPUT_CHARS = 1e5;
|
|
4600
5710
|
var MAX_TOOLS_LIST_PAGES = 100;
|
|
4601
5711
|
var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4602
5712
|
var isMcpToolDescriptor = (value) => isRecord4(value) && typeof value.name === "string";
|
|
4603
|
-
var
|
|
5713
|
+
var toSlug2 = (value) => {
|
|
4604
5714
|
const slug = value.toLowerCase().replace(/[^a-z0-9_]+/g, "_").replace(/^_+|_+$/g, "");
|
|
4605
5715
|
return slug || "tool";
|
|
4606
5716
|
};
|
|
4607
|
-
var hash8 = (value) =>
|
|
5717
|
+
var hash8 = (value) => import_node_crypto5.default.createHash("sha256").update(value).digest("hex").slice(0, 8);
|
|
4608
5718
|
var clampToolName = (value) => {
|
|
4609
5719
|
if (value.length <= 63) return value;
|
|
4610
5720
|
return value.slice(0, 63);
|
|
4611
5721
|
};
|
|
4612
5722
|
var buildToolName = (serverId, toolName) => {
|
|
4613
|
-
const serverSlug =
|
|
4614
|
-
const toolSlug =
|
|
5723
|
+
const serverSlug = toSlug2(serverId);
|
|
5724
|
+
const toolSlug = toSlug2(toolName);
|
|
4615
5725
|
const fingerprint = hash8(`${serverId}:${toolName}`);
|
|
4616
5726
|
const maxPrefixLength = 63 - (fingerprint.length + 1);
|
|
4617
5727
|
const prefix = `mcp_${serverSlug}_${toolSlug}`.slice(0, maxPrefixLength);
|
|
@@ -4769,7 +5879,7 @@ var createMcpToolAdapter = (params) => {
|
|
|
4769
5879
|
|
|
4770
5880
|
// src/mcp/manager.ts
|
|
4771
5881
|
var OAUTH_TOKEN_EXPIRY_SKEW_MS = 6e4;
|
|
4772
|
-
var
|
|
5882
|
+
var nowIso3 = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
4773
5883
|
var describeError2 = (error) => error instanceof Error ? error.message : String(error);
|
|
4774
5884
|
var toListServer = (id, runtime) => ({
|
|
4775
5885
|
id,
|
|
@@ -4791,7 +5901,7 @@ var McpManager = class {
|
|
|
4791
5901
|
this.options = options;
|
|
4792
5902
|
}
|
|
4793
5903
|
servers = /* @__PURE__ */ new Map();
|
|
4794
|
-
authStore = new
|
|
5904
|
+
authStore = new import_storage6.McpAuthStore();
|
|
4795
5905
|
startPromise = null;
|
|
4796
5906
|
authFile = { version: 1, servers: {} };
|
|
4797
5907
|
discoveredOAuth = /* @__PURE__ */ new Map();
|
|
@@ -4901,7 +6011,7 @@ var McpManager = class {
|
|
|
4901
6011
|
runtime.tools_count = runtime.toolAdapters.length;
|
|
4902
6012
|
runtime.state = "ready";
|
|
4903
6013
|
runtime.last_error = void 0;
|
|
4904
|
-
runtime.last_connected_at =
|
|
6014
|
+
runtime.last_connected_at = nowIso3();
|
|
4905
6015
|
connectOptions?.onStatus?.(
|
|
4906
6016
|
`MCP server ready: ${serverId} (${runtime.tools_count ?? 0} tools)`
|
|
4907
6017
|
);
|
|
@@ -5154,7 +6264,9 @@ var McpManager = class {
|
|
|
5154
6264
|
};
|
|
5155
6265
|
|
|
5156
6266
|
// src/rpc/handlers.ts
|
|
5157
|
-
var
|
|
6267
|
+
var import_config_loader3 = require("@codelia/config-loader");
|
|
6268
|
+
var import_protocol8 = require("@codelia/protocol");
|
|
6269
|
+
var import_storage8 = require("@codelia/storage");
|
|
5158
6270
|
|
|
5159
6271
|
// src/constants.ts
|
|
5160
6272
|
var SERVER_NAME = "codelia-runtime";
|
|
@@ -5162,6 +6274,7 @@ var SERVER_VERSION = "0.1.0";
|
|
|
5162
6274
|
var PROTOCOL_VERSION = "0";
|
|
5163
6275
|
|
|
5164
6276
|
// src/rpc/context.ts
|
|
6277
|
+
var import_protocol2 = require("@codelia/protocol");
|
|
5165
6278
|
var createContextHandlers = ({
|
|
5166
6279
|
state,
|
|
5167
6280
|
log: log2
|
|
@@ -5251,7 +6364,7 @@ var createContextHandlers = ({
|
|
|
5251
6364
|
sendResult(id, result);
|
|
5252
6365
|
} catch (error) {
|
|
5253
6366
|
sendError(id, {
|
|
5254
|
-
code:
|
|
6367
|
+
code: import_protocol2.RPC_ERROR_CODE.RUNTIME_INTERNAL,
|
|
5255
6368
|
message: `context.inspect failed: ${String(error)}`
|
|
5256
6369
|
});
|
|
5257
6370
|
}
|
|
@@ -5260,14 +6373,50 @@ var createContextHandlers = ({
|
|
|
5260
6373
|
};
|
|
5261
6374
|
|
|
5262
6375
|
// src/rpc/history.ts
|
|
5263
|
-
var
|
|
5264
|
-
var
|
|
6376
|
+
var import_node_fs15 = require("fs");
|
|
6377
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
5265
6378
|
var import_node_readline2 = require("readline");
|
|
5266
|
-
var
|
|
6379
|
+
var import_protocol3 = require("@codelia/protocol");
|
|
6380
|
+
var import_storage7 = require("@codelia/storage");
|
|
5267
6381
|
var DEFAULT_HISTORY_RUNS = 20;
|
|
5268
6382
|
var DEFAULT_HISTORY_EVENTS = 1500;
|
|
6383
|
+
var runStartInputToHiddenMessage = (input) => {
|
|
6384
|
+
if (!input) {
|
|
6385
|
+
return "";
|
|
6386
|
+
}
|
|
6387
|
+
if (input.type === "text") {
|
|
6388
|
+
return input.text;
|
|
6389
|
+
}
|
|
6390
|
+
if (!Array.isArray(input.parts)) {
|
|
6391
|
+
return "";
|
|
6392
|
+
}
|
|
6393
|
+
let message = "";
|
|
6394
|
+
for (const part of input.parts) {
|
|
6395
|
+
if (!part || typeof part !== "object") {
|
|
6396
|
+
continue;
|
|
6397
|
+
}
|
|
6398
|
+
if (part.type === "text") {
|
|
6399
|
+
message += part.text;
|
|
6400
|
+
continue;
|
|
6401
|
+
}
|
|
6402
|
+
if (part.type === "image_url") {
|
|
6403
|
+
message += "[image]";
|
|
6404
|
+
}
|
|
6405
|
+
}
|
|
6406
|
+
return message;
|
|
6407
|
+
};
|
|
6408
|
+
var parseRunHeader = (headerLine) => {
|
|
6409
|
+
try {
|
|
6410
|
+
const header = JSON.parse(headerLine);
|
|
6411
|
+
if (!header || typeof header !== "object") return null;
|
|
6412
|
+
if (header.type !== "header") return null;
|
|
6413
|
+
return header;
|
|
6414
|
+
} catch {
|
|
6415
|
+
return null;
|
|
6416
|
+
}
|
|
6417
|
+
};
|
|
5269
6418
|
var readFirstLine = async (filePath) => {
|
|
5270
|
-
const stream = (0,
|
|
6419
|
+
const stream = (0, import_node_fs15.createReadStream)(filePath, { encoding: "utf8" });
|
|
5271
6420
|
const reader = (0, import_node_readline2.createInterface)({
|
|
5272
6421
|
input: stream,
|
|
5273
6422
|
crlfDelay: Number.POSITIVE_INFINITY
|
|
@@ -5282,12 +6431,12 @@ var readFirstLine = async (filePath) => {
|
|
|
5282
6431
|
stream.destroy();
|
|
5283
6432
|
}
|
|
5284
6433
|
};
|
|
5285
|
-
var
|
|
5286
|
-
const paths = (0,
|
|
6434
|
+
var collectRunCandidates = async (log2) => {
|
|
6435
|
+
const paths = (0, import_storage7.resolveStoragePaths)();
|
|
5287
6436
|
const sessionsDir = paths.sessionsDir;
|
|
5288
6437
|
let yearEntries;
|
|
5289
6438
|
try {
|
|
5290
|
-
yearEntries = await
|
|
6439
|
+
yearEntries = await import_node_fs15.promises.readdir(sessionsDir, {
|
|
5291
6440
|
withFileTypes: true,
|
|
5292
6441
|
encoding: "utf8"
|
|
5293
6442
|
});
|
|
@@ -5299,10 +6448,10 @@ var collectSessionRuns = async (sessionId, maxRuns, log2) => {
|
|
|
5299
6448
|
for (const yearEntry of yearEntries) {
|
|
5300
6449
|
if (!yearEntry.isDirectory()) continue;
|
|
5301
6450
|
if (!/^\d{4}$/.test(yearEntry.name)) continue;
|
|
5302
|
-
const yearPath =
|
|
6451
|
+
const yearPath = import_node_path13.default.join(sessionsDir, yearEntry.name);
|
|
5303
6452
|
let monthEntries;
|
|
5304
6453
|
try {
|
|
5305
|
-
monthEntries = await
|
|
6454
|
+
monthEntries = await import_node_fs15.promises.readdir(yearPath, {
|
|
5306
6455
|
withFileTypes: true,
|
|
5307
6456
|
encoding: "utf8"
|
|
5308
6457
|
});
|
|
@@ -5312,10 +6461,10 @@ var collectSessionRuns = async (sessionId, maxRuns, log2) => {
|
|
|
5312
6461
|
for (const monthEntry of monthEntries) {
|
|
5313
6462
|
if (!monthEntry.isDirectory()) continue;
|
|
5314
6463
|
if (!/^\d{2}$/.test(monthEntry.name)) continue;
|
|
5315
|
-
const monthPath =
|
|
6464
|
+
const monthPath = import_node_path13.default.join(yearPath, monthEntry.name);
|
|
5316
6465
|
let dayEntries;
|
|
5317
6466
|
try {
|
|
5318
|
-
dayEntries = await
|
|
6467
|
+
dayEntries = await import_node_fs15.promises.readdir(monthPath, {
|
|
5319
6468
|
withFileTypes: true,
|
|
5320
6469
|
encoding: "utf8"
|
|
5321
6470
|
});
|
|
@@ -5325,10 +6474,10 @@ var collectSessionRuns = async (sessionId, maxRuns, log2) => {
|
|
|
5325
6474
|
for (const dayEntry of dayEntries) {
|
|
5326
6475
|
if (!dayEntry.isDirectory()) continue;
|
|
5327
6476
|
if (!/^\d{2}$/.test(dayEntry.name)) continue;
|
|
5328
|
-
const dayPath =
|
|
6477
|
+
const dayPath = import_node_path13.default.join(monthPath, dayEntry.name);
|
|
5329
6478
|
let files;
|
|
5330
6479
|
try {
|
|
5331
|
-
files = await
|
|
6480
|
+
files = await import_node_fs15.promises.readdir(dayPath, {
|
|
5332
6481
|
withFileTypes: true,
|
|
5333
6482
|
encoding: "utf8"
|
|
5334
6483
|
});
|
|
@@ -5338,9 +6487,9 @@ var collectSessionRuns = async (sessionId, maxRuns, log2) => {
|
|
|
5338
6487
|
for (const file of files) {
|
|
5339
6488
|
if (!file.isFile()) continue;
|
|
5340
6489
|
if (!file.name.endsWith(".jsonl")) continue;
|
|
5341
|
-
const filePath =
|
|
6490
|
+
const filePath = import_node_path13.default.join(dayPath, file.name);
|
|
5342
6491
|
try {
|
|
5343
|
-
const stat = await
|
|
6492
|
+
const stat = await import_node_fs15.promises.stat(filePath);
|
|
5344
6493
|
candidates.push({ path: filePath, mtimeMs: stat.mtimeMs });
|
|
5345
6494
|
} catch {
|
|
5346
6495
|
}
|
|
@@ -5349,6 +6498,10 @@ var collectSessionRuns = async (sessionId, maxRuns, log2) => {
|
|
|
5349
6498
|
}
|
|
5350
6499
|
}
|
|
5351
6500
|
candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
6501
|
+
return candidates;
|
|
6502
|
+
};
|
|
6503
|
+
var collectSessionRuns = async (sessionId, maxRuns, log2) => {
|
|
6504
|
+
const candidates = await collectRunCandidates(log2);
|
|
5352
6505
|
const runs = [];
|
|
5353
6506
|
for (const candidate of candidates) {
|
|
5354
6507
|
if (runs.length >= maxRuns) break;
|
|
@@ -5359,25 +6512,22 @@ var collectSessionRuns = async (sessionId, maxRuns, log2) => {
|
|
|
5359
6512
|
continue;
|
|
5360
6513
|
}
|
|
5361
6514
|
if (!headerLine) continue;
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
});
|
|
5373
|
-
} catch {
|
|
5374
|
-
}
|
|
6515
|
+
const header = parseRunHeader(headerLine);
|
|
6516
|
+
if (!header) continue;
|
|
6517
|
+
if (header.session_id !== sessionId) continue;
|
|
6518
|
+
const runId = header.run_id ?? import_node_path13.default.basename(candidate.path, ".jsonl");
|
|
6519
|
+
const startedAt = header.started_at ?? new Date(candidate.mtimeMs).toISOString();
|
|
6520
|
+
runs.push({
|
|
6521
|
+
path: candidate.path,
|
|
6522
|
+
run_id: runId,
|
|
6523
|
+
started_at: startedAt
|
|
6524
|
+
});
|
|
5375
6525
|
}
|
|
5376
6526
|
runs.sort((a, b) => a.started_at.localeCompare(b.started_at));
|
|
5377
6527
|
return runs;
|
|
5378
6528
|
};
|
|
5379
6529
|
var readJsonl = async function* (filePath) {
|
|
5380
|
-
const stream = (0,
|
|
6530
|
+
const stream = (0, import_node_fs15.createReadStream)(filePath, { encoding: "utf8" });
|
|
5381
6531
|
let buffer = "";
|
|
5382
6532
|
for await (const chunk of stream) {
|
|
5383
6533
|
buffer += chunk;
|
|
@@ -5419,7 +6569,7 @@ var createHistoryHandlers = ({
|
|
|
5419
6569
|
sessions = await sessionStateStore.list();
|
|
5420
6570
|
} catch (error) {
|
|
5421
6571
|
sendError(id, {
|
|
5422
|
-
code:
|
|
6572
|
+
code: import_protocol3.RPC_ERROR_CODE.SESSION_LIST_FAILED,
|
|
5423
6573
|
message: `session list failed: ${String(error)}`
|
|
5424
6574
|
});
|
|
5425
6575
|
return;
|
|
@@ -5434,7 +6584,7 @@ var createHistoryHandlers = ({
|
|
|
5434
6584
|
const handleSessionHistory = async (id, params) => {
|
|
5435
6585
|
const sessionId = params?.session_id?.trim();
|
|
5436
6586
|
if (!sessionId) {
|
|
5437
|
-
sendError(id, { code:
|
|
6587
|
+
sendError(id, { code: import_protocol3.RPC_ERROR_CODE.INVALID_PARAMS, message: "session_id is required" });
|
|
5438
6588
|
return;
|
|
5439
6589
|
}
|
|
5440
6590
|
const maxRuns = Math.max(0, params?.max_runs ?? DEFAULT_HISTORY_RUNS);
|
|
@@ -5464,7 +6614,7 @@ var createHistoryHandlers = ({
|
|
|
5464
6614
|
}
|
|
5465
6615
|
if (!record || typeof record !== "object") continue;
|
|
5466
6616
|
if (record.type === "run.start") {
|
|
5467
|
-
const input = record.input
|
|
6617
|
+
const input = runStartInputToHiddenMessage(record.input);
|
|
5468
6618
|
if (input) {
|
|
5469
6619
|
sendHistoryEvent(record.run_id, -1, {
|
|
5470
6620
|
type: "hidden_user_message",
|
|
@@ -5493,8 +6643,268 @@ var createHistoryHandlers = ({
|
|
|
5493
6643
|
|
|
5494
6644
|
// src/rpc/model.ts
|
|
5495
6645
|
var import_config_loader2 = require("@codelia/config-loader");
|
|
5496
|
-
var
|
|
6646
|
+
var import_core18 = require("@codelia/core");
|
|
5497
6647
|
var import_model_metadata2 = require("@codelia/model-metadata");
|
|
6648
|
+
var import_protocol4 = require("@codelia/protocol");
|
|
6649
|
+
var isSupportedProvider = (provider) => provider === "openai" || provider === "anthropic" || provider === "openrouter";
|
|
6650
|
+
var resolveProviderModelEntry = (providerEntries, provider, model) => {
|
|
6651
|
+
if (!providerEntries) return null;
|
|
6652
|
+
return providerEntries[model] ?? providerEntries[`${provider}/${model}`] ?? null;
|
|
6653
|
+
};
|
|
6654
|
+
var parseReleaseTimestamp = (entry) => {
|
|
6655
|
+
const releaseDate = entry?.releaseDate?.trim();
|
|
6656
|
+
if (!releaseDate) return null;
|
|
6657
|
+
const timestamp = Date.parse(releaseDate);
|
|
6658
|
+
if (Number.isNaN(timestamp)) return null;
|
|
6659
|
+
return timestamp;
|
|
6660
|
+
};
|
|
6661
|
+
var normalizeUsdPer1M = (value) => {
|
|
6662
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
6663
|
+
return void 0;
|
|
6664
|
+
}
|
|
6665
|
+
return value;
|
|
6666
|
+
};
|
|
6667
|
+
var buildModelListDetail = (entry) => {
|
|
6668
|
+
if (!entry) return null;
|
|
6669
|
+
const detail = {};
|
|
6670
|
+
if (entry.releaseDate?.trim()) {
|
|
6671
|
+
detail.release_date = entry.releaseDate.trim();
|
|
6672
|
+
}
|
|
6673
|
+
const limits = entry.limits;
|
|
6674
|
+
if (typeof limits?.contextWindow === "number" && Number.isFinite(limits.contextWindow) && limits.contextWindow > 0) {
|
|
6675
|
+
detail.context_window = limits.contextWindow;
|
|
6676
|
+
}
|
|
6677
|
+
if (typeof limits?.inputTokens === "number" && Number.isFinite(limits.inputTokens) && limits.inputTokens > 0) {
|
|
6678
|
+
detail.max_input_tokens = limits.inputTokens;
|
|
6679
|
+
}
|
|
6680
|
+
if (typeof limits?.outputTokens === "number" && Number.isFinite(limits.outputTokens) && limits.outputTokens > 0) {
|
|
6681
|
+
detail.max_output_tokens = limits.outputTokens;
|
|
6682
|
+
}
|
|
6683
|
+
const inputCost = normalizeUsdPer1M(entry.cost?.input);
|
|
6684
|
+
if (inputCost !== void 0) {
|
|
6685
|
+
detail.cost_per_1m_input_tokens_usd = inputCost;
|
|
6686
|
+
}
|
|
6687
|
+
const outputCost = normalizeUsdPer1M(entry.cost?.output);
|
|
6688
|
+
if (outputCost !== void 0) {
|
|
6689
|
+
detail.cost_per_1m_output_tokens_usd = outputCost;
|
|
6690
|
+
}
|
|
6691
|
+
return Object.keys(detail).length ? detail : null;
|
|
6692
|
+
};
|
|
6693
|
+
var sortModelsByReleaseDate = (models, provider, providerEntries) => {
|
|
6694
|
+
const sortable = models.map((model, index) => ({
|
|
6695
|
+
model,
|
|
6696
|
+
index,
|
|
6697
|
+
releaseTimestamp: parseReleaseTimestamp(
|
|
6698
|
+
resolveProviderModelEntry(providerEntries, provider, model)
|
|
6699
|
+
)
|
|
6700
|
+
}));
|
|
6701
|
+
sortable.sort((left, right) => {
|
|
6702
|
+
if (left.releaseTimestamp !== null && right.releaseTimestamp !== null && left.releaseTimestamp !== right.releaseTimestamp) {
|
|
6703
|
+
return right.releaseTimestamp - left.releaseTimestamp;
|
|
6704
|
+
}
|
|
6705
|
+
if (left.releaseTimestamp !== null && right.releaseTimestamp === null) {
|
|
6706
|
+
return -1;
|
|
6707
|
+
}
|
|
6708
|
+
if (left.releaseTimestamp === null && right.releaseTimestamp !== null) {
|
|
6709
|
+
return 1;
|
|
6710
|
+
}
|
|
6711
|
+
return left.index - right.index;
|
|
6712
|
+
});
|
|
6713
|
+
return sortable.map((item) => item.model);
|
|
6714
|
+
};
|
|
6715
|
+
var loadProviderModelEntries = async (provider) => {
|
|
6716
|
+
const metadataService = new import_model_metadata2.ModelMetadataServiceImpl();
|
|
6717
|
+
const allEntries = await metadataService.getAllModelEntries();
|
|
6718
|
+
return allEntries[provider] ?? null;
|
|
6719
|
+
};
|
|
6720
|
+
var OPENROUTER_BASE_URL2 = "https://openrouter.ai/api/v1";
|
|
6721
|
+
var parseUnixSecondsToDate = (value) => {
|
|
6722
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
6723
|
+
return void 0;
|
|
6724
|
+
}
|
|
6725
|
+
const date = new Date(Math.floor(value) * 1e3);
|
|
6726
|
+
const timestamp = date.getTime();
|
|
6727
|
+
if (!Number.isFinite(timestamp)) {
|
|
6728
|
+
return void 0;
|
|
6729
|
+
}
|
|
6730
|
+
return date.toISOString().slice(0, 10);
|
|
6731
|
+
};
|
|
6732
|
+
var parsePositiveNumber = (value) => {
|
|
6733
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
6734
|
+
return void 0;
|
|
6735
|
+
}
|
|
6736
|
+
return value;
|
|
6737
|
+
};
|
|
6738
|
+
var buildOpenRouterHeaders = (apiKey) => {
|
|
6739
|
+
const headers = new Headers();
|
|
6740
|
+
headers.set("Authorization", `Bearer ${apiKey}`);
|
|
6741
|
+
const referer = readEnvValue("OPENROUTER_HTTP_REFERER");
|
|
6742
|
+
if (referer) {
|
|
6743
|
+
headers.set("HTTP-Referer", referer);
|
|
6744
|
+
}
|
|
6745
|
+
const title = readEnvValue("OPENROUTER_X_TITLE");
|
|
6746
|
+
if (title) {
|
|
6747
|
+
headers.set("X-Title", title);
|
|
6748
|
+
}
|
|
6749
|
+
return headers;
|
|
6750
|
+
};
|
|
6751
|
+
var resolveOpenRouterApiKey = async () => {
|
|
6752
|
+
const envKey = readEnvValue("OPENROUTER_API_KEY");
|
|
6753
|
+
if (envKey) {
|
|
6754
|
+
return envKey;
|
|
6755
|
+
}
|
|
6756
|
+
const store = new AuthStore();
|
|
6757
|
+
const auth = await store.load();
|
|
6758
|
+
const providerAuth = auth.providers.openrouter;
|
|
6759
|
+
if (providerAuth?.method !== "api_key") {
|
|
6760
|
+
return null;
|
|
6761
|
+
}
|
|
6762
|
+
const value = providerAuth.api_key.trim();
|
|
6763
|
+
return value ? value : null;
|
|
6764
|
+
};
|
|
6765
|
+
var resolveOpenRouterApiKeyWithPrompt = async ({
|
|
6766
|
+
state,
|
|
6767
|
+
log: log2
|
|
6768
|
+
}) => {
|
|
6769
|
+
const existing = await resolveOpenRouterApiKey();
|
|
6770
|
+
if (existing) {
|
|
6771
|
+
return existing;
|
|
6772
|
+
}
|
|
6773
|
+
if (!state) {
|
|
6774
|
+
throw new Error("OpenRouter API key is required");
|
|
6775
|
+
}
|
|
6776
|
+
const authResolver = await AuthResolver.create(state, log2);
|
|
6777
|
+
const auth = await authResolver.resolveProviderAuth("openrouter");
|
|
6778
|
+
if (auth.method !== "api_key") {
|
|
6779
|
+
throw new Error("OpenRouter API key is required");
|
|
6780
|
+
}
|
|
6781
|
+
const value = auth.api_key.trim();
|
|
6782
|
+
if (!value) {
|
|
6783
|
+
throw new Error("OpenRouter API key is required");
|
|
6784
|
+
}
|
|
6785
|
+
return value;
|
|
6786
|
+
};
|
|
6787
|
+
var parseOpenRouterModel = (value) => {
|
|
6788
|
+
if (!value || typeof value !== "object") {
|
|
6789
|
+
return null;
|
|
6790
|
+
}
|
|
6791
|
+
const entry = value;
|
|
6792
|
+
const id = typeof entry.id === "string" ? entry.id.trim() : "";
|
|
6793
|
+
if (!id) {
|
|
6794
|
+
return null;
|
|
6795
|
+
}
|
|
6796
|
+
const topProvider = entry.top_provider && typeof entry.top_provider === "object" ? entry.top_provider : null;
|
|
6797
|
+
return {
|
|
6798
|
+
id,
|
|
6799
|
+
created: typeof entry.created === "number" && Number.isFinite(entry.created) ? entry.created : void 0,
|
|
6800
|
+
context_length: parsePositiveNumber(entry.context_length),
|
|
6801
|
+
top_provider: topProvider ? {
|
|
6802
|
+
context_length: parsePositiveNumber(topProvider.context_length),
|
|
6803
|
+
max_completion_tokens: parsePositiveNumber(
|
|
6804
|
+
topProvider.max_completion_tokens
|
|
6805
|
+
)
|
|
6806
|
+
} : void 0
|
|
6807
|
+
};
|
|
6808
|
+
};
|
|
6809
|
+
var fetchOpenRouterModels = async (apiKey) => {
|
|
6810
|
+
const response = await fetch(`${OPENROUTER_BASE_URL2}/models`, {
|
|
6811
|
+
headers: buildOpenRouterHeaders(apiKey)
|
|
6812
|
+
});
|
|
6813
|
+
if (!response.ok) {
|
|
6814
|
+
const body = await response.text().catch(() => "");
|
|
6815
|
+
const snippet = body ? body.slice(0, 300) : "(empty)";
|
|
6816
|
+
throw new Error(
|
|
6817
|
+
`OpenRouter models request failed (${response.status}): ${snippet}`
|
|
6818
|
+
);
|
|
6819
|
+
}
|
|
6820
|
+
const payload = await response.json();
|
|
6821
|
+
if (!payload || typeof payload !== "object") {
|
|
6822
|
+
throw new Error("OpenRouter models response is not an object");
|
|
6823
|
+
}
|
|
6824
|
+
const data = payload.data;
|
|
6825
|
+
if (!Array.isArray(data)) {
|
|
6826
|
+
throw new Error("OpenRouter models response has no data array");
|
|
6827
|
+
}
|
|
6828
|
+
const models = data.map((entry) => parseOpenRouterModel(entry)).filter((entry) => !!entry);
|
|
6829
|
+
models.sort((left, right) => {
|
|
6830
|
+
const leftCreated = left.created ?? 0;
|
|
6831
|
+
const rightCreated = right.created ?? 0;
|
|
6832
|
+
if (leftCreated !== rightCreated) {
|
|
6833
|
+
return rightCreated - leftCreated;
|
|
6834
|
+
}
|
|
6835
|
+
return left.id.localeCompare(right.id);
|
|
6836
|
+
});
|
|
6837
|
+
return models;
|
|
6838
|
+
};
|
|
6839
|
+
var buildOpenRouterModelList = async ({
|
|
6840
|
+
includeDetails,
|
|
6841
|
+
state,
|
|
6842
|
+
log: log2
|
|
6843
|
+
}) => {
|
|
6844
|
+
const apiKey = await resolveOpenRouterApiKeyWithPrompt({ state, log: log2 });
|
|
6845
|
+
const models = await fetchOpenRouterModels(apiKey);
|
|
6846
|
+
const ids = models.map((model) => model.id);
|
|
6847
|
+
if (!includeDetails) {
|
|
6848
|
+
return { models: ids };
|
|
6849
|
+
}
|
|
6850
|
+
const details = {};
|
|
6851
|
+
for (const model of models) {
|
|
6852
|
+
const detail = {};
|
|
6853
|
+
const releaseDate = parseUnixSecondsToDate(model.created);
|
|
6854
|
+
if (releaseDate) {
|
|
6855
|
+
detail.release_date = releaseDate;
|
|
6856
|
+
}
|
|
6857
|
+
const contextWindow = model.context_length ?? model.top_provider?.context_length;
|
|
6858
|
+
if (contextWindow && contextWindow > 0) {
|
|
6859
|
+
detail.context_window = contextWindow;
|
|
6860
|
+
detail.max_input_tokens = contextWindow;
|
|
6861
|
+
}
|
|
6862
|
+
const maxOutputTokens = model.top_provider?.max_completion_tokens;
|
|
6863
|
+
if (maxOutputTokens && maxOutputTokens > 0) {
|
|
6864
|
+
detail.max_output_tokens = maxOutputTokens;
|
|
6865
|
+
}
|
|
6866
|
+
if (Object.keys(detail).length) {
|
|
6867
|
+
details[model.id] = detail;
|
|
6868
|
+
}
|
|
6869
|
+
}
|
|
6870
|
+
return Object.keys(details).length ? { models: ids, details } : { models: ids };
|
|
6871
|
+
};
|
|
6872
|
+
var buildProviderModelList = async ({
|
|
6873
|
+
provider,
|
|
6874
|
+
includeDetails,
|
|
6875
|
+
state,
|
|
6876
|
+
log: log2
|
|
6877
|
+
}) => {
|
|
6878
|
+
if (provider === "openrouter") {
|
|
6879
|
+
return buildOpenRouterModelList({ includeDetails, state, log: log2 });
|
|
6880
|
+
}
|
|
6881
|
+
let providerEntries = null;
|
|
6882
|
+
try {
|
|
6883
|
+
providerEntries = await loadProviderModelEntries(provider);
|
|
6884
|
+
} catch (error) {
|
|
6885
|
+
if (includeDetails) {
|
|
6886
|
+
log2(`model.list details error: ${error}`);
|
|
6887
|
+
}
|
|
6888
|
+
}
|
|
6889
|
+
const models = sortModelsByReleaseDate(
|
|
6890
|
+
(0, import_core18.listModels)(import_core18.DEFAULT_MODEL_REGISTRY, provider).map((model) => model.id),
|
|
6891
|
+
provider,
|
|
6892
|
+
providerEntries
|
|
6893
|
+
);
|
|
6894
|
+
if (!includeDetails || !providerEntries) {
|
|
6895
|
+
return { models };
|
|
6896
|
+
}
|
|
6897
|
+
const details = {};
|
|
6898
|
+
for (const model of models) {
|
|
6899
|
+
const detail = buildModelListDetail(
|
|
6900
|
+
resolveProviderModelEntry(providerEntries, provider, model)
|
|
6901
|
+
);
|
|
6902
|
+
if (detail) {
|
|
6903
|
+
details[model] = detail;
|
|
6904
|
+
}
|
|
6905
|
+
}
|
|
6906
|
+
return Object.keys(details).length ? { models, details } : { models };
|
|
6907
|
+
};
|
|
5498
6908
|
var createModelHandlers = ({
|
|
5499
6909
|
state,
|
|
5500
6910
|
log: log2
|
|
@@ -5502,9 +6912,9 @@ var createModelHandlers = ({
|
|
|
5502
6912
|
const handleModelList = async (id, params) => {
|
|
5503
6913
|
const requestedProvider = params?.provider;
|
|
5504
6914
|
const includeDetails = params?.include_details ?? false;
|
|
5505
|
-
if (requestedProvider && requestedProvider
|
|
6915
|
+
if (requestedProvider && !isSupportedProvider(requestedProvider)) {
|
|
5506
6916
|
sendError(id, {
|
|
5507
|
-
code:
|
|
6917
|
+
code: import_protocol4.RPC_ERROR_CODE.INVALID_PARAMS,
|
|
5508
6918
|
message: `unsupported provider: ${requestedProvider}`
|
|
5509
6919
|
});
|
|
5510
6920
|
return;
|
|
@@ -5518,54 +6928,35 @@ var createModelHandlers = ({
|
|
|
5518
6928
|
current = config.name;
|
|
5519
6929
|
}
|
|
5520
6930
|
} catch (error) {
|
|
5521
|
-
sendError(id, { code:
|
|
6931
|
+
sendError(id, { code: import_protocol4.RPC_ERROR_CODE.RUNTIME_INTERNAL, message: String(error) });
|
|
5522
6932
|
return;
|
|
5523
6933
|
}
|
|
5524
6934
|
const provider = requestedProvider ?? configuredProvider ?? "openai";
|
|
5525
|
-
if (provider
|
|
6935
|
+
if (!isSupportedProvider(provider)) {
|
|
5526
6936
|
sendError(id, {
|
|
5527
|
-
code:
|
|
6937
|
+
code: import_protocol4.RPC_ERROR_CODE.INVALID_PARAMS,
|
|
5528
6938
|
message: `unsupported provider: ${provider}`
|
|
5529
6939
|
});
|
|
5530
6940
|
return;
|
|
5531
6941
|
}
|
|
5532
|
-
|
|
6942
|
+
let models;
|
|
6943
|
+
let details;
|
|
6944
|
+
try {
|
|
6945
|
+
const result2 = await buildProviderModelList({
|
|
6946
|
+
provider,
|
|
6947
|
+
includeDetails,
|
|
6948
|
+
state,
|
|
6949
|
+
log: log2
|
|
6950
|
+
});
|
|
6951
|
+
models = result2.models;
|
|
6952
|
+
details = result2.details;
|
|
6953
|
+
} catch (error) {
|
|
6954
|
+
sendError(id, { code: import_protocol4.RPC_ERROR_CODE.RUNTIME_INTERNAL, message: String(error) });
|
|
6955
|
+
return;
|
|
6956
|
+
}
|
|
5533
6957
|
if (current && !models.includes(current)) {
|
|
5534
6958
|
current = void 0;
|
|
5535
6959
|
}
|
|
5536
|
-
let details;
|
|
5537
|
-
if (includeDetails) {
|
|
5538
|
-
try {
|
|
5539
|
-
const metadataService = new import_model_metadata2.ModelMetadataServiceImpl();
|
|
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
6960
|
const result = {
|
|
5570
6961
|
provider,
|
|
5571
6962
|
models,
|
|
@@ -5576,26 +6967,28 @@ var createModelHandlers = ({
|
|
|
5576
6967
|
};
|
|
5577
6968
|
const handleModelSet = async (id, params) => {
|
|
5578
6969
|
if (state.activeRunId) {
|
|
5579
|
-
sendError(id, { code:
|
|
6970
|
+
sendError(id, { code: import_protocol4.RPC_ERROR_CODE.RUNTIME_BUSY, message: "runtime busy" });
|
|
5580
6971
|
return;
|
|
5581
6972
|
}
|
|
5582
6973
|
const provider = params?.provider ?? "openai";
|
|
5583
6974
|
const name = params?.name?.trim();
|
|
5584
6975
|
if (!name) {
|
|
5585
|
-
sendError(id, { code:
|
|
6976
|
+
sendError(id, { code: import_protocol4.RPC_ERROR_CODE.INVALID_PARAMS, message: "model name is required" });
|
|
5586
6977
|
return;
|
|
5587
6978
|
}
|
|
5588
|
-
if (provider !== "openai" && provider !== "anthropic") {
|
|
6979
|
+
if (provider !== "openai" && provider !== "anthropic" && provider !== "openrouter") {
|
|
5589
6980
|
sendError(id, {
|
|
5590
|
-
code:
|
|
6981
|
+
code: import_protocol4.RPC_ERROR_CODE.INVALID_PARAMS,
|
|
5591
6982
|
message: `unsupported provider: ${provider}`
|
|
5592
6983
|
});
|
|
5593
6984
|
return;
|
|
5594
6985
|
}
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
6986
|
+
if (provider !== "openrouter") {
|
|
6987
|
+
const spec = (0, import_core18.resolveModel)(import_core18.DEFAULT_MODEL_REGISTRY, name, provider);
|
|
6988
|
+
if (!spec) {
|
|
6989
|
+
sendError(id, { code: import_protocol4.RPC_ERROR_CODE.INVALID_PARAMS, message: `unknown model: ${name}` });
|
|
6990
|
+
return;
|
|
6991
|
+
}
|
|
5599
6992
|
}
|
|
5600
6993
|
try {
|
|
5601
6994
|
const configPath = resolveConfigPath();
|
|
@@ -5605,17 +6998,21 @@ var createModelHandlers = ({
|
|
|
5605
6998
|
sendResult(id, result);
|
|
5606
6999
|
log2(`model.set ${provider}/${name}`);
|
|
5607
7000
|
} catch (error) {
|
|
5608
|
-
sendError(id, { code:
|
|
7001
|
+
sendError(id, { code: import_protocol4.RPC_ERROR_CODE.RUNTIME_INTERNAL, message: String(error) });
|
|
5609
7002
|
}
|
|
5610
7003
|
};
|
|
5611
7004
|
return { handleModelList, handleModelSet };
|
|
5612
7005
|
};
|
|
5613
7006
|
|
|
7007
|
+
// src/rpc/run.ts
|
|
7008
|
+
var import_protocol5 = require("@codelia/protocol");
|
|
7009
|
+
|
|
5614
7010
|
// src/rpc/run-debug.ts
|
|
5615
|
-
var
|
|
7011
|
+
var import_core19 = require("@codelia/core");
|
|
5616
7012
|
var DEBUG_MAX_CONTENT_CHARS = 2e3;
|
|
5617
7013
|
var DEBUG_MAX_LOG_CHARS = 2e4;
|
|
5618
7014
|
var DEBUG_MAX_EVENT_RESULT_CHARS = 500;
|
|
7015
|
+
var DEBUG_MAX_EVENT_ARGS_CHARS = 2e3;
|
|
5619
7016
|
var truncateText2 = (value, maxChars) => {
|
|
5620
7017
|
if (value.length <= maxChars) return value;
|
|
5621
7018
|
return `${value.slice(0, maxChars)}...[truncated]`;
|
|
@@ -5628,7 +7025,7 @@ var stringifyUnknown = (value) => {
|
|
|
5628
7025
|
}
|
|
5629
7026
|
};
|
|
5630
7027
|
var contentToDebugText = (content, maxChars) => {
|
|
5631
|
-
const text = (0,
|
|
7028
|
+
const text = (0, import_core19.stringifyContent)(content, {
|
|
5632
7029
|
mode: "log",
|
|
5633
7030
|
joiner: "\n",
|
|
5634
7031
|
includeOtherPayload: true
|
|
@@ -5687,7 +7084,9 @@ var summarizeRunEvent = (event) => {
|
|
|
5687
7084
|
case "tool_call": {
|
|
5688
7085
|
const tool = typeof event.tool === "string" ? event.tool : "unknown";
|
|
5689
7086
|
const toolCallId = typeof event.tool_call_id === "string" ? event.tool_call_id : "unknown";
|
|
5690
|
-
|
|
7087
|
+
const rawArgs = typeof event.raw_args === "string" ? event.raw_args : stringifyUnknown(event.args);
|
|
7088
|
+
const rawArgsSnippet = truncateText2(rawArgs, DEBUG_MAX_EVENT_ARGS_CHARS);
|
|
7089
|
+
return `type=tool_call tool=${tool} tool_call_id=${toolCallId} raw_args=${stringifyUnknown(rawArgsSnippet)}`;
|
|
5691
7090
|
}
|
|
5692
7091
|
case "tool_result": {
|
|
5693
7092
|
const tool = typeof event.tool === "string" ? event.tool : "unknown";
|
|
@@ -5722,7 +7121,7 @@ var logRunDebug = (log2, runId, message) => {
|
|
|
5722
7121
|
if (!(0, import_logger.isDebugEnabled)()) return;
|
|
5723
7122
|
log2(`run debug run_id=${runId} ${message}`);
|
|
5724
7123
|
};
|
|
5725
|
-
var
|
|
7124
|
+
var normalizeToolCallHistory = (messages) => {
|
|
5726
7125
|
const assistantCallIds = /* @__PURE__ */ new Set();
|
|
5727
7126
|
const toolOutputCallIds = /* @__PURE__ */ new Set();
|
|
5728
7127
|
for (const message of messages) {
|
|
@@ -5806,8 +7205,92 @@ var prepareRunInputText = (inputText) => {
|
|
|
5806
7205
|
return `${inputText}${buildSkillMentionHint(mentions)}`;
|
|
5807
7206
|
};
|
|
5808
7207
|
|
|
7208
|
+
// src/rpc/run-input.ts
|
|
7209
|
+
var isRunInputImageMediaType = (value) => value === "image/png" || value === "image/jpeg" || value === "image/webp" || value === "image/gif";
|
|
7210
|
+
var isRunInputImageDetail = (value) => value === "auto" || value === "low" || value === "high";
|
|
7211
|
+
var normalizeRunInputTextPart = (part) => {
|
|
7212
|
+
if (typeof part.text !== "string") {
|
|
7213
|
+
throw new Error("run.start input.parts[text].text must be a string");
|
|
7214
|
+
}
|
|
7215
|
+
return {
|
|
7216
|
+
type: "text",
|
|
7217
|
+
text: prepareRunInputText(part.text)
|
|
7218
|
+
};
|
|
7219
|
+
};
|
|
7220
|
+
var normalizeRunInputImagePart = (part) => {
|
|
7221
|
+
const imageUrl = part.image_url;
|
|
7222
|
+
if (!imageUrl || typeof imageUrl !== "object") {
|
|
7223
|
+
throw new Error("run.start input.parts[image_url].image_url is required");
|
|
7224
|
+
}
|
|
7225
|
+
if (typeof imageUrl.url !== "string" || imageUrl.url.length === 0) {
|
|
7226
|
+
throw new Error(
|
|
7227
|
+
"run.start input.parts[image_url].image_url.url must be a non-empty string"
|
|
7228
|
+
);
|
|
7229
|
+
}
|
|
7230
|
+
if (imageUrl.media_type !== void 0 && !isRunInputImageMediaType(imageUrl.media_type)) {
|
|
7231
|
+
throw new Error(
|
|
7232
|
+
"run.start input.parts[image_url].image_url.media_type must be png/jpeg/webp/gif"
|
|
7233
|
+
);
|
|
7234
|
+
}
|
|
7235
|
+
if (imageUrl.detail !== void 0 && !isRunInputImageDetail(imageUrl.detail)) {
|
|
7236
|
+
throw new Error(
|
|
7237
|
+
"run.start input.parts[image_url].image_url.detail must be auto/low/high"
|
|
7238
|
+
);
|
|
7239
|
+
}
|
|
7240
|
+
return {
|
|
7241
|
+
type: "image_url",
|
|
7242
|
+
image_url: {
|
|
7243
|
+
url: imageUrl.url,
|
|
7244
|
+
...imageUrl.media_type ? { media_type: imageUrl.media_type } : {},
|
|
7245
|
+
...imageUrl.detail ? { detail: imageUrl.detail } : {}
|
|
7246
|
+
}
|
|
7247
|
+
};
|
|
7248
|
+
};
|
|
7249
|
+
var normalizeRunInput = (input) => {
|
|
7250
|
+
if (input.type === "text") {
|
|
7251
|
+
if (typeof input.text !== "string") {
|
|
7252
|
+
throw new Error("run.start input.text must be a string");
|
|
7253
|
+
}
|
|
7254
|
+
return prepareRunInputText(input.text);
|
|
7255
|
+
}
|
|
7256
|
+
if (!Array.isArray(input.parts)) {
|
|
7257
|
+
throw new Error("run.start input.parts must be an array");
|
|
7258
|
+
}
|
|
7259
|
+
return input.parts.map((part) => {
|
|
7260
|
+
if (!part || typeof part !== "object") {
|
|
7261
|
+
throw new Error("run.start input.parts entry must be an object");
|
|
7262
|
+
}
|
|
7263
|
+
if (part.type === "text") {
|
|
7264
|
+
return normalizeRunInputTextPart(part);
|
|
7265
|
+
}
|
|
7266
|
+
if (part.type === "image_url") {
|
|
7267
|
+
return normalizeRunInputImagePart(part);
|
|
7268
|
+
}
|
|
7269
|
+
throw new Error(
|
|
7270
|
+
`run.start input.parts type is not supported: ${String(part.type)}`
|
|
7271
|
+
);
|
|
7272
|
+
});
|
|
7273
|
+
};
|
|
7274
|
+
var runInputLengthForDebug = (input) => {
|
|
7275
|
+
if (typeof input === "string") {
|
|
7276
|
+
return input.length;
|
|
7277
|
+
}
|
|
7278
|
+
let total = 0;
|
|
7279
|
+
for (const part of input) {
|
|
7280
|
+
if (part.type === "text") {
|
|
7281
|
+
total += part.text.length;
|
|
7282
|
+
continue;
|
|
7283
|
+
}
|
|
7284
|
+
if (part.type === "image_url") {
|
|
7285
|
+
total += part.image_url.url.length;
|
|
7286
|
+
}
|
|
7287
|
+
}
|
|
7288
|
+
return total;
|
|
7289
|
+
};
|
|
7290
|
+
|
|
5809
7291
|
// src/rpc/run.ts
|
|
5810
|
-
var
|
|
7292
|
+
var nowIso4 = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
7293
|
+
var SESSION_STATE_SAVE_DEBOUNCE_MS = 1500;
|
|
5811
7294
|
var createSessionAppender = (store, onError) => {
|
|
5812
7295
|
let chain = Promise.resolve();
|
|
5813
7296
|
return (record) => {
|
|
@@ -5819,7 +7302,7 @@ var createSessionAppender = (store, onError) => {
|
|
|
5819
7302
|
var buildSessionState = (sessionId, runId, messages, invokeSeq) => ({
|
|
5820
7303
|
schema_version: 1,
|
|
5821
7304
|
session_id: sessionId,
|
|
5822
|
-
updated_at:
|
|
7305
|
+
updated_at: nowIso4(),
|
|
5823
7306
|
run_id: runId,
|
|
5824
7307
|
invoke_seq: invokeSeq,
|
|
5825
7308
|
messages
|
|
@@ -5830,18 +7313,26 @@ var createRunHandlers = ({
|
|
|
5830
7313
|
log: log2,
|
|
5831
7314
|
runEventStoreFactory,
|
|
5832
7315
|
sessionStateStore,
|
|
5833
|
-
appendSession
|
|
7316
|
+
appendSession,
|
|
7317
|
+
beforeRunStart
|
|
5834
7318
|
}) => {
|
|
5835
7319
|
let activeRunAbort = null;
|
|
5836
7320
|
let runStartQueue = Promise.resolve();
|
|
5837
7321
|
const normalizeRunHistoryAfterCancel = (runId, runtimeAgent) => {
|
|
5838
7322
|
const currentMessages = runtimeAgent.getHistoryMessages();
|
|
5839
|
-
const normalizedMessages =
|
|
7323
|
+
const normalizedMessages = normalizeToolCallHistory(currentMessages);
|
|
5840
7324
|
if (normalizedMessages !== currentMessages) {
|
|
5841
7325
|
runtimeAgent.replaceHistoryMessages(normalizedMessages);
|
|
5842
7326
|
log2(`run.cancel normalized history ${runId}`);
|
|
5843
7327
|
}
|
|
5844
7328
|
};
|
|
7329
|
+
const normalizeRestoredSessionMessages = (messages, sessionId) => {
|
|
7330
|
+
const normalizedMessages = normalizeToolCallHistory(messages);
|
|
7331
|
+
if (normalizedMessages !== messages) {
|
|
7332
|
+
log2(`session restore normalized history ${sessionId}`);
|
|
7333
|
+
}
|
|
7334
|
+
return normalizedMessages;
|
|
7335
|
+
};
|
|
5845
7336
|
const emitRunStatus = (runId, status, message) => {
|
|
5846
7337
|
sendRunStatus(runId, status, message);
|
|
5847
7338
|
const suffix = message ? ` message=${message}` : "";
|
|
@@ -5849,7 +7340,7 @@ var createRunHandlers = ({
|
|
|
5849
7340
|
appendSession({
|
|
5850
7341
|
type: "run.status",
|
|
5851
7342
|
run_id: runId,
|
|
5852
|
-
ts:
|
|
7343
|
+
ts: nowIso4(),
|
|
5853
7344
|
status,
|
|
5854
7345
|
...message ? { message } : {}
|
|
5855
7346
|
});
|
|
@@ -5858,22 +7349,43 @@ var createRunHandlers = ({
|
|
|
5858
7349
|
appendSession({
|
|
5859
7350
|
type: "run.end",
|
|
5860
7351
|
run_id: runId,
|
|
5861
|
-
ts:
|
|
7352
|
+
ts: nowIso4(),
|
|
5862
7353
|
outcome,
|
|
5863
7354
|
...final !== void 0 ? { final } : {}
|
|
5864
7355
|
});
|
|
5865
7356
|
};
|
|
5866
7357
|
const handleRunStart = (id, params) => {
|
|
5867
7358
|
const run = async () => {
|
|
7359
|
+
let normalizedInput;
|
|
7360
|
+
try {
|
|
7361
|
+
normalizedInput = normalizeRunInput(params.input);
|
|
7362
|
+
} catch (error) {
|
|
7363
|
+
sendError(id, {
|
|
7364
|
+
code: import_protocol5.RPC_ERROR_CODE.INVALID_PARAMS,
|
|
7365
|
+
message: String(error)
|
|
7366
|
+
});
|
|
7367
|
+
return;
|
|
7368
|
+
}
|
|
7369
|
+
if (beforeRunStart) {
|
|
7370
|
+
try {
|
|
7371
|
+
await beforeRunStart();
|
|
7372
|
+
} catch (error) {
|
|
7373
|
+
sendError(id, {
|
|
7374
|
+
code: import_protocol5.RPC_ERROR_CODE.RUNTIME_INTERNAL,
|
|
7375
|
+
message: `startup onboarding failed: ${String(error)}`
|
|
7376
|
+
});
|
|
7377
|
+
return;
|
|
7378
|
+
}
|
|
7379
|
+
}
|
|
5868
7380
|
if (state.activeRunId) {
|
|
5869
|
-
sendError(id, { code:
|
|
7381
|
+
sendError(id, { code: import_protocol5.RPC_ERROR_CODE.RUNTIME_BUSY, message: "runtime busy" });
|
|
5870
7382
|
return;
|
|
5871
7383
|
}
|
|
5872
7384
|
let runtimeAgent;
|
|
5873
7385
|
try {
|
|
5874
7386
|
runtimeAgent = await getAgent();
|
|
5875
7387
|
} catch (error) {
|
|
5876
|
-
sendError(id, { code:
|
|
7388
|
+
sendError(id, { code: import_protocol5.RPC_ERROR_CODE.RUNTIME_INTERNAL, message: String(error) });
|
|
5877
7389
|
return;
|
|
5878
7390
|
}
|
|
5879
7391
|
const requestedSessionId = params.session_id?.trim() || void 0;
|
|
@@ -5884,16 +7396,20 @@ var createRunHandlers = ({
|
|
|
5884
7396
|
resumeState = await sessionStateStore.load(requestedSessionId);
|
|
5885
7397
|
} catch (error) {
|
|
5886
7398
|
sendError(id, {
|
|
5887
|
-
code:
|
|
7399
|
+
code: import_protocol5.RPC_ERROR_CODE.SESSION_LOAD_FAILED,
|
|
5888
7400
|
message: `session load failed: ${String(error)}`
|
|
5889
7401
|
});
|
|
5890
7402
|
return;
|
|
5891
7403
|
}
|
|
5892
7404
|
if (!resumeState) {
|
|
5893
|
-
sendError(id, { code:
|
|
7405
|
+
sendError(id, { code: import_protocol5.RPC_ERROR_CODE.SESSION_NOT_FOUND, message: "session not found" });
|
|
5894
7406
|
return;
|
|
5895
7407
|
}
|
|
5896
|
-
const
|
|
7408
|
+
const restoredMessages = Array.isArray(resumeState.messages) ? resumeState.messages : [];
|
|
7409
|
+
const messages = normalizeRestoredSessionMessages(
|
|
7410
|
+
restoredMessages,
|
|
7411
|
+
requestedSessionId
|
|
7412
|
+
);
|
|
5897
7413
|
runtimeAgent.replaceHistoryMessages(messages);
|
|
5898
7414
|
sessionId = requestedSessionId;
|
|
5899
7415
|
state.sessionId = sessionId;
|
|
@@ -5901,17 +7417,25 @@ var createRunHandlers = ({
|
|
|
5901
7417
|
try {
|
|
5902
7418
|
resumeState = await sessionStateStore.load(state.sessionId);
|
|
5903
7419
|
} catch (error) {
|
|
5904
|
-
|
|
7420
|
+
sendError(id, {
|
|
7421
|
+
code: import_protocol5.RPC_ERROR_CODE.SESSION_LOAD_FAILED,
|
|
7422
|
+
message: `session reload failed: ${String(error)}`
|
|
7423
|
+
});
|
|
7424
|
+
return;
|
|
5905
7425
|
}
|
|
5906
7426
|
if (resumeState) {
|
|
5907
|
-
const
|
|
7427
|
+
const restoredMessages = Array.isArray(resumeState.messages) ? resumeState.messages : [];
|
|
7428
|
+
const messages = normalizeRestoredSessionMessages(
|
|
7429
|
+
restoredMessages,
|
|
7430
|
+
state.sessionId
|
|
7431
|
+
);
|
|
5908
7432
|
runtimeAgent.replaceHistoryMessages(messages);
|
|
5909
7433
|
}
|
|
5910
7434
|
} else if (!state.sessionId) {
|
|
5911
7435
|
state.sessionId = sessionId;
|
|
5912
7436
|
}
|
|
5913
7437
|
const runId = state.nextRunId();
|
|
5914
|
-
const startedAt =
|
|
7438
|
+
const startedAt = nowIso4();
|
|
5915
7439
|
state.beginRun(runId, params.ui_context ?? state.lastUiContext);
|
|
5916
7440
|
const runAbortController = new AbortController();
|
|
5917
7441
|
activeRunAbort = { runId, controller: runAbortController };
|
|
@@ -5961,7 +7485,7 @@ var createRunHandlers = ({
|
|
|
5961
7485
|
type: "run.start",
|
|
5962
7486
|
run_id: runId,
|
|
5963
7487
|
session_id: sessionId,
|
|
5964
|
-
ts:
|
|
7488
|
+
ts: nowIso4(),
|
|
5965
7489
|
input: params.input,
|
|
5966
7490
|
ui_context: params.ui_context,
|
|
5967
7491
|
meta: params.meta
|
|
@@ -5972,25 +7496,58 @@ var createRunHandlers = ({
|
|
|
5972
7496
|
emitRunStatus(runId, "running");
|
|
5973
7497
|
void (async () => {
|
|
5974
7498
|
let finalResponse;
|
|
5975
|
-
let
|
|
5976
|
-
|
|
7499
|
+
let sessionSaveChain = Promise.resolve();
|
|
7500
|
+
let lastSessionSaveAt = 0;
|
|
7501
|
+
const queueSessionSave = async (reason) => {
|
|
7502
|
+
sessionSaveChain = sessionSaveChain.then(async () => {
|
|
7503
|
+
if (!sessionId) return;
|
|
7504
|
+
const messages = runtimeAgent.getHistoryMessages();
|
|
7505
|
+
const snapshotMessages = normalizeToolCallHistory(messages);
|
|
7506
|
+
if (snapshotMessages !== messages) {
|
|
7507
|
+
logRunDebug(
|
|
7508
|
+
log2,
|
|
7509
|
+
runId,
|
|
7510
|
+
`session.save normalized reason=${reason}`
|
|
7511
|
+
);
|
|
7512
|
+
}
|
|
7513
|
+
const snapshot = buildSessionState(
|
|
7514
|
+
sessionId,
|
|
7515
|
+
runId,
|
|
7516
|
+
snapshotMessages,
|
|
7517
|
+
session.invoke_seq
|
|
7518
|
+
);
|
|
7519
|
+
await sessionStateStore.save(snapshot);
|
|
7520
|
+
logRunDebug(log2, runId, `session.save ${reason}`);
|
|
7521
|
+
}).catch((error) => {
|
|
7522
|
+
log2(`Error: session-state save failed: ${String(error)}`);
|
|
7523
|
+
});
|
|
7524
|
+
await sessionSaveChain;
|
|
7525
|
+
};
|
|
7526
|
+
const maybeDebouncedSessionSave = () => {
|
|
7527
|
+
const now = Date.now();
|
|
7528
|
+
if (now - lastSessionSaveAt < SESSION_STATE_SAVE_DEBOUNCE_MS) {
|
|
7529
|
+
return;
|
|
7530
|
+
}
|
|
7531
|
+
lastSessionSaveAt = now;
|
|
7532
|
+
void queueSessionSave("debounced");
|
|
7533
|
+
};
|
|
5977
7534
|
try {
|
|
5978
7535
|
logRunDebug(
|
|
5979
7536
|
log2,
|
|
5980
7537
|
runId,
|
|
5981
|
-
`stream.start input_chars=${
|
|
7538
|
+
`stream.start input_chars=${runInputLengthForDebug(normalizedInput)}`
|
|
5982
7539
|
);
|
|
5983
|
-
for await (const event of runtimeAgent.runStream(
|
|
7540
|
+
for await (const event of runtimeAgent.runStream(normalizedInput, {
|
|
5984
7541
|
session,
|
|
5985
7542
|
signal: runAbortController.signal,
|
|
5986
7543
|
forceCompaction: params.force_compaction
|
|
5987
7544
|
})) {
|
|
5988
7545
|
if (state.cancelRequested) {
|
|
5989
|
-
cancelledWhileStreaming = true;
|
|
5990
7546
|
break;
|
|
5991
7547
|
}
|
|
5992
7548
|
if (event.type === "final") {
|
|
5993
7549
|
finalResponse = event.content;
|
|
7550
|
+
await queueSessionSave("final");
|
|
5994
7551
|
}
|
|
5995
7552
|
if (event.type === "compaction_complete") {
|
|
5996
7553
|
logCompactionSnapshot(log2, runId, runtimeAgent, event.compacted);
|
|
@@ -6014,7 +7571,7 @@ var createRunHandlers = ({
|
|
|
6014
7571
|
appendSession({
|
|
6015
7572
|
type: "agent.event",
|
|
6016
7573
|
run_id: runId,
|
|
6017
|
-
ts:
|
|
7574
|
+
ts: nowIso4(),
|
|
6018
7575
|
seq,
|
|
6019
7576
|
event
|
|
6020
7577
|
});
|
|
@@ -6025,14 +7582,16 @@ var createRunHandlers = ({
|
|
|
6025
7582
|
appendSession({
|
|
6026
7583
|
type: "run.context",
|
|
6027
7584
|
run_id: runId,
|
|
6028
|
-
ts:
|
|
7585
|
+
ts: nowIso4(),
|
|
6029
7586
|
context_left_percent: contextLeftPercent
|
|
6030
7587
|
});
|
|
6031
7588
|
}
|
|
7589
|
+
maybeDebouncedSessionSave();
|
|
6032
7590
|
}
|
|
6033
|
-
if (
|
|
7591
|
+
if (state.cancelRequested) {
|
|
6034
7592
|
normalizeRunHistoryAfterCancel(runId, runtimeAgent);
|
|
6035
7593
|
}
|
|
7594
|
+
await queueSessionSave("terminal");
|
|
6036
7595
|
const status = state.cancelRequested ? "cancelled" : "completed";
|
|
6037
7596
|
emitRunStatus(runId, status);
|
|
6038
7597
|
emitRunEnd(
|
|
@@ -6044,15 +7603,17 @@ var createRunHandlers = ({
|
|
|
6044
7603
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
6045
7604
|
if (state.cancelRequested || isAbortLikeError(err)) {
|
|
6046
7605
|
normalizeRunHistoryAfterCancel(runId, runtimeAgent);
|
|
7606
|
+
await queueSessionSave("cancelled");
|
|
6047
7607
|
emitRunStatus(runId, "cancelled", err.message || "cancelled");
|
|
6048
7608
|
emitRunEnd(runId, "cancelled", finalResponse);
|
|
6049
7609
|
return;
|
|
6050
7610
|
}
|
|
7611
|
+
await queueSessionSave("error");
|
|
6051
7612
|
emitRunStatus(runId, "error", err.message);
|
|
6052
7613
|
appendSession({
|
|
6053
7614
|
type: "run.error",
|
|
6054
7615
|
run_id: runId,
|
|
6055
|
-
ts:
|
|
7616
|
+
ts: nowIso4(),
|
|
6056
7617
|
error: {
|
|
6057
7618
|
name: err.name,
|
|
6058
7619
|
message: err.message,
|
|
@@ -6062,18 +7623,10 @@ var createRunHandlers = ({
|
|
|
6062
7623
|
emitRunEnd(runId, "error");
|
|
6063
7624
|
} finally {
|
|
6064
7625
|
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
|
-
});
|
|
7626
|
+
if (state.cancelRequested) {
|
|
7627
|
+
normalizeRunHistoryAfterCancel(runId, runtimeAgent);
|
|
6076
7628
|
}
|
|
7629
|
+
await queueSessionSave("finally");
|
|
6077
7630
|
if (activeRunAbort?.runId === runId) {
|
|
6078
7631
|
activeRunAbort = null;
|
|
6079
7632
|
}
|
|
@@ -6099,19 +7652,20 @@ var createRunHandlers = ({
|
|
|
6099
7652
|
log2(`run.cancel ${params.run_id} (${params.reason ?? "no reason"})`);
|
|
6100
7653
|
return;
|
|
6101
7654
|
}
|
|
6102
|
-
sendError(id, { code:
|
|
7655
|
+
sendError(id, { code: import_protocol5.RPC_ERROR_CODE.RUN_NOT_FOUND, message: "run not found" });
|
|
6103
7656
|
};
|
|
6104
7657
|
return { handleRunStart, handleRunCancel };
|
|
6105
7658
|
};
|
|
6106
7659
|
|
|
6107
7660
|
// src/rpc/skills.ts
|
|
6108
|
-
var
|
|
7661
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
7662
|
+
var import_protocol6 = require("@codelia/protocol");
|
|
6109
7663
|
var createSkillsHandlers = ({
|
|
6110
7664
|
state,
|
|
6111
7665
|
log: log2
|
|
6112
7666
|
}) => {
|
|
6113
7667
|
const handleSkillsList = async (id, params) => {
|
|
6114
|
-
const requestedCwd = params?.cwd ?
|
|
7668
|
+
const requestedCwd = params?.cwd ? import_node_path14.default.resolve(params.cwd) : void 0;
|
|
6115
7669
|
const workingDir = requestedCwd ?? state.runtimeWorkingDir ?? state.lastUiContext?.cwd ?? process.cwd();
|
|
6116
7670
|
const forceReload = params?.force_reload ?? false;
|
|
6117
7671
|
if (!forceReload) {
|
|
@@ -6135,7 +7689,7 @@ var createSkillsHandlers = ({
|
|
|
6135
7689
|
});
|
|
6136
7690
|
} catch (error) {
|
|
6137
7691
|
sendError(id, {
|
|
6138
|
-
code:
|
|
7692
|
+
code: import_protocol6.RPC_ERROR_CODE.RUNTIME_INTERNAL,
|
|
6139
7693
|
message: `skills resolver init failed: ${String(error)}`
|
|
6140
7694
|
});
|
|
6141
7695
|
return;
|
|
@@ -6157,7 +7711,7 @@ var createSkillsHandlers = ({
|
|
|
6157
7711
|
} catch (error) {
|
|
6158
7712
|
log2(`skills.list error: ${String(error)}`);
|
|
6159
7713
|
sendError(id, {
|
|
6160
|
-
code:
|
|
7714
|
+
code: import_protocol6.RPC_ERROR_CODE.RUNTIME_INTERNAL,
|
|
6161
7715
|
message: `skills list failed: ${String(error)}`
|
|
6162
7716
|
});
|
|
6163
7717
|
}
|
|
@@ -6165,6 +7719,75 @@ var createSkillsHandlers = ({
|
|
|
6165
7719
|
return { handleSkillsList };
|
|
6166
7720
|
};
|
|
6167
7721
|
|
|
7722
|
+
// src/rpc/tool.ts
|
|
7723
|
+
var import_protocol7 = require("@codelia/protocol");
|
|
7724
|
+
var normalizeToolResult = (result) => {
|
|
7725
|
+
switch (result.type) {
|
|
7726
|
+
case "json":
|
|
7727
|
+
return result.value;
|
|
7728
|
+
case "text":
|
|
7729
|
+
return result.text;
|
|
7730
|
+
case "parts":
|
|
7731
|
+
return result.parts;
|
|
7732
|
+
}
|
|
7733
|
+
};
|
|
7734
|
+
var createToolContext = () => {
|
|
7735
|
+
const deps = /* @__PURE__ */ Object.create(null);
|
|
7736
|
+
const cache = /* @__PURE__ */ new Map();
|
|
7737
|
+
const resolve = async (key) => {
|
|
7738
|
+
if (cache.has(key.id)) {
|
|
7739
|
+
return cache.get(key.id);
|
|
7740
|
+
}
|
|
7741
|
+
const value = await key.create();
|
|
7742
|
+
cache.set(key.id, value);
|
|
7743
|
+
return value;
|
|
7744
|
+
};
|
|
7745
|
+
return {
|
|
7746
|
+
deps,
|
|
7747
|
+
resolve,
|
|
7748
|
+
now: () => /* @__PURE__ */ new Date()
|
|
7749
|
+
};
|
|
7750
|
+
};
|
|
7751
|
+
var createToolHandlers = ({
|
|
7752
|
+
state,
|
|
7753
|
+
getAgent
|
|
7754
|
+
}) => {
|
|
7755
|
+
const handleToolCall = async (id, params) => {
|
|
7756
|
+
const toolName = params?.name;
|
|
7757
|
+
if (!toolName) {
|
|
7758
|
+
sendError(id, { code: import_protocol7.RPC_ERROR_CODE.INVALID_PARAMS, message: "tool name is required" });
|
|
7759
|
+
return;
|
|
7760
|
+
}
|
|
7761
|
+
if (!state.tools) {
|
|
7762
|
+
await getAgent();
|
|
7763
|
+
}
|
|
7764
|
+
const tool = state.tools?.find((entry) => entry.name === toolName);
|
|
7765
|
+
if (!tool) {
|
|
7766
|
+
sendError(id, { code: import_protocol7.RPC_ERROR_CODE.INVALID_PARAMS, message: `unknown tool: ${toolName}` });
|
|
7767
|
+
return;
|
|
7768
|
+
}
|
|
7769
|
+
try {
|
|
7770
|
+
const result = await tool.executeRaw(
|
|
7771
|
+
JSON.stringify(params?.arguments ?? {}),
|
|
7772
|
+
createToolContext()
|
|
7773
|
+
);
|
|
7774
|
+
const response = {
|
|
7775
|
+
ok: true,
|
|
7776
|
+
result: normalizeToolResult(result)
|
|
7777
|
+
};
|
|
7778
|
+
sendResult(id, response);
|
|
7779
|
+
} catch (error) {
|
|
7780
|
+
sendError(id, {
|
|
7781
|
+
code: import_protocol7.RPC_ERROR_CODE.RUNTIME_INTERNAL,
|
|
7782
|
+
message: `tool call failed: ${String(error)}`
|
|
7783
|
+
});
|
|
7784
|
+
}
|
|
7785
|
+
};
|
|
7786
|
+
return {
|
|
7787
|
+
handleToolCall
|
|
7788
|
+
};
|
|
7789
|
+
};
|
|
7790
|
+
|
|
6168
7791
|
// src/rpc/handlers.ts
|
|
6169
7792
|
var createRuntimeHandlers = ({
|
|
6170
7793
|
state,
|
|
@@ -6172,31 +7795,132 @@ var createRuntimeHandlers = ({
|
|
|
6172
7795
|
log: log2,
|
|
6173
7796
|
mcpManager: injectedMcpManager,
|
|
6174
7797
|
sessionStateStore: injectedSessionStateStore,
|
|
6175
|
-
runEventStoreFactory: injectedRunEventStoreFactory
|
|
7798
|
+
runEventStoreFactory: injectedRunEventStoreFactory,
|
|
7799
|
+
buildProviderModelList: injectedBuildProviderModelList
|
|
6176
7800
|
}) => {
|
|
6177
|
-
const sessionStateStore = injectedSessionStateStore ?? new
|
|
7801
|
+
const sessionStateStore = injectedSessionStateStore ?? new import_storage8.SessionStateStoreImpl({
|
|
6178
7802
|
onError: (error, context) => {
|
|
6179
7803
|
log2(
|
|
6180
7804
|
`session-state ${context.action} error${context.detail ? ` (${context.detail})` : ""}: ${String(error)}`
|
|
6181
7805
|
);
|
|
6182
7806
|
}
|
|
6183
7807
|
});
|
|
6184
|
-
const runEventStoreFactory = injectedRunEventStoreFactory ?? new
|
|
7808
|
+
const runEventStoreFactory = injectedRunEventStoreFactory ?? new import_storage8.RunEventStoreFactoryImpl();
|
|
6185
7809
|
const mcpManager = injectedMcpManager ?? {
|
|
6186
7810
|
start: async () => void 0,
|
|
6187
7811
|
list: () => ({ servers: [] })
|
|
6188
7812
|
};
|
|
7813
|
+
const buildProviderModelList2 = injectedBuildProviderModelList ?? buildProviderModelList;
|
|
7814
|
+
let startupOnboardingPromise = null;
|
|
7815
|
+
let startupOnboardingStarted = false;
|
|
6189
7816
|
const appendSession = (record) => {
|
|
6190
7817
|
if (!state.sessionAppend) return;
|
|
6191
7818
|
state.sessionAppend(record);
|
|
6192
7819
|
};
|
|
7820
|
+
const formatUsdPer1M = (value) => {
|
|
7821
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
7822
|
+
return null;
|
|
7823
|
+
}
|
|
7824
|
+
const fixed = value.toFixed(4);
|
|
7825
|
+
return fixed.replace(/\.?0+$/, "");
|
|
7826
|
+
};
|
|
7827
|
+
const buildModelPickDetail = (details) => {
|
|
7828
|
+
const parts = [];
|
|
7829
|
+
if (details?.release_date) {
|
|
7830
|
+
parts.push(`released ${details.release_date}`);
|
|
7831
|
+
}
|
|
7832
|
+
const inputCost = formatUsdPer1M(details?.cost_per_1m_input_tokens_usd);
|
|
7833
|
+
const outputCost = formatUsdPer1M(details?.cost_per_1m_output_tokens_usd);
|
|
7834
|
+
if (inputCost !== null || outputCost !== null) {
|
|
7835
|
+
parts.push(
|
|
7836
|
+
`cost in/out ${inputCost ?? "-"}/${outputCost ?? "-"} USD per 1M`
|
|
7837
|
+
);
|
|
7838
|
+
}
|
|
7839
|
+
return parts.join(" \u2022 ");
|
|
7840
|
+
};
|
|
7841
|
+
const runStartupOnboarding = async () => {
|
|
7842
|
+
const supportsPick = !!state.uiCapabilities?.supports_pick;
|
|
7843
|
+
const supportsPrompt = !!state.uiCapabilities?.supports_prompt;
|
|
7844
|
+
if (!supportsPick || !supportsPrompt) {
|
|
7845
|
+
return;
|
|
7846
|
+
}
|
|
7847
|
+
const authResolver = await AuthResolver.create(state, log2);
|
|
7848
|
+
if (authResolver.hasAnyAvailableAuth()) {
|
|
7849
|
+
return;
|
|
7850
|
+
}
|
|
7851
|
+
const providerPick = await requestUiPick(state, {
|
|
7852
|
+
title: "Let's set up your provider to get started.",
|
|
7853
|
+
items: SUPPORTED_PROVIDERS.map((provider2) => ({
|
|
7854
|
+
id: provider2,
|
|
7855
|
+
label: provider2,
|
|
7856
|
+
detail: provider2 === "openai" ? "OAuth (ChatGPT Plus/Pro) or API key" : provider2 === "openrouter" ? "API key (OpenRouter)" : "API key"
|
|
7857
|
+
})),
|
|
7858
|
+
multi: false
|
|
7859
|
+
});
|
|
7860
|
+
const pickedProvider = providerPick?.ids?.[0];
|
|
7861
|
+
if (!pickedProvider || !SUPPORTED_PROVIDERS.includes(pickedProvider)) {
|
|
7862
|
+
log2("startup onboarding skipped (provider not selected)");
|
|
7863
|
+
return;
|
|
7864
|
+
}
|
|
7865
|
+
const provider = pickedProvider;
|
|
7866
|
+
try {
|
|
7867
|
+
await authResolver.resolveProviderAuth(provider);
|
|
7868
|
+
} catch (error) {
|
|
7869
|
+
log2(`startup onboarding auth failed: ${String(error)}`);
|
|
7870
|
+
return;
|
|
7871
|
+
}
|
|
7872
|
+
const { models, details } = await buildProviderModelList2({
|
|
7873
|
+
provider,
|
|
7874
|
+
includeDetails: true,
|
|
7875
|
+
state,
|
|
7876
|
+
log: log2
|
|
7877
|
+
});
|
|
7878
|
+
if (!models.length) {
|
|
7879
|
+
log2(`startup onboarding model list returned no models for ${provider}`);
|
|
7880
|
+
return;
|
|
7881
|
+
}
|
|
7882
|
+
const modelPick = await requestUiPick(state, {
|
|
7883
|
+
title: `Select model (${provider})`,
|
|
7884
|
+
items: models.map((model) => ({
|
|
7885
|
+
id: model,
|
|
7886
|
+
label: model,
|
|
7887
|
+
detail: buildModelPickDetail(details?.[model]) || void 0
|
|
7888
|
+
})),
|
|
7889
|
+
multi: false
|
|
7890
|
+
});
|
|
7891
|
+
const selectedModel = modelPick?.ids?.[0];
|
|
7892
|
+
if (!selectedModel || !models.includes(selectedModel)) {
|
|
7893
|
+
log2("startup onboarding skipped (model not selected)");
|
|
7894
|
+
return;
|
|
7895
|
+
}
|
|
7896
|
+
const configPath = resolveConfigPath();
|
|
7897
|
+
await (0, import_config_loader3.updateModelConfig)(configPath, { provider, name: selectedModel });
|
|
7898
|
+
state.agent = null;
|
|
7899
|
+
log2(`startup onboarding completed: ${provider}/${selectedModel}`);
|
|
7900
|
+
};
|
|
7901
|
+
const launchStartupOnboarding = () => {
|
|
7902
|
+
if (startupOnboardingStarted) {
|
|
7903
|
+
return;
|
|
7904
|
+
}
|
|
7905
|
+
startupOnboardingStarted = true;
|
|
7906
|
+
startupOnboardingPromise = runStartupOnboarding().catch((error) => {
|
|
7907
|
+
log2(`startup onboarding failed: ${String(error)}`);
|
|
7908
|
+
}).finally(() => {
|
|
7909
|
+
startupOnboardingPromise = null;
|
|
7910
|
+
});
|
|
7911
|
+
};
|
|
7912
|
+
const waitForStartupOnboarding = async () => {
|
|
7913
|
+
if (!startupOnboardingPromise) return;
|
|
7914
|
+
await startupOnboardingPromise;
|
|
7915
|
+
};
|
|
6193
7916
|
const { handleRunStart, handleRunCancel } = createRunHandlers({
|
|
6194
7917
|
state,
|
|
6195
7918
|
getAgent,
|
|
6196
7919
|
log: log2,
|
|
6197
7920
|
runEventStoreFactory,
|
|
6198
7921
|
sessionStateStore,
|
|
6199
|
-
appendSession
|
|
7922
|
+
appendSession,
|
|
7923
|
+
beforeRunStart: waitForStartupOnboarding
|
|
6200
7924
|
});
|
|
6201
7925
|
const { handleSessionList, handleSessionHistory } = createHistoryHandlers({
|
|
6202
7926
|
sessionStateStore,
|
|
@@ -6214,6 +7938,10 @@ var createRuntimeHandlers = ({
|
|
|
6214
7938
|
state,
|
|
6215
7939
|
log: log2
|
|
6216
7940
|
});
|
|
7941
|
+
const { handleToolCall } = createToolHandlers({
|
|
7942
|
+
state,
|
|
7943
|
+
getAgent
|
|
7944
|
+
});
|
|
6217
7945
|
const handleInitialize = (id, params) => {
|
|
6218
7946
|
const result = {
|
|
6219
7947
|
protocol_version: PROTOCOL_VERSION,
|
|
@@ -6223,13 +7951,16 @@ var createRuntimeHandlers = ({
|
|
|
6223
7951
|
supports_ui_requests: true,
|
|
6224
7952
|
supports_mcp_list: true,
|
|
6225
7953
|
supports_skills_list: true,
|
|
6226
|
-
supports_context_inspect: true
|
|
7954
|
+
supports_context_inspect: true,
|
|
7955
|
+
supports_tool_call: true,
|
|
7956
|
+
supports_permission_preflight_events: true
|
|
6227
7957
|
}
|
|
6228
7958
|
};
|
|
6229
7959
|
sendResult(id, result);
|
|
6230
7960
|
log2(`initialize from ${params.client?.name ?? "unknown"}`);
|
|
6231
7961
|
state.lastClientInfo = params.client ?? null;
|
|
6232
7962
|
state.setUiCapabilities(params.ui_capabilities);
|
|
7963
|
+
launchStartupOnboarding();
|
|
6233
7964
|
};
|
|
6234
7965
|
const handleUiContextUpdate = (params) => {
|
|
6235
7966
|
state.updateUiContext(params);
|
|
@@ -6239,7 +7970,7 @@ var createRuntimeHandlers = ({
|
|
|
6239
7970
|
};
|
|
6240
7971
|
const handleAuthLogout = async (id, params) => {
|
|
6241
7972
|
if (state.activeRunId) {
|
|
6242
|
-
sendError(id, { code:
|
|
7973
|
+
sendError(id, { code: import_protocol8.RPC_ERROR_CODE.RUNTIME_BUSY, message: "runtime busy" });
|
|
6243
7974
|
return;
|
|
6244
7975
|
}
|
|
6245
7976
|
const clearSession = params?.clear_session ?? true;
|
|
@@ -6247,7 +7978,7 @@ var createRuntimeHandlers = ({
|
|
|
6247
7978
|
const supportsConfirm = !!state.uiCapabilities?.supports_confirm;
|
|
6248
7979
|
if (!supportsConfirm) {
|
|
6249
7980
|
sendError(id, {
|
|
6250
|
-
code:
|
|
7981
|
+
code: import_protocol8.RPC_ERROR_CODE.RUNTIME_INTERNAL,
|
|
6251
7982
|
message: "UI confirmation is required for logout"
|
|
6252
7983
|
});
|
|
6253
7984
|
return;
|
|
@@ -6289,7 +8020,7 @@ var createRuntimeHandlers = ({
|
|
|
6289
8020
|
log2(`auth.logout session_cleared=${clearSession}`);
|
|
6290
8021
|
} catch (error) {
|
|
6291
8022
|
sendError(id, {
|
|
6292
|
-
code:
|
|
8023
|
+
code: import_protocol8.RPC_ERROR_CODE.RUNTIME_INTERNAL,
|
|
6293
8024
|
message: `auth logout failed: ${String(error)}`
|
|
6294
8025
|
});
|
|
6295
8026
|
}
|
|
@@ -6312,6 +8043,8 @@ var createRuntimeHandlers = ({
|
|
|
6312
8043
|
return handleModelList(req.id, req.params);
|
|
6313
8044
|
case "model.set":
|
|
6314
8045
|
return handleModelSet(req.id, req.params);
|
|
8046
|
+
case "tool.call":
|
|
8047
|
+
return handleToolCall(req.id, req.params);
|
|
6315
8048
|
case "mcp.list":
|
|
6316
8049
|
await mcpManager.start?.();
|
|
6317
8050
|
return sendResult(
|
|
@@ -6323,7 +8056,7 @@ var createRuntimeHandlers = ({
|
|
|
6323
8056
|
case "context.inspect":
|
|
6324
8057
|
return handleContextInspect(req.id, req.params);
|
|
6325
8058
|
default:
|
|
6326
|
-
return sendError(req.id, { code:
|
|
8059
|
+
return sendError(req.id, { code: import_protocol8.RPC_ERROR_CODE.METHOD_NOT_FOUND, message: "method not found" });
|
|
6327
8060
|
}
|
|
6328
8061
|
};
|
|
6329
8062
|
const handleNotification = (note) => {
|
|
@@ -6350,7 +8083,7 @@ var createRuntimeHandlers = ({
|
|
|
6350
8083
|
};
|
|
6351
8084
|
|
|
6352
8085
|
// src/runtime-state.ts
|
|
6353
|
-
var
|
|
8086
|
+
var import_node_crypto6 = __toESM(require("crypto"), 1);
|
|
6354
8087
|
var RuntimeState = class {
|
|
6355
8088
|
runSeq = /* @__PURE__ */ new Map();
|
|
6356
8089
|
uiRequestCounter = 0;
|
|
@@ -6363,6 +8096,7 @@ var RuntimeState = class {
|
|
|
6363
8096
|
uiCapabilities = null;
|
|
6364
8097
|
systemPrompt = null;
|
|
6365
8098
|
toolDefinitions = null;
|
|
8099
|
+
tools = null;
|
|
6366
8100
|
sessionId = null;
|
|
6367
8101
|
sessionAppend = null;
|
|
6368
8102
|
agent = null;
|
|
@@ -6373,10 +8107,10 @@ var RuntimeState = class {
|
|
|
6373
8107
|
runtimeWorkingDir = null;
|
|
6374
8108
|
runtimeSandboxRoot = null;
|
|
6375
8109
|
nextRunId() {
|
|
6376
|
-
return
|
|
8110
|
+
return import_node_crypto6.default.randomUUID();
|
|
6377
8111
|
}
|
|
6378
8112
|
nextSessionId() {
|
|
6379
|
-
return
|
|
8113
|
+
return import_node_crypto6.default.randomUUID();
|
|
6380
8114
|
}
|
|
6381
8115
|
beginRun(runId, uiContext) {
|
|
6382
8116
|
this.activeRunId = runId;
|
|
@@ -6466,42 +8200,59 @@ var RuntimeState = class {
|
|
|
6466
8200
|
|
|
6467
8201
|
// src/runtime.ts
|
|
6468
8202
|
var startRuntime = () => {
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
|
|
6472
|
-
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
const { processMessage } = createRuntimeHandlers({
|
|
6480
|
-
state,
|
|
6481
|
-
getAgent,
|
|
6482
|
-
log: import_logger.log,
|
|
6483
|
-
mcpManager
|
|
6484
|
-
});
|
|
6485
|
-
(0, import_logger.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
|
-
(0, import_logger.log)(`invalid json: ${String(error)}`);
|
|
6500
|
-
}
|
|
8203
|
+
void (async () => {
|
|
8204
|
+
const state = new RuntimeState();
|
|
8205
|
+
const workingDir = process.env.CODELIA_SANDBOX_ROOT ? import_node_path15.default.resolve(process.env.CODELIA_SANDBOX_ROOT) : process.cwd();
|
|
8206
|
+
state.runtimeWorkingDir = workingDir;
|
|
8207
|
+
state.runtimeSandboxRoot = workingDir;
|
|
8208
|
+
const sessionStateStore = new import_storage9.SessionStateStoreImpl({
|
|
8209
|
+
onError: (error, context) => {
|
|
8210
|
+
(0, import_logger.log)(
|
|
8211
|
+
`Error: session-state ${context.action} error${context.detail ? ` (${context.detail})` : ""}: ${String(error)}`
|
|
8212
|
+
);
|
|
6501
8213
|
}
|
|
6502
|
-
|
|
8214
|
+
});
|
|
8215
|
+
try {
|
|
8216
|
+
await sessionStateStore.list();
|
|
8217
|
+
} catch (error) {
|
|
8218
|
+
(0, import_logger.log)(`Error: session index database is not available: ${String(error)}`);
|
|
8219
|
+
process.exit(1);
|
|
8220
|
+
return;
|
|
6503
8221
|
}
|
|
6504
|
-
|
|
8222
|
+
const mcpManager = new McpManager({ workingDir, log: import_logger.log });
|
|
8223
|
+
void mcpManager.start({
|
|
8224
|
+
onStatus: (message) => (0, import_logger.log)(`mcp: ${message}`),
|
|
8225
|
+
requestOAuthTokens: ({ server_id, oauth: oauth2, error }) => requestMcpOAuthTokensWithRunStatus(state, server_id, oauth2, error)
|
|
8226
|
+
});
|
|
8227
|
+
const getAgent = createAgentFactory(state, { mcpManager });
|
|
8228
|
+
const { processMessage } = createRuntimeHandlers({
|
|
8229
|
+
state,
|
|
8230
|
+
getAgent,
|
|
8231
|
+
log: import_logger.log,
|
|
8232
|
+
mcpManager,
|
|
8233
|
+
sessionStateStore
|
|
8234
|
+
});
|
|
8235
|
+
(0, import_logger.log)("runtime started");
|
|
8236
|
+
process.stdin.setEncoding("utf8");
|
|
8237
|
+
let buffer = "";
|
|
8238
|
+
process.stdin.on("data", (chunk) => {
|
|
8239
|
+
buffer += chunk;
|
|
8240
|
+
let index = buffer.indexOf("\n");
|
|
8241
|
+
while (index >= 0) {
|
|
8242
|
+
const line = buffer.slice(0, index).trim();
|
|
8243
|
+
buffer = buffer.slice(index + 1);
|
|
8244
|
+
if (line) {
|
|
8245
|
+
try {
|
|
8246
|
+
const msg = JSON.parse(line);
|
|
8247
|
+
processMessage(msg);
|
|
8248
|
+
} catch (error) {
|
|
8249
|
+
(0, import_logger.log)(`invalid json: ${String(error)}`);
|
|
8250
|
+
}
|
|
8251
|
+
}
|
|
8252
|
+
index = buffer.indexOf("\n");
|
|
8253
|
+
}
|
|
8254
|
+
});
|
|
8255
|
+
})();
|
|
6505
8256
|
};
|
|
6506
8257
|
|
|
6507
8258
|
// src/index.ts
|