Skip to content

Commit b1a76bf

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 b1a76bf

2 files changed

Lines changed: 25 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: 17 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 ==="
@@ -481,9 +482,12 @@ jobs:
481482
displayName: "Setup agentic pipeline compiler"
482483
483484
- bash: |
485+
set -o pipefail
486+
484487
# Run threat analysis with AWF network isolation
485488
THREAT_OUTPUT_FILE="$(Agent.TempDirectory)/threat-analysis-output.txt"
486489
490+
# Stream threat analysis output in real-time with VSO command filtering
487491
sudo -E "$(Pipeline.Workspace)/awf/awf" \
488492
--allow-domains {{ allowed_domains }} \
489493
--skip-pull \
@@ -492,13 +496,10 @@ jobs:
492496
--log-level info \
493497
--proxy-logs-dir "$(Agent.TempDirectory)/threat-analysis-logs/firewall" \
494498
-- '/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 ==="
499+
2>&1 \
500+
| sed -u 's/##vso\[/[VSO-FILTERED] vso[/g; s/##\[/[VSO-FILTERED] [/g' \
501+
| tee "$THREAT_OUTPUT_FILE" \
502+
&& AGENT_EXIT_CODE=0 || AGENT_EXIT_CODE=$?
502503
503504
exit $AGENT_EXIT_CODE
504505
displayName: "Run threat analysis (AWF network isolated)"
@@ -643,6 +644,9 @@ jobs:
643644
- bash: |
644645
# Copy all logs to output directory for artifact upload
645646
mkdir -p "$(Agent.TempDirectory)/staging/logs"
647+
# Copy agent output log from analyzed_outputs for optimisation use
648+
cp "$(Pipeline.Workspace)/analyzed_outputs_$(Build.BuildId)/logs/agent-output.txt" \
649+
"$(Agent.TempDirectory)/staging/logs/agent-output.txt" 2>/dev/null || true
646650
if [ -d ~/.copilot/logs ]; then
647651
mkdir -p "$(Agent.TempDirectory)/staging/logs/copilot"
648652
cp -r ~/.copilot/logs/* "$(Agent.TempDirectory)/staging/logs/copilot/" 2>/dev/null || true

0 commit comments

Comments
 (0)