Skip to content

Commit d04ee33

Browse files
jamesadevineCopilot
andcommitted
feat: enable real-time agent output streaming with VSO filtering
Replace file-redirect-then-replay pattern with piped streaming: - AWF output piped through sed -u (unbuffered) for real-time VSO command filtering, then through tee for simultaneous display and file capture - Filters both ##vso[ and ##[ shorthand forms to prevent ADO command injection while streaming - Add dedicated agent_log artifact for easy access to agent output - Apply same streaming pattern to threat analysis steps in both standalone and 1ES templates Previously all agent output was hidden until the run completed, then replayed with post-hoc sed sanitization. Now output appears line-by-line in the ADO pipeline UI as the agent runs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent ffdd1f9 commit d04ee33

2 files changed

Lines changed: 27 additions & 20 deletions

File tree

templates/1es-base.yml

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -228,15 +228,16 @@ extends:
228228
displayName: "Start network proxy"
229229
230230
- bash: |
231-
THREAT_OUTPUT_FILE="$(Agent.TempDirectory)/threat-analysis-output.txt"
231+
set -o pipefail
232232
233-
# Use $(cat file) like gh-aw does - the command is executed directly, not via a variable
234-
copilot --prompt "$(cat $(Agent.TempDirectory)/threat-analysis-prompt.md)" {{ copilot_params }} > "$THREAT_OUTPUT_FILE" 2>&1
235-
AGENT_EXIT_CODE=$?
233+
THREAT_OUTPUT_FILE="$(Agent.TempDirectory)/threat-analysis-output.txt"
236234
237-
echo "=== Threat Analysis Output (sanitized) ==="
238-
sed 's/##vso\[/## [SANITIZED] vso[/g' "$THREAT_OUTPUT_FILE"
239-
echo "=== End Threat Analysis Output ==="
235+
# Stream threat analysis output in real-time with VSO command filtering
236+
copilot --prompt "$(cat $(Agent.TempDirectory)/threat-analysis-prompt.md)" {{ copilot_params }} \
237+
2>&1 \
238+
| sed -u 's/##vso\[/[VSO-FILTERED] vso[/g; s/##\[/[VSO-FILTERED] [/g' \
239+
| tee "$THREAT_OUTPUT_FILE" \
240+
&& AGENT_EXIT_CODE=0 || AGENT_EXIT_CODE=$?
240241
241242
exit $AGENT_EXIT_CODE
242243
displayName: "Run threat analysis"

templates/base.yml

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,10 @@ jobs:
299299
# (MCPG and SafeOutputs) via host.docker.internal.
300300
# AWF auto-mounts /tmp:/tmp:rw into the container, so copilot binary,
301301
# agent prompt, and MCP config are placed under /tmp/awf-tools/.
302+
# Stream agent output in real-time while filtering VSO commands.
303+
# sed -u = unbuffered (line-by-line) so output appears immediately.
304+
# tee writes to both stdout (ADO pipeline log) and the artifact file.
305+
# pipefail (set above) ensures AWF's exit code propagates through the pipe.
302306
sudo -E "$(Pipeline.Workspace)/awf/awf" \
303307
--allow-domains {{ allowed_domains }} \
304308
--skip-pull \
@@ -308,14 +312,11 @@ jobs:
308312
--log-level info \
309313
--proxy-logs-dir "$(Agent.TempDirectory)/staging/logs/firewall" \
310314
-- '/tmp/awf-tools/copilot --prompt "$(cat /tmp/awf-tools/agent-prompt.md)" --additional-mcp-config @/tmp/awf-tools/mcp-config.json {{ copilot_params }}' \
311-
> "$AGENT_OUTPUT_FILE" 2>&1 \
315+
2>&1 \
316+
| sed -u 's/##vso\[/[VSO-FILTERED] vso[/g; s/##\[/[VSO-FILTERED] [/g' \
317+
| tee "$AGENT_OUTPUT_FILE" \
312318
&& AGENT_EXIT_CODE=0 || AGENT_EXIT_CODE=$?
313319
314-
# Display sanitized output
315-
echo "=== Agent Output (sanitized) ==="
316-
sed 's/##vso\[/[SANITIZED] vso[/g' "$AGENT_OUTPUT_FILE"
317-
echo "=== End Agent Output ==="
318-
319320
# Print firewall summary if available
320321
if [ -x "$(Pipeline.Workspace)/awf/awf" ]; then
321322
echo "=== Firewall Summary ==="
@@ -374,6 +375,11 @@ jobs:
374375
artifact: agent_outputs_$(Build.BuildId)
375376
condition: always()
376377

378+
- publish: $(Agent.TempDirectory)/staging/logs/agent-output.txt
379+
artifact: agent_log_$(Build.BuildId)
380+
displayName: "Publish agent log"
381+
condition: always()
382+
377383
- job: AnalyzeSafeOutputs
378384
displayName: "Analyze safe outputs for threats"
379385
dependsOn: PerformAgenticTask
@@ -481,9 +487,12 @@ jobs:
481487
displayName: "Setup agentic pipeline compiler"
482488
483489
- bash: |
490+
set -o pipefail
491+
484492
# Run threat analysis with AWF network isolation
485493
THREAT_OUTPUT_FILE="$(Agent.TempDirectory)/threat-analysis-output.txt"
486494
495+
# Stream threat analysis output in real-time with VSO command filtering
487496
sudo -E "$(Pipeline.Workspace)/awf/awf" \
488497
--allow-domains {{ allowed_domains }} \
489498
--skip-pull \
@@ -492,13 +501,10 @@ jobs:
492501
--log-level info \
493502
--proxy-logs-dir "$(Agent.TempDirectory)/threat-analysis-logs/firewall" \
494503
-- '/tmp/awf-tools/copilot --prompt "$(cat /tmp/awf-tools/threat-analysis-prompt.md)" {{ copilot_params }}' \
495-
> "$THREAT_OUTPUT_FILE" 2>&1
496-
AGENT_EXIT_CODE=$?
497-
498-
# Display sanitized output
499-
echo "=== Threat Analysis Output (sanitized) ==="
500-
sed 's/##vso\[/## [SANITIZED] vso[/g' "$THREAT_OUTPUT_FILE"
501-
echo "=== End Threat Analysis Output ==="
504+
2>&1 \
505+
| sed -u 's/##vso\[/[VSO-FILTERED] vso[/g; s/##\[/[VSO-FILTERED] [/g' \
506+
| tee "$THREAT_OUTPUT_FILE" \
507+
&& AGENT_EXIT_CODE=0 || AGENT_EXIT_CODE=$?
502508
503509
exit $AGENT_EXIT_CODE
504510
displayName: "Run threat analysis (AWF network isolated)"

0 commit comments

Comments
 (0)