fix: echo protocol run inputs
This commit is contained in:
parent
8aafd388fa
commit
fdf1f43281
|
|
@ -2,7 +2,7 @@
|
||||||
title: 'Backend runtime経由の操作をprotocol transportへ統一しTUI同等にする'
|
title: 'Backend runtime経由の操作をprotocol transportへ統一しTUI同等にする'
|
||||||
state: 'planning'
|
state: 'planning'
|
||||||
created_at: '2026-07-21T09:20:07Z'
|
created_at: '2026-07-21T09:20:07Z'
|
||||||
updated_at: '2026-07-21T11:42:36Z'
|
updated_at: '2026-07-22T02:11:40Z'
|
||||||
assignee: null
|
assignee: null
|
||||||
readiness: 'draft'
|
readiness: 'draft'
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -166,3 +166,35 @@ Verification:
|
||||||
- `git diff --check`
|
- `git diff --check`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<!-- event: implementation_report author: assistant at: 2026-07-22T02:11:40Z -->
|
||||||
|
|
||||||
|
## Implementation report
|
||||||
|
|
||||||
|
Bug fix for missing local echo in TUI/Web:
|
||||||
|
|
||||||
|
Observed symptom:
|
||||||
|
- Sending from TUI/Web over unified `/protocol/ws` reached the runtime and was accepted as `protocol_method`, but the user's sent content did not appear in the Console.
|
||||||
|
|
||||||
|
Root cause:
|
||||||
|
- The old HTTP input path emitted observation events for accepted input via `input_protocol_event(...)` (`Event::UserMessage` for user input / `Event::SystemItem` for system input).
|
||||||
|
- The new raw `Method::Run` / `Method::Notify` protocol path dispatched the method to the execution backend but did not record the corresponding observation echo event.
|
||||||
|
- Therefore the unified protocol stream had assistant/runtime events, but no echo event representing the user-sent content.
|
||||||
|
|
||||||
|
Fix:
|
||||||
|
- Added protocol-method observation echo in `worker-runtime` after accepted `Runtime::send_protocol_method(...)` dispatch.
|
||||||
|
- `Method::Run` now records `Event::UserMessage { segments }`.
|
||||||
|
- `Method::Notify` now records a system input `Event::SystemItem`.
|
||||||
|
- Kept compact/list_rewind_targets/register_peer command echoes aligned with the old command-input observation shape.
|
||||||
|
- Added a runtime protocol WS regression test: send `Method::Run` over `/protocol/ws` and assert the same socket receives `Event::UserMessage` with the sent text.
|
||||||
|
|
||||||
|
Verification:
|
||||||
|
- `nix develop -c cargo fmt -- --check`
|
||||||
|
- `nix develop -c cargo check -p worker-runtime -p yoi-workspace-server -p client -p tui`
|
||||||
|
- `nix develop -c cargo test -p worker-runtime --features ws-server --lib protocol_ws_echoes_accepted_run_as_user_message`
|
||||||
|
- `nix develop -c cargo test -p worker-runtime --features ws-server --lib protocol_ws`
|
||||||
|
- `nix develop -c cargo test -p yoi-workspace-server protocol_ws --lib`
|
||||||
|
- `nix develop -c cargo test -p client backend_runtime --lib`
|
||||||
|
- `git diff --check`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
|
||||||
|
|
@ -1167,6 +1167,17 @@ mod ws_tests {
|
||||||
WorkerExecutionRunState::Idle,
|
WorkerExecutionRunState::Idle,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn dispatch_method(
|
||||||
|
&self,
|
||||||
|
_handle: &WorkerExecutionHandle,
|
||||||
|
_method: protocol::Method,
|
||||||
|
) -> WorkerExecutionResult {
|
||||||
|
WorkerExecutionResult::accepted(
|
||||||
|
WorkerExecutionOperation::ProtocolMethod,
|
||||||
|
WorkerExecutionRunState::Idle,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ws_test_bundle(profile: ProfileSelector) -> ConfigBundle {
|
fn ws_test_bundle(profile: ProfileSelector) -> ConfigBundle {
|
||||||
|
|
@ -1328,6 +1339,34 @@ mod ws_tests {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn protocol_ws_echoes_accepted_run_as_user_message() {
|
||||||
|
let (_runtime, _worker_ref, url) = spawn_runtime_server().await;
|
||||||
|
let (mut stream, _) = connect_async(&url).await.unwrap();
|
||||||
|
let _ = next_frame(&mut stream).await;
|
||||||
|
|
||||||
|
stream
|
||||||
|
.send(Message::Text(
|
||||||
|
serde_json::to_string(&protocol::Method::Run {
|
||||||
|
input: vec![protocol::Segment::text("hello from protocol")],
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
.into(),
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
match next_frame(&mut stream).await {
|
||||||
|
protocol::Event::UserMessage { segments } => {
|
||||||
|
assert_eq!(
|
||||||
|
protocol::Segment::flatten_to_text(&segments),
|
||||||
|
"hello from protocol"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
event => panic!("expected user message echo, got {event:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn protocol_ws_reports_malformed_cursor_and_method_frame() {
|
async fn protocol_ws_reports_malformed_cursor_and_method_frame() {
|
||||||
let (_runtime, _worker_ref, url) = spawn_runtime_server().await;
|
let (_runtime, _worker_ref, url) = spawn_runtime_server().await;
|
||||||
|
|
|
||||||
|
|
@ -576,6 +576,8 @@ impl Runtime {
|
||||||
return Ok(vec![Event::Completions { kind, entries }]);
|
return Ok(vec![Event::Completions { kind, entries }]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let observation_payload = protocol_method_observation_event(&method);
|
||||||
|
|
||||||
let (backend, handle) = {
|
let (backend, handle) = {
|
||||||
let mut state = self.lock()?;
|
let mut state = self.lock()?;
|
||||||
state.ensure_running()?;
|
state.ensure_running()?;
|
||||||
|
|
@ -622,6 +624,9 @@ impl Runtime {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.record_execution_result(worker_ref, dispatch_result)?;
|
self.record_execution_result(worker_ref, dispatch_result)?;
|
||||||
|
if let Some(payload) = observation_payload {
|
||||||
|
self.record_protocol_method_observation(worker_ref, payload)?;
|
||||||
|
}
|
||||||
Ok(Vec::new())
|
Ok(Vec::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -958,12 +963,7 @@ impl Runtime {
|
||||||
worker_ref: &WorkerRef,
|
worker_ref: &WorkerRef,
|
||||||
input: WorkerInput,
|
input: WorkerInput,
|
||||||
) -> Result<(), RuntimeError> {
|
) -> Result<(), RuntimeError> {
|
||||||
let mut state = self.lock()?;
|
self.record_protocol_method_observation(worker_ref, input_protocol_event(&input))
|
||||||
state.ensure_worker_ref(worker_ref)?;
|
|
||||||
let event =
|
|
||||||
state.push_worker_observation_event(worker_ref.clone(), input_protocol_event(&input));
|
|
||||||
state.persist_worker_observation_event(&event)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "ws-server"))]
|
#[cfg(not(feature = "ws-server"))]
|
||||||
|
|
@ -975,6 +975,28 @@ impl Runtime {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ws-server")]
|
||||||
|
fn record_protocol_method_observation(
|
||||||
|
&self,
|
||||||
|
worker_ref: &WorkerRef,
|
||||||
|
payload: Event,
|
||||||
|
) -> Result<(), RuntimeError> {
|
||||||
|
let mut state = self.lock()?;
|
||||||
|
state.ensure_worker_ref(worker_ref)?;
|
||||||
|
let event = state.push_worker_observation_event(worker_ref.clone(), payload);
|
||||||
|
state.persist_worker_observation_event(&event)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "ws-server"))]
|
||||||
|
fn record_protocol_method_observation(
|
||||||
|
&self,
|
||||||
|
_worker_ref: &WorkerRef,
|
||||||
|
_payload: Event,
|
||||||
|
) -> Result<(), RuntimeError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn transition_worker(
|
fn transition_worker(
|
||||||
&self,
|
&self,
|
||||||
worker_ref: &WorkerRef,
|
worker_ref: &WorkerRef,
|
||||||
|
|
@ -1836,6 +1858,48 @@ fn validate_worker_input(input: &WorkerInput) -> Result<(), RuntimeError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ws-server")]
|
||||||
|
fn protocol_method_observation_event(method: &Method) -> Option<Event> {
|
||||||
|
match method {
|
||||||
|
Method::Run { input } => Some(Event::UserMessage {
|
||||||
|
segments: input.clone(),
|
||||||
|
}),
|
||||||
|
Method::Notify { message, .. } => Some(Event::SystemItem {
|
||||||
|
item: serde_json::json!({
|
||||||
|
"kind": "embedded_worker_system_input",
|
||||||
|
"content": message,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
Method::RegisterPeer { name } => Some(Event::SystemItem {
|
||||||
|
item: serde_json::json!({
|
||||||
|
"kind": "embedded_worker_command_input",
|
||||||
|
"command": "register_peer",
|
||||||
|
"content": name,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
Method::Compact => Some(Event::SystemItem {
|
||||||
|
item: serde_json::json!({
|
||||||
|
"kind": "embedded_worker_command_input",
|
||||||
|
"command": "compact",
|
||||||
|
"content": "",
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
Method::ListRewindTargets => Some(Event::SystemItem {
|
||||||
|
item: serde_json::json!({
|
||||||
|
"kind": "embedded_worker_command_input",
|
||||||
|
"command": "list_rewind_targets",
|
||||||
|
"content": "",
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "ws-server"))]
|
||||||
|
fn protocol_method_observation_event(_method: &Method) -> Option<Event> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "ws-server")]
|
#[cfg(feature = "ws-server")]
|
||||||
fn input_protocol_event(input: &WorkerInput) -> protocol::Event {
|
fn input_protocol_event(input: &WorkerInput) -> protocol::Event {
|
||||||
match input.kind {
|
match input.kind {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user