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/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
- args: (prompt) => [
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
- args: (prompt) => ["-p", prompt, "--output-format", "stream-json"],
1064
- terminalArgs: (prompt) => ["-p", prompt],
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], { shell: false, stdio: ["ignore", "pipe", "ignore"] });
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 = (process.env.PATH ?? "").split(delimiter).filter((entry) => entry !== "");
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 { id, name: adapter.name, available, auth: "unknown" };
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: isAgentChoice(config.agent) ? config.agent : null,
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([currentAgents(), readAgentChoice(cwd)]).then(([agents, choice]) => sendJson(res, 200, {
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
- return void saveAgentChoice(cwd, { agent: parsed.agent }).then(() => sendJson(res, 200, { ok: true }), () => sendJson(res, 500, { ok: false, error: "Agent choice could not be saved." }));
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;