micro-models-agent 0.32.0 → 0.33.1

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.
Files changed (2) hide show
  1. package/dist/main.js +938 -149
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -2162,7 +2162,7 @@ var init_defaults = __esm(() => {
2162
2162
  init_config();
2163
2163
  DEFAULTS = {
2164
2164
  version: "2.0.0",
2165
- model: "qwen3.5-9b",
2165
+ model: "qwen/qwen3.5-9b",
2166
2166
  provider: {
2167
2167
  type: "openai-compat",
2168
2168
  baseUrl: "http://localhost:1234/v1",
@@ -2177,18 +2177,18 @@ var init_defaults = __esm(() => {
2177
2177
  },
2178
2178
  experts: {
2179
2179
  code: {
2180
- model: "qwen3.5-9b",
2180
+ model: "qwen/qwen3.5-9b",
2181
2181
  tool_tags: ["file", "code", "shell"],
2182
2182
  max_attempts: 3
2183
2183
  },
2184
- research: { model: "qwen3.5-9b", tool_tags: ["research"], max_attempts: 3 },
2184
+ research: { model: "qwen/qwen3.5-9b", tool_tags: ["research"], max_attempts: 3 },
2185
2185
  browser: {
2186
- model: "qwen3.5-9b",
2186
+ model: "qwen/qwen3.5-9b",
2187
2187
  tool_tags: ["browser", "vision"],
2188
2188
  max_attempts: 3
2189
2189
  },
2190
2190
  vision: {
2191
- model: "qwen3.5-9b",
2191
+ model: "qwen/qwen3.5-9b",
2192
2192
  tool_tags: ["browser", "vision", "file"],
2193
2193
  max_attempts: 2
2194
2194
  }
@@ -2458,7 +2458,7 @@ Command: {command}`,
2458
2458
  "plan.step_status": "Step {step}: {status}",
2459
2459
  "plan.no_active": "No active plan",
2460
2460
  "plan.step_not_found": "Step not found",
2461
- "plan.step_already_done": "Step {step} is already marked done. Stop re-marking it either continue with the next step or investigate why progress appears stuck.",
2461
+ "plan.step_already_done": "Step {step} is already done — nothing to do. Plan progress:",
2462
2462
  "plan.order_blocked": 'Cannot mark step {step} done: step {first} ("{desc}") is not finished yet. Complete earlier steps first, or mark step {first} status=skipped if it is not needed.',
2463
2463
  "plan.show_header": "Plan status:",
2464
2464
  "plan.show_empty": "(plan has no steps)",
@@ -3035,7 +3035,7 @@ var init_ru = __esm(() => {
3035
3035
  "plan.step_status": "Шаг {step}: {status}",
3036
3036
  "plan.no_active": "Нет активного плана",
3037
3037
  "plan.step_not_found": "Шаг не найден",
3038
- "plan.step_already_done": "Шаг {step} уже отмечен выполненным. Прекратите повторную отметку переходите к следующему шагу или выясните, почему прогресс заблокирован.",
3038
+ "plan.step_already_done": "Шаг {step} уже выполненничего делать не нужно. Прогресс плана:",
3039
3039
  "plan.order_blocked": "Нельзя отметить шаг {step} выполненным: шаг {first} («{desc}») ещё не завершён. Сначала завершите предыдущие шаги или отметьте шаг {first} status=skipped, если он не нужен.",
3040
3040
  "plan.show_header": "Статус плана:",
3041
3041
  "plan.show_empty": "(в плане нет шагов)",
@@ -10435,6 +10435,7 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
10435
10435
  this.abortController = new AbortController;
10436
10436
  let iteration = 0;
10437
10437
  let lastText = "";
10438
+ let lastModelText = "";
10438
10439
  let lastForcedCompactionIteration = -FORCED_COMPACTION_COOLDOWN;
10439
10440
  let hallucinationRetries = 0;
10440
10441
  let lastToolSignature = "";
@@ -10451,6 +10452,7 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
10451
10452
  let emptyResponseExhausted = false;
10452
10453
  let auditFailed = false;
10453
10454
  let lastAuditSummary = "";
10455
+ let suppressRepetitionRetry = false;
10454
10456
  let repeatedToolCount = 0;
10455
10457
  const MAX_REPEATED_TOOL_CALLS = 2;
10456
10458
  const allToolsForBudget = toolExecutor.getToolDefinitions(this.deps.toolTags);
@@ -10738,9 +10740,12 @@ ${taskReminder}</system-summary>`
10738
10740
  `));
10739
10741
  }
10740
10742
  }
10743
+ suppressRepetitionRetry = false;
10741
10744
  continue;
10742
10745
  }
10743
- hallucinationDetector.getConfidenceCheck().setPreviousResponse(lastText);
10746
+ hallucinationDetector.getConfidenceCheck().setPreviousResponse(lastModelText);
10747
+ if (textContent)
10748
+ lastModelText = textContent;
10744
10749
  const hallucinationResult = await hallucinationDetector.validate(textContent);
10745
10750
  if (hallucinationResult.status === "block") {
10746
10751
  logger.warn(`Response blocked: ${hallucinationResult.reason}`);
@@ -10772,30 +10777,45 @@ ${warnLine}
10772
10777
  lastText = textContent;
10773
10778
  break;
10774
10779
  }
10775
- if (hallucinationRetries >= MAX_HALLUCINATION_RETRIES) {
10776
- logger.warn(`Hallucination retries exhausted (${MAX_HALLUCINATION_RETRIES}), returning error`);
10777
- return {
10778
- success: false,
10779
- text: lastText,
10780
- error: t("error.response_blocked", {
10781
- reason: t("hall.max_retries_exhausted")
10782
- }),
10783
- iterationCount: iteration
10784
- };
10785
- }
10786
- hallucinationRetries++;
10787
- logger.warn(`Hallucination retry (${hallucinationRetries}/${MAX_HALLUCINATION_RETRIES}): ${hallucinationResult.reason}`);
10788
- if (textContent) {
10780
+ if (suppressRepetitionRetry && hallucinationResult.kind === "repetition") {
10781
+ suppressRepetitionRetry = false;
10782
+ logger.warn(`Audit-triggered re-answer repetition — not counted as hallucination retry`);
10783
+ const warnLine = `${t("hall.uncertainty_prefix").trim()} ${hallucinationResult.reason ?? ""}`;
10784
+ if (onMeta) {
10785
+ onMeta(`
10786
+ ${pc2.yellow(warnLine)}
10787
+ `);
10788
+ } else if (onChunk) {
10789
+ onChunk(`
10790
+ ${warnLine}
10791
+ `);
10792
+ }
10793
+ } else {
10794
+ if (hallucinationRetries >= MAX_HALLUCINATION_RETRIES) {
10795
+ logger.warn(`Hallucination retries exhausted (${MAX_HALLUCINATION_RETRIES}), returning error`);
10796
+ return {
10797
+ success: false,
10798
+ text: lastText,
10799
+ error: t("error.response_blocked", {
10800
+ reason: t("hall.max_retries_exhausted")
10801
+ }),
10802
+ iterationCount: iteration
10803
+ };
10804
+ }
10805
+ hallucinationRetries++;
10806
+ logger.warn(`Hallucination retry (${hallucinationRetries}/${MAX_HALLUCINATION_RETRIES}): ${hallucinationResult.reason}`);
10807
+ if (textContent) {
10808
+ contextManager.addMessage({
10809
+ role: "assistant",
10810
+ content: textContent
10811
+ });
10812
+ }
10789
10813
  contextManager.addMessage({
10790
- role: "assistant",
10791
- content: textContent
10814
+ role: "user",
10815
+ content: `<system-summary>[Retry context: ${hallucinationResult.reason}. Original task: "${input}". You must either call a needed tool or provide a substantive response. Empty replies are not allowed.]</system-summary>`
10792
10816
  });
10817
+ continue;
10793
10818
  }
10794
- contextManager.addMessage({
10795
- role: "user",
10796
- content: `<system-summary>[Retry context: ${hallucinationResult.reason}. Original task: "${input}". You must either call a needed tool or provide a substantive response. Empty replies are not allowed.]</system-summary>`
10797
- });
10798
- continue;
10799
10819
  }
10800
10820
  if (textContent) {
10801
10821
  contextManager.addMessage({ role: "assistant", content: textContent });
@@ -10847,6 +10867,7 @@ ${warnLine}
10847
10867
  auditFailed = true;
10848
10868
  break;
10849
10869
  }
10870
+ suppressRepetitionRetry = true;
10850
10871
  continue;
10851
10872
  }
10852
10873
  }
@@ -11296,13 +11317,18 @@ class ConfidenceCheck {
11296
11317
  }
11297
11318
  validate(response) {
11298
11319
  if (!response || response.length < MIN_CHARS) {
11299
- return { status: "retry", reason: t("hall.short_response") };
11320
+ return {
11321
+ status: "retry",
11322
+ kind: "short",
11323
+ reason: t("hall.short_response")
11324
+ };
11300
11325
  }
11301
11326
  const wordCount = response.split(/\s+/).filter(Boolean).length;
11302
11327
  const hasStructure = /```|^\s*[-*]\s|^\s*\d+\.\s|<[^>]+>/m.test(response);
11303
11328
  if (wordCount < MIN_WORDS && !hasStructure) {
11304
11329
  return {
11305
11330
  status: "warn",
11331
+ kind: "short",
11306
11332
  reason: t("hall.short_response")
11307
11333
  };
11308
11334
  }
@@ -11311,6 +11337,7 @@ class ConfidenceCheck {
11311
11337
  if (overlap > 0.5) {
11312
11338
  return {
11313
11339
  status: "retry",
11340
+ kind: "repetition",
11314
11341
  reason: t("hall.repetitive", { pct: Math.round(overlap * 100) })
11315
11342
  };
11316
11343
  }
@@ -11322,6 +11349,7 @@ class ConfidenceCheck {
11322
11349
  if (diversity < 0.25) {
11323
11350
  return {
11324
11351
  status: "warn",
11352
+ kind: "diversity",
11325
11353
  reason: t("hall.repetitive", {
11326
11354
  pct: Math.round((1 - diversity) * 100)
11327
11355
  })
@@ -11350,7 +11378,25 @@ function isJsMemberAccess(candidate) {
11350
11378
  const m = lower.match(/^([\w-]+)\./);
11351
11379
  return m !== null && JS_GLOBALS.has(m[1]);
11352
11380
  }
11353
- var JS_GLOBALS;
11381
+ function isFilePathLike(s) {
11382
+ const lower = s.toLowerCase();
11383
+ if (TECH_NAMES.has(lower))
11384
+ return false;
11385
+ if (!s.includes("/") && !s.includes("\\") && !/^\w+\.[a-z]{2,4}$/i.test(s))
11386
+ return false;
11387
+ if (isJsMemberAccess(s))
11388
+ return false;
11389
+ const ext = s.split(".").pop()?.toLowerCase() || "";
11390
+ return !NON_FILE_EXTENSIONS.has(ext);
11391
+ }
11392
+ function extractFileLikeTokens(text) {
11393
+ const matches = text.match(FILE_TOKEN_RE) || [];
11394
+ return [...new Set(matches.filter(isFilePathLike))];
11395
+ }
11396
+ function stripUrls(text) {
11397
+ return text.replace(/\bhttps?:\/\/[^\s"'<>]+/gi, " ");
11398
+ }
11399
+ var JS_GLOBALS, NON_FILE_EXTENSIONS, TECH_NAMES, FILE_TOKEN_RE;
11354
11400
  var init_js_identifiers = __esm(() => {
11355
11401
  JS_GLOBALS = new Set([
11356
11402
  "process",
@@ -11408,22 +11454,46 @@ var init_js_identifiers = __esm(() => {
11408
11454
  "deno",
11409
11455
  "node"
11410
11456
  ]);
11457
+ NON_FILE_EXTENSIONS = new Set([
11458
+ "com",
11459
+ "org",
11460
+ "net",
11461
+ "gov",
11462
+ "edu",
11463
+ "io",
11464
+ "dev",
11465
+ "app",
11466
+ "co",
11467
+ "ai",
11468
+ "ru",
11469
+ "de",
11470
+ "fr",
11471
+ "uk",
11472
+ "us",
11473
+ "es",
11474
+ "it",
11475
+ "jp",
11476
+ "cn",
11477
+ "br"
11478
+ ]);
11479
+ TECH_NAMES = new Set([
11480
+ "node.js",
11481
+ "react.js",
11482
+ "vue.js",
11483
+ "next.js",
11484
+ "angular.js",
11485
+ "express.js",
11486
+ "deno.js",
11487
+ "qwik.js",
11488
+ "svelte.js",
11489
+ "jquery.js"
11490
+ ]);
11491
+ FILE_TOKEN_RE = /\b(?:[a-zA-Z]:[\\/])?[\w./\\-]+\.[a-z]{2,6}\b/gi;
11411
11492
  });
11412
11493
 
11413
11494
  // src/modules/hallucination/factual.ts
11414
11495
  import { existsSync as existsSync22, readdirSync as readdirSync5 } from "fs";
11415
11496
  import { resolve as resolve12, isAbsolute as isAbsolute2, join as join13 } from "path";
11416
- function looksLikeFilePath(s) {
11417
- const lower = s.toLowerCase();
11418
- if (TECH_NAMES.has(lower))
11419
- return false;
11420
- if (!s.includes("/") && !s.includes("\\") && !s.match(/^\w+\.[a-z]{2,4}$/i))
11421
- return false;
11422
- if (isJsMemberAccess(s))
11423
- return false;
11424
- const ext = s.split(".").pop()?.toLowerCase() || "";
11425
- return !NON_FILE_EXTENSIONS.has(ext);
11426
- }
11427
11497
 
11428
11498
  class FactualCheck {
11429
11499
  baseDir;
@@ -11522,12 +11592,10 @@ class FactualCheck {
11522
11592
  return count;
11523
11593
  }
11524
11594
  extractFilePaths(text) {
11525
- const pattern = /\b(?:[a-zA-Z]:[\\/])?[\w./\\-]+\.[a-z]{2,6}\b/gi;
11526
- const matches = text.match(pattern) || [];
11527
- return [...new Set(matches)].filter(looksLikeFilePath);
11595
+ return extractFileLikeTokens(stripUrls(text));
11528
11596
  }
11529
11597
  }
11530
- var IGNORED_DIRS, NON_FILE_EXTENSIONS, TECH_NAMES;
11598
+ var IGNORED_DIRS;
11531
11599
  var init_factual = __esm(() => {
11532
11600
  init_i18n();
11533
11601
  init_js_identifiers();
@@ -11540,40 +11608,6 @@ var init_factual = __esm(() => {
11540
11608
  ".mma",
11541
11609
  "_testing"
11542
11610
  ]);
11543
- NON_FILE_EXTENSIONS = new Set([
11544
- "com",
11545
- "org",
11546
- "net",
11547
- "gov",
11548
- "edu",
11549
- "io",
11550
- "dev",
11551
- "app",
11552
- "co",
11553
- "ai",
11554
- "ru",
11555
- "de",
11556
- "fr",
11557
- "uk",
11558
- "us",
11559
- "es",
11560
- "it",
11561
- "jp",
11562
- "cn",
11563
- "br"
11564
- ]);
11565
- TECH_NAMES = new Set([
11566
- "node.js",
11567
- "react.js",
11568
- "vue.js",
11569
- "next.js",
11570
- "angular.js",
11571
- "express.js",
11572
- "deno.js",
11573
- "qwik.js",
11574
- "svelte.js",
11575
- "jquery.js"
11576
- ]);
11577
11611
  });
11578
11612
 
11579
11613
  // src/modules/hallucination/llm-judge.ts
@@ -12433,7 +12467,7 @@ var init_web_fetch = __esm(() => {
12433
12467
  init_security();
12434
12468
  webFetchTool = {
12435
12469
  name: "web_fetch",
12436
- description: "Fetch a URL and convert its content to markdown. Use for reading documentation, APIs, web pages.",
12470
+ description: 'Fetch a URL and convert its content to markdown. Use for reading documentation, APIs, web pages. This is a plain HTTP request without a browser engine — it does NOT run JavaScript, so JS-rendered single-page apps (weather sites, dashboards, modern SPAs) return empty or near-empty content; use the "browser" tool for those.',
12437
12471
  tags: ["research"],
12438
12472
  parameters: {
12439
12473
  type: "object",
@@ -12519,7 +12553,7 @@ var init_web_browse = __esm(() => {
12519
12553
  init_session_isolation();
12520
12554
  webBrowseTool = {
12521
12555
  name: "web_browse",
12522
- description: `Fetch and read a web page. Returns the page content as plain text (max ${MAX_PREVIEW_LINES} lines / ${MAX_CHARS2} chars).`,
12556
+ description: `Fetch and read a web page. Returns the page content as plain text (max ${MAX_PREVIEW_LINES} lines / ${MAX_CHARS2} chars). This is a plain HTTP request — it does NOT execute JavaScript, so JS-rendered single-page apps (weather sites, dashboards, modern SPAs) return empty or near-empty content. For such pages use the "browser" tool instead.`,
12523
12557
  tags: ["research"],
12524
12558
  parameters: {
12525
12559
  type: "object",
@@ -14619,6 +14653,7 @@ function createBrowserTool() {
14619
14653
  tags: ["browser", "vision"],
14620
14654
  description: [
14621
14655
  "Control a headless browser. Navigate pages, click elements, type text, scroll, take screenshots.",
14656
+ "Runs a real browser with JavaScript enabled — use it for JS-rendered sites (weather, dashboards, SPAs) where web_fetch/web_browse return empty content.",
14622
14657
  "Each snapshot returns: a numbered list of interactive elements, the visible page text (Content section), browser console messages, and network errors.",
14623
14658
  "Use the Content section to understand what the page says; use element numbers as targets for click/type.",
14624
14659
  "Actions: open (url), click (target), type (target, text), scroll (direction), back, forward, screenshot, snapshot, close, wait (ms)."
@@ -15480,10 +15515,7 @@ class Auditor {
15480
15515
  }
15481
15516
  async audit(plan) {
15482
15517
  const allStepText = plan.steps.map((s) => s.description.replace(/\([^)]*\)/g, " ")).join(" ");
15483
- const fileMatches = allStepText.match(/\b[\w./-]+\.[a-z]+/gi) || [];
15484
- const uniqueFiles = [
15485
- ...new Set(fileMatches.filter((f) => !isJsMemberAccess(f)))
15486
- ];
15518
+ const uniqueFiles = extractFileLikeTokens(stripUrls(allStepText));
15487
15519
  const missingFiles = [];
15488
15520
  const existingFiles = [];
15489
15521
  for (const filePath of uniqueFiles) {
@@ -16031,11 +16063,15 @@ ${display}`,
16031
16063
  }
16032
16064
  const status = String(args.status || "done");
16033
16065
  if (status === "done" && target.status === "done") {
16066
+ const progress2 = this.tracker.getProgressString();
16067
+ const display2 = this.tracker.toPromptBlock();
16034
16068
  return {
16035
- success: false,
16036
- output: t("plan.step_already_done", {
16069
+ success: true,
16070
+ output: `${t("plan.step_already_done", {
16037
16071
  step: String(stepId)
16038
- })
16072
+ })}
16073
+ ${progress2}`,
16074
+ display: display2
16039
16075
  };
16040
16076
  }
16041
16077
  if (status === "done") {
@@ -19295,7 +19331,7 @@ async function runSetup(externalRl) {
19295
19331
  const idx = Math.max(0, Math.min(models.length - 1, (parseInt(choice) || 1) - 1));
19296
19332
  model = models[idx];
19297
19333
  } else {
19298
- model = await ask(rl, t("setup.model_name"), "qwen3.5-9b");
19334
+ model = await ask(rl, t("setup.model_name"), "qwen/qwen3.5-9b");
19299
19335
  }
19300
19336
  const connected = await withSpinner(t("setup.testing_spinner", { model }), () => testChat(apiBase, apiKey, model));
19301
19337
  if (connected) {
@@ -28583,7 +28619,725 @@ init_bootstrap();
28583
28619
 
28584
28620
  // src/cli/repl.ts
28585
28621
  init_colors();
28622
+ import * as readline3 from "readline";
28623
+
28624
+ // src/ui/line-editor.ts
28625
+ init_string_width();
28586
28626
  import * as readline2 from "readline";
28627
+
28628
+ // src/ui/line-math.ts
28629
+ init_string_width();
28630
+ var ANSI_RE = /\x1b\[[0-9;?]*[ -/]*[@-~]/g;
28631
+ function stripAnsi2(s) {
28632
+ return s.replace(ANSI_RE, "");
28633
+ }
28634
+ function charLen(s) {
28635
+ return Array.from(s).length;
28636
+ }
28637
+ function wrapLine(line, width) {
28638
+ const w = width > 0 ? width : 80;
28639
+ const chars = Array.from(line);
28640
+ const rows = [];
28641
+ let start = 0;
28642
+ let cells = 0;
28643
+ for (let i = 0;i < chars.length; i++) {
28644
+ const ch = chars[i];
28645
+ if (ch === `
28646
+ `) {
28647
+ rows.push({ text: chars.slice(start, i).join(""), charStart: start });
28648
+ start = i + 1;
28649
+ cells = 0;
28650
+ continue;
28651
+ }
28652
+ const cw = stringWidth(ch);
28653
+ if (cells + cw > w && cells > 0) {
28654
+ rows.push({ text: chars.slice(start, i).join(""), charStart: start });
28655
+ start = i;
28656
+ cells = cw;
28657
+ } else {
28658
+ cells += cw;
28659
+ }
28660
+ }
28661
+ rows.push({ text: chars.slice(start).join(""), charStart: start });
28662
+ return rows;
28663
+ }
28664
+ function buildLayout(lines, width) {
28665
+ const rows = [];
28666
+ for (let b = 0;b < lines.length; b++) {
28667
+ for (const r of wrapLine(lines[b], width)) {
28668
+ rows.push({ bufRow: b, charStart: r.charStart, text: r.text });
28669
+ }
28670
+ }
28671
+ return rows;
28672
+ }
28673
+ function layoutIndexFor(rows, bufRow, col) {
28674
+ let idx = 0;
28675
+ for (let i = 0;i < rows.length; i++) {
28676
+ if (rows[i].bufRow === bufRow && rows[i].charStart <= col)
28677
+ idx = i;
28678
+ }
28679
+ return idx;
28680
+ }
28681
+ function visualColAt(rows, idx, col) {
28682
+ const r = rows[idx];
28683
+ const upTo = col - r.charStart;
28684
+ return stringWidth(Array.from(r.text).slice(0, upTo).join(""));
28685
+ }
28686
+ function charOffsetFor(rows, idx, visualCol) {
28687
+ const r = rows[idx];
28688
+ const chars = Array.from(r.text);
28689
+ let cells = 0;
28690
+ for (let i = 0;i < chars.length; i++) {
28691
+ const cw = stringWidth(chars[i]);
28692
+ if (cells + cw > visualCol)
28693
+ return r.charStart + i;
28694
+ cells += cw;
28695
+ }
28696
+ return r.charStart + chars.length;
28697
+ }
28698
+
28699
+ // src/ui/line-editor.ts
28700
+ class LineEditor {
28701
+ input;
28702
+ output;
28703
+ onKeyInput;
28704
+ promptStr;
28705
+ completer;
28706
+ history;
28707
+ historySize;
28708
+ lines = [""];
28709
+ row = 0;
28710
+ col = 0;
28711
+ historyIndex = -1;
28712
+ stash = null;
28713
+ isDone = false;
28714
+ isPaste = false;
28715
+ lastWasCR = false;
28716
+ questionCb = null;
28717
+ rawMode = false;
28718
+ listeners = {};
28719
+ prevCursorRow = 0;
28720
+ constructor(opts) {
28721
+ this.input = opts.input;
28722
+ this.output = opts.output;
28723
+ this.promptStr = opts.prompt ?? "";
28724
+ this.completer = opts.completer;
28725
+ this.history = opts.history ?? [];
28726
+ this.historySize = opts.historySize ?? 50;
28727
+ if (this.history.length > this.historySize) {
28728
+ this.history = this.history.slice(-this.historySize);
28729
+ }
28730
+ if (this.input.isTTY) {
28731
+ this.rawMode = typeof this.input.setRawMode === "function";
28732
+ if (this.rawMode)
28733
+ this.input.setRawMode(true);
28734
+ this.enableTerminalProtocols();
28735
+ readline2.emitKeypressEvents(this.input);
28736
+ this.input.on("keypress", (str, key) => this.onKey(str, key));
28737
+ } else {
28738
+ this.input.on("data", (buf) => this.onData(buf));
28739
+ this.input.on("end", () => {
28740
+ const last = this.lines.join(`
28741
+ `);
28742
+ this.lines = [""];
28743
+ this.row = 0;
28744
+ this.col = 0;
28745
+ if (last)
28746
+ this.emitLine(last);
28747
+ this.emitClose();
28748
+ });
28749
+ }
28750
+ }
28751
+ on(event, listener) {
28752
+ if (event === "line") {
28753
+ this.listeners.line = listener;
28754
+ } else {
28755
+ this.listeners.close = listener;
28756
+ }
28757
+ return this;
28758
+ }
28759
+ setPrompt(prompt) {
28760
+ this.promptStr = prompt;
28761
+ if (this.input.isTTY)
28762
+ this.render();
28763
+ }
28764
+ prompt(_preserveCursor) {
28765
+ if (this.input.isTTY)
28766
+ this.render();
28767
+ }
28768
+ write(s) {
28769
+ this.insertText(s);
28770
+ }
28771
+ reset() {
28772
+ this.lines = [""];
28773
+ this.row = 0;
28774
+ this.col = 0;
28775
+ this.historyIndex = -1;
28776
+ this.stash = null;
28777
+ this.prevCursorRow = 0;
28778
+ if (this.input.isTTY)
28779
+ this.render();
28780
+ }
28781
+ question(query, cb) {
28782
+ this.questionCb = cb;
28783
+ this.promptStr = query;
28784
+ this.lines = [""];
28785
+ this.row = 0;
28786
+ this.col = 0;
28787
+ this.prevCursorRow = 0;
28788
+ if (this.input.isTTY)
28789
+ this.render();
28790
+ }
28791
+ close() {
28792
+ if (this.isDone)
28793
+ return;
28794
+ this.isDone = true;
28795
+ this.disableTerminalProtocols();
28796
+ if (this.rawMode && this.input.setRawMode) {
28797
+ this.input.setRawMode(false);
28798
+ }
28799
+ this.emitClose();
28800
+ }
28801
+ onData(buf) {
28802
+ if (this.isDone)
28803
+ return;
28804
+ let text = buf.toString("utf-8");
28805
+ text = text.replace(/\r\n/g, `
28806
+ `).replace(/\r/g, `
28807
+ `);
28808
+ const parts = text.split(`
28809
+ `);
28810
+ for (let i = 0;i < parts.length; i++) {
28811
+ if (i < parts.length - 1) {
28812
+ this.emitLine(parts[i]);
28813
+ this.lines = [""];
28814
+ this.row = 0;
28815
+ this.col = 0;
28816
+ } else if (parts[i]) {
28817
+ this.emitLine(parts[i]);
28818
+ this.lines = [""];
28819
+ this.row = 0;
28820
+ this.col = 0;
28821
+ }
28822
+ }
28823
+ }
28824
+ onKey(str, key) {
28825
+ if (this.isDone)
28826
+ return;
28827
+ if (this.onKeyInput && this.onKeyInput(str, key))
28828
+ return;
28829
+ const k = key;
28830
+ const name = k?.name ?? "undefined";
28831
+ const seq2 = k?.sequence ?? str ?? "";
28832
+ if (name === "undefined" && /^\x1b\[\d+(?:;\d+)?u$/.test(seq2)) {
28833
+ const m = seq2.match(/^\x1b\[(\d+)(?:;(\d+))?u$/);
28834
+ if (m && Number(m[1]) === 13 && Number(m[2] ?? 0) !== 0) {
28835
+ this.insertNewline();
28836
+ }
28837
+ return;
28838
+ }
28839
+ if (name === "paste-start" || seq2 === "\x1B[200~") {
28840
+ this.isPaste = true;
28841
+ return;
28842
+ }
28843
+ if (name === "paste-end" || seq2 === "\x1B[201~") {
28844
+ this.isPaste = false;
28845
+ this.render();
28846
+ return;
28847
+ }
28848
+ if (this.isPaste) {
28849
+ if (name === "return" || seq2 === "\r" || seq2 === `
28850
+ `) {
28851
+ this.insertText(`
28852
+ `);
28853
+ } else if (str) {
28854
+ this.insertText(str);
28855
+ }
28856
+ this.render();
28857
+ return;
28858
+ }
28859
+ if (name === "return" && (seq2 === "\x1B\r" || k.meta)) {
28860
+ this.insertNewline();
28861
+ return;
28862
+ }
28863
+ if (name === "undefined" && seq2 === "\x1B\r") {
28864
+ this.insertNewline();
28865
+ return;
28866
+ }
28867
+ if (k?.ctrl && name === "j") {
28868
+ this.insertNewline();
28869
+ return;
28870
+ }
28871
+ if (name === "escape")
28872
+ return;
28873
+ switch (name) {
28874
+ case "return":
28875
+ this.lastWasCR = true;
28876
+ this.submit();
28877
+ return;
28878
+ case "enter":
28879
+ if (this.lastWasCR) {
28880
+ this.lastWasCR = false;
28881
+ return;
28882
+ }
28883
+ this.insertNewline();
28884
+ return;
28885
+ case "backspace":
28886
+ this.lastWasCR = false;
28887
+ this.backspace();
28888
+ return;
28889
+ case "delete":
28890
+ this.lastWasCR = false;
28891
+ this.deleteForward();
28892
+ return;
28893
+ case "left":
28894
+ this.lastWasCR = false;
28895
+ this.moveLeft();
28896
+ return;
28897
+ case "right":
28898
+ this.lastWasCR = false;
28899
+ this.moveRight();
28900
+ return;
28901
+ case "up":
28902
+ this.lastWasCR = false;
28903
+ this.moveUp();
28904
+ return;
28905
+ case "down":
28906
+ this.lastWasCR = false;
28907
+ this.moveDown();
28908
+ return;
28909
+ case "home":
28910
+ this.lastWasCR = false;
28911
+ this.moveHome();
28912
+ return;
28913
+ case "end":
28914
+ this.lastWasCR = false;
28915
+ this.moveEnd();
28916
+ return;
28917
+ case "pageup":
28918
+ this.lastWasCR = false;
28919
+ this.moveToStart();
28920
+ return;
28921
+ case "pagedown":
28922
+ this.lastWasCR = false;
28923
+ this.moveToEnd();
28924
+ return;
28925
+ case "tab":
28926
+ this.lastWasCR = false;
28927
+ this.tabComplete();
28928
+ return;
28929
+ }
28930
+ if (k?.ctrl) {
28931
+ this.lastWasCR = false;
28932
+ switch (name) {
28933
+ case "c":
28934
+ this.ctrlC();
28935
+ return;
28936
+ case "d":
28937
+ this.ctrlD();
28938
+ return;
28939
+ case "a":
28940
+ this.moveHome();
28941
+ return;
28942
+ case "e":
28943
+ this.moveEnd();
28944
+ return;
28945
+ case "u":
28946
+ this.killToStart();
28947
+ return;
28948
+ case "k":
28949
+ this.killToEnd();
28950
+ return;
28951
+ case "w":
28952
+ this.killWordBefore();
28953
+ return;
28954
+ case "l":
28955
+ this.clearScreen();
28956
+ return;
28957
+ default:
28958
+ return;
28959
+ }
28960
+ }
28961
+ if (k?.meta) {
28962
+ this.lastWasCR = false;
28963
+ switch (name) {
28964
+ case "b":
28965
+ this.moveWordLeft();
28966
+ return;
28967
+ case "f":
28968
+ this.moveWordRight();
28969
+ return;
28970
+ case "d":
28971
+ this.killWordAfter();
28972
+ return;
28973
+ default:
28974
+ return;
28975
+ }
28976
+ }
28977
+ if (str && !/^[\u0000-\u001f\u007f]/.test(str)) {
28978
+ this.lastWasCR = false;
28979
+ this.insertText(str);
28980
+ }
28981
+ }
28982
+ submit() {
28983
+ if (this.questionCb) {
28984
+ const cb = this.questionCb;
28985
+ this.questionCb = null;
28986
+ const answer = this.lines.join(`
28987
+ `).trim();
28988
+ this.commitFrame();
28989
+ this.output.write(`\r
28990
+ `);
28991
+ this.lines = [""];
28992
+ this.row = 0;
28993
+ this.col = 0;
28994
+ this.prevCursorRow = 0;
28995
+ cb(answer);
28996
+ return;
28997
+ }
28998
+ const text = this.lines.join(`
28999
+ `);
29000
+ if (text.trim()) {
29001
+ this.history.push(text);
29002
+ if (this.history.length > this.historySize) {
29003
+ this.history = this.history.slice(-this.historySize);
29004
+ }
29005
+ }
29006
+ this.historyIndex = -1;
29007
+ this.stash = null;
29008
+ this.commitFrame();
29009
+ this.output.write(`\r
29010
+ `);
29011
+ this.lines = [""];
29012
+ this.row = 0;
29013
+ this.col = 0;
29014
+ this.prevCursorRow = 0;
29015
+ this.emitLine(text);
29016
+ }
29017
+ commitFrame() {
29018
+ const layout = this.layout();
29019
+ const idx = layoutIndexFor(layout, this.row, this.col);
29020
+ const rowsDown = layout.length - 1 - idx;
29021
+ const lastLen = charLen(stripAnsi2(layout[layout.length - 1].text));
29022
+ const out = [];
29023
+ if (rowsDown > 0)
29024
+ out.push(`\x1B[${rowsDown}B`);
29025
+ out.push("\r");
29026
+ if (lastLen > 0)
29027
+ out.push(`\x1B[${lastLen}C`);
29028
+ this.output.write(out.join(""));
29029
+ }
29030
+ ctrlC() {
29031
+ this.lines = [""];
29032
+ this.row = 0;
29033
+ this.col = 0;
29034
+ this.prevCursorRow = 0;
29035
+ this.render();
29036
+ process.emit("SIGINT");
29037
+ }
29038
+ ctrlD() {
29039
+ if (this.lines.length === 1 && this.lines[0] === "") {
29040
+ this.emitClose();
29041
+ } else {
29042
+ this.deleteForward();
29043
+ }
29044
+ }
29045
+ currentChars() {
29046
+ return Array.from(this.lines[this.row]);
29047
+ }
29048
+ setCurrentChars(chars) {
29049
+ this.lines[this.row] = chars.join("");
29050
+ }
29051
+ insertText(text) {
29052
+ const sanitized = text.replace(/\r\n/g, `
29053
+ `).replace(/\r/g, "");
29054
+ const chars = this.currentChars();
29055
+ const t2 = Array.from(sanitized);
29056
+ chars.splice(this.col, 0, ...t2);
29057
+ this.setCurrentChars(chars);
29058
+ this.col += t2.length;
29059
+ this.render();
29060
+ }
29061
+ insertNewline() {
29062
+ const chars = this.currentChars();
29063
+ const rest = chars.slice(this.col).join("");
29064
+ this.setCurrentChars(chars.slice(0, this.col));
29065
+ this.lines.splice(this.row + 1, 0, rest);
29066
+ this.row++;
29067
+ this.col = 0;
29068
+ this.render();
29069
+ }
29070
+ backspace() {
29071
+ const chars = this.currentChars();
29072
+ if (this.col > 0) {
29073
+ chars.splice(this.col - 1, 1);
29074
+ this.setCurrentChars(chars);
29075
+ this.col--;
29076
+ } else if (this.row > 0) {
29077
+ const prevLen = charLen(this.lines[this.row - 1]);
29078
+ this.lines[this.row - 1] = this.lines[this.row - 1] + this.lines[this.row];
29079
+ this.lines.splice(this.row, 1);
29080
+ this.row--;
29081
+ this.col = prevLen;
29082
+ }
29083
+ this.render();
29084
+ }
29085
+ deleteForward() {
29086
+ const chars = this.currentChars();
29087
+ if (this.col < chars.length) {
29088
+ chars.splice(this.col, 1);
29089
+ this.setCurrentChars(chars);
29090
+ } else if (this.row < this.lines.length - 1) {
29091
+ this.lines[this.row] = this.lines[this.row] + this.lines[this.row + 1];
29092
+ this.lines.splice(this.row + 1, 1);
29093
+ }
29094
+ this.render();
29095
+ }
29096
+ killToStart() {
29097
+ const chars = this.currentChars();
29098
+ this.setCurrentChars(chars.slice(this.col));
29099
+ this.col = 0;
29100
+ this.render();
29101
+ }
29102
+ killToEnd() {
29103
+ const chars = this.currentChars();
29104
+ this.setCurrentChars(chars.slice(0, this.col));
29105
+ this.render();
29106
+ }
29107
+ killWordBefore() {
29108
+ const chars = this.currentChars();
29109
+ let i = this.col;
29110
+ while (i > 0 && /\s/.test(chars[i - 1]))
29111
+ i--;
29112
+ while (i > 0 && !/\s/.test(chars[i - 1]))
29113
+ i--;
29114
+ chars.splice(i, this.col - i);
29115
+ this.setCurrentChars(chars);
29116
+ this.col = i;
29117
+ this.render();
29118
+ }
29119
+ killWordAfter() {
29120
+ const chars = this.currentChars();
29121
+ const n = chars.length;
29122
+ let i = this.col;
29123
+ while (i < n && /\s/.test(chars[i]))
29124
+ i++;
29125
+ while (i < n && !/\s/.test(chars[i]))
29126
+ i++;
29127
+ chars.splice(this.col, i - this.col);
29128
+ this.setCurrentChars(chars);
29129
+ this.render();
29130
+ }
29131
+ moveLeft() {
29132
+ if (this.col > 0) {
29133
+ this.col--;
29134
+ } else if (this.row > 0) {
29135
+ this.row--;
29136
+ this.col = charLen(this.lines[this.row]);
29137
+ }
29138
+ this.render();
29139
+ }
29140
+ moveRight() {
29141
+ if (this.col < charLen(this.lines[this.row])) {
29142
+ this.col++;
29143
+ } else if (this.row < this.lines.length - 1) {
29144
+ this.row++;
29145
+ this.col = 0;
29146
+ }
29147
+ this.render();
29148
+ }
29149
+ moveUp() {
29150
+ const layout = this.layout();
29151
+ const idx = layoutIndexFor(layout, this.row, this.col);
29152
+ const vcol = visualColAt(layout, idx, this.col);
29153
+ if (idx > 0) {
29154
+ const target = layout[idx - 1];
29155
+ this.row = target.bufRow;
29156
+ this.col = charOffsetFor(layout, idx - 1, vcol);
29157
+ this.render();
29158
+ } else {
29159
+ this.historyPrev();
29160
+ }
29161
+ }
29162
+ moveDown() {
29163
+ const layout = this.layout();
29164
+ const idx = layoutIndexFor(layout, this.row, this.col);
29165
+ const vcol = visualColAt(layout, idx, this.col);
29166
+ if (idx < layout.length - 1) {
29167
+ const target = layout[idx + 1];
29168
+ this.row = target.bufRow;
29169
+ this.col = charOffsetFor(layout, idx + 1, vcol);
29170
+ this.render();
29171
+ } else {
29172
+ this.historyNext();
29173
+ }
29174
+ }
29175
+ moveHome() {
29176
+ this.col = 0;
29177
+ this.render();
29178
+ }
29179
+ moveEnd() {
29180
+ this.col = charLen(this.lines[this.row]);
29181
+ this.render();
29182
+ }
29183
+ moveToStart() {
29184
+ this.row = 0;
29185
+ this.col = 0;
29186
+ this.render();
29187
+ }
29188
+ moveToEnd() {
29189
+ this.row = this.lines.length - 1;
29190
+ this.col = charLen(this.lines[this.row]);
29191
+ this.render();
29192
+ }
29193
+ moveWordLeft() {
29194
+ const chars = this.currentChars();
29195
+ let i = this.col;
29196
+ while (i > 0 && /\s/.test(chars[i - 1]))
29197
+ i--;
29198
+ while (i > 0 && !/\s/.test(chars[i - 1]))
29199
+ i--;
29200
+ this.col = i;
29201
+ this.render();
29202
+ }
29203
+ moveWordRight() {
29204
+ const chars = this.currentChars();
29205
+ const n = chars.length;
29206
+ let i = this.col;
29207
+ while (i < n && !/\s/.test(chars[i]))
29208
+ i++;
29209
+ while (i < n && /\s/.test(chars[i]))
29210
+ i++;
29211
+ this.col = i;
29212
+ this.render();
29213
+ }
29214
+ historyPrev() {
29215
+ if (this.history.length === 0)
29216
+ return;
29217
+ if (this.historyIndex === -1) {
29218
+ this.stash = {
29219
+ lines: this.lines.slice(),
29220
+ row: this.row,
29221
+ col: this.col
29222
+ };
29223
+ this.historyIndex = 0;
29224
+ } else if (this.historyIndex < this.history.length - 1) {
29225
+ this.historyIndex++;
29226
+ } else {
29227
+ return;
29228
+ }
29229
+ this.applyHistoryEntry();
29230
+ }
29231
+ historyNext() {
29232
+ if (this.historyIndex === -1)
29233
+ return;
29234
+ if (this.historyIndex > 0) {
29235
+ this.historyIndex--;
29236
+ this.applyHistoryEntry();
29237
+ } else if (this.stash) {
29238
+ this.lines = this.stash.lines.slice();
29239
+ this.row = this.stash.row;
29240
+ this.col = this.stash.col;
29241
+ this.historyIndex = -1;
29242
+ this.stash = null;
29243
+ this.render();
29244
+ }
29245
+ }
29246
+ applyHistoryEntry() {
29247
+ const entry = this.history[this.history.length - 1 - this.historyIndex];
29248
+ this.lines = entry.split(`
29249
+ `);
29250
+ if (this.lines.length === 0)
29251
+ this.lines = [""];
29252
+ this.row = this.lines.length - 1;
29253
+ this.col = charLen(this.lines[this.row]);
29254
+ this.render();
29255
+ }
29256
+ tabComplete() {
29257
+ if (!this.completer)
29258
+ return;
29259
+ const line = this.lines[this.row];
29260
+ const [matches, partial] = this.completer(line);
29261
+ if (matches.length === 0) {
29262
+ this.output.write("\x07");
29263
+ return;
29264
+ }
29265
+ if (matches.length === 1) {
29266
+ const from = line.lastIndexOf(partial);
29267
+ const head = line.slice(0, from);
29268
+ const tail = line.slice(from + partial.length);
29269
+ this.lines[this.row] = head + matches[0] + tail;
29270
+ this.col = charLen(head) + charLen(matches[0]);
29271
+ this.render();
29272
+ return;
29273
+ }
29274
+ const lines = matches.map((m) => " " + m);
29275
+ this.output.write(`\r
29276
+ ` + lines.join(`\r
29277
+ `) + `\r
29278
+ `);
29279
+ this.output.write(`\x1B[${lines.length + 1}A`);
29280
+ this.render();
29281
+ }
29282
+ clearScreen() {
29283
+ this.output.write("\x1B[2J\x1B[H");
29284
+ this.prevCursorRow = 0;
29285
+ this.render();
29286
+ }
29287
+ columns() {
29288
+ return this.output.columns || 80;
29289
+ }
29290
+ layout() {
29291
+ return buildLayout(this.lines, this.columns());
29292
+ }
29293
+ render() {
29294
+ if (!this.input.isTTY || this.isDone)
29295
+ return;
29296
+ const layout = this.layout();
29297
+ const promptWidth = stringWidth(stripAnsi2(this.promptStr));
29298
+ const idx = layoutIndexFor(layout, this.row, this.col);
29299
+ const vcol = visualColAt(layout, idx, this.col);
29300
+ const rowsAfter = layout.length - idx - 1;
29301
+ const ccol = (idx === 0 ? promptWidth : 0) + vcol;
29302
+ const out = [];
29303
+ if (this.prevCursorRow > 0)
29304
+ out.push(`\x1B[${this.prevCursorRow}A`);
29305
+ out.push("\r");
29306
+ for (let i = 0;i < layout.length; i++) {
29307
+ out.push("\x1B[2K");
29308
+ if (i === 0)
29309
+ out.push(this.promptStr);
29310
+ out.push(layout[i].text);
29311
+ if (i < layout.length - 1)
29312
+ out.push(`\r
29313
+ `);
29314
+ }
29315
+ out.push("\x1B[J");
29316
+ if (rowsAfter > 0)
29317
+ out.push(`\x1B[${rowsAfter}A`);
29318
+ out.push("\r");
29319
+ if (ccol > 0)
29320
+ out.push(`\x1B[${ccol}C`);
29321
+ this.prevCursorRow = idx;
29322
+ this.output.write(out.join(""));
29323
+ }
29324
+ enableTerminalProtocols() {
29325
+ this.output.write("\x1B[?2004h");
29326
+ this.output.write("\x1B[>1u");
29327
+ }
29328
+ disableTerminalProtocols() {
29329
+ this.output.write("\x1B[?2004l");
29330
+ this.output.write("\x1B[<u");
29331
+ }
29332
+ emitLine(line) {
29333
+ this.listeners.line?.(line);
29334
+ }
29335
+ emitClose() {
29336
+ this.listeners.close?.();
29337
+ }
29338
+ }
29339
+
29340
+ // src/cli/repl.ts
28587
29341
  import { existsSync as existsSync45, readFileSync as readFileSync31, writeFileSync as writeFileSync16 } from "fs";
28588
29342
  import { join as join40, dirname as dirname13 } from "path";
28589
29343
  import { homedir as homedir17 } from "os";
@@ -28885,14 +29639,6 @@ class FormattingStream {
28885
29639
  return result;
28886
29640
  }
28887
29641
  }
28888
- function formatMarkdown(text) {
28889
- const lines = [];
28890
- const stream = new FormattingStream((l) => lines.push(l));
28891
- stream.write(text);
28892
- stream.flush();
28893
- return lines.join(`
28894
- `);
28895
- }
28896
29642
 
28897
29643
  // src/ui/renderer.ts
28898
29644
  init_spinner();
@@ -29145,6 +29891,7 @@ class Repl {
29145
29891
  baseDir;
29146
29892
  noAgentsMd;
29147
29893
  pendingClipboardImage = null;
29894
+ exitOnClose = false;
29148
29895
  rl;
29149
29896
  agent;
29150
29897
  config;
@@ -29152,9 +29899,10 @@ class Repl {
29152
29899
  skillsModule;
29153
29900
  pluginManager;
29154
29901
  logger;
29155
- constructor(agent, config, sessionManager, skillsModule, pluginManager, configDir, baseDir, noAgentsMd, logger) {
29902
+ constructor(agent, config, sessionManager, skillsModule, pluginManager, configDir, baseDir, noAgentsMd, logger, exitOnClose) {
29156
29903
  this.agent = agent;
29157
29904
  this.config = config;
29905
+ this.exitOnClose = exitOnClose === true;
29158
29906
  this.sessionManager = sessionManager;
29159
29907
  this.skillsModule = skillsModule;
29160
29908
  this.pluginManager = pluginManager;
@@ -29164,7 +29912,19 @@ class Repl {
29164
29912
  this.noAgentsMd = noAgentsMd === true;
29165
29913
  this.historyPath = join40(homedir17(), ".mma", "repl-history");
29166
29914
  this.loadHistory();
29167
- this.rl = readline2.createInterface({
29915
+ this.rl = process.stdin.isTTY ? new LineEditor({
29916
+ input: process.stdin,
29917
+ output: process.stdout,
29918
+ prompt: pc2.cyan("> "),
29919
+ history: this.history,
29920
+ historySize: this.maxHistory,
29921
+ completer: (line) => {
29922
+ const [matches, partial] = this.completer.complete(line);
29923
+ if (matches.length > 0)
29924
+ return [matches, partial];
29925
+ return [[], line];
29926
+ }
29927
+ }) : readline3.createInterface({
29168
29928
  input: process.stdin,
29169
29929
  output: process.stdout,
29170
29930
  prompt: pc2.cyan("> "),
@@ -29281,46 +30041,22 @@ class Repl {
29281
30041
  });
29282
30042
  this.rl.on("close", () => {
29283
30043
  this.running = false;
30044
+ this.saveHistory();
30045
+ this.agent.shutdown();
30046
+ if (this.exitOnClose)
30047
+ process.exit(0);
29284
30048
  });
29285
- if (process.stdin.isTTY) {
29286
- readline2.emitKeypressEvents(process.stdin);
29287
- process.stdin.on("keypress", async (str, key) => {
29288
- if (key.name === "escape") {
29289
- const escBytes = key.sequence ? (key.sequence.match(/\x1b/g) || []).length : 1;
29290
- const now = Date.now();
29291
- const withinWindow = now - this.lastEscTime < this.doubleEscDelay;
29292
- this.lastEscTime = now;
29293
- if (escBytes >= 2 || withinWindow) {
29294
- this.lastEscTime = 0;
29295
- if (this.agentRunning) {
29296
- process.stdout.write(pc2.yellow(`
29297
- ${t("repl.interrupt")}
29298
- `));
29299
- this.agent.shutdown();
29300
- }
29301
- }
29302
- return;
29303
- }
29304
- if (key.ctrl && key.name === "v" && !this.agentRunning) {
29305
- try {
29306
- const { readClipboardImage: readClipboardImage2, bufferToDataUrl: bufferToDataUrl2 } = await Promise.resolve().then(() => (init_image_utils(), exports_image_utils));
29307
- const clipBuf = await readClipboardImage2();
29308
- if (clipBuf) {
29309
- const { dataUrl } = await bufferToDataUrl2(clipBuf);
29310
- this.pendingClipboardImage = dataUrl;
29311
- const sizeKb = Math.round(dataUrl.length * 3 / 4 / 1024);
29312
- console.log(pc2.green(`
29313
- ${t("image.attached", { source: "clipboard", size: `${sizeKb} KB` })}`));
29314
- this.rl.prompt();
29315
- } else {
29316
- console.log(pc2.yellow(`
29317
- ${t("image.clipboard_empty")}`));
29318
- this.rl.prompt();
29319
- }
29320
- } catch {}
29321
- }
30049
+ if (this.exitOnClose && !process.stdin.isTTY) {
30050
+ process.stdin.on("end", () => {
30051
+ this.running = false;
30052
+ this.saveHistory();
30053
+ this.agent.shutdown();
30054
+ process.exit(0);
29322
30055
  });
29323
30056
  }
30057
+ if (this.rl instanceof LineEditor) {
30058
+ this.rl.onKeyInput = (str, key) => this.handleSpecialKey(str, key);
30059
+ }
29324
30060
  let forceExitTimer = null;
29325
30061
  process.on("SIGINT", () => {
29326
30062
  if (this.agentRunning) {
@@ -29336,6 +30072,51 @@ ${t("image.clipboard_empty")}`));
29336
30072
  }
29337
30073
  });
29338
30074
  }
30075
+ handleSpecialKey(str, key) {
30076
+ if (key.name === "escape") {
30077
+ const escBytes = key.sequence ? (key.sequence.match(/\x1b/g) || []).length : 1;
30078
+ const now = Date.now();
30079
+ const withinWindow = now - this.lastEscTime < this.doubleEscDelay;
30080
+ this.lastEscTime = now;
30081
+ if (escBytes >= 2 || withinWindow) {
30082
+ this.lastEscTime = 0;
30083
+ if (this.agentRunning) {
30084
+ process.stdout.write(pc2.yellow(`
30085
+ ${t("repl.interrupt")}
30086
+ `));
30087
+ this.agent.shutdown();
30088
+ }
30089
+ }
30090
+ return true;
30091
+ }
30092
+ if (key.ctrl && key.name === "v" && !this.agentRunning) {
30093
+ this.pasteClipboardImage();
30094
+ return true;
30095
+ }
30096
+ return false;
30097
+ }
30098
+ async pasteClipboardImage() {
30099
+ try {
30100
+ const { readClipboardImage: readClipboardImage2, bufferToDataUrl: bufferToDataUrl2 } = await Promise.resolve().then(() => (init_image_utils(), exports_image_utils));
30101
+ const clipBuf = await readClipboardImage2();
30102
+ if (clipBuf) {
30103
+ const { dataUrl } = await bufferToDataUrl2(clipBuf);
30104
+ this.pendingClipboardImage = dataUrl;
30105
+ const sizeKb = Math.round(dataUrl.length * 3 / 4 / 1024);
30106
+ console.log(pc2.green(`
30107
+ ${t("image.attached", { source: "clipboard", size: `${sizeKb} KB` })}`));
30108
+ if (this.rl instanceof LineEditor)
30109
+ this.rl.reset();
30110
+ this.rl.prompt();
30111
+ } else {
30112
+ console.log(pc2.yellow(`
30113
+ ${t("image.clipboard_empty")}`));
30114
+ if (this.rl instanceof LineEditor)
30115
+ this.rl.reset();
30116
+ this.rl.prompt();
30117
+ }
30118
+ } catch {}
30119
+ }
29339
30120
  isMultiLineInput(line) {
29340
30121
  if (line.endsWith("\\"))
29341
30122
  return true;
@@ -29361,6 +30142,8 @@ ${t("image.clipboard_empty")}`));
29361
30142
  this.pendingClipboardImage = null;
29362
30143
  }
29363
30144
  this.logger?.logREPL("user", input);
30145
+ if (this.rl instanceof LineEditor)
30146
+ this.rl.reset();
29364
30147
  process.stdout.write(`
29365
30148
  ` + pc2.green(t("repl.agent")));
29366
30149
  const renderer = new Renderer({
@@ -29552,6 +30335,21 @@ ${t("image.clipboard_empty")}`));
29552
30335
  }
29553
30336
  }
29554
30337
 
30338
+ // src/cli/run-result.ts
30339
+ init_i18n();
30340
+ init_colors();
30341
+ function printRunResult(result, flush) {
30342
+ flush();
30343
+ if (result.success) {
30344
+ if (!result.text) {
30345
+ console.log(pc2.yellow(t("cli.no_output")));
30346
+ }
30347
+ return 0;
30348
+ }
30349
+ console.error(`${t("error.prefix")}${result.error}`);
30350
+ return 1;
30351
+ }
30352
+
29555
30353
  // src/cli/main.ts
29556
30354
  init_setup();
29557
30355
  init_config2();
@@ -29784,18 +30582,9 @@ async function main() {
29784
30582
  }
29785
30583
  });
29786
30584
  renderer.flush();
29787
- if (result.success) {
29788
- if (!result.text) {
29789
- console.log(pc2.yellow(t("cli.no_output")));
29790
- }
29791
- } else {
29792
- console.error(`${t("error.prefix")}${result.error}`);
29793
- if (result.text) {
29794
- console.log(formatMarkdown(result.text));
29795
- }
29796
- process.exit(1);
29797
- }
30585
+ const exitCode = printRunResult(result, () => renderer.flush());
29798
30586
  agent.shutdown();
30587
+ process.exit(exitCode);
29799
30588
  } else {
29800
30589
  const configPath = join41(homedir18(), ".mma", "config.json");
29801
30590
  if (!existsSync46(configPath)) {
@@ -29858,7 +30647,7 @@ async function main() {
29858
30647
  logger
29859
30648
  } = await bootstrap(undefined, projectDir, noAgentsMd, exitOnComplete);
29860
30649
  startAutoUpdate(config);
29861
- const repl = new Repl(agent, config, sessionManager, skillsModule, pluginManager, configDir, baseDir, noAgentsMd, logger);
30650
+ const repl = new Repl(agent, config, sessionManager, skillsModule, pluginManager, configDir, baseDir, noAgentsMd, logger, true);
29862
30651
  repl.start();
29863
30652
  }
29864
30653
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "micro-models-agent",
3
- "version": "0.32.0",
3
+ "version": "0.33.1",
4
4
  "description": "Micro Models Agent (MMA) — LLM agent harness for small models (Qwen3.5-9B, 32K-64K context)",
5
5
  "type": "module",
6
6
  "bin": {