diff --git a/apps/api/src/services/ai_agent_autonomous_runtime_control.py b/apps/api/src/services/ai_agent_autonomous_runtime_control.py index c3b6a3e1..548c5913 100644 --- a/apps/api/src/services/ai_agent_autonomous_runtime_control.py +++ b/apps/api/src/services/ai_agent_autonomous_runtime_control.py @@ -596,6 +596,153 @@ def _build_log_integration_taxonomy( } +def _build_work_item_progress( + *, + trace_ledger: Mapping[str, Any], + log_integration_taxonomy: Mapping[str, Any], + db_read_status: str, +) -> dict[str, Any]: + """Build ordered work items that the UI and agent can keep advancing.""" + + taxonomy_rollups = log_integration_taxonomy.get("rollups") + if not isinstance(taxonomy_rollups, Mapping): + taxonomy_rollups = {} + source_families = log_integration_taxonomy.get("source_families") + if not isinstance(source_families, list): + source_families = [] + inactive_source_count = _int_value(taxonomy_rollups.get("inactive_source_family_count")) + missing_required = trace_ledger.get("missing_required_stage_ids") + if not isinstance(missing_required, list): + missing_required = [] + deployed_readback_complete = ( + db_read_status == "ok" + and trace_ledger.get("schema_version") == "ai_agent_autonomous_trace_ledger_v1" + and log_integration_taxonomy.get("schema_version") == "ai_agent_log_integration_taxonomy_v1" + ) + + ordered_items = [ + { + "work_item_id": "P0-A-runtime-truth", + "priority": "P0-A", + "title": "Controlled apply runtime truth readback", + "status": "completed", + "exit_criteria": "production API reports db_read_status=ok and live executor receipts", + }, + { + "work_item_id": "P0-B-trace-ledger", + "priority": "P0-B", + "title": "Trace ledger for MCP/log/executor/verifier/KM/PlayBook/Telegram", + "status": "completed" if not missing_required else "in_progress", + "exit_criteria": "trace_ledger exposes required closed-loop stages and missing_required_stage_ids", + }, + { + "work_item_id": "P0-C-log-taxonomy", + "priority": "P0-C", + "title": "Project/product/site/service/package/tool log taxonomy", + "status": "completed", + "exit_criteria": "log_integration_taxonomy lists source families, labels, and public-safety policy", + }, + { + "work_item_id": "P0-D-ui-visibility", + "priority": "P0-D", + "title": "AwoooP UI shows automation loop and log integration progress", + "status": "completed", + "exit_criteria": "AwoooP, Approvals, Runs, and Work Items show trace/log taxonomy panel", + }, + { + "work_item_id": "P0-E-verification-deploy", + "priority": "P0-E", + "title": "Focused verification and production deploy marker readback", + "status": "completed" if deployed_readback_complete else "in_progress", + "exit_criteria": "deploy marker includes this code and production API exposes trace_ledger/log_integration_taxonomy", + "blocker": None if deployed_readback_complete else "waiting_for_successful_gitea_cd_deploy_marker", + }, + { + "work_item_id": "P1-A-ingestion-coverage", + "priority": "P1-A", + "title": "Collector and sanitizer coverage for all source families", + "status": "completed" if inactive_source_count == 0 else "in_progress", + "exit_criteria": "all source families have active sanitized classified events", + "remaining_source_family_count": inactive_source_count, + }, + { + "work_item_id": "P1-B-agent-decision-wiring", + "priority": "P1-B", + "title": "RAG retrieval to PlayBook select/repair/check-mode/apply/verifier", + "status": "pending", + "exit_criteria": "AI Agent consumes labeled evidence and emits target selector, dry-run, apply, verifier, rollback", + }, + { + "work_item_id": "P1-C-learning-loop", + "priority": "P1-C", + "title": "KM / PlayBook trust learning loop", + "status": "pending", + "exit_criteria": "verified execution updates KM entries, trust delta, similar-case clusters, and repair candidates", + }, + { + "work_item_id": "P1-D-alert-noise-reduction", + "priority": "P1-D", + "title": "Alert grouping and AI controlled workflow routing", + "status": "pending", + "exit_criteria": "repeated alerts are clustered, deduped, routed to controlled automation, and no longer default to manual handling", + }, + { + "work_item_id": "P2-A-ui-ux-productization", + "priority": "P2-A", + "title": "Professional product UI replacing text-heavy surfaces", + "status": "pending", + "exit_criteria": "AI automation status is shown as dense dashboard controls, filters, counters, and action rails", + }, + { + "work_item_id": "P2-B-multi-product-expansion", + "priority": "P2-B", + "title": "Reuse taxonomy across AWOOOI products/projects", + "status": "pending", + "exit_criteria": "StockPlatform, VibeWork, MOMO, AwoooGo, and other products report the same log taxonomy contract", + }, + ] + source_family_items = [] + for source in source_families: + if not isinstance(source, Mapping): + continue + total = _int_value(source.get("total")) + source_family_items.append({ + "work_item_id": f"P1-A-source-{source.get('source_family_id')}", + "priority": "P1-A", + "source_family_id": source.get("source_family_id"), + "title": f"Ingest and label {source.get('source_family_id')}", + "status": "completed" if total > 0 else "not_started", + "label_dimensions": source.get("label_dimensions") or [], + "next_controlled_action": ( + "keep_learning_and_quality_checks" + if total > 0 + else source.get("next_action_if_empty") + ), + }) + + all_items = [*ordered_items, *source_family_items] + by_status: dict[str, int] = {} + for item in all_items: + status = str(item.get("status") or "unknown") + by_status[status] = by_status.get(status, 0) + 1 + return { + "schema_version": "ai_agent_automation_work_item_progress_v1", + "ordered_items": ordered_items, + "source_family_items": source_family_items, + "rollups": { + "work_item_count": len(all_items), + "ordered_work_item_count": len(ordered_items), + "source_family_work_item_count": len(source_family_items), + "completed_count": by_status.get("completed", 0), + "in_progress_count": by_status.get("in_progress", 0), + "pending_count": by_status.get("pending", 0), + "blocked_count": by_status.get("blocked", 0), + "not_started_count": by_status.get("not_started", 0), + "by_status": by_status, + }, + } + + def _first_operation( rows: Iterable[Mapping[str, Any]], operation_type: str, @@ -1396,6 +1543,11 @@ def build_runtime_receipt_readback_from_rows( timeline_summary=timeline_summary, playbook_trust_summary=playbook_trust_summary, ) + work_item_progress = _build_work_item_progress( + trace_ledger=trace_ledger, + log_integration_taxonomy=log_integration_taxonomy, + db_read_status=db_read_status, + ) apply_summary = operation_summary.get("ansible_apply_executed") or {} readback = { "schema_version": _LIVE_READBACK_SCHEMA_VERSION, @@ -1515,6 +1667,7 @@ def build_runtime_receipt_readback_from_rows( "autonomous_execution_loop_ledger": loop_ledger, "trace_ledger": trace_ledger, "log_integration_taxonomy": log_integration_taxonomy, + "work_item_progress": work_item_progress, } if error_type: readback["error"] = { @@ -1617,6 +1770,31 @@ def _attach_runtime_receipt_readback( "recent_classified_event_total" ) ), + "live_work_item_count": _int_value( + ((readback.get("work_item_progress") or {}).get("rollups") or {}).get( + "work_item_count" + ) + ), + "live_work_item_completed_count": _int_value( + ((readback.get("work_item_progress") or {}).get("rollups") or {}).get( + "completed_count" + ) + ), + "live_work_item_in_progress_count": _int_value( + ((readback.get("work_item_progress") or {}).get("rollups") or {}).get( + "in_progress_count" + ) + ), + "live_work_item_pending_count": _int_value( + ((readback.get("work_item_progress") or {}).get("rollups") or {}).get( + "pending_count" + ) + ), + "live_work_item_blocked_count": _int_value( + ((readback.get("work_item_progress") or {}).get("rollups") or {}).get( + "blocked_count" + ) + ), }) return payload diff --git a/apps/api/tests/test_ai_agent_autonomous_runtime_control.py b/apps/api/tests/test_ai_agent_autonomous_runtime_control.py index 06832956..b20d7bb1 100644 --- a/apps/api/tests/test_ai_agent_autonomous_runtime_control.py +++ b/apps/api/tests/test_ai_agent_autonomous_runtime_control.py @@ -384,6 +384,28 @@ def test_runtime_receipt_readback_summarizes_live_executor_closure_rows(): assert taxonomy["rollups"]["classified_event_total"] > 0 assert taxonomy["public_safety"]["raw_secret_collection_allowed"] is False assert taxonomy["public_safety"]["unredacted_payload_storage_allowed"] is False + progress = readback["work_item_progress"] + assert progress["schema_version"] == "ai_agent_automation_work_item_progress_v1" + ordered_ids = [item["work_item_id"] for item in progress["ordered_items"]] + assert ordered_ids == [ + "P0-A-runtime-truth", + "P0-B-trace-ledger", + "P0-C-log-taxonomy", + "P0-D-ui-visibility", + "P0-E-verification-deploy", + "P1-A-ingestion-coverage", + "P1-B-agent-decision-wiring", + "P1-C-learning-loop", + "P1-D-alert-noise-reduction", + "P2-A-ui-ux-productization", + "P2-B-multi-product-expansion", + ] + assert progress["ordered_items"][4]["status"] == "completed" + assert progress["ordered_items"][5]["status"] == "completed" + assert progress["source_family_items"] + assert {item["status"] for item in progress["source_family_items"]} == {"completed"} + assert progress["rollups"]["source_family_work_item_count"] == 10 + assert progress["rollups"]["pending_count"] >= 5 def test_runtime_receipt_readback_classifies_closed_failed_apply_as_ai_repair(): diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 564cdc5d..fb479ddb 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -11355,7 +11355,9 @@ "sources": "Log sources", "labels": "Label dimensions", "events": "Classified events", - "learning": "Learning sources" + "learning": "Learning sources", + "workItems": "Work items", + "workItemsDetail": "Active {active} / pending {pending} / blocked {blocked}" }, "policy": { "label": "Controlled risk tiers", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 3af12471..42a13225 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -11355,7 +11355,9 @@ "sources": "Log 來源", "labels": "貼標維度", "events": "分類事件", - "learning": "學習來源" + "learning": "學習來源", + "workItems": "工作項目", + "workItemsDetail": "進行 {active} / 待辦 {pending} / 阻塞 {blocked}" }, "policy": { "label": "受控風險層", diff --git a/apps/web/src/components/awooop/autonomous-runtime-receipt-panel.tsx b/apps/web/src/components/awooop/autonomous-runtime-receipt-panel.tsx index a551043a..565bbe45 100644 --- a/apps/web/src/components/awooop/autonomous-runtime-receipt-panel.tsx +++ b/apps/web/src/components/awooop/autonomous-runtime-receipt-panel.tsx @@ -88,6 +88,15 @@ type RuntimeReceiptReadback = { learning_source_family_count?: number | null; } | null; } | null; + work_item_progress?: { + rollups?: { + work_item_count?: number | null; + completed_count?: number | null; + in_progress_count?: number | null; + pending_count?: number | null; + blocked_count?: number | null; + } | null; + } | null; latest_flow_closure?: { apply_op_id?: string | null; incident_id?: string | null; @@ -208,6 +217,7 @@ export function AutonomousRuntimeReceiptPanel({ const traceLedger = readback?.trace_ledger; const logTaxonomy = readback?.log_integration_taxonomy; const logRollups = logTaxonomy?.rollups ?? {}; + const workItemRollups = readback?.work_item_progress?.rollups ?? {}; const latestFlow = readback?.latest_flow_closure; const rollups = payload?.rollups ?? {}; const closed = ledger?.closed === true || latestFlow?.closed === true; @@ -400,7 +410,7 @@ export function AutonomousRuntimeReceiptPanel({ })} -
{t("taxonomy.sources")}
@@ -427,6 +437,21 @@ export function AutonomousRuntimeReceiptPanel({ {numberValue(logRollups.learning_source_family_count)}
{t("taxonomy.workItems")}
++ {numberValue(rollups.live_work_item_completed_count ?? workItemRollups.completed_count)} + {" / "} + {numberValue(rollups.live_work_item_count ?? workItemRollups.work_item_count)} +
++ {t("taxonomy.workItemsDetail", { + active: numberValue(rollups.live_work_item_in_progress_count ?? workItemRollups.in_progress_count), + pending: numberValue(rollups.live_work_item_pending_count ?? workItemRollups.pending_count), + blocked: numberValue(rollups.live_work_item_blocked_count ?? workItemRollups.blocked_count), + })} +
+