diff --git a/.github/workflows/copilot-opt.lock.yml b/.github/workflows/copilot-opt.lock.yml index 6e62c3529ac..4dbd57470d7 100644 --- a/.github/workflows/copilot-opt.lock.yml +++ b/.github/workflows/copilot-opt.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"9bfe372009c5b72b856d32eb5470132a25833d26bb16285dcce48b28c79b8a7c","body_hash":"15eea9df7b1b42f63363098c4d319dcdcd0df77413449d4eed2841201f2a9547","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.77","copilot-sdk":"1.0.8"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"dc75b132aef887fa74e48f15c61b902c059585d4e57f4053cf4b744f8f1b57d1","body_hash":"261613d0ce71d87f5cf34cd89bde4234df3333aa8e89b68883c5d3e90f5ac3b5","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.77","copilot-sdk":"1.0.8"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.43","digest":"sha256:04e2d1987a565000a8f114b89d806ae7a3864dd4f944be65275b28c93d8690e6","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.43@sha256:04e2d1987a565000a8f114b89d806ae7a3864dd4f944be65275b28c93d8690e6"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.43","digest":"sha256:d85f57975af5ea23af4996e41ed73fbc8f5b4a47402472bfe82e508f352cb0c1","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.43@sha256:d85f57975af5ea23af4996e41ed73fbc8f5b4a47402472bfe82e508f352cb0c1"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.43","digest":"sha256:65c45ea2967984d0024f3df61bc71335658a77ede96c8d9665da7a5f33a795ab","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.43@sha256:65c45ea2967984d0024f3df61bc71335658a77ede96c8d9665da7a5f33a795ab"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.43","digest":"sha256:26be5e0b8c8f4c41c8a59126b29bb5d80b07253597472ded2a16bdd75abcbf9d","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.43@sha256:26be5e0b8c8f4c41c8a59126b29bb5d80b07253597472ded2a16bdd75abcbf9d"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.7","digest":"sha256:7545220a9aca134b71e51193ee0eaf4c50756ebf8fbd25a63ae7556e62815c00","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.7@sha256:7545220a9aca134b71e51193ee0eaf4c50756ebf8fbd25a63ae7556e62815c00"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196"},{"image":"ghcr.io/github/github-mcp-server:v1.8.0","digest":"sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520","pinned_image":"ghcr.io/github/github-mcp-server:v1.8.0@sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520"}]} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -527,7 +527,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} name: Fetch Copilot session data - run: "# Create output directories\nmkdir -p /tmp/gh-aw/agent/session-data\nmkdir -p /tmp/gh-aw/agent/session-data/logs\nmkdir -p /tmp/gh-aw/cache-memory\n\n# Get today's date for cache identification\nTODAY=$(date '+%Y-%m-%d')\nCACHE_DIR=\"/tmp/gh-aw/cache-memory\"\n\n# Check if cached data exists from today\nif [ -f \"$CACHE_DIR/copilot-sessions-${TODAY}.json\" ] && [ -s \"$CACHE_DIR/copilot-sessions-${TODAY}.json\" ]; then\n echo \"✓ Found cached session data from ${TODAY}\"\n cp \"$CACHE_DIR/copilot-sessions-${TODAY}.json\" /tmp/gh-aw/agent/session-data/sessions-list.json\n \n # Regenerate schema if missing\n if [ ! -f \"$CACHE_DIR/copilot-sessions-${TODAY}-schema.json\" ]; then\n ./.github/skills/jqschema/jqschema.sh < /tmp/gh-aw/agent/session-data/sessions-list.json > \"$CACHE_DIR/copilot-sessions-${TODAY}-schema.json\"\n fi\n cp \"$CACHE_DIR/copilot-sessions-${TODAY}-schema.json\" /tmp/gh-aw/agent/session-data/sessions-schema.json\n \n # Restore cached log files if they exist\n if [ -d \"$CACHE_DIR/session-logs-${TODAY}\" ]; then\n echo \"✓ Found cached session logs from ${TODAY}\"\n cp -r \"$CACHE_DIR/session-logs-${TODAY}\"/* /tmp/gh-aw/agent/session-data/logs/ 2>/dev/null || true\n echo \"Restored $(find /tmp/gh-aw/agent/session-data/logs -type f | wc -l) session log files from cache\"\n fi\n \n echo \"Using cached data from ${TODAY}\"\n echo \"Total sessions in cache: $(jq 'length' /tmp/gh-aw/agent/session-data/sessions-list.json)\"\nelse\n echo \"⬇ Downloading fresh session data...\"\n \n # Calculate date 30 days ago\n DATE_30_DAYS_AGO=$(date -d '30 days ago' '+%Y-%m-%d' 2>/dev/null || date -v-30d '+%Y-%m-%d')\n\n # Search for workflow runs from copilot/* branches\n # This fetches GitHub Copilot coding agent task runs by searching for workflow runs on copilot/* branches\n echo \"Fetching Copilot coding agent workflow runs from the last 30 days...\"\n \n # Get workflow runs from copilot/* branches\n gh api \"repos/$GITHUB_REPOSITORY/actions/runs\" \\\n --paginate \\\n --jq \".workflow_runs[] | select(.head_branch | startswith(\\\"copilot/\\\")) | select(.created_at >= \\\"${DATE_30_DAYS_AGO}\\\") | {id, name, head_branch, created_at, updated_at, status, conclusion, html_url}\" \\\n | jq -s '.[0:50]' \\\n > /tmp/gh-aw/agent/session-data/sessions-list.json\n\n # Generate schema for reference\n ./.github/skills/jqschema/jqschema.sh < /tmp/gh-aw/agent/session-data/sessions-list.json > /tmp/gh-aw/agent/session-data/sessions-schema.json\n\n # Download conversation logs for actual Copilot agent runs.\n # CI gate runs (e.g. \"Smoke CI\", \"CGO\", \"CWI\" quality gates) always end with\n # conclusion=action_required because a human must approve them to continue; they\n # contain no Copilot agent activity and have no conversation transcript.\n # Each real agent run (conclusion=success/failure) emits [cca-engine] turn= lines\n # in its job log which is the per-turn conversation transcript.\n SESSION_COUNT=$(jq 'length' /tmp/gh-aw/agent/session-data/sessions-list.json)\n AGENT_COUNT=$(jq '[.[] | select(.conclusion != \"action_required\")] | length' /tmp/gh-aw/agent/session-data/sessions-list.json)\n echo \"Downloading conversation logs for $AGENT_COUNT agent runs (skipping $((SESSION_COUNT - AGENT_COUNT)) CI gate runs)...\"\n\n jq -r '.[] | select(.conclusion != \"action_required\") | \"\\(.id) \\(.head_branch)\"' /tmp/gh-aw/agent/session-data/sessions-list.json | while read -r run_id branch; do\n if [ -n \"$run_id\" ]; then\n echo \"Downloading conversation log for run $run_id (branch: $branch)\"\n\n # Get the first job ID for this run via the GitHub API (actions:read suffices).\n # Copilot coding agent runs have exactly one job (\"Copilot Coding Agent\"), so\n # .jobs[0] is always the correct and only job containing the transcript log.\n job_id=$(gh api \"repos/$GITHUB_REPOSITORY/actions/runs/${run_id}/jobs\" \\\n --jq '.jobs[0].id' 2>/dev/null || true)\n\n if [ -n \"$job_id\" ] && [ \"$job_id\" != \"null\" ]; then\n # Download the raw job log; gh api follows the 302 redirect automatically\n gh api \"repos/$GITHUB_REPOSITORY/actions/jobs/${job_id}/logs\" \\\n > \"/tmp/gh-aw/agent/session-data/logs/${run_id}-raw.log\" 2>/dev/null || true\n\n if [ -f \"/tmp/gh-aw/agent/session-data/logs/${run_id}-raw.log\" ] && [ -s \"/tmp/gh-aw/agent/session-data/logs/${run_id}-raw.log\" ]; then\n # Extract conversation transcript: [cca-engine] turn= lines carry turn-by-turn data\n grep \"\\[cca-engine\\] turn=\" \"/tmp/gh-aw/agent/session-data/logs/${run_id}-raw.log\" \\\n > \"/tmp/gh-aw/agent/session-data/logs/${run_id}-conversation.txt\" 2>/dev/null || true\n rm -f \"/tmp/gh-aw/agent/session-data/logs/${run_id}-raw.log\"\n\n if [ -s \"/tmp/gh-aw/agent/session-data/logs/${run_id}-conversation.txt\" ]; then\n LINE_COUNT=$(wc -l < \"/tmp/gh-aw/agent/session-data/logs/${run_id}-conversation.txt\")\n echo \" Saved transcript: $LINE_COUNT lines for run $run_id\"\n else\n echo \" Warning: No [cca-engine] conversation lines found for run $run_id\"\n rm -f \"/tmp/gh-aw/agent/session-data/logs/${run_id}-conversation.txt\"\n fi\n else\n echo \" Warning: Could not download job logs for run $run_id (may be expired)\"\n rm -f \"/tmp/gh-aw/agent/session-data/logs/${run_id}-raw.log\" 2>/dev/null || true\n fi\n else\n echo \" Warning: Could not determine job ID for run $run_id\"\n fi\n fi\n done\n\n LOG_COUNT=$(find /tmp/gh-aw/agent/session-data/logs/ -type f -name \"*-conversation.txt\" | wc -l)\n echo \"Conversation logs downloaded: $LOG_COUNT session logs\"\n\n # Store in cache with today's date\n cp /tmp/gh-aw/agent/session-data/sessions-list.json \"$CACHE_DIR/copilot-sessions-${TODAY}.json\"\n cp /tmp/gh-aw/agent/session-data/sessions-schema.json \"$CACHE_DIR/copilot-sessions-${TODAY}-schema.json\"\n \n # Cache the log files\n mkdir -p \"$CACHE_DIR/session-logs-${TODAY}\"\n cp -r /tmp/gh-aw/agent/session-data/logs/* \"$CACHE_DIR/session-logs-${TODAY}/\" 2>/dev/null || true\n\n echo \"✓ Session data saved to cache: copilot-sessions-${TODAY}.json\"\n echo \"Total sessions found: $(jq 'length' /tmp/gh-aw/agent/session-data/sessions-list.json)\"\nfi\n\n# Always ensure data is available at expected locations for backward compatibility\necho \"Session data available at: /tmp/gh-aw/agent/session-data/sessions-list.json\"\necho \"Schema available at: /tmp/gh-aw/agent/session-data/sessions-schema.json\"\necho \"Logs available at: /tmp/gh-aw/agent/session-data/logs/\"\n\n# Set outputs for downstream use\necho \"sessions_count=$(jq 'length' /tmp/gh-aw/agent/session-data/sessions-list.json)\" >> \"$GITHUB_OUTPUT\"\n" + run: "# Create output directories\nmkdir -p /tmp/gh-aw/agent/session-data\nmkdir -p /tmp/gh-aw/agent/session-data/logs\nmkdir -p /tmp/gh-aw/cache-memory\n\n# Get today's date for cache identification\nTODAY=$(date '+%Y-%m-%d')\nCACHE_DIR=\"/tmp/gh-aw/cache-memory\"\n\ncount_agent_logs() {\n sessions_file=\"$1\"\n logs_dir=\"$2\"\n count=0\n\n if [ ! -f \"$sessions_file\" ] || [ ! -d \"$logs_dir\" ]; then\n echo 0\n return\n fi\n\n while read -r agent_run_id; do\n if [ -s \"$logs_dir/${agent_run_id}-events.jsonl\" ] || [ -s \"$logs_dir/${agent_run_id}-conversation.txt\" ]; then\n count=$((count + 1))\n fi\n done < <(jq -r '.[] | select(.status == \"completed\" and .conclusion != \"action_required\") | .id' \"$sessions_file\")\n\n echo \"$count\"\n}\n\nUSE_CACHE=false\n\n# Check if cached data exists from today\nif [ -f \"$CACHE_DIR/copilot-sessions-${TODAY}.json\" ] && [ -s \"$CACHE_DIR/copilot-sessions-${TODAY}.json\" ]; then\n CACHED_AGENT_COUNT=$(jq '[.[] | select(.status == \"completed\" and .conclusion != \"action_required\")] | length' \"$CACHE_DIR/copilot-sessions-${TODAY}.json\")\n CACHED_LOG_RUN_COUNT=$(count_agent_logs \"$CACHE_DIR/copilot-sessions-${TODAY}.json\" \"$CACHE_DIR/session-logs-${TODAY}\")\n if [ \"$CACHED_AGENT_COUNT\" -eq 0 ] || [ \"$CACHED_LOG_RUN_COUNT\" -ge \"$CACHED_AGENT_COUNT\" ]; then\n USE_CACHE=true\n else\n echo \"::warning::Cached session metadata exists but only $CACHED_LOG_RUN_COUNT of $CACHED_AGENT_COUNT agent runs have logs; refreshing session data\"\n fi\nfi\n\nif [ \"$USE_CACHE\" = \"true\" ]; then\n echo \"✓ Found cached session data from ${TODAY}\"\n cp \"$CACHE_DIR/copilot-sessions-${TODAY}.json\" /tmp/gh-aw/agent/session-data/sessions-list.json\n \n # Regenerate schema if missing\n if [ ! -f \"$CACHE_DIR/copilot-sessions-${TODAY}-schema.json\" ]; then\n ./.github/skills/jqschema/jqschema.sh < /tmp/gh-aw/agent/session-data/sessions-list.json > \"$CACHE_DIR/copilot-sessions-${TODAY}-schema.json\"\n fi\n cp \"$CACHE_DIR/copilot-sessions-${TODAY}-schema.json\" /tmp/gh-aw/agent/session-data/sessions-schema.json\n \n # Restore cached log files if they exist\n if [ -d \"$CACHE_DIR/session-logs-${TODAY}\" ]; then\n echo \"✓ Found cached session logs from ${TODAY}\"\n cp -r \"$CACHE_DIR/session-logs-${TODAY}\"/* /tmp/gh-aw/agent/session-data/logs/ 2>/dev/null || true\n echo \"Restored $(find /tmp/gh-aw/agent/session-data/logs -type f | wc -l) session log files from cache\"\n fi\n \n echo \"Using cached data from ${TODAY}\"\n echo \"Total sessions in cache: $(jq 'length' /tmp/gh-aw/agent/session-data/sessions-list.json)\"\nelse\n echo \"⬇ Downloading fresh session data...\"\n \n # Calculate date 30 days ago\n DATE_30_DAYS_AGO=$(date -d '30 days ago' '+%Y-%m-%d' 2>/dev/null || date -v-30d '+%Y-%m-%d')\n\n # Search for workflow runs from copilot/* branches\n # This fetches GitHub Copilot coding agent task runs by searching for workflow runs on copilot/* branches\n echo \"Fetching Copilot coding agent workflow runs from the last 30 days...\"\n \n # Get workflow runs from copilot/* branches\n gh api \"repos/$GITHUB_REPOSITORY/actions/runs\" \\\n --paginate \\\n --jq \".workflow_runs[] | select(.head_branch | startswith(\\\"copilot/\\\")) | select(.created_at >= \\\"${DATE_30_DAYS_AGO}\\\") | {id, name, head_branch, created_at, updated_at, status, conclusion, html_url}\" \\\n | jq -s '.[0:50]' \\\n > /tmp/gh-aw/agent/session-data/sessions-list.json\n\n # Generate schema for reference\n ./.github/skills/jqschema/jqschema.sh < /tmp/gh-aw/agent/session-data/sessions-list.json > /tmp/gh-aw/agent/session-data/sessions-schema.json\n\n # Download per-session logs for actual Copilot agent runs.\n # CI gate runs (e.g. \"Smoke CI\", \"CGO\", \"CWI\" quality gates) always end with\n # conclusion=action_required because a human must approve them to continue; they\n # contain no Copilot agent activity and have no conversation transcript.\n # Prefer structured events.jsonl artifacts; fall back to [cca-engine] turn= lines\n # in raw Actions job logs when no events.jsonl artifact is available.\n SESSION_COUNT=$(jq 'length' /tmp/gh-aw/agent/session-data/sessions-list.json)\n AGENT_COUNT=$(jq '[.[] | select(.status == \"completed\" and .conclusion != \"action_required\")] | length' /tmp/gh-aw/agent/session-data/sessions-list.json)\n echo \"Downloading per-session logs for $AGENT_COUNT agent runs (skipping $((SESSION_COUNT - AGENT_COUNT)) CI gate runs)...\"\n\n RUNS_TO_FETCH=\"${RUNNER_TEMP:-/tmp}/copilot-session-runs-${TODAY}.txt\"\n jq -r '.[] | select(.status == \"completed\" and .conclusion != \"action_required\") | \"\\(.id) \\(.head_branch)\"' /tmp/gh-aw/agent/session-data/sessions-list.json > \"$RUNS_TO_FETCH\"\n\n while read -r run_id branch; do\n if [ -n \"$run_id\" ]; then\n echo \"Downloading session log for run $run_id (branch: $branch)\"\n\n # Prefer structured Copilot session events from run artifacts. The agent\n # writes events.jsonl under the session-state logs artifact when available.\n artifact_work_dir=\"${RUNNER_TEMP:-/tmp}/copilot-session-artifacts-${run_id}\"\n rm -rf \"$artifact_work_dir\"\n mkdir -p \"$artifact_work_dir\"\n events_tmp=\"/tmp/gh-aw/agent/session-data/logs/${run_id}-events.jsonl.tmp\"\n events_out=\"/tmp/gh-aw/agent/session-data/logs/${run_id}-events.jsonl\"\n rm -f \"$events_tmp\" \"$events_out\"\n\n for artifact_id in $(gh api \"repos/$GITHUB_REPOSITORY/actions/runs/${run_id}/artifacts\" \\\n --jq '.artifacts[] | select(.expired == false) | .id' 2>/dev/null || true); do\n artifact_zip=\"${artifact_work_dir}/${artifact_id}.zip\"\n artifact_dir=\"${artifact_work_dir}/${artifact_id}\"\n mkdir -p \"$artifact_dir\"\n\n if gh api \"repos/$GITHUB_REPOSITORY/actions/artifacts/${artifact_id}/zip\" \\\n > \"$artifact_zip\" 2>/dev/null && [ -s \"$artifact_zip\" ]; then\n unzip -q -o \"$artifact_zip\" -d \"$artifact_dir\" 2>/dev/null || true\n fi\n done\n\n for events_file in $(find \"$artifact_work_dir\" -type f -name events.jsonl 2>/dev/null || true); do\n cat \"$events_file\" >> \"$events_tmp\"\n done\n\n if [ -s \"$events_tmp\" ]; then\n cp \"$events_tmp\" \"$events_out\"\n EVENT_COUNT=$(wc -l < \"$events_out\")\n echo \" Saved events.jsonl: $EVENT_COUNT events for run $run_id\"\n fi\n rm -f \"$events_tmp\"\n rm -rf \"$artifact_work_dir\"\n\n if [ ! -s \"$events_out\" ]; then\n # Download raw job logs as a transcript fallback. gh api follows the\n # 302 redirect automatically; inspect every job in case the agent job\n # is not first in the run.\n conversation_tmp=\"/tmp/gh-aw/agent/session-data/logs/${run_id}-conversation.txt.tmp\"\n conversation_out=\"/tmp/gh-aw/agent/session-data/logs/${run_id}-conversation.txt\"\n rm -f \"$conversation_tmp\" \"$conversation_out\"\n\n for job_id in $(gh api \"repos/$GITHUB_REPOSITORY/actions/runs/${run_id}/jobs\" \\\n --jq '.jobs[].id' 2>/dev/null || true); do\n raw_log=\"/tmp/gh-aw/agent/session-data/logs/${run_id}-${job_id}-raw.log\"\n gh api \"repos/$GITHUB_REPOSITORY/actions/jobs/${job_id}/logs\" \\\n > \"$raw_log\" 2>/dev/null || true\n\n if [ -f \"$raw_log\" ] && [ -s \"$raw_log\" ]; then\n grep \"\\[cca-engine\\] turn=\" \"$raw_log\" >> \"$conversation_tmp\" 2>/dev/null || true\n fi\n rm -f \"$raw_log\" 2>/dev/null || true\n done\n\n if [ -s \"$conversation_tmp\" ]; then\n cp \"$conversation_tmp\" \"$conversation_out\"\n LINE_COUNT=$(wc -l < \"$conversation_out\")\n echo \" Saved transcript fallback: $LINE_COUNT lines for run $run_id\"\n fi\n rm -f \"$conversation_tmp\"\n fi\n\n if [ ! -s \"/tmp/gh-aw/agent/session-data/logs/${run_id}-events.jsonl\" ] && [ ! -s \"/tmp/gh-aw/agent/session-data/logs/${run_id}-conversation.txt\" ]; then\n echo \"::error::No events.jsonl artifact or conversation transcript could be downloaded for agent run $run_id\"\n fi\n fi\n done < \"$RUNS_TO_FETCH\"\n\n LOG_RUN_COUNT=$(count_agent_logs /tmp/gh-aw/agent/session-data/sessions-list.json /tmp/gh-aw/agent/session-data/logs)\n EVENTS_COUNT=$(find /tmp/gh-aw/agent/session-data/logs/ -type f -name \"*-events.jsonl\" | wc -l)\n CONVERSATION_COUNT=$(find /tmp/gh-aw/agent/session-data/logs/ -type f -name \"*-conversation.txt\" | wc -l)\n echo \"Session logs downloaded: $LOG_RUN_COUNT of $AGENT_COUNT agent runs ($EVENTS_COUNT events.jsonl, $CONVERSATION_COUNT transcript fallbacks)\"\n\n if [ \"$AGENT_COUNT\" -gt 0 ] && [ \"$LOG_RUN_COUNT\" -lt \"$AGENT_COUNT\" ]; then\n echo \"::error::Missing per-session logs for $((AGENT_COUNT - LOG_RUN_COUNT)) of $AGENT_COUNT agent runs; failing to prevent incomplete optimization analysis\"\n exit 1\n fi\n\n # Store in cache with today's date\n cp /tmp/gh-aw/agent/session-data/sessions-list.json \"$CACHE_DIR/copilot-sessions-${TODAY}.json\"\n cp /tmp/gh-aw/agent/session-data/sessions-schema.json \"$CACHE_DIR/copilot-sessions-${TODAY}-schema.json\"\n \n # Cache the log files\n mkdir -p \"$CACHE_DIR/session-logs-${TODAY}\"\n cp -r /tmp/gh-aw/agent/session-data/logs/* \"$CACHE_DIR/session-logs-${TODAY}/\" 2>/dev/null || true\n\n echo \"✓ Session data saved to cache: copilot-sessions-${TODAY}.json\"\n echo \"Total sessions found: $(jq 'length' /tmp/gh-aw/agent/session-data/sessions-list.json)\"\nfi\n\n# Always ensure data is available at expected locations for backward compatibility\necho \"Session data available at: /tmp/gh-aw/agent/session-data/sessions-list.json\"\necho \"Schema available at: /tmp/gh-aw/agent/session-data/sessions-schema.json\"\necho \"Logs available at: /tmp/gh-aw/agent/session-data/logs/\"\n\n# Set outputs for downstream use\necho \"sessions_count=$(jq 'length' /tmp/gh-aw/agent/session-data/sessions-list.json)\" >> \"$GITHUB_OUTPUT\"\n" - name: Install gh CLI run: | bash "${RUNNER_TEMP}/gh-aw/actions/install_gh_cli.sh" @@ -870,6 +870,7 @@ jobs: # --allow-tool shell(date) # --allow-tool shell(echo) # --allow-tool shell(find) + # --allow-tool shell(gh api) # --allow-tool shell(gh:*) # --allow-tool shell(git:*) # --allow-tool shell(grep) @@ -933,7 +934,7 @@ jobs: COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }} COPILOT_SDK_URI: http://127.0.0.1:3002 GH_AW_COPILOT_SDK_DRIVER: 1 - GH_AW_COPILOT_SDK_SERVER_ARGS: '["--headless","--no-auto-update","--port","3002","--add-dir","/tmp/gh-aw/","--log-level","all","--log-dir","/tmp/gh-aw/sandbox/agent/logs/","--disable-builtin-mcps","--no-ask-user","--allow-tool","github","--allow-tool","safeoutputs","--allow-tool","shell(./.github/skills/jqschema/jqschema.sh)","--allow-tool","shell(cat)","--allow-tool","shell(cp)","--allow-tool","shell(date)","--allow-tool","shell(echo)","--allow-tool","shell(find)","--allow-tool","shell(gh:*)","--allow-tool","shell(git:*)","--allow-tool","shell(grep)","--allow-tool","shell(head)","--allow-tool","shell(jq)","--allow-tool","shell(ln)","--allow-tool","shell(ls)","--allow-tool","shell(mkdir)","--allow-tool","shell(printf)","--allow-tool","shell(pwd)","--allow-tool","shell(python)","--allow-tool","shell(rm)","--allow-tool","shell(safeoutputs:*)","--allow-tool","shell(sort)","--allow-tool","shell(tail)","--allow-tool","shell(uniq)","--allow-tool","shell(unzip)","--allow-tool","shell(wc)","--allow-tool","shell(yq)","--allow-tool","write","--add-dir","/tmp/gh-aw/cache-memory/","--allow-all-paths"]' + GH_AW_COPILOT_SDK_SERVER_ARGS: '["--headless","--no-auto-update","--port","3002","--add-dir","/tmp/gh-aw/","--log-level","all","--log-dir","/tmp/gh-aw/sandbox/agent/logs/","--disable-builtin-mcps","--no-ask-user","--allow-tool","github","--allow-tool","safeoutputs","--allow-tool","shell(./.github/skills/jqschema/jqschema.sh)","--allow-tool","shell(cat)","--allow-tool","shell(cp)","--allow-tool","shell(date)","--allow-tool","shell(echo)","--allow-tool","shell(find)","--allow-tool","shell(gh api)","--allow-tool","shell(gh:*)","--allow-tool","shell(git:*)","--allow-tool","shell(grep)","--allow-tool","shell(head)","--allow-tool","shell(jq)","--allow-tool","shell(ln)","--allow-tool","shell(ls)","--allow-tool","shell(mkdir)","--allow-tool","shell(printf)","--allow-tool","shell(pwd)","--allow-tool","shell(python)","--allow-tool","shell(rm)","--allow-tool","shell(safeoutputs:*)","--allow-tool","shell(sort)","--allow-tool","shell(tail)","--allow-tool","shell(uniq)","--allow-tool","shell(unzip)","--allow-tool","shell(wc)","--allow-tool","shell(yq)","--allow-tool","write","--add-dir","/tmp/gh-aw/cache-memory/","--allow-all-paths"]' GH_AW_LLM_PROVIDER: github GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} GH_AW_MAX_TOOL_DENIALS: 3 diff --git a/.github/workflows/copilot-session-insights.lock.yml b/.github/workflows/copilot-session-insights.lock.yml index 4f53f4edc43..c96fc291655 100644 --- a/.github/workflows/copilot-session-insights.lock.yml +++ b/.github/workflows/copilot-session-insights.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"ba6cd877994f667cc35cbf02568dc5cd05272c1e0e4287b19dcf0dca004f7d60","body_hash":"466083d3dbc2ff6427e3fdb265ff890a044e0b88d7eb1872dc79244196fd79b8","strict":true,"agent_id":"claude","engine_versions":{"claude":"2.1.220"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"5ba8723e900834e74527d3c555c9a11b69f5d79f2f2db304682b56ca876e076b","body_hash":"584a744d1206fc503efb9dbf5aa83dff7f458331ae1b18895024bc5feb1df92e","strict":true,"agent_id":"claude","engine_versions":{"claude":"2.1.220"}} # gh-aw-manifest: {"version":1,"secrets":["ANTHROPIC_API_KEY","COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/setup-python","sha":"5fda3b95a4ea91299a34e894583c3862153e4b97","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.43","digest":"sha256:04e2d1987a565000a8f114b89d806ae7a3864dd4f944be65275b28c93d8690e6","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.43@sha256:04e2d1987a565000a8f114b89d806ae7a3864dd4f944be65275b28c93d8690e6"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.43","digest":"sha256:d85f57975af5ea23af4996e41ed73fbc8f5b4a47402472bfe82e508f352cb0c1","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.43@sha256:d85f57975af5ea23af4996e41ed73fbc8f5b4a47402472bfe82e508f352cb0c1"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.43","digest":"sha256:65c45ea2967984d0024f3df61bc71335658a77ede96c8d9665da7a5f33a795ab","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.43@sha256:65c45ea2967984d0024f3df61bc71335658a77ede96c8d9665da7a5f33a795ab"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.43","digest":"sha256:26be5e0b8c8f4c41c8a59126b29bb5d80b07253597472ded2a16bdd75abcbf9d","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.43@sha256:26be5e0b8c8f4c41c8a59126b29bb5d80b07253597472ded2a16bdd75abcbf9d"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.7","digest":"sha256:7545220a9aca134b71e51193ee0eaf4c50756ebf8fbd25a63ae7556e62815c00","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.7@sha256:7545220a9aca134b71e51193ee0eaf4c50756ebf8fbd25a63ae7556e62815c00"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196"},{"image":"ghcr.io/github/github-mcp-server:v1.8.0","digest":"sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520","pinned_image":"ghcr.io/github/github-mcp-server:v1.8.0@sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520"}]} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -574,7 +574,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} name: Fetch Copilot session data - run: "# Create output directories\nmkdir -p /tmp/gh-aw/agent/session-data\nmkdir -p /tmp/gh-aw/agent/session-data/logs\nmkdir -p /tmp/gh-aw/cache-memory\n\n# Get today's date for cache identification\nTODAY=$(date '+%Y-%m-%d')\nCACHE_DIR=\"/tmp/gh-aw/cache-memory\"\n\n# Check if cached data exists from today\nif [ -f \"$CACHE_DIR/copilot-sessions-${TODAY}.json\" ] && [ -s \"$CACHE_DIR/copilot-sessions-${TODAY}.json\" ]; then\n echo \"✓ Found cached session data from ${TODAY}\"\n cp \"$CACHE_DIR/copilot-sessions-${TODAY}.json\" /tmp/gh-aw/agent/session-data/sessions-list.json\n \n # Regenerate schema if missing\n if [ ! -f \"$CACHE_DIR/copilot-sessions-${TODAY}-schema.json\" ]; then\n ./.github/skills/jqschema/jqschema.sh < /tmp/gh-aw/agent/session-data/sessions-list.json > \"$CACHE_DIR/copilot-sessions-${TODAY}-schema.json\"\n fi\n cp \"$CACHE_DIR/copilot-sessions-${TODAY}-schema.json\" /tmp/gh-aw/agent/session-data/sessions-schema.json\n \n # Restore cached log files if they exist\n if [ -d \"$CACHE_DIR/session-logs-${TODAY}\" ]; then\n echo \"✓ Found cached session logs from ${TODAY}\"\n cp -r \"$CACHE_DIR/session-logs-${TODAY}\"/* /tmp/gh-aw/agent/session-data/logs/ 2>/dev/null || true\n echo \"Restored $(find /tmp/gh-aw/agent/session-data/logs -type f | wc -l) session log files from cache\"\n fi\n \n echo \"Using cached data from ${TODAY}\"\n echo \"Total sessions in cache: $(jq 'length' /tmp/gh-aw/agent/session-data/sessions-list.json)\"\nelse\n echo \"⬇ Downloading fresh session data...\"\n \n # Calculate date 30 days ago\n DATE_30_DAYS_AGO=$(date -d '30 days ago' '+%Y-%m-%d' 2>/dev/null || date -v-30d '+%Y-%m-%d')\n\n # Search for workflow runs from copilot/* branches\n # This fetches GitHub Copilot coding agent task runs by searching for workflow runs on copilot/* branches\n echo \"Fetching Copilot coding agent workflow runs from the last 30 days...\"\n \n # Get workflow runs from copilot/* branches\n gh api \"repos/$GITHUB_REPOSITORY/actions/runs\" \\\n --paginate \\\n --jq \".workflow_runs[] | select(.head_branch | startswith(\\\"copilot/\\\")) | select(.created_at >= \\\"${DATE_30_DAYS_AGO}\\\") | {id, name, head_branch, created_at, updated_at, status, conclusion, html_url}\" \\\n | jq -s '.[0:50]' \\\n > /tmp/gh-aw/agent/session-data/sessions-list.json\n\n # Generate schema for reference\n ./.github/skills/jqschema/jqschema.sh < /tmp/gh-aw/agent/session-data/sessions-list.json > /tmp/gh-aw/agent/session-data/sessions-schema.json\n\n # Download conversation logs for actual Copilot agent runs.\n # CI gate runs (e.g. \"Smoke CI\", \"CGO\", \"CWI\" quality gates) always end with\n # conclusion=action_required because a human must approve them to continue; they\n # contain no Copilot agent activity and have no conversation transcript.\n # Each real agent run (conclusion=success/failure) emits [cca-engine] turn= lines\n # in its job log which is the per-turn conversation transcript.\n SESSION_COUNT=$(jq 'length' /tmp/gh-aw/agent/session-data/sessions-list.json)\n AGENT_COUNT=$(jq '[.[] | select(.conclusion != \"action_required\")] | length' /tmp/gh-aw/agent/session-data/sessions-list.json)\n echo \"Downloading conversation logs for $AGENT_COUNT agent runs (skipping $((SESSION_COUNT - AGENT_COUNT)) CI gate runs)...\"\n\n jq -r '.[] | select(.conclusion != \"action_required\") | \"\\(.id) \\(.head_branch)\"' /tmp/gh-aw/agent/session-data/sessions-list.json | while read -r run_id branch; do\n if [ -n \"$run_id\" ]; then\n echo \"Downloading conversation log for run $run_id (branch: $branch)\"\n\n # Get the first job ID for this run via the GitHub API (actions:read suffices).\n # Copilot coding agent runs have exactly one job (\"Copilot Coding Agent\"), so\n # .jobs[0] is always the correct and only job containing the transcript log.\n job_id=$(gh api \"repos/$GITHUB_REPOSITORY/actions/runs/${run_id}/jobs\" \\\n --jq '.jobs[0].id' 2>/dev/null || true)\n\n if [ -n \"$job_id\" ] && [ \"$job_id\" != \"null\" ]; then\n # Download the raw job log; gh api follows the 302 redirect automatically\n gh api \"repos/$GITHUB_REPOSITORY/actions/jobs/${job_id}/logs\" \\\n > \"/tmp/gh-aw/agent/session-data/logs/${run_id}-raw.log\" 2>/dev/null || true\n\n if [ -f \"/tmp/gh-aw/agent/session-data/logs/${run_id}-raw.log\" ] && [ -s \"/tmp/gh-aw/agent/session-data/logs/${run_id}-raw.log\" ]; then\n # Extract conversation transcript: [cca-engine] turn= lines carry turn-by-turn data\n grep \"\\[cca-engine\\] turn=\" \"/tmp/gh-aw/agent/session-data/logs/${run_id}-raw.log\" \\\n > \"/tmp/gh-aw/agent/session-data/logs/${run_id}-conversation.txt\" 2>/dev/null || true\n rm -f \"/tmp/gh-aw/agent/session-data/logs/${run_id}-raw.log\"\n\n if [ -s \"/tmp/gh-aw/agent/session-data/logs/${run_id}-conversation.txt\" ]; then\n LINE_COUNT=$(wc -l < \"/tmp/gh-aw/agent/session-data/logs/${run_id}-conversation.txt\")\n echo \" Saved transcript: $LINE_COUNT lines for run $run_id\"\n else\n echo \" Warning: No [cca-engine] conversation lines found for run $run_id\"\n rm -f \"/tmp/gh-aw/agent/session-data/logs/${run_id}-conversation.txt\"\n fi\n else\n echo \" Warning: Could not download job logs for run $run_id (may be expired)\"\n rm -f \"/tmp/gh-aw/agent/session-data/logs/${run_id}-raw.log\" 2>/dev/null || true\n fi\n else\n echo \" Warning: Could not determine job ID for run $run_id\"\n fi\n fi\n done\n\n LOG_COUNT=$(find /tmp/gh-aw/agent/session-data/logs/ -type f -name \"*-conversation.txt\" | wc -l)\n echo \"Conversation logs downloaded: $LOG_COUNT session logs\"\n\n # Store in cache with today's date\n cp /tmp/gh-aw/agent/session-data/sessions-list.json \"$CACHE_DIR/copilot-sessions-${TODAY}.json\"\n cp /tmp/gh-aw/agent/session-data/sessions-schema.json \"$CACHE_DIR/copilot-sessions-${TODAY}-schema.json\"\n \n # Cache the log files\n mkdir -p \"$CACHE_DIR/session-logs-${TODAY}\"\n cp -r /tmp/gh-aw/agent/session-data/logs/* \"$CACHE_DIR/session-logs-${TODAY}/\" 2>/dev/null || true\n\n echo \"✓ Session data saved to cache: copilot-sessions-${TODAY}.json\"\n echo \"Total sessions found: $(jq 'length' /tmp/gh-aw/agent/session-data/sessions-list.json)\"\nfi\n\n# Always ensure data is available at expected locations for backward compatibility\necho \"Session data available at: /tmp/gh-aw/agent/session-data/sessions-list.json\"\necho \"Schema available at: /tmp/gh-aw/agent/session-data/sessions-schema.json\"\necho \"Logs available at: /tmp/gh-aw/agent/session-data/logs/\"\n\n# Set outputs for downstream use\necho \"sessions_count=$(jq 'length' /tmp/gh-aw/agent/session-data/sessions-list.json)\" >> \"$GITHUB_OUTPUT\"\n" + run: "# Create output directories\nmkdir -p /tmp/gh-aw/agent/session-data\nmkdir -p /tmp/gh-aw/agent/session-data/logs\nmkdir -p /tmp/gh-aw/cache-memory\n\n# Get today's date for cache identification\nTODAY=$(date '+%Y-%m-%d')\nCACHE_DIR=\"/tmp/gh-aw/cache-memory\"\n\ncount_agent_logs() {\n sessions_file=\"$1\"\n logs_dir=\"$2\"\n count=0\n\n if [ ! -f \"$sessions_file\" ] || [ ! -d \"$logs_dir\" ]; then\n echo 0\n return\n fi\n\n while read -r agent_run_id; do\n if [ -s \"$logs_dir/${agent_run_id}-events.jsonl\" ] || [ -s \"$logs_dir/${agent_run_id}-conversation.txt\" ]; then\n count=$((count + 1))\n fi\n done < <(jq -r '.[] | select(.status == \"completed\" and .conclusion != \"action_required\") | .id' \"$sessions_file\")\n\n echo \"$count\"\n}\n\nUSE_CACHE=false\n\n# Check if cached data exists from today\nif [ -f \"$CACHE_DIR/copilot-sessions-${TODAY}.json\" ] && [ -s \"$CACHE_DIR/copilot-sessions-${TODAY}.json\" ]; then\n CACHED_AGENT_COUNT=$(jq '[.[] | select(.status == \"completed\" and .conclusion != \"action_required\")] | length' \"$CACHE_DIR/copilot-sessions-${TODAY}.json\")\n CACHED_LOG_RUN_COUNT=$(count_agent_logs \"$CACHE_DIR/copilot-sessions-${TODAY}.json\" \"$CACHE_DIR/session-logs-${TODAY}\")\n if [ \"$CACHED_AGENT_COUNT\" -eq 0 ] || [ \"$CACHED_LOG_RUN_COUNT\" -ge \"$CACHED_AGENT_COUNT\" ]; then\n USE_CACHE=true\n else\n echo \"::warning::Cached session metadata exists but only $CACHED_LOG_RUN_COUNT of $CACHED_AGENT_COUNT agent runs have logs; refreshing session data\"\n fi\nfi\n\nif [ \"$USE_CACHE\" = \"true\" ]; then\n echo \"✓ Found cached session data from ${TODAY}\"\n cp \"$CACHE_DIR/copilot-sessions-${TODAY}.json\" /tmp/gh-aw/agent/session-data/sessions-list.json\n \n # Regenerate schema if missing\n if [ ! -f \"$CACHE_DIR/copilot-sessions-${TODAY}-schema.json\" ]; then\n ./.github/skills/jqschema/jqschema.sh < /tmp/gh-aw/agent/session-data/sessions-list.json > \"$CACHE_DIR/copilot-sessions-${TODAY}-schema.json\"\n fi\n cp \"$CACHE_DIR/copilot-sessions-${TODAY}-schema.json\" /tmp/gh-aw/agent/session-data/sessions-schema.json\n \n # Restore cached log files if they exist\n if [ -d \"$CACHE_DIR/session-logs-${TODAY}\" ]; then\n echo \"✓ Found cached session logs from ${TODAY}\"\n cp -r \"$CACHE_DIR/session-logs-${TODAY}\"/* /tmp/gh-aw/agent/session-data/logs/ 2>/dev/null || true\n echo \"Restored $(find /tmp/gh-aw/agent/session-data/logs -type f | wc -l) session log files from cache\"\n fi\n \n echo \"Using cached data from ${TODAY}\"\n echo \"Total sessions in cache: $(jq 'length' /tmp/gh-aw/agent/session-data/sessions-list.json)\"\nelse\n echo \"⬇ Downloading fresh session data...\"\n \n # Calculate date 30 days ago\n DATE_30_DAYS_AGO=$(date -d '30 days ago' '+%Y-%m-%d' 2>/dev/null || date -v-30d '+%Y-%m-%d')\n\n # Search for workflow runs from copilot/* branches\n # This fetches GitHub Copilot coding agent task runs by searching for workflow runs on copilot/* branches\n echo \"Fetching Copilot coding agent workflow runs from the last 30 days...\"\n \n # Get workflow runs from copilot/* branches\n gh api \"repos/$GITHUB_REPOSITORY/actions/runs\" \\\n --paginate \\\n --jq \".workflow_runs[] | select(.head_branch | startswith(\\\"copilot/\\\")) | select(.created_at >= \\\"${DATE_30_DAYS_AGO}\\\") | {id, name, head_branch, created_at, updated_at, status, conclusion, html_url}\" \\\n | jq -s '.[0:50]' \\\n > /tmp/gh-aw/agent/session-data/sessions-list.json\n\n # Generate schema for reference\n ./.github/skills/jqschema/jqschema.sh < /tmp/gh-aw/agent/session-data/sessions-list.json > /tmp/gh-aw/agent/session-data/sessions-schema.json\n\n # Download per-session logs for actual Copilot agent runs.\n # CI gate runs (e.g. \"Smoke CI\", \"CGO\", \"CWI\" quality gates) always end with\n # conclusion=action_required because a human must approve them to continue; they\n # contain no Copilot agent activity and have no conversation transcript.\n # Prefer structured events.jsonl artifacts; fall back to [cca-engine] turn= lines\n # in raw Actions job logs when no events.jsonl artifact is available.\n SESSION_COUNT=$(jq 'length' /tmp/gh-aw/agent/session-data/sessions-list.json)\n AGENT_COUNT=$(jq '[.[] | select(.status == \"completed\" and .conclusion != \"action_required\")] | length' /tmp/gh-aw/agent/session-data/sessions-list.json)\n echo \"Downloading per-session logs for $AGENT_COUNT agent runs (skipping $((SESSION_COUNT - AGENT_COUNT)) CI gate runs)...\"\n\n RUNS_TO_FETCH=\"${RUNNER_TEMP:-/tmp}/copilot-session-runs-${TODAY}.txt\"\n jq -r '.[] | select(.status == \"completed\" and .conclusion != \"action_required\") | \"\\(.id) \\(.head_branch)\"' /tmp/gh-aw/agent/session-data/sessions-list.json > \"$RUNS_TO_FETCH\"\n\n while read -r run_id branch; do\n if [ -n \"$run_id\" ]; then\n echo \"Downloading session log for run $run_id (branch: $branch)\"\n\n # Prefer structured Copilot session events from run artifacts. The agent\n # writes events.jsonl under the session-state logs artifact when available.\n artifact_work_dir=\"${RUNNER_TEMP:-/tmp}/copilot-session-artifacts-${run_id}\"\n rm -rf \"$artifact_work_dir\"\n mkdir -p \"$artifact_work_dir\"\n events_tmp=\"/tmp/gh-aw/agent/session-data/logs/${run_id}-events.jsonl.tmp\"\n events_out=\"/tmp/gh-aw/agent/session-data/logs/${run_id}-events.jsonl\"\n rm -f \"$events_tmp\" \"$events_out\"\n\n for artifact_id in $(gh api \"repos/$GITHUB_REPOSITORY/actions/runs/${run_id}/artifacts\" \\\n --jq '.artifacts[] | select(.expired == false) | .id' 2>/dev/null || true); do\n artifact_zip=\"${artifact_work_dir}/${artifact_id}.zip\"\n artifact_dir=\"${artifact_work_dir}/${artifact_id}\"\n mkdir -p \"$artifact_dir\"\n\n if gh api \"repos/$GITHUB_REPOSITORY/actions/artifacts/${artifact_id}/zip\" \\\n > \"$artifact_zip\" 2>/dev/null && [ -s \"$artifact_zip\" ]; then\n unzip -q -o \"$artifact_zip\" -d \"$artifact_dir\" 2>/dev/null || true\n fi\n done\n\n for events_file in $(find \"$artifact_work_dir\" -type f -name events.jsonl 2>/dev/null || true); do\n cat \"$events_file\" >> \"$events_tmp\"\n done\n\n if [ -s \"$events_tmp\" ]; then\n cp \"$events_tmp\" \"$events_out\"\n EVENT_COUNT=$(wc -l < \"$events_out\")\n echo \" Saved events.jsonl: $EVENT_COUNT events for run $run_id\"\n fi\n rm -f \"$events_tmp\"\n rm -rf \"$artifact_work_dir\"\n\n if [ ! -s \"$events_out\" ]; then\n # Download raw job logs as a transcript fallback. gh api follows the\n # 302 redirect automatically; inspect every job in case the agent job\n # is not first in the run.\n conversation_tmp=\"/tmp/gh-aw/agent/session-data/logs/${run_id}-conversation.txt.tmp\"\n conversation_out=\"/tmp/gh-aw/agent/session-data/logs/${run_id}-conversation.txt\"\n rm -f \"$conversation_tmp\" \"$conversation_out\"\n\n for job_id in $(gh api \"repos/$GITHUB_REPOSITORY/actions/runs/${run_id}/jobs\" \\\n --jq '.jobs[].id' 2>/dev/null || true); do\n raw_log=\"/tmp/gh-aw/agent/session-data/logs/${run_id}-${job_id}-raw.log\"\n gh api \"repos/$GITHUB_REPOSITORY/actions/jobs/${job_id}/logs\" \\\n > \"$raw_log\" 2>/dev/null || true\n\n if [ -f \"$raw_log\" ] && [ -s \"$raw_log\" ]; then\n grep \"\\[cca-engine\\] turn=\" \"$raw_log\" >> \"$conversation_tmp\" 2>/dev/null || true\n fi\n rm -f \"$raw_log\" 2>/dev/null || true\n done\n\n if [ -s \"$conversation_tmp\" ]; then\n cp \"$conversation_tmp\" \"$conversation_out\"\n LINE_COUNT=$(wc -l < \"$conversation_out\")\n echo \" Saved transcript fallback: $LINE_COUNT lines for run $run_id\"\n fi\n rm -f \"$conversation_tmp\"\n fi\n\n if [ ! -s \"/tmp/gh-aw/agent/session-data/logs/${run_id}-events.jsonl\" ] && [ ! -s \"/tmp/gh-aw/agent/session-data/logs/${run_id}-conversation.txt\" ]; then\n echo \"::error::No events.jsonl artifact or conversation transcript could be downloaded for agent run $run_id\"\n fi\n fi\n done < \"$RUNS_TO_FETCH\"\n\n LOG_RUN_COUNT=$(count_agent_logs /tmp/gh-aw/agent/session-data/sessions-list.json /tmp/gh-aw/agent/session-data/logs)\n EVENTS_COUNT=$(find /tmp/gh-aw/agent/session-data/logs/ -type f -name \"*-events.jsonl\" | wc -l)\n CONVERSATION_COUNT=$(find /tmp/gh-aw/agent/session-data/logs/ -type f -name \"*-conversation.txt\" | wc -l)\n echo \"Session logs downloaded: $LOG_RUN_COUNT of $AGENT_COUNT agent runs ($EVENTS_COUNT events.jsonl, $CONVERSATION_COUNT transcript fallbacks)\"\n\n if [ \"$AGENT_COUNT\" -gt 0 ] && [ \"$LOG_RUN_COUNT\" -lt \"$AGENT_COUNT\" ]; then\n echo \"::error::Missing per-session logs for $((AGENT_COUNT - LOG_RUN_COUNT)) of $AGENT_COUNT agent runs; failing to prevent incomplete optimization analysis\"\n exit 1\n fi\n\n # Store in cache with today's date\n cp /tmp/gh-aw/agent/session-data/sessions-list.json \"$CACHE_DIR/copilot-sessions-${TODAY}.json\"\n cp /tmp/gh-aw/agent/session-data/sessions-schema.json \"$CACHE_DIR/copilot-sessions-${TODAY}-schema.json\"\n \n # Cache the log files\n mkdir -p \"$CACHE_DIR/session-logs-${TODAY}\"\n cp -r /tmp/gh-aw/agent/session-data/logs/* \"$CACHE_DIR/session-logs-${TODAY}/\" 2>/dev/null || true\n\n echo \"✓ Session data saved to cache: copilot-sessions-${TODAY}.json\"\n echo \"Total sessions found: $(jq 'length' /tmp/gh-aw/agent/session-data/sessions-list.json)\"\nfi\n\n# Always ensure data is available at expected locations for backward compatibility\necho \"Session data available at: /tmp/gh-aw/agent/session-data/sessions-list.json\"\necho \"Schema available at: /tmp/gh-aw/agent/session-data/sessions-schema.json\"\necho \"Logs available at: /tmp/gh-aw/agent/session-data/logs/\"\n\n# Set outputs for downstream use\necho \"sessions_count=$(jq 'length' /tmp/gh-aw/agent/session-data/sessions-list.json)\" >> \"$GITHUB_OUTPUT\"\n" - name: Setup Python environment run: "# Create working directory for Python scripts\nmkdir -p /tmp/gh-aw/python\nmkdir -p /tmp/gh-aw/python/data\nmkdir -p /tmp/gh-aw/python/charts\nmkdir -p /tmp/gh-aw/python/artifacts\n\necho \"Python environment setup complete\"\necho \"Working directory: /tmp/gh-aw/python\"\necho \"Data directory: /tmp/gh-aw/python/data\"\necho \"Charts directory: /tmp/gh-aw/python/charts\"\necho \"Artifacts directory: /tmp/gh-aw/python/artifacts\"\n" - name: Install Python scientific libraries diff --git a/.github/workflows/shared/copilot-session-data-fetch.md b/.github/workflows/shared/copilot-session-data-fetch.md index dc2db5136dd..7dfbf3bf7d3 100644 --- a/.github/workflows/shared/copilot-session-data-fetch.md +++ b/.github/workflows/shared/copilot-session-data-fetch.md @@ -16,6 +16,7 @@ tools: cache-memory: key: copilot-session-data bash: + - "gh api *" - "jq *" - "./.github/skills/jqschema/jqschema.sh" - "mkdir *" @@ -25,6 +26,8 @@ tools: - "find *" - "rm *" - "cat *" + - "grep *" + - "wc *" steps: - name: Install gh CLI @@ -44,9 +47,40 @@ steps: # Get today's date for cache identification TODAY=$(date '+%Y-%m-%d') CACHE_DIR="/tmp/gh-aw/cache-memory" - + + count_agent_logs() { + sessions_file="$1" + logs_dir="$2" + count=0 + + if [ ! -f "$sessions_file" ] || [ ! -d "$logs_dir" ]; then + echo 0 + return + fi + + while read -r agent_run_id; do + if [ -s "$logs_dir/${agent_run_id}-events.jsonl" ] || [ -s "$logs_dir/${agent_run_id}-conversation.txt" ]; then + count=$((count + 1)) + fi + done < <(jq -r '.[] | select(.status == "completed" and .conclusion != "action_required") | .id' "$sessions_file") + + echo "$count" + } + + USE_CACHE=false + # Check if cached data exists from today if [ -f "$CACHE_DIR/copilot-sessions-${TODAY}.json" ] && [ -s "$CACHE_DIR/copilot-sessions-${TODAY}.json" ]; then + CACHED_AGENT_COUNT=$(jq '[.[] | select(.status == "completed" and .conclusion != "action_required")] | length' "$CACHE_DIR/copilot-sessions-${TODAY}.json") + CACHED_LOG_RUN_COUNT=$(count_agent_logs "$CACHE_DIR/copilot-sessions-${TODAY}.json" "$CACHE_DIR/session-logs-${TODAY}") + if [ "$CACHED_AGENT_COUNT" -eq 0 ] || [ "$CACHED_LOG_RUN_COUNT" -ge "$CACHED_AGENT_COUNT" ]; then + USE_CACHE=true + else + echo "::warning::Cached session metadata exists but only $CACHED_LOG_RUN_COUNT of $CACHED_AGENT_COUNT agent runs have logs; refreshing session data" + fi + fi + + if [ "$USE_CACHE" = "true" ]; then echo "✓ Found cached session data from ${TODAY}" cp "$CACHE_DIR/copilot-sessions-${TODAY}.json" /tmp/gh-aw/agent/session-data/sessions-list.json @@ -85,56 +119,99 @@ steps: # Generate schema for reference ./.github/skills/jqschema/jqschema.sh < /tmp/gh-aw/agent/session-data/sessions-list.json > /tmp/gh-aw/agent/session-data/sessions-schema.json - # Download conversation logs for actual Copilot agent runs. + # Download per-session logs for actual Copilot agent runs. # CI gate runs (e.g. "Smoke CI", "CGO", "CWI" quality gates) always end with # conclusion=action_required because a human must approve them to continue; they # contain no Copilot agent activity and have no conversation transcript. - # Each real agent run (conclusion=success/failure) emits [cca-engine] turn= lines - # in its job log which is the per-turn conversation transcript. + # Prefer structured events.jsonl artifacts; fall back to [cca-engine] turn= lines + # in raw Actions job logs when no events.jsonl artifact is available. SESSION_COUNT=$(jq 'length' /tmp/gh-aw/agent/session-data/sessions-list.json) - AGENT_COUNT=$(jq '[.[] | select(.conclusion != "action_required")] | length' /tmp/gh-aw/agent/session-data/sessions-list.json) - echo "Downloading conversation logs for $AGENT_COUNT agent runs (skipping $((SESSION_COUNT - AGENT_COUNT)) CI gate runs)..." + AGENT_COUNT=$(jq '[.[] | select(.status == "completed" and .conclusion != "action_required")] | length' /tmp/gh-aw/agent/session-data/sessions-list.json) + echo "Downloading per-session logs for $AGENT_COUNT agent runs (skipping $((SESSION_COUNT - AGENT_COUNT)) CI gate runs)..." - jq -r '.[] | select(.conclusion != "action_required") | "\(.id) \(.head_branch)"' /tmp/gh-aw/agent/session-data/sessions-list.json | while read -r run_id branch; do + RUNS_TO_FETCH="${RUNNER_TEMP:-/tmp}/copilot-session-runs-${TODAY}.txt" + jq -r '.[] | select(.status == "completed" and .conclusion != "action_required") | "\(.id) \(.head_branch)"' /tmp/gh-aw/agent/session-data/sessions-list.json > "$RUNS_TO_FETCH" + + while read -r run_id branch; do if [ -n "$run_id" ]; then - echo "Downloading conversation log for run $run_id (branch: $branch)" - - # Get the first job ID for this run via the GitHub API (actions:read suffices). - # Copilot coding agent runs have exactly one job ("Copilot Coding Agent"), so - # .jobs[0] is always the correct and only job containing the transcript log. - job_id=$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/${run_id}/jobs" \ - --jq '.jobs[0].id' 2>/dev/null || true) - - if [ -n "$job_id" ] && [ "$job_id" != "null" ]; then - # Download the raw job log; gh api follows the 302 redirect automatically - gh api "repos/$GITHUB_REPOSITORY/actions/jobs/${job_id}/logs" \ - > "/tmp/gh-aw/agent/session-data/logs/${run_id}-raw.log" 2>/dev/null || true - - if [ -f "/tmp/gh-aw/agent/session-data/logs/${run_id}-raw.log" ] && [ -s "/tmp/gh-aw/agent/session-data/logs/${run_id}-raw.log" ]; then - # Extract conversation transcript: [cca-engine] turn= lines carry turn-by-turn data - grep "\[cca-engine\] turn=" "/tmp/gh-aw/agent/session-data/logs/${run_id}-raw.log" \ - > "/tmp/gh-aw/agent/session-data/logs/${run_id}-conversation.txt" 2>/dev/null || true - rm -f "/tmp/gh-aw/agent/session-data/logs/${run_id}-raw.log" - - if [ -s "/tmp/gh-aw/agent/session-data/logs/${run_id}-conversation.txt" ]; then - LINE_COUNT=$(wc -l < "/tmp/gh-aw/agent/session-data/logs/${run_id}-conversation.txt") - echo " Saved transcript: $LINE_COUNT lines for run $run_id" - else - echo " Warning: No [cca-engine] conversation lines found for run $run_id" - rm -f "/tmp/gh-aw/agent/session-data/logs/${run_id}-conversation.txt" + echo "Downloading session log for run $run_id (branch: $branch)" + + # Prefer structured Copilot session events from run artifacts. The agent + # writes events.jsonl under the session-state logs artifact when available. + artifact_work_dir="${RUNNER_TEMP:-/tmp}/copilot-session-artifacts-${run_id}" + rm -rf "$artifact_work_dir" + mkdir -p "$artifact_work_dir" + events_tmp="/tmp/gh-aw/agent/session-data/logs/${run_id}-events.jsonl.tmp" + events_out="/tmp/gh-aw/agent/session-data/logs/${run_id}-events.jsonl" + rm -f "$events_tmp" "$events_out" + + for artifact_id in $(gh api "repos/$GITHUB_REPOSITORY/actions/runs/${run_id}/artifacts" \ + --jq '.artifacts[] | select(.expired == false) | .id' 2>/dev/null || true); do + artifact_zip="${artifact_work_dir}/${artifact_id}.zip" + artifact_dir="${artifact_work_dir}/${artifact_id}" + mkdir -p "$artifact_dir" + + if gh api "repos/$GITHUB_REPOSITORY/actions/artifacts/${artifact_id}/zip" \ + > "$artifact_zip" 2>/dev/null && [ -s "$artifact_zip" ]; then + unzip -q -o "$artifact_zip" -d "$artifact_dir" 2>/dev/null || true + fi + done + + for events_file in $(find "$artifact_work_dir" -type f -name events.jsonl 2>/dev/null || true); do + cat "$events_file" >> "$events_tmp" + done + + if [ -s "$events_tmp" ]; then + cp "$events_tmp" "$events_out" + EVENT_COUNT=$(wc -l < "$events_out") + echo " Saved events.jsonl: $EVENT_COUNT events for run $run_id" + fi + rm -f "$events_tmp" + rm -rf "$artifact_work_dir" + + if [ ! -s "$events_out" ]; then + # Download raw job logs as a transcript fallback. gh api follows the + # 302 redirect automatically; inspect every job in case the agent job + # is not first in the run. + conversation_tmp="/tmp/gh-aw/agent/session-data/logs/${run_id}-conversation.txt.tmp" + conversation_out="/tmp/gh-aw/agent/session-data/logs/${run_id}-conversation.txt" + rm -f "$conversation_tmp" "$conversation_out" + + for job_id in $(gh api "repos/$GITHUB_REPOSITORY/actions/runs/${run_id}/jobs" \ + --jq '.jobs[].id' 2>/dev/null || true); do + raw_log="/tmp/gh-aw/agent/session-data/logs/${run_id}-${job_id}-raw.log" + gh api "repos/$GITHUB_REPOSITORY/actions/jobs/${job_id}/logs" \ + > "$raw_log" 2>/dev/null || true + + if [ -f "$raw_log" ] && [ -s "$raw_log" ]; then + grep "\[cca-engine\] turn=" "$raw_log" >> "$conversation_tmp" 2>/dev/null || true fi - else - echo " Warning: Could not download job logs for run $run_id (may be expired)" - rm -f "/tmp/gh-aw/agent/session-data/logs/${run_id}-raw.log" 2>/dev/null || true + rm -f "$raw_log" 2>/dev/null || true + done + + if [ -s "$conversation_tmp" ]; then + cp "$conversation_tmp" "$conversation_out" + LINE_COUNT=$(wc -l < "$conversation_out") + echo " Saved transcript fallback: $LINE_COUNT lines for run $run_id" fi - else - echo " Warning: Could not determine job ID for run $run_id" + rm -f "$conversation_tmp" + fi + + if [ ! -s "/tmp/gh-aw/agent/session-data/logs/${run_id}-events.jsonl" ] && [ ! -s "/tmp/gh-aw/agent/session-data/logs/${run_id}-conversation.txt" ]; then + echo "::error::No events.jsonl artifact or conversation transcript could be downloaded for agent run $run_id" fi fi - done + done < "$RUNS_TO_FETCH" + + LOG_RUN_COUNT=$(count_agent_logs /tmp/gh-aw/agent/session-data/sessions-list.json /tmp/gh-aw/agent/session-data/logs) + EVENTS_COUNT=$(find /tmp/gh-aw/agent/session-data/logs/ -type f -name "*-events.jsonl" | wc -l) + CONVERSATION_COUNT=$(find /tmp/gh-aw/agent/session-data/logs/ -type f -name "*-conversation.txt" | wc -l) + echo "Session logs downloaded: $LOG_RUN_COUNT of $AGENT_COUNT agent runs ($EVENTS_COUNT events.jsonl, $CONVERSATION_COUNT transcript fallbacks)" - LOG_COUNT=$(find /tmp/gh-aw/agent/session-data/logs/ -type f -name "*-conversation.txt" | wc -l) - echo "Conversation logs downloaded: $LOG_COUNT session logs" + if [ "$AGENT_COUNT" -gt 0 ] && [ "$LOG_RUN_COUNT" -lt "$AGENT_COUNT" ]; then + echo "::error::Missing per-session logs for $((AGENT_COUNT - LOG_RUN_COUNT)) of $AGENT_COUNT agent runs; failing to prevent incomplete optimization analysis" + exit 1 + fi # Store in cache with today's date cp /tmp/gh-aw/agent/session-data/sessions-list.json "$CACHE_DIR/copilot-sessions-${TODAY}.json" @@ -173,14 +250,16 @@ This shared component fetches GitHub Copilot coding agent session data by analyz 4. If cache doesn't exist: - Calculates the date 30 days ago (cross-platform compatible) - Fetches all workflow runs from branches starting with `copilot/` using GitHub API - - **Downloads conversation logs** from GitHub Actions job logs for actual agent runs (skips CI gate runs) + - **Downloads per-session logs** for actual agent runs (skips CI gate runs): structured `events.jsonl` from run artifacts first, then conversation transcripts from GitHub Actions job logs as a fallback - Saves data to cache with date-based filename (e.g., `copilot-sessions-2024-11-22.json`) - Copies data to working directory for use 5. Generates a schema of the data structure -### Conversation Transcript Access +### Session Log Access + +The fetcher first downloads non-expired GitHub Actions artifacts for each completed real agent run (status = `completed`, conclusion ≠ `action_required`) and extracts any `events.jsonl` files. When no structured events artifact is available, it falls back to raw GitHub Actions job logs and extracts `[cca-engine] turn=` lines as a transcript. CI gate runs (`action_required`) are skipped because they have no agent conversation. -Transcripts are fetched from GitHub Actions job logs using the standard GitHub API (`actions:read` permission). Each real agent run (conclusion ≠ `action_required`) produces `[cca-engine] turn=` log lines that contain the full turn-by-turn conversation — model used, token counts, tool calls and results. CI gate runs (`action_required`) are skipped because they have no agent conversation. +If any real agent run has neither an `events.jsonl` artifact nor a transcript fallback, the fetch step emits a GitHub Actions error and exits non-zero so audits do not silently proceed on metadata-only data. The `gh agent-task view --log` approach that was previously used **requires an OAuth token** that the default `GITHUB_TOKEN` does not provide, and relied on extracting a numeric session ID from the branch name — which stopped working when Copilot switched to descriptive branch slugs (e.g., `copilot/fix-mcp-gateway-docker-daemon-access`). @@ -193,14 +272,15 @@ The `gh agent-task view --log` approach that was previously used **requires an O - Multiple workflows running on the same day share the same session data - Reduces GitHub API rate limit usage - Faster workflow execution after first fetch of the day - - Includes conversation transcript cache + - Includes structured event and transcript fallback cache ### Output Files - **`/tmp/gh-aw/agent/session-data/sessions-list.json`**: Full session data including run ID, name, branch, timestamps, status, conclusion, and URL - **`/tmp/gh-aw/agent/session-data/sessions-schema.json`**: JSON schema showing the structure of the session data -- **`/tmp/gh-aw/agent/session-data/logs/`**: Directory containing session conversation logs - - **`{run_id}-conversation.txt`**: Agent conversation transcript — `[cca-engine] turn=` lines from the job log containing turn-by-turn model/token/tool data (only present for actual agent runs) +- **`/tmp/gh-aw/agent/session-data/logs/`**: Directory containing session logs + - **`{run_id}-events.jsonl`**: Structured Copilot session events extracted from run artifacts (preferred; only present for actual agent runs with events artifacts) + - **`{run_id}-conversation.txt`**: Agent conversation transcript fallback — `[cca-engine] turn=` lines from the job log containing turn-by-turn model/token/tool data (only present when `events.jsonl` is unavailable) - **`/tmp/gh-aw/cache-memory/copilot-sessions-YYYY-MM-DD.json`**: Cached session data with date - **`/tmp/gh-aw/cache-memory/copilot-sessions-YYYY-MM-DD-schema.json`**: Cached schema with date - **`/tmp/gh-aw/cache-memory/session-logs-YYYY-MM-DD/`**: Cached log files with date @@ -227,12 +307,15 @@ jq --arg today "$TODAY" '[.[] | select(.created_at >= $today)]' /tmp/gh-aw/agent jq 'length' /tmp/gh-aw/agent/session-data/sessions-list.json # Find actual agent runs (not CI gate runs) -jq -r '.[] | select(.conclusion != "action_required") | "\(.id) \(.name)"' /tmp/gh-aw/agent/session-data/sessions-list.json +jq -r '.[] | select(.status == "completed" and .conclusion != "action_required") | "\(.id) \(.name)"' /tmp/gh-aw/agent/session-data/sessions-list.json + +# List session log files (one events or transcript fallback file per actual agent run) +find /tmp/gh-aw/agent/session-data/logs -type f -# List conversation log files (one per actual agent run) -find /tmp/gh-aw/agent/session-data/logs -type f -name "*-conversation.txt" +# Read a specific events.jsonl file (by run ID) +cat /tmp/gh-aw/agent/session-data/logs/29001106791-events.jsonl -# Read a specific conversation log (by run ID) +# Read a specific transcript fallback (by run ID) cat /tmp/gh-aw/agent/session-data/logs/29001106791-conversation.txt ``` @@ -240,7 +323,7 @@ cat /tmp/gh-aw/agent/session-data/logs/29001106791-conversation.txt - Automatically imports the `jqschema` skill for schema generation (via transitive import closure) - Uses GitHub Actions API to fetch workflow runs from `copilot/*` branches -- **Fetches conversation transcripts from GitHub Actions job logs** using `actions: read` permission (standard `GITHUB_TOKEN` is sufficient) +- **Fetches structured `events.jsonl` from GitHub Actions artifacts**, then falls back to conversation transcripts from GitHub Actions job logs using `actions: read` permission (standard `GITHUB_TOKEN` is sufficient) - Cross-platform date calculation (works on both GNU and BSD date commands) - Cache-memory tool is automatically configured for data persistence @@ -248,9 +331,9 @@ cat /tmp/gh-aw/agent/session-data/logs/29001106791-conversation.txt GitHub Copilot creates branches with the `copilot/` prefix, making branch-based workflow run search a reliable way to identify Copilot coding agent sessions. -### Conversation Log Format +### Session Log Format -Transcripts (`{run_id}-conversation.txt`) contain one `[cca-engine] turn=` line per event, for example: +Structured logs (`{run_id}-events.jsonl`) contain one JSON event per line from the Copilot session-state artifact. Transcript fallbacks (`{run_id}-conversation.txt`) contain one `[cca-engine] turn=` line per event, for example: ``` 2026-07-09T07:24:53.0Z [cca-engine] turn=1 user.message: 4123 chars