diff --git a/.github/workflows/validate-templates.yml b/.github/workflows/validate-templates.yml index 9e8b0a7d9..42c3cd4b7 100644 --- a/.github/workflows/validate-templates.yml +++ b/.github/workflows/validate-templates.yml @@ -82,7 +82,7 @@ jobs: echo "Checking PowerShell scripts..." errors=0 for f in bin/ps/*.ps1; do - result=$(pwsh -NoProfile -Command "\$errs=\$null; [System.Management.Automation.Language.Parser]::ParseFile('$f', [ref]\$null, [ref]\$errs); if(\$errs -and \$errs.Count -gt 0){\$errs | ForEach-Object { \$_.Message }}" 2>&1) + result=$(pwsh -NoProfile -Command "\$errs=\$null; \$null = [System.Management.Automation.Language.Parser]::ParseFile('$f', [ref]\$null, [ref]\$errs); if(\$errs -and \$errs.Count -gt 0){\$errs | ForEach-Object { \$_.Message }}" 2>&1) if [[ -n "$result" && "$result" != *"InvalidOperation"* ]]; then echo "❌ $f: $result" errors=$((errors + 1)) diff --git a/sreagent-templates/bicep/Apply-Extras.ps1 b/sreagent-templates/bicep/Apply-Extras.ps1 index 50eb30cb3..6486856d9 100644 --- a/sreagent-templates/bicep/Apply-Extras.ps1 +++ b/sreagent-templates/bicep/Apply-Extras.ps1 @@ -1062,61 +1062,40 @@ if ($DpTokenAvailable) { if ($token) { try { $headers = @{ Authorization = "Bearer $token" } - $ghStatus = Invoke-RestMethod -TimeoutSec 30 -Uri "$AgentEndpoint/api/v1/Github/auth/status" -Headers $headers - $ghConfigured = ($ghStatus.isConfigured -eq $true) -or ($ghStatus.hosts[0].isConfigured -eq $true) + $ghStatus = Invoke-RestMethod -TimeoutSec 30 -Uri "$AgentEndpoint/api/v2/github/domains" -Headers $headers + $ghConfigured = ($ghStatus.values -and $ghStatus.values.Count -gt 0) } catch { } } - # Shared identity + connector body (used in both fast-path and OAuth wait) - $ident = if ($AgentUami) { $AgentUami } else { - Write-Host " WARN: agent has no user-assigned MI; falling back to SystemAssigned." - "SystemAssigned" - } - $connBody = @{ - name = "github" - type = "AgentConnector" - properties = @{ - dataConnectorType = "GitHubOAuth" - dataSource = "github-oauth" - identity = $ident - } - } | ConvertTo-Json -Compress -Depth 10 + # NOTE: The old GitHubOAuth connector type was deprecated in platform build + # 26.4.216.0 (April 2026). Auth is now stored via /api/v2/github/domains. + # We no longer PUT /api/v2/extendedAgent/connectors/github. $connectorOk = $false - # Fast path: if auth/status says configured (or PAT), try the connector PUT immediately + # Fast path: if domains shows configured (or PAT), go straight to repo wiring if ($ghConfigured -or $env:GITHUB_PAT) { - Write-Host "-- Wiring GitHub connector + repos --" - try { - $token = Get-DpToken - $headers = @{ - Authorization = "Bearer $token" - "Content-Type" = "application/json" - } - $null = Invoke-RestMethod -TimeoutSec 30 -Uri "$AgentEndpoint/api/v2/extendedAgent/connectors/github" ` - -Method Put -Headers $headers -Body $connBody -ContentType "application/json" - # Verify the connector is actually healthy (not just metadata) - Start-Sleep -Seconds 3 + Write-Host "-- Wiring GitHub repos --" + # If GITHUB_PAT is provided and domains not yet configured, store PAT via domains API + if ($env:GITHUB_PAT -and -not $ghConfigured) { try { - $connCheck = Invoke-RestMethod -TimeoutSec 30 -Uri "$AgentEndpoint/api/v2/extendedAgent/connectors/github" ` - -Method Get -Headers @{ Authorization = "Bearer $token" } - $provState = $connCheck.properties.provisioningState - if ($provState -eq 'Succeeded') { - Write-Host " ok connector/github (GitHubOAuth, identity=$($ident.Split('/')[-1]))" - $connectorOk = $true - } else { - Write-Host " WARN: connector created but state=$provState — falling back to OAuth wait..." + $token = Get-DpToken + $headers = @{ + Authorization = "Bearer $token" + "Content-Type" = "application/json" } + $patBody = @{ AuthType = "Pat"; Pat = $env:GITHUB_PAT } | ConvertTo-Json -Compress + $null = Invoke-RestMethod -TimeoutSec 30 -Uri "$AgentEndpoint/api/v2/github/domains/github_com" ` + -Method Put -Headers $headers -Body $patBody -ContentType "application/json" + Write-Host " ok github/domains/github_com (PAT)" } catch { - Write-Host " ok connector/github (GitHubOAuth, identity=$($ident.Split('/')[-1]))" - $connectorOk = $true + Write-Host " FAILED -- PUT /api/v2/github/domains/github_com" } - } catch { - Write-Host " WARN: connector PUT failed (stale auth?) — falling back to OAuth wait..." } + $connectorOk = $true } - # OAuth wait: if connector not yet created, show URL and poll until user completes auth + # OAuth wait: if not configured yet, show URL and poll until user completes auth if (-not $connectorOk -and -not $env:GITHUB_PAT) { Write-Host "-- GitHub OAuth sign-in required --" Write-Host "Repos waiting: $($oauthRepos -join ' ')" @@ -1125,7 +1104,7 @@ if ($DpTokenAvailable) { if ($token) { try { $headers = @{ Authorization = "Bearer $token" } - $ghConfig = Invoke-RestMethod -TimeoutSec 30 -Uri "$AgentEndpoint/api/v1/Github/config" -Headers $headers + $ghConfig = Invoke-RestMethod -TimeoutSec 30 -Uri "$AgentEndpoint/api/v2/github/oauth/config" -Headers $headers $oauthUrl = if ($ghConfig.oAuthUrl) { $ghConfig.oAuthUrl } elseif ($ghConfig.OAuthUrl) { $ghConfig.OAuthUrl } else { $null } } catch { } } @@ -1139,18 +1118,11 @@ if ($DpTokenAvailable) { Start-Sleep -Seconds 10 try { $token = Get-DpToken - $headers = @{ - Authorization = "Bearer $token" - "Content-Type" = "application/json" - } - # Check auth/status — only trust isConfigured, not connector PUT success - $ghCheck = Invoke-RestMethod -TimeoutSec 30 -Uri "$AgentEndpoint/api/v1/Github/auth/status" -Headers $headers - $isAuth = ($ghCheck.isConfigured -eq $true) -or ($ghCheck.hosts -and $ghCheck.hosts[0].isConfigured -eq $true) + $headers = @{ Authorization = "Bearer $token" } + $ghCheck = Invoke-RestMethod -TimeoutSec 30 -Uri "$AgentEndpoint/api/v2/github/domains" -Headers $headers + $isAuth = ($ghCheck.values -and $ghCheck.values.Count -gt 0) if ($isAuth) { - $null = Invoke-RestMethod -TimeoutSec 30 -Uri "$AgentEndpoint/api/v2/extendedAgent/connectors/github" ` - -Method Put -Headers $headers -Body $connBody -ContentType "application/json" Write-Host " GitHub authorized!" - Write-Host " ok connector/github" $connectorOk = $true break } @@ -1164,7 +1136,7 @@ if ($DpTokenAvailable) { Write-Host " Headless alternative: `$env:GITHUB_PAT='ghp_xxx' && re-run" } } else { - Write-Host " Could not fetch OAuth URL from $AgentEndpoint/api/v1/Github/config." + Write-Host " Could not fetch OAuth URL from $AgentEndpoint/api/v2/github/oauth/config." Write-Host " Fallback: Azure portal -> agent -> Repos -> 'Authorize' next to each repo." } } diff --git a/sreagent-templates/bicep/apply-extras.sh b/sreagent-templates/bicep/apply-extras.sh index f3f089ef3..567c2ee40 100755 --- a/sreagent-templates/bicep/apply-extras.sh +++ b/sreagent-templates/bicep/apply-extras.sh @@ -1058,14 +1058,14 @@ fi echo # --------------------------------------------------------------------------- -# GitHub: OAuth sign-in + connector + repo wiring. +# GitHub: OAuth sign-in + repo wiring. # Three-state flow: # - No repos requested → skip # - Repos requested, OAuth NOT done → print sign-in URL, instruct re-run -# - Repos requested, OAuth DONE → create GitHubOAuth connector + PUT repos -# Repo PUT body and connector body shapes come from sreagent-investigation: -# src/Agent/Agent.Web/Client/src/src/Common/Clients/ExtendedAgentClient.ts -# src/Agent/Agent.Web/Client/src/src/Space/Settings/Connectors/Wizard/Common/DialogHelper.tsx +# - Repos requested, OAuth DONE → PUT repos +# NOTE: The old GitHubOAuth connector type was deprecated in platform build +# 26.4.216.0 (April 2026). Auth is now stored via /api/v2/github/domains. +# We no longer PUT /api/v2/extendedAgent/connectors/github. # --------------------------------------------------------------------------- if [[ ${#oauth_repos[@]} -gt 0 ]]; then TOKEN=$(_dp_token 2>/dev/null || true) @@ -1077,46 +1077,25 @@ if [[ ${#oauth_repos[@]} -gt 0 ]]; then else GH_CONFIGURED="false" fi - # Also check if the github connector already exists (OAuth was done in a prior deploy) - if [[ "$GH_CONFIGURED" == "false" ]]; then - _connectors=$(curl -sS -H "Authorization: Bearer ${TOKEN}" -H "Accept: application/json" \ - "${AGENT_ENDPOINT}/api/v2/extendedAgent/connectors" 2>/dev/null || echo '{}') - if echo "$_connectors" | jq -e '[.value // [] | .[] | select(.name == "github")] | length > 0' >/dev/null 2>&1; then - GH_CONFIGURED="true" - fi - fi if [[ "$GH_CONFIGURED" == "true" || -n "${GITHUB_PAT:-}" ]]; then - # ── OAuth (or PAT) is in place — wire the connector + repos ── - echo "── Wiring GitHub connector + repos ──" + # ── OAuth (or PAT) is in place — wire repos ── + echo "── Wiring GitHub repos ──" - if [[ -z "$AGENT_UAMI" ]]; then - echo " WARN: agent has no user-assigned MI; falling back to SystemAssigned." - IDENT="SystemAssigned" - else - IDENT="$AGENT_UAMI" - fi - - # 1) Create the GitHubOAuth connector (named 'github') - body=$(jq -nc --arg id "$IDENT" '{ - name: "github", - type: "AgentConnector", - properties: { - dataConnectorType: "GitHubOAuth", - dataSource: "github-oauth", - identity: $id - } - }') - if curl -sS -f -X PUT "${AGENT_ENDPOINT}/api/v2/extendedAgent/connectors/github" \ - -H "Authorization: Bearer ${TOKEN}" \ - -H "Content-Type: application/json" \ - --data "$body" >/dev/null; then - echo " ok connector/github (GitHubOAuth, identity=${IDENT##*/})" - else - echo " FAILED — PUT /api/v2/extendedAgent/connectors/github" + # If GITHUB_PAT is provided, store it via the domains API so the agent + # can use it for GitHub API calls (replaces deprecated connector PUT). + if [[ -n "${GITHUB_PAT:-}" && "$GH_CONFIGURED" != "true" ]]; then + if curl -sS -f -X PUT "${AGENT_ENDPOINT}/api/v2/github/domains/github_com" \ + -H "Authorization: Bearer ${TOKEN}" \ + -H "Content-Type: application/json" \ + --data "{\"AuthType\":\"Pat\",\"Pat\":\"${GITHUB_PAT}\"}" >/dev/null; then + echo " ok github/domains/github_com (PAT)" + else + echo " FAILED — PUT /api/v2/github/domains/github_com" + fi fi - # 2) Attach each repo via the v2 repos dataplane (CodeRepoApiController). + # Attach each repo via the v2 repos dataplane (CodeRepoApiController). # Route: PUT /api/v2/repos/{name} # Body : { name, type:"CodeRepo", properties:{ url, type:"GitHub"|"AzureDevOps", description? } } count=$(jq '.repos // [] | length' "$FILE") @@ -1177,12 +1156,6 @@ if [[ ${#oauth_repos[@]} -gt 0 ]]; then _has_domain=$(echo "$_poll" | jq -r 'if (.values // []) | length > 0 then "true" else "false" end' 2>/dev/null) if [[ "$_has_domain" == "true" ]]; then echo " GitHub authorized!" - # Now create the connector - if [[ -z "$AGENT_UAMI" ]]; then IDENT="SystemAssigned"; else IDENT="$AGENT_UAMI"; fi - conn_body=$(jq -nc --arg id "$IDENT" '{name:"github",type:"AgentConnector",properties:{dataConnectorType:"GitHubOAuth",dataSource:"github-oauth",identity:$id}}') - curl -sS -f -X PUT "${AGENT_ENDPOINT}/api/v2/extendedAgent/connectors/github" \ - -H "Authorization: Bearer ${TOKEN}" -H "Content-Type: application/json" \ - --data "$conn_body" >/dev/null 2>&1 && echo " ok connector/github" || echo " WARN connector/github PUT failed" auth_ok=true break fi @@ -1191,7 +1164,7 @@ if [[ ${#oauth_repos[@]} -gt 0 ]]; then echo if [[ "$auth_ok" == "true" ]]; then - # Connector already created in polling loop — wire repos only + # OAuth token stored by platform callback — wire repos echo "── Wiring GitHub repos ──" TOKEN=$(_dp_token) count=$(jq '.repos // [] | length' "$FILE") diff --git a/sreagent-templates/bicep/assemble-agent.sh b/sreagent-templates/bicep/assemble-agent.sh index 0632fca92..ad99184e3 100755 --- a/sreagent-templates/bicep/assemble-agent.sh +++ b/sreagent-templates/bicep/assemble-agent.sh @@ -301,7 +301,8 @@ fi REPO_INSTRUCTIONS="[]" [[ -f "${DIR}/data/repo-instructions.json" ]] && REPO_INSTRUCTIONS=$(cat "${DIR}/data/repo-instructions.json") -# Auto-discover .md files in data/ and data/knowledge/ → convert to knowledge items for upload +# Auto-discover .md files in data/ and data/knowledge/ → upload via AgentMemory (data-plane) +# These show in the portal Knowledge tab (RAG-indexed), NOT as KnowledgeFile connectors. MD_FILES=$(find "${DIR}/data" -maxdepth 1 -name "*.md" -type f 2>/dev/null || true; \ find "${DIR}/data/knowledge" -maxdepth 1 -name "*.md" -type f 2>/dev/null || true) if [[ -n "$MD_FILES" ]]; then @@ -309,9 +310,9 @@ if [[ -n "$MD_FILES" ]]; then _log "Found ${MD_COUNT} knowledge .md file(s) in data/" for mdf in $MD_FILES; do fname=$(basename "$mdf") - content=$(cat "$mdf") - KNOWLEDGE_ITEMS=$(echo "$KNOWLEDGE_ITEMS" | jq --arg name "$fname" --arg content "$content" \ - '. + [{"name": $name, "type": "KnowledgeText", "content": $content}]') + abs_path=$(cd "$(dirname "$mdf")" && pwd)/$(basename "$mdf") + KNOWLEDGE=$(echo "$KNOWLEDGE" | jq --arg fname "$fname" --arg path "$abs_path" \ + '. + [{"filename": $fname, "mimeType": "text/markdown", "triggerIndexing": true, "localPath": $path}]') done fi diff --git a/sreagent-templates/bin/export-agent.sh b/sreagent-templates/bin/export-agent.sh index 328bd3e9e..10e0e4698 100755 --- a/sreagent-templates/bin/export-agent.sh +++ b/sreagent-templates/bin/export-agent.sh @@ -670,6 +670,9 @@ else fi # ── 2. Knowledge items (KnowledgeText/File/WebPage/Repository via connectors API) ── +# NOTE: KnowledgeText items are exported as plain .md files in data/ so that +# on re-deploy they go through the AgentMemory data-plane upload (Knowledge tab) +# instead of being re-created as ARM KnowledgeFile connectors. if [[ "$INCLUDE_KNOWLEDGE_ITEMS" == "true" ]]; then _log "Reading knowledge items from connectors API..." RAW_KNOWLEDGE_ITEMS=$(dp_get "/api/v2/extendedAgent/connectors") @@ -690,37 +693,51 @@ if [[ "$INCLUDE_KNOWLEDGE_ITEMS" == "true" ]]; then KI_COUNT=$(echo "$KNOWLEDGE_ITEMS" | jq 'length') _log " Found ${KI_COUNT} knowledge item(s)" - # Download knowledge item content if requested + # Download knowledge item content and write KnowledgeText as .md in data/ if [[ "$DOWNLOAD_FILES" == "true" && "$KI_COUNT" -gt 0 ]]; then _log " Downloading knowledge item content..." KI_DIR="${FILES_DIR}/knowledge-items" mkdir -p "$KI_DIR" + KI_TEXT_EXPORTED=0 + KI_OTHER_ITEMS="[]" for i in $(seq 0 $((KI_COUNT - 1))); do kiname=$(echo "$KNOWLEDGE_ITEMS" | jq -r --argjson i "$i" '.[$i].name') kitype=$(echo "$KNOWLEDGE_ITEMS" | jq -r --argjson i "$i" '.[$i].type') - # Determine file extension based on type case "$kitype" in - KnowledgeText) ext=".md" ;; - KnowledgeWebPage) ext=".html" ;; - KnowledgeFile) ext="" ;; - *) ext=".json" ;; + KnowledgeText) + # Export as .md file in data/ → will be uploaded via AgentMemory on redeploy + fname="${kiname}.md" + # Try to strip trailing -md suffix from connector name for cleaner filenames + [[ "$kiname" == *-md ]] && fname="${kiname%-md}.md" + dp_download "/api/v2/extendedAgent/connectors/$(printf %s "$kiname" | jq -sRr @uri)/content" \ + "${KI_DIR}/${fname}" 2>/dev/null && { + _log " ✓ ${kiname} → data/${fname} (will use AgentMemory on redeploy)" + KI_TEXT_EXPORTED=$((KI_TEXT_EXPORTED + 1)) + } || _log " ✗ ${kiname} (could not download content)" + ;; + *) + # Non-text items (WebPage, File, Repository) stay as knowledgeItems + ext="" + case "$kitype" in + KnowledgeWebPage) ext=".html" ;; + KnowledgeFile) ext="" ;; + *) ext=".json" ;; + esac + dp_download "/api/v2/extendedAgent/connectors/$(printf %s "$kiname" | jq -sRr @uri)/content" \ + "${KI_DIR}/${kiname}${ext}" 2>/dev/null && \ + _log " ✓ ${kiname} (${kitype})" || \ + _log " ✗ ${kiname} (could not download content)" + KI_OTHER_ITEMS=$(echo "$KI_OTHER_ITEMS" | jq --argjson i "$i" --arg dir "$KI_DIR" --arg ext "$ext" \ + --slurpfile items <(echo "$KNOWLEDGE_ITEMS") \ + '. + [$items[0][$i] + {localPath: ($dir + "/" + $items[0][$i].name + $ext)}]') + ;; esac - dp_download "/api/v2/extendedAgent/connectors/$(printf %s "$kiname" | jq -sRr @uri)/content" \ - "${KI_DIR}/${kiname}${ext}" 2>/dev/null && \ - _log " ✓ ${kiname} (${kitype})" || \ - _log " ✗ ${kiname} (could not download content)" done - # Update entries with localPath - KNOWLEDGE_ITEMS=$(echo "$KNOWLEDGE_ITEMS" | jq -c --arg dir "$KI_DIR" '[ - .[] | . + { - localPath: ($dir + "/" + .name + ( - if .type == "KnowledgeText" then ".md" - elif .type == "KnowledgeWebPage" then ".html" - elif .type == "KnowledgeFile" then "" - else ".json" end - )) - } - ]') + # Only keep non-text items in KNOWLEDGE_ITEMS (text ones became .md files) + KNOWLEDGE_ITEMS="$KI_OTHER_ITEMS" + if [[ "$KI_TEXT_EXPORTED" -gt 0 ]]; then + _log " Migrated ${KI_TEXT_EXPORTED} KnowledgeText item(s) to data/ .md files (AgentMemory path)" + fi fi else _log "Skipping knowledge items (use --include-knowledge-items to include)" @@ -1421,12 +1438,26 @@ fi # Move downloaded files into data/ if they were downloaded if [[ "$DOWNLOAD_FILES" == "true" && -d "$FILES_DIR" ]]; then - for subdir in knowledge knowledge-items synthesized-knowledge repo-instructions; do + for subdir in knowledge synthesized-knowledge repo-instructions; do if [[ -d "${FILES_DIR}/${subdir}" ]]; then mkdir -p "${DATA_DIR}/${subdir}" cp -r "${FILES_DIR}/${subdir}/." "${DATA_DIR}/${subdir}/" 2>/dev/null || true fi done + # KnowledgeText .md files go to data/ root so assemble auto-discovers them + # for AgentMemory upload (not back into knowledge-items which creates ARM connectors) + if [[ -d "${FILES_DIR}/knowledge-items" ]]; then + for f in "${FILES_DIR}/knowledge-items/"*.md; do + [[ -f "$f" ]] && cp "$f" "${DATA_DIR}/" 2>/dev/null || true + done + # Non-.md files (WebPage, File) stay in knowledge-items/ + for f in "${FILES_DIR}/knowledge-items/"*; do + [[ -f "$f" && "$f" != *.md ]] && { + mkdir -p "${DATA_DIR}/knowledge-items" + cp "$f" "${DATA_DIR}/knowledge-items/" 2>/dev/null || true + } + done + fi # Clean up temp files dir if it was separate [[ "$FILES_DIR" != "$DATA_DIR" && "$FILES_DIR" != "${EXPORT_DIR}/data" ]] && rm -rf "$FILES_DIR" 2>/dev/null || true fi