АРЧ - Добавить поддержку агентных смысловых прогонов в автопрогоны и правило AGENT в AGENTS.md

This commit is contained in:
2026-04-17 10:51:13 +03:00
parent dc8dfcf237
commit 1484c2375f
25 changed files with 10574 additions and 743 deletions
@@ -114,6 +114,7 @@ describe("counterparty shipment item flow and open-items routing", () => {
expect(result?.handled).toBe(true);
expect(result?.response_type).toBe("FACTUAL_LIST");
expect(result?.debug.detected_intent).toBe("list_documents_by_counterparty");
expect(result?.debug.address_truth_gate_v1?.truth_gate_status).toBe("full_confirmed");
expect(String(result?.reply_text ?? "")).toContain("Контрагент: Чепурнов П.Д.");
expect(String(result?.reply_text ?? "")).toContain("Позиции:");
expect(String(result?.reply_text ?? "")).toContain("Кабель силовой");
@@ -144,6 +144,38 @@ describe("inventory root frame regressions", () => {
expect(result?.baseReasons).toContain("address_followup_context_applied");
});
it("restores inventory root frame for 'покажи еще раз остатки на эту же дату' after item documents drilldown", () => {
const result = runAddressDecomposeStage("покажи еще раз остатки на эту же дату", {
previous_intent: "inventory_purchase_documents_for_item",
previous_filters: {
item: "Столешница 600*3050*26 альмандин",
organization: 'ООО "Альтернатива Плюс"',
as_of_date: "2021-03-31"
},
previous_anchor_type: "item",
previous_anchor_value: "Столешница 600*3050*26 альмандин",
root_intent: "inventory_on_hand_as_of_date",
root_filters: {
organization: 'ООО "Альтернатива Плюс"',
period_from: "2021-03-01",
period_to: "2021-03-31",
as_of_date: "2021-03-31"
},
root_anchor_type: "organization",
root_anchor_value: 'ООО "Альтернатива Плюс"',
current_frame_kind: "inventory_drilldown"
});
expect(result).not.toBeNull();
expect(result?.intent.intent).toBe("inventory_on_hand_as_of_date");
expect(result?.intent.reasons).toContain("intent_restored_to_inventory_root_frame");
expect(result?.filters.extracted_filters.organization).toBe('ООО "Альтернатива Плюс"');
expect(result?.filters.extracted_filters.as_of_date).toBe("2021-03-31");
expect(result?.filters.extracted_filters.period_from).toBe("2021-03-01");
expect(result?.filters.extracted_filters.period_to).toBe("2021-03-31");
expect(result?.filters.extracted_filters.item).toBeUndefined();
});
it("restores inventory root frame for root-context-only month follow-up after drilldown", () => {
const result = runAddressDecomposeStage("остатки на июль 2019", {
previous_filters: {
@@ -107,6 +107,8 @@ describe("inventory selected-object follow-up", () => {
expect(result?.debug.capability_route_mode).toBe("exact");
expect(result?.debug.reasons).toContain("period_window_auto_broadened_to_available_data");
expect(result?.debug.limitations).toContain("period_window_auto_broadened_to_available_data");
expect(result?.debug.address_truth_gate_v1?.truth_gate_status).toBe("limited_temporal_or_contextual");
expect(result?.debug.address_truth_gate_v1?.carryover_eligibility).toBe("object_only");
const replyLines = String(result?.reply_text ?? "").split("\n");
expect(replyLines[0]).toContain("По позиции Кромка с клеем 33 альмандин 137 м");
expect(replyLines[0]).toContain("до 31.03.2021 подтвержден поставщик");
@@ -197,6 +199,56 @@ describe("inventory selected-object follow-up", () => {
expect(String(result?.reply_text ?? "")).toContain("Торговый дом \\Союз МСК\\");
});
it("handles selected-object supplier wording 'кто нам это поставил' as provenance follow-up", async () => {
executeAddressMcpQueryMock.mockResolvedValueOnce({
fetched_rows: 1,
matched_rows: 1,
raw_rows: [
{
Period: "2019-02-12T00:00:00Z",
Registrator: "Поступление товаров и услуг 00000000003 от 12.02.2019 0:00:00",
AccountDt: "41.01",
AccountKt: "60.01",
Amount: 3690,
SubcontoDt1: "Столешница 600*3050*26 альмандин",
SubcontoDt3: "Основной склад",
SubcontoKt1: "Торговый дом \\Союз",
SubcontoKt2: "Договор поставки № 12 от 01.02.2019",
Organization: "ООО \\Альтернатива Плюс\\"
}
],
rows: [],
error: null
});
const service = new AddressQueryService();
const result = await service.tryHandle(
'По выбранному объекту "Столешница 600*3050*26 альмандин": кто нам это поставил?',
{
followupContext: {
previous_intent: "inventory_on_hand_as_of_date",
previous_filters: {
as_of_date: "2021-03-31",
period_from: "2021-03-01",
period_to: "2021-03-31",
warehouse: "Основной склад",
organization: "ООО \\Альтернатива Плюс\\"
},
previous_anchor_type: "unknown",
previous_anchor_value: null
}
}
);
expect(result?.handled).toBe(true);
expect(result?.response_type).toBe("FACTUAL_SUMMARY");
expect(result?.debug.detected_intent).toBe("inventory_purchase_provenance_for_item");
expect(result?.debug.extracted_filters?.item).toBe("Столешница 600*3050*26 альмандин");
expect(result?.debug.extracted_filters?.as_of_date).toBeUndefined();
expect(result?.debug.capability_id).toBe("inventory_inventory_purchase_provenance_for_item");
expect(String(result?.reply_text ?? "")).toContain("Торговый дом \\Союз");
});
it("handles selected-object colloquial supplier wording 'у кого купили' as provenance follow-up", async () => {
executeAddressMcpQueryMock.mockResolvedValueOnce({
fetched_rows: 1,
@@ -123,6 +123,38 @@ describe("assistant capability runtime binding adapter", () => {
expect(binding.violations).toContain("transition_not_supported_by_capability");
});
it("allows inventory root capability to return through T6 after a drilldown pivot", () => {
const binding = resolveAssistantCapabilityRuntimeBinding({
addressDebug: {
capability_id: "confirmed_inventory_on_hand_as_of_date",
detected_intent: "inventory_on_hand_as_of_date",
detected_mode: "address_query",
capability_layer: "compute",
capability_route_mode: "exact",
rows_matched: 11,
route_expectation_status: "matched"
},
runtimeContractShadow: {
schema_version: "assistant_runtime_contracts_v1",
transition_contract_id: "T6",
transition_contract_title: "Domain Pivot With Root-Only Carryover",
transition_contract_reason: ["root_context_reused_after_drilldown_context"],
capability_contract_id: "confirmed_inventory_on_hand_as_of_date",
capability_contract_reason: ["debug_capability_id_matched_contract"],
truth_gate_contract_status: "full_confirmed",
carryover_eligibility: "root_only"
},
groundingStatus: "grounded",
replyType: "factual"
});
expect(binding.binding_status).toBe("bound");
expect(binding.binding_action).toBe("allow");
expect(binding.transition_id).toBe("T6");
expect(binding.transition_allowed).toBe(true);
expect(binding.violations).toEqual([]);
});
it("attaches compact debug fields and preserves the binding contract", () => {
const debug = attachAssistantCapabilityRuntimeBinding(
{
@@ -117,6 +117,33 @@ describe("assistant truth answer policy runtime adapter", () => {
expect(policy.answer_shape.may_power_followup).toBe(true);
});
it("keeps explicit temporal-limited factual answers limited in the truth contract", () => {
const policy = resolveAssistantTruthAnswerPolicyRuntime({
addressDebug: {
capability_id: "inventory_inventory_purchase_provenance_for_item",
rows_matched: 2,
address_truth_gate_v1: {
schema_version: "address_truth_gate_v1",
policy_owner: "addressTruthGatePolicy",
truth_gate_status: "limited_temporal_or_contextual",
carryover_eligibility: "object_only",
limited_reason_category: null,
runtime_readiness: "LIVE_QUERYABLE_WITH_LIMITS",
reason_codes: ["period_window_auto_broadened_to_available_data"],
blocked_or_limited_explanation: "temporal_or_contextual_limit"
}
},
replyType: "factual"
});
expect(policy.truth_gate.coverage_status).toBe("partial");
expect(policy.truth_gate.truth_mode).toBe("limited");
expect(policy.truth_gate.carryover_eligibility).toBe("object_only");
expect(policy.truth_gate.blocked_or_limited_explanation).toBe("temporal_or_contextual_limit");
expect(policy.answer_shape.answer_shape).toBe("limited_with_reason");
expect(policy.answer_shape.must_include_limitation).toBe(true);
});
it("attaches top-level debug fields without hiding the nested contract", () => {
const debug = attachAssistantTruthAnswerPolicy(
{