@menteeai/menteeswe 0.1.7 → 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 +272 -142
  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".
@@ -631,11 +661,11 @@ function isRateLimitError(error) {
631
661
  }
632
662
  return /\b429\b|max rpm|rate limit|too many requests/i.test(error.message);
633
663
  }
634
- async function generateWithRetry(provider, model, system, messages, tools, bus) {
664
+ async function generateWithRetry(provider, model, system, messages, tools, bus, onToken) {
635
665
  let lastError = null;
636
666
  for (let attempt = 0; attempt <= RATE_LIMIT_MAX_ATTEMPTS; attempt++) {
637
667
  try {
638
- return await provider.generate({ system, messages, tools: tools.schemas() }, model);
668
+ return await provider.generate({ system, messages, tools: tools.schemas(), onToken }, model);
639
669
  } catch (error) {
640
670
  lastError = error;
641
671
  if (attempt >= RATE_LIMIT_MAX_ATTEMPTS || !isRateLimitError(lastError)) {
@@ -719,10 +749,24 @@ ${systemExtra}` : "");
719
749
  state.iteration += 1;
720
750
  state.usage.modelRequests += 1;
721
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
+ };
722
765
  let response;
723
766
  try {
724
- response = await generateWithRetry(provider, model, system, messages, tools, bus);
767
+ response = await generateWithRetry(provider, model, system, messages, tools, bus, onToken);
725
768
  } catch (error) {
769
+ flushThink();
726
770
  const message = error.message;
727
771
  let hint = "";
728
772
  if (/insufficient balance|no resource package|recharge/i.test(message)) {
@@ -744,6 +788,7 @@ ${systemExtra}` : "");
744
788
  });
745
789
  return { success: false, finalText, state };
746
790
  }
791
+ flushThink();
747
792
  bus.emit(
748
793
  "model_response",
749
794
  void 0,
@@ -772,7 +817,7 @@ ${systemExtra}` : "");
772
817
  content: response.content,
773
818
  tool_calls: response.toolCalls
774
819
  });
775
- if (response.content && response.content.trim()) {
820
+ if (!onToken && response.content && response.content.trim()) {
776
821
  bus.emit("info", response.content.trim().slice(0, 300));
777
822
  }
778
823
  for (const call of response.toolCalls) {
@@ -866,6 +911,13 @@ ${systemExtra}` : "");
866
911
  success: result.success,
867
912
  output: result.output.slice(0, 2e3)
868
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
+ }
869
921
  messages.push({
870
922
  role: "tool",
871
923
  tool_call_id: call.id,
@@ -894,7 +946,8 @@ ${systemExtra}` : "");
894
946
  toolCalls: state.totalToolCalls,
895
947
  modifiedFiles: state.modifiedFiles,
896
948
  usage: state.usage,
897
- durationMs: Date.now() - state.startedAt
949
+ durationMs: Date.now() - state.startedAt,
950
+ cancelled: signal?.aborted === true
898
951
  });
899
952
  return { success, finalText, state };
900
953
  }
@@ -1079,7 +1132,7 @@ var init_KeyDialog = __esm({
1079
1132
  var version;
1080
1133
  var init_package = __esm({
1081
1134
  "package.json"() {
1082
- version = "0.1.7";
1135
+ version = "0.1.9";
1083
1136
  }
1084
1137
  });
1085
1138
 
@@ -1089,11 +1142,12 @@ __export(App_exports, {
1089
1142
  App: () => App
1090
1143
  });
1091
1144
  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";
1145
+ import { Box as Box4, Static, Text as Text4, useApp, useInput as useInput4, useStdout } from "ink";
1093
1146
  import TextInput2 from "ink-text-input";
1094
1147
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1095
1148
  function App(props) {
1096
1149
  const { exit } = useApp();
1150
+ const { stdout } = useStdout();
1097
1151
  const bus = useMemo2(() => new EventBus(), []);
1098
1152
  const bridge = useMemo2(() => new ApprovalBridge(), []);
1099
1153
  const [log, setLog] = useState3([]);
@@ -1116,24 +1170,31 @@ function App(props) {
1116
1170
  const [toolHistory, setToolHistory] = useState3([]);
1117
1171
  const [inspectorIndex, setInspectorIndex] = useState3(null);
1118
1172
  const [detailsOpen, setDetailsOpen] = useState3(false);
1119
- const [showTools, setShowTools] = useState3(true);
1173
+ const [showDetails, setShowDetails] = useState3(false);
1174
+ const [thinkingText, setThinkingText] = useState3("");
1120
1175
  const pendingTool = useRef(null);
1121
1176
  const counter = useRef(0);
1122
1177
  const stateRef = useRef({ running, approval, dialog, factoryResult, providerName, model });
1123
1178
  stateRef.current = { running, approval, dialog, factoryResult, providerName, model };
1124
1179
  const abortRef = useRef(null);
1125
- const appendLog = (text) => {
1180
+ const appendLog = (text, noise = false) => {
1126
1181
  if (!text) return;
1127
1182
  counter.current += 1;
1128
- const line = { id: counter.current, text };
1183
+ const line = { id: counter.current, text, noise };
1129
1184
  setLog((prev) => [...prev, line]);
1130
1185
  };
1131
1186
  useEffect2(() => {
1132
- 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"]);
1133
1188
  const unsubscribeLog = bus.subscribe((event) => {
1134
- if (!(showTools === false && HIDDEN_NOISY.has(event.type))) {
1135
- 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;
1193
+ }
1194
+ if (event.type === "model_request" || event.type === "task_completed") {
1195
+ setThinkingText("");
1136
1196
  }
1197
+ appendLog(formatEvent(event), TOOL_NOISE.has(event.type));
1137
1198
  if (event.type === "tool_started") {
1138
1199
  const name = typeof event.data?.tool === "string" ? event.data.tool : "";
1139
1200
  const target = typeof event.message === "string" ? event.message : "";
@@ -1166,7 +1227,7 @@ function App(props) {
1166
1227
  unsubscribeLog();
1167
1228
  unsubscribeLog2();
1168
1229
  };
1169
- }, [bus, bridge, showTools]);
1230
+ }, [bus, bridge, showDetails]);
1170
1231
  useEffect2(() => {
1171
1232
  if (!running) return;
1172
1233
  const timer = setInterval(() => setFrame((f) => (f + 1) % SPINNER_FRAMES.length), 80);
@@ -1185,8 +1246,11 @@ function App(props) {
1185
1246
  return;
1186
1247
  }
1187
1248
  const timer = setTimeout(() => {
1188
- setTypedText(finalPending.slice(0, typedText.length + 3));
1189
- }, 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);
1190
1254
  return () => clearTimeout(timer);
1191
1255
  }, [typing, finalPending, typedText]);
1192
1256
  const updateConfig = (mutate) => {
@@ -1331,6 +1395,7 @@ function App(props) {
1331
1395
  setToolHistory([]);
1332
1396
  setInspectorIndex(null);
1333
1397
  setDetailsOpen(false);
1398
+ setShowDetails(false);
1334
1399
  setRunning(true);
1335
1400
  const ac = new AbortController();
1336
1401
  abortRef.current = ac;
@@ -1384,6 +1449,7 @@ function App(props) {
1384
1449
  useInput4((input, key) => {
1385
1450
  if (key.ctrl && input === "c") {
1386
1451
  if (stateRef.current.running) {
1452
+ appendLog("\u23F9 Interrupted by you \u2014 stopping the current task and returning to the prompt.");
1387
1453
  killActiveChild();
1388
1454
  abortRef.current?.abort();
1389
1455
  return;
@@ -1392,12 +1458,14 @@ function App(props) {
1392
1458
  setTimeout(() => process.exit(0), 50);
1393
1459
  return;
1394
1460
  }
1461
+ if (key.meta && (input === "d" || input === "D")) {
1462
+ setShowDetails((v) => !v);
1463
+ return;
1464
+ }
1395
1465
  if (stateRef.current.dialog || stateRef.current.approval || stateRef.current.running) return;
1396
1466
  if (key.meta && (input === "m" || input === "M")) openModelDialog();
1397
1467
  else if (key.ctrl && input === "p") setDialog({ kind: "provider" });
1398
1468
  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
1469
  });
1402
1470
  const modelLabel = model ?? factoryResult.provider?.defaultModel ?? (factoryResult.error ? "no key" : "");
1403
1471
  const header = /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginBottom: 1, children: [
@@ -1408,114 +1476,117 @@ function App(props) {
1408
1476
  " \xB7 ",
1409
1477
  providerName,
1410
1478
  modelLabel ? `:${modelLabel}` : "",
1411
- " \xB7 tools ",
1412
- showTools ? "on" : "off",
1479
+ " \xB7 details ",
1480
+ showDetails ? "open" : "collapsed",
1413
1481
  " \xB7 ",
1414
1482
  props.cwd
1415
1483
  ] }),
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" })
1484
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Alt+M model \xB7 Ctrl+P provider \xB7 Ctrl+K key \xB7 Alt+D details \xB7 /help" })
1417
1485
  ] }, "header");
1486
+ const cols = stdout.columns || 80;
1487
+ const marquee = thinkingText.slice(-Math.max(1, cols - 4));
1418
1488
  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.");
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)
1500
1532
  }
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,
1533
+ ) : dialog?.kind === "provider" ? /* @__PURE__ */ jsx4(
1534
+ SelectDialog,
1512
1535
  {
1513
- value: taskInput,
1514
- onChange: setTaskInput,
1515
- onSubmit: handleSubmit,
1516
- 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)
1517
1545
  }
1518
- )
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
+ ] })
1519
1590
  ] })
1520
1591
  ] });
1521
1592
  }
@@ -1586,6 +1657,20 @@ function toWireTools(tools) {
1586
1657
  }
1587
1658
  }));
1588
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
+ }
1589
1674
  var OpenAICompatProvider = class {
1590
1675
  name;
1591
1676
  defaultModel;
@@ -1603,26 +1688,71 @@ var OpenAICompatProvider = class {
1603
1688
  { role: "system", content: request.system },
1604
1689
  ...request.messages
1605
1690
  ];
1606
- const response = await this.client.chat.completions.create({
1691
+ const baseParams = {
1607
1692
  model: model ?? this.defaultModel,
1608
1693
  // the wire format matches our internal shape; keep loose typing at the boundary
1609
1694
  messages,
1610
1695
  ...request.tools.length > 0 ? { tools: toWireTools(request.tools) } : {},
1611
1696
  ...request.temperature !== void 0 ? { temperature: request.temperature } : {},
1612
1697
  ...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
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
+ }
1626
1756
  }
1627
1757
  async listModels() {
1628
1758
  const page = await this.client.models.list();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@menteeai/menteeswe",
3
- "version": "0.1.7",
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",