@modeloslab/modelcode 0.1.5 → 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-t9m4v4fc.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-rxaw95g7.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
  });
@@ -8108,6 +8109,19 @@ function runProc(cmd, opts = {}) {
8108
8109
  finish(124);
8109
8110
  }, 2000);
8110
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
+ }
8111
8125
  p.stdout?.on("data", (d) => {
8112
8126
  const s = dOut.write(d);
8113
8127
  stdout += s;
@@ -15067,37 +15081,43 @@ function toolSchemas() {
15067
15081
 
15068
15082
  // src/ui/diff.ts
15069
15083
  var A = { red: "\x1B[31m", green: "\x1B[32m", gray: "\x1B[90m", cyan: "\x1B[36m", yellow: "\x1B[33m", mag: "\x1B[35m", reset: "\x1B[0m" };
15070
- function renderDiff(before, after, path = "") {
15084
+ function renderDiffPlain(before, after) {
15071
15085
  const a = before.split(`
15072
15086
  `), b = after.split(`
15073
15087
  `);
15074
- 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);
15075
15089
  const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
15076
15090
  for (let i2 = n - 1;i2 >= 0; i2--)
15077
15091
  for (let j2 = m - 1;j2 >= 0; j2--)
15078
15092
  dp[i2][j2] = a[i2] === b[j2] ? dp[i2 + 1][j2 + 1] + 1 : Math.max(dp[i2 + 1][j2], dp[i2][j2 + 1]);
15079
- const out = path ? [`${A.cyan}--- ${path}${A.reset}`] : [];
15093
+ const out = [];
15080
15094
  let i = 0, j = 0;
15081
15095
  while (i < n && j < m) {
15082
15096
  if (a[i] === b[j]) {
15083
- out.push(`${A.gray} ${a[i]}${A.reset}`);
15097
+ out.push(` ${a[i]}`);
15084
15098
  i++;
15085
15099
  j++;
15086
15100
  } else if (dp[i + 1][j] >= dp[i][j + 1]) {
15087
- out.push(`${A.red}-${a[i]}${A.reset}`);
15101
+ out.push(`- ${a[i]}`);
15088
15102
  i++;
15089
15103
  } else {
15090
- out.push(`${A.green}+${b[j]}${A.reset}`);
15104
+ out.push(`+ ${b[j]}`);
15091
15105
  j++;
15092
15106
  }
15093
15107
  }
15094
15108
  while (i < n)
15095
- out.push(`${A.red}-${a[i++]}${A.reset}`);
15109
+ out.push(`- ${a[i++]}`);
15096
15110
  while (j < m)
15097
- out.push(`${A.green}+${b[j++]}${A.reset}`);
15111
+ out.push(`+ ${b[j++]}`);
15098
15112
  return out.join(`
15099
15113
  `);
15100
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
+ }
15101
15121
 
15102
15122
  // src/memory/store.ts
15103
15123
  import { join as join7 } from "path";
@@ -24335,4 +24355,4 @@ async function decryptWallet(enc, password, network = MAINNET) {
24335
24355
  }
24336
24356
  return w;
24337
24357
  }
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 };
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-t9m4v4fc.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")
@@ -21859,6 +21875,55 @@ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct,
21859
21875
  }, undefined, false, undefined, this)
21860
21876
  ]
21861
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
21924
+ ]
21925
+ }, i, true, undefined, this);
21926
+ }
21862
21927
  if (l.kind === "error")
21863
21928
  return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21864
21929
  flexDirection: "column",
@@ -21888,21 +21953,36 @@ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct,
21888
21953
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21889
21954
  borderStyle: "round",
21890
21955
  borderColor: t.accent,
21891
- paddingX: 1,
21892
- justifyContent: "space-between",
21956
+ paddingX: 2,
21957
+ paddingY: 1,
21958
+ flexDirection: "column",
21959
+ alignItems: "center",
21893
21960
  children: [
21894
21961
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21895
21962
  bold: true,
21896
21963
  color: t.accent,
21897
- children: " modelcode"
21964
+ children: " modelcode"
21898
21965
  }, undefined, false, undefined, this),
21899
21966
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21900
21967
  color: t.dim,
21968
+ children: "the AI coding agent on modelOS"
21969
+ }, undefined, false, undefined, this),
21970
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21901
21971
  children: [
21902
- model,
21903
- " · 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)
21904
21980
  ]
21905
- }, 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)
21906
21986
  ]
21907
21987
  }, undefined, true, undefined, this),
21908
21988
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Static, {
@@ -21962,6 +22042,34 @@ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct,
21962
22042
  ]
21963
22043
  }, c2.cmd, true, undefined, this))
21964
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,
21965
22073
  confirmReq ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
21966
22074
  flexDirection: "column",
21967
22075
  borderStyle: "round",
@@ -21985,13 +22093,13 @@ function App2({ theme, model, mdlUsd, lines, streaming, sessionMdl, contextPct,
21985
22093
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
21986
22094
  children: [
21987
22095
  C_KEY(t.user, "y"),
21988
- " yes ",
22096
+ " accept ",
21989
22097
  C_KEY(t.user, "a"),
21990
22098
  " always (project) ",
21991
22099
  C_KEY(t.user, "g"),
21992
- " global ",
22100
+ " always (global) ",
21993
22101
  C_KEY(t.warn, "n"),
21994
- " no"
22102
+ " decline"
21995
22103
  ]
21996
22104
  }, undefined, true, undefined, this)
21997
22105
  ]
@@ -22086,6 +22194,15 @@ Tool discipline (important): call each tool ONCE for a given action — never re
22086
22194
  A tool result is authoritative: once a write/edit succeeds, trust it and move on. Do NOT claim a task is
22087
22195
  done in the same message where you call the tool to do it — call the tool first, then confirm only after
22088
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
+ }
22089
22206
 
22090
22207
  class Agent {
22091
22208
  cfg;
@@ -22208,6 +22325,7 @@ ${summary}` : "Earlier conversation was auto-compacted to free context (summary
22208
22325
  recordTurn(this.ctx.cwd, "user", userText);
22209
22326
  newTurn();
22210
22327
  this._doneCalls.clear();
22328
+ this._launchHandled.clear();
22211
22329
  const ac = this.abortController = new AbortController;
22212
22330
  const signal = ac.signal;
22213
22331
  let turnGrains = 0;
@@ -22257,6 +22375,26 @@ ${tail2}`;
22257
22375
  }
22258
22376
  this.history.push({ role: "tool", tool_call_id: call.id, name: call.function.name, content });
22259
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
+ }
22260
22398
  }
22261
22399
  }
22262
22400
  } catch (e) {
@@ -22284,6 +22422,7 @@ ${tail2}`;
22284
22422
  }
22285
22423
  }
22286
22424
  _suggested = false;
22425
+ _launchHandled = new Set;
22287
22426
  _doneCalls = new Map;
22288
22427
  async execToolCall(call) {
22289
22428
  const tool = getTool(call.function.name);
@@ -22309,11 +22448,11 @@ ${tail2}`;
22309
22448
  if (!gate.allowed) {
22310
22449
  result2 = `blocked by hook: ${gate.reason}`;
22311
22450
  } else if (needsConfirm && !await this.h.confirm(tool.name, args)) {
22312
- result2 = "denied by user";
22451
+ result2 = "declined by user";
22313
22452
  } else {
22314
22453
  if (MUTATING_TOOLS.has(tool.name) && typeof args.path === "string")
22315
22454
  snapshot(this.ctx.cwd, args.path);
22316
- 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 };
22317
22456
  try {
22318
22457
  result2 = await tool.run(args, runCtx);
22319
22458
  } catch (e) {
@@ -22332,7 +22471,7 @@ ${tail2}`;
22332
22471
  }
22333
22472
  }
22334
22473
  this.h.onToolResult(tool.name, result2);
22335
- if (!result2.startsWith("error") && !result2.startsWith("denied") && !result2.startsWith("blocked"))
22474
+ if (!result2.startsWith("error") && !result2.startsWith("declined") && !result2.startsWith("blocked"))
22336
22475
  this._doneCalls.set(sig, result2);
22337
22476
  return { content: result2, edited };
22338
22477
  }
@@ -23039,7 +23178,7 @@ var bash = {
23039
23178
  async run(args, ctx) {
23040
23179
  const cmd = String(args.command ?? "");
23041
23180
  const timeout = Number(args.timeout_ms) || 120000;
23042
- 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) });
23043
23182
  return `exit ${r.code}
23044
23183
  ${(r.stdout + (r.stderr ? `
23045
23184
  [stderr]
@@ -23110,7 +23249,7 @@ var edit = {
23110
23249
  const updated = args.replace_all ? src.split(oldS).join(newS) : src.replace(oldS, newS);
23111
23250
  writeFileSync6(p, updated);
23112
23251
  return `edited ${args.path} (${count} replacement${count === 1 ? "" : "s"})
23113
- ${renderDiff(oldS, newS, String(args.path))}`;
23252
+ ${renderDiffPlain(oldS, newS)}`;
23114
23253
  }
23115
23254
  };
23116
23255
  var glob = {
@@ -23875,6 +24014,7 @@ function Root({ cfg, resume, mcpCount }) {
23875
24014
  const [walletMdl, setWalletMdl] = import_react35.useState(null);
23876
24015
  const [mode, setMode] = import_react35.useState("default");
23877
24016
  const [confirmReq, setConfirmReq] = import_react35.useState(null);
24017
+ const [launchReq, setLaunchReq] = import_react35.useState(null);
23878
24018
  const [history, setHistory] = import_react35.useState([]);
23879
24019
  const [toolStream, setToolStream] = import_react35.useState("");
23880
24020
  const [turnStart, setTurnStart] = import_react35.useState(0);
@@ -23887,6 +24027,7 @@ function Root({ cfg, resume, mcpCount }) {
23887
24027
  const busyRef = import_react35.useRef(false);
23888
24028
  const queueRef = import_react35.useRef([]);
23889
24029
  const confirmResolve = import_react35.useRef(null);
24030
+ const launchResolve = import_react35.useRef(null);
23890
24031
  const sessionIdRef = import_react35.useRef(newSessionId());
23891
24032
  const sessionMdlRef = import_react35.useRef(0);
23892
24033
  const mcpCountRef = import_react35.useRef(mcpCount);
@@ -23915,6 +24056,17 @@ function Root({ cfg, resume, mcpCount }) {
23915
24056
  confirmResolve.current?.(decision !== "no");
23916
24057
  confirmResolve.current = null;
23917
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
+ }, []);
23918
24070
  const agent = import_react35.useRef(new Agent(cfg, { cwd: cwd2 }, {
23919
24071
  onAssistantDelta: (txt) => {
23920
24072
  streamBuf.current += txt;
@@ -23926,10 +24078,13 @@ function Root({ cfg, resume, mcpCount }) {
23926
24078
  setToolStream("");
23927
24079
  add2({ kind: "tool", text: toolUseLabel(name, args) });
23928
24080
  },
23929
- onToolResult: (_n, r) => {
24081
+ onToolResult: (name, r) => {
23930
24082
  toolStreamRef.current = "";
23931
24083
  setToolStream("");
23932
- add2({ kind: "result", text: capLines(r, 8) });
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) });
23933
24088
  },
23934
24089
  onToolStream: (chunk2) => {
23935
24090
  toolStreamRef.current = (toolStreamRef.current + chunk2).slice(-2000);
@@ -23940,6 +24095,7 @@ function Root({ cfg, resume, mcpCount }) {
23940
24095
  return v;
23941
24096
  }),
23942
24097
  confirm,
24098
+ onLaunchError,
23943
24099
  onSuggestSkill: (n) => add2({ kind: "system", text: `\uD83D\uDCA1 that took ${n} steps — /skill-create to save it` }),
23944
24100
  onCompact: (cs) => add2({ kind: "system", text: `\uD83D\uDDDC context auto-compacted (now ~${cs.pct}% full)` })
23945
24101
  })).current;
@@ -24112,8 +24268,10 @@ function Root({ cfg, resume, mcpCount }) {
24112
24268
  turnStart,
24113
24269
  turnTokens,
24114
24270
  confirmReq,
24271
+ launchReq,
24115
24272
  history,
24116
24273
  onConfirm,
24274
+ onLaunchDecision,
24117
24275
  onSubmit,
24118
24276
  onInterrupt,
24119
24277
  onExit
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modeloslab/modelcode",
3
- "version": "0.1.5",
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": {