leglas 0.5.0 → 0.6.0
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/README.md +16 -6
- package/dist/bin.js +139 -30
- package/dist/index.js +139 -30
- package/dist/shell/assets/index-Bfon5OrV.js +14 -0
- package/dist/shell/assets/index-CIdZiWhI.css +1 -0
- package/dist/shell/index.html +2 -2
- package/package.json +1 -1
- package/dist/shell/assets/index-B7_ncDLZ.js +0 -14
- package/dist/shell/assets/index-f2TzxtQm.css +0 -1
package/dist/index.js
CHANGED
|
@@ -952,9 +952,12 @@ function nextRequest(requests, failed) {
|
|
|
952
952
|
|
|
953
953
|
// ../server/dist/agents.js
|
|
954
954
|
import { spawn } from "child_process";
|
|
955
|
-
import { constants } from "fs";
|
|
955
|
+
import { constants, readdirSync } from "fs";
|
|
956
956
|
import { access, mkdir, readFile, writeFile } from "fs/promises";
|
|
957
957
|
import { delimiter, dirname, isAbsolute, join, relative } from "path";
|
|
958
|
+
var AGENT_EFFORTS = ["low", "medium", "high", "xhigh", "max"];
|
|
959
|
+
var effortFlag = (effort) => effort === null ? [] : ["--effort", effort];
|
|
960
|
+
var codexEffortConfig = (effort) => effort === null ? [] : ["-c", `model_reasoning_effort=${effort}`];
|
|
958
961
|
var CODEX_WORKSPACE_CONFIG = [
|
|
959
962
|
"-c",
|
|
960
963
|
"sandbox_workspace_write.network_access=true"
|
|
@@ -963,22 +966,25 @@ var KNOWN_AGENTS = {
|
|
|
963
966
|
claude: {
|
|
964
967
|
name: "Claude",
|
|
965
968
|
binary: "claude",
|
|
966
|
-
|
|
969
|
+
efforts: AGENT_EFFORTS,
|
|
970
|
+
args: (prompt, effort = null) => [
|
|
967
971
|
"-p",
|
|
968
972
|
prompt,
|
|
969
973
|
"--output-format",
|
|
970
974
|
"stream-json",
|
|
971
975
|
"--verbose",
|
|
972
976
|
"--permission-mode",
|
|
973
|
-
"acceptEdits"
|
|
977
|
+
"acceptEdits",
|
|
978
|
+
...effortFlag(effort)
|
|
974
979
|
],
|
|
975
|
-
terminalArgs: (prompt) => [
|
|
980
|
+
terminalArgs: (prompt, effort = null) => [
|
|
976
981
|
"-p",
|
|
977
982
|
prompt,
|
|
978
983
|
"--permission-mode",
|
|
979
|
-
"acceptEdits"
|
|
984
|
+
"acceptEdits",
|
|
985
|
+
...effortFlag(effort)
|
|
980
986
|
],
|
|
981
|
-
resumeArgs: (sessionId, prompt) => [
|
|
987
|
+
resumeArgs: (sessionId, prompt, effort = null) => [
|
|
982
988
|
"-p",
|
|
983
989
|
"--resume",
|
|
984
990
|
sessionId,
|
|
@@ -987,7 +993,8 @@ var KNOWN_AGENTS = {
|
|
|
987
993
|
"stream-json",
|
|
988
994
|
"--verbose",
|
|
989
995
|
"--permission-mode",
|
|
990
|
-
"acceptEdits"
|
|
996
|
+
"acceptEdits",
|
|
997
|
+
...effortFlag(effort)
|
|
991
998
|
],
|
|
992
999
|
// Non-interactive Claude cannot approve a Bash call: acceptEdits covers
|
|
993
1000
|
// files, so a command the prompt requires is refused every time with
|
|
@@ -1015,6 +1022,7 @@ var KNOWN_AGENTS = {
|
|
|
1015
1022
|
codex: {
|
|
1016
1023
|
name: "Codex",
|
|
1017
1024
|
binary: "codex",
|
|
1025
|
+
efforts: AGENT_EFFORTS,
|
|
1018
1026
|
// `--skip-git-repo-check` is what lets Codex run at all in a project the
|
|
1019
1027
|
// user never put under version control: without it codex-cli refuses
|
|
1020
1028
|
// before it reaches a model, with "Not inside a trusted directory and
|
|
@@ -1023,18 +1031,20 @@ var KNOWN_AGENTS = {
|
|
|
1023
1031
|
// moves that precondition and only that: `-s workspace-write` still
|
|
1024
1032
|
// confines writes to the project, so the sandbox boundary is unchanged,
|
|
1025
1033
|
// and in a git repository the flag does nothing at all.
|
|
1026
|
-
args: (prompt) => [
|
|
1034
|
+
args: (prompt, effort = null) => [
|
|
1027
1035
|
"exec",
|
|
1028
1036
|
"--json",
|
|
1029
1037
|
...CODEX_WORKSPACE_CONFIG,
|
|
1038
|
+
...codexEffortConfig(effort),
|
|
1030
1039
|
"-s",
|
|
1031
1040
|
"workspace-write",
|
|
1032
1041
|
"--skip-git-repo-check",
|
|
1033
1042
|
prompt
|
|
1034
1043
|
],
|
|
1035
|
-
terminalArgs: (prompt) => [
|
|
1044
|
+
terminalArgs: (prompt, effort = null) => [
|
|
1036
1045
|
"exec",
|
|
1037
1046
|
...CODEX_WORKSPACE_CONFIG,
|
|
1047
|
+
...codexEffortConfig(effort),
|
|
1038
1048
|
"-s",
|
|
1039
1049
|
"workspace-write",
|
|
1040
1050
|
"--skip-git-repo-check",
|
|
@@ -1043,12 +1053,13 @@ var KNOWN_AGENTS = {
|
|
|
1043
1053
|
// No sandbox flag here: `codex exec resume` refuses it and inherits the
|
|
1044
1054
|
// session's own sandbox, which the first turn set to workspace-write. The
|
|
1045
1055
|
// repository check is per invocation, so resume needs the flag of its own.
|
|
1046
|
-
resumeArgs: (sessionId, prompt) => [
|
|
1056
|
+
resumeArgs: (sessionId, prompt, effort = null) => [
|
|
1047
1057
|
"exec",
|
|
1048
1058
|
"resume",
|
|
1049
1059
|
sessionId,
|
|
1050
1060
|
"--json",
|
|
1051
1061
|
...CODEX_WORKSPACE_CONFIG,
|
|
1062
|
+
...codexEffortConfig(effort),
|
|
1052
1063
|
"--skip-git-repo-check",
|
|
1053
1064
|
prompt
|
|
1054
1065
|
],
|
|
@@ -1060,8 +1071,14 @@ var KNOWN_AGENTS = {
|
|
|
1060
1071
|
cursor: {
|
|
1061
1072
|
name: "Cursor",
|
|
1062
1073
|
binary: "cursor-agent",
|
|
1063
|
-
|
|
1064
|
-
|
|
1074
|
+
efforts: [],
|
|
1075
|
+
args: (prompt, _effort = null) => [
|
|
1076
|
+
"-p",
|
|
1077
|
+
prompt,
|
|
1078
|
+
"--output-format",
|
|
1079
|
+
"stream-json"
|
|
1080
|
+
],
|
|
1081
|
+
terminalArgs: (prompt, _effort = null) => ["-p", prompt],
|
|
1065
1082
|
authArgs: ["status"],
|
|
1066
1083
|
// UNVERIFIED: cursor-agent was not available on the build machine. The
|
|
1067
1084
|
// reading is deliberately loose, and anything ambiguous stays unknown.
|
|
@@ -1079,7 +1096,11 @@ function execProbe(binary, args, timeoutMs = PROBE_TIMEOUT_MS) {
|
|
|
1079
1096
|
return new Promise((resolve) => {
|
|
1080
1097
|
let child;
|
|
1081
1098
|
try {
|
|
1082
|
-
child = spawn(binary, [...args], {
|
|
1099
|
+
child = spawn(binary, [...args], {
|
|
1100
|
+
env: agentEnvironment(),
|
|
1101
|
+
shell: false,
|
|
1102
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1103
|
+
});
|
|
1083
1104
|
} catch {
|
|
1084
1105
|
return resolve(null);
|
|
1085
1106
|
}
|
|
@@ -1102,8 +1123,50 @@ function execProbe(binary, args, timeoutMs = PROBE_TIMEOUT_MS) {
|
|
|
1102
1123
|
});
|
|
1103
1124
|
});
|
|
1104
1125
|
}
|
|
1126
|
+
function agentSearchPath(env = process.env, platform = process.platform) {
|
|
1127
|
+
const home = env.HOME ?? env.USERPROFILE ?? "";
|
|
1128
|
+
const npmPrefix = env.NPM_CONFIG_PREFIX;
|
|
1129
|
+
const versionBins = (root, suffix) => {
|
|
1130
|
+
try {
|
|
1131
|
+
return readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join(root, entry.name, ...suffix));
|
|
1132
|
+
} catch {
|
|
1133
|
+
return [];
|
|
1134
|
+
}
|
|
1135
|
+
};
|
|
1136
|
+
const candidates = [
|
|
1137
|
+
...(env.PATH ?? "").split(delimiter),
|
|
1138
|
+
env.PNPM_HOME,
|
|
1139
|
+
env.NVM_BIN,
|
|
1140
|
+
env.BUN_INSTALL === void 0 ? void 0 : join(env.BUN_INSTALL, "bin"),
|
|
1141
|
+
env.CARGO_HOME === void 0 ? void 0 : join(env.CARGO_HOME, "bin"),
|
|
1142
|
+
npmPrefix === void 0 ? void 0 : platform === "win32" ? npmPrefix : join(npmPrefix, "bin"),
|
|
1143
|
+
home === "" ? void 0 : join(home, ".local", "bin"),
|
|
1144
|
+
home === "" ? void 0 : join(home, ".npm-global", "bin"),
|
|
1145
|
+
home === "" ? void 0 : join(home, ".bun", "bin"),
|
|
1146
|
+
home === "" ? void 0 : join(home, ".cargo", "bin"),
|
|
1147
|
+
home === "" ? void 0 : join(home, ".volta", "bin"),
|
|
1148
|
+
home === "" ? void 0 : join(home, ".asdf", "shims"),
|
|
1149
|
+
home === "" ? void 0 : join(home, ".local", "share", "mise", "shims"),
|
|
1150
|
+
home === "" ? void 0 : join(home, ".local", "share", "pnpm"),
|
|
1151
|
+
home === "" ? void 0 : join(home, "Library", "pnpm"),
|
|
1152
|
+
...home === "" ? [] : versionBins(join(home, ".nvm", "versions", "node"), ["bin"]),
|
|
1153
|
+
...home === "" ? [] : versionBins(join(home, ".local", "share", "fnm", "node-versions"), [
|
|
1154
|
+
"installation",
|
|
1155
|
+
"bin"
|
|
1156
|
+
]),
|
|
1157
|
+
platform === "win32" ? env.APPDATA : void 0,
|
|
1158
|
+
platform === "darwin" ? "/opt/homebrew/bin" : void 0,
|
|
1159
|
+
platform === "darwin" ? "/usr/local/bin" : void 0,
|
|
1160
|
+
platform === "darwin" ? "/Applications/Codex.app/Contents/Resources" : void 0,
|
|
1161
|
+
platform === "darwin" ? "/Applications/Codex++.app/Contents/Resources" : void 0
|
|
1162
|
+
].filter((entry) => typeof entry === "string" && entry !== "");
|
|
1163
|
+
return [...new Set(candidates)].join(delimiter);
|
|
1164
|
+
}
|
|
1165
|
+
function agentEnvironment(env = process.env) {
|
|
1166
|
+
return { ...env, PATH: agentSearchPath(env) };
|
|
1167
|
+
}
|
|
1105
1168
|
async function pathLookup(binary) {
|
|
1106
|
-
const entries = (
|
|
1169
|
+
const entries = agentSearchPath().split(delimiter).filter((entry) => entry !== "");
|
|
1107
1170
|
const extensions = process.platform === "win32" ? (process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter((entry) => entry !== "") : [""];
|
|
1108
1171
|
for (const entry of entries) {
|
|
1109
1172
|
for (const extension of extensions) {
|
|
@@ -1120,14 +1183,22 @@ async function detectAgents(lookup = pathLookup, probe2 = execProbe) {
|
|
|
1120
1183
|
const entries = Object.entries(KNOWN_AGENTS);
|
|
1121
1184
|
return Promise.all(entries.map(async ([id, adapter]) => {
|
|
1122
1185
|
const available = await lookup(adapter.binary).catch(() => false);
|
|
1123
|
-
if (!available)
|
|
1124
|
-
return {
|
|
1186
|
+
if (!available) {
|
|
1187
|
+
return {
|
|
1188
|
+
id,
|
|
1189
|
+
name: adapter.name,
|
|
1190
|
+
available,
|
|
1191
|
+
auth: "unknown",
|
|
1192
|
+
efforts: adapter.efforts
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1125
1195
|
const result = await probe2(adapter.binary, adapter.authArgs).catch(() => null);
|
|
1126
1196
|
return {
|
|
1127
1197
|
id,
|
|
1128
1198
|
name: adapter.name,
|
|
1129
1199
|
available,
|
|
1130
|
-
auth: result === null ? "unknown" : adapter.authVerdict(result)
|
|
1200
|
+
auth: result === null ? "unknown" : adapter.authVerdict(result),
|
|
1201
|
+
efforts: adapter.efforts
|
|
1131
1202
|
};
|
|
1132
1203
|
}));
|
|
1133
1204
|
}
|
|
@@ -1254,6 +1325,9 @@ function retryFrom(agent, line) {
|
|
|
1254
1325
|
function isAgentChoice(value) {
|
|
1255
1326
|
return value === "custom" || typeof value === "string" && Object.hasOwn(KNOWN_AGENTS, value);
|
|
1256
1327
|
}
|
|
1328
|
+
function isAgentEffort(value) {
|
|
1329
|
+
return typeof value === "string" && AGENT_EFFORTS.includes(value);
|
|
1330
|
+
}
|
|
1257
1331
|
async function readWatchConfig(cwd) {
|
|
1258
1332
|
try {
|
|
1259
1333
|
const parsed = JSON.parse(await readFile(join(cwd, WATCH_PATH), "utf8"));
|
|
@@ -1264,14 +1338,28 @@ async function readWatchConfig(cwd) {
|
|
|
1264
1338
|
}
|
|
1265
1339
|
async function readAgentChoice(cwd) {
|
|
1266
1340
|
const config = await readWatchConfig(cwd);
|
|
1341
|
+
const agent = isAgentChoice(config.agent) ? config.agent : null;
|
|
1342
|
+
const efforts = record(config.efforts);
|
|
1267
1343
|
return {
|
|
1268
|
-
agent
|
|
1344
|
+
agent,
|
|
1345
|
+
effort: agent !== null && agent !== "custom" && isAgentEffort(efforts?.[agent]) ? efforts[agent] : null,
|
|
1269
1346
|
run: typeof config.run === "string" && config.run !== "" ? config.run : null
|
|
1270
1347
|
};
|
|
1271
1348
|
}
|
|
1272
1349
|
async function saveAgentChoice(cwd, choice) {
|
|
1273
1350
|
const config = await readWatchConfig(cwd);
|
|
1274
1351
|
config.agent = choice.agent;
|
|
1352
|
+
if (choice.agent !== "custom" && choice.effort !== void 0) {
|
|
1353
|
+
const efforts = record(config.efforts) ?? {};
|
|
1354
|
+
if (choice.effort === null)
|
|
1355
|
+
delete efforts[choice.agent];
|
|
1356
|
+
else
|
|
1357
|
+
efforts[choice.agent] = choice.effort;
|
|
1358
|
+
if (Object.keys(efforts).length === 0)
|
|
1359
|
+
delete config.efforts;
|
|
1360
|
+
else
|
|
1361
|
+
config.efforts = efforts;
|
|
1362
|
+
}
|
|
1275
1363
|
if (choice.run !== void 0)
|
|
1276
1364
|
config.run = choice.run;
|
|
1277
1365
|
const path = join(cwd, WATCH_PATH);
|
|
@@ -2127,7 +2215,7 @@ function resolveCommand(choice, prompt, sessionId = null, registration = null) {
|
|
|
2127
2215
|
agent: choice.agent,
|
|
2128
2216
|
name: adapter.name,
|
|
2129
2217
|
command: adapter.binary,
|
|
2130
|
-
args: [...adapter.resumeArgs(sessionId, prompt), ...allow],
|
|
2218
|
+
args: [...adapter.resumeArgs(sessionId, prompt, choice.effort), ...allow],
|
|
2131
2219
|
resumed: true
|
|
2132
2220
|
};
|
|
2133
2221
|
}
|
|
@@ -2135,7 +2223,7 @@ function resolveCommand(choice, prompt, sessionId = null, registration = null) {
|
|
|
2135
2223
|
agent: choice.agent,
|
|
2136
2224
|
name: adapter.name,
|
|
2137
2225
|
command: adapter.binary,
|
|
2138
|
-
args: [...adapter.args(prompt), ...allow],
|
|
2226
|
+
args: [...adapter.args(prompt, choice.effort), ...allow],
|
|
2139
2227
|
resumed: false
|
|
2140
2228
|
};
|
|
2141
2229
|
}
|
|
@@ -2158,7 +2246,7 @@ function lineReader(stream, onLine) {
|
|
|
2158
2246
|
return flush;
|
|
2159
2247
|
}
|
|
2160
2248
|
function defaultSpawn(command, args, options) {
|
|
2161
|
-
return nodeSpawn(command, args, options);
|
|
2249
|
+
return nodeSpawn(command, args, { ...options, env: agentEnvironment() });
|
|
2162
2250
|
}
|
|
2163
2251
|
function startRunner(options) {
|
|
2164
2252
|
const spawn4 = options.spawn ?? defaultSpawn;
|
|
@@ -2668,18 +2756,16 @@ async function startServer(options) {
|
|
|
2668
2756
|
});
|
|
2669
2757
|
return agentsInflight;
|
|
2670
2758
|
};
|
|
2671
|
-
const currentAgents = () => {
|
|
2672
|
-
if (agentsCache === null)
|
|
2759
|
+
const currentAgents = (refresh = false) => {
|
|
2760
|
+
if (refresh || agentsCache === null || Date.now() - agentsCache.at > AGENTS_FRESH_MS) {
|
|
2673
2761
|
return probeAgents();
|
|
2674
|
-
if (Date.now() - agentsCache.at > AGENTS_FRESH_MS) {
|
|
2675
|
-
void probeAgents().catch(() => {
|
|
2676
|
-
});
|
|
2677
2762
|
}
|
|
2678
2763
|
return Promise.resolve(agentsCache.agents);
|
|
2679
2764
|
};
|
|
2680
2765
|
const server = http2.createServer((req, res) => {
|
|
2681
2766
|
const url = req.url ?? "/";
|
|
2682
2767
|
const path = url.split("?")[0] ?? "/";
|
|
2768
|
+
const query = new URLSearchParams(url.includes("?") ? url.slice(url.indexOf("?") + 1) : "");
|
|
2683
2769
|
if (req.method === "POST" && path.startsWith(`${LEGLAS_PREFIX}/api/`) && !isTrustedMutation(req)) {
|
|
2684
2770
|
return sendJson(res, 403, { ok: false, error: "Cross-origin API mutations are refused." });
|
|
2685
2771
|
}
|
|
@@ -2819,10 +2905,14 @@ async function startServer(options) {
|
|
|
2819
2905
|
});
|
|
2820
2906
|
}
|
|
2821
2907
|
if (path === `${LEGLAS_PREFIX}/api/agents` && req.method === "GET") {
|
|
2822
|
-
return void Promise.all([
|
|
2908
|
+
return void Promise.all([
|
|
2909
|
+
currentAgents(query.get("refresh") === "1"),
|
|
2910
|
+
readAgentChoice(cwd)
|
|
2911
|
+
]).then(([agents, choice]) => sendJson(res, 200, {
|
|
2823
2912
|
agents,
|
|
2824
2913
|
choice: choice.agent,
|
|
2825
|
-
customRun: choice.run
|
|
2914
|
+
customRun: choice.run,
|
|
2915
|
+
effort: choice.effort
|
|
2826
2916
|
}));
|
|
2827
2917
|
}
|
|
2828
2918
|
if (path === `${LEGLAS_PREFIX}/api/agent` && req.method === "POST") {
|
|
@@ -2850,7 +2940,17 @@ async function startServer(options) {
|
|
|
2850
2940
|
if (parsed.run !== void 0 && typeof parsed.run !== "string") {
|
|
2851
2941
|
return sendJson(res, 400, { ok: false, error: "The custom run command must be a string." });
|
|
2852
2942
|
}
|
|
2943
|
+
const effort = parsed.effort === null || isAgentEffort(parsed.effort) ? parsed.effort : void 0;
|
|
2944
|
+
if (parsed.effort !== void 0 && effort === void 0) {
|
|
2945
|
+
return sendJson(res, 400, { ok: false, error: "Effort must be a supported level or null." });
|
|
2946
|
+
}
|
|
2853
2947
|
if (parsed.agent === "custom") {
|
|
2948
|
+
if (effort !== void 0) {
|
|
2949
|
+
return sendJson(res, 400, {
|
|
2950
|
+
ok: false,
|
|
2951
|
+
error: "Custom agents manage effort in their own command."
|
|
2952
|
+
});
|
|
2953
|
+
}
|
|
2854
2954
|
if (typeof parsed.run !== "string") {
|
|
2855
2955
|
return sendJson(res, 400, { ok: false, error: "A custom agent needs a run command." });
|
|
2856
2956
|
}
|
|
@@ -2859,7 +2959,16 @@ async function startServer(options) {
|
|
|
2859
2959
|
return sendJson(res, 400, { ok: false, error: template.error });
|
|
2860
2960
|
return void saveAgentChoice(cwd, { agent: "custom", run: parsed.run }).then(() => sendJson(res, 200, { ok: true }), () => sendJson(res, 500, { ok: false, error: "Agent choice could not be saved." }));
|
|
2861
2961
|
}
|
|
2862
|
-
|
|
2962
|
+
if (effort !== void 0 && effort !== null && !KNOWN_AGENTS[parsed.agent].efforts.includes(effort)) {
|
|
2963
|
+
return sendJson(res, 400, {
|
|
2964
|
+
ok: false,
|
|
2965
|
+
error: `${KNOWN_AGENTS[parsed.agent].name} does not expose an effort override.`
|
|
2966
|
+
});
|
|
2967
|
+
}
|
|
2968
|
+
return void saveAgentChoice(cwd, {
|
|
2969
|
+
agent: parsed.agent,
|
|
2970
|
+
...effort === void 0 ? {} : { effort }
|
|
2971
|
+
}).then(() => sendJson(res, 200, { ok: true }), () => sendJson(res, 500, { ok: false, error: "Agent choice could not be saved." }));
|
|
2863
2972
|
});
|
|
2864
2973
|
}
|
|
2865
2974
|
if (path === `${LEGLAS_PREFIX}/api/watch` && req.method === "POST") {
|
|
@@ -3738,7 +3847,7 @@ async function runWatch(options, deps) {
|
|
|
3738
3847
|
const adapter = KNOWN_AGENTS[saved.agent];
|
|
3739
3848
|
template = {
|
|
3740
3849
|
command: adapter.binary,
|
|
3741
|
-
args: adapter.terminalArgs(PROMPT_TOKEN)
|
|
3850
|
+
args: adapter.terminalArgs(PROMPT_TOKEN, saved.effort)
|
|
3742
3851
|
};
|
|
3743
3852
|
shownCommand2 = [template.command, ...template.args].join(" ");
|
|
3744
3853
|
synthesizedAgent = adapter.name;
|