-
Notifications
You must be signed in to change notification settings - Fork 0
feat: implement dual-channel architecture for concurrent task streaming (#48) #49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
3790411
feat: implement dual-channel architecture for concurrent task streami…
4b48d8c
fix: address PR #49 review feedback — sink swap, null-safety, iterati…
316f122
fix: auto-replay background task output on completion
e651460
fix: auto-push background task output via JLine printAbove
f9c3d40
feat: auto-background on keypress — user can type while task streams
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
119 changes: 119 additions & 0 deletions
119
aceclaw-cli/src/main/java/dev/aceclaw/cli/BackgroundOutputBuffer.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| package dev.aceclaw.cli; | ||
|
|
||
| import com.fasterxml.jackson.databind.JsonNode; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.Collections; | ||
| import java.util.List; | ||
| import java.util.Objects; | ||
|
|
||
| /** | ||
| * Buffers streaming events for background tasks. | ||
| * | ||
| * <p>When a task is sent to the background, its {@link OutputSink} is swapped | ||
| * to this buffer. Events accumulate silently and can be replayed to a | ||
| * {@link ForegroundOutputSink} when the task is brought back to the foreground. | ||
| */ | ||
| public final class BackgroundOutputBuffer implements OutputSink { | ||
|
|
||
| private final List<OutputEvent> events = Collections.synchronizedList(new ArrayList<>()); | ||
|
|
||
| @Override | ||
| public void onThinkingDelta(String delta) { | ||
| events.add(new OutputEvent.Thinking(delta)); | ||
| } | ||
|
|
||
| @Override | ||
| public void onTextDelta(String delta) { | ||
| events.add(new OutputEvent.Text(delta)); | ||
| } | ||
|
|
||
| @Override | ||
| public void onToolUse(String toolName) { | ||
| events.add(new OutputEvent.ToolUse(toolName)); | ||
| } | ||
|
|
||
| @Override | ||
| public void onStreamError(String error) { | ||
| events.add(new OutputEvent.Error(error)); | ||
| } | ||
|
|
||
| @Override | ||
| public void onStreamCancelled() { | ||
| events.add(new OutputEvent.Cancelled()); | ||
| } | ||
|
|
||
| @Override | ||
| public void onTurnComplete(JsonNode result, boolean hasError) { | ||
| events.add(new OutputEvent.Complete(result, hasError)); | ||
| } | ||
|
|
||
| @Override | ||
| public void onConnectionClosed() { | ||
| events.add(new OutputEvent.ConnectionClosed()); | ||
| } | ||
|
|
||
| /** | ||
| * Replays all buffered events to a foreground sink. | ||
| * | ||
| * @param sink the foreground sink to replay events to | ||
| */ | ||
| public void replay(ForegroundOutputSink sink) { | ||
| Objects.requireNonNull(sink, "sink"); | ||
| List<OutputEvent> snapshot; | ||
| synchronized (events) { | ||
| snapshot = new ArrayList<>(events); | ||
| } | ||
| for (var event : snapshot) { | ||
| switch (event) { | ||
| case OutputEvent.Thinking e -> sink.onThinkingDelta(e.delta()); | ||
| case OutputEvent.Text e -> sink.onTextDelta(e.delta()); | ||
| case OutputEvent.ToolUse e -> sink.onToolUse(e.toolName()); | ||
| case OutputEvent.Error e -> sink.onStreamError(e.error()); | ||
| case OutputEvent.Cancelled _ -> sink.onStreamCancelled(); | ||
| case OutputEvent.Complete e -> sink.onTurnComplete(e.result(), e.hasError()); | ||
| case OutputEvent.ConnectionClosed _ -> sink.onConnectionClosed(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Extracts just the text content from buffered events (no spinners, no thinking). | ||
| * Safe for rendering to a StringWriter without side effects. | ||
| * | ||
| * @return the concatenated text deltas and error messages | ||
| */ | ||
| public String extractTextContent() { | ||
| var sb = new StringBuilder(); | ||
| synchronized (events) { | ||
| for (var event : events) { | ||
| switch (event) { | ||
| case OutputEvent.Text e -> sb.append(e.delta()); | ||
| case OutputEvent.Error e -> sb.append("[error: ").append(e.error()).append("]\n"); | ||
| default -> {} // skip thinking, tool use, lifecycle events | ||
| } | ||
| } | ||
| } | ||
| return sb.toString(); | ||
| } | ||
|
|
||
| /** | ||
| * Returns the number of buffered events. | ||
| */ | ||
| public int size() { | ||
| return events.size(); | ||
| } | ||
|
|
||
| /** | ||
| * Sealed event hierarchy for buffered output events. | ||
| */ | ||
| public sealed interface OutputEvent { | ||
| record Thinking(String delta) implements OutputEvent {} | ||
| record Text(String delta) implements OutputEvent {} | ||
| record ToolUse(String toolName) implements OutputEvent {} | ||
| record Error(String error) implements OutputEvent {} | ||
| record Cancelled() implements OutputEvent {} | ||
| record Complete(JsonNode result, boolean hasError) implements OutputEvent {} | ||
| record ConnectionClosed() implements OutputEvent {} | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.