@modeloslab/modelcode 0.1.3 → 0.1.5

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-t9m4v4fc.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-rxaw95g7.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++) {
@@ -8048,15 +8073,40 @@ function extsFromGlob(glob) {
8048
8073
  function runProc(cmd, opts = {}) {
8049
8074
  return new Promise((resolve) => {
8050
8075
  const [bin, ...args] = cmd;
8051
- const so = { cwd: opts.cwd, env: { ...process.env, ...opts.env }, stdio: ["ignore", "pipe", "pipe"] };
8076
+ const so = { cwd: opts.cwd, env: { ...process.env, ...opts.env }, stdio: ["ignore", "pipe", "pipe"], detached: !isWin };
8052
8077
  const p = spawn(bin, args, so);
8053
- let stdout = "", stderr = "";
8078
+ let stdout = "", stderr = "", done = false;
8054
8079
  const dOut = new StringDecoder("utf8"), dErr = new StringDecoder("utf8");
8080
+ const finish = (code) => {
8081
+ if (done)
8082
+ return;
8083
+ done = true;
8084
+ if (killer)
8085
+ clearTimeout(killer);
8086
+ resolve({ code, stdout: stdout + dOut.end(), stderr: stderr + dErr.end() });
8087
+ };
8088
+ const killTree = (signal) => {
8089
+ try {
8090
+ if (isWin)
8091
+ spawnSync("taskkill", ["/pid", String(p.pid), "/T", "/F"], { stdio: "ignore" });
8092
+ else if (p.pid)
8093
+ process.kill(-p.pid, signal);
8094
+ else
8095
+ p.kill(signal);
8096
+ } catch {
8097
+ try {
8098
+ p.kill(signal);
8099
+ } catch {}
8100
+ }
8101
+ };
8055
8102
  let killer = null;
8056
8103
  if (opts.timeoutMs)
8057
8104
  killer = setTimeout(() => {
8058
- p.kill("SIGTERM");
8059
- setTimeout(() => p.kill("SIGKILL"), 2000);
8105
+ killTree("SIGTERM");
8106
+ setTimeout(() => {
8107
+ killTree("SIGKILL");
8108
+ finish(124);
8109
+ }, 2000);
8060
8110
  }, opts.timeoutMs);
8061
8111
  p.stdout?.on("data", (d) => {
8062
8112
  const s = dOut.write(d);
@@ -8068,15 +8118,10 @@ function runProc(cmd, opts = {}) {
8068
8118
  stderr += s;
8069
8119
  opts.onChunk?.(s, true);
8070
8120
  });
8071
- p.on("close", (code) => {
8072
- if (killer)
8073
- clearTimeout(killer);
8074
- resolve({ code: code ?? 0, stdout: stdout + dOut.end(), stderr: stderr + dErr.end() });
8075
- });
8121
+ p.on("close", (code) => finish(code ?? 0));
8076
8122
  p.on("error", (e) => {
8077
- if (killer)
8078
- clearTimeout(killer);
8079
- resolve({ code: 1, stdout, stderr: stderr + String(e) });
8123
+ stderr += String(e);
8124
+ finish(1);
8080
8125
  });
8081
8126
  });
8082
8127
  }
@@ -21025,6 +21070,7 @@ async function chat2(cfg, messages, tools, onDelta, modelOverride, timeoutMs = 1
21025
21070
  messages,
21026
21071
  max_tokens: cfg.maxTokens,
21027
21072
  stream: true,
21073
+ stream_options: { include_usage: true },
21028
21074
  ...tools.length ? { tools, tool_choice: "auto" } : {}
21029
21075
  };
21030
21076
  const attempt = async (live) => {
@@ -21032,7 +21078,13 @@ async function chat2(cfg, messages, tools, onDelta, modelOverride, timeoutMs = 1
21032
21078
  let content = "";
21033
21079
  let finishReason = null;
21034
21080
  let feeGrains = 0;
21081
+ let promptTokens = 0, completionTokens = 0;
21035
21082
  const calls = {};
21083
+ const readFee = (o) => {
21084
+ const x = o;
21085
+ const v = x?.x_modelos?.fee_grains ?? x?.fee_grains ?? x?.cost_grains;
21086
+ return v != null ? Number(v) || 0 : 0;
21087
+ };
21036
21088
  for await (const chunk of stream) {
21037
21089
  const choice = chunk.choices?.[0];
21038
21090
  const delta = choice?.delta;
@@ -21053,22 +21105,33 @@ async function chat2(cfg, messages, tools, onDelta, modelOverride, timeoutMs = 1
21053
21105
  }
21054
21106
  if (choice?.finish_reason)
21055
21107
  finishReason = choice.finish_reason;
21056
- const x = chunk.x_modelos;
21057
- if (x?.fee_grains)
21058
- feeGrains = Number(x.fee_grains) || feeGrains;
21108
+ feeGrains = readFee(chunk) || readFee(chunk.usage) || feeGrains;
21109
+ const usage = chunk.usage;
21110
+ if (usage) {
21111
+ promptTokens = usage.prompt_tokens ?? promptTokens;
21112
+ completionTokens = usage.completion_tokens ?? completionTokens;
21113
+ }
21059
21114
  }
21060
- return { content, toolCalls: Object.values(calls), finishReason, feeGrains };
21115
+ return { content, toolCalls: Object.values(calls), finishReason, feeGrains, promptTokens, completionTokens };
21061
21116
  };
21062
- try {
21063
- return await attempt(true);
21064
- } catch (e) {
21065
- await new Promise((r) => setTimeout(r, 1500));
21117
+ let lastErr;
21118
+ for (let i = 0;i < 3; i++) {
21066
21119
  try {
21067
21120
  return await attempt(true);
21068
- } catch {
21069
- throw e;
21121
+ } catch (e) {
21122
+ lastErr = e;
21123
+ const status = e.status;
21124
+ if (status && status >= 400 && status < 500)
21125
+ break;
21126
+ if (i < 2)
21127
+ await new Promise((r) => setTimeout(r, 1000 * (i + 1)));
21070
21128
  }
21071
21129
  }
21130
+ const err = lastErr;
21131
+ const reachable = err?.status !== undefined;
21132
+ if (!reachable)
21133
+ throw new Error(`inference API not reachable (${cfg.baseUrl}) — check your connection. ${err?.message ?? err?.code ?? ""}`.trim());
21134
+ throw new Error(`inference API error${err.status ? ` (HTTP ${err.status})` : ""}: ${err.message ?? "request rejected"}`);
21072
21135
  }
21073
21136
 
21074
21137
  // src/router/router.ts
@@ -21387,21 +21450,39 @@ async function runSubagent(cfg2, agent, task, ctx, onProgress, extraTools = [])
21387
21450
  const granted = base ? [...base, ...extraTools] : extraTools.length ? [...allTools(), ...extraTools] : null;
21388
21451
  const tools = granted ? granted.map((t) => ({ type: "function", function: { name: t.name, description: t.description, parameters: t.parameters } })) : toolSchemas();
21389
21452
  const lookup = (name) => granted ? granted.find((t) => t.name === name) : getTool(name);
21390
- const turnCtx = buildTurnContext(task, ctx.cwd, {});
21453
+ const env = `Working directory: ${ctx.cwd}
21454
+ Platform: ${process.platform}
21455
+ Today: ${new Date().toISOString().slice(0, 10)}`;
21456
+ const projSlice = loadProjectContext(ctx.cwd).slice(0, 4000);
21457
+ const turnCtx = (buildTurnContext(task, ctx.cwd, {}) || "").slice(0, 4000);
21458
+ const sys = [agent.systemPrompt, env, projSlice, turnCtx].filter(Boolean).join(`
21459
+
21460
+ `);
21391
21461
  const history = [
21392
- { role: "system", content: agent.systemPrompt + loadProjectContext(ctx.cwd) },
21393
- ...turnCtx ? [{ role: "system", content: turnCtx }] : [],
21462
+ { role: "system", content: sys },
21394
21463
  { role: "user", content: task }
21395
21464
  ];
21396
21465
  let feeGrains = 0;
21397
21466
  const routed = cfg2.autoRoute ? route(task)?.model : undefined;
21398
21467
  const subModel = cfg2.subagentModel || routed || agent.model || cfg2.model;
21468
+ onProgress?.(` ↳ ${agent.name} on ${subModel}`);
21469
+ const RESULT_CAP = Number(process.env.MODELCODE_MAX_TOOL_RESULT || "40000");
21470
+ const capResult = (s) => s.length <= RESULT_CAP ? s : `${s.slice(0, Math.floor(RESULT_CAP * 0.7))}
21471
+
21472
+ …[truncated ${s.length - RESULT_CAP} chars]…
21473
+
21474
+ ${s.slice(-Math.floor(RESULT_CAP * 0.2))}`;
21399
21475
  const finish = (text) => {
21400
21476
  fireEvent("subagentStop", ctx.cwd, { AGENT: agent.name, RESULT: text.slice(0, 4000) }).catch(() => {});
21401
21477
  return { text, feeGrains };
21402
21478
  };
21403
21479
  for (let step = 0;step < MAX_STEPS; step++) {
21404
- const res = await chat2(cfg2, history, tools, () => {}, subModel, 60000);
21480
+ let res;
21481
+ try {
21482
+ res = await chat2(cfg2, history, tools, () => {}, subModel, 120000);
21483
+ } catch (e) {
21484
+ return finish(`error: ${agent.name} (${subModel}) — ${e.message}`);
21485
+ }
21405
21486
  feeGrains += res.feeGrains;
21406
21487
  history.push({ role: "assistant", content: res.content || null, ...res.toolCalls.length ? { tool_calls: res.toolCalls } : {} });
21407
21488
  if (!res.toolCalls.length)
@@ -21423,7 +21504,7 @@ async function runSubagent(cfg2, agent, task, ctx, onProgress, extraTools = [])
21423
21504
  result = `error: ${e.message}`;
21424
21505
  }
21425
21506
  }
21426
- history.push({ role: "tool", tool_call_id: call.id, name: call.function.name, content: result });
21507
+ history.push({ role: "tool", tool_call_id: call.id, name: call.function.name, content: capResult(result) });
21427
21508
  }
21428
21509
  }
21429
21510
  return finish("(subagent hit the step limit without finishing)");
@@ -24254,4 +24335,4 @@ async function decryptWallet(enc, password, network = MAINNET) {
24254
24335
  }
24255
24336
  return w;
24256
24337
  }
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 };
24338
+ 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-t9m4v4fc.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,39 +21808,60 @@ 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")
21786
21831
  return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21787
- color: t.tool,
21788
21832
  children: [
21789
- " ⚙ ",
21790
- l.text
21833
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21834
+ color: t.tool,
21835
+ children: "⏺ "
21836
+ }, undefined, false, undefined, this),
21837
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21838
+ children: l.text
21839
+ }, undefined, false, undefined, this)
21840
+ ]
21841
+ }, i, true, undefined, this);
21842
+ if (l.kind === "result")
21843
+ return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21844
+ flexDirection: "row",
21845
+ children: [
21846
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21847
+ color: t.dim,
21848
+ children: " ⎿ "
21849
+ }, undefined, false, undefined, this),
21850
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21851
+ flexShrink: 1,
21852
+ flexGrow: 1,
21853
+ children: /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21854
+ color: t.dim,
21855
+ children: l.text.split(`
21856
+ `).join(`
21857
+ `)
21858
+ }, undefined, false, undefined, this)
21859
+ }, undefined, false, undefined, this)
21791
21860
  ]
21792
21861
  }, i, true, undefined, this);
21793
21862
  if (l.kind === "error")
21794
21863
  return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21864
+ flexDirection: "column",
21795
21865
  marginTop: 1,
21796
21866
  children: [
21797
21867
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Chip, {
@@ -21800,11 +21870,8 @@ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct,
21800
21870
  }, undefined, false, undefined, this),
21801
21871
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21802
21872
  color: t.warn,
21803
- children: [
21804
- " ",
21805
- l.text
21806
- ]
21807
- }, undefined, true, undefined, this)
21873
+ children: l.text
21874
+ }, undefined, false, undefined, this)
21808
21875
  ]
21809
21876
  }, i, true, undefined, this);
21810
21877
  return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
@@ -21846,18 +21913,17 @@ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct,
21846
21913
  flexDirection: "column",
21847
21914
  children: [
21848
21915
  streaming ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21916
+ flexDirection: "column",
21849
21917
  marginTop: 1,
21850
21918
  children: [
21851
- /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Chip, {
21852
- label: "◆",
21853
- color: t.accent
21919
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21920
+ color: t.accent,
21921
+ bold: true,
21922
+ children: "◆ modelcode"
21854
21923
  }, undefined, false, undefined, this),
21855
21924
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21856
- children: [
21857
- " ",
21858
- streaming
21859
- ]
21860
- }, undefined, true, undefined, this)
21925
+ children: formatMarkdown(streaming)
21926
+ }, undefined, false, undefined, this)
21861
21927
  ]
21862
21928
  }, undefined, true, undefined, this) : null,
21863
21929
  toolStream ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
@@ -21931,26 +21997,27 @@ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct,
21931
21997
  ]
21932
21998
  }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21933
21999
  borderStyle: "round",
21934
- borderColor: busy ? t.dim : t.user,
22000
+ borderColor: t.user,
21935
22001
  paddingX: 1,
21936
22002
  marginTop: 1,
21937
22003
  children: [
21938
22004
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21939
- color: busy ? t.dim : t.user,
21940
- children: [
21941
- busy ? "…" : "›",
21942
- " "
21943
- ]
21944
- }, undefined, true, undefined, this),
22005
+ color: t.user,
22006
+ children: "› "
22007
+ }, undefined, false, undefined, this),
21945
22008
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21946
- children: input || (busy ? "" : "")
22009
+ children: input
21947
22010
  }, undefined, false, undefined, this),
21948
- !busy ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
22011
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21949
22012
  color: t.accent,
21950
22013
  children: "▋"
21951
- }, undefined, false, undefined, this) : null
22014
+ }, undefined, false, undefined, this)
21952
22015
  ]
21953
22016
  }, undefined, true, undefined, this),
22017
+ busy && input ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
22018
+ color: t.dim,
22019
+ children: " ↵ to queue this for after the current turn"
22020
+ }, undefined, false, undefined, this) : null,
21954
22021
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21955
22022
  justifyContent: "space-between",
21956
22023
  paddingX: 1,
@@ -22051,6 +22118,16 @@ class Agent {
22051
22118
  note(text) {
22052
22119
  this.history.push({ role: "system", content: `User steering note (apply going forward): ${text}` });
22053
22120
  }
22121
+ async sideQuestion(question, onDelta) {
22122
+ const forked = [
22123
+ ...this.history,
22124
+ { 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." },
22125
+ { role: "user", content: question }
22126
+ ];
22127
+ const res = await chat(this.cfg, forked, [], onDelta ?? (() => {}), this.modelFor(question));
22128
+ this.h.onCost(res.feeGrains);
22129
+ return res.content || "(no answer)";
22130
+ }
22054
22131
  get messages() {
22055
22132
  return [...this.history];
22056
22133
  }
@@ -22236,7 +22313,7 @@ ${tail2}`;
22236
22313
  } else {
22237
22314
  if (MUTATING_TOOLS.has(tool.name) && typeof args.path === "string")
22238
22315
  snapshot(this.ctx.cwd, args.path);
22239
- const runCtx = { ...this.ctx, onStream: (c2) => this.h.onToolStream?.(c2) };
22316
+ const runCtx = { ...this.ctx, onStream: (c2) => this.h.onToolStream?.(c2), onCost: (g) => this.h.onCost(g) };
22240
22317
  try {
22241
22318
  result2 = await tool.run(args, runCtx);
22242
22319
  } catch (e) {
@@ -22888,7 +22965,9 @@ ${typeof m.content === "string" ? m.content : JSON.stringify(m.content)}`).join(
22888
22965
  `) : "no saved sessions" };
22889
22966
  }
22890
22967
  case "/btw":
22891
- return arg ? { note: arg, output: "noted — will apply going forward" } : { output: "usage: /btw <steering note>" };
22968
+ return arg ? { sideQuestion: arg } : { output: "usage: /btw <quick question> — answered without changing the current task" };
22969
+ case "/steer":
22970
+ return arg ? { note: arg, output: "noted — will apply going forward" } : { output: "usage: /steer <note> — steer the ongoing task" };
22892
22971
  case "/web":
22893
22972
  case "/fetch":
22894
22973
  case "/browse":
@@ -22947,7 +23026,7 @@ function inside(cwd2, p) {
22947
23026
  }
22948
23027
  var bash = {
22949
23028
  name: "bash",
22950
- description: "Run a shell command in the working directory; returns combined stdout+stderr. Use for builds, tests, git, file ops.",
23029
+ 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
23030
  permission: "ask",
22952
23031
  parameters: {
22953
23032
  type: "object",
@@ -23137,7 +23216,9 @@ function registerAgentTool(cfg, onProgress) {
23137
23216
  if (!agent)
23138
23217
  return `error: unknown subagent '${args.subagent_type}'. available: ${names.join(", ")}`;
23139
23218
  const { text, feeGrains } = await runSubagent(cfg, agent, String(args.prompt ?? ""), ctx, onProgress);
23140
- return `[${agent.name} · ${(feeGrains / 1e8).toFixed(6)} MDL · free/uncapped]
23219
+ ctx.onCost?.(feeGrains);
23220
+ onProgress?.(` ↳ ${agent.name} done · ${(feeGrains / 1e8).toFixed(4)} MDL`);
23221
+ return `[${agent.name} · ${(feeGrains / 1e8).toFixed(6)} MDL]
23141
23222
  ${text}`;
23142
23223
  }
23143
23224
  };
@@ -23158,7 +23239,8 @@ ${text}`;
23158
23239
  async run(args, ctx) {
23159
23240
  const members = Array.isArray(args.members) ? args.members.map(String) : [];
23160
23241
  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]
23242
+ ctx.onCost?.(feeGrains);
23243
+ return `[team · ${(feeGrains / 1e8).toFixed(6)} MDL]
23162
23244
  ${transcript}`;
23163
23245
  }
23164
23246
  };
@@ -23761,6 +23843,26 @@ function imageMentions(line, cwd2) {
23761
23843
 
23762
23844
  // src/ui/tui.tsx
23763
23845
  var jsx_dev_runtime2 = __toESM(require_jsx_dev_runtime(), 1);
23846
+ function toolUseLabel(name, args) {
23847
+ const cap = (s, n = 160) => s.length > n ? s.slice(0, n).trim() + "…" : s;
23848
+ if (name === "bash")
23849
+ return cap(String(args.command ?? "").split(`
23850
+ `)[0] ?? "");
23851
+ if (name === "edit" || name === "write" || name === "read")
23852
+ return `${name} ${cap(String(args.path ?? ""), 80)}`;
23853
+ const keys2 = Object.entries(args).map(([k, v]) => `${k}=${cap(String(v), 40)}`).join(" ");
23854
+ return cap(`${name} ${keys2}`);
23855
+ }
23856
+ function capLines(s, max2) {
23857
+ const lines = s.replace(/\s+$/, "").split(`
23858
+ `);
23859
+ if (lines.length <= max2)
23860
+ return lines.join(`
23861
+ `);
23862
+ return lines.slice(0, max2).join(`
23863
+ `) + `
23864
+ … +${lines.length - max2} lines`;
23865
+ }
23764
23866
  function Root({ cfg, resume, mcpCount }) {
23765
23867
  const [lines, setLines] = import_react35.useState([
23766
23868
  { kind: "system", text: `ready · model ${cfg.model} — ask me anything, or /help` },
@@ -23779,7 +23881,11 @@ function Root({ cfg, resume, mcpCount }) {
23779
23881
  const [turnTokens, setTurnTokens] = import_react35.useState(0);
23780
23882
  const mdlUsd = import_react35.useRef(null);
23781
23883
  const streamBuf = import_react35.useRef("");
23884
+ const toolStreamRef = import_react35.useRef("");
23782
23885
  const tokensRef = import_react35.useRef(0);
23886
+ const pumpRef = import_react35.useRef(null);
23887
+ const busyRef = import_react35.useRef(false);
23888
+ const queueRef = import_react35.useRef([]);
23783
23889
  const confirmResolve = import_react35.useRef(null);
23784
23890
  const sessionIdRef = import_react35.useRef(newSessionId());
23785
23891
  const sessionMdlRef = import_react35.useRef(0);
@@ -23812,20 +23918,22 @@ function Root({ cfg, resume, mcpCount }) {
23812
23918
  const agent = import_react35.useRef(new Agent(cfg, { cwd: cwd2 }, {
23813
23919
  onAssistantDelta: (txt) => {
23814
23920
  streamBuf.current += txt;
23815
- setStreaming(streamBuf.current);
23816
23921
  tokensRef.current += Math.max(1, Math.round(txt.length / 4));
23817
- setTurnTokens(tokensRef.current);
23818
23922
  },
23819
23923
  onToolStart: (name, args) => {
23820
23924
  flushAssistant();
23925
+ toolStreamRef.current = "";
23821
23926
  setToolStream("");
23822
- add2({ kind: "tool", text: `${name} ${JSON.stringify(args).slice(0, 80)}` });
23927
+ add2({ kind: "tool", text: toolUseLabel(name, args) });
23823
23928
  },
23824
23929
  onToolResult: (_n, r) => {
23930
+ toolStreamRef.current = "";
23825
23931
  setToolStream("");
23826
- add2({ kind: "system", text: r.slice(0, 300) });
23932
+ add2({ kind: "result", text: capLines(r, 8) });
23933
+ },
23934
+ onToolStream: (chunk2) => {
23935
+ toolStreamRef.current = (toolStreamRef.current + chunk2).slice(-2000);
23827
23936
  },
23828
- onToolStream: (chunk2) => setToolStream((s) => (s + chunk2).slice(-2000)),
23829
23937
  onCost: (g) => setSessionMdl((s) => {
23830
23938
  const v = s + g / 1e8;
23831
23939
  sessionMdlRef.current = v;
@@ -23872,12 +23980,21 @@ function Root({ cfg, resume, mcpCount }) {
23872
23980
  return;
23873
23981
  }
23874
23982
  streamBuf.current = "";
23983
+ toolStreamRef.current = "";
23875
23984
  setStreaming("");
23876
23985
  setToolStream("");
23877
23986
  tokensRef.current = 0;
23878
23987
  setTurnTokens(0);
23879
23988
  setTurnStart(Date.now());
23989
+ busyRef.current = true;
23880
23990
  setBusy(true);
23991
+ if (pumpRef.current)
23992
+ clearInterval(pumpRef.current);
23993
+ pumpRef.current = setInterval(() => {
23994
+ setStreaming(streamBuf.current);
23995
+ setToolStream(toolStreamRef.current);
23996
+ setTurnTokens(tokensRef.current);
23997
+ }, 80);
23881
23998
  try {
23882
23999
  await agent.send(expandFileMentions(prompt, cwd2), imageMentions(prompt, cwd2));
23883
24000
  flushAssistant();
@@ -23894,21 +24011,28 @@ function Root({ cfg, resume, mcpCount }) {
23894
24011
  flushAssistant();
23895
24012
  add2({ kind: "error", text: e.message });
23896
24013
  } finally {
24014
+ if (pumpRef.current) {
24015
+ clearInterval(pumpRef.current);
24016
+ pumpRef.current = null;
24017
+ }
23897
24018
  setStreaming("");
23898
24019
  setToolStream("");
23899
24020
  streamBuf.current = "";
24021
+ toolStreamRef.current = "";
24022
+ busyRef.current = false;
23900
24023
  setBusy(false);
23901
24024
  setContextPct(agent.contextStatus().pct);
23902
24025
  setMode(agent.mode);
23903
24026
  refreshWallet();
24027
+ const next = queueRef.current.shift();
24028
+ if (next !== undefined)
24029
+ runInput(next);
23904
24030
  }
23905
24031
  }, [agent, add2, refreshWallet, cfg, cwd2, flushAssistant]);
23906
24032
  const onInterrupt = import_react35.useCallback(() => {
23907
24033
  agent.abort();
23908
24034
  }, [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));
24035
+ const runInput = import_react35.useCallback(async (input) => {
23912
24036
  if (input.startsWith("/")) {
23913
24037
  const r = await handleSlash(input, { agent, cfg, cwd: cwd2, sessionMdl: sessionMdlRef.current, mcpCount: mcpCountRef.current });
23914
24038
  if (r) {
@@ -23933,6 +24057,28 @@ function Root({ cfg, resume, mcpCount }) {
23933
24057
  setMode(agent.mode);
23934
24058
  if (r.output)
23935
24059
  add2({ kind: "system", text: r.output });
24060
+ if (r.sideQuestion) {
24061
+ busyRef.current = true;
24062
+ setBusy(true);
24063
+ setTurnStart(Date.now());
24064
+ streamBuf.current = "";
24065
+ setStreaming("");
24066
+ try {
24067
+ const ans = await agent.sideQuestion(r.sideQuestion, (t) => {
24068
+ streamBuf.current += t;
24069
+ setStreaming(streamBuf.current);
24070
+ });
24071
+ add2({ kind: "system", text: `btw › ${ans}` });
24072
+ } catch (e) {
24073
+ add2({ kind: "error", text: e.message });
24074
+ } finally {
24075
+ streamBuf.current = "";
24076
+ setStreaming("");
24077
+ busyRef.current = false;
24078
+ setBusy(false);
24079
+ refreshWallet();
24080
+ }
24081
+ }
23936
24082
  if (r.agentPrompt)
23937
24083
  await runTurn(r.agentPrompt);
23938
24084
  return;
@@ -23940,6 +24086,17 @@ function Root({ cfg, resume, mcpCount }) {
23940
24086
  }
23941
24087
  await runTurn(input);
23942
24088
  }, [agent, add2, cfg, cwd2, onExit, runTurn]);
24089
+ const onSubmit = import_react35.useCallback((input) => {
24090
+ setHistory((h) => (h[h.length - 1] === input ? h : [...h, input]).slice(-100));
24091
+ if (busyRef.current) {
24092
+ queueRef.current.push(input);
24093
+ add2({ kind: "user", text: input });
24094
+ add2({ kind: "system", text: "↳ queued (runs after the current turn)" });
24095
+ return;
24096
+ }
24097
+ add2({ kind: "user", text: input });
24098
+ runInput(input);
24099
+ }, [add2, runInput]);
23943
24100
  return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(App2, {
23944
24101
  theme: cfg.theme,
23945
24102
  model: cfg.model,
@@ -23962,12 +24119,25 @@ function Root({ cfg, resume, mcpCount }) {
23962
24119
  onExit
23963
24120
  }, undefined, false, undefined, this);
23964
24121
  }
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);
24122
+ function getRenderStdin() {
24123
+ if (process.stdin.isTTY)
24124
+ return;
24125
+ if (process.env.CI)
24126
+ return;
24127
+ if (process.platform === "win32")
24128
+ return;
24129
+ try {
24130
+ const { openSync } = __require("fs");
24131
+ const { ReadStream } = __require("tty");
24132
+ const s = new ReadStream(openSync("/dev/tty", "r"));
24133
+ s.isTTY = true;
24134
+ return s;
24135
+ } catch {
24136
+ return;
23970
24137
  }
24138
+ }
24139
+ async function runTui(cfg, resume = "none") {
24140
+ const renderStdin = getRenderStdin();
23971
24141
  registerCoreTools();
23972
24142
  registerMemoryTools();
23973
24143
  registerAgentTool(cfg, (line) => emitProgress(line));
@@ -23975,7 +24145,12 @@ async function runTui(cfg, resume = "none") {
23975
24145
  registerMoreTools();
23976
24146
  registerBrowserTool();
23977
24147
  registerTaskTools();
23978
- const mcpCount = await registerMcpTools(process.cwd()).catch(() => 0);
24148
+ let mcpCount = 0;
24149
+ registerMcpTools(process.cwd()).then((n) => {
24150
+ mcpCount = n;
24151
+ if (n)
24152
+ emitProgress(`✓ ${n} MCP tool(s) connected`);
24153
+ }).catch(() => {});
23979
24154
  startScheduler(async (prompt, jobCwd) => {
23980
24155
  emitProgress(`⏰ running scheduled job…`);
23981
24156
  const a = new Agent(cfg, { cwd: jobCwd }, { onAssistantDelta: () => {}, onToolStart: () => {}, onToolResult: () => {}, onCost: () => {}, confirm: async () => true });
@@ -23984,11 +24159,26 @@ async function runTui(cfg, resume = "none") {
23984
24159
  });
23985
24160
  pullTeamMemory().catch(() => {});
23986
24161
  indexCodebaseIncremental(process.cwd()).catch(() => {});
24162
+ const restore = () => {
24163
+ try {
24164
+ if (process.stdin.isTTY)
24165
+ process.stdin.setRawMode(false);
24166
+ } catch {}
24167
+ };
24168
+ process.on("exit", restore);
24169
+ process.on("SIGINT", () => {
24170
+ restore();
24171
+ process.exit(0);
24172
+ });
24173
+ process.on("SIGTERM", () => {
24174
+ restore();
24175
+ process.exit(0);
24176
+ });
23987
24177
  render_default(/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Root, {
23988
24178
  cfg,
23989
24179
  resume,
23990
24180
  mcpCount
23991
- }, undefined, false, undefined, this));
24181
+ }, undefined, false, undefined, this), { exitOnCtrlC: false, ...renderStdin ? { stdin: renderStdin } : {} });
23992
24182
  }
23993
24183
  export {
23994
24184
  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.5",
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": {