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.
- package/dist/main.js +938 -149
- 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
|
|
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(
|
|
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 (
|
|
10776
|
-
|
|
10777
|
-
|
|
10778
|
-
|
|
10779
|
-
|
|
10780
|
-
|
|
10781
|
-
|
|
10782
|
-
|
|
10783
|
-
|
|
10784
|
-
|
|
10785
|
-
|
|
10786
|
-
|
|
10787
|
-
|
|
10788
|
-
|
|
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: "
|
|
10791
|
-
content:
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
29287
|
-
|
|
29288
|
-
|
|
29289
|
-
|
|
29290
|
-
|
|
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
|
-
|
|
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
|
}
|