@modeloslab/modelcode 0.1.4 → 0.1.8

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
@@ -48,11 +48,12 @@ import {
48
48
  removeCard,
49
49
  removeJob,
50
50
  renderBoard,
51
- renderDiff,
51
+ renderDiffPlain,
52
52
  route,
53
53
  runProc,
54
54
  runSubagent,
55
55
  runTeam,
56
+ saveConfig,
56
57
  searchFacts,
57
58
  searchSessions,
58
59
  shellInvocation,
@@ -64,7 +65,7 @@ import {
64
65
  validateMnemonic,
65
66
  walletFromMnemonic,
66
67
  which
67
- } from "./main-wr686fnv.mjs";
68
+ } from "./main-pj08pbgh.mjs";
68
69
  import"./main-p2xnn95s.mjs";
69
70
  import {
70
71
  __require
@@ -93,6 +94,7 @@ var ConfigSchema = exports_external.object({
93
94
  autoSummary: exports_external.boolean().default(true),
94
95
  theme: exports_external.enum(["amber", "mono", "ocean"]).default("amber"),
95
96
  teamMemoryUrl: exports_external.string().optional(),
97
+ autoFixLaunchErrors: exports_external.boolean().default(false),
96
98
  spendCapMdl: exports_external.number().default(0),
97
99
  providers: exports_external.record(exports_external.object({ baseUrl: exports_external.string(), apiKey: exports_external.string().optional(), model: exports_external.string().optional() })).default({})
98
100
  });
@@ -119,7 +121,7 @@ function loadConfig(cwd = process.cwd()) {
119
121
  raw.apiKey = envKey;
120
122
  return ConfigSchema.parse(raw);
121
123
  }
122
- function saveConfig(patch) {
124
+ function saveConfig2(patch) {
123
125
  const p = join(globalDir2(), "config.json");
124
126
  let cur = {};
125
127
  try {
@@ -155,7 +157,7 @@ var bash = {
155
157
  async run(args, ctx) {
156
158
  const cmd = String(args.command ?? "");
157
159
  const timeout = Number(args.timeout_ms) || 120000;
158
- const r = await runProc(shellInvocation(cmd), { cwd: ctx.cwd, timeoutMs: timeout, onChunk: (s) => ctx.onStream?.(s) });
160
+ const r = await runProc(shellInvocation(cmd), { cwd: ctx.cwd, timeoutMs: timeout, signal: ctx.signal, onChunk: (s) => ctx.onStream?.(s) });
159
161
  return `exit ${r.code}
160
162
  ${(r.stdout + (r.stderr ? `
161
163
  [stderr]
@@ -226,7 +228,7 @@ var edit = {
226
228
  const updated = args.replace_all ? src.split(oldS).join(newS) : src.replace(oldS, newS);
227
229
  writeFileSync2(p, updated);
228
230
  return `edited ${args.path} (${count} replacement${count === 1 ? "" : "s"})
229
- ${renderDiff(oldS, newS, String(args.path))}`;
231
+ ${renderDiffPlain(oldS, newS)}`;
230
232
  }
231
233
  };
232
234
  var glob = {
@@ -379,6 +381,15 @@ Tool discipline (important): call each tool ONCE for a given action — never re
379
381
  A tool result is authoritative: once a write/edit succeeds, trust it and move on. Do NOT claim a task is
380
382
  done in the same message where you call the tool to do it — call the tool first, then confirm only after
381
383
  you see its result. Keep confirmations to one short sentence.`;
384
+ var LAUNCH_RE = /\b(npm|pnpm|yarn|bun)\s+(run\s+)?(dev|start|serve)\b|\b(vite|next|nuxt|astro|remix|webpack[\- ]dev[\- ]server|nodemon|tsx\s+watch|ts-node-dev)\b|\b(uvicorn|gunicorn|flask\s+run|manage\.py\s+runserver|rails\s+s(erver)?|go\s+run|cargo\s+run|dotnet\s+run|php\s+artisan\s+serve|air|nest\s+start)\b/i;
385
+ function isLaunchCommand(cmd) {
386
+ return LAUNCH_RE.test(cmd);
387
+ }
388
+ function looksLikeLaunchError(result) {
389
+ if (!/^exit\s+([1-9]\d*|124)\b/m.test(result))
390
+ return false;
391
+ return /\b(error|exception|cannot find module|module not found|eaddrinuse|syntaxerror|typeerror|referenceerror|failed to compile|command not found|traceback|panic:|address already in use|unexpected token|ENOENT|MODULE_NOT_FOUND)\b/i.test(result);
392
+ }
382
393
 
383
394
  class Agent {
384
395
  cfg;
@@ -501,6 +512,7 @@ ${summary}` : "Earlier conversation was auto-compacted to free context (summary
501
512
  recordTurn(this.ctx.cwd, "user", userText);
502
513
  newTurn();
503
514
  this._doneCalls.clear();
515
+ this._launchHandled.clear();
504
516
  const ac = this.abortController = new AbortController;
505
517
  const signal = ac.signal;
506
518
  let turnGrains = 0;
@@ -550,6 +562,26 @@ ${tail}`;
550
562
  }
551
563
  this.history.push({ role: "tool", tool_call_id: call.id, name: call.function.name, content });
552
564
  toolCount++;
565
+ if (call.function.name === "bash" && !this._launchHandled.has(call.id)) {
566
+ let bashCmd = "";
567
+ try {
568
+ bashCmd = String(JSON.parse(call.function.arguments || "{}").command ?? "");
569
+ } catch {}
570
+ if (isLaunchCommand(bashCmd) && looksLikeLaunchError(content)) {
571
+ this._launchHandled.add(call.id);
572
+ let decision = this.cfg.autoFixLaunchErrors ? "always" : "no";
573
+ if (!this.cfg.autoFixLaunchErrors && this.h.onLaunchError) {
574
+ decision = await this.h.onLaunchError(bashCmd);
575
+ }
576
+ if (decision === "always") {
577
+ this.cfg.autoFixLaunchErrors = true;
578
+ saveConfig({ autoFixLaunchErrors: true });
579
+ }
580
+ if (decision === "yes" || decision === "always") {
581
+ this.history.push({ role: "system", content: `The launch command \`${bashCmd}\` failed to start. Diagnose the root cause from the error output above, fix it (install missing deps, fix config/code, free the port, etc.), then re-run the launch to verify it starts cleanly.` });
582
+ }
583
+ }
584
+ }
553
585
  }
554
586
  }
555
587
  } catch (e) {
@@ -577,6 +609,7 @@ ${tail}`;
577
609
  }
578
610
  }
579
611
  _suggested = false;
612
+ _launchHandled = new Set;
580
613
  _doneCalls = new Map;
581
614
  async execToolCall(call) {
582
615
  const tool = getTool(call.function.name);
@@ -602,11 +635,11 @@ ${tail}`;
602
635
  if (!gate.allowed) {
603
636
  result = `blocked by hook: ${gate.reason}`;
604
637
  } else if (needsConfirm && !await this.h.confirm(tool.name, args)) {
605
- result = "denied by user";
638
+ result = "declined by user";
606
639
  } else {
607
640
  if (MUTATING_TOOLS.has(tool.name) && typeof args.path === "string")
608
641
  snapshot(this.ctx.cwd, args.path);
609
- const runCtx = { ...this.ctx, onStream: (c) => this.h.onToolStream?.(c), onCost: (g) => this.h.onCost(g) };
642
+ const runCtx = { ...this.ctx, onStream: (c) => this.h.onToolStream?.(c), onCost: (g) => this.h.onCost(g), signal: this.abortController?.signal };
610
643
  try {
611
644
  result = await tool.run(args, runCtx);
612
645
  } catch (e) {
@@ -625,7 +658,7 @@ ${tail}`;
625
658
  }
626
659
  }
627
660
  this.h.onToolResult(tool.name, result);
628
- if (!result.startsWith("error") && !result.startsWith("denied") && !result.startsWith("blocked"))
661
+ if (!result.startsWith("error") && !result.startsWith("declined") && !result.startsWith("blocked"))
629
662
  this._doneCalls.set(sig, result);
630
663
  return { content: result, edited };
631
664
  }
@@ -1310,9 +1343,6 @@ function registerTaskTools() {
1310
1343
  register(schedule);
1311
1344
  }
1312
1345
 
1313
- // src/tips/tips.ts
1314
- var FREQUENCY = Math.max(1, Number(process.env.MODELCODE_TIP_FREQUENCY || "1"));
1315
-
1316
1346
  // src/wallet/store.ts
1317
1347
  import { join as join5 } from "path";
1318
1348
  import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
@@ -1382,7 +1412,7 @@ async function interactiveLogin(rl, cfg) {
1382
1412
  `);
1383
1413
  return false;
1384
1414
  }
1385
- saveConfig({ apiKey: key });
1415
+ saveConfig2({ apiKey: key });
1386
1416
  cfg.apiKey = key;
1387
1417
  stdout.write(`${C.green}\u2713 logged in${C.reset} (key saved to ~/.modelcode/config.json)
1388
1418
  `);
@@ -1410,7 +1440,7 @@ async function interactiveLogin(rl, cfg) {
1410
1440
  }
1411
1441
  try {
1412
1442
  const { apiKey, depositAddr } = await registerApiKey(address);
1413
- saveConfig({ apiKey });
1443
+ saveConfig2({ apiKey });
1414
1444
  cfg.apiKey = apiKey;
1415
1445
  stdout.write(`${C.green}\u2713 API key created + saved${C.reset}
1416
1446
  ` + `${C.dim}To buy credits: send MDL to your wallet, then run ${C.reset}modelcode credits fund <mdl>${C.dim} ` + `(it funds the API bank ${depositAddr ? depositAddr.slice(0, 16) + "\u2026" : ""} and claims credits).${C.reset}
@@ -1440,7 +1470,7 @@ async function maybeAutoKey(address) {
1440
1470
  return;
1441
1471
  try {
1442
1472
  const { apiKey, depositAddr } = await registerApiKey(address);
1443
- saveConfig({ apiKey });
1473
+ saveConfig2({ apiKey });
1444
1474
  stdout.write(`${C.green}API key created & saved${C.reset} (${apiKey.slice(0, 12)}\u2026)
1445
1475
  ` + `deposit address: ${depositAddr}
1446
1476
  fund it in one command: ${C.cyan}modelcode credits fund <mdl>${C.reset}
@@ -1609,7 +1639,7 @@ async function main() {
1609
1639
  s.resume();
1610
1640
  await new Promise((r) => setImmediate(r));
1611
1641
  }
1612
- const { runTui } = await import("./tui-sekv1hga.mjs");
1642
+ const { runTui } = await import("./tui-pgwd6eyk.mjs");
1613
1643
  return runTui(cfg, resume);
1614
1644
  }
1615
1645
  if (cmd === "tui")
@@ -7921,6 +7921,7 @@ var ConfigSchema = exports_external.object({
7921
7921
  autoSummary: exports_external.boolean().default(true),
7922
7922
  theme: exports_external.enum(["amber", "mono", "ocean"]).default("amber"),
7923
7923
  teamMemoryUrl: exports_external.string().optional(),
7924
+ autoFixLaunchErrors: exports_external.boolean().default(false),
7924
7925
  spendCapMdl: exports_external.number().default(0),
7925
7926
  providers: exports_external.record(exports_external.object({ baseUrl: exports_external.string(), apiKey: exports_external.string().optional(), model: exports_external.string().optional() })).default({})
7926
7927
  });
@@ -8073,16 +8074,54 @@ function extsFromGlob(glob) {
8073
8074
  function runProc(cmd, opts = {}) {
8074
8075
  return new Promise((resolve) => {
8075
8076
  const [bin, ...args] = cmd;
8076
- const so = { cwd: opts.cwd, env: { ...process.env, ...opts.env }, stdio: ["ignore", "pipe", "pipe"] };
8077
+ const so = { cwd: opts.cwd, env: { ...process.env, ...opts.env }, stdio: ["ignore", "pipe", "pipe"], detached: !isWin };
8077
8078
  const p = spawn(bin, args, so);
8078
- let stdout = "", stderr = "";
8079
+ let stdout = "", stderr = "", done = false;
8079
8080
  const dOut = new StringDecoder("utf8"), dErr = new StringDecoder("utf8");
8081
+ const finish = (code) => {
8082
+ if (done)
8083
+ return;
8084
+ done = true;
8085
+ if (killer)
8086
+ clearTimeout(killer);
8087
+ resolve({ code, stdout: stdout + dOut.end(), stderr: stderr + dErr.end() });
8088
+ };
8089
+ const killTree = (signal) => {
8090
+ try {
8091
+ if (isWin)
8092
+ spawnSync("taskkill", ["/pid", String(p.pid), "/T", "/F"], { stdio: "ignore" });
8093
+ else if (p.pid)
8094
+ process.kill(-p.pid, signal);
8095
+ else
8096
+ p.kill(signal);
8097
+ } catch {
8098
+ try {
8099
+ p.kill(signal);
8100
+ } catch {}
8101
+ }
8102
+ };
8080
8103
  let killer = null;
8081
8104
  if (opts.timeoutMs)
8082
8105
  killer = setTimeout(() => {
8083
- p.kill("SIGTERM");
8084
- setTimeout(() => p.kill("SIGKILL"), 2000);
8106
+ killTree("SIGTERM");
8107
+ setTimeout(() => {
8108
+ killTree("SIGKILL");
8109
+ finish(124);
8110
+ }, 2000);
8085
8111
  }, opts.timeoutMs);
8112
+ if (opts.signal) {
8113
+ if (opts.signal.aborted) {
8114
+ killTree("SIGKILL");
8115
+ finish(130);
8116
+ } else
8117
+ opts.signal.addEventListener("abort", () => {
8118
+ killTree("SIGTERM");
8119
+ setTimeout(() => {
8120
+ killTree("SIGKILL");
8121
+ finish(130);
8122
+ }, 500);
8123
+ }, { once: true });
8124
+ }
8086
8125
  p.stdout?.on("data", (d) => {
8087
8126
  const s = dOut.write(d);
8088
8127
  stdout += s;
@@ -8093,15 +8132,10 @@ function runProc(cmd, opts = {}) {
8093
8132
  stderr += s;
8094
8133
  opts.onChunk?.(s, true);
8095
8134
  });
8096
- p.on("close", (code) => {
8097
- if (killer)
8098
- clearTimeout(killer);
8099
- resolve({ code: code ?? 0, stdout: stdout + dOut.end(), stderr: stderr + dErr.end() });
8100
- });
8135
+ p.on("close", (code) => finish(code ?? 0));
8101
8136
  p.on("error", (e) => {
8102
- if (killer)
8103
- clearTimeout(killer);
8104
- resolve({ code: 1, stdout, stderr: stderr + String(e) });
8137
+ stderr += String(e);
8138
+ finish(1);
8105
8139
  });
8106
8140
  });
8107
8141
  }
@@ -15047,37 +15081,43 @@ function toolSchemas() {
15047
15081
 
15048
15082
  // src/ui/diff.ts
15049
15083
  var A = { red: "\x1B[31m", green: "\x1B[32m", gray: "\x1B[90m", cyan: "\x1B[36m", yellow: "\x1B[33m", mag: "\x1B[35m", reset: "\x1B[0m" };
15050
- function renderDiff(before, after, path = "") {
15084
+ function renderDiffPlain(before, after) {
15051
15085
  const a = before.split(`
15052
15086
  `), b = after.split(`
15053
15087
  `);
15054
- const n = Math.min(a.length, 2000), m = Math.min(b.length, 2000);
15088
+ const n = Math.min(a.length, 4000), m = Math.min(b.length, 4000);
15055
15089
  const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
15056
15090
  for (let i2 = n - 1;i2 >= 0; i2--)
15057
15091
  for (let j2 = m - 1;j2 >= 0; j2--)
15058
15092
  dp[i2][j2] = a[i2] === b[j2] ? dp[i2 + 1][j2 + 1] + 1 : Math.max(dp[i2 + 1][j2], dp[i2][j2 + 1]);
15059
- const out = path ? [`${A.cyan}--- ${path}${A.reset}`] : [];
15093
+ const out = [];
15060
15094
  let i = 0, j = 0;
15061
15095
  while (i < n && j < m) {
15062
15096
  if (a[i] === b[j]) {
15063
- out.push(`${A.gray} ${a[i]}${A.reset}`);
15097
+ out.push(` ${a[i]}`);
15064
15098
  i++;
15065
15099
  j++;
15066
15100
  } else if (dp[i + 1][j] >= dp[i][j + 1]) {
15067
- out.push(`${A.red}-${a[i]}${A.reset}`);
15101
+ out.push(`- ${a[i]}`);
15068
15102
  i++;
15069
15103
  } else {
15070
- out.push(`${A.green}+${b[j]}${A.reset}`);
15104
+ out.push(`+ ${b[j]}`);
15071
15105
  j++;
15072
15106
  }
15073
15107
  }
15074
15108
  while (i < n)
15075
- out.push(`${A.red}-${a[i++]}${A.reset}`);
15109
+ out.push(`- ${a[i++]}`);
15076
15110
  while (j < m)
15077
- out.push(`${A.green}+${b[j++]}${A.reset}`);
15111
+ out.push(`+ ${b[j++]}`);
15078
15112
  return out.join(`
15079
15113
  `);
15080
15114
  }
15115
+ var KEYWORDS = /\b(const|let|var|function|class|interface|type|import|export|return|if|else|for|while|async|await|def|fn|struct|enum|public|private|func|package|use|impl|match|new)\b/g;
15116
+ var STRINGS = /("[^"]*"|'[^']*'|`[^`]*`)/g;
15117
+ var COMMENTS = /(\/\/[^\n]*|#[^\n]*)/g;
15118
+ function highlight(code) {
15119
+ return code.replace(COMMENTS, (m) => `${A.gray}${m}${A.reset}`).replace(STRINGS, (m) => `${A.yellow}${m}${A.reset}`).replace(KEYWORDS, (m) => `${A.mag}${m}${A.reset}`).replace(/\b(\d+(?:\.\d+)?)\b/g, (m) => `${A.cyan}${m}${A.reset}`);
15120
+ }
15081
15121
 
15082
15122
  // src/memory/store.ts
15083
15123
  import { join as join7 } from "path";
@@ -24315,4 +24355,4 @@ async function decryptWallet(enc, password, network = MAINNET) {
24315
24355
  }
24316
24356
  return w;
24317
24357
  }
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 };
24358
+ export { exports_external, register, getTool, toolSchemas, renderDiffPlain, highlight, 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 };
@@ -23,6 +23,7 @@ import {
23
23
  getUsage,
24
24
  globMatch,
25
25
  globalDir,
26
+ highlight,
26
27
  imageToDataUrl,
27
28
  impactSummary,
28
29
  indexCodebase,
@@ -51,7 +52,7 @@ import {
51
52
  removeCard,
52
53
  removeJob,
53
54
  renderBoard,
54
- renderDiff,
55
+ renderDiffPlain,
55
56
  route,
56
57
  runProc,
57
58
  runSubagent,
@@ -68,7 +69,7 @@ import {
68
69
  undo,
69
70
  userPromptSubmit,
70
71
  which
71
- } from "./main-wr686fnv.mjs";
72
+ } from "./main-pj08pbgh.mjs";
72
73
  import"./main-p2xnn95s.mjs";
73
74
  import {
74
75
  __commonJS,
@@ -21593,7 +21594,7 @@ function formatMarkdown(src) {
21593
21594
  continue;
21594
21595
  }
21595
21596
  if (inFence) {
21596
- out.push(`${DIM} ${line}${_DIM}`);
21597
+ out.push(` ${highlight(line)}`);
21597
21598
  continue;
21598
21599
  }
21599
21600
  const h = line.match(/^(#{1,6})\s+(.*)$/);
@@ -21692,6 +21693,11 @@ function Chip({ label, color }) {
21692
21693
  ]
21693
21694
  }, undefined, true, undefined, this);
21694
21695
  }
21696
+ function truncPath(p, max2 = 60) {
21697
+ if (p.length <= max2)
21698
+ return p;
21699
+ return "…" + p.slice(p.length - max2 + 1);
21700
+ }
21695
21701
  function C_KEY(color, k) {
21696
21702
  return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21697
21703
  color,
@@ -21703,7 +21709,7 @@ function C_KEY(color, k) {
21703
21709
  ]
21704
21710
  }, undefined, true, undefined, this);
21705
21711
  }
21706
- function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct, walletMdl, mode, busy, toolStream, turnStart, turnTokens, confirmReq, history, onConfirm, onSubmit, onInterrupt, onExit }) {
21712
+ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct, walletMdl, mode, busy, toolStream, turnStart, turnTokens, confirmReq, launchReq, history, onConfirm, onLaunchDecision, onSubmit, onInterrupt, onExit }) {
21707
21713
  const t = getTheme(theme);
21708
21714
  const [input, setInput] = import_react34.useState("");
21709
21715
  const [histIdx, setHistIdx] = import_react34.useState(-1);
@@ -21737,6 +21743,16 @@ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct,
21737
21743
  armExit();
21738
21744
  return;
21739
21745
  }
21746
+ if (launchReq) {
21747
+ const k = ch.toLowerCase();
21748
+ if (k === "y")
21749
+ onLaunchDecision("yes");
21750
+ else if (k === "a")
21751
+ onLaunchDecision("always");
21752
+ else if (k === "n" || key.return || key.escape)
21753
+ onLaunchDecision("no");
21754
+ return;
21755
+ }
21740
21756
  if (confirmReq) {
21741
21757
  const k = ch.toLowerCase();
21742
21758
  if (k === "y")
@@ -21829,12 +21845,85 @@ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct,
21829
21845
  }, i, true, undefined, this);
21830
21846
  if (l.kind === "tool")
21831
21847
  return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21832
- color: t.tool,
21833
21848
  children: [
21834
- " ⚙ ",
21835
- l.text
21849
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21850
+ color: t.tool,
21851
+ children: "⏺ "
21852
+ }, undefined, false, undefined, this),
21853
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21854
+ children: l.text
21855
+ }, undefined, false, undefined, this)
21856
+ ]
21857
+ }, i, true, undefined, this);
21858
+ if (l.kind === "result")
21859
+ return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21860
+ flexDirection: "row",
21861
+ children: [
21862
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21863
+ color: t.dim,
21864
+ children: " ⎿ "
21865
+ }, undefined, false, undefined, this),
21866
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21867
+ flexShrink: 1,
21868
+ flexGrow: 1,
21869
+ children: /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21870
+ color: t.dim,
21871
+ children: l.text.split(`
21872
+ `).join(`
21873
+ `)
21874
+ }, undefined, false, undefined, this)
21875
+ }, undefined, false, undefined, this)
21876
+ ]
21877
+ }, i, true, undefined, this);
21878
+ if (l.kind === "diff") {
21879
+ const rows = l.text.split(`
21880
+ `);
21881
+ const header = rows[0] && !/^[+\- ] /.test(rows[0]) ? rows.shift() : null;
21882
+ let ln = 0;
21883
+ return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21884
+ flexDirection: "column",
21885
+ children: [
21886
+ header ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21887
+ children: [
21888
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21889
+ color: t.tool,
21890
+ children: "⏺ "
21891
+ }, undefined, false, undefined, this),
21892
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21893
+ children: header
21894
+ }, undefined, false, undefined, this)
21895
+ ]
21896
+ }, undefined, true, undefined, this) : null,
21897
+ rows.slice(0, 60).map((row, k) => {
21898
+ const add2 = row.startsWith("+ "), del = row.startsWith("- ");
21899
+ if (!del)
21900
+ ln++;
21901
+ const num = String(ln).padStart(4);
21902
+ const body = row.slice(2);
21903
+ return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21904
+ children: [
21905
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21906
+ color: t.dim,
21907
+ children: ` ${add2 ? " " : num} `
21908
+ }, undefined, false, undefined, this),
21909
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21910
+ backgroundColor: add2 ? "#10301a" : del ? "#3a1418" : undefined,
21911
+ color: add2 ? t.ok : del ? t.warn : t.dim,
21912
+ children: [
21913
+ add2 ? "+ " : del ? "- " : " ",
21914
+ body
21915
+ ]
21916
+ }, undefined, true, undefined, this)
21917
+ ]
21918
+ }, k, true, undefined, this);
21919
+ }),
21920
+ rows.length > 60 ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21921
+ color: t.dim,
21922
+ children: ` … +${rows.length - 60} more diff lines`
21923
+ }, undefined, false, undefined, this) : null
21836
21924
  ]
21837
21925
  }, i, true, undefined, this);
21926
+ }
21838
21927
  if (l.kind === "error")
21839
21928
  return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21840
21929
  flexDirection: "column",
@@ -21864,21 +21953,36 @@ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct,
21864
21953
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21865
21954
  borderStyle: "round",
21866
21955
  borderColor: t.accent,
21867
- paddingX: 1,
21868
- justifyContent: "space-between",
21956
+ paddingX: 2,
21957
+ paddingY: 1,
21958
+ flexDirection: "column",
21959
+ alignItems: "center",
21869
21960
  children: [
21870
21961
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21871
21962
  bold: true,
21872
21963
  color: t.accent,
21873
- children: " modelcode"
21964
+ children: " modelcode"
21874
21965
  }, undefined, false, undefined, this),
21875
21966
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21876
21967
  color: t.dim,
21968
+ children: "the AI coding agent on modelOS"
21969
+ }, undefined, false, undefined, this),
21970
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21877
21971
  children: [
21878
- model,
21879
- " · pay-per-use · no limits"
21972
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21973
+ color: t.user,
21974
+ children: model
21975
+ }, undefined, false, undefined, this),
21976
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21977
+ color: t.dim,
21978
+ children: " · pay-per-use in MDL · no rate limits"
21979
+ }, undefined, false, undefined, this)
21880
21980
  ]
21881
- }, undefined, true, undefined, this)
21981
+ }, undefined, true, undefined, this),
21982
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21983
+ color: t.dim,
21984
+ children: truncPath(process.cwd())
21985
+ }, undefined, false, undefined, this)
21882
21986
  ]
21883
21987
  }, undefined, true, undefined, this),
21884
21988
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Static, {
@@ -21938,6 +22042,34 @@ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct,
21938
22042
  ]
21939
22043
  }, c2.cmd, true, undefined, this))
21940
22044
  }, undefined, false, undefined, this) : null,
22045
+ launchReq ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
22046
+ flexDirection: "column",
22047
+ borderStyle: "round",
22048
+ borderColor: t.warn,
22049
+ paddingX: 1,
22050
+ marginTop: 1,
22051
+ children: [
22052
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
22053
+ color: t.warn,
22054
+ bold: true,
22055
+ children: "launch failed — fix it?"
22056
+ }, undefined, false, undefined, this),
22057
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
22058
+ color: t.dim,
22059
+ children: launchReq.cmd
22060
+ }, undefined, false, undefined, this),
22061
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
22062
+ children: [
22063
+ C_KEY(t.user, "y"),
22064
+ " accept ",
22065
+ C_KEY(t.user, "n"),
22066
+ " decline ",
22067
+ C_KEY(t.accent, "a"),
22068
+ " at all times (every project)"
22069
+ ]
22070
+ }, undefined, true, undefined, this)
22071
+ ]
22072
+ }, undefined, true, undefined, this) : null,
21941
22073
  confirmReq ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21942
22074
  flexDirection: "column",
21943
22075
  borderStyle: "round",
@@ -21961,13 +22093,13 @@ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct,
21961
22093
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21962
22094
  children: [
21963
22095
  C_KEY(t.user, "y"),
21964
- " yes ",
22096
+ " accept ",
21965
22097
  C_KEY(t.user, "a"),
21966
22098
  " always (project) ",
21967
22099
  C_KEY(t.user, "g"),
21968
- " global ",
22100
+ " always (global) ",
21969
22101
  C_KEY(t.warn, "n"),
21970
- " no"
22102
+ " decline"
21971
22103
  ]
21972
22104
  }, undefined, true, undefined, this)
21973
22105
  ]
@@ -22062,6 +22194,15 @@ Tool discipline (important): call each tool ONCE for a given action — never re
22062
22194
  A tool result is authoritative: once a write/edit succeeds, trust it and move on. Do NOT claim a task is
22063
22195
  done in the same message where you call the tool to do it — call the tool first, then confirm only after
22064
22196
  you see its result. Keep confirmations to one short sentence.`;
22197
+ var LAUNCH_RE = /\b(npm|pnpm|yarn|bun)\s+(run\s+)?(dev|start|serve)\b|\b(vite|next|nuxt|astro|remix|webpack[\- ]dev[\- ]server|nodemon|tsx\s+watch|ts-node-dev)\b|\b(uvicorn|gunicorn|flask\s+run|manage\.py\s+runserver|rails\s+s(erver)?|go\s+run|cargo\s+run|dotnet\s+run|php\s+artisan\s+serve|air|nest\s+start)\b/i;
22198
+ function isLaunchCommand(cmd) {
22199
+ return LAUNCH_RE.test(cmd);
22200
+ }
22201
+ function looksLikeLaunchError(result2) {
22202
+ if (!/^exit\s+([1-9]\d*|124)\b/m.test(result2))
22203
+ return false;
22204
+ return /\b(error|exception|cannot find module|module not found|eaddrinuse|syntaxerror|typeerror|referenceerror|failed to compile|command not found|traceback|panic:|address already in use|unexpected token|ENOENT|MODULE_NOT_FOUND)\b/i.test(result2);
22205
+ }
22065
22206
 
22066
22207
  class Agent {
22067
22208
  cfg;
@@ -22184,6 +22325,7 @@ ${summary}` : "Earlier conversation was auto-compacted to free context (summary
22184
22325
  recordTurn(this.ctx.cwd, "user", userText);
22185
22326
  newTurn();
22186
22327
  this._doneCalls.clear();
22328
+ this._launchHandled.clear();
22187
22329
  const ac = this.abortController = new AbortController;
22188
22330
  const signal = ac.signal;
22189
22331
  let turnGrains = 0;
@@ -22233,6 +22375,26 @@ ${tail2}`;
22233
22375
  }
22234
22376
  this.history.push({ role: "tool", tool_call_id: call.id, name: call.function.name, content });
22235
22377
  toolCount++;
22378
+ if (call.function.name === "bash" && !this._launchHandled.has(call.id)) {
22379
+ let bashCmd = "";
22380
+ try {
22381
+ bashCmd = String(JSON.parse(call.function.arguments || "{}").command ?? "");
22382
+ } catch {}
22383
+ if (isLaunchCommand(bashCmd) && looksLikeLaunchError(content)) {
22384
+ this._launchHandled.add(call.id);
22385
+ let decision = this.cfg.autoFixLaunchErrors ? "always" : "no";
22386
+ if (!this.cfg.autoFixLaunchErrors && this.h.onLaunchError) {
22387
+ decision = await this.h.onLaunchError(bashCmd);
22388
+ }
22389
+ if (decision === "always") {
22390
+ this.cfg.autoFixLaunchErrors = true;
22391
+ saveConfig({ autoFixLaunchErrors: true });
22392
+ }
22393
+ if (decision === "yes" || decision === "always") {
22394
+ this.history.push({ role: "system", content: `The launch command \`${bashCmd}\` failed to start. Diagnose the root cause from the error output above, fix it (install missing deps, fix config/code, free the port, etc.), then re-run the launch to verify it starts cleanly.` });
22395
+ }
22396
+ }
22397
+ }
22236
22398
  }
22237
22399
  }
22238
22400
  } catch (e) {
@@ -22260,6 +22422,7 @@ ${tail2}`;
22260
22422
  }
22261
22423
  }
22262
22424
  _suggested = false;
22425
+ _launchHandled = new Set;
22263
22426
  _doneCalls = new Map;
22264
22427
  async execToolCall(call) {
22265
22428
  const tool = getTool(call.function.name);
@@ -22285,11 +22448,11 @@ ${tail2}`;
22285
22448
  if (!gate.allowed) {
22286
22449
  result2 = `blocked by hook: ${gate.reason}`;
22287
22450
  } else if (needsConfirm && !await this.h.confirm(tool.name, args)) {
22288
- result2 = "denied by user";
22451
+ result2 = "declined by user";
22289
22452
  } else {
22290
22453
  if (MUTATING_TOOLS.has(tool.name) && typeof args.path === "string")
22291
22454
  snapshot(this.ctx.cwd, args.path);
22292
- const runCtx = { ...this.ctx, onStream: (c2) => this.h.onToolStream?.(c2), onCost: (g) => this.h.onCost(g) };
22455
+ const runCtx = { ...this.ctx, onStream: (c2) => this.h.onToolStream?.(c2), onCost: (g) => this.h.onCost(g), signal: this.abortController?.signal };
22293
22456
  try {
22294
22457
  result2 = await tool.run(args, runCtx);
22295
22458
  } catch (e) {
@@ -22308,7 +22471,7 @@ ${tail2}`;
22308
22471
  }
22309
22472
  }
22310
22473
  this.h.onToolResult(tool.name, result2);
22311
- if (!result2.startsWith("error") && !result2.startsWith("denied") && !result2.startsWith("blocked"))
22474
+ if (!result2.startsWith("error") && !result2.startsWith("declined") && !result2.startsWith("blocked"))
22312
22475
  this._doneCalls.set(sig, result2);
22313
22476
  return { content: result2, edited };
22314
22477
  }
@@ -23015,7 +23178,7 @@ var bash = {
23015
23178
  async run(args, ctx) {
23016
23179
  const cmd = String(args.command ?? "");
23017
23180
  const timeout = Number(args.timeout_ms) || 120000;
23018
- const r = await runProc(shellInvocation(cmd), { cwd: ctx.cwd, timeoutMs: timeout, onChunk: (s) => ctx.onStream?.(s) });
23181
+ const r = await runProc(shellInvocation(cmd), { cwd: ctx.cwd, timeoutMs: timeout, signal: ctx.signal, onChunk: (s) => ctx.onStream?.(s) });
23019
23182
  return `exit ${r.code}
23020
23183
  ${(r.stdout + (r.stderr ? `
23021
23184
  [stderr]
@@ -23086,7 +23249,7 @@ var edit = {
23086
23249
  const updated = args.replace_all ? src.split(oldS).join(newS) : src.replace(oldS, newS);
23087
23250
  writeFileSync6(p, updated);
23088
23251
  return `edited ${args.path} (${count} replacement${count === 1 ? "" : "s"})
23089
- ${renderDiff(oldS, newS, String(args.path))}`;
23252
+ ${renderDiffPlain(oldS, newS)}`;
23090
23253
  }
23091
23254
  };
23092
23255
  var glob = {
@@ -23819,6 +23982,26 @@ function imageMentions(line, cwd2) {
23819
23982
 
23820
23983
  // src/ui/tui.tsx
23821
23984
  var jsx_dev_runtime2 = __toESM(require_jsx_dev_runtime(), 1);
23985
+ function toolUseLabel(name, args) {
23986
+ const cap = (s, n = 160) => s.length > n ? s.slice(0, n).trim() + "…" : s;
23987
+ if (name === "bash")
23988
+ return cap(String(args.command ?? "").split(`
23989
+ `)[0] ?? "");
23990
+ if (name === "edit" || name === "write" || name === "read")
23991
+ return `${name} ${cap(String(args.path ?? ""), 80)}`;
23992
+ const keys2 = Object.entries(args).map(([k, v]) => `${k}=${cap(String(v), 40)}`).join(" ");
23993
+ return cap(`${name} ${keys2}`);
23994
+ }
23995
+ function capLines(s, max2) {
23996
+ const lines = s.replace(/\s+$/, "").split(`
23997
+ `);
23998
+ if (lines.length <= max2)
23999
+ return lines.join(`
24000
+ `);
24001
+ return lines.slice(0, max2).join(`
24002
+ `) + `
24003
+ … +${lines.length - max2} lines`;
24004
+ }
23822
24005
  function Root({ cfg, resume, mcpCount }) {
23823
24006
  const [lines, setLines] = import_react35.useState([
23824
24007
  { kind: "system", text: `ready · model ${cfg.model} — ask me anything, or /help` },
@@ -23831,16 +24014,20 @@ function Root({ cfg, resume, mcpCount }) {
23831
24014
  const [walletMdl, setWalletMdl] = import_react35.useState(null);
23832
24015
  const [mode, setMode] = import_react35.useState("default");
23833
24016
  const [confirmReq, setConfirmReq] = import_react35.useState(null);
24017
+ const [launchReq, setLaunchReq] = import_react35.useState(null);
23834
24018
  const [history, setHistory] = import_react35.useState([]);
23835
24019
  const [toolStream, setToolStream] = import_react35.useState("");
23836
24020
  const [turnStart, setTurnStart] = import_react35.useState(0);
23837
24021
  const [turnTokens, setTurnTokens] = import_react35.useState(0);
23838
24022
  const mdlUsd = import_react35.useRef(null);
23839
24023
  const streamBuf = import_react35.useRef("");
24024
+ const toolStreamRef = import_react35.useRef("");
23840
24025
  const tokensRef = import_react35.useRef(0);
24026
+ const pumpRef = import_react35.useRef(null);
23841
24027
  const busyRef = import_react35.useRef(false);
23842
24028
  const queueRef = import_react35.useRef([]);
23843
24029
  const confirmResolve = import_react35.useRef(null);
24030
+ const launchResolve = import_react35.useRef(null);
23844
24031
  const sessionIdRef = import_react35.useRef(newSessionId());
23845
24032
  const sessionMdlRef = import_react35.useRef(0);
23846
24033
  const mcpCountRef = import_react35.useRef(mcpCount);
@@ -23869,29 +24056,46 @@ function Root({ cfg, resume, mcpCount }) {
23869
24056
  confirmResolve.current?.(decision !== "no");
23870
24057
  confirmResolve.current = null;
23871
24058
  }, [confirmReq, cwd2]);
24059
+ const onLaunchError = import_react35.useCallback((cmd) => {
24060
+ setLaunchReq({ cmd });
24061
+ return new Promise((resolve3) => {
24062
+ launchResolve.current = resolve3;
24063
+ });
24064
+ }, []);
24065
+ const onLaunchDecision = import_react35.useCallback((d) => {
24066
+ setLaunchReq(null);
24067
+ launchResolve.current?.(d);
24068
+ launchResolve.current = null;
24069
+ }, []);
23872
24070
  const agent = import_react35.useRef(new Agent(cfg, { cwd: cwd2 }, {
23873
24071
  onAssistantDelta: (txt) => {
23874
24072
  streamBuf.current += txt;
23875
- setStreaming(streamBuf.current);
23876
24073
  tokensRef.current += Math.max(1, Math.round(txt.length / 4));
23877
- setTurnTokens(tokensRef.current);
23878
24074
  },
23879
24075
  onToolStart: (name, args) => {
23880
24076
  flushAssistant();
24077
+ toolStreamRef.current = "";
23881
24078
  setToolStream("");
23882
- add2({ kind: "tool", text: `${name} ${JSON.stringify(args).slice(0, 80)}` });
24079
+ add2({ kind: "tool", text: toolUseLabel(name, args) });
23883
24080
  },
23884
- onToolResult: (_n, r) => {
24081
+ onToolResult: (name, r) => {
24082
+ toolStreamRef.current = "";
23885
24083
  setToolStream("");
23886
- add2({ kind: "system", text: r.slice(0, 300) });
24084
+ if ((name === "edit" || name === "write") && /^[+\-] /m.test(r))
24085
+ add2({ kind: "diff", text: r });
24086
+ else
24087
+ add2({ kind: "result", text: capLines(r, 8) });
24088
+ },
24089
+ onToolStream: (chunk2) => {
24090
+ toolStreamRef.current = (toolStreamRef.current + chunk2).slice(-2000);
23887
24091
  },
23888
- onToolStream: (chunk2) => setToolStream((s) => (s + chunk2).slice(-2000)),
23889
24092
  onCost: (g) => setSessionMdl((s) => {
23890
24093
  const v = s + g / 1e8;
23891
24094
  sessionMdlRef.current = v;
23892
24095
  return v;
23893
24096
  }),
23894
24097
  confirm,
24098
+ onLaunchError,
23895
24099
  onSuggestSkill: (n) => add2({ kind: "system", text: `\uD83D\uDCA1 that took ${n} steps — /skill-create to save it` }),
23896
24100
  onCompact: (cs) => add2({ kind: "system", text: `\uD83D\uDDDC context auto-compacted (now ~${cs.pct}% full)` })
23897
24101
  })).current;
@@ -23932,6 +24136,7 @@ function Root({ cfg, resume, mcpCount }) {
23932
24136
  return;
23933
24137
  }
23934
24138
  streamBuf.current = "";
24139
+ toolStreamRef.current = "";
23935
24140
  setStreaming("");
23936
24141
  setToolStream("");
23937
24142
  tokensRef.current = 0;
@@ -23939,6 +24144,13 @@ function Root({ cfg, resume, mcpCount }) {
23939
24144
  setTurnStart(Date.now());
23940
24145
  busyRef.current = true;
23941
24146
  setBusy(true);
24147
+ if (pumpRef.current)
24148
+ clearInterval(pumpRef.current);
24149
+ pumpRef.current = setInterval(() => {
24150
+ setStreaming(streamBuf.current);
24151
+ setToolStream(toolStreamRef.current);
24152
+ setTurnTokens(tokensRef.current);
24153
+ }, 80);
23942
24154
  try {
23943
24155
  await agent.send(expandFileMentions(prompt, cwd2), imageMentions(prompt, cwd2));
23944
24156
  flushAssistant();
@@ -23955,9 +24167,14 @@ function Root({ cfg, resume, mcpCount }) {
23955
24167
  flushAssistant();
23956
24168
  add2({ kind: "error", text: e.message });
23957
24169
  } finally {
24170
+ if (pumpRef.current) {
24171
+ clearInterval(pumpRef.current);
24172
+ pumpRef.current = null;
24173
+ }
23958
24174
  setStreaming("");
23959
24175
  setToolStream("");
23960
24176
  streamBuf.current = "";
24177
+ toolStreamRef.current = "";
23961
24178
  busyRef.current = false;
23962
24179
  setBusy(false);
23963
24180
  setContextPct(agent.contextStatus().pct);
@@ -24051,8 +24268,10 @@ function Root({ cfg, resume, mcpCount }) {
24051
24268
  turnStart,
24052
24269
  turnTokens,
24053
24270
  confirmReq,
24271
+ launchReq,
24054
24272
  history,
24055
24273
  onConfirm,
24274
+ onLaunchDecision,
24056
24275
  onSubmit,
24057
24276
  onInterrupt,
24058
24277
  onExit
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modeloslab/modelcode",
3
- "version": "0.1.4",
3
+ "version": "0.1.8",
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": {