@menteeai/menteeswe 0.1.11 → 0.1.13

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.
Files changed (2) hide show
  1. package/dist/cli.js +315 -215
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -194,6 +194,154 @@ var init_exec = __esm({
194
194
  }
195
195
  });
196
196
 
197
+ // src/tools/testing.ts
198
+ import fs4 from "fs";
199
+ import path4 from "path";
200
+ function readPackageJson(cwd) {
201
+ try {
202
+ return JSON.parse(fs4.readFileSync(path4.join(cwd, "package.json"), "utf8"));
203
+ } catch {
204
+ return null;
205
+ }
206
+ }
207
+ function readTextIfExists(cwd, file) {
208
+ try {
209
+ return fs4.readFileSync(path4.join(cwd, file), "utf8");
210
+ } catch {
211
+ return null;
212
+ }
213
+ }
214
+ function fileExists(cwd, file) {
215
+ try {
216
+ return fs4.existsSync(path4.join(cwd, file));
217
+ } catch {
218
+ return false;
219
+ }
220
+ }
221
+ function detectTestCommand(cwd) {
222
+ const pkg = readPackageJson(cwd);
223
+ const testScript = pkg && typeof pkg.scripts?.test === "string" ? pkg.scripts.test : null;
224
+ if (testScript && !/error|no test specified/i.test(testScript)) {
225
+ return "npm test";
226
+ }
227
+ if (fileExists(cwd, "pytest.ini") || fileExists(cwd, "tests")) return "pytest -q";
228
+ const pyproject = readTextIfExists(cwd, "pyproject.toml");
229
+ if (pyproject && /\[tool\.pytest/.test(pyproject)) return "pytest -q";
230
+ if (fileExists(cwd, "go.mod")) return "go test ./...";
231
+ if (fileExists(cwd, "Cargo.toml")) return "cargo test";
232
+ return null;
233
+ }
234
+ function detectLintCommand(cwd) {
235
+ if (fileExists(cwd, "eslint.config.js") || fileExists(cwd, "eslint.config.mjs") || fileExists(cwd, ".eslintrc.json") || fileExists(cwd, ".eslintrc.js")) {
236
+ return "npx --no-install eslint .";
237
+ }
238
+ if (fileExists(cwd, "biome.json")) return "npx --no-install biome check .";
239
+ if (fileExists(cwd, ".ruff.toml") || fileExists(cwd, "ruff.toml")) return "ruff check .";
240
+ const pyproject = readTextIfExists(cwd, "pyproject.toml");
241
+ if (pyproject && /\[tool\.ruff/.test(pyproject)) return "ruff check .";
242
+ return null;
243
+ }
244
+ function detectTypecheckCommand(cwd) {
245
+ if (fileExists(cwd, "tsconfig.json")) return "npx --no-install tsc --noEmit";
246
+ if (fileExists(cwd, "mypy.ini")) return "python -m mypy .";
247
+ const pyproject = readTextIfExists(cwd, "pyproject.toml");
248
+ if (pyproject && /\[tool\.mypy/.test(pyproject)) return "python -m mypy .";
249
+ if (fileExists(cwd, "go.mod")) return "go vet ./...";
250
+ if (fileExists(cwd, "Cargo.toml")) return "cargo check";
251
+ return null;
252
+ }
253
+ function makeRunnerTool(config) {
254
+ return {
255
+ name: config.name,
256
+ description: config.description,
257
+ risk: "safe",
258
+ timeoutMs: DEFAULT_TIMEOUT_MS2,
259
+ parameters: {
260
+ type: "object",
261
+ properties: {
262
+ command: {
263
+ type: "string",
264
+ description: "Exact command to run. If omitted, the project type is auto-detected."
265
+ },
266
+ timeout_ms: { type: "number", description: `Timeout in ms. Defaults to ${DEFAULT_TIMEOUT_MS2}.` }
267
+ }
268
+ },
269
+ async execute(args, ctx) {
270
+ let command = firstString(args, "command");
271
+ if (!command) {
272
+ command = config.detect(ctx.cwd) ?? void 0;
273
+ if (!command) {
274
+ return {
275
+ success: false,
276
+ output: `Could not auto-detect a command for this project. ${config.fallbackHint} Use the command argument to run one explicitly.`
277
+ };
278
+ }
279
+ }
280
+ const timeoutMs = Math.min(6e5, firstNumber(args, "timeout_ms") ?? DEFAULT_TIMEOUT_MS2);
281
+ const outcome = await runShellCommand(command, ctx.cwd, timeoutMs);
282
+ const formatted = formatOutcome(outcome);
283
+ return { success: formatted.success, output: formatted.output, truncated: formatted.truncated };
284
+ }
285
+ };
286
+ }
287
+ var DEFAULT_TIMEOUT_MS2, runTests, runLinter, runTypecheck, inspectEnv;
288
+ var init_testing = __esm({
289
+ "src/tools/testing.ts"() {
290
+ "use strict";
291
+ init_base();
292
+ init_exec();
293
+ DEFAULT_TIMEOUT_MS2 = 18e4;
294
+ runTests = makeRunnerTool({
295
+ name: "run_tests",
296
+ description: "Run the project test suite. Auto-detects npm test / pytest / go test / cargo test from the project files, or run an exact command. Use this to verify changes.",
297
+ detect: detectTestCommand,
298
+ fallbackHint: "No test runner found (looked for package.json scripts.test, pytest, go, cargo)."
299
+ });
300
+ runLinter = makeRunnerTool({
301
+ name: "run_linter",
302
+ description: "Run the project linter (eslint / biome / ruff auto-detected).",
303
+ detect: detectLintCommand,
304
+ fallbackHint: "No linter config found (looked for eslint, biome, ruff)."
305
+ });
306
+ runTypecheck = makeRunnerTool({
307
+ name: "run_typecheck",
308
+ description: "Run static type checking (tsc --noEmit / mypy / go vet / cargo check auto-detected).",
309
+ detect: detectTypecheckCommand,
310
+ fallbackHint: "No typechecker config found (looked for tsconfig.json, mypy, go, cargo)."
311
+ });
312
+ inspectEnv = {
313
+ name: "inspect_env",
314
+ description: "Inspect the environment: OS info, and which toolchains are installed with versions (node, npm, python, pip, git). Use this to adapt commands to the machine.",
315
+ risk: "safe",
316
+ parameters: { type: "object", properties: {} },
317
+ async execute(_args, ctx) {
318
+ const lines = [
319
+ `os: ${process.platform} ${process.version} ${process.arch}`,
320
+ `workspace: ${ctx.cwd}`
321
+ ];
322
+ const probes = [
323
+ ["node", "node --version"],
324
+ ["npm", "npm --version"],
325
+ ["npx", "npx --version"],
326
+ ["python", "python --version"],
327
+ ["pip", "pip --version"],
328
+ ["git", "git --version"],
329
+ ["docker", "docker --version"]
330
+ ];
331
+ const results = await Promise.all(
332
+ probes.map(async ([label, probe]) => {
333
+ const outcome = await runShellCommand(probe, ctx.cwd, 8e3);
334
+ const version2 = `${outcome.stdout} ${outcome.stderr}`.trim().split("\n")[0] ?? "";
335
+ return `${label}: ${outcome.exitCode === 0 ? version2 || "installed (version unknown)" : "not available"}`;
336
+ })
337
+ );
338
+ lines.push(...results);
339
+ return { success: true, output: lines.join("\n") };
340
+ }
341
+ };
342
+ }
343
+ });
344
+
197
345
  // src/events.ts
198
346
  var EventBus;
199
347
  var init_events = __esm({
@@ -635,6 +783,7 @@ function createAgentState(task, maxIterations) {
635
783
  sameErrorCount: 0,
636
784
  totalToolCalls: 0,
637
785
  readCount: 0,
786
+ verifyAttempts: 0,
638
787
  usage: { inputTokens: 0, outputTokens: 0, modelRequests: 0 },
639
788
  startedAt: Date.now()
640
789
  };
@@ -724,6 +873,31 @@ function trimOldToolResults(messages) {
724
873
  message.content = `\u27E8prior tool output trimmed: ${head}\u2026 (${content.length} chars)\u27E9`;
725
874
  }
726
875
  }
876
+ async function runVerification(tools, ctx, bus, state, cwd) {
877
+ const checks = [
878
+ { tool: tools.get("run_typecheck"), detect: detectTypecheckCommand },
879
+ { tool: tools.get("run_tests"), detect: detectTestCommand }
880
+ ];
881
+ let combined = "";
882
+ for (const { tool, detect } of checks) {
883
+ if (!tool) continue;
884
+ if (!detect(cwd)) continue;
885
+ bus.emit("tool_started", `verify \xB7 ${tool.name}`, { tool: tool.name });
886
+ const res = await tool.execute({}, ctx);
887
+ bus.emit("tool_completed", `verify \xB7 ${tool.name}`, {
888
+ tool: tool.name,
889
+ success: res.success,
890
+ output: res.output.slice(0, 2e3)
891
+ });
892
+ combined += `${tool.name}: ${res.output}
893
+ `;
894
+ if (!res.success) {
895
+ state.errors.push(res.output.slice(0, 500));
896
+ return { ok: false, output: combined };
897
+ }
898
+ }
899
+ return { ok: true, output: combined };
900
+ }
727
901
  async function runAgent(options) {
728
902
  const {
729
903
  task,
@@ -756,7 +930,12 @@ ${systemExtra}` : "");
756
930
  }
757
931
  };
758
932
  const messages = [{ role: "user", content: task }];
933
+ const setPhase = (p) => {
934
+ state.phase = p;
935
+ bus.emit("phase", p);
936
+ };
759
937
  bus.emit("task_started", task, { provider: provider.name, model: model ?? provider.defaultModel });
938
+ setPhase("EXPLORATION");
760
939
  let finalText = "";
761
940
  let sawFinish = false;
762
941
  while (state.iteration < maxIterations) {
@@ -828,6 +1007,27 @@ ${systemExtra}` : "");
828
1007
  if (response.toolCalls.length === 0) {
829
1008
  finalText = response.content ?? "";
830
1009
  sawFinish = true;
1010
+ if (state.modifiedFiles.length > 0 && state.verifyAttempts < MAX_VERIFY) {
1011
+ setPhase("VALIDATION");
1012
+ const verdict = await runVerification(tools, toolCtx, bus, state, cwd);
1013
+ if (!verdict.ok) {
1014
+ state.verifyAttempts += 1;
1015
+ if (state.verifyAttempts >= MAX_VERIFY) {
1016
+ bus.emit("warning", "Verification failed after multiple attempts; finishing with the issue reported.");
1017
+ break;
1018
+ }
1019
+ setPhase("DEBUGGING");
1020
+ messages.push({
1021
+ role: "user",
1022
+ content: `SYSTEM NOTE: Verification failed.
1023
+ ${verdict.output}
1024
+ Investigate the root cause, fix it, then finish with a final answer.`
1025
+ });
1026
+ sawFinish = false;
1027
+ continue;
1028
+ }
1029
+ setPhase("REVIEW");
1030
+ }
831
1031
  break;
832
1032
  }
833
1033
  messages.push({
@@ -859,6 +1059,7 @@ ${systemExtra}` : "");
859
1059
  });
860
1060
  continue;
861
1061
  }
1062
+ if (EDIT_TOOLS.has(tool.name) && state.phase === "EXPLORATION") setPhase("IMPLEMENTATION");
862
1063
  bus.emit("tool_started", toolCallPreview(call), { tool: tool.name, args });
863
1064
  const risk = tool.dynamicRisk ? tool.dynamicRisk(args) : tool.risk;
864
1065
  let approved = true;
@@ -969,7 +1170,7 @@ ${systemExtra}` : "");
969
1170
  });
970
1171
  return { success, finalText, state };
971
1172
  }
972
- var MAX_CONTEXT_CHARS, READ_BUDGET, RATE_LIMIT_MAX_ATTEMPTS, KEEP_RECENT_TOOL;
1173
+ var MAX_CONTEXT_CHARS, READ_BUDGET, RATE_LIMIT_MAX_ATTEMPTS, MAX_VERIFY, EDIT_TOOLS, KEEP_RECENT_TOOL;
973
1174
  var init_loop = __esm({
974
1175
  "src/agent/loop.ts"() {
975
1176
  "use strict";
@@ -977,9 +1178,12 @@ var init_loop = __esm({
977
1178
  init_conversation();
978
1179
  init_state();
979
1180
  init_base();
1181
+ init_testing();
980
1182
  MAX_CONTEXT_CHARS = 7e4;
981
1183
  READ_BUDGET = 6;
982
1184
  RATE_LIMIT_MAX_ATTEMPTS = 8;
1185
+ MAX_VERIFY = 3;
1186
+ EDIT_TOOLS = /* @__PURE__ */ new Set(["apply_patch", "write_file", "move_path"]);
983
1187
  KEEP_RECENT_TOOL = 4;
984
1188
  }
985
1189
  });
@@ -988,7 +1192,7 @@ var init_loop = __esm({
988
1192
  var version;
989
1193
  var init_package = __esm({
990
1194
  "package.json"() {
991
- version = "0.1.11";
1195
+ version = "0.1.13";
992
1196
  }
993
1197
  });
994
1198
 
@@ -1112,59 +1316,45 @@ function compactChanges(oldStr, newStr) {
1112
1316
  added: newLines.slice(start, endNew)
1113
1317
  };
1114
1318
  }
1115
- function FileDiffView({ diffs }) {
1116
- const files = Object.entries(diffs);
1117
- if (files.length === 0) return null;
1118
- const blocks = [];
1319
+ function CurrentEditBox({ edit }) {
1320
+ if (!edit) return null;
1321
+ const c = compactChanges(edit.old, edit.new);
1322
+ const removed = c.removed.slice(0, MAX_LINES);
1323
+ const added = c.added.slice(0, MAX_LINES);
1324
+ const lines = [];
1119
1325
  let key = 0;
1120
- let lineBudget = MAX_LINES;
1121
- const shown = files.slice(0, MAX_FILES);
1122
- shown.forEach(([file, edits], fi) => {
1123
- let removedTotal = 0;
1124
- let addedTotal = 0;
1125
- const lines = [];
1126
- for (const edit of edits) {
1127
- const c = compactChanges(edit.old, edit.new);
1128
- removedTotal += c.removed.length;
1129
- addedTotal += c.added.length;
1130
- if (lineBudget > 0) {
1131
- for (const l of c.removed) {
1132
- if (lineBudget <= 0) break;
1133
- lines.push(
1134
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: "redBright", color: "white", children: ` - ${l}` }, `r${key++}`)
1135
- );
1136
- lineBudget--;
1137
- }
1138
- for (const l of c.added) {
1139
- if (lineBudget <= 0) break;
1140
- lines.push(
1141
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: "greenBright", color: "black", children: ` + ${l}` }, `a${key++}`)
1142
- );
1143
- lineBudget--;
1144
- }
1145
- }
1146
- }
1147
- blocks.push(
1148
- /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginTop: 1, children: [
1149
- /* @__PURE__ */ jsx2(Text2, { bold: true, color: "magenta", children: `\u270E ${file}` }),
1150
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: ` ${edits.length} edit(s) \xB7 +${addedTotal} / -${removedTotal}` }),
1151
- lines
1152
- ] }, `f${fi}`)
1326
+ for (const l of removed) {
1327
+ lines.push(
1328
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: "redBright", color: "white", children: ` - ${l}` }, `r${key++}`)
1153
1329
  );
1154
- });
1155
- if (files.length > MAX_FILES) {
1156
- blocks.push(
1157
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: ` \u2026 and ${files.length - MAX_FILES} more file(s)` }, "more")
1330
+ }
1331
+ for (const l of added) {
1332
+ lines.push(
1333
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: "greenBright", color: "black", children: ` + ${l}` }, `a${key++}`)
1158
1334
  );
1159
1335
  }
1160
- return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: blocks });
1336
+ return /* @__PURE__ */ jsxs2(
1337
+ Box2,
1338
+ {
1339
+ flexDirection: "column",
1340
+ marginTop: 1,
1341
+ borderStyle: "single",
1342
+ borderColor: "gray",
1343
+ borderLeft: false,
1344
+ borderRight: false,
1345
+ paddingX: 1,
1346
+ children: [
1347
+ /* @__PURE__ */ jsx2(Text2, { bold: true, color: "magenta", children: `\u270E ${edit.path}` }),
1348
+ lines.length > 0 ? lines : /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " (no text change)" })
1349
+ ]
1350
+ }
1351
+ );
1161
1352
  }
1162
- var MAX_FILES, MAX_LINES;
1353
+ var MAX_LINES;
1163
1354
  var init_diff = __esm({
1164
1355
  "src/tui/diff.tsx"() {
1165
1356
  "use strict";
1166
- MAX_FILES = 12;
1167
- MAX_LINES = 160;
1357
+ MAX_LINES = 24;
1168
1358
  }
1169
1359
  });
1170
1360
 
@@ -1367,7 +1557,8 @@ function App(props) {
1367
1557
  const [detailsOpen, setDetailsOpen] = useState3(false);
1368
1558
  const [showDetails, setShowDetails] = useState3(false);
1369
1559
  const [thinkingText, setThinkingText] = useState3("");
1370
- const [fileDiffs, setFileDiffs] = useState3({});
1560
+ const [currentEdit, setCurrentEdit] = useState3(null);
1561
+ const [phase, setPhase] = useState3("");
1371
1562
  const finalTextRef = useRef(null);
1372
1563
  const pendingTool = useRef(null);
1373
1564
  const counter = useRef(0);
@@ -1388,6 +1579,10 @@ function App(props) {
1388
1579
  if (delta) setThinkingText((prev) => (prev + delta).slice(-4e3));
1389
1580
  return;
1390
1581
  }
1582
+ if (event.type === "phase") {
1583
+ setPhase(event.message ?? "");
1584
+ return;
1585
+ }
1391
1586
  if (event.type === "model_request" || event.type === "task_completed") {
1392
1587
  setThinkingText("");
1393
1588
  }
@@ -1395,10 +1590,7 @@ function App(props) {
1395
1590
  const filePath = typeof event.data?.path === "string" ? event.data.path : "file";
1396
1591
  const oldStr = typeof event.data?.old_string === "string" ? event.data.old_string : "";
1397
1592
  const newStr = typeof event.data?.new_string === "string" ? event.data.new_string : "";
1398
- setFileDiffs((prev) => {
1399
- const arr = prev[filePath] ? [...prev[filePath], { old: oldStr, new: newStr }] : [{ old: oldStr, new: newStr }];
1400
- return { ...prev, [filePath]: arr };
1401
- });
1593
+ setCurrentEdit({ path: filePath, old: oldStr, new: newStr });
1402
1594
  return;
1403
1595
  }
1404
1596
  appendLog(formatEvent(event), TOOL_NOISE.has(event.type));
@@ -1608,9 +1800,8 @@ function App(props) {
1608
1800
  setToolHistory([]);
1609
1801
  setInspectorIndex(null);
1610
1802
  setDetailsOpen(false);
1611
- setShowDetails(false);
1612
1803
  setRunning(true);
1613
- setFileDiffs({});
1804
+ setCurrentEdit(null);
1614
1805
  const ac = new AbortController();
1615
1806
  abortRef.current = ac;
1616
1807
  try {
@@ -1709,7 +1900,7 @@ function App(props) {
1709
1900
  typedText,
1710
1901
  typing ? "\u258C" : ""
1711
1902
  ] }) }) : null,
1712
- /* @__PURE__ */ jsx6(FileDiffView, { diffs: fileDiffs }),
1903
+ /* @__PURE__ */ jsx6(CurrentEditBox, { edit: currentEdit }),
1713
1904
  detailsOpen && inspectorIndex !== null && toolHistory[inspectorIndex] ? (() => {
1714
1905
  const rec = toolHistory[inspectorIndex];
1715
1906
  const outLines = rec.output.split("\n").slice(0, 24).join("\n");
@@ -1785,7 +1976,11 @@ function App(props) {
1785
1976
  SPINNER_FRAMES[frame],
1786
1977
  " "
1787
1978
  ] }),
1788
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "agent working... (Ctrl+C to exit)" })
1979
+ /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1980
+ "agent ",
1981
+ PHASE_LABEL[phase] ?? "working",
1982
+ "\u2026 (Ctrl+C to exit)"
1983
+ ] })
1789
1984
  ] }),
1790
1985
  thinkingText ? /* @__PURE__ */ jsxs6(Box6, { children: [
1791
1986
  /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: "\u{1F4AD} " }),
@@ -1807,7 +2002,7 @@ function App(props) {
1807
2002
  ] })
1808
2003
  ] });
1809
2004
  }
1810
- var SPINNER_FRAMES, PROVIDER_NAMES, PROVIDER_DESCRIPTIONS, BANNER;
2005
+ var SPINNER_FRAMES, PHASE_LABEL, PROVIDER_NAMES, PROVIDER_DESCRIPTIONS, BANNER;
1811
2006
  var init_App = __esm({
1812
2007
  "src/tui/App.tsx"() {
1813
2008
  "use strict";
@@ -1826,6 +2021,16 @@ var init_App = __esm({
1826
2021
  init_KeyDialog();
1827
2022
  init_package();
1828
2023
  SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
2024
+ PHASE_LABEL = {
2025
+ INITIALIZATION: "starting",
2026
+ EXPLORATION: "exploring",
2027
+ PLANNING: "planning",
2028
+ IMPLEMENTATION: "implementing",
2029
+ VALIDATION: "verifying",
2030
+ DEBUGGING: "debugging",
2031
+ REVIEW: "reviewing",
2032
+ COMPLETION: "finalizing"
2033
+ };
1829
2034
  PROVIDER_NAMES = ["kimi", "glm", "zai", "zai-coding", "openrouter", "nvidia", "mock"];
1830
2035
  PROVIDER_DESCRIPTIONS = {
1831
2036
  kimi: "Moonshot \xB7 api.moonshot.ai",
@@ -1918,14 +2123,14 @@ var OpenAICompatProvider = class {
1918
2123
  ...request.maxTokens ? { max_tokens: request.maxTokens } : {}
1919
2124
  };
1920
2125
  if (!request.onToken) {
1921
- const response = await this.client.chat.completions.create(baseParams);
1922
- return parseResponse(response);
2126
+ const response2 = await this.client.chat.completions.create(baseParams);
2127
+ return parseResponse(response2);
1923
2128
  }
1924
- try {
2129
+ const streamOnce = async (withUsage) => {
1925
2130
  const stream = await this.client.chat.completions.create({
1926
2131
  ...baseParams,
1927
2132
  stream: true,
1928
- stream_options: { include_usage: true }
2133
+ ...withUsage ? { stream_options: { include_usage: true } } : {}
1929
2134
  });
1930
2135
  let content = "";
1931
2136
  let finishReason = "stop";
@@ -1936,7 +2141,7 @@ var OpenAICompatProvider = class {
1936
2141
  const delta = choice?.delta;
1937
2142
  if (delta?.content) {
1938
2143
  content += delta.content;
1939
- request.onToken(delta.content);
2144
+ request.onToken?.(delta.content);
1940
2145
  }
1941
2146
  if (delta?.tool_calls) {
1942
2147
  for (const tc of delta.tool_calls) {
@@ -1970,10 +2175,21 @@ var OpenAICompatProvider = class {
1970
2175
  finishReason: mapFinishReason(finishReason),
1971
2176
  usage
1972
2177
  };
1973
- } catch {
1974
- const response = await this.client.chat.completions.create(baseParams);
1975
- return parseResponse(response);
2178
+ };
2179
+ for (const withUsage of [true, false]) {
2180
+ try {
2181
+ return await streamOnce(withUsage);
2182
+ } catch {
2183
+ }
2184
+ }
2185
+ const response = await this.client.chat.completions.create(baseParams);
2186
+ const parsed = parseResponse(response);
2187
+ if (parsed.content) {
2188
+ for (let i = 0; i < parsed.content.length; i += 3) {
2189
+ request.onToken?.(parsed.content.slice(i, i + 3));
2190
+ }
1976
2191
  }
2192
+ return parsed;
1977
2193
  }
1978
2194
  async listModels() {
1979
2195
  const page = await this.client.models.list();
@@ -2331,20 +2547,43 @@ var applyPatch = {
2331
2547
  if (oldString === newString) {
2332
2548
  return { success: false, output: "old_string and new_string are identical; nothing to change." };
2333
2549
  }
2334
- const occurrences = content.split(oldString).length - 1;
2335
- if (occurrences === 0) {
2336
- return {
2337
- success: false,
2338
- output: "old_string not found in file. Read the file again and copy the exact text, including whitespace."
2339
- };
2550
+ const tryMatch = (c, o, n) => {
2551
+ const occ = c.split(o).length - 1;
2552
+ return occ === 1 ? c.replace(o, n) : null;
2553
+ };
2554
+ let updated = tryMatch(content, oldString, newString);
2555
+ let note = "";
2556
+ if (updated === null) {
2557
+ const lfContent = content.replace(/\r\n/g, "\n");
2558
+ const lfOld = oldString.replace(/\r\n/g, "\n");
2559
+ const lfNew = newString.replace(/\r\n/g, "\n");
2560
+ const lfUpdated = tryMatch(lfContent, lfOld, lfNew);
2561
+ if (lfUpdated !== null) {
2562
+ updated = lfUpdated;
2563
+ note = " (applied after normalizing CRLF \u2192 LF line endings)";
2564
+ }
2565
+ }
2566
+ if (updated === null) {
2567
+ const strip = (s) => s.split("\n").map((l) => l.replace(/\s+$/, "")).join("\n");
2568
+ const sUpdated = tryMatch(strip(content), strip(oldString), strip(newString));
2569
+ if (sUpdated !== null) {
2570
+ updated = sUpdated;
2571
+ note = " (applied after trimming trailing whitespace to match)";
2572
+ }
2340
2573
  }
2341
- if (occurrences > 1) {
2574
+ if (updated === null) {
2575
+ const occ = content.split(oldString).length - 1;
2576
+ if (occ === 0) {
2577
+ return {
2578
+ success: false,
2579
+ output: "old_string not found in file. Read the file again and copy the exact text, including whitespace."
2580
+ };
2581
+ }
2342
2582
  return {
2343
2583
  success: false,
2344
- output: `old_string occurs ${occurrences} times. Include more surrounding lines to make it unique.`
2584
+ output: `old_string occurs ${occ} times. Include more surrounding lines to make it unique.`
2345
2585
  };
2346
2586
  }
2347
- const updated = content.replace(oldString, newString);
2348
2587
  fs2.writeFileSync(filePath, updated, "utf8");
2349
2588
  const removed = oldString.split(/\r?\n/).length;
2350
2589
  const added = newString.split(/\r?\n/).length;
@@ -2908,147 +3147,8 @@ var executeCommand = {
2908
3147
  }
2909
3148
  };
2910
3149
 
2911
- // src/tools/testing.ts
2912
- init_base();
2913
- init_exec();
2914
- import fs4 from "fs";
2915
- import path4 from "path";
2916
- var DEFAULT_TIMEOUT_MS2 = 18e4;
2917
- function readPackageJson(cwd) {
2918
- try {
2919
- return JSON.parse(fs4.readFileSync(path4.join(cwd, "package.json"), "utf8"));
2920
- } catch {
2921
- return null;
2922
- }
2923
- }
2924
- function readTextIfExists(cwd, file) {
2925
- try {
2926
- return fs4.readFileSync(path4.join(cwd, file), "utf8");
2927
- } catch {
2928
- return null;
2929
- }
2930
- }
2931
- function fileExists(cwd, file) {
2932
- try {
2933
- return fs4.existsSync(path4.join(cwd, file));
2934
- } catch {
2935
- return false;
2936
- }
2937
- }
2938
- function detectTestCommand(cwd) {
2939
- const pkg = readPackageJson(cwd);
2940
- const testScript = pkg && typeof pkg.scripts?.test === "string" ? pkg.scripts.test : null;
2941
- if (testScript && !/error|no test specified/i.test(testScript)) {
2942
- return "npm test";
2943
- }
2944
- if (fileExists(cwd, "pytest.ini") || fileExists(cwd, "tests")) return "pytest -q";
2945
- const pyproject = readTextIfExists(cwd, "pyproject.toml");
2946
- if (pyproject && /\[tool\.pytest/.test(pyproject)) return "pytest -q";
2947
- if (fileExists(cwd, "go.mod")) return "go test ./...";
2948
- if (fileExists(cwd, "Cargo.toml")) return "cargo test";
2949
- return null;
2950
- }
2951
- function detectLintCommand(cwd) {
2952
- if (fileExists(cwd, "eslint.config.js") || fileExists(cwd, "eslint.config.mjs") || fileExists(cwd, ".eslintrc.json") || fileExists(cwd, ".eslintrc.js")) {
2953
- return "npx --no-install eslint .";
2954
- }
2955
- if (fileExists(cwd, "biome.json")) return "npx --no-install biome check .";
2956
- if (fileExists(cwd, ".ruff.toml") || fileExists(cwd, "ruff.toml")) return "ruff check .";
2957
- const pyproject = readTextIfExists(cwd, "pyproject.toml");
2958
- if (pyproject && /\[tool\.ruff/.test(pyproject)) return "ruff check .";
2959
- return null;
2960
- }
2961
- function detectTypecheckCommand(cwd) {
2962
- if (fileExists(cwd, "tsconfig.json")) return "npx --no-install tsc --noEmit";
2963
- if (fileExists(cwd, "mypy.ini")) return "python -m mypy .";
2964
- const pyproject = readTextIfExists(cwd, "pyproject.toml");
2965
- if (pyproject && /\[tool\.mypy/.test(pyproject)) return "python -m mypy .";
2966
- if (fileExists(cwd, "go.mod")) return "go vet ./...";
2967
- if (fileExists(cwd, "Cargo.toml")) return "cargo check";
2968
- return null;
2969
- }
2970
- function makeRunnerTool(config) {
2971
- return {
2972
- name: config.name,
2973
- description: config.description,
2974
- risk: "safe",
2975
- timeoutMs: DEFAULT_TIMEOUT_MS2,
2976
- parameters: {
2977
- type: "object",
2978
- properties: {
2979
- command: {
2980
- type: "string",
2981
- description: "Exact command to run. If omitted, the project type is auto-detected."
2982
- },
2983
- timeout_ms: { type: "number", description: `Timeout in ms. Defaults to ${DEFAULT_TIMEOUT_MS2}.` }
2984
- }
2985
- },
2986
- async execute(args, ctx) {
2987
- let command = firstString(args, "command");
2988
- if (!command) {
2989
- command = config.detect(ctx.cwd) ?? void 0;
2990
- if (!command) {
2991
- return {
2992
- success: false,
2993
- output: `Could not auto-detect a command for this project. ${config.fallbackHint} Use the command argument to run one explicitly.`
2994
- };
2995
- }
2996
- }
2997
- const timeoutMs = Math.min(6e5, firstNumber(args, "timeout_ms") ?? DEFAULT_TIMEOUT_MS2);
2998
- const outcome = await runShellCommand(command, ctx.cwd, timeoutMs);
2999
- const formatted = formatOutcome(outcome);
3000
- return { success: formatted.success, output: formatted.output, truncated: formatted.truncated };
3001
- }
3002
- };
3003
- }
3004
- var runTests = makeRunnerTool({
3005
- name: "run_tests",
3006
- description: "Run the project test suite. Auto-detects npm test / pytest / go test / cargo test from the project files, or run an exact command. Use this to verify changes.",
3007
- detect: detectTestCommand,
3008
- fallbackHint: "No test runner found (looked for package.json scripts.test, pytest, go, cargo)."
3009
- });
3010
- var runLinter = makeRunnerTool({
3011
- name: "run_linter",
3012
- description: "Run the project linter (eslint / biome / ruff auto-detected).",
3013
- detect: detectLintCommand,
3014
- fallbackHint: "No linter config found (looked for eslint, biome, ruff)."
3015
- });
3016
- var runTypecheck = makeRunnerTool({
3017
- name: "run_typecheck",
3018
- description: "Run static type checking (tsc --noEmit / mypy / go vet / cargo check auto-detected).",
3019
- detect: detectTypecheckCommand,
3020
- fallbackHint: "No typechecker config found (looked for tsconfig.json, mypy, go, cargo)."
3021
- });
3022
- var inspectEnv = {
3023
- name: "inspect_env",
3024
- description: "Inspect the environment: OS info, and which toolchains are installed with versions (node, npm, python, pip, git). Use this to adapt commands to the machine.",
3025
- risk: "safe",
3026
- parameters: { type: "object", properties: {} },
3027
- async execute(_args, ctx) {
3028
- const lines = [
3029
- `os: ${process.platform} ${process.version} ${process.arch}`,
3030
- `workspace: ${ctx.cwd}`
3031
- ];
3032
- const probes = [
3033
- ["node", "node --version"],
3034
- ["npm", "npm --version"],
3035
- ["npx", "npx --version"],
3036
- ["python", "python --version"],
3037
- ["pip", "pip --version"],
3038
- ["git", "git --version"],
3039
- ["docker", "docker --version"]
3040
- ];
3041
- const results = await Promise.all(
3042
- probes.map(async ([label, probe]) => {
3043
- const outcome = await runShellCommand(probe, ctx.cwd, 8e3);
3044
- const version2 = `${outcome.stdout} ${outcome.stderr}`.trim().split("\n")[0] ?? "";
3045
- return `${label}: ${outcome.exitCode === 0 ? version2 || "installed (version unknown)" : "not available"}`;
3046
- })
3047
- );
3048
- lines.push(...results);
3049
- return { success: true, output: lines.join("\n") };
3050
- }
3051
- };
3150
+ // src/tools/registry.ts
3151
+ init_testing();
3052
3152
 
3053
3153
  // src/tools/memory.ts
3054
3154
  init_config();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@menteeai/menteeswe",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "MenteE SWE — a model-agnostic autonomous software-engineering agent CLI. Bring your own intelligence: Kimi, GLM/Z.ai, and more.",
5
5
  "type": "module",
6
6
  "license": "MIT",