@menteeai/menteeswe 0.1.7 → 0.1.10

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 +450 -194
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -107,7 +107,12 @@ function killTree(child) {
107
107
  }
108
108
  }
109
109
  function killActiveChild() {
110
- if (activeChild) killTree(activeChild);
110
+ if (!activeChild) return;
111
+ try {
112
+ activeChild.kill("SIGKILL");
113
+ } catch {
114
+ }
115
+ killTree(activeChild);
111
116
  }
112
117
  async function runShellCommand(command, cwd, timeoutMs = 12e4, maxChars = 1e5) {
113
118
  return new Promise((resolve) => {
@@ -246,17 +251,17 @@ function friendlyToolName(name) {
246
251
  function formatEvent(event) {
247
252
  switch (event.type) {
248
253
  case "task_started":
249
- return chalk.bold.magenta(`\u25B8 Task: ${event.message ?? ""}`);
254
+ return "\n" + chalk.bold.magenta(`\u25B8 Task: ${event.message ?? ""}`);
250
255
  case "model_request":
251
- return chalk.gray("\u25CF thinking...");
256
+ return chalk.gray("\u25CF thinking...");
252
257
  case "info":
253
- return chalk.cyan.bold(`\u{1F4AD} ${event.message ?? ""}`);
258
+ return chalk.cyan.bold(`\u{1F4AD} ${event.message ?? ""}`);
254
259
  case "tool_started": {
255
260
  const tool = typeof event.data?.tool === "string" ? event.data.tool : "";
256
261
  const color = CATEGORY_COLOR[TOOL_CATEGORY[tool] ?? "other"];
257
262
  const friendly = friendlyToolName(tool);
258
263
  const preview = tool && event.message?.startsWith(tool) ? event.message.slice(tool.length).trim() : event.message ?? "";
259
- return color(`\u2699 ${friendly} (${preview})...`);
264
+ return color(`\u2699 ${friendly} (${preview})...`);
260
265
  }
261
266
  case "tool_completed": {
262
267
  const tool = typeof event.data?.tool === "string" ? event.data.tool : "";
@@ -264,14 +269,32 @@ function formatEvent(event) {
264
269
  const output = typeof event.data?.output === "string" ? event.data.output.trim() : "";
265
270
  const firstLine = (output.split("\n")[0] ?? event.message ?? "").slice(0, 160);
266
271
  const color = success ? CATEGORY_COLOR[TOOL_CATEGORY[tool] ?? "other"] : chalk.red;
267
- return color(` ${success ? "\u2713" : "\u2717"} ${firstLine}`);
272
+ return color(` ${success ? "\u2713" : "\u2717"} ${firstLine}`);
268
273
  }
269
274
  case "approval":
270
- 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"}...`);
271
276
  case "warning":
272
- return chalk.yellow(`\u26A0 ${event.message ?? ""}`);
277
+ return chalk.yellow(`\u26A0 ${event.message ?? ""}`);
273
278
  case "error":
274
- 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
+ }
275
298
  case "task_completed": {
276
299
  const success = event.data?.success === true;
277
300
  const usage = event.data?.usage;
@@ -292,10 +315,12 @@ function formatEvent(event) {
292
315
  if (modifiedFiles.length > 0) stats.push(`files ${modifiedFiles.length}`);
293
316
  const sep = chalk.dim("\u2500".repeat(48));
294
317
  const statsLine = chalk.cyan(` ${stats.join(" \xB7 ")}`);
295
- const statusLine = success ? chalk.green.bold("\u2714 Task completed") : chalk.red.bold("\u2716 Task failed");
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");
296
320
  const filesLine = modifiedFiles.length > 0 ? chalk.cyan.dim(` \u{1F4DD} ${modifiedFiles.length} file(s): ${modifiedFiles.join(", ")}`) : "";
297
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");
298
- const block = [sep, statusLine, statsLine, filesLine, tipLine, sep, ""];
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, ""];
299
324
  return block.filter((line) => line !== "").join("\n");
300
325
  }
301
326
  default:
@@ -459,6 +484,9 @@ function buildSystemPrompt(cwd) {
459
484
  const tree = projectTree(cwd).trim() || "(empty)";
460
485
  return `You are MenteE, an autonomous software-engineering agent working inside a repository on the user's machine.
461
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
+
462
490
  # Environment
463
491
  - Workspace: ${cwd}
464
492
  - Platform: ${os2.platform()} (${os2.release()}), shell commands run with the system shell
@@ -479,6 +507,8 @@ ${tree}
479
507
  # Tool usage notes
480
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.
481
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.
482
512
 
483
513
  # Research strategy (IMPORTANT \u2014 HARD RULE)
484
514
  - The file tree above already shows the whole structure. Do NOT read files just to "see what's there".
@@ -563,16 +593,24 @@ function appendTurn(cwd, task, response) {
563
593
  if (turns.length > 50) turns = turns.slice(-50);
564
594
  fs9.writeFileSync(file, JSON.stringify(turns, null, 2));
565
595
  }
566
- function formatConversationContext(cwd, limit = 12) {
596
+ function formatConversationContext(cwd, limit = 10) {
567
597
  const turns = loadConversation(cwd, limit);
568
598
  if (turns.length === 0) return "";
569
- const lines = ["# Prior conversation in this project (oldest first)"];
570
- for (const t of turns) {
571
- lines.push(`User: ${t.task}`);
572
- lines.push(`Assistant: ${t.response.trim()}`);
573
- lines.push("");
599
+ const MAX_PRIOR_CHARS = 5e3;
600
+ const header = "# Prior conversation in this project (most recent shown)";
601
+ const blocks = [];
602
+ let total = header.length;
603
+ for (let i = turns.length - 1; i >= 0; i--) {
604
+ const t = turns[i];
605
+ if (!t) continue;
606
+ const block = `User: ${t.task}
607
+ Assistant: ${t.response.trim()}
608
+ `;
609
+ if (blocks.length > 0 && total + block.length > MAX_PRIOR_CHARS) break;
610
+ blocks.unshift(block);
611
+ total += block.length;
574
612
  }
575
- return lines.join("\n");
613
+ return [header, ...blocks, ""].join("\n");
576
614
  }
577
615
  var init_conversation = __esm({
578
616
  "src/agent/conversation.ts"() {
@@ -631,11 +669,11 @@ function isRateLimitError(error) {
631
669
  }
632
670
  return /\b429\b|max rpm|rate limit|too many requests/i.test(error.message);
633
671
  }
634
- async function generateWithRetry(provider, model, system, messages, tools, bus) {
672
+ async function generateWithRetry(provider, model, system, messages, tools, bus, onToken) {
635
673
  let lastError = null;
636
674
  for (let attempt = 0; attempt <= RATE_LIMIT_MAX_ATTEMPTS; attempt++) {
637
675
  try {
638
- return await provider.generate({ system, messages, tools: tools.schemas() }, model);
676
+ return await provider.generate({ system, messages, tools: tools.schemas(), onToken }, model);
639
677
  } catch (error) {
640
678
  lastError = error;
641
679
  if (attempt >= RATE_LIMIT_MAX_ATTEMPTS || !isRateLimitError(lastError)) {
@@ -670,10 +708,17 @@ function estimateContextChars(messages, system) {
670
708
  return total;
671
709
  }
672
710
  function trimOldToolResults(messages) {
673
- for (const message of messages) {
674
- if (message.role === "tool" && (message.content?.length ?? 0) > 2e3) {
675
- message.content = message.content.slice(0, 500) + "\n...[older tool output trimmed to save context]";
676
- }
711
+ let seen = 0;
712
+ for (let i = messages.length - 1; i >= 0; i--) {
713
+ const message = messages[i];
714
+ if (!message) continue;
715
+ if (message.role !== "tool") continue;
716
+ seen++;
717
+ if (seen <= KEEP_RECENT_TOOL) continue;
718
+ const content = message.content ?? "";
719
+ if (content.length <= 240) continue;
720
+ const head = content.replace(/\s+/g, " ").slice(0, 160);
721
+ message.content = `\u27E8prior tool output trimmed: ${head}\u2026 (${content.length} chars)\u27E9`;
677
722
  }
678
723
  }
679
724
  async function runAgent(options) {
@@ -719,10 +764,24 @@ ${systemExtra}` : "");
719
764
  state.iteration += 1;
720
765
  state.usage.modelRequests += 1;
721
766
  bus.emit("model_request", `iteration ${state.iteration}/${maxIterations}`);
767
+ let thinkBuf = "";
768
+ let thinkTimer = null;
769
+ const flushThink = () => {
770
+ if (thinkBuf) {
771
+ bus.emit("thinking", thinkBuf);
772
+ thinkBuf = "";
773
+ }
774
+ thinkTimer = null;
775
+ };
776
+ const onToken = (delta) => {
777
+ thinkBuf += delta;
778
+ if (thinkTimer == null) thinkTimer = setTimeout(flushThink, 80);
779
+ };
722
780
  let response;
723
781
  try {
724
- response = await generateWithRetry(provider, model, system, messages, tools, bus);
782
+ response = await generateWithRetry(provider, model, system, messages, tools, bus, onToken);
725
783
  } catch (error) {
784
+ flushThink();
726
785
  const message = error.message;
727
786
  let hint = "";
728
787
  if (/insufficient balance|no resource package|recharge/i.test(message)) {
@@ -744,6 +803,7 @@ ${systemExtra}` : "");
744
803
  });
745
804
  return { success: false, finalText, state };
746
805
  }
806
+ flushThink();
747
807
  bus.emit(
748
808
  "model_response",
749
809
  void 0,
@@ -772,7 +832,7 @@ ${systemExtra}` : "");
772
832
  content: response.content,
773
833
  tool_calls: response.toolCalls
774
834
  });
775
- if (response.content && response.content.trim()) {
835
+ if (!onToken && response.content && response.content.trim()) {
776
836
  bus.emit("info", response.content.trim().slice(0, 300));
777
837
  }
778
838
  for (const call of response.toolCalls) {
@@ -866,6 +926,13 @@ ${systemExtra}` : "");
866
926
  success: result.success,
867
927
  output: result.output.slice(0, 2e3)
868
928
  });
929
+ if (tool.name === "apply_patch" && result.success && result.data?.kind === "patch") {
930
+ bus.emit("patch", void 0, {
931
+ path: result.data.path,
932
+ old_string: result.data.old_string,
933
+ new_string: result.data.new_string
934
+ });
935
+ }
869
936
  messages.push({
870
937
  role: "tool",
871
938
  tool_call_id: call.id,
@@ -894,11 +961,12 @@ ${systemExtra}` : "");
894
961
  toolCalls: state.totalToolCalls,
895
962
  modifiedFiles: state.modifiedFiles,
896
963
  usage: state.usage,
897
- durationMs: Date.now() - state.startedAt
964
+ durationMs: Date.now() - state.startedAt,
965
+ cancelled: signal?.aborted === true
898
966
  });
899
967
  return { success, finalText, state };
900
968
  }
901
- var MAX_CONTEXT_CHARS, READ_BUDGET, RATE_LIMIT_MAX_ATTEMPTS;
969
+ var MAX_CONTEXT_CHARS, READ_BUDGET, RATE_LIMIT_MAX_ATTEMPTS, KEEP_RECENT_TOOL;
902
970
  var init_loop = __esm({
903
971
  "src/agent/loop.ts"() {
904
972
  "use strict";
@@ -906,15 +974,116 @@ var init_loop = __esm({
906
974
  init_conversation();
907
975
  init_state();
908
976
  init_base();
909
- MAX_CONTEXT_CHARS = 48e4;
977
+ MAX_CONTEXT_CHARS = 7e4;
910
978
  READ_BUDGET = 6;
911
979
  RATE_LIMIT_MAX_ATTEMPTS = 8;
980
+ KEEP_RECENT_TOOL = 4;
912
981
  }
913
982
  });
914
983
 
915
- // src/tui/Approval.tsx
916
- import { Box, Text, useInput } from "ink";
984
+ // src/tui/markdown.tsx
985
+ import { Box, Text } from "ink";
917
986
  import { jsx, jsxs } from "react/jsx-runtime";
987
+ function renderInline(text, keyPrefix) {
988
+ const nodes = [];
989
+ const regex = /(\*\*([^*]+)\*\*)|(\*([^*]+)\*)|(`([^`]+)`)/g;
990
+ let last = 0;
991
+ let m;
992
+ let i = 0;
993
+ while ((m = regex.exec(text)) !== null) {
994
+ if (m.index > last) nodes.push(text.slice(last, m.index));
995
+ if (m[2] !== void 0) {
996
+ nodes.push(
997
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: m[2] }, `${keyPrefix}-b${i}`)
998
+ );
999
+ } else if (m[4] !== void 0) {
1000
+ nodes.push(
1001
+ /* @__PURE__ */ jsx(Text, { italic: true, children: m[4] }, `${keyPrefix}-i${i}`)
1002
+ );
1003
+ } else if (m[6] !== void 0) {
1004
+ nodes.push(
1005
+ /* @__PURE__ */ jsx(Text, { color: "greenBright", children: m[6] }, `${keyPrefix}-c${i}`)
1006
+ );
1007
+ }
1008
+ last = m.index + m[0].length;
1009
+ i++;
1010
+ }
1011
+ if (last < text.length) nodes.push(text.slice(last));
1012
+ return nodes;
1013
+ }
1014
+ function renderMarkdown(md) {
1015
+ const lines = md.split("\n");
1016
+ const blocks = [];
1017
+ let para = [];
1018
+ let key = 0;
1019
+ const flushPara = () => {
1020
+ if (para.length === 0) return;
1021
+ blocks.push(/* @__PURE__ */ jsx(Text, { children: renderInline(para.join(" "), `p${key}`) }, `p${key++}`));
1022
+ para = [];
1023
+ };
1024
+ for (const line of lines) {
1025
+ if (line.trim() === "") {
1026
+ flushPara();
1027
+ continue;
1028
+ }
1029
+ const heading = /^(#{1,6})\s+(.*)$/.exec(line);
1030
+ if (heading) {
1031
+ flushPara();
1032
+ const level = heading[1]?.length ?? 0;
1033
+ blocks.push(
1034
+ /* @__PURE__ */ jsx(Text, { bold: true, color: level <= 2 ? "cyan" : "white", children: renderInline(heading[2] ?? "", `h${key}`) }, `h${key++}`)
1035
+ );
1036
+ continue;
1037
+ }
1038
+ const bullet = /^\s*[-*]\s+(.*)$/.exec(line);
1039
+ if (bullet) {
1040
+ flushPara();
1041
+ blocks.push(
1042
+ /* @__PURE__ */ jsxs(Text, { children: [
1043
+ " \u2022 ",
1044
+ renderInline(bullet[1] ?? "", `b${key}`)
1045
+ ] }, `b${key++}`)
1046
+ );
1047
+ continue;
1048
+ }
1049
+ const numbered = /^\s*\d+\.\s+(.*)$/.exec(line);
1050
+ if (numbered) {
1051
+ flushPara();
1052
+ blocks.push(
1053
+ /* @__PURE__ */ jsxs(Text, { children: [
1054
+ " ",
1055
+ renderInline(numbered[1] ?? "", `n${key}`)
1056
+ ] }, `n${key++}`)
1057
+ );
1058
+ continue;
1059
+ }
1060
+ para.push(line.trim());
1061
+ }
1062
+ flushPara();
1063
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: blocks });
1064
+ }
1065
+ function AnswerBlock({ text }) {
1066
+ return /* @__PURE__ */ jsx(
1067
+ Box,
1068
+ {
1069
+ flexDirection: "column",
1070
+ borderStyle: "round",
1071
+ borderColor: "cyan",
1072
+ paddingX: 1,
1073
+ marginY: 0,
1074
+ children: renderMarkdown(text)
1075
+ }
1076
+ );
1077
+ }
1078
+ var init_markdown = __esm({
1079
+ "src/tui/markdown.tsx"() {
1080
+ "use strict";
1081
+ }
1082
+ });
1083
+
1084
+ // src/tui/Approval.tsx
1085
+ import { Box as Box2, Text as Text2, useInput } from "ink";
1086
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
918
1087
  function argPreview(request) {
919
1088
  const args = request.args;
920
1089
  if (request.tool === "apply_patch" && typeof args.old_string === "string" && typeof args.new_string === "string") {
@@ -932,8 +1101,8 @@ function ApprovalPrompt({ request, onDecision }) {
932
1101
  else if (input === "a") onDecision("always");
933
1102
  else if (input === "n" || key.escape) onDecision("deny");
934
1103
  });
935
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 1, marginY: 1, children: [
936
- /* @__PURE__ */ jsxs(Text, { bold: true, color: "magenta", children: [
1104
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 1, marginY: 1, children: [
1105
+ /* @__PURE__ */ jsxs2(Text2, { bold: true, color: "magenta", children: [
937
1106
  "Permission needed: ",
938
1107
  friendlyToolName(request.tool),
939
1108
  " (",
@@ -941,14 +1110,14 @@ function ApprovalPrompt({ request, onDecision }) {
941
1110
  ") \u2014 ",
942
1111
  request.risk
943
1112
  ] }),
944
- /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { children: argPreview(request) }) }),
945
- /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsxs(Text, { children: [
946
- /* @__PURE__ */ jsx(Text, { color: "green", bold: true, children: "[y]" }),
1113
+ /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { children: argPreview(request) }) }),
1114
+ /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsxs2(Text2, { children: [
1115
+ /* @__PURE__ */ jsx2(Text2, { color: "green", bold: true, children: "[y]" }),
947
1116
  " allow once \xB7 ",
948
- /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "[a]" }),
1117
+ /* @__PURE__ */ jsx2(Text2, { color: "cyan", bold: true, children: "[a]" }),
949
1118
  " allow this session \xB7",
950
1119
  " ",
951
- /* @__PURE__ */ jsx(Text, { color: "red", bold: true, children: "[n]" }),
1120
+ /* @__PURE__ */ jsx2(Text2, { color: "red", bold: true, children: "[n]" }),
952
1121
  " deny"
953
1122
  ] }) })
954
1123
  ] });
@@ -962,8 +1131,8 @@ var init_Approval = __esm({
962
1131
 
963
1132
  // src/tui/SelectDialog.tsx
964
1133
  import { useEffect, useMemo, useState } from "react";
965
- import { Box as Box2, Text as Text2, useInput as useInput2 } from "ink";
966
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1134
+ import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
1135
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
967
1136
  function SelectDialog({
968
1137
  title,
969
1138
  items,
@@ -998,34 +1167,34 @@ function SelectDialog({
998
1167
  Math.min(cursor - Math.floor(VISIBLE / 2), Math.max(0, filtered.length - VISIBLE))
999
1168
  );
1000
1169
  const visible = filtered.slice(start, start + VISIBLE);
1001
- return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, marginY: 1, children: [
1002
- /* @__PURE__ */ jsx2(Text2, { bold: true, color: "cyan", children: title }),
1003
- /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, children: [
1004
- /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
1170
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, marginY: 1, children: [
1171
+ /* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: title }),
1172
+ /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, children: [
1173
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
1005
1174
  "search: ",
1006
1175
  filter
1007
1176
  ] }),
1008
- /* @__PURE__ */ jsx2(Text2, { children: "\u258F" })
1177
+ /* @__PURE__ */ jsx3(Text3, { children: "\u258F" })
1009
1178
  ] }),
1010
- loading ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "loading\u2026" }) : null,
1011
- error ? /* @__PURE__ */ jsx2(Text2, { color: "red", children: error }) : null,
1012
- !loading && !error ? /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
1179
+ loading ? /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "loading\u2026" }) : null,
1180
+ error ? /* @__PURE__ */ jsx3(Text3, { color: "red", children: error }) : null,
1181
+ !loading && !error ? /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
1013
1182
  visible.map((item, i) => {
1014
1183
  const index = start + i;
1015
1184
  const isCursor = index === cursor;
1016
1185
  const isCurrent = item === current;
1017
1186
  const description = descriptions?.[item];
1018
- return /* @__PURE__ */ jsxs2(Text2, { color: isCursor ? "cyan" : void 0, bold: isCursor, children: [
1187
+ return /* @__PURE__ */ jsxs3(Text3, { color: isCursor ? "cyan" : void 0, bold: isCursor, children: [
1019
1188
  isCursor ? "\u276F " : " ",
1020
1189
  isCurrent ? "\u25CF " : "\u25CB ",
1021
1190
  item,
1022
1191
  isCurrent ? " (current)" : "",
1023
- description ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: ` \u2014 ${description}` }) : null
1192
+ description ? /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: ` \u2014 ${description}` }) : null
1024
1193
  ] }, item);
1025
1194
  }),
1026
- filtered.length === 0 ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "no matches" }) : null
1195
+ filtered.length === 0 ? /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "no matches" }) : null
1027
1196
  ] }) : null,
1028
- /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\u2191\u2193 move \xB7 type to filter \xB7 enter select \xB7 esc cancel" }) })
1197
+ /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "\u2191\u2193 move \xB7 type to filter \xB7 enter select \xB7 esc cancel" }) })
1029
1198
  ] });
1030
1199
  }
1031
1200
  var VISIBLE;
@@ -1038,22 +1207,22 @@ var init_SelectDialog = __esm({
1038
1207
 
1039
1208
  // src/tui/KeyDialog.tsx
1040
1209
  import { useState as useState2 } from "react";
1041
- import { Box as Box3, Text as Text3, useInput as useInput3 } from "ink";
1210
+ import { Box as Box4, Text as Text4, useInput as useInput3 } from "ink";
1042
1211
  import TextInput from "ink-text-input";
1043
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1212
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1044
1213
  function KeyDialog({ providerName, onSubmit, onClose }) {
1045
1214
  const [value, setValue] = useState2("");
1046
1215
  useInput3((_input, key) => {
1047
1216
  if (key.escape) onClose();
1048
1217
  });
1049
- return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 1, marginY: 1, children: [
1050
- /* @__PURE__ */ jsxs3(Text3, { bold: true, color: "magenta", children: [
1218
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 1, marginY: 1, children: [
1219
+ /* @__PURE__ */ jsxs4(Text4, { bold: true, color: "magenta", children: [
1051
1220
  "API key for ",
1052
1221
  providerName
1053
1222
  ] }),
1054
- /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, children: [
1055
- /* @__PURE__ */ jsx3(Text3, { children: "key: " }),
1056
- /* @__PURE__ */ jsx3(
1223
+ /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, children: [
1224
+ /* @__PURE__ */ jsx4(Text4, { children: "key: " }),
1225
+ /* @__PURE__ */ jsx4(
1057
1226
  TextInput,
1058
1227
  {
1059
1228
  mask: "*",
@@ -1066,7 +1235,7 @@ function KeyDialog({ providerName, onSubmit, onClose }) {
1066
1235
  }
1067
1236
  )
1068
1237
  ] }),
1069
- /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "enter confirm \xB7 esc cancel \xB7 saved to ~/.mentee/config.json" }) })
1238
+ /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "enter confirm \xB7 esc cancel \xB7 saved to ~/.mentee/config.json" }) })
1070
1239
  ] });
1071
1240
  }
1072
1241
  var init_KeyDialog = __esm({
@@ -1079,7 +1248,7 @@ var init_KeyDialog = __esm({
1079
1248
  var version;
1080
1249
  var init_package = __esm({
1081
1250
  "package.json"() {
1082
- version = "0.1.7";
1251
+ version = "0.1.10";
1083
1252
  }
1084
1253
  });
1085
1254
 
@@ -1089,11 +1258,12 @@ __export(App_exports, {
1089
1258
  App: () => App
1090
1259
  });
1091
1260
  import { useEffect as useEffect2, useMemo as useMemo2, useRef, useState as useState3 } from "react";
1092
- import { Box as Box4, Static, Text as Text4, useApp, useInput as useInput4 } from "ink";
1261
+ import { Box as Box5, Static, Text as Text5, useApp, useInput as useInput4, useStdout } from "ink";
1093
1262
  import TextInput2 from "ink-text-input";
1094
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1263
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1095
1264
  function App(props) {
1096
1265
  const { exit } = useApp();
1266
+ const { stdout } = useStdout();
1097
1267
  const bus = useMemo2(() => new EventBus(), []);
1098
1268
  const bridge = useMemo2(() => new ApprovalBridge(), []);
1099
1269
  const [log, setLog] = useState3([]);
@@ -1116,24 +1286,32 @@ function App(props) {
1116
1286
  const [toolHistory, setToolHistory] = useState3([]);
1117
1287
  const [inspectorIndex, setInspectorIndex] = useState3(null);
1118
1288
  const [detailsOpen, setDetailsOpen] = useState3(false);
1119
- const [showTools, setShowTools] = useState3(true);
1289
+ const [showDetails, setShowDetails] = useState3(false);
1290
+ const [thinkingText, setThinkingText] = useState3("");
1291
+ const finalTextRef = useRef(null);
1120
1292
  const pendingTool = useRef(null);
1121
1293
  const counter = useRef(0);
1122
1294
  const stateRef = useRef({ running, approval, dialog, factoryResult, providerName, model });
1123
1295
  stateRef.current = { running, approval, dialog, factoryResult, providerName, model };
1124
1296
  const abortRef = useRef(null);
1125
- const appendLog = (text) => {
1297
+ const appendLog = (text, noise = false, kind) => {
1126
1298
  if (!text) return;
1127
1299
  counter.current += 1;
1128
- const line = { id: counter.current, text };
1300
+ const line = { id: counter.current, text, noise, kind };
1129
1301
  setLog((prev) => [...prev, line]);
1130
1302
  };
1131
1303
  useEffect2(() => {
1132
- const HIDDEN_NOISY = /* @__PURE__ */ new Set(["tool_started", "tool_completed", "model_request", "info"]);
1304
+ const TOOL_NOISE = /* @__PURE__ */ new Set(["tool_started", "tool_completed", "model_request"]);
1133
1305
  const unsubscribeLog = bus.subscribe((event) => {
1134
- if (!(showTools === false && HIDDEN_NOISY.has(event.type))) {
1135
- appendLog(formatEvent(event));
1306
+ if (event.type === "thinking") {
1307
+ const delta = (event.message ?? "").replace(/[\r\n]+/g, " ");
1308
+ if (delta) setThinkingText((prev) => (prev + delta).slice(-4e3));
1309
+ return;
1310
+ }
1311
+ if (event.type === "model_request" || event.type === "task_completed") {
1312
+ setThinkingText("");
1136
1313
  }
1314
+ appendLog(formatEvent(event), TOOL_NOISE.has(event.type));
1137
1315
  if (event.type === "tool_started") {
1138
1316
  const name = typeof event.data?.tool === "string" ? event.data.tool : "";
1139
1317
  const target = typeof event.message === "string" ? event.message : "";
@@ -1150,10 +1328,12 @@ function App(props) {
1150
1328
  } else if (event.type === "task_completed") {
1151
1329
  const text = typeof event.data?.finalText === "string" ? event.data.finalText.trim() : "";
1152
1330
  if (text) {
1153
- appendLog(text);
1331
+ finalTextRef.current = text;
1154
1332
  setFinalPending(text);
1155
1333
  setTypedText("");
1156
1334
  setTyping(true);
1335
+ } else {
1336
+ finalTextRef.current = null;
1157
1337
  }
1158
1338
  }
1159
1339
  });
@@ -1166,7 +1346,7 @@ function App(props) {
1166
1346
  unsubscribeLog();
1167
1347
  unsubscribeLog2();
1168
1348
  };
1169
- }, [bus, bridge, showTools]);
1349
+ }, [bus, bridge, showDetails]);
1170
1350
  useEffect2(() => {
1171
1351
  if (!running) return;
1172
1352
  const timer = setInterval(() => setFrame((f) => (f + 1) % SPINNER_FRAMES.length), 80);
@@ -1185,8 +1365,11 @@ function App(props) {
1185
1365
  return;
1186
1366
  }
1187
1367
  const timer = setTimeout(() => {
1188
- setTypedText(finalPending.slice(0, typedText.length + 3));
1189
- }, 16);
1368
+ const rest = finalPending.slice(typedText.length);
1369
+ const match = /^(\S+\s*)/.exec(rest);
1370
+ const step = match ? match[0].length : 1;
1371
+ setTypedText(finalPending.slice(0, typedText.length + step));
1372
+ }, 55);
1190
1373
  return () => clearTimeout(timer);
1191
1374
  }, [typing, finalPending, typedText]);
1192
1375
  const updateConfig = (mutate) => {
@@ -1325,12 +1508,17 @@ function App(props) {
1325
1508
  appendLog(factoryResult.error ?? "No provider configured. Press Ctrl+K to add an API key.");
1326
1509
  return;
1327
1510
  }
1511
+ if (finalTextRef.current) {
1512
+ appendLog(finalTextRef.current, false, "answer");
1513
+ finalTextRef.current = null;
1514
+ }
1328
1515
  setFinalPending(null);
1329
1516
  setTypedText("");
1330
1517
  setTyping(false);
1331
1518
  setToolHistory([]);
1332
1519
  setInspectorIndex(null);
1333
1520
  setDetailsOpen(false);
1521
+ setShowDetails(false);
1334
1522
  setRunning(true);
1335
1523
  const ac = new AbortController();
1336
1524
  abortRef.current = ac;
@@ -1384,6 +1572,7 @@ function App(props) {
1384
1572
  useInput4((input, key) => {
1385
1573
  if (key.ctrl && input === "c") {
1386
1574
  if (stateRef.current.running) {
1575
+ appendLog("\u23F9 Interrupted by you \u2014 stopping the current task and returning to the prompt.");
1387
1576
  killActiveChild();
1388
1577
  abortRef.current?.abort();
1389
1578
  return;
@@ -1392,130 +1581,137 @@ function App(props) {
1392
1581
  setTimeout(() => process.exit(0), 50);
1393
1582
  return;
1394
1583
  }
1584
+ if (key.meta && (input === "d" || input === "D")) {
1585
+ setShowDetails((v) => !v);
1586
+ return;
1587
+ }
1395
1588
  if (stateRef.current.dialog || stateRef.current.approval || stateRef.current.running) return;
1396
1589
  if (key.meta && (input === "m" || input === "M")) openModelDialog();
1397
1590
  else if (key.ctrl && input === "p") setDialog({ kind: "provider" });
1398
1591
  else if (key.ctrl && input === "k") setDialog({ kind: "key" });
1399
- else if (input === "d" || input === "D") cycleInspector();
1400
- else if (input === "t" || input === "T") setShowTools((v) => !v);
1401
1592
  });
1402
1593
  const modelLabel = model ?? factoryResult.provider?.defaultModel ?? (factoryResult.error ? "no key" : "");
1403
- const header = /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginBottom: 1, children: [
1404
- /* @__PURE__ */ jsx4(Text4, { color: "cyan", children: BANNER }),
1405
- /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
1594
+ const header = /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: 1, children: [
1595
+ /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: BANNER }),
1596
+ /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
1406
1597
  "v",
1407
1598
  version,
1408
1599
  " \xB7 ",
1409
1600
  providerName,
1410
1601
  modelLabel ? `:${modelLabel}` : "",
1411
- " \xB7 tools ",
1412
- showTools ? "on" : "off",
1602
+ " \xB7 details ",
1603
+ showDetails ? "open" : "collapsed",
1413
1604
  " \xB7 ",
1414
1605
  props.cwd
1415
1606
  ] }),
1416
- /* @__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" })
1607
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Alt+M model \xB7 Ctrl+P provider \xB7 Ctrl+K key \xB7 Alt+D details \xB7 /help" })
1417
1608
  ] }, "header");
1418
- return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
1419
- /* @__PURE__ */ jsx4(
1420
- Static,
1421
- {
1422
- items: [
1423
- { key: "header", text: header },
1424
- ...log.map((line) => ({ key: `l${line.id}`, text: /* @__PURE__ */ jsx4(Text4, { children: line.text }) }))
1425
- ],
1426
- children: (item) => /* @__PURE__ */ jsx4(Box4, { children: item.text }, item.key)
1427
- }
1428
- ),
1429
- finalPending !== null ? /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, children: [
1430
- /* @__PURE__ */ jsx4(Text4, { color: "green", children: typedText }),
1431
- typing ? /* @__PURE__ */ jsx4(Text4, { color: "green", children: "\u258C" }) : null
1432
- ] }) : null,
1433
- detailsOpen && inspectorIndex !== null && toolHistory[inspectorIndex] ? (() => {
1434
- const rec = toolHistory[inspectorIndex];
1435
- const outLines = rec.output.split("\n").slice(0, 24).join("\n");
1436
- return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginTop: 1, borderStyle: "round", borderColor: "gray", paddingX: 1, children: [
1437
- /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
1438
- " tool details (",
1439
- inspectorIndex + 1,
1440
- "/",
1441
- toolHistory.length,
1442
- ") \xB7 press d to cycle "
1443
- ] }),
1444
- /* @__PURE__ */ jsxs4(Text4, { color: toolColorName(rec.name), bold: true, children: [
1445
- rec.name,
1446
- " ",
1447
- rec.target
1448
- ] }),
1449
- /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
1450
- "args: ",
1451
- rec.args.slice(0, 400) || "(none)"
1452
- ] }),
1453
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: outLines || "(no output)" })
1454
- ] });
1455
- })() : null,
1456
- dialog?.kind === "model" ? /* @__PURE__ */ jsx4(
1457
- SelectDialog,
1458
- {
1459
- title: `Model \u2014 ${providerName}`,
1460
- items: modelItems,
1461
- loading: modelLoading,
1462
- error: modelError,
1463
- current: model ?? factoryResult.provider?.defaultModel,
1464
- onSelect: (id) => {
1465
- setDialog(null);
1466
- setModelAndPersist(id);
1467
- },
1468
- onClose: () => setDialog(null)
1469
- }
1470
- ) : dialog?.kind === "provider" ? /* @__PURE__ */ jsx4(
1471
- SelectDialog,
1472
- {
1473
- title: "Provider",
1474
- items: PROVIDER_NAMES,
1475
- descriptions: PROVIDER_DESCRIPTIONS,
1476
- current: providerName,
1477
- onSelect: (name) => {
1478
- setDialog(null);
1479
- switchProvider(name);
1480
- },
1481
- onClose: () => setDialog(null)
1482
- }
1483
- ) : dialog?.kind === "key" ? /* @__PURE__ */ jsx4(
1484
- KeyDialog,
1485
- {
1486
- providerName,
1487
- onSubmit: (value) => {
1488
- setDialog(null);
1489
- saveKey(value);
1490
- },
1491
- onClose: () => setDialog(null)
1492
- }
1493
- ) : approval ? /* @__PURE__ */ jsx4(
1494
- ApprovalPrompt,
1495
- {
1496
- request: approval,
1497
- onDecision: (decision) => {
1498
- bridge.resolve(decision);
1499
- if (decision === "always") bus.emit("info", "Approved for the rest of this session.");
1609
+ const cols = stdout.columns || 80;
1610
+ const marquee = thinkingText.slice(-Math.max(1, cols - 4));
1611
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
1612
+ /* @__PURE__ */ jsx5(Static, { items: [{ key: "header", text: header }], children: (item) => /* @__PURE__ */ jsx5(Box5, { children: item.text }, item.key) }),
1613
+ /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
1614
+ log.filter((l) => showDetails || !l.noise).map(
1615
+ (line) => line.kind === "answer" ? /* @__PURE__ */ jsx5(AnswerBlock, { text: line.text }, line.id) : /* @__PURE__ */ jsx5(Text5, { children: line.text }, line.id)
1616
+ ),
1617
+ finalPending !== null ? /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
1618
+ /* @__PURE__ */ jsx5(Text5, { color: "green", children: typedText }),
1619
+ typing ? /* @__PURE__ */ jsx5(Text5, { color: "green", children: "\u258C" }) : null
1620
+ ] }) : null,
1621
+ detailsOpen && inspectorIndex !== null && toolHistory[inspectorIndex] ? (() => {
1622
+ const rec = toolHistory[inspectorIndex];
1623
+ const outLines = rec.output.split("\n").slice(0, 24).join("\n");
1624
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, borderStyle: "round", borderColor: "gray", paddingX: 1, children: [
1625
+ /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
1626
+ " tool details (",
1627
+ inspectorIndex + 1,
1628
+ "/",
1629
+ toolHistory.length,
1630
+ ") \xB7 press d to cycle "
1631
+ ] }),
1632
+ /* @__PURE__ */ jsxs5(Text5, { color: toolColorName(rec.name), bold: true, children: [
1633
+ rec.name,
1634
+ " ",
1635
+ rec.target
1636
+ ] }),
1637
+ /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
1638
+ "args: ",
1639
+ rec.args.slice(0, 400) || "(none)"
1640
+ ] }),
1641
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: outLines || "(no output)" })
1642
+ ] });
1643
+ })() : null,
1644
+ dialog?.kind === "model" ? /* @__PURE__ */ jsx5(
1645
+ SelectDialog,
1646
+ {
1647
+ title: `Model \u2014 ${providerName}`,
1648
+ items: modelItems,
1649
+ loading: modelLoading,
1650
+ error: modelError,
1651
+ current: model ?? factoryResult.provider?.defaultModel,
1652
+ onSelect: (id) => {
1653
+ setDialog(null);
1654
+ setModelAndPersist(id);
1655
+ },
1656
+ onClose: () => setDialog(null)
1500
1657
  }
1501
- }
1502
- ) : running ? /* @__PURE__ */ jsxs4(Box4, { children: [
1503
- /* @__PURE__ */ jsxs4(Text4, { color: "yellow", children: [
1504
- SPINNER_FRAMES[frame],
1505
- " "
1506
- ] }),
1507
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "agent working... (Ctrl+C to exit)" })
1508
- ] }) : /* @__PURE__ */ jsxs4(Box4, { children: [
1509
- /* @__PURE__ */ jsx4(Text4, { color: "cyan", bold: true, children: "\u25B8 " }),
1510
- /* @__PURE__ */ jsx4(
1511
- TextInput2,
1658
+ ) : dialog?.kind === "provider" ? /* @__PURE__ */ jsx5(
1659
+ SelectDialog,
1512
1660
  {
1513
- value: taskInput,
1514
- onChange: setTaskInput,
1515
- onSubmit: handleSubmit,
1516
- placeholder: "Describe a task, or /help"
1661
+ title: "Provider",
1662
+ items: PROVIDER_NAMES,
1663
+ descriptions: PROVIDER_DESCRIPTIONS,
1664
+ current: providerName,
1665
+ onSelect: (name) => {
1666
+ setDialog(null);
1667
+ switchProvider(name);
1668
+ },
1669
+ onClose: () => setDialog(null)
1517
1670
  }
1518
- )
1671
+ ) : dialog?.kind === "key" ? /* @__PURE__ */ jsx5(
1672
+ KeyDialog,
1673
+ {
1674
+ providerName,
1675
+ onSubmit: (value) => {
1676
+ setDialog(null);
1677
+ saveKey(value);
1678
+ },
1679
+ onClose: () => setDialog(null)
1680
+ }
1681
+ ) : approval ? /* @__PURE__ */ jsx5(
1682
+ ApprovalPrompt,
1683
+ {
1684
+ request: approval,
1685
+ onDecision: (decision) => {
1686
+ bridge.resolve(decision);
1687
+ if (decision === "always") bus.emit("info", "Approved for the rest of this session.");
1688
+ }
1689
+ }
1690
+ ) : running ? /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
1691
+ /* @__PURE__ */ jsxs5(Box5, { children: [
1692
+ /* @__PURE__ */ jsxs5(Text5, { color: "yellow", children: [
1693
+ SPINNER_FRAMES[frame],
1694
+ " "
1695
+ ] }),
1696
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "agent working... (Ctrl+C to exit)" })
1697
+ ] }),
1698
+ thinkingText ? /* @__PURE__ */ jsxs5(Box5, { children: [
1699
+ /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "\u{1F4AD} " }),
1700
+ /* @__PURE__ */ jsx5(Text5, { color: "cyan", dimColor: true, children: marquee }),
1701
+ /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "\u258C" })
1702
+ ] }) : null
1703
+ ] }) : /* @__PURE__ */ jsxs5(Box5, { children: [
1704
+ /* @__PURE__ */ jsx5(Text5, { color: "cyan", bold: true, children: "\u25B8 " }),
1705
+ /* @__PURE__ */ jsx5(
1706
+ TextInput2,
1707
+ {
1708
+ value: taskInput,
1709
+ onChange: setTaskInput,
1710
+ onSubmit: handleSubmit,
1711
+ placeholder: "Describe a task, or /help"
1712
+ }
1713
+ )
1714
+ ] })
1519
1715
  ] })
1520
1716
  ] });
1521
1717
  }
@@ -1526,6 +1722,7 @@ var init_App = __esm({
1526
1722
  init_events();
1527
1723
  init_logging();
1528
1724
  init_render();
1725
+ init_markdown();
1529
1726
  init_approval();
1530
1727
  init_loop();
1531
1728
  init_conversation();
@@ -1586,6 +1783,20 @@ function toWireTools(tools) {
1586
1783
  }
1587
1784
  }));
1588
1785
  }
1786
+ function parseResponse(response) {
1787
+ const choice = response.choices?.[0];
1788
+ const message = choice?.message;
1789
+ const usage = response.usage ? {
1790
+ inputTokens: response.usage.prompt_tokens ?? 0,
1791
+ outputTokens: response.usage.completion_tokens ?? 0
1792
+ } : void 0;
1793
+ return {
1794
+ content: message?.content ?? null,
1795
+ toolCalls: message?.tool_calls ?? [],
1796
+ finishReason: mapFinishReason(choice?.finish_reason),
1797
+ usage
1798
+ };
1799
+ }
1589
1800
  var OpenAICompatProvider = class {
1590
1801
  name;
1591
1802
  defaultModel;
@@ -1603,26 +1814,71 @@ var OpenAICompatProvider = class {
1603
1814
  { role: "system", content: request.system },
1604
1815
  ...request.messages
1605
1816
  ];
1606
- const response = await this.client.chat.completions.create({
1817
+ const baseParams = {
1607
1818
  model: model ?? this.defaultModel,
1608
1819
  // the wire format matches our internal shape; keep loose typing at the boundary
1609
1820
  messages,
1610
1821
  ...request.tools.length > 0 ? { tools: toWireTools(request.tools) } : {},
1611
1822
  ...request.temperature !== void 0 ? { temperature: request.temperature } : {},
1612
1823
  ...request.maxTokens ? { max_tokens: request.maxTokens } : {}
1613
- });
1614
- const choice = response.choices[0];
1615
- const message = choice?.message;
1616
- const usage = response.usage ? {
1617
- inputTokens: response.usage.prompt_tokens ?? 0,
1618
- outputTokens: response.usage.completion_tokens ?? 0
1619
- } : void 0;
1620
- return {
1621
- content: message?.content ?? null,
1622
- toolCalls: message?.tool_calls ?? [],
1623
- finishReason: mapFinishReason(choice?.finish_reason),
1624
- usage
1625
1824
  };
1825
+ if (!request.onToken) {
1826
+ const response = await this.client.chat.completions.create(baseParams);
1827
+ return parseResponse(response);
1828
+ }
1829
+ try {
1830
+ const stream = await this.client.chat.completions.create({
1831
+ ...baseParams,
1832
+ stream: true,
1833
+ stream_options: { include_usage: true }
1834
+ });
1835
+ let content = "";
1836
+ let finishReason = "stop";
1837
+ let usage;
1838
+ const tcMap = /* @__PURE__ */ new Map();
1839
+ for await (const chunk of stream) {
1840
+ const choice = chunk.choices?.[0];
1841
+ const delta = choice?.delta;
1842
+ if (delta?.content) {
1843
+ content += delta.content;
1844
+ request.onToken(delta.content);
1845
+ }
1846
+ if (delta?.tool_calls) {
1847
+ for (const tc of delta.tool_calls) {
1848
+ const idx = tc.index ?? 0;
1849
+ let acc = tcMap.get(idx);
1850
+ if (!acc) {
1851
+ acc = { args: "" };
1852
+ tcMap.set(idx, acc);
1853
+ }
1854
+ if (tc.id) acc.id = tc.id;
1855
+ if (tc.function?.name) acc.name = tc.function.name;
1856
+ if (tc.function?.arguments) acc.args += tc.function.arguments;
1857
+ }
1858
+ }
1859
+ if (choice?.finish_reason) finishReason = choice.finish_reason;
1860
+ if (chunk.usage) {
1861
+ usage = {
1862
+ inputTokens: chunk.usage.prompt_tokens ?? 0,
1863
+ outputTokens: chunk.usage.completion_tokens ?? 0
1864
+ };
1865
+ }
1866
+ }
1867
+ const toolCalls = [...tcMap.entries()].sort((a, b) => a[0] - b[0]).map(([idx, acc]) => ({
1868
+ id: acc.id ?? `call_${idx}`,
1869
+ type: "function",
1870
+ function: { name: acc.name ?? "", arguments: acc.args }
1871
+ }));
1872
+ return {
1873
+ content,
1874
+ toolCalls,
1875
+ finishReason: mapFinishReason(finishReason),
1876
+ usage
1877
+ };
1878
+ } catch {
1879
+ const response = await this.client.chat.completions.create(baseParams);
1880
+ return parseResponse(response);
1881
+ }
1626
1882
  }
1627
1883
  async listModels() {
1628
1884
  const page = await this.client.models.list();
@@ -3202,7 +3458,7 @@ Saved to ${configFilePath()}
3202
3458
 
3203
3459
  // src/cli.tsx
3204
3460
  import path9 from "path";
3205
- import { jsx as jsx5 } from "react/jsx-runtime";
3461
+ import { jsx as jsx6 } from "react/jsx-runtime";
3206
3462
  var packageJson = { version: "0.1.0" };
3207
3463
  var program = new Command();
3208
3464
  program.name("mentee").description("MenteE SWE \u2014 an autonomous SWE agent in your terminal. Bring your own model: Kimi, GLM, and more.").version(packageJson.version);
@@ -3259,7 +3515,7 @@ program.argument("[task...]", "the software task to perform (omit to type it int
3259
3515
  const { App: App2 } = await Promise.resolve().then(() => (init_App(), App_exports));
3260
3516
  const { render } = await import("ink");
3261
3517
  const { waitUntilExit } = render(
3262
- /* @__PURE__ */ jsx5(
3518
+ /* @__PURE__ */ jsx6(
3263
3519
  App2,
3264
3520
  {
3265
3521
  initialTask: task || void 0,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@menteeai/menteeswe",
3
- "version": "0.1.7",
3
+ "version": "0.1.10",
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",