@modeloslab/modelcode 0.1.3 → 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.
package/dist/cli.mjs CHANGED
@@ -56,6 +56,7 @@ import {
56
56
  searchFacts,
57
57
  searchSessions,
58
58
  shellInvocation,
59
+ shellName,
59
60
  snapshot,
60
61
  spawnProc,
61
62
  toolSchemas,
@@ -63,7 +64,7 @@ import {
63
64
  validateMnemonic,
64
65
  walletFromMnemonic,
65
66
  which
66
- } from "./main-k6vk3448.mjs";
67
+ } from "./main-wr686fnv.mjs";
67
68
  import"./main-p2xnn95s.mjs";
68
69
  import {
69
70
  __require
@@ -141,7 +142,7 @@ function inside(cwd, p) {
141
142
  }
142
143
  var bash = {
143
144
  name: "bash",
144
- description: "Run a shell command in the working directory; returns combined stdout+stderr. Use for builds, tests, git, file ops.",
145
+ 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." : ""}`,
145
146
  permission: "ask",
146
147
  parameters: {
147
148
  type: "object",
@@ -331,7 +332,9 @@ function registerAgentTool(cfg, onProgress) {
331
332
  if (!agent)
332
333
  return `error: unknown subagent '${args.subagent_type}'. available: ${names.join(", ")}`;
333
334
  const { text, feeGrains } = await runSubagent(cfg, agent, String(args.prompt ?? ""), ctx, onProgress);
334
- return `[${agent.name} · ${(feeGrains / 1e8).toFixed(6)} MDL · free/uncapped]
335
+ ctx.onCost?.(feeGrains);
336
+ onProgress?.(` ↳ ${agent.name} done · ${(feeGrains / 1e8).toFixed(4)} MDL`);
337
+ return `[${agent.name} · ${(feeGrains / 1e8).toFixed(6)} MDL]
335
338
  ${text}`;
336
339
  }
337
340
  };
@@ -352,7 +355,8 @@ ${text}`;
352
355
  async run(args, ctx) {
353
356
  const members = Array.isArray(args.members) ? args.members.map(String) : [];
354
357
  const { transcript, feeGrains } = await runTeam(cfg, members, String(args.prompt ?? ""), ctx, Number(args.rounds) || 2, onProgress);
355
- return `[team · ${(feeGrains / 1e8).toFixed(6)} MDL · free/uncapped]
358
+ ctx.onCost?.(feeGrains);
359
+ return `[team · ${(feeGrains / 1e8).toFixed(6)} MDL]
356
360
  ${transcript}`;
357
361
  }
358
362
  };
@@ -407,6 +411,16 @@ class Agent {
407
411
  note(text) {
408
412
  this.history.push({ role: "system", content: `User steering note (apply going forward): ${text}` });
409
413
  }
414
+ async sideQuestion(question, onDelta) {
415
+ const forked = [
416
+ ...this.history,
417
+ { 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." },
418
+ { role: "user", content: question }
419
+ ];
420
+ const res = await chat(this.cfg, forked, [], onDelta ?? (() => {}), this.modelFor(question));
421
+ this.h.onCost(res.feeGrains);
422
+ return res.content || "(no answer)";
423
+ }
410
424
  get messages() {
411
425
  return [...this.history];
412
426
  }
@@ -592,7 +606,7 @@ ${tail}`;
592
606
  } else {
593
607
  if (MUTATING_TOOLS.has(tool.name) && typeof args.path === "string")
594
608
  snapshot(this.ctx.cwd, args.path);
595
- const runCtx = { ...this.ctx, onStream: (c) => this.h.onToolStream?.(c) };
609
+ const runCtx = { ...this.ctx, onStream: (c) => this.h.onToolStream?.(c), onCost: (g) => this.h.onCost(g) };
596
610
  try {
597
611
  result = await tool.run(args, runCtx);
598
612
  } catch (e) {
@@ -1585,8 +1599,17 @@ async function main() {
1585
1599
  const rl = createInterface({ input: stdin, output: stdout });
1586
1600
  await interactiveLogin(rl, cfg);
1587
1601
  rl.close();
1602
+ const s = stdin;
1603
+ s.removeAllListeners("data");
1604
+ s.removeAllListeners("keypress");
1605
+ try {
1606
+ if (s.isTTY)
1607
+ s.setRawMode(false);
1608
+ } catch {}
1609
+ s.resume();
1610
+ await new Promise((r) => setImmediate(r));
1588
1611
  }
1589
- const { runTui } = await import("./tui-gh76vcf8.mjs");
1612
+ const { runTui } = await import("./tui-sekv1hga.mjs");
1590
1613
  return runTui(cfg, resume);
1591
1614
  }
1592
1615
  if (cmd === "tui")
@@ -7966,7 +7966,19 @@ var isWin = process.platform === "win32";
7966
7966
  function shellInvocation(cmd) {
7967
7967
  if (!isWin)
7968
7968
  return ["bash", "-lc", cmd];
7969
- return hasBash() ? ["bash", "-c", cmd] : ["cmd", "/c", cmd];
7969
+ if (hasBash())
7970
+ return ["bash", "-c", cmd];
7971
+ const ps = powershellBin();
7972
+ if (ps)
7973
+ return [ps, "-NoProfile", "-NonInteractive", "-Command", cmd];
7974
+ return ["cmd", "/c", cmd];
7975
+ }
7976
+ function shellName() {
7977
+ if (!isWin)
7978
+ return "bash";
7979
+ if (hasBash())
7980
+ return "bash";
7981
+ return powershellBin() ?? "cmd";
7970
7982
  }
7971
7983
  var _bash = null;
7972
7984
  function hasBash() {
@@ -7979,6 +7991,19 @@ function hasBash() {
7979
7991
  }
7980
7992
  return _bash;
7981
7993
  }
7994
+ var _ps;
7995
+ function powershellBin() {
7996
+ if (_ps !== undefined)
7997
+ return _ps;
7998
+ for (const bin of ["pwsh", "powershell"]) {
7999
+ try {
8000
+ if (spawnSync(bin, ["-NoProfile", "-Command", "exit 0"], { stdio: "ignore" }).status === 0) {
8001
+ return _ps = bin;
8002
+ }
8003
+ } catch {}
8004
+ }
8005
+ return _ps = null;
8006
+ }
7982
8007
  function packageRoot(startDir) {
7983
8008
  let dir = startDir;
7984
8009
  for (let i = 0;i < 12; i++) {
@@ -21025,6 +21050,7 @@ async function chat2(cfg, messages, tools, onDelta, modelOverride, timeoutMs = 1
21025
21050
  messages,
21026
21051
  max_tokens: cfg.maxTokens,
21027
21052
  stream: true,
21053
+ stream_options: { include_usage: true },
21028
21054
  ...tools.length ? { tools, tool_choice: "auto" } : {}
21029
21055
  };
21030
21056
  const attempt = async (live) => {
@@ -21032,7 +21058,13 @@ async function chat2(cfg, messages, tools, onDelta, modelOverride, timeoutMs = 1
21032
21058
  let content = "";
21033
21059
  let finishReason = null;
21034
21060
  let feeGrains = 0;
21061
+ let promptTokens = 0, completionTokens = 0;
21035
21062
  const calls = {};
21063
+ const readFee = (o) => {
21064
+ const x = o;
21065
+ const v = x?.x_modelos?.fee_grains ?? x?.fee_grains ?? x?.cost_grains;
21066
+ return v != null ? Number(v) || 0 : 0;
21067
+ };
21036
21068
  for await (const chunk of stream) {
21037
21069
  const choice = chunk.choices?.[0];
21038
21070
  const delta = choice?.delta;
@@ -21053,22 +21085,33 @@ async function chat2(cfg, messages, tools, onDelta, modelOverride, timeoutMs = 1
21053
21085
  }
21054
21086
  if (choice?.finish_reason)
21055
21087
  finishReason = choice.finish_reason;
21056
- const x = chunk.x_modelos;
21057
- if (x?.fee_grains)
21058
- feeGrains = Number(x.fee_grains) || feeGrains;
21088
+ feeGrains = readFee(chunk) || readFee(chunk.usage) || feeGrains;
21089
+ const usage = chunk.usage;
21090
+ if (usage) {
21091
+ promptTokens = usage.prompt_tokens ?? promptTokens;
21092
+ completionTokens = usage.completion_tokens ?? completionTokens;
21093
+ }
21059
21094
  }
21060
- return { content, toolCalls: Object.values(calls), finishReason, feeGrains };
21095
+ return { content, toolCalls: Object.values(calls), finishReason, feeGrains, promptTokens, completionTokens };
21061
21096
  };
21062
- try {
21063
- return await attempt(true);
21064
- } catch (e) {
21065
- await new Promise((r) => setTimeout(r, 1500));
21097
+ let lastErr;
21098
+ for (let i = 0;i < 3; i++) {
21066
21099
  try {
21067
21100
  return await attempt(true);
21068
- } catch {
21069
- throw e;
21101
+ } catch (e) {
21102
+ lastErr = e;
21103
+ const status = e.status;
21104
+ if (status && status >= 400 && status < 500)
21105
+ break;
21106
+ if (i < 2)
21107
+ await new Promise((r) => setTimeout(r, 1000 * (i + 1)));
21070
21108
  }
21071
21109
  }
21110
+ const err = lastErr;
21111
+ const reachable = err?.status !== undefined;
21112
+ if (!reachable)
21113
+ throw new Error(`inference API not reachable (${cfg.baseUrl}) — check your connection. ${err?.message ?? err?.code ?? ""}`.trim());
21114
+ throw new Error(`inference API error${err.status ? ` (HTTP ${err.status})` : ""}: ${err.message ?? "request rejected"}`);
21072
21115
  }
21073
21116
 
21074
21117
  // src/router/router.ts
@@ -21387,21 +21430,39 @@ async function runSubagent(cfg2, agent, task, ctx, onProgress, extraTools = [])
21387
21430
  const granted = base ? [...base, ...extraTools] : extraTools.length ? [...allTools(), ...extraTools] : null;
21388
21431
  const tools = granted ? granted.map((t) => ({ type: "function", function: { name: t.name, description: t.description, parameters: t.parameters } })) : toolSchemas();
21389
21432
  const lookup = (name) => granted ? granted.find((t) => t.name === name) : getTool(name);
21390
- const turnCtx = buildTurnContext(task, ctx.cwd, {});
21433
+ const env = `Working directory: ${ctx.cwd}
21434
+ Platform: ${process.platform}
21435
+ Today: ${new Date().toISOString().slice(0, 10)}`;
21436
+ const projSlice = loadProjectContext(ctx.cwd).slice(0, 4000);
21437
+ const turnCtx = (buildTurnContext(task, ctx.cwd, {}) || "").slice(0, 4000);
21438
+ const sys = [agent.systemPrompt, env, projSlice, turnCtx].filter(Boolean).join(`
21439
+
21440
+ `);
21391
21441
  const history = [
21392
- { role: "system", content: agent.systemPrompt + loadProjectContext(ctx.cwd) },
21393
- ...turnCtx ? [{ role: "system", content: turnCtx }] : [],
21442
+ { role: "system", content: sys },
21394
21443
  { role: "user", content: task }
21395
21444
  ];
21396
21445
  let feeGrains = 0;
21397
21446
  const routed = cfg2.autoRoute ? route(task)?.model : undefined;
21398
21447
  const subModel = cfg2.subagentModel || routed || agent.model || cfg2.model;
21448
+ onProgress?.(` ↳ ${agent.name} on ${subModel}`);
21449
+ const RESULT_CAP = Number(process.env.MODELCODE_MAX_TOOL_RESULT || "40000");
21450
+ const capResult = (s) => s.length <= RESULT_CAP ? s : `${s.slice(0, Math.floor(RESULT_CAP * 0.7))}
21451
+
21452
+ …[truncated ${s.length - RESULT_CAP} chars]…
21453
+
21454
+ ${s.slice(-Math.floor(RESULT_CAP * 0.2))}`;
21399
21455
  const finish = (text) => {
21400
21456
  fireEvent("subagentStop", ctx.cwd, { AGENT: agent.name, RESULT: text.slice(0, 4000) }).catch(() => {});
21401
21457
  return { text, feeGrains };
21402
21458
  };
21403
21459
  for (let step = 0;step < MAX_STEPS; step++) {
21404
- const res = await chat2(cfg2, history, tools, () => {}, subModel, 60000);
21460
+ let res;
21461
+ try {
21462
+ res = await chat2(cfg2, history, tools, () => {}, subModel, 120000);
21463
+ } catch (e) {
21464
+ return finish(`error: ${agent.name} (${subModel}) — ${e.message}`);
21465
+ }
21405
21466
  feeGrains += res.feeGrains;
21406
21467
  history.push({ role: "assistant", content: res.content || null, ...res.toolCalls.length ? { tool_calls: res.toolCalls } : {} });
21407
21468
  if (!res.toolCalls.length)
@@ -21423,7 +21484,7 @@ async function runSubagent(cfg2, agent, task, ctx, onProgress, extraTools = [])
21423
21484
  result = `error: ${e.message}`;
21424
21485
  }
21425
21486
  }
21426
- history.push({ role: "tool", tool_call_id: call.id, name: call.function.name, content: result });
21487
+ history.push({ role: "tool", tool_call_id: call.id, name: call.function.name, content: capResult(result) });
21427
21488
  }
21428
21489
  }
21429
21490
  return finish("(subagent hit the step limit without finishing)");
@@ -24254,4 +24315,4 @@ async function decryptWallet(enc, password, network = MAINNET) {
24254
24315
  }
24255
24316
  return w;
24256
24317
  }
24257
- export { exports_external, register, getTool, toolSchemas, renderDiff, shellInvocation, globMatch, runProc, which, spawnProc, globalDir, loadConfig, saveConfig, rememberFact, allFacts, searchFacts, deleteFact, recordTurn, searchSessions, parseFrontmatter, loadSubagents, chat2 as chat, route, loadProjectContext, addEntity, addRelation, query, indexFile, indexCodebaseIncremental, indexCodebase, loadSkills, buildTurnContext, preToolUse, postToolUse, userPromptSubmit, fireEvent, runSubagent, runTeam, isImagePath, imageToDataUrl, estimateTokens, contextStatus, compactionThreshold, findCompactionCut, newTurn, snapshot, undo, depth, MUTATING_TOOLS, isProtectedBuiltin, recordUse, recordPatch, recordCreate, getUsage, allUsage, activityCount, analyzeImpact, impactSummary, lspFor, closeAllLsp, COLUMNS, addCard, moveCard, removeCard, renderBoard, addJob, listJobs, removeJob, startScheduler, MAINNET, generateMnemonic2 as generateMnemonic, validateMnemonic2 as validateMnemonic, walletFromMnemonic, importWalletHex, encryptWallet, decryptWallet, createTransfer };
24318
+ export { exports_external, register, getTool, toolSchemas, renderDiff, shellInvocation, shellName, globMatch, runProc, which, spawnProc, globalDir, loadConfig, saveConfig, rememberFact, allFacts, searchFacts, deleteFact, recordTurn, searchSessions, parseFrontmatter, loadSubagents, chat2 as chat, route, loadProjectContext, addEntity, addRelation, query, indexFile, indexCodebaseIncremental, indexCodebase, loadSkills, buildTurnContext, preToolUse, postToolUse, userPromptSubmit, fireEvent, runSubagent, runTeam, isImagePath, imageToDataUrl, estimateTokens, contextStatus, compactionThreshold, findCompactionCut, newTurn, snapshot, undo, depth, MUTATING_TOOLS, isProtectedBuiltin, recordUse, recordPatch, recordCreate, getUsage, allUsage, activityCount, analyzeImpact, impactSummary, lspFor, closeAllLsp, COLUMNS, addCard, moveCard, removeCard, renderBoard, addJob, listJobs, removeJob, startScheduler, MAINNET, generateMnemonic2 as generateMnemonic, validateMnemonic2 as validateMnemonic, walletFromMnemonic, importWalletHex, encryptWallet, decryptWallet, createTransfer };
@@ -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
  ] },
@@ -21566,6 +21568,54 @@ function randomVerb() {
21566
21568
  return SPINNER_VERBS[Math.floor(Math.random() * SPINNER_VERBS.length)];
21567
21569
  }
21568
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
+
21569
21619
  // src/ui/themes.ts
21570
21620
  var THEMES = {
21571
21621
  amber: { accent: "yellow", user: "green", tool: "cyan", dim: "gray", ok: "green", warn: "yellow" },
@@ -21703,8 +21753,6 @@ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct,
21703
21753
  onInterrupt();
21704
21754
  return;
21705
21755
  }
21706
- if (busy)
21707
- return;
21708
21756
  if (key.upArrow) {
21709
21757
  if (history.length) {
21710
21758
  const i = histIdx < 0 ? history.length - 1 : Math.max(0, histIdx - 1);
@@ -21752,6 +21800,7 @@ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct,
21752
21800
  const renderLine = (l, i) => {
21753
21801
  if (l.kind === "user")
21754
21802
  return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21803
+ flexDirection: "column",
21755
21804
  marginTop: 1,
21756
21805
  children: [
21757
21806
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Chip, {
@@ -21759,27 +21808,23 @@ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct,
21759
21808
  color: t.user
21760
21809
  }, undefined, false, undefined, this),
21761
21810
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21762
- children: [
21763
- " ",
21764
- l.text
21765
- ]
21766
- }, undefined, true, undefined, this)
21811
+ children: l.text
21812
+ }, undefined, false, undefined, this)
21767
21813
  ]
21768
21814
  }, i, true, undefined, this);
21769
21815
  if (l.kind === "assistant")
21770
21816
  return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21817
+ flexDirection: "column",
21771
21818
  marginTop: 1,
21772
21819
  children: [
21773
- /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Chip, {
21774
- label: "◆",
21775
- color: t.accent
21820
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21821
+ color: t.accent,
21822
+ bold: true,
21823
+ children: "◆ modelcode"
21776
21824
  }, undefined, false, undefined, this),
21777
21825
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21778
- children: [
21779
- " ",
21780
- l.text
21781
- ]
21782
- }, undefined, true, undefined, this)
21826
+ children: formatMarkdown(l.text)
21827
+ }, undefined, false, undefined, this)
21783
21828
  ]
21784
21829
  }, i, true, undefined, this);
21785
21830
  if (l.kind === "tool")
@@ -21792,6 +21837,7 @@ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct,
21792
21837
  }, i, true, undefined, this);
21793
21838
  if (l.kind === "error")
21794
21839
  return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21840
+ flexDirection: "column",
21795
21841
  marginTop: 1,
21796
21842
  children: [
21797
21843
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Chip, {
@@ -21800,11 +21846,8 @@ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct,
21800
21846
  }, undefined, false, undefined, this),
21801
21847
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21802
21848
  color: t.warn,
21803
- children: [
21804
- " ",
21805
- l.text
21806
- ]
21807
- }, undefined, true, undefined, this)
21849
+ children: l.text
21850
+ }, undefined, false, undefined, this)
21808
21851
  ]
21809
21852
  }, i, true, undefined, this);
21810
21853
  return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
@@ -21846,18 +21889,17 @@ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct,
21846
21889
  flexDirection: "column",
21847
21890
  children: [
21848
21891
  streaming ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21892
+ flexDirection: "column",
21849
21893
  marginTop: 1,
21850
21894
  children: [
21851
- /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Chip, {
21852
- label: "◆",
21853
- color: t.accent
21895
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21896
+ color: t.accent,
21897
+ bold: true,
21898
+ children: "◆ modelcode"
21854
21899
  }, undefined, false, undefined, this),
21855
21900
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21856
- children: [
21857
- " ",
21858
- streaming
21859
- ]
21860
- }, undefined, true, undefined, this)
21901
+ children: formatMarkdown(streaming)
21902
+ }, undefined, false, undefined, this)
21861
21903
  ]
21862
21904
  }, undefined, true, undefined, this) : null,
21863
21905
  toolStream ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
@@ -21931,26 +21973,27 @@ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct,
21931
21973
  ]
21932
21974
  }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21933
21975
  borderStyle: "round",
21934
- borderColor: busy ? t.dim : t.user,
21976
+ borderColor: t.user,
21935
21977
  paddingX: 1,
21936
21978
  marginTop: 1,
21937
21979
  children: [
21938
21980
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21939
- color: busy ? t.dim : t.user,
21940
- children: [
21941
- busy ? "…" : "›",
21942
- " "
21943
- ]
21944
- }, undefined, true, undefined, this),
21981
+ color: t.user,
21982
+ children: "› "
21983
+ }, undefined, false, undefined, this),
21945
21984
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21946
- children: input || (busy ? "" : "")
21985
+ children: input
21947
21986
  }, undefined, false, undefined, this),
21948
- !busy ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21987
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21949
21988
  color: t.accent,
21950
21989
  children: "▋"
21951
- }, undefined, false, undefined, this) : null
21990
+ }, undefined, false, undefined, this)
21952
21991
  ]
21953
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,
21954
21997
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21955
21998
  justifyContent: "space-between",
21956
21999
  paddingX: 1,
@@ -22051,6 +22094,16 @@ class Agent {
22051
22094
  note(text) {
22052
22095
  this.history.push({ role: "system", content: `User steering note (apply going forward): ${text}` });
22053
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
+ }
22054
22107
  get messages() {
22055
22108
  return [...this.history];
22056
22109
  }
@@ -22236,7 +22289,7 @@ ${tail2}`;
22236
22289
  } else {
22237
22290
  if (MUTATING_TOOLS.has(tool.name) && typeof args.path === "string")
22238
22291
  snapshot(this.ctx.cwd, args.path);
22239
- 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) };
22240
22293
  try {
22241
22294
  result2 = await tool.run(args, runCtx);
22242
22295
  } catch (e) {
@@ -22888,7 +22941,9 @@ ${typeof m.content === "string" ? m.content : JSON.stringify(m.content)}`).join(
22888
22941
  `) : "no saved sessions" };
22889
22942
  }
22890
22943
  case "/btw":
22891
- 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" };
22892
22947
  case "/web":
22893
22948
  case "/fetch":
22894
22949
  case "/browse":
@@ -22947,7 +23002,7 @@ function inside(cwd2, p) {
22947
23002
  }
22948
23003
  var bash = {
22949
23004
  name: "bash",
22950
- 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." : ""}`,
22951
23006
  permission: "ask",
22952
23007
  parameters: {
22953
23008
  type: "object",
@@ -23137,7 +23192,9 @@ function registerAgentTool(cfg, onProgress) {
23137
23192
  if (!agent)
23138
23193
  return `error: unknown subagent '${args.subagent_type}'. available: ${names.join(", ")}`;
23139
23194
  const { text, feeGrains } = await runSubagent(cfg, agent, String(args.prompt ?? ""), ctx, onProgress);
23140
- 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]
23141
23198
  ${text}`;
23142
23199
  }
23143
23200
  };
@@ -23158,7 +23215,8 @@ ${text}`;
23158
23215
  async run(args, ctx) {
23159
23216
  const members = Array.isArray(args.members) ? args.members.map(String) : [];
23160
23217
  const { transcript, feeGrains } = await runTeam(cfg, members, String(args.prompt ?? ""), ctx, Number(args.rounds) || 2, onProgress);
23161
- return `[team · ${(feeGrains / 1e8).toFixed(6)} MDL · free/uncapped]
23218
+ ctx.onCost?.(feeGrains);
23219
+ return `[team · ${(feeGrains / 1e8).toFixed(6)} MDL]
23162
23220
  ${transcript}`;
23163
23221
  }
23164
23222
  };
@@ -23780,6 +23838,8 @@ function Root({ cfg, resume, mcpCount }) {
23780
23838
  const mdlUsd = import_react35.useRef(null);
23781
23839
  const streamBuf = import_react35.useRef("");
23782
23840
  const tokensRef = import_react35.useRef(0);
23841
+ const busyRef = import_react35.useRef(false);
23842
+ const queueRef = import_react35.useRef([]);
23783
23843
  const confirmResolve = import_react35.useRef(null);
23784
23844
  const sessionIdRef = import_react35.useRef(newSessionId());
23785
23845
  const sessionMdlRef = import_react35.useRef(0);
@@ -23877,6 +23937,7 @@ function Root({ cfg, resume, mcpCount }) {
23877
23937
  tokensRef.current = 0;
23878
23938
  setTurnTokens(0);
23879
23939
  setTurnStart(Date.now());
23940
+ busyRef.current = true;
23880
23941
  setBusy(true);
23881
23942
  try {
23882
23943
  await agent.send(expandFileMentions(prompt, cwd2), imageMentions(prompt, cwd2));
@@ -23897,18 +23958,20 @@ function Root({ cfg, resume, mcpCount }) {
23897
23958
  setStreaming("");
23898
23959
  setToolStream("");
23899
23960
  streamBuf.current = "";
23961
+ busyRef.current = false;
23900
23962
  setBusy(false);
23901
23963
  setContextPct(agent.contextStatus().pct);
23902
23964
  setMode(agent.mode);
23903
23965
  refreshWallet();
23966
+ const next = queueRef.current.shift();
23967
+ if (next !== undefined)
23968
+ runInput(next);
23904
23969
  }
23905
23970
  }, [agent, add2, refreshWallet, cfg, cwd2, flushAssistant]);
23906
23971
  const onInterrupt = import_react35.useCallback(() => {
23907
23972
  agent.abort();
23908
23973
  }, [agent]);
23909
- const onSubmit = import_react35.useCallback(async (input) => {
23910
- add2({ kind: "user", text: input });
23911
- setHistory((h) => (h[h.length - 1] === input ? h : [...h, input]).slice(-100));
23974
+ const runInput = import_react35.useCallback(async (input) => {
23912
23975
  if (input.startsWith("/")) {
23913
23976
  const r = await handleSlash(input, { agent, cfg, cwd: cwd2, sessionMdl: sessionMdlRef.current, mcpCount: mcpCountRef.current });
23914
23977
  if (r) {
@@ -23933,6 +23996,28 @@ function Root({ cfg, resume, mcpCount }) {
23933
23996
  setMode(agent.mode);
23934
23997
  if (r.output)
23935
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
+ }
23936
24021
  if (r.agentPrompt)
23937
24022
  await runTurn(r.agentPrompt);
23938
24023
  return;
@@ -23940,6 +24025,17 @@ function Root({ cfg, resume, mcpCount }) {
23940
24025
  }
23941
24026
  await runTurn(input);
23942
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]);
23943
24039
  return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(App2, {
23944
24040
  theme: cfg.theme,
23945
24041
  model: cfg.model,
@@ -23962,12 +24058,25 @@ function Root({ cfg, resume, mcpCount }) {
23962
24058
  onExit
23963
24059
  }, undefined, false, undefined, this);
23964
24060
  }
23965
- async function runTui(cfg, resume = "none") {
23966
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
23967
- process.stderr.write(`modelcode: the interactive TUI needs a terminal (TTY). For non-interactive use, run: modelcode -p "<prompt>"
23968
- `);
23969
- 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;
23970
24076
  }
24077
+ }
24078
+ async function runTui(cfg, resume = "none") {
24079
+ const renderStdin = getRenderStdin();
23971
24080
  registerCoreTools();
23972
24081
  registerMemoryTools();
23973
24082
  registerAgentTool(cfg, (line) => emitProgress(line));
@@ -23975,7 +24084,12 @@ async function runTui(cfg, resume = "none") {
23975
24084
  registerMoreTools();
23976
24085
  registerBrowserTool();
23977
24086
  registerTaskTools();
23978
- 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(() => {});
23979
24093
  startScheduler(async (prompt, jobCwd) => {
23980
24094
  emitProgress(`⏰ running scheduled job…`);
23981
24095
  const a = new Agent(cfg, { cwd: jobCwd }, { onAssistantDelta: () => {}, onToolStart: () => {}, onToolResult: () => {}, onCost: () => {}, confirm: async () => true });
@@ -23984,11 +24098,26 @@ async function runTui(cfg, resume = "none") {
23984
24098
  });
23985
24099
  pullTeamMemory().catch(() => {});
23986
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
+ });
23987
24116
  render_default(/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Root, {
23988
24117
  cfg,
23989
24118
  resume,
23990
24119
  mcpCount
23991
- }, undefined, false, undefined, this));
24120
+ }, undefined, false, undefined, this), { exitOnCtrlC: false, ...renderStdin ? { stdin: renderStdin } : {} });
23992
24121
  }
23993
24122
  export {
23994
24123
  runTui
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modeloslab/modelcode",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "modelOS-native AI coding agent CLI — remembers like Hermes, codes like Claude Code, runs on modelOS (pay-per-use in MDL, no rate limits). Knowledge-graph memory, subagents, MCP, vision, spend caps.",
5
5
  "type": "module",
6
6
  "bin": {