@menteeai/menteeswe 0.1.5 → 0.1.9

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 +401 -227
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -96,6 +96,102 @@ var init_base = __esm({
96
96
  }
97
97
  });
98
98
 
99
+ // src/tools/exec.ts
100
+ import { spawn } from "child_process";
101
+ function killTree(child) {
102
+ if (child.pid === void 0) return;
103
+ if (process.platform === "win32") {
104
+ spawn(`taskkill /pid ${String(child.pid)} /T /F`, { stdio: "ignore", shell: true });
105
+ } else {
106
+ child.kill("SIGKILL");
107
+ }
108
+ }
109
+ function killActiveChild() {
110
+ if (!activeChild) return;
111
+ try {
112
+ activeChild.kill("SIGKILL");
113
+ } catch {
114
+ }
115
+ killTree(activeChild);
116
+ }
117
+ async function runShellCommand(command, cwd, timeoutMs = 12e4, maxChars = 1e5) {
118
+ return new Promise((resolve) => {
119
+ const started = Date.now();
120
+ const child = spawn(command, {
121
+ shell: true,
122
+ cwd,
123
+ env: { ...process.env, MENTEE: "1" },
124
+ windowsHide: true
125
+ });
126
+ activeChild = child;
127
+ let stdout = "";
128
+ let stderr = "";
129
+ let killed = false;
130
+ const timer = setTimeout(() => {
131
+ killed = true;
132
+ killTree(child);
133
+ }, timeoutMs);
134
+ child.stdout?.on("data", (chunk) => {
135
+ if (stdout.length < maxChars * 2) stdout += chunk.toString("utf8");
136
+ });
137
+ child.stderr?.on("data", (chunk) => {
138
+ if (stderr.length < maxChars * 2) stderr += chunk.toString("utf8");
139
+ });
140
+ child.on("error", (error) => {
141
+ clearTimeout(timer);
142
+ if (activeChild === child) activeChild = null;
143
+ resolve({
144
+ exitCode: -1,
145
+ stdout,
146
+ stderr: `${stderr}
147
+ ${error.message}`.trim(),
148
+ durationMs: Date.now() - started
149
+ });
150
+ });
151
+ child.on("close", (code) => {
152
+ clearTimeout(timer);
153
+ if (activeChild === child) activeChild = null;
154
+ resolve({
155
+ exitCode: killed ? "timeout" : code ?? -1,
156
+ stdout,
157
+ stderr,
158
+ durationMs: Date.now() - started
159
+ });
160
+ });
161
+ });
162
+ }
163
+ function formatOutcome(outcome, maxChars = 1e5) {
164
+ const cut = (text, limit) => {
165
+ if (text.length <= limit) return { text, truncated: false };
166
+ const head = Math.floor(limit * 0.7);
167
+ return {
168
+ text: `${text.slice(0, head)}
169
+ ...[truncated, ${text.length - head} chars removed]...`,
170
+ truncated: true
171
+ };
172
+ };
173
+ const out = cut(outcome.stdout.trim(), maxChars);
174
+ const err = cut(outcome.stderr.trim(), Math.floor(maxChars / 2));
175
+ const sections = [`exit_code: ${outcome.exitCode}`, `duration_ms: ${outcome.durationMs}`];
176
+ if (out.text) sections.push(`stdout:
177
+ ${out.text}`);
178
+ if (err.text) sections.push(`stderr:
179
+ ${err.text}`);
180
+ if (!out.text && !err.text) sections.push("(no output)");
181
+ return {
182
+ output: sections.join("\n"),
183
+ success: outcome.exitCode === 0,
184
+ truncated: out.truncated || err.truncated
185
+ };
186
+ }
187
+ var activeChild;
188
+ var init_exec = __esm({
189
+ "src/tools/exec.ts"() {
190
+ "use strict";
191
+ activeChild = null;
192
+ }
193
+ });
194
+
99
195
  // src/events.ts
100
196
  var EventBus;
101
197
  var init_events = __esm({
@@ -155,17 +251,17 @@ function friendlyToolName(name) {
155
251
  function formatEvent(event) {
156
252
  switch (event.type) {
157
253
  case "task_started":
158
- return chalk.bold.magenta(`\u25B8 Task: ${event.message ?? ""}`);
254
+ return "\n" + chalk.bold.magenta(`\u25B8 Task: ${event.message ?? ""}`);
159
255
  case "model_request":
160
- return chalk.gray("\u25CF thinking...");
256
+ return chalk.gray("\u25CF thinking...");
161
257
  case "info":
162
- return chalk.cyan(` ${event.message ?? ""}`);
258
+ return chalk.cyan.bold(`\u{1F4AD} ${event.message ?? ""}`);
163
259
  case "tool_started": {
164
260
  const tool = typeof event.data?.tool === "string" ? event.data.tool : "";
165
261
  const color = CATEGORY_COLOR[TOOL_CATEGORY[tool] ?? "other"];
166
262
  const friendly = friendlyToolName(tool);
167
263
  const preview = tool && event.message?.startsWith(tool) ? event.message.slice(tool.length).trim() : event.message ?? "";
168
- return color(`\u2699 ${friendly} (${preview})...`);
264
+ return color(`\u2699 ${friendly} (${preview})...`);
169
265
  }
170
266
  case "tool_completed": {
171
267
  const tool = typeof event.data?.tool === "string" ? event.data.tool : "";
@@ -173,14 +269,32 @@ function formatEvent(event) {
173
269
  const output = typeof event.data?.output === "string" ? event.data.output.trim() : "";
174
270
  const firstLine = (output.split("\n")[0] ?? event.message ?? "").slice(0, 160);
175
271
  const color = success ? CATEGORY_COLOR[TOOL_CATEGORY[tool] ?? "other"] : chalk.red;
176
- return color(` ${success ? "\u2713" : "\u2717"} ${firstLine}`);
272
+ return color(` ${success ? "\u2713" : "\u2717"} ${firstLine}`);
177
273
  }
178
274
  case "approval":
179
- return chalk.magenta(`\u{1F510} Asking permission to use ${event.message ?? "tool"}...`);
275
+ return chalk.magenta(`\u{1F510} Asking permission to use ${event.message ?? "tool"}...`);
180
276
  case "warning":
181
- return chalk.yellow(`\u26A0 ${event.message ?? ""}`);
277
+ return chalk.yellow(`\u26A0 ${event.message ?? ""}`);
182
278
  case "error":
183
- return chalk.red(`\u2716 ${event.message ?? ""}`);
279
+ return chalk.red(`\u2716 ${event.message ?? ""}`);
280
+ case "patch": {
281
+ const filePath = typeof event.data?.path === "string" ? event.data.path : "file";
282
+ const oldStr = typeof event.data?.old_string === "string" ? event.data.old_string : "";
283
+ const newStr = typeof event.data?.new_string === "string" ? event.data.new_string : "";
284
+ const MAX = 60;
285
+ const oldLines = oldStr.split(/\r?\n/);
286
+ const newLines = newStr.split(/\r?\n/);
287
+ const out = [chalk.bold.magenta(`\u270E ${filePath}`)];
288
+ for (const line of oldLines.slice(0, MAX)) {
289
+ out.push(chalk.bgRed.white(` - ${line}`));
290
+ }
291
+ if (oldLines.length > MAX) out.push(chalk.bgRed.white(` \u2026 ${oldLines.length - MAX} more removed line(s)`));
292
+ for (const line of newLines.slice(0, MAX)) {
293
+ out.push(chalk.bgGreen.black(` + ${line}`));
294
+ }
295
+ if (newLines.length > MAX) out.push(chalk.bgGreen.black(` \u2026 ${newLines.length - MAX} more added line(s)`));
296
+ return out.join("\n");
297
+ }
184
298
  case "task_completed": {
185
299
  const success = event.data?.success === true;
186
300
  const usage = event.data?.usage;
@@ -201,9 +315,13 @@ function formatEvent(event) {
201
315
  if (modifiedFiles.length > 0) stats.push(`files ${modifiedFiles.length}`);
202
316
  const sep = chalk.dim("\u2500".repeat(48));
203
317
  const statsLine = chalk.cyan(` ${stats.join(" \xB7 ")}`);
204
- const statusLine = success ? chalk.green.bold("\u2714 Task completed") : chalk.red.bold("\u2716 Task failed");
205
- const block = [statusLine, sep, statsLine, sep];
206
- return block.join("\n");
318
+ const cancelled = event.data?.cancelled === true;
319
+ const statusLine = cancelled ? chalk.yellow.bold("\u2716 Task cancelled") : success ? chalk.green.bold("\u2714 Task completed") : chalk.red.bold("\u2716 Task failed");
320
+ const filesLine = modifiedFiles.length > 0 ? chalk.cyan.dim(` \u{1F4DD} ${modifiedFiles.length} file(s): ${modifiedFiles.join(", ")}`) : "";
321
+ const tipLine = success ? chalk.dim(" Next: type a new task, or /help for commands") : chalk.yellow.dim(" The task did not finish \u2014 check the steps above and retry");
322
+ const detailsLine = toolCalls > 0 ? chalk.dim(` \u{1F50D} press d to toggle ${toolCalls} tool call(s)`) : "";
323
+ const block = [sep, statusLine, statsLine, filesLine, tipLine, detailsLine, sep, ""];
324
+ return block.filter((line) => line !== "").join("\n");
207
325
  }
208
326
  default:
209
327
  return null;
@@ -366,6 +484,9 @@ function buildSystemPrompt(cwd) {
366
484
  const tree = projectTree(cwd).trim() || "(empty)";
367
485
  return `You are MenteE, an autonomous software-engineering agent working inside a repository on the user's machine.
368
486
 
487
+ # Your design philosophy
488
+ You are built around a tool harness: every meaningful action (reading, searching, editing, running, git) goes through a tool. Use the harness decisively and efficiently. Prefer precise tool calls over long prose so token usage stays very low while your engineering stays sharp and intelligent. A good session does more with fewer tokens \u2014 call exactly the tool needed, read only what you must, and let the harness do the heavy lifting.
489
+
369
490
  # Environment
370
491
  - Workspace: ${cwd}
371
492
  - Platform: ${os2.platform()} (${os2.release()}), shell commands run with the system shell
@@ -386,6 +507,8 @@ ${tree}
386
507
  # Tool usage notes
387
508
  - Call only the tools needed for the immediate next step. Do NOT read files you already understand, and do NOT read build/config files (e.g. *.config.ts, tsconfig.json) unless the task specifically needs them.
388
509
  - Prefer search_code over reading many files.
510
+ - NEVER start long-running servers, dev servers, or watchers (e.g. 'npm run dev', 'npm start', 'vite', 'npm run watch', 'python -m http.server') yourself. These block and never exit. If the user needs to run the app, give them the exact command to run in their OWN terminal. Only run commands that exit on their own.
511
+ - If a task result requires running the app to verify, say so and provide the command \u2014 do not run it for them.
389
512
 
390
513
  # Research strategy (IMPORTANT \u2014 HARD RULE)
391
514
  - The file tree above already shows the whole structure. Do NOT read files just to "see what's there".
@@ -538,11 +661,11 @@ function isRateLimitError(error) {
538
661
  }
539
662
  return /\b429\b|max rpm|rate limit|too many requests/i.test(error.message);
540
663
  }
541
- async function generateWithRetry(provider, model, system, messages, tools, bus) {
664
+ async function generateWithRetry(provider, model, system, messages, tools, bus, onToken) {
542
665
  let lastError = null;
543
666
  for (let attempt = 0; attempt <= RATE_LIMIT_MAX_ATTEMPTS; attempt++) {
544
667
  try {
545
- return await provider.generate({ system, messages, tools: tools.schemas() }, model);
668
+ return await provider.generate({ system, messages, tools: tools.schemas(), onToken }, model);
546
669
  } catch (error) {
547
670
  lastError = error;
548
671
  if (attempt >= RATE_LIMIT_MAX_ATTEMPTS || !isRateLimitError(lastError)) {
@@ -593,7 +716,8 @@ async function runAgent(options) {
593
716
  bus,
594
717
  approval,
595
718
  maxIterations = 40,
596
- systemExtra
719
+ systemExtra,
720
+ signal
597
721
  } = options;
598
722
  const state = createAgentState(task, maxIterations);
599
723
  const prior = formatConversationContext(cwd);
@@ -618,13 +742,31 @@ ${systemExtra}` : "");
618
742
  let finalText = "";
619
743
  let sawFinish = false;
620
744
  while (state.iteration < maxIterations) {
745
+ if (signal?.aborted) {
746
+ finalText = "Task cancelled.";
747
+ break;
748
+ }
621
749
  state.iteration += 1;
622
750
  state.usage.modelRequests += 1;
623
751
  bus.emit("model_request", `iteration ${state.iteration}/${maxIterations}`);
752
+ let thinkBuf = "";
753
+ let thinkTimer = null;
754
+ const flushThink = () => {
755
+ if (thinkBuf) {
756
+ bus.emit("thinking", thinkBuf);
757
+ thinkBuf = "";
758
+ }
759
+ thinkTimer = null;
760
+ };
761
+ const onToken = (delta) => {
762
+ thinkBuf += delta;
763
+ if (thinkTimer == null) thinkTimer = setTimeout(flushThink, 80);
764
+ };
624
765
  let response;
625
766
  try {
626
- response = await generateWithRetry(provider, model, system, messages, tools, bus);
767
+ response = await generateWithRetry(provider, model, system, messages, tools, bus, onToken);
627
768
  } catch (error) {
769
+ flushThink();
628
770
  const message = error.message;
629
771
  let hint = "";
630
772
  if (/insufficient balance|no resource package|recharge/i.test(message)) {
@@ -646,6 +788,7 @@ ${systemExtra}` : "");
646
788
  });
647
789
  return { success: false, finalText, state };
648
790
  }
791
+ flushThink();
649
792
  bus.emit(
650
793
  "model_response",
651
794
  void 0,
@@ -674,7 +817,7 @@ ${systemExtra}` : "");
674
817
  content: response.content,
675
818
  tool_calls: response.toolCalls
676
819
  });
677
- if (response.content && response.content.trim()) {
820
+ if (!onToken && response.content && response.content.trim()) {
678
821
  bus.emit("info", response.content.trim().slice(0, 300));
679
822
  }
680
823
  for (const call of response.toolCalls) {
@@ -768,12 +911,23 @@ ${systemExtra}` : "");
768
911
  success: result.success,
769
912
  output: result.output.slice(0, 2e3)
770
913
  });
914
+ if (tool.name === "apply_patch" && result.success && result.data?.kind === "patch") {
915
+ bus.emit("patch", void 0, {
916
+ path: result.data.path,
917
+ old_string: result.data.old_string,
918
+ new_string: result.data.new_string
919
+ });
920
+ }
771
921
  messages.push({
772
922
  role: "tool",
773
923
  tool_call_id: call.id,
774
924
  content: result.output
775
925
  });
776
926
  }
927
+ if (signal?.aborted) {
928
+ finalText = "Task cancelled.";
929
+ break;
930
+ }
777
931
  if (estimateContextChars(messages, system) > MAX_CONTEXT_CHARS) {
778
932
  bus.emit("warning", "Context is large; trimming oldest tool outputs.");
779
933
  trimOldToolResults(messages);
@@ -784,7 +938,7 @@ ${systemExtra}` : "");
784
938
  bus.emit("warning", finalText);
785
939
  }
786
940
  const success = sawFinish && state.errors.length === 0;
787
- if (finalText) appendTurn(cwd, task, finalText);
941
+ if (finalText && !signal?.aborted) appendTurn(cwd, task, finalText);
788
942
  bus.emit("task_completed", finalText.slice(0, 400), {
789
943
  success,
790
944
  finalText,
@@ -792,7 +946,8 @@ ${systemExtra}` : "");
792
946
  toolCalls: state.totalToolCalls,
793
947
  modifiedFiles: state.modifiedFiles,
794
948
  usage: state.usage,
795
- durationMs: Date.now() - state.startedAt
949
+ durationMs: Date.now() - state.startedAt,
950
+ cancelled: signal?.aborted === true
796
951
  });
797
952
  return { success, finalText, state };
798
953
  }
@@ -977,7 +1132,7 @@ var init_KeyDialog = __esm({
977
1132
  var version;
978
1133
  var init_package = __esm({
979
1134
  "package.json"() {
980
- version = "0.1.5";
1135
+ version = "0.1.9";
981
1136
  }
982
1137
  });
983
1138
 
@@ -987,11 +1142,12 @@ __export(App_exports, {
987
1142
  App: () => App
988
1143
  });
989
1144
  import { useEffect as useEffect2, useMemo as useMemo2, useRef, useState as useState3 } from "react";
990
- import { Box as Box4, Static, Text as Text4, useApp, useInput as useInput4 } from "ink";
1145
+ import { Box as Box4, Static, Text as Text4, useApp, useInput as useInput4, useStdout } from "ink";
991
1146
  import TextInput2 from "ink-text-input";
992
1147
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
993
1148
  function App(props) {
994
1149
  const { exit } = useApp();
1150
+ const { stdout } = useStdout();
995
1151
  const bus = useMemo2(() => new EventBus(), []);
996
1152
  const bridge = useMemo2(() => new ApprovalBridge(), []);
997
1153
  const [log, setLog] = useState3([]);
@@ -1014,23 +1170,31 @@ function App(props) {
1014
1170
  const [toolHistory, setToolHistory] = useState3([]);
1015
1171
  const [inspectorIndex, setInspectorIndex] = useState3(null);
1016
1172
  const [detailsOpen, setDetailsOpen] = useState3(false);
1017
- const [showTools, setShowTools] = useState3(true);
1173
+ const [showDetails, setShowDetails] = useState3(false);
1174
+ const [thinkingText, setThinkingText] = useState3("");
1018
1175
  const pendingTool = useRef(null);
1019
1176
  const counter = useRef(0);
1020
1177
  const stateRef = useRef({ running, approval, dialog, factoryResult, providerName, model });
1021
1178
  stateRef.current = { running, approval, dialog, factoryResult, providerName, model };
1022
- const appendLog = (text) => {
1179
+ const abortRef = useRef(null);
1180
+ const appendLog = (text, noise = false) => {
1023
1181
  if (!text) return;
1024
1182
  counter.current += 1;
1025
- const line = { id: counter.current, text };
1183
+ const line = { id: counter.current, text, noise };
1026
1184
  setLog((prev) => [...prev, line]);
1027
1185
  };
1028
1186
  useEffect2(() => {
1029
- const HIDDEN_NOISY = /* @__PURE__ */ new Set(["tool_started", "tool_completed", "model_request", "info"]);
1187
+ const TOOL_NOISE = /* @__PURE__ */ new Set(["tool_started", "tool_completed", "model_request"]);
1030
1188
  const unsubscribeLog = bus.subscribe((event) => {
1031
- if (!(showTools === false && HIDDEN_NOISY.has(event.type))) {
1032
- appendLog(formatEvent(event));
1189
+ if (event.type === "thinking") {
1190
+ const delta = (event.message ?? "").replace(/[\r\n]+/g, " ");
1191
+ if (delta) setThinkingText((prev) => (prev + delta).slice(-4e3));
1192
+ return;
1033
1193
  }
1194
+ if (event.type === "model_request" || event.type === "task_completed") {
1195
+ setThinkingText("");
1196
+ }
1197
+ appendLog(formatEvent(event), TOOL_NOISE.has(event.type));
1034
1198
  if (event.type === "tool_started") {
1035
1199
  const name = typeof event.data?.tool === "string" ? event.data.tool : "";
1036
1200
  const target = typeof event.message === "string" ? event.message : "";
@@ -1063,12 +1227,18 @@ function App(props) {
1063
1227
  unsubscribeLog();
1064
1228
  unsubscribeLog2();
1065
1229
  };
1066
- }, [bus, bridge, showTools]);
1230
+ }, [bus, bridge, showDetails]);
1067
1231
  useEffect2(() => {
1068
1232
  if (!running) return;
1069
1233
  const timer = setInterval(() => setFrame((f) => (f + 1) % SPINNER_FRAMES.length), 80);
1070
1234
  return () => clearInterval(timer);
1071
1235
  }, [running]);
1236
+ useEffect2(() => {
1237
+ const turns = loadConversation(props.cwd);
1238
+ if (turns.length > 0) {
1239
+ appendLog(`Resumed previous session \u2014 ${turns.length} prior turn(s) loaded as context.`);
1240
+ }
1241
+ }, []);
1072
1242
  useEffect2(() => {
1073
1243
  if (!typing || finalPending == null) return;
1074
1244
  if (typedText.length >= finalPending.length) {
@@ -1076,8 +1246,11 @@ function App(props) {
1076
1246
  return;
1077
1247
  }
1078
1248
  const timer = setTimeout(() => {
1079
- setTypedText(finalPending.slice(0, typedText.length + 3));
1080
- }, 16);
1249
+ const rest = finalPending.slice(typedText.length);
1250
+ const match = /^(\S+\s*)/.exec(rest);
1251
+ const step = match ? match[0].length : 1;
1252
+ setTypedText(finalPending.slice(0, typedText.length + step));
1253
+ }, 55);
1081
1254
  return () => clearTimeout(timer);
1082
1255
  }, [typing, finalPending, typedText]);
1083
1256
  const updateConfig = (mutate) => {
@@ -1222,7 +1395,10 @@ function App(props) {
1222
1395
  setToolHistory([]);
1223
1396
  setInspectorIndex(null);
1224
1397
  setDetailsOpen(false);
1398
+ setShowDetails(false);
1225
1399
  setRunning(true);
1400
+ const ac = new AbortController();
1401
+ abortRef.current = ac;
1226
1402
  try {
1227
1403
  await runAgent({
1228
1404
  task,
@@ -1233,7 +1409,8 @@ function App(props) {
1233
1409
  bus,
1234
1410
  approval: props.yes ? async () => "allow" : bridge.handler,
1235
1411
  maxIterations: props.maxIterations,
1236
- systemExtra: props.systemExtra
1412
+ systemExtra: props.systemExtra,
1413
+ signal: ac.signal
1237
1414
  });
1238
1415
  } catch (error) {
1239
1416
  bus.emit("error", error.message);
@@ -1270,16 +1447,25 @@ function App(props) {
1270
1447
  }
1271
1448
  };
1272
1449
  useInput4((input, key) => {
1450
+ if (key.ctrl && input === "c") {
1451
+ if (stateRef.current.running) {
1452
+ appendLog("\u23F9 Interrupted by you \u2014 stopping the current task and returning to the prompt.");
1453
+ killActiveChild();
1454
+ abortRef.current?.abort();
1455
+ return;
1456
+ }
1457
+ exit();
1458
+ setTimeout(() => process.exit(0), 50);
1459
+ return;
1460
+ }
1461
+ if (key.meta && (input === "d" || input === "D")) {
1462
+ setShowDetails((v) => !v);
1463
+ return;
1464
+ }
1273
1465
  if (stateRef.current.dialog || stateRef.current.approval || stateRef.current.running) return;
1274
1466
  if (key.meta && (input === "m" || input === "M")) openModelDialog();
1275
1467
  else if (key.ctrl && input === "p") setDialog({ kind: "provider" });
1276
1468
  else if (key.ctrl && input === "k") setDialog({ kind: "key" });
1277
- else if (input === "d" || input === "D") cycleInspector();
1278
- else if (input === "t" || input === "T") setShowTools((v) => !v);
1279
- else if (key.ctrl && input === "c") {
1280
- exit();
1281
- setTimeout(() => process.exit(0), 50);
1282
- }
1283
1469
  });
1284
1470
  const modelLabel = model ?? factoryResult.provider?.defaultModel ?? (factoryResult.error ? "no key" : "");
1285
1471
  const header = /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginBottom: 1, children: [
@@ -1290,114 +1476,117 @@ function App(props) {
1290
1476
  " \xB7 ",
1291
1477
  providerName,
1292
1478
  modelLabel ? `:${modelLabel}` : "",
1293
- " \xB7 tools ",
1294
- showTools ? "on" : "off",
1479
+ " \xB7 details ",
1480
+ showDetails ? "open" : "collapsed",
1295
1481
  " \xB7 ",
1296
1482
  props.cwd
1297
1483
  ] }),
1298
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Alt+M model \xB7 Ctrl+P provider \xB7 Ctrl+K key \xB7 t hide tools \xB7 d details \xB7 /help" })
1484
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Alt+M model \xB7 Ctrl+P provider \xB7 Ctrl+K key \xB7 Alt+D details \xB7 /help" })
1299
1485
  ] }, "header");
1486
+ const cols = stdout.columns || 80;
1487
+ const marquee = thinkingText.slice(-Math.max(1, cols - 4));
1300
1488
  return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
1301
- /* @__PURE__ */ jsx4(
1302
- Static,
1303
- {
1304
- items: [
1305
- { key: "header", text: header },
1306
- ...log.map((line) => ({ key: `l${line.id}`, text: /* @__PURE__ */ jsx4(Text4, { children: line.text }) }))
1307
- ],
1308
- children: (item) => /* @__PURE__ */ jsx4(Box4, { children: item.text }, item.key)
1309
- }
1310
- ),
1311
- finalPending !== null ? /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, children: [
1312
- /* @__PURE__ */ jsx4(Text4, { color: "green", children: typedText }),
1313
- typing ? /* @__PURE__ */ jsx4(Text4, { color: "green", children: "\u258C" }) : null
1314
- ] }) : null,
1315
- detailsOpen && inspectorIndex !== null && toolHistory[inspectorIndex] ? (() => {
1316
- const rec = toolHistory[inspectorIndex];
1317
- const outLines = rec.output.split("\n").slice(0, 24).join("\n");
1318
- return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginTop: 1, borderStyle: "round", borderColor: "gray", paddingX: 1, children: [
1319
- /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
1320
- " tool details (",
1321
- inspectorIndex + 1,
1322
- "/",
1323
- toolHistory.length,
1324
- ") \xB7 press d to cycle "
1325
- ] }),
1326
- /* @__PURE__ */ jsxs4(Text4, { color: toolColorName(rec.name), bold: true, children: [
1327
- rec.name,
1328
- " ",
1329
- rec.target
1330
- ] }),
1331
- /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
1332
- "args: ",
1333
- rec.args.slice(0, 400) || "(none)"
1334
- ] }),
1335
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: outLines || "(no output)" })
1336
- ] });
1337
- })() : null,
1338
- dialog?.kind === "model" ? /* @__PURE__ */ jsx4(
1339
- SelectDialog,
1340
- {
1341
- title: `Model \u2014 ${providerName}`,
1342
- items: modelItems,
1343
- loading: modelLoading,
1344
- error: modelError,
1345
- current: model ?? factoryResult.provider?.defaultModel,
1346
- onSelect: (id) => {
1347
- setDialog(null);
1348
- setModelAndPersist(id);
1349
- },
1350
- onClose: () => setDialog(null)
1351
- }
1352
- ) : dialog?.kind === "provider" ? /* @__PURE__ */ jsx4(
1353
- SelectDialog,
1354
- {
1355
- title: "Provider",
1356
- items: PROVIDER_NAMES,
1357
- descriptions: PROVIDER_DESCRIPTIONS,
1358
- current: providerName,
1359
- onSelect: (name) => {
1360
- setDialog(null);
1361
- switchProvider(name);
1362
- },
1363
- onClose: () => setDialog(null)
1364
- }
1365
- ) : dialog?.kind === "key" ? /* @__PURE__ */ jsx4(
1366
- KeyDialog,
1367
- {
1368
- providerName,
1369
- onSubmit: (value) => {
1370
- setDialog(null);
1371
- saveKey(value);
1372
- },
1373
- onClose: () => setDialog(null)
1374
- }
1375
- ) : approval ? /* @__PURE__ */ jsx4(
1376
- ApprovalPrompt,
1377
- {
1378
- request: approval,
1379
- onDecision: (decision) => {
1380
- bridge.resolve(decision);
1381
- if (decision === "always") bus.emit("info", "Approved for the rest of this session.");
1489
+ /* @__PURE__ */ jsx4(Static, { items: [{ key: "header", text: header }], children: (item) => /* @__PURE__ */ jsx4(Box4, { children: item.text }, item.key) }),
1490
+ /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
1491
+ log.filter((l) => showDetails || !l.noise).map((line) => /* @__PURE__ */ jsx4(Text4, { children: line.text }, line.id)),
1492
+ finalPending !== null ? /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, children: [
1493
+ /* @__PURE__ */ jsx4(Text4, { color: "green", children: typedText }),
1494
+ typing ? /* @__PURE__ */ jsx4(Text4, { color: "green", children: "\u258C" }) : null
1495
+ ] }) : null,
1496
+ detailsOpen && inspectorIndex !== null && toolHistory[inspectorIndex] ? (() => {
1497
+ const rec = toolHistory[inspectorIndex];
1498
+ const outLines = rec.output.split("\n").slice(0, 24).join("\n");
1499
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginTop: 1, borderStyle: "round", borderColor: "gray", paddingX: 1, children: [
1500
+ /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
1501
+ " tool details (",
1502
+ inspectorIndex + 1,
1503
+ "/",
1504
+ toolHistory.length,
1505
+ ") \xB7 press d to cycle "
1506
+ ] }),
1507
+ /* @__PURE__ */ jsxs4(Text4, { color: toolColorName(rec.name), bold: true, children: [
1508
+ rec.name,
1509
+ " ",
1510
+ rec.target
1511
+ ] }),
1512
+ /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
1513
+ "args: ",
1514
+ rec.args.slice(0, 400) || "(none)"
1515
+ ] }),
1516
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: outLines || "(no output)" })
1517
+ ] });
1518
+ })() : null,
1519
+ dialog?.kind === "model" ? /* @__PURE__ */ jsx4(
1520
+ SelectDialog,
1521
+ {
1522
+ title: `Model \u2014 ${providerName}`,
1523
+ items: modelItems,
1524
+ loading: modelLoading,
1525
+ error: modelError,
1526
+ current: model ?? factoryResult.provider?.defaultModel,
1527
+ onSelect: (id) => {
1528
+ setDialog(null);
1529
+ setModelAndPersist(id);
1530
+ },
1531
+ onClose: () => setDialog(null)
1382
1532
  }
1383
- }
1384
- ) : running ? /* @__PURE__ */ jsxs4(Box4, { children: [
1385
- /* @__PURE__ */ jsxs4(Text4, { color: "yellow", children: [
1386
- SPINNER_FRAMES[frame],
1387
- " "
1388
- ] }),
1389
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "agent working... (Ctrl+C to exit)" })
1390
- ] }) : /* @__PURE__ */ jsxs4(Box4, { children: [
1391
- /* @__PURE__ */ jsx4(Text4, { color: "cyan", bold: true, children: "\u25B8 " }),
1392
- /* @__PURE__ */ jsx4(
1393
- TextInput2,
1533
+ ) : dialog?.kind === "provider" ? /* @__PURE__ */ jsx4(
1534
+ SelectDialog,
1394
1535
  {
1395
- value: taskInput,
1396
- onChange: setTaskInput,
1397
- onSubmit: handleSubmit,
1398
- placeholder: "Describe a task, or /help"
1536
+ title: "Provider",
1537
+ items: PROVIDER_NAMES,
1538
+ descriptions: PROVIDER_DESCRIPTIONS,
1539
+ current: providerName,
1540
+ onSelect: (name) => {
1541
+ setDialog(null);
1542
+ switchProvider(name);
1543
+ },
1544
+ onClose: () => setDialog(null)
1399
1545
  }
1400
- )
1546
+ ) : dialog?.kind === "key" ? /* @__PURE__ */ jsx4(
1547
+ KeyDialog,
1548
+ {
1549
+ providerName,
1550
+ onSubmit: (value) => {
1551
+ setDialog(null);
1552
+ saveKey(value);
1553
+ },
1554
+ onClose: () => setDialog(null)
1555
+ }
1556
+ ) : approval ? /* @__PURE__ */ jsx4(
1557
+ ApprovalPrompt,
1558
+ {
1559
+ request: approval,
1560
+ onDecision: (decision) => {
1561
+ bridge.resolve(decision);
1562
+ if (decision === "always") bus.emit("info", "Approved for the rest of this session.");
1563
+ }
1564
+ }
1565
+ ) : running ? /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
1566
+ /* @__PURE__ */ jsxs4(Box4, { children: [
1567
+ /* @__PURE__ */ jsxs4(Text4, { color: "yellow", children: [
1568
+ SPINNER_FRAMES[frame],
1569
+ " "
1570
+ ] }),
1571
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "agent working... (Ctrl+C to exit)" })
1572
+ ] }),
1573
+ thinkingText ? /* @__PURE__ */ jsxs4(Box4, { children: [
1574
+ /* @__PURE__ */ jsx4(Text4, { color: "cyan", children: "\u{1F4AD} " }),
1575
+ /* @__PURE__ */ jsx4(Text4, { color: "cyan", dimColor: true, children: marquee }),
1576
+ /* @__PURE__ */ jsx4(Text4, { color: "cyan", children: "\u258C" })
1577
+ ] }) : null
1578
+ ] }) : /* @__PURE__ */ jsxs4(Box4, { children: [
1579
+ /* @__PURE__ */ jsx4(Text4, { color: "cyan", bold: true, children: "\u25B8 " }),
1580
+ /* @__PURE__ */ jsx4(
1581
+ TextInput2,
1582
+ {
1583
+ value: taskInput,
1584
+ onChange: setTaskInput,
1585
+ onSubmit: handleSubmit,
1586
+ placeholder: "Describe a task, or /help"
1587
+ }
1588
+ )
1589
+ ] })
1401
1590
  ] })
1402
1591
  ] });
1403
1592
  }
@@ -1410,6 +1599,8 @@ var init_App = __esm({
1410
1599
  init_render();
1411
1600
  init_approval();
1412
1601
  init_loop();
1602
+ init_conversation();
1603
+ init_exec();
1413
1604
  init_config();
1414
1605
  init_Approval();
1415
1606
  init_SelectDialog();
@@ -1466,6 +1657,20 @@ function toWireTools(tools) {
1466
1657
  }
1467
1658
  }));
1468
1659
  }
1660
+ function parseResponse(response) {
1661
+ const choice = response.choices?.[0];
1662
+ const message = choice?.message;
1663
+ const usage = response.usage ? {
1664
+ inputTokens: response.usage.prompt_tokens ?? 0,
1665
+ outputTokens: response.usage.completion_tokens ?? 0
1666
+ } : void 0;
1667
+ return {
1668
+ content: message?.content ?? null,
1669
+ toolCalls: message?.tool_calls ?? [],
1670
+ finishReason: mapFinishReason(choice?.finish_reason),
1671
+ usage
1672
+ };
1673
+ }
1469
1674
  var OpenAICompatProvider = class {
1470
1675
  name;
1471
1676
  defaultModel;
@@ -1483,26 +1688,71 @@ var OpenAICompatProvider = class {
1483
1688
  { role: "system", content: request.system },
1484
1689
  ...request.messages
1485
1690
  ];
1486
- const response = await this.client.chat.completions.create({
1691
+ const baseParams = {
1487
1692
  model: model ?? this.defaultModel,
1488
1693
  // the wire format matches our internal shape; keep loose typing at the boundary
1489
1694
  messages,
1490
1695
  ...request.tools.length > 0 ? { tools: toWireTools(request.tools) } : {},
1491
1696
  ...request.temperature !== void 0 ? { temperature: request.temperature } : {},
1492
1697
  ...request.maxTokens ? { max_tokens: request.maxTokens } : {}
1493
- });
1494
- const choice = response.choices[0];
1495
- const message = choice?.message;
1496
- const usage = response.usage ? {
1497
- inputTokens: response.usage.prompt_tokens ?? 0,
1498
- outputTokens: response.usage.completion_tokens ?? 0
1499
- } : void 0;
1500
- return {
1501
- content: message?.content ?? null,
1502
- toolCalls: message?.tool_calls ?? [],
1503
- finishReason: mapFinishReason(choice?.finish_reason),
1504
- usage
1505
1698
  };
1699
+ if (!request.onToken) {
1700
+ const response = await this.client.chat.completions.create(baseParams);
1701
+ return parseResponse(response);
1702
+ }
1703
+ try {
1704
+ const stream = await this.client.chat.completions.create({
1705
+ ...baseParams,
1706
+ stream: true,
1707
+ stream_options: { include_usage: true }
1708
+ });
1709
+ let content = "";
1710
+ let finishReason = "stop";
1711
+ let usage;
1712
+ const tcMap = /* @__PURE__ */ new Map();
1713
+ for await (const chunk of stream) {
1714
+ const choice = chunk.choices?.[0];
1715
+ const delta = choice?.delta;
1716
+ if (delta?.content) {
1717
+ content += delta.content;
1718
+ request.onToken(delta.content);
1719
+ }
1720
+ if (delta?.tool_calls) {
1721
+ for (const tc of delta.tool_calls) {
1722
+ const idx = tc.index ?? 0;
1723
+ let acc = tcMap.get(idx);
1724
+ if (!acc) {
1725
+ acc = { args: "" };
1726
+ tcMap.set(idx, acc);
1727
+ }
1728
+ if (tc.id) acc.id = tc.id;
1729
+ if (tc.function?.name) acc.name = tc.function.name;
1730
+ if (tc.function?.arguments) acc.args += tc.function.arguments;
1731
+ }
1732
+ }
1733
+ if (choice?.finish_reason) finishReason = choice.finish_reason;
1734
+ if (chunk.usage) {
1735
+ usage = {
1736
+ inputTokens: chunk.usage.prompt_tokens ?? 0,
1737
+ outputTokens: chunk.usage.completion_tokens ?? 0
1738
+ };
1739
+ }
1740
+ }
1741
+ const toolCalls = [...tcMap.entries()].sort((a, b) => a[0] - b[0]).map(([idx, acc]) => ({
1742
+ id: acc.id ?? `call_${idx}`,
1743
+ type: "function",
1744
+ function: { name: acc.name ?? "", arguments: acc.args }
1745
+ }));
1746
+ return {
1747
+ content,
1748
+ toolCalls,
1749
+ finishReason: mapFinishReason(finishReason),
1750
+ usage
1751
+ };
1752
+ } catch {
1753
+ const response = await this.client.chat.completions.create(baseParams);
1754
+ return parseResponse(response);
1755
+ }
1506
1756
  }
1507
1757
  async listModels() {
1508
1758
  const page = await this.client.models.list();
@@ -2374,85 +2624,8 @@ function classifyCommand(command) {
2374
2624
  return overall;
2375
2625
  }
2376
2626
 
2377
- // src/tools/exec.ts
2378
- import { spawn } from "child_process";
2379
- function killTree(child) {
2380
- if (child.pid === void 0) return;
2381
- if (process.platform === "win32") {
2382
- spawn(`taskkill /pid ${String(child.pid)} /T /F`, { stdio: "ignore", shell: true });
2383
- } else {
2384
- child.kill("SIGKILL");
2385
- }
2386
- }
2387
- async function runShellCommand(command, cwd, timeoutMs = 12e4, maxChars = 1e5) {
2388
- return new Promise((resolve) => {
2389
- const started = Date.now();
2390
- const child = spawn(command, {
2391
- shell: true,
2392
- cwd,
2393
- env: { ...process.env, MENTEE: "1" },
2394
- windowsHide: true
2395
- });
2396
- let stdout = "";
2397
- let stderr = "";
2398
- let killed = false;
2399
- const timer = setTimeout(() => {
2400
- killed = true;
2401
- killTree(child);
2402
- }, timeoutMs);
2403
- child.stdout?.on("data", (chunk) => {
2404
- if (stdout.length < maxChars * 2) stdout += chunk.toString("utf8");
2405
- });
2406
- child.stderr?.on("data", (chunk) => {
2407
- if (stderr.length < maxChars * 2) stderr += chunk.toString("utf8");
2408
- });
2409
- child.on("error", (error) => {
2410
- clearTimeout(timer);
2411
- resolve({
2412
- exitCode: -1,
2413
- stdout,
2414
- stderr: `${stderr}
2415
- ${error.message}`.trim(),
2416
- durationMs: Date.now() - started
2417
- });
2418
- });
2419
- child.on("close", (code) => {
2420
- clearTimeout(timer);
2421
- resolve({
2422
- exitCode: killed ? "timeout" : code ?? -1,
2423
- stdout,
2424
- stderr,
2425
- durationMs: Date.now() - started
2426
- });
2427
- });
2428
- });
2429
- }
2430
- function formatOutcome(outcome, maxChars = 1e5) {
2431
- const cut = (text, limit) => {
2432
- if (text.length <= limit) return { text, truncated: false };
2433
- const head = Math.floor(limit * 0.7);
2434
- return {
2435
- text: `${text.slice(0, head)}
2436
- ...[truncated, ${text.length - head} chars removed]...`,
2437
- truncated: true
2438
- };
2439
- };
2440
- const out = cut(outcome.stdout.trim(), maxChars);
2441
- const err = cut(outcome.stderr.trim(), Math.floor(maxChars / 2));
2442
- const sections = [`exit_code: ${outcome.exitCode}`, `duration_ms: ${outcome.durationMs}`];
2443
- if (out.text) sections.push(`stdout:
2444
- ${out.text}`);
2445
- if (err.text) sections.push(`stderr:
2446
- ${err.text}`);
2447
- if (!out.text && !err.text) sections.push("(no output)");
2448
- return {
2449
- output: sections.join("\n"),
2450
- success: outcome.exitCode === 0,
2451
- truncated: out.truncated || err.truncated
2452
- };
2453
- }
2454
-
2455
2627
  // src/tools/terminal.ts
2628
+ init_exec();
2456
2629
  var DEFAULT_TIMEOUT_MS = 12e4;
2457
2630
  var MAX_OUTPUT_CHARS = 1e5;
2458
2631
  var executeCommand = {
@@ -2488,6 +2661,7 @@ var executeCommand = {
2488
2661
 
2489
2662
  // src/tools/testing.ts
2490
2663
  init_base();
2664
+ init_exec();
2491
2665
  import fs4 from "fs";
2492
2666
  import path4 from "path";
2493
2667
  var DEFAULT_TIMEOUT_MS2 = 18e4;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@menteeai/menteeswe",
3
- "version": "0.1.5",
3
+ "version": "0.1.9",
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",