feat(ui): show ai loop log source tags
Some checks failed
CD Pipeline / workflow-shape (push) Has been cancelled
CD Pipeline / cancel-stale-cd (push) Has been cancelled
CD Pipeline / tests (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled

This commit is contained in:
Your Name
2026-07-01 13:52:08 +08:00
parent 37cf044af3
commit d7ae71debc
4 changed files with 256 additions and 0 deletions

View File

@@ -9034,6 +9034,32 @@
"watching": "觀察期",
"blocked": "阻塞"
},
"aiLoopLogSources": {
"eyebrow": "AI Loop source labels",
"title": "LOG grouping and learning route",
"subtitle": "Reads metadata-only tags for the current blocker from the priority work-order so KM, RAG, PlayBook, MCP, Verifier, and AI Agent share one source taxonomy.",
"loading": "Loading LOG source labels",
"empty": "No LOG source labels read back yet.",
"blocker": "Current blocker: {value}",
"boundary": "Metadata-only labels; no raw log read, no secret display, no writeback, and no runtime apply from this panel.",
"metrics": {
"tags": "Tags",
"groups": "Groups",
"contract": "Contract"
},
"tagLabels": {
"projectId": "Project",
"product": "Product",
"siteOrRoute": "Site / route",
"service": "Service",
"package": "Package",
"tool": "Tool",
"sourceSystem": "Source system",
"runtimeComponent": "Runtime",
"signalLane": "Signal lane",
"evidenceBoundary": "Evidence boundary"
}
},
"operatorSop": {
"eyebrow": "操作 SOP 判讀",
"title": "AI 受控卡點與自動化缺口接手面板",

View File

@@ -9034,6 +9034,32 @@
"watching": "觀察期",
"blocked": "阻塞"
},
"aiLoopLogSources": {
"eyebrow": "AI Loop 來源貼標",
"title": "LOG 分群與學習路由",
"subtitle": "從 priority work-order 讀回 current blocker 的 metadata-only tags讓 KM、RAG、PlayBook、MCP、Verifier 與 AI Agent 用同一組來源維度學習。",
"loading": "讀取 LOG 來源貼標中",
"empty": "尚未讀回 LOG 來源貼標。",
"blocker": "Current blocker{value}",
"boundary": "只顯示 metadata-only 標籤;不讀 raw log、不顯示 secret、不觸發寫入或 runtime apply。",
"metrics": {
"tags": "Tags",
"groups": "分群鍵",
"contract": "Contract"
},
"tagLabels": {
"projectId": "專案",
"product": "產品",
"siteOrRoute": "網站 / 路由",
"service": "服務",
"package": "套件",
"tool": "工具",
"sourceSystem": "來源系統",
"runtimeComponent": "Runtime",
"signalLane": "訊號 Lane",
"evidenceBoundary": "證據邊界"
}
},
"operatorSop": {
"eyebrow": "操作 SOP 判讀",
"title": "AI 受控卡點與自動化缺口接手面板",

View File

@@ -1013,6 +1013,36 @@ type ReportSourceHealthResponse = {
};
};
type AiLoopLogSourceTag = {
tag_key?: string | null;
tag_value?: string | null;
metadata_only?: boolean | null;
raw_output_allowed?: boolean | null;
secret_value_allowed?: boolean | null;
};
type AiLoopLogSourceContract = {
tag_key?: string | null;
purpose?: string | null;
};
type PriorityWorkOrderResponse = {
summary?: {
ai_loop_current_blocker_id?: string | null;
ai_loop_current_blocker_log_source_tag_count?: number | null;
ai_loop_current_blocker_log_source_tag_keys?: string[] | null;
ai_loop_log_source_grouping_key_count?: number | null;
ai_loop_log_source_grouping_keys?: string[] | null;
ai_loop_log_source_tagging_contract_count?: number | null;
} | null;
in_progress_or_blocked_in_priority_order?: Array<{
evidence?: {
ai_loop_current_blocker_log_source_tags?: AiLoopLogSourceTag[] | null;
ai_loop_log_source_tagging_contract?: AiLoopLogSourceContract[] | null;
} | null;
}>;
};
type Telemetry = {
quality: AutomationQualitySummary | null;
governanceEvents: GovernanceEventsResponse | null;
@@ -1034,6 +1064,7 @@ type Telemetry = {
incidentTimeline: IncidentTimelineResponse | null;
aiRouteStatus: AiRouteStatusResponse | null;
reportSourceHealth: ReportSourceHealthResponse | null;
priorityWorkOrder: PriorityWorkOrderResponse | null;
};
type AutomationAssetLedgerKey = "km" | "playbook" | "script" | "schedule" | "verifier";
@@ -7729,6 +7760,153 @@ function DriftFingerprintPanel({
);
}
function tagDisplayLabel(
key: string,
labels: Record<string, string>
): string {
return labels[key] ?? key;
}
function AiLoopLogSourceTagsPanel({
priority,
loading,
}: {
priority: PriorityWorkOrderResponse | null;
loading: boolean;
}) {
const t = useTranslations("awooop.workItems.aiLoopLogSources");
const evidence = priority?.in_progress_or_blocked_in_priority_order?.find(
(item) => item.evidence?.ai_loop_current_blocker_log_source_tags?.length
)?.evidence;
const tags = (evidence?.ai_loop_current_blocker_log_source_tags ?? []).filter(
(tag) => Boolean(tag?.tag_key)
);
const contracts = evidence?.ai_loop_log_source_tagging_contract ?? [];
const groupingKeys = priority?.summary?.ai_loop_log_source_grouping_keys ?? [];
const summary = priority?.summary;
const labelMap: Record<string, string> = {
project_id: t("tagLabels.projectId"),
product: t("tagLabels.product"),
site_or_route: t("tagLabels.siteOrRoute"),
service: t("tagLabels.service"),
package: t("tagLabels.package"),
tool: t("tagLabels.tool"),
source_system: t("tagLabels.sourceSystem"),
runtime_component: t("tagLabels.runtimeComponent"),
signal_lane: t("tagLabels.signalLane"),
evidence_boundary: t("tagLabels.evidenceBoundary"),
};
const metrics = [
{
key: "tags",
icon: Fingerprint,
label: t("metrics.tags"),
value: summary?.ai_loop_current_blocker_log_source_tag_count ?? tags.length,
},
{
key: "groups",
icon: Network,
label: t("metrics.groups"),
value: summary?.ai_loop_log_source_grouping_key_count ?? groupingKeys.length,
},
{
key: "contract",
icon: ListChecks,
label: t("metrics.contract"),
value: summary?.ai_loop_log_source_tagging_contract_count ?? contracts.length,
},
];
return (
<section
className="border border-[#e0ddd4] bg-white"
data-testid="ai-loop-log-source-tags-panel"
>
<div className="grid gap-px bg-[#e0ddd4] lg:grid-cols-[minmax(0,1.1fr)_minmax(0,1.6fr)]">
<div className="bg-white p-4">
<div className="flex items-center gap-3">
<Database className="h-5 w-5 text-[#4A90D9]" aria-hidden="true" />
<div className="min-w-0">
<p className="text-[11px] font-semibold uppercase tracking-[0.08em] text-[#9a968c]">
{t("eyebrow")}
</p>
<h3 className="mt-1 text-base font-semibold tracking-normal text-[#141413]">
{t("title")}
</h3>
</div>
</div>
<p className="mt-3 text-xs leading-5 text-[#77736a]">
{t("subtitle")}
</p>
<div className="mt-4 grid gap-px border border-[#e0ddd4] bg-[#e0ddd4] sm:grid-cols-3">
{metrics.map((metric) => {
const Icon = metric.icon;
return (
<div key={metric.key} className="bg-[#faf9f3] p-3">
<div className="flex items-center justify-between gap-2">
<span className="text-[11px] font-semibold text-[#77736a]">
{metric.label}
</span>
<Icon className="h-3.5 w-3.5 text-[#4A90D9]" aria-hidden="true" />
</div>
<div className="mt-2 font-mono text-lg font-semibold text-[#141413]">
{loading ? "--" : metric.value}
</div>
</div>
);
})}
</div>
<div className="mt-3 border border-[#d8d3c7] bg-[#faf9f3] px-3 py-2 font-mono text-[11px] leading-5 text-[#5f5b52]">
{t("blocker", {
value: summary?.ai_loop_current_blocker_id ?? "--",
})}
</div>
</div>
<div className="bg-white p-4">
{tags.length > 0 ? (
<div className="grid gap-2 sm:grid-cols-2 xl:grid-cols-3">
{tags.map((tag) => {
const key = String(tag.tag_key ?? "");
return (
<div
key={key}
className="min-w-0 border border-[#d8d3c7] bg-[#faf9f3] px-3 py-2"
>
<div className="text-[10px] font-semibold uppercase tracking-[0.08em] text-[#9a968c]">
{tagDisplayLabel(key, labelMap)}
</div>
<div className="mt-1 break-all font-mono text-[11px] leading-5 text-[#141413]">
{tag.tag_value || "--"}
</div>
</div>
);
})}
</div>
) : (
<div className="border border-[#d8d3c7] bg-[#faf9f3] px-3 py-6 text-center text-xs text-[#77736a]">
{loading ? t("loading") : t("empty")}
</div>
)}
<div className="mt-3 flex flex-wrap gap-1.5">
{groupingKeys.map((key) => (
<span
key={key}
className="border border-[#c9d8ea] bg-[#eef5ff] px-2 py-0.5 font-mono text-[11px] text-[#1f5b9b]"
>
{tagDisplayLabel(key, labelMap)}
</span>
))}
</div>
<p className="mt-3 text-[11px] leading-5 text-[#77736a]">
{t("boundary")}
</p>
</div>
</div>
</section>
);
}
export default function AwoooPWorkItemsPage() {
const t = useTranslations("awooop.workItems");
const locale = useLocale();
@@ -7767,6 +7945,7 @@ export default function AwoooPWorkItemsPage() {
incidentTimeline: null,
aiRouteStatus: null,
reportSourceHealth: null,
priorityWorkOrder: null,
});
const [loading, setLoading] = useState(true);
const [lastUpdated, setLastUpdated] = useState<Date | null>(null);
@@ -7816,6 +7995,7 @@ export default function AwoooPWorkItemsPage() {
projectId
);
const reportSourceHealthUrl = `${API_BASE}/api/v1/agents/agent-report-source-health`;
const priorityWorkOrderUrl = `${API_BASE}/api/v1/agents/awoooi-priority-work-order-readback`;
const [
quality,
@@ -7836,6 +8016,7 @@ export default function AwoooPWorkItemsPage() {
callbackReplies,
aiRouteStatus,
reportSourceHealth,
priorityWorkOrder,
] = await Promise.all([
fetchJson<AutomationQualitySummary>(qualityUrl, 15000),
fetchJson<GovernanceEventsResponse>(governanceEventsUrl),
@@ -7855,6 +8036,7 @@ export default function AwoooPWorkItemsPage() {
fetchJson<CallbackRepliesWorkItemResponse>(callbackRepliesUrl, 12000),
fetchJson<AiRouteStatusResponse>(aiRouteStatusUrl, 12000),
fetchJson<ReportSourceHealthResponse>(reportSourceHealthUrl, 12000),
fetchJson<PriorityWorkOrderResponse>(priorityWorkOrderUrl, 12000),
]);
const statusChainIncidentId = selectStatusChainIncidentId(
@@ -7903,6 +8085,7 @@ export default function AwoooPWorkItemsPage() {
incidentTimeline,
aiRouteStatus,
reportSourceHealth,
priorityWorkOrder,
});
setLastUpdated(new Date());
setLoading(false);
@@ -7996,6 +8179,11 @@ export default function AwoooPWorkItemsPage() {
<AutonomousRuntimeReceiptPanel mode="compact" />
<AiLoopLogSourceTagsPanel
priority={telemetry.priorityWorkOrder}
loading={loading}
/>
<OperatorSopRail
telemetry={telemetry}
workItems={workItems}

View File

@@ -1,3 +1,19 @@
## 2026-07-01 — 14:55 Work Items 顯示 AI Loop LOG source tags
**照主線修正的問題**
- API 已輸出 current blocker 的 LOG 來源貼標與分群 contract但 AwoooP Work Items 尚未把這些 tags 顯示出來,使用者仍只能從長段 evidence 文字判斷 project / product / service / package / tool / source_system / signal_lane。
- 本次在 `/awooop/work-items` telemetry 加入 `/api/v1/agents/awoooi-priority-work-order-readback`,新增 `AI Loop 來源貼標` 面板,直接顯示 current blocker、tag count、grouping keys、contract count 與 10 個 metadata-only source tags。
- 這讓 Work Items 第一屏能看見 LOG 已依 project、product、site_or_route、service、package、tool、source_system、runtime_component、signal_lane、evidence_boundary 分群,後續 UI 可用同一 contract 做篩選或 chips不再只靠大段描述文字。
**驗證**
- `python3.11 -m json.tool --indent 2 apps/web/messages/zh-TW.json >/dev/null && python3.11 -m json.tool --indent 2 apps/web/messages/en.json >/dev/null`:通過。
- `python3.11 scripts/security/awooop-controlled-automation-copy-guard.py``AWOOOP_CONTROLLED_AUTOMATION_COPY_GUARD_OK`
- `python3.11 scripts/dev/awoooi-navigation-coverage-guard.py``AWOOOI_NAVIGATION_COVERAGE_GUARD_OK routes=47 nav_routes=46 nav_label_keys=50`
- `git diff --check`:通過。
- `pnpm --dir apps/web typecheck`:未完成,本機 `node_modules` 仍缺 `typescript@5.9.3/.../bin/tsc`,不是 TypeScript 診斷。
**邊界**:只改 AwoooP Work Items UI、i18n 與 LOGBOOK未使用 GitHub / `gh` / GitHub API未 workflow_dispatch未讀 secret / token / `.env` / raw sessions / SQLite / auth未執行 runtime apply。
## 2026-07-01 — 14:35 AI Loop LOG 來源貼標與分群 contract
**照主線修正的問題**