@menteeai/menteeswe 0.1.19 → 0.1.21

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 +107 -40
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -751,39 +751,93 @@ function recentFile(cwd) {
751
751
  const hash = crypto3.createHash("sha1").update(cwd).digest("hex").slice(0, 16);
752
752
  return path8.join(sessionsDir(), `${hash}.files.json`);
753
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 {
754
+ function fileHash(content) {
755
+ return crypto3.createHash("sha1").update(content).digest("hex").slice(0, 12);
756
+ }
757
+ function normalizeRecords(value) {
758
+ if (!Array.isArray(value)) return [];
759
+ const out = [];
760
+ for (const item of value) {
761
+ if (typeof item === "string") out.push({ path: item });
762
+ else if (item && typeof item === "object" && typeof item.path === "string") {
763
+ out.push(item);
761
764
  }
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
765
  }
766
+ return out;
769
767
  }
770
- function formatRecentFiles(cwd) {
768
+ function loadRecentFiles(cwd) {
771
769
  try {
772
770
  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."
771
+ return { reads: normalizeRecords(parsed.reads), writes: normalizeRecords(parsed.writes) };
772
+ } catch {
773
+ return { reads: [], writes: [] };
774
+ }
775
+ }
776
+ function saveRecentFiles(cwd, reads, writes, hashes) {
777
+ try {
778
+ const prev = loadRecentFiles(cwd);
779
+ const absHash = (rel) => hashes?.get(path8.resolve(cwd, rel));
780
+ const merge = (known, fresh) => {
781
+ const byPath = /* @__PURE__ */ new Map();
782
+ for (const rel of fresh) {
783
+ byPath.set(rel, { path: rel, hash: absHash(rel) });
784
+ }
785
+ for (const rec of known) {
786
+ if (!byPath.has(rec.path)) byPath.set(rec.path, rec);
787
+ }
788
+ return [...byPath.values()].slice(0, 15);
789
+ };
790
+ fs9.writeFileSync(
791
+ recentFile(cwd),
792
+ JSON.stringify({ reads: merge(prev.reads, reads), writes: merge(prev.writes, writes) }, null, 2)
781
793
  );
782
- return lines.join("\n");
783
794
  } catch {
784
- return "";
785
795
  }
786
796
  }
797
+ function formatRecentFiles(cwd, prewarm) {
798
+ const stored = loadRecentFiles(cwd);
799
+ if (stored.reads.length === 0 && stored.writes.length === 0) return "";
800
+ const verify = (records) => {
801
+ const unchanged = [];
802
+ const changed = [];
803
+ const legacy = [];
804
+ for (const rec of records.slice(0, 15)) {
805
+ if (!rec.hash) {
806
+ legacy.push(rec);
807
+ continue;
808
+ }
809
+ try {
810
+ const content = fs9.readFileSync(path8.resolve(cwd, rec.path), "utf8");
811
+ const current = fileHash(content);
812
+ if (current === rec.hash) {
813
+ unchanged.push(rec);
814
+ prewarm?.set(path8.resolve(cwd, rec.path), current);
815
+ } else {
816
+ changed.push(rec);
817
+ }
818
+ } catch {
819
+ changed.push(rec);
820
+ }
821
+ }
822
+ return { unchanged, changed, legacy };
823
+ };
824
+ const w = verify(stored.writes);
825
+ const writePaths = new Set(stored.writes.map((r2) => r2.path));
826
+ const r = verify(stored.reads.filter((rec) => !writePaths.has(rec.path)));
827
+ const lines = ["# Known files from earlier tasks in this project"];
828
+ const join = (list) => list.map((rec) => rec.path).join(", ");
829
+ if (w.unchanged.length > 0) lines.push(`Modified recently, verified unchanged on disk: ${join(w.unchanged)}`);
830
+ if (r.unchanged.length > 0) lines.push(`Read recently, verified unchanged on disk: ${join(r.unchanged)}`);
831
+ const stale = [...w.changed, ...r.changed];
832
+ if (stale.length > 0) lines.push(`Changed (or missing) on disk since then \u2014 re-read before editing: ${join(stale)}`);
833
+ for (const rec of [...w.legacy, ...r.legacy]) {
834
+ lines.push(`Known from earlier tasks (state unknown): ${rec.path}`);
835
+ }
836
+ lines.push(
837
+ "For files verified unchanged you may apply_patch directly without re-reading; the harness validates the hash and will tell you if the file changed. Do not re-search or re-read verified files unless needed."
838
+ );
839
+ return lines.join("\n");
840
+ }
787
841
  var init_conversation = __esm({
788
842
  "src/agent/conversation.ts"() {
789
843
  "use strict";
@@ -843,13 +897,15 @@ function isRateLimitError(error) {
843
897
  }
844
898
  return /\b429\b|max rpm|rate limit|too many requests/i.test(error.message);
845
899
  }
846
- async function generateWithRetry(provider, model, system, messages, tools, bus, onToken) {
900
+ async function generateWithRetry(provider, model, system, messages, tools, bus, onToken, signal) {
847
901
  let lastError = null;
848
902
  for (let attempt = 0; attempt <= RATE_LIMIT_MAX_ATTEMPTS; attempt++) {
903
+ if (signal?.aborted) throw new Error("Aborted by user");
849
904
  try {
850
- return await provider.generate({ system, messages, tools: tools.schemas(), onToken }, model);
905
+ return await provider.generate({ system, messages, tools: tools.schemas(), onToken, signal }, model);
851
906
  } catch (error) {
852
907
  lastError = error;
908
+ if (signal?.aborted) throw lastError;
853
909
  if (attempt >= RATE_LIMIT_MAX_ATTEMPTS || !isRateLimitError(lastError)) {
854
910
  throw lastError;
855
911
  }
@@ -935,8 +991,9 @@ async function runAgent(options) {
935
991
  signal
936
992
  } = options;
937
993
  const state = createAgentState(task, maxIterations);
994
+ const fileState = /* @__PURE__ */ new Map();
938
995
  const prior = formatConversationContext(cwd);
939
- const knownFiles = formatRecentFiles(cwd);
996
+ const knownFiles = formatRecentFiles(cwd, fileState);
940
997
  const system = buildSystemPrompt(cwd) + (knownFiles ? `
941
998
 
942
999
  ${knownFiles}` : "") + (prior ? `
@@ -951,7 +1008,7 @@ ${systemExtra}` : "");
951
1008
  const toolCtx = {
952
1009
  cwd,
953
1010
  approval,
954
- fileState: /* @__PURE__ */ new Map(),
1011
+ fileState,
955
1012
  emit: (type, message, data) => {
956
1013
  bus.emit(type, message, data);
957
1014
  }
@@ -990,9 +1047,13 @@ ${systemExtra}` : "");
990
1047
  };
991
1048
  let response;
992
1049
  try {
993
- response = await generateWithRetry(provider, model, system, messages, tools, bus, onToken);
1050
+ response = await generateWithRetry(provider, model, system, messages, tools, bus, onToken, signal);
994
1051
  } catch (error) {
995
1052
  flushThink();
1053
+ if (signal?.aborted) {
1054
+ finalText = "Task cancelled.";
1055
+ break;
1056
+ }
996
1057
  const message = error.message;
997
1058
  let hint = "";
998
1059
  if (/insufficient balance|no resource package|recharge/i.test(message)) {
@@ -1003,7 +1064,7 @@ ${systemExtra}` : "");
1003
1064
  bus.emit("error", message + hint);
1004
1065
  finalText = `Model request failed: ${message}`;
1005
1066
  appendTurn(cwd, task, finalText);
1006
- saveRecentFiles(cwd, state.readFiles, state.modifiedFiles);
1067
+ saveRecentFiles(cwd, state.readFiles, state.modifiedFiles, fileState);
1007
1068
  bus.emit("task_completed", finalText.slice(0, 400), {
1008
1069
  success: false,
1009
1070
  finalText,
@@ -1076,6 +1137,7 @@ Investigate the root cause, fix it, then finish with a final answer.`
1076
1137
  bus.emit("info", response.content.trim().slice(0, 300));
1077
1138
  }
1078
1139
  for (const call of response.toolCalls) {
1140
+ if (signal?.aborted) break;
1079
1141
  const tool = tools.get(call.function.name);
1080
1142
  if (!tool) {
1081
1143
  messages.push({
@@ -1208,7 +1270,7 @@ Investigate the root cause, fix it, then finish with a final answer.`
1208
1270
  const success = sawFinish && state.errors.length === 0;
1209
1271
  if (finalText && !signal?.aborted) {
1210
1272
  appendTurn(cwd, task, finalText);
1211
- saveRecentFiles(cwd, state.readFiles, state.modifiedFiles);
1273
+ saveRecentFiles(cwd, state.readFiles, state.modifiedFiles, fileState);
1212
1274
  }
1213
1275
  bus.emit("task_completed", finalText.slice(0, 400), {
1214
1276
  success,
@@ -1244,7 +1306,7 @@ var init_loop = __esm({
1244
1306
  var version;
1245
1307
  var init_package = __esm({
1246
1308
  "package.json"() {
1247
- version = "0.1.19";
1309
+ version = "0.1.21";
1248
1310
  }
1249
1311
  });
1250
1312
 
@@ -2197,16 +2259,20 @@ var OpenAICompatProvider = class {
2197
2259
  ...request.temperature !== void 0 ? { temperature: request.temperature } : {},
2198
2260
  ...request.maxTokens ? { max_tokens: request.maxTokens } : {}
2199
2261
  };
2262
+ const reqOptions = request.signal ? { signal: request.signal } : void 0;
2200
2263
  if (!request.onToken) {
2201
- const response2 = await this.client.chat.completions.create(baseParams);
2264
+ const response2 = await this.client.chat.completions.create(baseParams, reqOptions);
2202
2265
  return parseResponse(response2);
2203
2266
  }
2204
2267
  const streamOnce = async (withUsage) => {
2205
- const stream = await this.client.chat.completions.create({
2206
- ...baseParams,
2207
- stream: true,
2208
- ...withUsage ? { stream_options: { include_usage: true } } : {}
2209
- });
2268
+ const stream = await this.client.chat.completions.create(
2269
+ {
2270
+ ...baseParams,
2271
+ stream: true,
2272
+ ...withUsage ? { stream_options: { include_usage: true } } : {}
2273
+ },
2274
+ reqOptions
2275
+ );
2210
2276
  let content = "";
2211
2277
  let finishReason = "stop";
2212
2278
  let usage;
@@ -2257,7 +2323,7 @@ var OpenAICompatProvider = class {
2257
2323
  } catch {
2258
2324
  }
2259
2325
  }
2260
- const response = await this.client.chat.completions.create(baseParams);
2326
+ const response = await this.client.chat.completions.create(baseParams, reqOptions);
2261
2327
  const parsed = parseResponse(response);
2262
2328
  if (parsed.content) {
2263
2329
  for (let i = 0; i < parsed.content.length; i += 3) {
@@ -3838,7 +3904,8 @@ program.argument("[task...]", "the software task to perform (omit to type it int
3838
3904
  yes: options.yes === true,
3839
3905
  maxIterations
3840
3906
  }
3841
- )
3907
+ ),
3908
+ { exitOnCtrlC: false }
3842
3909
  );
3843
3910
  await waitUntilExit();
3844
3911
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@menteeai/menteeswe",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
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",