diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 5b1f005c..adfd9830 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -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 受控卡點與自動化缺口接手面板", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index cde2db52..11034816 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -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 受控卡點與自動化缺口接手面板", diff --git a/apps/web/src/app/[locale]/awooop/work-items/page.tsx b/apps/web/src/app/[locale]/awooop/work-items/page.tsx index eb43c215..6294b9c2 100644 --- a/apps/web/src/app/[locale]/awooop/work-items/page.tsx +++ b/apps/web/src/app/[locale]/awooop/work-items/page.tsx @@ -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 { + 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 = { + 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 ( +
+
+
+
+
+

+ {t("subtitle")} +

+
+ {metrics.map((metric) => { + const Icon = metric.icon; + return ( +
+
+ + {metric.label} + +
+
+ {loading ? "--" : metric.value} +
+
+ ); + })} +
+
+ {t("blocker", { + value: summary?.ai_loop_current_blocker_id ?? "--", + })} +
+
+ +
+ {tags.length > 0 ? ( +
+ {tags.map((tag) => { + const key = String(tag.tag_key ?? ""); + return ( +
+
+ {tagDisplayLabel(key, labelMap)} +
+
+ {tag.tag_value || "--"} +
+
+ ); + })} +
+ ) : ( +
+ {loading ? t("loading") : t("empty")} +
+ )} +
+ {groupingKeys.map((key) => ( + + {tagDisplayLabel(key, labelMap)} + + ))} +
+

+ {t("boundary")} +

+
+
+
+ ); +} + 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(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(qualityUrl, 15000), fetchJson(governanceEventsUrl), @@ -7855,6 +8036,7 @@ export default function AwoooPWorkItemsPage() { fetchJson(callbackRepliesUrl, 12000), fetchJson(aiRouteStatusUrl, 12000), fetchJson(reportSourceHealthUrl, 12000), + fetchJson(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() { + + /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 **照主線修正的問題**: