@menteeai/menteeswe 0.1.17 → 0.1.19

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 +97 -19
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -426,7 +426,9 @@ function formatEvent(event) {
426
426
  const output = typeof event.data?.output === "string" ? event.data.output.trim() : "";
427
427
  const firstLine = (output.split("\n")[0] ?? event.message ?? "").slice(0, 160);
428
428
  const color = success ? CATEGORY_COLOR[TOOL_CATEGORY[tool] ?? "other"] : chalk.red;
429
- return color(` ${success ? "\u2713" : "\u2717"} ${firstLine}`);
429
+ const resultChars = typeof event.data?.resultChars === "number" ? event.data.resultChars : 0;
430
+ const sizeNote = resultChars > 0 ? chalk.dim(` \xB7 ~${(resultChars / 4 / 1e3).toFixed(1)}k tok`) : "";
431
+ return color(` ${success ? "\u2713" : "\u2717"} ${firstLine}`) + sizeNote;
430
432
  }
431
433
  case "approval":
432
434
  return chalk.magenta(`\u{1F510} Asking permission to use ${event.message ?? "tool"}...`);
@@ -692,7 +694,7 @@ var init_prompts = __esm({
692
694
  // src/agent/conversation.ts
693
695
  import fs9 from "fs";
694
696
  import path8 from "path";
695
- import crypto2 from "crypto";
697
+ import crypto3 from "crypto";
696
698
  import os3 from "os";
697
699
  function sessionsDir() {
698
700
  const dir = path8.join(os3.homedir(), ".mentee", "sessions");
@@ -700,7 +702,7 @@ function sessionsDir() {
700
702
  return dir;
701
703
  }
702
704
  function sessionFile(cwd) {
703
- const hash = crypto2.createHash("sha1").update(cwd).digest("hex").slice(0, 16);
705
+ const hash = crypto3.createHash("sha1").update(cwd).digest("hex").slice(0, 16);
704
706
  return path8.join(sessionsDir(), `${hash}.json`);
705
707
  }
706
708
  function loadConversation(cwd, limit = 12) {
@@ -745,6 +747,43 @@ Assistant: ${t.response.trim()}
745
747
  }
746
748
  return [header, ...blocks, ""].join("\n");
747
749
  }
750
+ function recentFile(cwd) {
751
+ const hash = crypto3.createHash("sha1").update(cwd).digest("hex").slice(0, 16);
752
+ return path8.join(sessionsDir(), `${hash}.files.json`);
753
+ }
754
+ function saveRecentFiles(cwd, reads, writes) {
755
+ try {
756
+ let prev = { reads: [], writes: [] };
757
+ try {
758
+ const parsed = JSON.parse(fs9.readFileSync(recentFile(cwd), "utf8"));
759
+ if (parsed && Array.isArray(parsed.reads) && Array.isArray(parsed.writes)) prev = parsed;
760
+ } catch {
761
+ }
762
+ const merge = (known, fresh) => [.../* @__PURE__ */ new Set([...fresh, ...known])].slice(0, 15);
763
+ fs9.writeFileSync(
764
+ recentFile(cwd),
765
+ JSON.stringify({ reads: merge(prev.reads, reads), writes: merge(prev.writes, writes) }, null, 2)
766
+ );
767
+ } catch {
768
+ }
769
+ }
770
+ function formatRecentFiles(cwd) {
771
+ try {
772
+ const parsed = JSON.parse(fs9.readFileSync(recentFile(cwd), "utf8"));
773
+ const writes = (parsed.writes ?? []).slice(0, 10);
774
+ const reads = (parsed.reads ?? []).filter((f) => !writes.includes(f)).slice(0, 10);
775
+ if (reads.length === 0 && writes.length === 0) return "";
776
+ const lines = ["# Known files from earlier tasks in this project"];
777
+ if (writes.length > 0) lines.push(`Modified recently: ${writes.join(", ")}`);
778
+ if (reads.length > 0) lines.push(`Read recently: ${reads.join(", ")}`);
779
+ lines.push(
780
+ "Reuse this knowledge \u2014 do not re-search or re-read these files unless you suspect they changed. If you read a file earlier and it is unchanged, you may apply_patch directly."
781
+ );
782
+ return lines.join("\n");
783
+ } catch {
784
+ return "";
785
+ }
786
+ }
748
787
  var init_conversation = __esm({
749
788
  "src/agent/conversation.ts"() {
750
789
  "use strict";
@@ -759,6 +798,7 @@ function createAgentState(task, maxIterations) {
759
798
  iteration: 0,
760
799
  maxIterations,
761
800
  modifiedFiles: [],
801
+ readFiles: [],
762
802
  commandsRun: [],
763
803
  errors: [],
764
804
  lastErrorSignature: null,
@@ -869,7 +909,8 @@ async function runVerification(tools, ctx, bus, state, cwd) {
869
909
  bus.emit("tool_completed", `verify \xB7 ${tool.name}`, {
870
910
  tool: tool.name,
871
911
  success: res.success,
872
- output: res.output.slice(0, 2e3)
912
+ output: res.output.slice(0, 2e3),
913
+ resultChars: res.output.length
873
914
  });
874
915
  combined += `${tool.name}: ${res.output}
875
916
  `;
@@ -895,7 +936,10 @@ async function runAgent(options) {
895
936
  } = options;
896
937
  const state = createAgentState(task, maxIterations);
897
938
  const prior = formatConversationContext(cwd);
898
- const system = buildSystemPrompt(cwd) + (prior ? `
939
+ const knownFiles = formatRecentFiles(cwd);
940
+ const system = buildSystemPrompt(cwd) + (knownFiles ? `
941
+
942
+ ${knownFiles}` : "") + (prior ? `
899
943
 
900
944
  ${prior}
901
945
 
@@ -907,6 +951,7 @@ ${systemExtra}` : "");
907
951
  const toolCtx = {
908
952
  cwd,
909
953
  approval,
954
+ fileState: /* @__PURE__ */ new Map(),
910
955
  emit: (type, message, data) => {
911
956
  bus.emit(type, message, data);
912
957
  }
@@ -958,6 +1003,7 @@ ${systemExtra}` : "");
958
1003
  bus.emit("error", message + hint);
959
1004
  finalText = `Model request failed: ${message}`;
960
1005
  appendTurn(cwd, task, finalText);
1006
+ saveRecentFiles(cwd, state.readFiles, state.modifiedFiles);
961
1007
  bus.emit("task_completed", finalText.slice(0, 400), {
962
1008
  success: false,
963
1009
  finalText,
@@ -1011,7 +1057,7 @@ ${systemExtra}` : "");
1011
1057
  messages.push({
1012
1058
  role: "user",
1013
1059
  content: `SYSTEM NOTE: Verification failed.
1014
- ${verdict.output}
1060
+ ${verdict.output.slice(0, 2e3)}
1015
1061
  Investigate the root cause, fix it, then finish with a final answer.`
1016
1062
  });
1017
1063
  sawFinish = false;
@@ -1106,7 +1152,8 @@ Investigate the root cause, fix it, then finish with a final answer.`
1106
1152
  bus.emit("tool_completed", toolCallPreview(call), {
1107
1153
  tool: tool.name,
1108
1154
  success: result.success,
1109
- output: result.output.slice(0, 2e3)
1155
+ output: result.output.slice(0, 2e3),
1156
+ resultChars: result.output.length
1110
1157
  });
1111
1158
  if (tool.name === "apply_patch" && result.success && result.data?.kind === "patch") {
1112
1159
  bus.emit("patch", void 0, {
@@ -1117,7 +1164,10 @@ Investigate the root cause, fix it, then finish with a final answer.`
1117
1164
  }
1118
1165
  const readHeader = result.success && tool.name === "read_file" ? result.output.split("\n")[0] ?? "" : "";
1119
1166
  const readRel = readHeader ? normalizeRel(readHeader.replace(/\s+\(\d+ lines\)\s*$/, "").trim()) : "";
1120
- if (readRel) readMsgIndex.set(readRel, messages.length);
1167
+ if (readRel) {
1168
+ readMsgIndex.set(readRel, messages.length);
1169
+ if (!state.readFiles.includes(readRel) && state.readFiles.length < 20) state.readFiles.push(readRel);
1170
+ }
1121
1171
  if ((tool.name === "apply_patch" || tool.name === "write_file") && result.success) {
1122
1172
  const patchedRel = normalizeRel(
1123
1173
  typeof result.data?.path === "string" && result.data.path ? result.data.path : typeof args.path === "string" ? args.path : ""
@@ -1135,10 +1185,11 @@ Investigate the root cause, fix it, then finish with a final answer.`
1135
1185
  }
1136
1186
  }
1137
1187
  }
1188
+ const { text: historyOutput } = truncateOutput(result.output, MAX_RESULT_HISTORY_CHARS);
1138
1189
  messages.push({
1139
1190
  role: "tool",
1140
1191
  tool_call_id: call.id,
1141
- content: result.output
1192
+ content: historyOutput
1142
1193
  });
1143
1194
  }
1144
1195
  if (signal?.aborted) {
@@ -1155,7 +1206,10 @@ Investigate the root cause, fix it, then finish with a final answer.`
1155
1206
  bus.emit("warning", finalText);
1156
1207
  }
1157
1208
  const success = sawFinish && state.errors.length === 0;
1158
- if (finalText && !signal?.aborted) appendTurn(cwd, task, finalText);
1209
+ if (finalText && !signal?.aborted) {
1210
+ appendTurn(cwd, task, finalText);
1211
+ saveRecentFiles(cwd, state.readFiles, state.modifiedFiles);
1212
+ }
1159
1213
  bus.emit("task_completed", finalText.slice(0, 400), {
1160
1214
  success,
1161
1215
  finalText,
@@ -1168,7 +1222,7 @@ Investigate the root cause, fix it, then finish with a final answer.`
1168
1222
  });
1169
1223
  return { success, finalText, state };
1170
1224
  }
1171
- var MAX_CONTEXT_CHARS, RATE_LIMIT_MAX_ATTEMPTS, MAX_VERIFY, EDIT_TOOLS, KEEP_RECENT_TOOL;
1225
+ var MAX_CONTEXT_CHARS, MAX_RESULT_HISTORY_CHARS, RATE_LIMIT_MAX_ATTEMPTS, MAX_VERIFY, EDIT_TOOLS, KEEP_RECENT_TOOL;
1172
1226
  var init_loop = __esm({
1173
1227
  "src/agent/loop.ts"() {
1174
1228
  "use strict";
@@ -1178,6 +1232,7 @@ var init_loop = __esm({
1178
1232
  init_base();
1179
1233
  init_testing();
1180
1234
  MAX_CONTEXT_CHARS = 4e4;
1235
+ MAX_RESULT_HISTORY_CHARS = 12e3;
1181
1236
  RATE_LIMIT_MAX_ATTEMPTS = 8;
1182
1237
  MAX_VERIFY = 3;
1183
1238
  EDIT_TOOLS = /* @__PURE__ */ new Set(["apply_patch", "write_file", "move_path"]);
@@ -1189,7 +1244,7 @@ var init_loop = __esm({
1189
1244
  var version;
1190
1245
  var init_package = __esm({
1191
1246
  "package.json"() {
1192
- version = "0.1.17";
1247
+ version = "0.1.19";
1193
1248
  }
1194
1249
  });
1195
1250
 
@@ -1552,7 +1607,7 @@ function App(props) {
1552
1607
  const [toolHistory, setToolHistory] = useState3([]);
1553
1608
  const [inspectorIndex, setInspectorIndex] = useState3(null);
1554
1609
  const [detailsOpen, setDetailsOpen] = useState3(false);
1555
- const [showDetails, setShowDetails] = useState3(false);
1610
+ const [showDetails, setShowDetails] = useState3(true);
1556
1611
  const [thinkingText, setThinkingText] = useState3("");
1557
1612
  const [phase, setPhase] = useState3("");
1558
1613
  const [showEdits, setShowEdits] = useState3(true);
@@ -2363,6 +2418,10 @@ function createProvider(provider, config, modelOverride) {
2363
2418
  init_base();
2364
2419
  import fs2 from "fs";
2365
2420
  import path2 from "path";
2421
+ import crypto from "crypto";
2422
+ function contentHash(content) {
2423
+ return crypto.createHash("sha1").update(content).digest("hex").slice(0, 12);
2424
+ }
2366
2425
  function resolveWithinWorkspace(cwd, target) {
2367
2426
  const resolved = path2.resolve(cwd, target);
2368
2427
  const normalizedCwd = path2.resolve(cwd);
@@ -2497,9 +2556,16 @@ var readFile = {
2497
2556
  if (start > lines.length) {
2498
2557
  return {
2499
2558
  success: false,
2500
- output: `start_line ${start} is beyond end of file (${lines.length} lines)`
2559
+ output: `READ_RANGE_INVALID: start_line ${start} is beyond end of file (total_lines ${lines.length}). Valid range: 1-${lines.length}. Recovery: retry read_file with start_line <= ${Math.max(1, lines.length)} or omit the range.`
2560
+ };
2561
+ }
2562
+ if (Number.isNaN(end) || end < start) {
2563
+ return {
2564
+ success: false,
2565
+ output: `READ_RANGE_INVALID: end_line (${args.end_line}) is before start_line (${start}). Valid range: 1-${lines.length}. Recovery: retry with end_line >= start_line.`
2501
2566
  };
2502
2567
  }
2568
+ ctx.fileState?.set(filePath, contentHash(content));
2503
2569
  const slice = lines.slice(start - 1, end).map((line, i) => `${start + i}: ${line}`);
2504
2570
  const { text, truncated } = truncateOutput(slice.join("\n"), 12e3);
2505
2571
  const note = end < lines.length ? `
@@ -2529,6 +2595,7 @@ var writeFile = {
2529
2595
  const content = requireString(args, "content");
2530
2596
  fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
2531
2597
  fs2.writeFileSync(filePath, content, "utf8");
2598
+ ctx.fileState?.set(filePath, contentHash(content));
2532
2599
  return {
2533
2600
  success: true,
2534
2601
  output: `Wrote ${Buffer.byteLength(content, "utf8")} bytes to ${relativeToWorkspace(ctx.cwd, filePath)}`
@@ -2561,6 +2628,16 @@ var applyPatch = {
2561
2628
  if (oldString === newString) {
2562
2629
  return { success: false, output: "old_string and new_string are identical; nothing to change." };
2563
2630
  }
2631
+ const relPath = relativeToWorkspace(ctx.cwd, filePath);
2632
+ const totalLines = content.split(/\r?\n/).length;
2633
+ const currentHash = contentHash(content);
2634
+ const knownHash = ctx.fileState?.get(filePath);
2635
+ if (knownHash && knownHash !== currentHash) {
2636
+ return {
2637
+ success: false,
2638
+ output: `FILE_CHANGED: ${relPath} changed since it was last read (expected hash ${knownHash}, current ${currentHash}, total_lines ${totalLines}). Recovery: read_file ${relPath} again, then retry apply_patch against the current text.`
2639
+ };
2640
+ }
2564
2641
  const tryMatch = (c, o, n) => {
2565
2642
  const occ = c.split(o).length - 1;
2566
2643
  return occ === 1 ? c.replace(o, n) : null;
@@ -2590,15 +2667,16 @@ var applyPatch = {
2590
2667
  if (occ === 0) {
2591
2668
  return {
2592
2669
  success: false,
2593
- output: "old_string not found in file. Read the file again and copy the exact text, including whitespace."
2670
+ output: `PATCH_FAILED: old_string not found in ${relPath} (total_lines ${totalLines}, hash ${currentHash}). Recovery: read_file ${relPath} (or the relevant line range) and copy the exact text including whitespace, then retry. Do not guess.`
2594
2671
  };
2595
2672
  }
2596
2673
  return {
2597
2674
  success: false,
2598
- output: `old_string occurs ${occ} times. Include more surrounding lines to make it unique.`
2675
+ output: `PATCH_FAILED: old_string occurs ${occ} times in ${relPath}. Recovery: include more surrounding lines to make it unique, then retry.`
2599
2676
  };
2600
2677
  }
2601
2678
  fs2.writeFileSync(filePath, updated, "utf8");
2679
+ ctx.fileState?.set(filePath, contentHash(updated));
2602
2680
  const removed = oldString.split(/\r?\n/).length;
2603
2681
  const added = newString.split(/\r?\n/).length;
2604
2682
  return {
@@ -3169,12 +3247,12 @@ init_config();
3169
3247
  init_base();
3170
3248
  import fs5 from "fs";
3171
3249
  import path5 from "path";
3172
- import crypto from "crypto";
3250
+ import crypto2 from "crypto";
3173
3251
  function memoryDir() {
3174
3252
  return path5.join(configDir(), "memory");
3175
3253
  }
3176
3254
  function memoryFile(cwd) {
3177
- const key = crypto.createHash("sha1").update(path5.resolve(cwd)).digest("hex").slice(0, 16);
3255
+ const key = crypto2.createHash("sha1").update(path5.resolve(cwd)).digest("hex").slice(0, 16);
3178
3256
  return path5.join(memoryDir(), `${key}.json`);
3179
3257
  }
3180
3258
  function load(cwd) {
@@ -3216,7 +3294,7 @@ var memoryTool = {
3216
3294
  }
3217
3295
  const topic = firstString(args, "topic") ?? "";
3218
3296
  const entry = {
3219
- id: crypto.randomBytes(4).toString("hex"),
3297
+ id: crypto2.randomBytes(4).toString("hex"),
3220
3298
  topic,
3221
3299
  content: content.trim(),
3222
3300
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@menteeai/menteeswe",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
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",