Stream deployment script output to the task log live
Summary
- A long-running deployment action showed an empty Running node with no log lines until it completed: the agent's incremental output (received every Halibut
GetStatuspoll) went only to the server's Serilog, and the task log was populated once, in bulk, after completion. Operators couldn't see what an in-flight script was doing. - Thread an optional output sink from the executor → execution strategy → script observer. On the Halibut/Tentacle path the observer forwards each incremental batch as it arrives; the executor emits a new
ScriptProgressReceivedEventandDeploymentActivityLoggerpersists it toServerTaskLoglive, reusing the exact sequence / category / source / activity-node / masking logic of the bulk path. - The result is marked
OutputStreamedso the post-completion bulk persist is skipped — lines are never duplicated.
Non-breaking
- The sink is an optional param defaulting to
null. Every other execution strategy (SSH, OpenClaw, local, K8s-API) and any caller that doesn't opt in behaves exactly as before:OutputStreamedstays false and the bulk persist runs unchanged. - Streaming failure falls back to the bulk persist (
OutputStreamed=false) — output is never lost (at-least-once; may duplicate on the rare failure, preferred over loss). - Sequence allocation is already
Interlocked, so concurrent streaming from parallel actions/targets is safe. - No schema change. (The optional param forced
It.IsAny<ScriptOutputSink>()into existing observer mocks — test-only.)
Test plan
-
Unit — observer ( HalibutScriptObserverStreamingTests): per-poll-batch delivery in order; stderr source tagging; empty-line skip; sink-throws → fallback (OutputStreamed=false, no loss); timeout streams its synthetic line + marks streamed; no-sink → unchanged. -
Unit — activity logger ( DeploymentActivityLoggerStreamingTests): live chunk persisted with category (stderr→Error), source, masking, monotonic sequence; empty chunk no-op;OutputStreamed=trueskips bulk;falsepersists bulk (legacy). -
Integration — real pipeline + DB ( DeploymentLogWriterIntegrationTests): chunks persist incrementally and each line lands exactly once (final bulk skipped), stderr categorised, attributed to the action node; control path (OutputStreamed=false) persists at end. -
Full unit suite 5766/5766; resume-reattach integration 4/4; full solution builds 0 errors.