582 lines
28 KiB
YAML
582 lines
28 KiB
YAML
# =============================================================================
|
||
# AWOOOI Agent Market Watch (Gitea Actions)
|
||
# =============================================================================
|
||
# Weekly read-only AI Agent market scan. This workflow detects primary-source
|
||
# changes only; it does not install SDKs, call LLM APIs, commit reports, approve
|
||
# shadow/canary, or change production routing.
|
||
|
||
name: Agent Market Watch
|
||
|
||
on:
|
||
workflow_dispatch:
|
||
schedule:
|
||
- cron: '0 1 * * 1' # 每週一 09:00 台北 (UTC+8)
|
||
|
||
env:
|
||
GITEA_ACTIONS_URL: http://192.168.0.110:3001/wooo/awoooi/actions
|
||
SRE_GROUP_CHAT_ID: "-1003711974679"
|
||
|
||
jobs:
|
||
market-watch:
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 10
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
|
||
- name: Run read-only market watch
|
||
id: watch
|
||
run: |
|
||
set -euo pipefail
|
||
REPORT="/tmp/agent_market_watch_report.json"
|
||
PREVIOUS_REPORT="$(find docs/evaluations -maxdepth 1 -type f -name 'agent_market_watch_report_*.json' | sort | tail -n 1 || true)"
|
||
PREVIOUS_ARGS=()
|
||
if [ -n "$PREVIOUS_REPORT" ]; then
|
||
PREVIOUS_ARGS=(--previous-report "$PREVIOUS_REPORT")
|
||
echo "Using previous committed market watch baseline: $PREVIOUS_REPORT"
|
||
else
|
||
echo "No previous committed market watch baseline found; running first live baseline."
|
||
fi
|
||
|
||
python3 scripts/agents/agent-market-watch.py \
|
||
--registry docs/ai/agent-market-watch-sources.v1.json \
|
||
--output "$REPORT" \
|
||
--mode live \
|
||
--timeout-seconds 12 \
|
||
"${PREVIOUS_ARGS[@]}"
|
||
|
||
python3 -m json.tool "$REPORT" >/dev/null
|
||
python3 - "$REPORT" <<'PY'
|
||
import json
|
||
import os
|
||
import sys
|
||
|
||
report_path = sys.argv[1]
|
||
with open(report_path, encoding="utf-8") as handle:
|
||
data = json.load(handle)
|
||
|
||
if data.get("schema_version") != "agent_market_watch_report_v1":
|
||
raise SystemExit("unexpected market watch schema_version")
|
||
if data.get("mode") != "live":
|
||
raise SystemExit("market watch workflow must run in live mode")
|
||
|
||
summary = data.get("summary")
|
||
if not isinstance(summary, dict):
|
||
raise SystemExit("missing market watch summary")
|
||
|
||
required = [
|
||
"candidate_count",
|
||
"source_count",
|
||
"changed_candidates",
|
||
"watch_only_candidates",
|
||
"integration_queue_count",
|
||
"failure_count",
|
||
]
|
||
missing = [key for key in required if key not in summary]
|
||
if missing:
|
||
raise SystemExit(f"missing market watch summary keys: {missing}")
|
||
|
||
integration_queue = data.get("integration_queue")
|
||
if not isinstance(integration_queue, list):
|
||
raise SystemExit("integration_queue must be a list")
|
||
|
||
output_path = os.environ.get("GITHUB_OUTPUT")
|
||
if output_path:
|
||
with open(output_path, "a", encoding="utf-8") as handle:
|
||
for key in required:
|
||
handle.write(f"{key}={summary.get(key, 0)}\n")
|
||
|
||
step_summary_path = os.environ.get("GITHUB_STEP_SUMMARY")
|
||
if step_summary_path:
|
||
with open(step_summary_path, "a", encoding="utf-8") as handle:
|
||
handle.write("## Agent Market Watch\n\n")
|
||
handle.write(f"- Candidates: {summary['candidate_count']}\n")
|
||
handle.write(f"- Sources: {summary['source_count']}\n")
|
||
handle.write(f"- Changed candidates: {summary['changed_candidates']}\n")
|
||
handle.write(f"- Integration queue: {summary['integration_queue_count']}\n")
|
||
handle.write(f"- Source failures: {summary['failure_count']}\n")
|
||
handle.write("\nPolicy: read-only watch; no SDK/API/prod change is approved by this workflow.\n")
|
||
|
||
print(json.dumps(summary, ensure_ascii=False, sort_keys=True))
|
||
PY
|
||
|
||
- name: Run read-only integration review
|
||
id: review
|
||
run: |
|
||
set -euo pipefail
|
||
REVIEW="/tmp/agent_market_integration_review.json"
|
||
python3 scripts/agents/agent-market-integration-review.py \
|
||
--watch-report /tmp/agent_market_watch_report.json \
|
||
--candidates docs/ai/agent-replacement-candidates.v1.json \
|
||
--scorecard docs/evaluations/agent_market_capability_scorecard_2026-06-01.json \
|
||
--review-scope all \
|
||
--output "$REVIEW"
|
||
|
||
python3 -m json.tool "$REVIEW" >/dev/null
|
||
python3 - "$REVIEW" <<'PY'
|
||
import json
|
||
import os
|
||
import sys
|
||
|
||
review_path = sys.argv[1]
|
||
with open(review_path, encoding="utf-8") as handle:
|
||
data = json.load(handle)
|
||
|
||
if data.get("schema_version") != "agent_market_integration_review_v1":
|
||
raise SystemExit("unexpected integration review schema_version")
|
||
policy = data.get("policy") or {}
|
||
forbidden = [
|
||
"production_changes_approved",
|
||
"replacement_decision_allowed",
|
||
"sdk_installation_approved",
|
||
"paid_api_calls_approved",
|
||
"shadow_or_canary_approved",
|
||
]
|
||
unsafe = [key for key in forbidden if policy.get(key) is not False]
|
||
if unsafe:
|
||
raise SystemExit(f"integration review policy must stay false: {unsafe}")
|
||
|
||
summary = data.get("summary")
|
||
if not isinstance(summary, dict):
|
||
raise SystemExit("missing integration review summary")
|
||
required = [
|
||
"reviewed_candidates",
|
||
"blocked_from_integration",
|
||
"requires_cost_approval",
|
||
"requires_dependency_approval",
|
||
"source_failures",
|
||
"production_changes_approved",
|
||
"shadow_or_canary_approved",
|
||
]
|
||
missing = [key for key in required if key not in summary]
|
||
if missing:
|
||
raise SystemExit(f"missing integration review summary keys: {missing}")
|
||
|
||
output_path = os.environ.get("GITHUB_OUTPUT")
|
||
if output_path:
|
||
with open(output_path, "a", encoding="utf-8") as handle:
|
||
for key in required:
|
||
handle.write(f"{key}={summary.get(key, 0)}\n")
|
||
|
||
step_summary_path = os.environ.get("GITHUB_STEP_SUMMARY")
|
||
if step_summary_path:
|
||
with open(step_summary_path, "a", encoding="utf-8") as handle:
|
||
handle.write("\n## Agent Integration Review\n\n")
|
||
handle.write("- Review scope: all candidates\n")
|
||
handle.write(f"- Reviewed candidates: {summary['reviewed_candidates']}\n")
|
||
handle.write(f"- Blocked from integration: {summary['blocked_from_integration']}\n")
|
||
handle.write(f"- Cost approvals required: {summary['requires_cost_approval']}\n")
|
||
handle.write(f"- Dependency approvals required: {summary['requires_dependency_approval']}\n")
|
||
handle.write(f"- Production changes approved: {summary['production_changes_approved']}\n")
|
||
handle.write(f"- Shadow/canary approved: {summary['shadow_or_canary_approved']}\n")
|
||
|
||
print(json.dumps(summary, ensure_ascii=False, sort_keys=True))
|
||
PY
|
||
|
||
- name: Run read-only discovery review
|
||
id: discovery
|
||
run: |
|
||
set -euo pipefail
|
||
DISCOVERY="/tmp/agent_market_discovery_review.json"
|
||
PREVIOUS_DISCOVERY="$(find docs/evaluations -maxdepth 1 -type f -name 'agent_market_discovery_review_*.json' | sort | tail -n 1 || true)"
|
||
PREVIOUS_ARGS=()
|
||
if [ -n "$PREVIOUS_DISCOVERY" ]; then
|
||
PREVIOUS_ARGS=(--previous-review "$PREVIOUS_DISCOVERY")
|
||
echo "Using previous committed discovery review baseline: $PREVIOUS_DISCOVERY"
|
||
else
|
||
echo "No previous committed discovery review baseline found; running first discovery intake."
|
||
fi
|
||
|
||
python3 scripts/agents/agent-market-discovery-review.py \
|
||
--watch-report /tmp/agent_market_watch_report.json \
|
||
--candidates docs/ai/agent-replacement-candidates.v1.json \
|
||
--source-registry docs/ai/agent-market-watch-sources.v1.json \
|
||
--output "$DISCOVERY" \
|
||
"${PREVIOUS_ARGS[@]}"
|
||
|
||
python3 -m json.tool "$DISCOVERY" >/dev/null
|
||
python3 - "$DISCOVERY" <<'PY'
|
||
import json
|
||
import os
|
||
import sys
|
||
|
||
discovery_path = sys.argv[1]
|
||
with open(discovery_path, encoding="utf-8") as handle:
|
||
data = json.load(handle)
|
||
|
||
if data.get("schema_version") != "agent_market_discovery_review_v1":
|
||
raise SystemExit("unexpected discovery review schema_version")
|
||
policy = data.get("policy") or {}
|
||
forbidden = [
|
||
"auto_registry_addition_approved",
|
||
"sdk_installation_approved",
|
||
"paid_api_calls_approved",
|
||
"production_changes_approved",
|
||
"shadow_or_canary_approved",
|
||
"replacement_decision_allowed",
|
||
]
|
||
unsafe = [key for key in forbidden if policy.get(key) is not False]
|
||
if unsafe:
|
||
raise SystemExit(f"discovery review policy must stay false: {unsafe}")
|
||
|
||
summary = data.get("summary")
|
||
if not isinstance(summary, dict):
|
||
raise SystemExit("missing discovery review summary")
|
||
required = [
|
||
"discovery_sources",
|
||
"discovered_items",
|
||
"unique_repositories",
|
||
"already_watched_or_registered",
|
||
"manual_classification_required",
|
||
"new_manual_classification_required",
|
||
"source_failures",
|
||
]
|
||
missing = [key for key in required if key not in summary]
|
||
if missing:
|
||
raise SystemExit(f"missing discovery review summary keys: {missing}")
|
||
|
||
output_path = os.environ.get("GITHUB_OUTPUT")
|
||
if output_path:
|
||
with open(output_path, "a", encoding="utf-8") as handle:
|
||
for key in required:
|
||
handle.write(f"{key}={summary.get(key, 0)}\n")
|
||
|
||
step_summary_path = os.environ.get("GITHUB_STEP_SUMMARY")
|
||
if step_summary_path:
|
||
with open(step_summary_path, "a", encoding="utf-8") as handle:
|
||
handle.write("\n## Agent Discovery Review\n\n")
|
||
handle.write(f"- Discovery sources: {summary['discovery_sources']}\n")
|
||
handle.write(f"- Unique repositories: {summary['unique_repositories']}\n")
|
||
handle.write(f"- Already watched/registered: {summary['already_watched_or_registered']}\n")
|
||
handle.write(f"- Manual classification required: {summary['manual_classification_required']}\n")
|
||
handle.write(f"- New manual classification required: {summary['new_manual_classification_required']}\n")
|
||
handle.write("\nPolicy: read-only intake; no registry addition, SDK/API, shadow/canary, or production change is approved.\n")
|
||
|
||
print(json.dumps(summary, ensure_ascii=False, sort_keys=True))
|
||
PY
|
||
|
||
- name: Run read-only discovery classification
|
||
id: classify
|
||
if: ${{ steps.discovery.outputs.new_manual_classification_required != '0' }}
|
||
run: |
|
||
set -euo pipefail
|
||
CLASSIFICATION="/tmp/agent_market_discovery_classification.json"
|
||
python3 scripts/agents/agent-market-discovery-classify.py \
|
||
--discovery-review /tmp/agent_market_discovery_review.json \
|
||
--output "$CLASSIFICATION" \
|
||
--timeout-seconds 12
|
||
|
||
python3 -m json.tool "$CLASSIFICATION" >/dev/null
|
||
python3 - "$CLASSIFICATION" <<'PY'
|
||
import json
|
||
import os
|
||
import sys
|
||
|
||
classification_path = sys.argv[1]
|
||
with open(classification_path, encoding="utf-8") as handle:
|
||
data = json.load(handle)
|
||
|
||
if data.get("schema_version") != "agent_market_discovery_classification_v1":
|
||
raise SystemExit("unexpected discovery classification schema_version")
|
||
policy = data.get("policy") or {}
|
||
forbidden = [
|
||
"auto_watch_registry_addition_approved",
|
||
"sdk_installation_approved",
|
||
"paid_api_calls_approved",
|
||
"production_changes_approved",
|
||
"shadow_or_canary_approved",
|
||
"replacement_decision_allowed",
|
||
]
|
||
unsafe = [key for key in forbidden if policy.get(key) is not False]
|
||
if unsafe:
|
||
raise SystemExit(f"discovery classification policy must stay false: {unsafe}")
|
||
|
||
summary = data.get("summary")
|
||
if not isinstance(summary, dict):
|
||
raise SystemExit("missing discovery classification summary")
|
||
required = [
|
||
"classified_repositories",
|
||
"recommended_watch_additions",
|
||
"watch_only_or_defer",
|
||
"production_changes_approved",
|
||
"shadow_or_canary_approved",
|
||
]
|
||
missing = [key for key in required if key not in summary]
|
||
if missing:
|
||
raise SystemExit(f"missing discovery classification summary keys: {missing}")
|
||
|
||
output_path = os.environ.get("GITHUB_OUTPUT")
|
||
if output_path:
|
||
with open(output_path, "a", encoding="utf-8") as handle:
|
||
for key in required:
|
||
handle.write(f"{key}={summary.get(key, 0)}\n")
|
||
|
||
step_summary_path = os.environ.get("GITHUB_STEP_SUMMARY")
|
||
if step_summary_path:
|
||
with open(step_summary_path, "a", encoding="utf-8") as handle:
|
||
handle.write("\n## Agent Discovery Classification\n\n")
|
||
handle.write(f"- Classified repositories: {summary['classified_repositories']}\n")
|
||
handle.write(f"- Recommended watch additions: {summary['recommended_watch_additions']}\n")
|
||
handle.write(f"- Watch-only/defer: {summary['watch_only_or_defer']}\n")
|
||
handle.write("\nPolicy: read-only classification; no watch registry addition, SDK/API, replay, shadow/canary, or production change is approved.\n")
|
||
|
||
print(json.dumps(summary, ensure_ascii=False, sort_keys=True))
|
||
PY
|
||
|
||
- name: Run read-only watch promotion review
|
||
id: promote
|
||
run: |
|
||
set -euo pipefail
|
||
PROMOTION="/tmp/agent_market_watch_promotion_review.json"
|
||
CLASSIFICATION="/tmp/agent_market_discovery_classification.json"
|
||
if [ ! -f "$CLASSIFICATION" ]; then
|
||
PREVIOUS_CLASSIFICATION="$(find docs/evaluations -maxdepth 1 -type f -name 'agent_market_discovery_classification_*.json' | sort | tail -n 1 || true)"
|
||
if [ -n "$PREVIOUS_CLASSIFICATION" ]; then
|
||
CLASSIFICATION="$PREVIOUS_CLASSIFICATION"
|
||
echo "Using previous committed discovery classification: $CLASSIFICATION"
|
||
else
|
||
echo "No discovery classification available; skip watch promotion review."
|
||
exit 0
|
||
fi
|
||
fi
|
||
|
||
python3 scripts/agents/agent-market-watch-promotion-review.py \
|
||
--watch-report /tmp/agent_market_watch_report.json \
|
||
--integration-review /tmp/agent_market_integration_review.json \
|
||
--discovery-classification "$CLASSIFICATION" \
|
||
--candidates docs/ai/agent-replacement-candidates.v1.json \
|
||
--output "$PROMOTION"
|
||
|
||
python3 -m json.tool "$PROMOTION" >/dev/null
|
||
python3 - "$PROMOTION" <<'PY'
|
||
import json
|
||
import os
|
||
import sys
|
||
|
||
promotion_path = sys.argv[1]
|
||
with open(promotion_path, encoding="utf-8") as handle:
|
||
data = json.load(handle)
|
||
|
||
if data.get("schema_version") != "agent_market_watch_promotion_review_v1":
|
||
raise SystemExit("unexpected watch promotion review schema_version")
|
||
policy = data.get("policy") or {}
|
||
forbidden = [
|
||
"priority_upgrade_approved",
|
||
"market_scorecard_update_approved",
|
||
"replay_candidate_approved",
|
||
"sdk_installation_approved",
|
||
"paid_api_calls_approved",
|
||
"production_changes_approved",
|
||
"shadow_or_canary_approved",
|
||
"replacement_decision_allowed",
|
||
]
|
||
unsafe = [key for key in forbidden if policy.get(key) is not False]
|
||
if unsafe:
|
||
raise SystemExit(f"watch promotion policy must stay false: {unsafe}")
|
||
|
||
summary = data.get("summary")
|
||
if not isinstance(summary, dict):
|
||
raise SystemExit("missing watch promotion summary")
|
||
required = [
|
||
"watch_only_candidates_reviewed",
|
||
"eligible_for_market_scorecard_prescreen",
|
||
"remain_watch_only",
|
||
"priority_upgrades_approved",
|
||
"market_scorecard_updates_approved",
|
||
"replay_candidates_approved",
|
||
]
|
||
missing = [key for key in required if key not in summary]
|
||
if missing:
|
||
raise SystemExit(f"missing watch promotion summary keys: {missing}")
|
||
|
||
output_path = os.environ.get("GITHUB_OUTPUT")
|
||
if output_path:
|
||
with open(output_path, "a", encoding="utf-8") as handle:
|
||
for key in required:
|
||
handle.write(f"{key}={summary.get(key, 0)}\n")
|
||
|
||
step_summary_path = os.environ.get("GITHUB_STEP_SUMMARY")
|
||
if step_summary_path:
|
||
with open(step_summary_path, "a", encoding="utf-8") as handle:
|
||
handle.write("\n## Agent Watch Promotion Review\n\n")
|
||
handle.write(f"- Watch-only candidates reviewed: {summary['watch_only_candidates_reviewed']}\n")
|
||
handle.write(f"- Eligible for scorecard prescreen: {summary['eligible_for_market_scorecard_prescreen']}\n")
|
||
handle.write(f"- Remain watch-only: {summary['remain_watch_only']}\n")
|
||
handle.write(f"- Priority upgrades approved: {summary['priority_upgrades_approved']}\n")
|
||
handle.write(f"- Replay candidates approved: {summary['replay_candidates_approved']}\n")
|
||
handle.write("\nPolicy: read-only promotion readiness; no priority upgrade, scorecard update, replay, SDK/API, shadow/canary, or production change is approved.\n")
|
||
|
||
print(json.dumps(summary, ensure_ascii=False, sort_keys=True))
|
||
PY
|
||
|
||
- name: Build read-only governance snapshot
|
||
id: snapshot
|
||
run: |
|
||
set -euo pipefail
|
||
SNAPSHOT="/tmp/agent_market_governance_snapshot.json"
|
||
CLASSIFICATION="/tmp/agent_market_discovery_classification.json"
|
||
if [ ! -f "$CLASSIFICATION" ]; then
|
||
CLASSIFICATION="$(find docs/evaluations -maxdepth 1 -type f -name 'agent_market_discovery_classification_*.json' | sort | tail -n 1 || true)"
|
||
fi
|
||
PROMOTION="/tmp/agent_market_watch_promotion_review.json"
|
||
if [ ! -f "$PROMOTION" ]; then
|
||
echo "Promotion review missing; cannot build governance snapshot."
|
||
exit 1
|
||
fi
|
||
|
||
python3 scripts/agents/agent-market-governance-snapshot.py \
|
||
--watch-report /tmp/agent_market_watch_report.json \
|
||
--integration-review /tmp/agent_market_integration_review.json \
|
||
--discovery-classification "$CLASSIFICATION" \
|
||
--promotion-review "$PROMOTION" \
|
||
--candidates docs/ai/agent-replacement-candidates.v1.json \
|
||
--output "$SNAPSHOT"
|
||
|
||
python3 -m json.tool "$SNAPSHOT" >/dev/null
|
||
python3 - "$SNAPSHOT" <<'PY'
|
||
import json
|
||
import os
|
||
import sys
|
||
|
||
snapshot_path = sys.argv[1]
|
||
with open(snapshot_path, encoding="utf-8") as handle:
|
||
data = json.load(handle)
|
||
|
||
if data.get("schema_version") != "agent_market_governance_snapshot_v1":
|
||
raise SystemExit("unexpected governance snapshot schema_version")
|
||
policy = data.get("policy") or {}
|
||
forbidden = [
|
||
"priority_upgrade_approved",
|
||
"market_scorecard_update_approved",
|
||
"replay_candidate_approved",
|
||
"sdk_installation_approved",
|
||
"paid_api_calls_approved",
|
||
"production_changes_approved",
|
||
"shadow_or_canary_approved",
|
||
"replacement_decision_allowed",
|
||
]
|
||
unsafe = [key for key in forbidden if policy.get(key) is not False]
|
||
if unsafe:
|
||
raise SystemExit(f"governance snapshot policy must stay false: {unsafe}")
|
||
|
||
summary = data.get("summary")
|
||
if not isinstance(summary, dict):
|
||
raise SystemExit("missing governance snapshot summary")
|
||
required = [
|
||
"candidate_count",
|
||
"source_count",
|
||
"blocked_from_integration",
|
||
"eligible_for_market_scorecard_prescreen",
|
||
"replacement_decisions_approved",
|
||
"replay_candidates_approved",
|
||
"production_changes_approved",
|
||
]
|
||
missing = [key for key in required if key not in summary]
|
||
if missing:
|
||
raise SystemExit(f"missing governance snapshot summary keys: {missing}")
|
||
|
||
output_path = os.environ.get("GITHUB_OUTPUT")
|
||
if output_path:
|
||
with open(output_path, "a", encoding="utf-8") as handle:
|
||
for key in required:
|
||
handle.write(f"{key}={summary.get(key, 0)}\n")
|
||
|
||
step_summary_path = os.environ.get("GITHUB_STEP_SUMMARY")
|
||
if step_summary_path:
|
||
with open(step_summary_path, "a", encoding="utf-8") as handle:
|
||
handle.write("\n## Agent Market Governance Snapshot\n\n")
|
||
handle.write(f"- Current decision: {data['current_decision']}\n")
|
||
handle.write(f"- Candidates: {summary['candidate_count']}\n")
|
||
handle.write(f"- Sources: {summary['source_count']}\n")
|
||
handle.write(f"- Blocked from integration: {summary['blocked_from_integration']}\n")
|
||
handle.write(f"- Scorecard prescreen eligible: {summary['eligible_for_market_scorecard_prescreen']}\n")
|
||
handle.write(f"- Replacement approvals: {summary['replacement_decisions_approved']}\n")
|
||
handle.write(f"- Replay approvals: {summary['replay_candidates_approved']}\n")
|
||
handle.write(f"- Production approvals: {summary['production_changes_approved']}\n")
|
||
|
||
print(json.dumps(summary, ensure_ascii=False, sort_keys=True))
|
||
PY
|
||
|
||
- name: Summarize actionable change or failure
|
||
if: always()
|
||
env:
|
||
TG_CHAT_ID: ${{ env.SRE_GROUP_CHAT_ID }}
|
||
JOB_STATUS: ${{ job.status }}
|
||
CANDIDATE_COUNT: ${{ steps.watch.outputs.candidate_count }}
|
||
SOURCE_COUNT: ${{ steps.watch.outputs.source_count }}
|
||
CHANGED_CANDIDATES: ${{ steps.watch.outputs.changed_candidates }}
|
||
INTEGRATION_QUEUE_COUNT: ${{ steps.watch.outputs.integration_queue_count }}
|
||
FAILURE_COUNT: ${{ steps.watch.outputs.failure_count }}
|
||
REVIEWED_CANDIDATES: ${{ steps.review.outputs.reviewed_candidates }}
|
||
BLOCKED_FROM_INTEGRATION: ${{ steps.review.outputs.blocked_from_integration }}
|
||
REVIEW_COST_APPROVALS: ${{ steps.review.outputs.requires_cost_approval }}
|
||
REVIEW_DEPENDENCY_APPROVALS: ${{ steps.review.outputs.requires_dependency_approval }}
|
||
DISCOVERY_MANUAL_REQUIRED: ${{ steps.discovery.outputs.manual_classification_required }}
|
||
DISCOVERY_NEW_MANUAL_REQUIRED: ${{ steps.discovery.outputs.new_manual_classification_required }}
|
||
DISCOVERY_UNIQUE_REPOSITORIES: ${{ steps.discovery.outputs.unique_repositories }}
|
||
CLASSIFIED_REPOSITORIES: ${{ steps.classify.outputs.classified_repositories }}
|
||
RECOMMENDED_WATCH_ADDITIONS: ${{ steps.classify.outputs.recommended_watch_additions }}
|
||
WATCH_PROMOTION_ELIGIBLE: ${{ steps.promote.outputs.eligible_for_market_scorecard_prescreen }}
|
||
WATCH_PROMOTION_APPROVED: ${{ steps.promote.outputs.priority_upgrades_approved }}
|
||
REPLAY_CANDIDATES_APPROVED: ${{ steps.promote.outputs.replay_candidates_approved }}
|
||
GITEA_ACTIONS_URL: ${{ env.GITEA_ACTIONS_URL }}
|
||
run: |
|
||
set -euo pipefail
|
||
CHANGED="${CHANGED_CANDIDATES:-0}"
|
||
QUEUE="${INTEGRATION_QUEUE_COUNT:-0}"
|
||
FAILURES="${FAILURE_COUNT:-0}"
|
||
NEW_DISCOVERY="${DISCOVERY_NEW_MANUAL_REQUIRED:-0}"
|
||
|
||
if [ "$JOB_STATUS" = "success" ] && [ "$CHANGED" = "0" ] && [ "$QUEUE" = "0" ] && [ "$FAILURES" = "0" ] && [ "$NEW_DISCOVERY" = "0" ]; then
|
||
echo "No actionable market changes; keep Telegram quiet."
|
||
exit 0
|
||
fi
|
||
|
||
python3 - <<'PY'
|
||
import os
|
||
from datetime import datetime
|
||
from zoneinfo import ZoneInfo
|
||
|
||
status = os.environ.get("JOB_STATUS", "unknown")
|
||
changed = os.environ.get("CHANGED_CANDIDATES") or "0"
|
||
queue = os.environ.get("INTEGRATION_QUEUE_COUNT") or "0"
|
||
failures = os.environ.get("FAILURE_COUNT") or "0"
|
||
reviewed = os.environ.get("REVIEWED_CANDIDATES") or "0"
|
||
blocked = os.environ.get("BLOCKED_FROM_INTEGRATION") or "0"
|
||
cost_approvals = os.environ.get("REVIEW_COST_APPROVALS") or "0"
|
||
dependency_approvals = os.environ.get("REVIEW_DEPENDENCY_APPROVALS") or "0"
|
||
discovery_manual = os.environ.get("DISCOVERY_MANUAL_REQUIRED") or "0"
|
||
discovery_new = os.environ.get("DISCOVERY_NEW_MANUAL_REQUIRED") or "0"
|
||
discovery_repos = os.environ.get("DISCOVERY_UNIQUE_REPOSITORIES") or "0"
|
||
classified_repos = os.environ.get("CLASSIFIED_REPOSITORIES") or "0"
|
||
recommended_watch_additions = os.environ.get("RECOMMENDED_WATCH_ADDITIONS") or "0"
|
||
watch_promotion_eligible = os.environ.get("WATCH_PROMOTION_ELIGIBLE") or "0"
|
||
watch_promotion_approved = os.environ.get("WATCH_PROMOTION_APPROVED") or "0"
|
||
replay_candidates_approved = os.environ.get("REPLAY_CANDIDATES_APPROVED") or "0"
|
||
candidates = os.environ.get("CANDIDATE_COUNT") or "0"
|
||
sources = os.environ.get("SOURCE_COUNT") or "0"
|
||
actions_url = os.environ.get("GITEA_ACTIONS_URL", "")
|
||
generated = datetime.now(ZoneInfo("Asia/Taipei")).strftime("%Y-%m-%d %H:%M")
|
||
|
||
title = "Agent Market Watch 需要複核" if status == "success" else "Agent Market Watch 執行失敗"
|
||
lines = [
|
||
f"## {title}",
|
||
"",
|
||
f"- 時間:`{generated}`",
|
||
f"- 狀態:`{status}`",
|
||
f"- 候選 / 來源:`{candidates}` / `{sources}`",
|
||
f"- 變動候選 / 整合佇列 / 來源失敗:`{changed}` / `{queue}` / `{failures}`",
|
||
f"- Review:已審 `{reviewed}`;擋下整合 `{blocked}`;成本批准需求 `{cost_approvals}`;依賴批准需求 `{dependency_approvals}`",
|
||
f"- Discovery:unique repo `{discovery_repos}`;需人工分類 `{discovery_manual}`;新未分類 `{discovery_new}`;已分類 `{classified_repos}`;建議 watch `{recommended_watch_additions}`",
|
||
f"- Promotion:scorecard prescreen eligible `{watch_promotion_eligible}`;priority upgrade approved `{watch_promotion_approved}`;replay approved `{replay_candidates_approved}`",
|
||
"",
|
||
"政策:此 workflow 只建立市場觀察、整合審查、discovery intake/classification 訊號,不批准 SDK 安裝、付費 API、replay、shadow/canary 或 OpenClaw 取代。",
|
||
f"Log:{actions_url}",
|
||
]
|
||
summary = "\n".join(lines) + "\n"
|
||
print(summary)
|
||
step_summary_path = os.environ.get("GITHUB_STEP_SUMMARY")
|
||
if step_summary_path:
|
||
with open(step_summary_path, "a", encoding="utf-8") as handle:
|
||
handle.write(summary)
|
||
PY
|