@iola_adm/iola-cli 0.1.81 → 0.1.83
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/package.json +1 -1
- package/src/cli.js +94 -9
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -774,9 +774,10 @@ async function startAgentReadline() {
|
|
|
774
774
|
}
|
|
775
775
|
|
|
776
776
|
async function startAgentRawInput() {
|
|
777
|
-
const state = { history: [], buffer: "", selected: 0, slashOffset: 0, slashOpen: false, running: false, renderedInputLines: 0, renderedLines: 0, rawMode: true, pendingOutput: "", aiStatus: null };
|
|
777
|
+
const state = { history: [], buffer: "", selected: 0, slashOffset: 0, slashOpen: false, running: false, renderedInputLines: 0, renderedLines: 0, rawMode: true, pendingOutput: "", aiStatus: null, statusBar: false, statusRows: 0 };
|
|
778
778
|
const wasRaw = input.isRaw;
|
|
779
779
|
activateRawInput(input);
|
|
780
|
+
setupAgentStatusBar(state);
|
|
780
781
|
|
|
781
782
|
await refreshAgentAiStatus(state);
|
|
782
783
|
const render = () => renderAgentInput(state);
|
|
@@ -857,6 +858,8 @@ async function startAgentRawInput() {
|
|
|
857
858
|
}
|
|
858
859
|
}
|
|
859
860
|
} finally {
|
|
861
|
+
clearAgentInputArea(state);
|
|
862
|
+
clearAgentStatusBar(state);
|
|
860
863
|
if (!wasRaw) input.setRawMode(false);
|
|
861
864
|
input.pause();
|
|
862
865
|
}
|
|
@@ -1305,8 +1308,6 @@ function renderAgentInput(state) {
|
|
|
1305
1308
|
const prompt = "> ";
|
|
1306
1309
|
const lines = state.buffer.split("\n");
|
|
1307
1310
|
const inputLines = [`${prompt}${lines[0] || ""}`, ...lines.slice(1)];
|
|
1308
|
-
const statusLine = truncateTerminalLine(` ${buildAgentStatusLine(state)}`);
|
|
1309
|
-
const cwdLine = colorMuted(statusLine);
|
|
1310
1311
|
const menuLines = [];
|
|
1311
1312
|
if (state.slashOpen) {
|
|
1312
1313
|
const matches = currentSlashMatches(state);
|
|
@@ -1328,7 +1329,8 @@ function renderAgentInput(state) {
|
|
|
1328
1329
|
}
|
|
1329
1330
|
}
|
|
1330
1331
|
|
|
1331
|
-
|
|
1332
|
+
renderAgentStatusBar(state);
|
|
1333
|
+
const renderedLines = [...menuLines, ...inputLines];
|
|
1332
1334
|
output.write(renderedLines.join("\n"));
|
|
1333
1335
|
if (output.isTTY) {
|
|
1334
1336
|
const cursorColumn = visibleLength(inputLines[inputLines.length - 1]);
|
|
@@ -1349,6 +1351,37 @@ function clearAgentInputArea(state = null) {
|
|
|
1349
1351
|
}
|
|
1350
1352
|
}
|
|
1351
1353
|
|
|
1354
|
+
function setupAgentStatusBar(state) {
|
|
1355
|
+
if (!output.isTTY) return;
|
|
1356
|
+
const rows = Number(output.rows || 0);
|
|
1357
|
+
if (rows < 4) return;
|
|
1358
|
+
state.statusBar = true;
|
|
1359
|
+
state.statusRows = rows;
|
|
1360
|
+
output.write(`\x1b[1;${rows - 1}r`);
|
|
1361
|
+
output.write(`\x1b[${rows - 1};1H`);
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
function renderAgentStatusBar(state) {
|
|
1365
|
+
if (!output.isTTY || !state.statusBar) return;
|
|
1366
|
+
const rows = Number(output.rows || state.statusRows || 0);
|
|
1367
|
+
if (rows < 4) return;
|
|
1368
|
+
if (rows !== state.statusRows) {
|
|
1369
|
+
state.statusRows = rows;
|
|
1370
|
+
output.write(`\x1b[1;${rows - 1}r`);
|
|
1371
|
+
}
|
|
1372
|
+
const statusLine = colorMuted(truncateTerminalLine(` ${buildAgentStatusLine(state)} `));
|
|
1373
|
+
output.write(`\x1b7\x1b[${rows};1H\x1b[2K${statusLine}\x1b8`);
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
function clearAgentStatusBar(state) {
|
|
1377
|
+
if (!output.isTTY || !state?.statusBar) return;
|
|
1378
|
+
const rows = Number(output.rows || state.statusRows || 0);
|
|
1379
|
+
output.write("\x1b[r");
|
|
1380
|
+
if (rows >= 1) output.write(`\x1b7\x1b[${rows};1H\x1b[2K\x1b8`);
|
|
1381
|
+
state.statusBar = false;
|
|
1382
|
+
state.statusRows = 0;
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1352
1385
|
function startActivityIndicator(label = "работаю") {
|
|
1353
1386
|
const doneLabel = "готово";
|
|
1354
1387
|
if (!output.isTTY || process.env.NO_COLOR === "1") {
|
|
@@ -6107,7 +6140,7 @@ function pickDirectDataItem(question, dataContext, rows) {
|
|
|
6107
6140
|
function itemNameHasNumber(item, number) {
|
|
6108
6141
|
const name = String(item.name || item.title || item.fns_full_name || item.fns_short_name || "").toLocaleLowerCase("ru-RU");
|
|
6109
6142
|
const escaped = String(number).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6110
|
-
return new RegExp(`(?:№\\s*${escaped}|\\b(?:школа|сош|лицей|гимназия|сад|детский сад)\\s*№?\\s*${escaped}\\b)`, "iu").test(name);
|
|
6143
|
+
return new RegExp(`(?:№\\s*${escaped}(?!\\d)|\\b(?:школа|сош|лицей|гимназия|сад|детский сад)\\s*№?\\s*${escaped}\\b)`, "iu").test(name);
|
|
6111
6144
|
}
|
|
6112
6145
|
|
|
6113
6146
|
function formatDirectDataField(field, item) {
|
|
@@ -6115,7 +6148,7 @@ function formatDirectDataField(field, item) {
|
|
|
6115
6148
|
if (field === "head") {
|
|
6116
6149
|
const head = item.head || item.fns_head_name;
|
|
6117
6150
|
if (!head) return "";
|
|
6118
|
-
const position = item.fns_head_position || (item.layer === "kindergartens" ? "заведующий" : "директор");
|
|
6151
|
+
const position = capitalizeFirst(item.fns_head_position || (item.layer === "kindergartens" ? "заведующий" : "директор"));
|
|
6119
6152
|
return `${position}: ${head} (${name}).`;
|
|
6120
6153
|
}
|
|
6121
6154
|
if (field === "website") return item.website ? `Сайт: ${item.website}` : `Сайт для ${name} в открытых данных не указан.`;
|
|
@@ -6138,6 +6171,11 @@ function getDirectDataItemName(item) {
|
|
|
6138
6171
|
return item.name || item.title || item.fns_short_name || item.fns_full_name || "организация";
|
|
6139
6172
|
}
|
|
6140
6173
|
|
|
6174
|
+
function capitalizeFirst(value) {
|
|
6175
|
+
const text = String(value || "");
|
|
6176
|
+
return text ? `${text[0].toLocaleUpperCase("ru-RU")}${text.slice(1)}` : text;
|
|
6177
|
+
}
|
|
6178
|
+
|
|
6141
6179
|
async function resolveUsableAiProfile(config, options = {}) {
|
|
6142
6180
|
const explicit = Boolean(options.profile || options.provider);
|
|
6143
6181
|
const providerConfig = resolveAiProfile(config, options);
|
|
@@ -6556,6 +6594,7 @@ async function buildDataContext(question) {
|
|
|
6556
6594
|
try {
|
|
6557
6595
|
const context = await callPublicMcpTool("layer_answer_context", { question, limit: 8 });
|
|
6558
6596
|
const layerMap = Object.fromEntries((context.results || []).map((result) => [result.layer?.id || result.layer, result.items || []]));
|
|
6597
|
+
await enrichLayerMapWithExactMatches(layerMap, question, queryTerms, patterns);
|
|
6559
6598
|
return {
|
|
6560
6599
|
source: "remote-mcp",
|
|
6561
6600
|
contract_version: context.contract_version,
|
|
@@ -6593,6 +6632,31 @@ async function buildDataContext(question) {
|
|
|
6593
6632
|
}
|
|
6594
6633
|
}
|
|
6595
6634
|
|
|
6635
|
+
async function enrichLayerMapWithExactMatches(layerMap, question, queryTerms, patterns) {
|
|
6636
|
+
if (!patterns.numbers?.length) return;
|
|
6637
|
+
const targetLayerIds = resolveTargetLayerIds(patterns);
|
|
6638
|
+
await Promise.all(targetLayerIds.map(async (layer) => {
|
|
6639
|
+
try {
|
|
6640
|
+
const result = await queryLayer(layer, { query: question, terms: queryTerms, patterns, limit: 8 });
|
|
6641
|
+
const existing = layerMap[layer] || [];
|
|
6642
|
+
const existingKeys = new Set(existing.map((item) => item.inn || item.name || item.fns_short_name).filter(Boolean));
|
|
6643
|
+
const exact = (result.items || []).filter((item) =>
|
|
6644
|
+
patterns.numbers.some((number) => itemNameHasNumber(item, number)));
|
|
6645
|
+
layerMap[layer] = [
|
|
6646
|
+
...exact.filter((item) => {
|
|
6647
|
+
const key = item.inn || item.name || item.fns_short_name;
|
|
6648
|
+
if (!key || existingKeys.has(key)) return false;
|
|
6649
|
+
existingKeys.add(key);
|
|
6650
|
+
return true;
|
|
6651
|
+
}),
|
|
6652
|
+
...existing,
|
|
6653
|
+
];
|
|
6654
|
+
} catch {
|
|
6655
|
+
// Remote MCP remains the primary source; exact local/API enrichment is best effort.
|
|
6656
|
+
}
|
|
6657
|
+
}));
|
|
6658
|
+
}
|
|
6659
|
+
|
|
6596
6660
|
function resolveTargetLayerIds(patterns = {}) {
|
|
6597
6661
|
const knownLayers = Object.keys(DATASETS);
|
|
6598
6662
|
if (patterns.targetLayers?.length) return patterns.targetLayers.filter((layer) => DATASETS[layer]);
|
|
@@ -6690,13 +6754,16 @@ function extractSearchTerms(question) {
|
|
|
6690
6754
|
|
|
6691
6755
|
function extractStructuredPatterns(question) {
|
|
6692
6756
|
const normalized = question.toLocaleLowerCase("ru-RU");
|
|
6693
|
-
const numbers = [...new Set([
|
|
6757
|
+
const numbers = [...new Set([
|
|
6758
|
+
...[...normalized.matchAll(/\b\d{1,3}\b/g)].map((match) => match[0]),
|
|
6759
|
+
...extractOrdinalNumbers(normalized),
|
|
6760
|
+
])];
|
|
6694
6761
|
const inns = [...new Set([...normalized.matchAll(/\b\d{10,12}\b/g)].map((match) => match[0]))];
|
|
6695
6762
|
const targetLayers = [];
|
|
6696
|
-
if (/(
|
|
6763
|
+
if (/(школ|сош|лице|гимнази)/iu.test(normalized)) {
|
|
6697
6764
|
targetLayers.push("schools");
|
|
6698
6765
|
}
|
|
6699
|
-
if (/(
|
|
6766
|
+
if (/(детсад|детск|сад|сады|доу|мбдоу)/iu.test(normalized)) {
|
|
6700
6767
|
targetLayers.push("kindergartens");
|
|
6701
6768
|
}
|
|
6702
6769
|
const streetMatches = [
|
|
@@ -6708,6 +6775,24 @@ function extractStructuredPatterns(question) {
|
|
|
6708
6775
|
return { numbers, inns, streets, targetLayers: [...new Set(targetLayers)] };
|
|
6709
6776
|
}
|
|
6710
6777
|
|
|
6778
|
+
function extractOrdinalNumbers(normalizedQuestion) {
|
|
6779
|
+
const ordinals = [
|
|
6780
|
+
["1", "(?:перв(?:ая|ой|ую|ое|ого|ом|ым|ых)?|первую)"],
|
|
6781
|
+
["2", "(?:втор(?:ая|ой|ую|ое|ого|ом|ым|ых)?|вторую)"],
|
|
6782
|
+
["3", "(?:трет(?:ья|ий|ью|ье|ьего|ьем|ьим|ьих)?|третью)"],
|
|
6783
|
+
["4", "четверт(?:ая|ой|ую|ое|ого|ом|ым|ых)?"],
|
|
6784
|
+
["5", "пят(?:ая|ой|ую|ое|ого|ом|ым|ых)?"],
|
|
6785
|
+
["6", "шест(?:ая|ой|ую|ое|ого|ом|ым|ых)?"],
|
|
6786
|
+
["7", "седьм(?:ая|ой|ую|ое|ого|ом|ым|ых)?"],
|
|
6787
|
+
["8", "восьм(?:ая|ой|ую|ое|ого|ом|ым|ых)?"],
|
|
6788
|
+
["9", "девят(?:ая|ой|ую|ое|ого|ом|ым|ых)?"],
|
|
6789
|
+
["10", "десят(?:ая|ой|ую|ое|ого|ом|ым|ых)?"],
|
|
6790
|
+
];
|
|
6791
|
+
return ordinals
|
|
6792
|
+
.filter(([, pattern]) => new RegExp(`(^|[^а-яёa-z])${pattern}(?=$|[^а-яёa-z])`, "iu").test(normalizedQuestion))
|
|
6793
|
+
.map(([number]) => number);
|
|
6794
|
+
}
|
|
6795
|
+
|
|
6711
6796
|
function cleanupPattern(value) {
|
|
6712
6797
|
return value
|
|
6713
6798
|
.replace(/\b(школа|школы|сад|детский|детские|сады|лицей|гимназия|контакты|телефон|адрес|найди|покажи)\b/giu, " ")
|