@modeloslab/modelcode 0.1.2 → 0.1.4

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.
@@ -60,6 +60,7 @@ import {
60
60
  searchFacts,
61
61
  searchSessions,
62
62
  shellInvocation,
63
+ shellName,
63
64
  snapshot,
64
65
  spawnProc,
65
66
  startScheduler,
@@ -67,7 +68,7 @@ import {
67
68
  undo,
68
69
  userPromptSubmit,
69
70
  which
70
- } from "./main-k6vk3448.mjs";
71
+ } from "./main-wr686fnv.mjs";
71
72
  import"./main-p2xnn95s.mjs";
72
73
  import {
73
74
  __commonJS,
@@ -21382,7 +21383,8 @@ var COMMAND_GROUPS = [
21382
21383
  { cmd: "/summary", desc: "recap the session (also /recap); auto-runs after heavy tasks — /summary disable to turn off" },
21383
21384
  { cmd: "/compact <summary>", desc: "collapse history to a summary (free context)" },
21384
21385
  { cmd: "/fork", desc: "save a snapshot of the current conversation" },
21385
- { cmd: "/btw <note>", desc: "inject a steering note mid-task" },
21386
+ { cmd: "/btw <question>", desc: "quick side question answered without changing the current task" },
21387
+ { cmd: "/steer <note>", desc: "steer the ongoing task (applied going forward)" },
21386
21388
  { cmd: "@<path>", desc: "mention a file — its contents are attached to your message" },
21387
21389
  { cmd: "/exit", desc: "quit" }
21388
21390
  ] },
@@ -21480,6 +21482,140 @@ ${c.dim}Tips show on startup (one at a time). Toggle off with MODELCODE_TIPS=0.$
21480
21482
  `);
21481
21483
  }
21482
21484
 
21485
+ // src/ui/spinnerVerbs.ts
21486
+ var SPINNER_VERBS = [
21487
+ "Accomplishing",
21488
+ "Architecting",
21489
+ "Baking",
21490
+ "Brewing",
21491
+ "Calculating",
21492
+ "Cerebrating",
21493
+ "Channeling",
21494
+ "Churning",
21495
+ "Cogitating",
21496
+ "Composing",
21497
+ "Computing",
21498
+ "Concocting",
21499
+ "Considering",
21500
+ "Contemplating",
21501
+ "Cooking",
21502
+ "Crafting",
21503
+ "Creating",
21504
+ "Crunching",
21505
+ "Crystallizing",
21506
+ "Deciphering",
21507
+ "Deliberating",
21508
+ "Determining",
21509
+ "Digging",
21510
+ "Distilling",
21511
+ "Doing",
21512
+ "Effecting",
21513
+ "Elaborating",
21514
+ "Engineering",
21515
+ "Envisioning",
21516
+ "Excavating",
21517
+ "Exploring",
21518
+ "Fabricating",
21519
+ "Fashioning",
21520
+ "Finagling",
21521
+ "Forging",
21522
+ "Formulating",
21523
+ "Generating",
21524
+ "Germinating",
21525
+ "Hatching",
21526
+ "Herding",
21527
+ "Honing",
21528
+ "Hustling",
21529
+ "Ideating",
21530
+ "Imagining",
21531
+ "Incubating",
21532
+ "Inferring",
21533
+ "Manifesting",
21534
+ "Marinating",
21535
+ "Mulling",
21536
+ "Musing",
21537
+ "Noodling",
21538
+ "Orchestrating",
21539
+ "Percolating",
21540
+ "Philosophizing",
21541
+ "Plotting",
21542
+ "Pondering",
21543
+ "Processing",
21544
+ "Puttering",
21545
+ "Puzzling",
21546
+ "Reticulating",
21547
+ "Ruminating",
21548
+ "Scheming",
21549
+ "Sculpting",
21550
+ "Shimmying",
21551
+ "Simmering",
21552
+ "Smooshing",
21553
+ "Spinning",
21554
+ "Stewing",
21555
+ "Summoning",
21556
+ "Synthesizing",
21557
+ "Thinking",
21558
+ "Tinkering",
21559
+ "Transmuting",
21560
+ "Unfurling",
21561
+ "Vibing",
21562
+ "Weaving",
21563
+ "Whirring",
21564
+ "Working",
21565
+ "Wrangling"
21566
+ ];
21567
+ function randomVerb() {
21568
+ return SPINNER_VERBS[Math.floor(Math.random() * SPINNER_VERBS.length)];
21569
+ }
21570
+
21571
+ // src/ui/markdown.ts
21572
+ var B = "\x1B[1m";
21573
+ var _B = "\x1B[22m";
21574
+ var I = "\x1B[3m";
21575
+ var _I = "\x1B[23m";
21576
+ var U = "\x1B[4m";
21577
+ var _U = "\x1B[24m";
21578
+ var DIM = "\x1B[2m";
21579
+ var _DIM = "\x1B[22m";
21580
+ var CY = "\x1B[36m";
21581
+ var _CY = "\x1B[39m";
21582
+ function inline(s) {
21583
+ return s.replace(/`([^`]+)`/g, `${CY}$1${_CY}`).replace(/\*\*([^*]+)\*\*/g, `${B}$1${_B}`).replace(/__([^_]+)__/g, `${B}$1${_B}`).replace(/(^|[^*])\*([^*\n]+)\*/g, `$1${I}$2${_I}`).replace(/(^|[^_])_([^_\n]+)_/g, `$1${I}$2${_I}`);
21584
+ }
21585
+ function formatMarkdown(src) {
21586
+ const out = [];
21587
+ let inFence = false;
21588
+ for (const raw of src.split(`
21589
+ `)) {
21590
+ const line = raw.replace(/\s+$/, "");
21591
+ if (/^\s*```/.test(line)) {
21592
+ inFence = !inFence;
21593
+ continue;
21594
+ }
21595
+ if (inFence) {
21596
+ out.push(`${DIM} ${line}${_DIM}`);
21597
+ continue;
21598
+ }
21599
+ const h = line.match(/^(#{1,6})\s+(.*)$/);
21600
+ if (h) {
21601
+ out.push(h[1].length === 1 ? `${B}${U}${h[2]}${_U}${_B}` : `${B}${inline(h[2])}${_B}`);
21602
+ continue;
21603
+ }
21604
+ if (/^\s*([-*_])\1{2,}\s*$/.test(line)) {
21605
+ out.push(`${DIM}────────${_DIM}`);
21606
+ continue;
21607
+ }
21608
+ const b = line.match(/^(\s*)[-*+]\s+(.*)$/);
21609
+ if (b) {
21610
+ out.push(`${b[1]}${CY}•${_CY} ${inline(b[2])}`);
21611
+ continue;
21612
+ }
21613
+ out.push(inline(line));
21614
+ }
21615
+ return out.join(`
21616
+ `);
21617
+ }
21618
+
21483
21619
  // src/ui/themes.ts
21484
21620
  var THEMES = {
21485
21621
  amber: { accent: "yellow", user: "green", tool: "cyan", dim: "gray", ok: "green", warn: "yellow" },
@@ -21492,11 +21628,11 @@ function getTheme(name) {
21492
21628
 
21493
21629
  // src/ui/App.tsx
21494
21630
  var jsx_dev_runtime = __toESM(require_jsx_dev_runtime(), 1);
21495
- var SPINNER = ["", "", "", "", "", "", "⠦", "⠧", "⠇", "⠏"];
21631
+ var SPINNER = ["", "", "", "", "", ""];
21496
21632
  function Spinner({ color }) {
21497
21633
  const [i, setI] = import_react34.useState(0);
21498
21634
  import_react34.useEffect(() => {
21499
- const t = setInterval(() => setI((n) => (n + 1) % SPINNER.length), 80);
21635
+ const t = setInterval(() => setI((n) => (n + 1) % SPINNER.length), 120);
21500
21636
  return () => clearInterval(t);
21501
21637
  }, []);
21502
21638
  return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
@@ -21504,6 +21640,46 @@ function Spinner({ color }) {
21504
21640
  children: SPINNER[i]
21505
21641
  }, undefined, false, undefined, this);
21506
21642
  }
21643
+ function WorkingRow({ start, tokens, color, dim }) {
21644
+ const [, tick] = import_react34.useState(0);
21645
+ const [verb, setVerb] = import_react34.useState(() => randomVerb());
21646
+ import_react34.useEffect(() => {
21647
+ const t = setInterval(() => tick((n) => n + 1), 1000);
21648
+ const v = setInterval(() => setVerb(randomVerb()), 4000);
21649
+ return () => {
21650
+ clearInterval(t);
21651
+ clearInterval(v);
21652
+ };
21653
+ }, []);
21654
+ const secs = Math.max(0, Math.floor((Date.now() - start) / 1000));
21655
+ const tok = tokens > 0 ? ` · ↓ ${tokens.toLocaleString()} tok` : "";
21656
+ return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21657
+ children: [
21658
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Spinner, {
21659
+ color
21660
+ }, undefined, false, undefined, this),
21661
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21662
+ color,
21663
+ bold: true,
21664
+ children: [
21665
+ " ",
21666
+ verb,
21667
+ "…"
21668
+ ]
21669
+ }, undefined, true, undefined, this),
21670
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21671
+ color: dim,
21672
+ children: [
21673
+ " (",
21674
+ secs,
21675
+ "s",
21676
+ tok,
21677
+ " · esc to interrupt)"
21678
+ ]
21679
+ }, undefined, true, undefined, this)
21680
+ ]
21681
+ }, undefined, true, undefined, this);
21682
+ }
21507
21683
  function Chip({ label, color }) {
21508
21684
  return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21509
21685
  backgroundColor: color,
@@ -21527,15 +21703,38 @@ function C_KEY(color, k) {
21527
21703
  ]
21528
21704
  }, undefined, true, undefined, this);
21529
21705
  }
21530
- function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct, walletMdl, mode, busy, confirmReq, history, onConfirm, onSubmit, onInterrupt, onExit }) {
21706
+ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct, walletMdl, mode, busy, toolStream, turnStart, turnTokens, confirmReq, history, onConfirm, onSubmit, onInterrupt, onExit }) {
21531
21707
  const t = getTheme(theme);
21532
21708
  const [input, setInput] = import_react34.useState("");
21533
21709
  const [histIdx, setHistIdx] = import_react34.useState(-1);
21710
+ const [exitHint, setExitHint] = import_react34.useState(false);
21711
+ const exitTimer = import_react34.useRef(null);
21534
21712
  const { exit } = use_app_default();
21713
+ const armExit = () => {
21714
+ setExitHint(true);
21715
+ if (exitTimer.current)
21716
+ clearTimeout(exitTimer.current);
21717
+ exitTimer.current = setTimeout(() => setExitHint(false), 800);
21718
+ };
21719
+ const doExit = () => {
21720
+ if (exitTimer.current)
21721
+ clearTimeout(exitTimer.current);
21722
+ onExit();
21723
+ exit();
21724
+ };
21535
21725
  use_input_default((ch, key) => {
21726
+ if (key.ctrl && ch === "d") {
21727
+ doExit();
21728
+ return;
21729
+ }
21536
21730
  if (key.ctrl && ch === "c") {
21537
- onExit();
21538
- exit();
21731
+ if (exitHint) {
21732
+ doExit();
21733
+ return;
21734
+ }
21735
+ if (busy)
21736
+ onInterrupt();
21737
+ armExit();
21539
21738
  return;
21540
21739
  }
21541
21740
  if (confirmReq) {
@@ -21554,8 +21753,6 @@ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct,
21554
21753
  onInterrupt();
21555
21754
  return;
21556
21755
  }
21557
- if (busy)
21558
- return;
21559
21756
  if (key.upArrow) {
21560
21757
  if (history.length) {
21561
21758
  const i = histIdx < 0 ? history.length - 1 : Math.max(0, histIdx - 1);
@@ -21603,6 +21800,7 @@ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct,
21603
21800
  const renderLine = (l, i) => {
21604
21801
  if (l.kind === "user")
21605
21802
  return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21803
+ flexDirection: "column",
21606
21804
  marginTop: 1,
21607
21805
  children: [
21608
21806
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Chip, {
@@ -21610,27 +21808,23 @@ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct,
21610
21808
  color: t.user
21611
21809
  }, undefined, false, undefined, this),
21612
21810
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21613
- children: [
21614
- " ",
21615
- l.text
21616
- ]
21617
- }, undefined, true, undefined, this)
21811
+ children: l.text
21812
+ }, undefined, false, undefined, this)
21618
21813
  ]
21619
21814
  }, i, true, undefined, this);
21620
21815
  if (l.kind === "assistant")
21621
21816
  return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21817
+ flexDirection: "column",
21622
21818
  marginTop: 1,
21623
21819
  children: [
21624
- /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Chip, {
21625
- label: "◆",
21626
- color: t.accent
21820
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21821
+ color: t.accent,
21822
+ bold: true,
21823
+ children: "◆ modelcode"
21627
21824
  }, undefined, false, undefined, this),
21628
21825
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21629
- children: [
21630
- " ",
21631
- l.text
21632
- ]
21633
- }, undefined, true, undefined, this)
21826
+ children: formatMarkdown(l.text)
21827
+ }, undefined, false, undefined, this)
21634
21828
  ]
21635
21829
  }, i, true, undefined, this);
21636
21830
  if (l.kind === "tool")
@@ -21643,6 +21837,7 @@ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct,
21643
21837
  }, i, true, undefined, this);
21644
21838
  if (l.kind === "error")
21645
21839
  return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21840
+ flexDirection: "column",
21646
21841
  marginTop: 1,
21647
21842
  children: [
21648
21843
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Chip, {
@@ -21651,11 +21846,8 @@ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct,
21651
21846
  }, undefined, false, undefined, this),
21652
21847
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21653
21848
  color: t.warn,
21654
- children: [
21655
- " ",
21656
- l.text
21657
- ]
21658
- }, undefined, true, undefined, this)
21849
+ children: l.text
21850
+ }, undefined, false, undefined, this)
21659
21851
  ]
21660
21852
  }, i, true, undefined, this);
21661
21853
  return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
@@ -21697,36 +21889,34 @@ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct,
21697
21889
  flexDirection: "column",
21698
21890
  children: [
21699
21891
  streaming ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21892
+ flexDirection: "column",
21700
21893
  marginTop: 1,
21701
21894
  children: [
21702
- /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Chip, {
21703
- label: "◆",
21704
- color: t.accent
21705
- }, undefined, false, undefined, this),
21706
21895
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21707
- children: [
21708
- " ",
21709
- streaming
21710
- ]
21711
- }, undefined, true, undefined, this)
21712
- ]
21713
- }, undefined, true, undefined, this) : null,
21714
- busy && !streaming ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21715
- marginTop: 1,
21716
- children: [
21717
- /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Spinner, {
21718
- color: t.accent
21896
+ color: t.accent,
21897
+ bold: true,
21898
+ children: "◆ modelcode"
21719
21899
  }, undefined, false, undefined, this),
21720
21900
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21721
- color: t.dim,
21722
- children: [
21723
- " thinking… ",
21724
- C_KEY(t.dim, "esc"),
21725
- " to interrupt"
21726
- ]
21727
- }, undefined, true, undefined, this)
21901
+ children: formatMarkdown(streaming)
21902
+ }, undefined, false, undefined, this)
21728
21903
  ]
21729
- }, undefined, true, undefined, this) : null
21904
+ }, undefined, true, undefined, this) : null,
21905
+ toolStream ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21906
+ color: t.dim,
21907
+ children: toolStream.split(`
21908
+ `).slice(-6).join(`
21909
+ `)
21910
+ }, undefined, false, undefined, this) : null,
21911
+ busy ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21912
+ marginTop: streaming || toolStream ? 0 : 1,
21913
+ children: /* @__PURE__ */ jsx_dev_runtime.jsxDEV(WorkingRow, {
21914
+ start: turnStart,
21915
+ tokens: turnTokens,
21916
+ color: t.accent,
21917
+ dim: t.dim
21918
+ }, undefined, false, undefined, this)
21919
+ }, undefined, false, undefined, this) : null
21730
21920
  ]
21731
21921
  }, undefined, true, undefined, this),
21732
21922
  suggestions.length ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
@@ -21783,26 +21973,27 @@ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct,
21783
21973
  ]
21784
21974
  }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21785
21975
  borderStyle: "round",
21786
- borderColor: busy ? t.dim : t.user,
21976
+ borderColor: t.user,
21787
21977
  paddingX: 1,
21788
21978
  marginTop: 1,
21789
21979
  children: [
21790
21980
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21791
- color: busy ? t.dim : t.user,
21792
- children: [
21793
- busy ? "…" : "›",
21794
- " "
21795
- ]
21796
- }, undefined, true, undefined, this),
21981
+ color: t.user,
21982
+ children: "› "
21983
+ }, undefined, false, undefined, this),
21797
21984
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21798
21985
  children: input
21799
21986
  }, undefined, false, undefined, this),
21800
- !busy ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21987
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21801
21988
  color: t.accent,
21802
21989
  children: "▋"
21803
- }, undefined, false, undefined, this) : null
21990
+ }, undefined, false, undefined, this)
21804
21991
  ]
21805
21992
  }, undefined, true, undefined, this),
21993
+ busy && input ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21994
+ color: t.dim,
21995
+ children: " ↵ to queue this for after the current turn"
21996
+ }, undefined, false, undefined, this) : null,
21806
21997
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21807
21998
  justifyContent: "space-between",
21808
21999
  paddingX: 1,
@@ -21831,16 +22022,22 @@ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct,
21831
22022
  ctxBar,
21832
22023
  " ",
21833
22024
  contextPct,
21834
- "%"
22025
+ "%",
22026
+ turnTokens > 0 ? ` · ↓${turnTokens.toLocaleString()} tok` : ""
21835
22027
  ]
21836
22028
  }, undefined, true, undefined, this),
21837
- /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
22029
+ exitHint ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
22030
+ color: t.warn,
22031
+ children: "press Ctrl-C again to exit"
22032
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21838
22033
  color: t.dim,
21839
22034
  children: [
21840
- sessionMdl.toFixed(6),
22035
+ sessionMdl.toFixed(4),
21841
22036
  " MDL",
21842
22037
  usd,
21843
- " · /help · ^C"
22038
+ " · ",
22039
+ busy ? "esc=stop · " : "",
22040
+ "^C ^C=exit"
21844
22041
  ]
21845
22042
  }, undefined, true, undefined, this)
21846
22043
  ]
@@ -21897,6 +22094,16 @@ class Agent {
21897
22094
  note(text) {
21898
22095
  this.history.push({ role: "system", content: `User steering note (apply going forward): ${text}` });
21899
22096
  }
22097
+ async sideQuestion(question, onDelta) {
22098
+ const forked = [
22099
+ ...this.history,
22100
+ { role: "system", content: "The user has a quick side question. Answer it concisely using the conversation so far. This does NOT change the current task." },
22101
+ { role: "user", content: question }
22102
+ ];
22103
+ const res = await chat(this.cfg, forked, [], onDelta ?? (() => {}), this.modelFor(question));
22104
+ this.h.onCost(res.feeGrains);
22105
+ return res.content || "(no answer)";
22106
+ }
21900
22107
  get messages() {
21901
22108
  return [...this.history];
21902
22109
  }
@@ -22082,7 +22289,7 @@ ${tail2}`;
22082
22289
  } else {
22083
22290
  if (MUTATING_TOOLS.has(tool.name) && typeof args.path === "string")
22084
22291
  snapshot(this.ctx.cwd, args.path);
22085
- const runCtx = { ...this.ctx, onStream: (c2) => this.h.onToolStream?.(c2) };
22292
+ const runCtx = { ...this.ctx, onStream: (c2) => this.h.onToolStream?.(c2), onCost: (g) => this.h.onCost(g) };
22086
22293
  try {
22087
22294
  result2 = await tool.run(args, runCtx);
22088
22295
  } catch (e) {
@@ -22584,13 +22791,15 @@ async function handleSlash(line, ctx) {
22584
22791
  }
22585
22792
  case "/model": {
22586
22793
  if (!arg)
22587
- return { output: `build model: ${cfg.model}${cfg.autoRoute ? " (auto-route on)" : ""}` };
22794
+ return { output: `build model: ${cfg.model}${cfg.autoRoute ? " (auto-route ON — /route to turn off)" : ""}` };
22588
22795
  const id = resolveModelName(arg);
22589
22796
  if (!id)
22590
22797
  return { output: `unknown model '${arg}' — /models to list` };
22798
+ const wasRouting = cfg.autoRoute;
22591
22799
  cfg.model = id;
22592
- saveConfig({ model: id });
22593
- return { output: `✓ build model → ${id}` };
22800
+ cfg.autoRoute = false;
22801
+ saveConfig({ model: id, autoRoute: false });
22802
+ return { output: `✓ build model → ${id}${wasRouting ? " (auto-route turned OFF — your model is used for every turn)" : ""}`, modeChanged: true };
22594
22803
  }
22595
22804
  case "/submodel": {
22596
22805
  if (!arg) {
@@ -22732,7 +22941,9 @@ ${typeof m.content === "string" ? m.content : JSON.stringify(m.content)}`).join(
22732
22941
  `) : "no saved sessions" };
22733
22942
  }
22734
22943
  case "/btw":
22735
- return arg ? { note: arg, output: "noted — will apply going forward" } : { output: "usage: /btw <steering note>" };
22944
+ return arg ? { sideQuestion: arg } : { output: "usage: /btw <quick question> — answered without changing the current task" };
22945
+ case "/steer":
22946
+ return arg ? { note: arg, output: "noted — will apply going forward" } : { output: "usage: /steer <note> — steer the ongoing task" };
22736
22947
  case "/web":
22737
22948
  case "/fetch":
22738
22949
  case "/browse":
@@ -22791,7 +23002,7 @@ function inside(cwd2, p) {
22791
23002
  }
22792
23003
  var bash = {
22793
23004
  name: "bash",
22794
- description: "Run a shell command in the working directory; returns combined stdout+stderr. Use for builds, tests, git, file ops.",
23005
+ description: `Run a shell command in the working directory (shell: ${shellName()}); returns combined stdout+stderr. Use for builds, tests, git, file ops.${shellName() === "powershell" || shellName() === "pwsh" ? " NOTE: this is Windows PowerShell — use PowerShell syntax (Get-ChildItem/ls, Get-Content/cat, 2>$null, Select-Object -First N)." : shellName() === "cmd" ? " NOTE: this is Windows cmd.exe — use cmd syntax." : ""}`,
22795
23006
  permission: "ask",
22796
23007
  parameters: {
22797
23008
  type: "object",
@@ -22981,7 +23192,9 @@ function registerAgentTool(cfg, onProgress) {
22981
23192
  if (!agent)
22982
23193
  return `error: unknown subagent '${args.subagent_type}'. available: ${names.join(", ")}`;
22983
23194
  const { text, feeGrains } = await runSubagent(cfg, agent, String(args.prompt ?? ""), ctx, onProgress);
22984
- return `[${agent.name} · ${(feeGrains / 1e8).toFixed(6)} MDL · free/uncapped]
23195
+ ctx.onCost?.(feeGrains);
23196
+ onProgress?.(` ↳ ${agent.name} done · ${(feeGrains / 1e8).toFixed(4)} MDL`);
23197
+ return `[${agent.name} · ${(feeGrains / 1e8).toFixed(6)} MDL]
22985
23198
  ${text}`;
22986
23199
  }
22987
23200
  };
@@ -23002,7 +23215,8 @@ ${text}`;
23002
23215
  async run(args, ctx) {
23003
23216
  const members = Array.isArray(args.members) ? args.members.map(String) : [];
23004
23217
  const { transcript, feeGrains } = await runTeam(cfg, members, String(args.prompt ?? ""), ctx, Number(args.rounds) || 2, onProgress);
23005
- return `[team · ${(feeGrains / 1e8).toFixed(6)} MDL · free/uncapped]
23218
+ ctx.onCost?.(feeGrains);
23219
+ return `[team · ${(feeGrains / 1e8).toFixed(6)} MDL]
23006
23220
  ${transcript}`;
23007
23221
  }
23008
23222
  };
@@ -23606,7 +23820,10 @@ function imageMentions(line, cwd2) {
23606
23820
  // src/ui/tui.tsx
23607
23821
  var jsx_dev_runtime2 = __toESM(require_jsx_dev_runtime(), 1);
23608
23822
  function Root({ cfg, resume, mcpCount }) {
23609
- const [lines, setLines] = import_react35.useState([{ kind: "system", text: "ready — ask me anything, or /help" }]);
23823
+ const [lines, setLines] = import_react35.useState([
23824
+ { kind: "system", text: `ready · model ${cfg.model} — ask me anything, or /help` },
23825
+ ...cfg.autoRoute ? [{ kind: "system", text: `⚠ auto-route is ON — turns may use a different model than ${cfg.model}. Run /route to turn it off (or /model ${cfg.model} to lock it in).` }] : []
23826
+ ]);
23610
23827
  const [streaming, setStreaming] = import_react35.useState("");
23611
23828
  const [busy, setBusy] = import_react35.useState(false);
23612
23829
  const [sessionMdl, setSessionMdl] = import_react35.useState(0);
@@ -23615,14 +23832,27 @@ function Root({ cfg, resume, mcpCount }) {
23615
23832
  const [mode, setMode] = import_react35.useState("default");
23616
23833
  const [confirmReq, setConfirmReq] = import_react35.useState(null);
23617
23834
  const [history, setHistory] = import_react35.useState([]);
23835
+ const [toolStream, setToolStream] = import_react35.useState("");
23836
+ const [turnStart, setTurnStart] = import_react35.useState(0);
23837
+ const [turnTokens, setTurnTokens] = import_react35.useState(0);
23618
23838
  const mdlUsd = import_react35.useRef(null);
23619
23839
  const streamBuf = import_react35.useRef("");
23840
+ const tokensRef = import_react35.useRef(0);
23841
+ const busyRef = import_react35.useRef(false);
23842
+ const queueRef = import_react35.useRef([]);
23620
23843
  const confirmResolve = import_react35.useRef(null);
23621
23844
  const sessionIdRef = import_react35.useRef(newSessionId());
23622
23845
  const sessionMdlRef = import_react35.useRef(0);
23623
23846
  const mcpCountRef = import_react35.useRef(mcpCount);
23624
23847
  const cwd2 = process.cwd();
23625
23848
  const add2 = import_react35.useCallback((l) => setLines((prev) => [...prev, l]), []);
23849
+ const flushAssistant = import_react35.useCallback(() => {
23850
+ const txt = streamBuf.current.trim();
23851
+ streamBuf.current = "";
23852
+ setStreaming("");
23853
+ if (txt)
23854
+ setLines((prev) => [...prev, { kind: "assistant", text: txt }]);
23855
+ }, []);
23626
23856
  const confirm = import_react35.useCallback((name, args) => {
23627
23857
  if (isAllowed(name, cwd2))
23628
23858
  return Promise.resolve(true);
@@ -23643,13 +23873,19 @@ function Root({ cfg, resume, mcpCount }) {
23643
23873
  onAssistantDelta: (txt) => {
23644
23874
  streamBuf.current += txt;
23645
23875
  setStreaming(streamBuf.current);
23876
+ tokensRef.current += Math.max(1, Math.round(txt.length / 4));
23877
+ setTurnTokens(tokensRef.current);
23646
23878
  },
23647
- onToolStart: (name, args) => add2({ kind: "tool", text: `${name} ${JSON.stringify(args).slice(0, 80)}` }),
23648
- onToolResult: (_n, r) => add2({ kind: "system", text: r.slice(0, 300) }),
23649
- onToolStream: (chunk2) => {
23650
- streamBuf.current += chunk2;
23651
- setStreaming(streamBuf.current);
23879
+ onToolStart: (name, args) => {
23880
+ flushAssistant();
23881
+ setToolStream("");
23882
+ add2({ kind: "tool", text: `${name} ${JSON.stringify(args).slice(0, 80)}` });
23883
+ },
23884
+ onToolResult: (_n, r) => {
23885
+ setToolStream("");
23886
+ add2({ kind: "system", text: r.slice(0, 300) });
23652
23887
  },
23888
+ onToolStream: (chunk2) => setToolStream((s) => (s + chunk2).slice(-2000)),
23653
23889
  onCost: (g) => setSessionMdl((s) => {
23654
23890
  const v = s + g / 1e8;
23655
23891
  sessionMdlRef.current = v;
@@ -23695,39 +23931,47 @@ function Root({ cfg, resume, mcpCount }) {
23695
23931
  add2({ kind: "system", text: "not logged in — run `modelcode login` (or option 1/2 at startup) to set an API key. /help works without one." });
23696
23932
  return;
23697
23933
  }
23698
- setBusy(true);
23699
23934
  streamBuf.current = "";
23700
23935
  setStreaming("");
23936
+ setToolStream("");
23937
+ tokensRef.current = 0;
23938
+ setTurnTokens(0);
23939
+ setTurnStart(Date.now());
23940
+ busyRef.current = true;
23941
+ setBusy(true);
23701
23942
  try {
23702
23943
  await agent.send(expandFileMentions(prompt, cwd2), imageMentions(prompt, cwd2));
23703
- if (streamBuf.current)
23704
- add2({ kind: "assistant", text: streamBuf.current });
23944
+ flushAssistant();
23705
23945
  saveSession(sessionIdRef.current, cwd2, agent.messages);
23706
23946
  const heavyMin = Number(process.env.MODELCODE_AUTOSUMMARY_MIN || "8");
23707
23947
  if (cfg.autoSummary && agent.lastTurnToolCount >= heavyMin) {
23708
- streamBuf.current = "";
23948
+ tokensRef.current = 0;
23949
+ setTurnTokens(0);
23950
+ setTurnStart(Date.now());
23709
23951
  await agent.send("Recap concisely (bullets): what we worked on, key changes/decisions (files touched), current state, and any open/next steps.");
23710
- if (streamBuf.current)
23711
- add2({ kind: "system", text: `— recap —
23712
- ` + streamBuf.current });
23952
+ flushAssistant();
23713
23953
  }
23714
23954
  } catch (e) {
23955
+ flushAssistant();
23715
23956
  add2({ kind: "error", text: e.message });
23716
23957
  } finally {
23717
23958
  setStreaming("");
23959
+ setToolStream("");
23718
23960
  streamBuf.current = "";
23961
+ busyRef.current = false;
23719
23962
  setBusy(false);
23720
23963
  setContextPct(agent.contextStatus().pct);
23721
23964
  setMode(agent.mode);
23722
23965
  refreshWallet();
23966
+ const next = queueRef.current.shift();
23967
+ if (next !== undefined)
23968
+ runInput(next);
23723
23969
  }
23724
- }, [agent, add2, refreshWallet, cfg, cwd2]);
23970
+ }, [agent, add2, refreshWallet, cfg, cwd2, flushAssistant]);
23725
23971
  const onInterrupt = import_react35.useCallback(() => {
23726
23972
  agent.abort();
23727
23973
  }, [agent]);
23728
- const onSubmit = import_react35.useCallback(async (input) => {
23729
- add2({ kind: "user", text: input });
23730
- setHistory((h) => (h[h.length - 1] === input ? h : [...h, input]).slice(-100));
23974
+ const runInput = import_react35.useCallback(async (input) => {
23731
23975
  if (input.startsWith("/")) {
23732
23976
  const r = await handleSlash(input, { agent, cfg, cwd: cwd2, sessionMdl: sessionMdlRef.current, mcpCount: mcpCountRef.current });
23733
23977
  if (r) {
@@ -23752,6 +23996,28 @@ function Root({ cfg, resume, mcpCount }) {
23752
23996
  setMode(agent.mode);
23753
23997
  if (r.output)
23754
23998
  add2({ kind: "system", text: r.output });
23999
+ if (r.sideQuestion) {
24000
+ busyRef.current = true;
24001
+ setBusy(true);
24002
+ setTurnStart(Date.now());
24003
+ streamBuf.current = "";
24004
+ setStreaming("");
24005
+ try {
24006
+ const ans = await agent.sideQuestion(r.sideQuestion, (t) => {
24007
+ streamBuf.current += t;
24008
+ setStreaming(streamBuf.current);
24009
+ });
24010
+ add2({ kind: "system", text: `btw › ${ans}` });
24011
+ } catch (e) {
24012
+ add2({ kind: "error", text: e.message });
24013
+ } finally {
24014
+ streamBuf.current = "";
24015
+ setStreaming("");
24016
+ busyRef.current = false;
24017
+ setBusy(false);
24018
+ refreshWallet();
24019
+ }
24020
+ }
23755
24021
  if (r.agentPrompt)
23756
24022
  await runTurn(r.agentPrompt);
23757
24023
  return;
@@ -23759,6 +24025,17 @@ function Root({ cfg, resume, mcpCount }) {
23759
24025
  }
23760
24026
  await runTurn(input);
23761
24027
  }, [agent, add2, cfg, cwd2, onExit, runTurn]);
24028
+ const onSubmit = import_react35.useCallback((input) => {
24029
+ setHistory((h) => (h[h.length - 1] === input ? h : [...h, input]).slice(-100));
24030
+ if (busyRef.current) {
24031
+ queueRef.current.push(input);
24032
+ add2({ kind: "user", text: input });
24033
+ add2({ kind: "system", text: "↳ queued (runs after the current turn)" });
24034
+ return;
24035
+ }
24036
+ add2({ kind: "user", text: input });
24037
+ runInput(input);
24038
+ }, [add2, runInput]);
23762
24039
  return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(App2, {
23763
24040
  theme: cfg.theme,
23764
24041
  model: cfg.model,
@@ -23770,6 +24047,9 @@ function Root({ cfg, resume, mcpCount }) {
23770
24047
  walletMdl,
23771
24048
  mode,
23772
24049
  busy,
24050
+ toolStream,
24051
+ turnStart,
24052
+ turnTokens,
23773
24053
  confirmReq,
23774
24054
  history,
23775
24055
  onConfirm,
@@ -23778,12 +24058,25 @@ function Root({ cfg, resume, mcpCount }) {
23778
24058
  onExit
23779
24059
  }, undefined, false, undefined, this);
23780
24060
  }
23781
- async function runTui(cfg, resume = "none") {
23782
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
23783
- process.stderr.write(`modelcode: the interactive TUI needs a terminal (TTY). For non-interactive use, run: modelcode -p "<prompt>"
23784
- `);
23785
- process.exit(1);
24061
+ function getRenderStdin() {
24062
+ if (process.stdin.isTTY)
24063
+ return;
24064
+ if (process.env.CI)
24065
+ return;
24066
+ if (process.platform === "win32")
24067
+ return;
24068
+ try {
24069
+ const { openSync } = __require("fs");
24070
+ const { ReadStream } = __require("tty");
24071
+ const s = new ReadStream(openSync("/dev/tty", "r"));
24072
+ s.isTTY = true;
24073
+ return s;
24074
+ } catch {
24075
+ return;
23786
24076
  }
24077
+ }
24078
+ async function runTui(cfg, resume = "none") {
24079
+ const renderStdin = getRenderStdin();
23787
24080
  registerCoreTools();
23788
24081
  registerMemoryTools();
23789
24082
  registerAgentTool(cfg, (line) => emitProgress(line));
@@ -23791,7 +24084,12 @@ async function runTui(cfg, resume = "none") {
23791
24084
  registerMoreTools();
23792
24085
  registerBrowserTool();
23793
24086
  registerTaskTools();
23794
- const mcpCount = await registerMcpTools(process.cwd()).catch(() => 0);
24087
+ let mcpCount = 0;
24088
+ registerMcpTools(process.cwd()).then((n) => {
24089
+ mcpCount = n;
24090
+ if (n)
24091
+ emitProgress(`✓ ${n} MCP tool(s) connected`);
24092
+ }).catch(() => {});
23795
24093
  startScheduler(async (prompt, jobCwd) => {
23796
24094
  emitProgress(`⏰ running scheduled job…`);
23797
24095
  const a = new Agent(cfg, { cwd: jobCwd }, { onAssistantDelta: () => {}, onToolStart: () => {}, onToolResult: () => {}, onCost: () => {}, confirm: async () => true });
@@ -23800,11 +24098,26 @@ async function runTui(cfg, resume = "none") {
23800
24098
  });
23801
24099
  pullTeamMemory().catch(() => {});
23802
24100
  indexCodebaseIncremental(process.cwd()).catch(() => {});
24101
+ const restore = () => {
24102
+ try {
24103
+ if (process.stdin.isTTY)
24104
+ process.stdin.setRawMode(false);
24105
+ } catch {}
24106
+ };
24107
+ process.on("exit", restore);
24108
+ process.on("SIGINT", () => {
24109
+ restore();
24110
+ process.exit(0);
24111
+ });
24112
+ process.on("SIGTERM", () => {
24113
+ restore();
24114
+ process.exit(0);
24115
+ });
23803
24116
  render_default(/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Root, {
23804
24117
  cfg,
23805
24118
  resume,
23806
24119
  mcpCount
23807
- }, undefined, false, undefined, this));
24120
+ }, undefined, false, undefined, this), { exitOnCtrlC: false, ...renderStdin ? { stdin: renderStdin } : {} });
23808
24121
  }
23809
24122
  export {
23810
24123
  runTui