@iola_adm/iola-cli 0.1.98 → 0.1.100
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 +166 -10
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -899,8 +899,7 @@ async function startAgentRawInput() {
|
|
|
899
899
|
}
|
|
900
900
|
} finally {
|
|
901
901
|
clearAgentInputArea(state);
|
|
902
|
-
|
|
903
|
-
finishAgentTerminalLine();
|
|
902
|
+
finishAgentTerminalLine(state);
|
|
904
903
|
if (!wasRaw) input.setRawMode(false);
|
|
905
904
|
input.pause();
|
|
906
905
|
}
|
|
@@ -1425,9 +1424,21 @@ function clearAgentStatusBar(state) {
|
|
|
1425
1424
|
state.statusRows = 0;
|
|
1426
1425
|
}
|
|
1427
1426
|
|
|
1428
|
-
function finishAgentTerminalLine() {
|
|
1427
|
+
function finishAgentTerminalLine(state = null) {
|
|
1429
1428
|
if (!output.isTTY) return;
|
|
1430
|
-
output.
|
|
1429
|
+
const rows = Number(output.rows || state?.statusRows || 0);
|
|
1430
|
+
output.write("\x1b[r");
|
|
1431
|
+
if (rows >= 1) {
|
|
1432
|
+
output.write(`\x1b[${rows};1H\x1b[2K\n`);
|
|
1433
|
+
} else {
|
|
1434
|
+
output.write("\r\x1b[0K\n");
|
|
1435
|
+
}
|
|
1436
|
+
if (state) {
|
|
1437
|
+
state.statusBar = false;
|
|
1438
|
+
state.statusRows = 0;
|
|
1439
|
+
state.renderedInputLines = 0;
|
|
1440
|
+
state.renderedLines = 0;
|
|
1441
|
+
}
|
|
1431
1442
|
}
|
|
1432
1443
|
|
|
1433
1444
|
function startActivityIndicator(label = "работаю") {
|
|
@@ -6302,7 +6313,7 @@ function buildDirectDataAnswer(question, dataContext) {
|
|
|
6302
6313
|
|
|
6303
6314
|
function detectDirectDataFields(normalizedQuestion) {
|
|
6304
6315
|
const fields = [];
|
|
6305
|
-
if (/(
|
|
6316
|
+
if (/(директ|руководител|заведующ|кто возглавляет)/iu.test(normalizedQuestion)) fields.push("head");
|
|
6306
6317
|
if (/(сайт|website|url|ссылка)/iu.test(normalizedQuestion)) fields.push("website");
|
|
6307
6318
|
if (/(телефон|номер телефона|позвонить)/iu.test(normalizedQuestion)) fields.push("phone");
|
|
6308
6319
|
if (/(почт|email|e-mail|имейл|электронн)/iu.test(normalizedQuestion)) fields.push("email");
|
|
@@ -6448,6 +6459,11 @@ async function localToolAsk(question, providerConfig, options) {
|
|
|
6448
6459
|
return casualAnswer;
|
|
6449
6460
|
}
|
|
6450
6461
|
await ensureLocalData();
|
|
6462
|
+
const personRoleAnswer = buildPersonRoleDirectAnswer(question);
|
|
6463
|
+
if (personRoleAnswer) {
|
|
6464
|
+
if (!options.quiet) console.log(personRoleAnswer);
|
|
6465
|
+
return personRoleAnswer;
|
|
6466
|
+
}
|
|
6451
6467
|
const plan = await buildLocalToolPlan(question, providerConfig, options);
|
|
6452
6468
|
if (plan.directAnswer) {
|
|
6453
6469
|
if (!options.quiet) console.log(plan.directAnswer);
|
|
@@ -6524,6 +6540,68 @@ function buildCasualDirectAnswer(question) {
|
|
|
6524
6540
|
return "";
|
|
6525
6541
|
}
|
|
6526
6542
|
|
|
6543
|
+
function buildPersonRoleDirectAnswer(question) {
|
|
6544
|
+
const normalized = String(question || "").toLocaleLowerCase("ru-RU");
|
|
6545
|
+
const asksHead = /(директ|руководител|заведующ|возглавляет)/iu.test(normalized);
|
|
6546
|
+
const asksOrganization = /(какой|какая|где|чего|организац|учрежден|школ|сад|детсад|гимнази|лицей)/iu.test(normalized);
|
|
6547
|
+
if (!asksHead || !asksOrganization) return "";
|
|
6548
|
+
|
|
6549
|
+
const nameTokens = extractPersonNameTokens(question);
|
|
6550
|
+
if (nameTokens.length < 2) return "";
|
|
6551
|
+
|
|
6552
|
+
const dataset = normalized.includes("сад") || normalized.includes("детсад")
|
|
6553
|
+
? "kindergartens"
|
|
6554
|
+
: normalized.includes("школ") || normalized.includes("гимнази") || normalized.includes("лицей")
|
|
6555
|
+
? "schools"
|
|
6556
|
+
: "all";
|
|
6557
|
+
const rows = searchLocalRecords("", { dataset, limit: 10000 })
|
|
6558
|
+
.filter((row) => personTokensMatchHead(row.head, nameTokens));
|
|
6559
|
+
|
|
6560
|
+
if (rows.length === 0) {
|
|
6561
|
+
return `В открытых данных не нашел руководителя с ФИО: ${formatPersonTokens(nameTokens)}.`;
|
|
6562
|
+
}
|
|
6563
|
+
|
|
6564
|
+
const exactRows = rows.filter((row) => personTokensMatchHead(row.head, nameTokens, { strict: true }));
|
|
6565
|
+
const matches = exactRows.length > 0 ? exactRows : rows;
|
|
6566
|
+
if (matches.length === 1) {
|
|
6567
|
+
const row = matches[0];
|
|
6568
|
+
return [
|
|
6569
|
+
`${row.head} является руководителем: ${row.name}.`,
|
|
6570
|
+
row.address ? `Адрес: ${row.address}` : "",
|
|
6571
|
+
row.inn ? `ИНН: ${row.inn}` : "",
|
|
6572
|
+
"Источник: открытый слой образования.",
|
|
6573
|
+
].filter(Boolean).join("\n");
|
|
6574
|
+
}
|
|
6575
|
+
|
|
6576
|
+
return [
|
|
6577
|
+
`Нашел несколько организаций для ФИО ${formatPersonTokens(nameTokens)}:`,
|
|
6578
|
+
...matches.slice(0, 10).map((row) => `- ${row.head}: ${row.name}${row.inn ? `, ИНН ${row.inn}` : ""}`),
|
|
6579
|
+
].join("\n");
|
|
6580
|
+
}
|
|
6581
|
+
|
|
6582
|
+
function extractPersonNameTokens(question) {
|
|
6583
|
+
const stopWords = new Set([
|
|
6584
|
+
"так", "а", "и", "или", "кто", "что", "какой", "какая", "какого", "какой-то", "это",
|
|
6585
|
+
"директор", "директора", "директором", "руководитель", "руководителем", "заведующая", "заведующий",
|
|
6586
|
+
"школа", "школы", "школе", "сад", "сада", "детский", "детского", "гимназия", "лицей",
|
|
6587
|
+
"является", "возглавляет", "найди", "покажи",
|
|
6588
|
+
]);
|
|
6589
|
+
return [...String(question || "").toLocaleLowerCase("ru-RU").matchAll(/\p{L}{3,}/gu)]
|
|
6590
|
+
.map((match) => match[0])
|
|
6591
|
+
.filter((token) => !stopWords.has(token));
|
|
6592
|
+
}
|
|
6593
|
+
|
|
6594
|
+
function personTokensMatchHead(head, tokens, options = {}) {
|
|
6595
|
+
const headTokens = new Set([...String(head || "").toLocaleLowerCase("ru-RU").matchAll(/\p{L}{3,}/gu)].map((match) => match[0]));
|
|
6596
|
+
if (options.strict) return tokens.every((token) => headTokens.has(token));
|
|
6597
|
+
const headText = [...headTokens].join(" ");
|
|
6598
|
+
return tokens.every((token) => headTokens.has(token) || headText.includes(token));
|
|
6599
|
+
}
|
|
6600
|
+
|
|
6601
|
+
function formatPersonTokens(tokens) {
|
|
6602
|
+
return tokens.map(capitalizeFirst).join(" ");
|
|
6603
|
+
}
|
|
6604
|
+
|
|
6527
6605
|
function printToolPlan(plan) {
|
|
6528
6606
|
console.log("План выполнения:");
|
|
6529
6607
|
plan.steps.forEach((step, index) => {
|
|
@@ -6662,15 +6740,68 @@ async function searchPublicEntities(args = {}) {
|
|
|
6662
6740
|
}
|
|
6663
6741
|
|
|
6664
6742
|
async function resolvePublicEntityField(args = {}) {
|
|
6665
|
-
const
|
|
6666
|
-
|
|
6743
|
+
const endpoint = `${await getApiBaseUrl()}/resolve-entity-field`;
|
|
6744
|
+
const requestedField = normalizeEntityField(args.field);
|
|
6745
|
+
const layer = normalizeEntityLayer(args.layer);
|
|
6746
|
+
const payload = {
|
|
6747
|
+
layer,
|
|
6667
6748
|
entity_number: args.entity_number ?? args.number,
|
|
6668
6749
|
entity_name: args.entity_name || args.name,
|
|
6669
6750
|
inn: args.inn,
|
|
6670
|
-
field:
|
|
6751
|
+
field: requestedField,
|
|
6671
6752
|
must_refute_user_value: args.must_refute_user_value,
|
|
6753
|
+
};
|
|
6754
|
+
try {
|
|
6755
|
+
return await postJson(endpoint, payload);
|
|
6756
|
+
} catch (error) {
|
|
6757
|
+
const fallbackField = pickResolveFieldFallback(requestedField, error);
|
|
6758
|
+
if (fallbackField && fallbackField !== requestedField) {
|
|
6759
|
+
try {
|
|
6760
|
+
return await postJson(endpoint, { ...payload, field: fallbackField });
|
|
6761
|
+
} catch (retryError) {
|
|
6762
|
+
const resolvedBySearch = await resolvePublicEntityFieldViaSearch({ ...payload, field: fallbackField }, retryError);
|
|
6763
|
+
if (resolvedBySearch) return resolvedBySearch;
|
|
6764
|
+
throw retryError;
|
|
6765
|
+
}
|
|
6766
|
+
}
|
|
6767
|
+
const resolvedBySearch = await resolvePublicEntityFieldViaSearch(payload, error);
|
|
6768
|
+
if (resolvedBySearch) return resolvedBySearch;
|
|
6769
|
+
throw error;
|
|
6770
|
+
}
|
|
6771
|
+
}
|
|
6772
|
+
|
|
6773
|
+
async function resolvePublicEntityFieldViaSearch(payload, originalError) {
|
|
6774
|
+
const details = parseErrorJsonDetails(originalError);
|
|
6775
|
+
if (details?.error !== "entity_not_found") return null;
|
|
6776
|
+
if (payload.inn) return null;
|
|
6777
|
+
const query = payload.entity_name || buildEntitySearchQuery(payload.layer, payload.entity_number);
|
|
6778
|
+
if (!query) return null;
|
|
6779
|
+
const candidates = await searchPublicEntities({ layer: payload.layer, query, limit: 10 });
|
|
6780
|
+
const candidate = pickResolvedEntityCandidate(candidates, payload);
|
|
6781
|
+
if (!candidate?.inn) return null;
|
|
6782
|
+
return postJson(`${await getApiBaseUrl()}/resolve-entity-field`, {
|
|
6783
|
+
layer: payload.layer,
|
|
6784
|
+
inn: candidate.inn,
|
|
6785
|
+
field: payload.field,
|
|
6786
|
+
must_refute_user_value: payload.must_refute_user_value,
|
|
6672
6787
|
});
|
|
6673
|
-
|
|
6788
|
+
}
|
|
6789
|
+
|
|
6790
|
+
function buildEntitySearchQuery(layer, number) {
|
|
6791
|
+
if (number === undefined || number === null || number === "") return "";
|
|
6792
|
+
const label = layer === "kindergartens" ? "детский сад" : "школа";
|
|
6793
|
+
return `${label} ${number}`;
|
|
6794
|
+
}
|
|
6795
|
+
|
|
6796
|
+
function pickResolvedEntityCandidate(candidates, payload) {
|
|
6797
|
+
if (!Array.isArray(candidates) || candidates.length === 0) return null;
|
|
6798
|
+
const number = payload.entity_number === undefined || payload.entity_number === null ? "" : String(payload.entity_number);
|
|
6799
|
+
if (number) {
|
|
6800
|
+
const exact = candidates.find((item) => itemNameHasNumber(item, number));
|
|
6801
|
+
if (exact) return exact;
|
|
6802
|
+
}
|
|
6803
|
+
if (candidates.length === 1) return candidates[0];
|
|
6804
|
+
return candidates.find((item) => Number(item.score || 0) > 0) || candidates[0];
|
|
6674
6805
|
}
|
|
6675
6806
|
|
|
6676
6807
|
function normalizeEntityLayer(layer) {
|
|
@@ -6682,7 +6813,7 @@ function normalizeEntityLayer(layer) {
|
|
|
6682
6813
|
|
|
6683
6814
|
function normalizeEntityField(field) {
|
|
6684
6815
|
const value = String(field || "").toLocaleLowerCase("ru-RU");
|
|
6685
|
-
if (value === "director" || value === "head" || value.includes("
|
|
6816
|
+
if (value === "director" || value === "directors" || value === "directs" || value === "direct" || value === "head" || value === "head_name" || value.includes("директ") || value.includes("руковод")) return "director";
|
|
6686
6817
|
if (value === "site" || value === "url" || value === "website" || value.includes("сайт")) return "website";
|
|
6687
6818
|
if (value === "mail" || value === "email" || value.includes("почт")) return "email";
|
|
6688
6819
|
if (value === "phone" || value.includes("тел")) return "phone";
|
|
@@ -6691,6 +6822,31 @@ function normalizeEntityField(field) {
|
|
|
6691
6822
|
return value || "name";
|
|
6692
6823
|
}
|
|
6693
6824
|
|
|
6825
|
+
function pickResolveFieldFallback(requestedField, error) {
|
|
6826
|
+
const details = parseErrorJsonDetails(error);
|
|
6827
|
+
if (details?.error !== "field_not_public") return "";
|
|
6828
|
+
const publicFields = new Set((details.public_fields || []).map((field) => String(field)));
|
|
6829
|
+
const aliases = {
|
|
6830
|
+
director: ["director", "head_name", "head"],
|
|
6831
|
+
head: ["director", "head_name", "head"],
|
|
6832
|
+
head_name: ["director", "head_name", "head"],
|
|
6833
|
+
license_status: ["license_status", "license_number", "license_date"],
|
|
6834
|
+
}[requestedField] || [];
|
|
6835
|
+
return aliases.find((field) => field !== requestedField && publicFields.has(field)) || "";
|
|
6836
|
+
}
|
|
6837
|
+
|
|
6838
|
+
function parseErrorJsonDetails(error) {
|
|
6839
|
+
const text = error instanceof Error ? error.message : String(error || "");
|
|
6840
|
+
const match = text.match(/\{[\s\S]*\}$/);
|
|
6841
|
+
if (!match) return null;
|
|
6842
|
+
try {
|
|
6843
|
+
const parsed = JSON.parse(match[0]);
|
|
6844
|
+
return parsed.detail || parsed;
|
|
6845
|
+
} catch {
|
|
6846
|
+
return null;
|
|
6847
|
+
}
|
|
6848
|
+
}
|
|
6849
|
+
|
|
6694
6850
|
function availableToolNames(options = {}) {
|
|
6695
6851
|
const names = new Set(LOCAL_TOOLS);
|
|
6696
6852
|
for (const tool of getLocalMcpToolNames()) names.add(tool);
|