runtime: remove fake companion responses

This commit is contained in:
Keisuke Hirata 2026-06-28 03:06:12 +09:00
parent 67df7d1a53
commit 2fb7514daa
No known key found for this signature in database
6 changed files with 77 additions and 343 deletions

View File

@ -960,7 +960,7 @@ mod tests {
.await; .await;
assert_eq!(response.status(), StatusCode::OK); assert_eq!(response.status(), StatusCode::OK);
let detail: RuntimeHttpWorkerResponse = read_json(response).await; let detail: RuntimeHttpWorkerResponse = read_json(response).await;
assert_eq!(detail.worker.transcript_len, 2); assert_eq!(detail.worker.transcript_len, 1);
let response = empty_request( let response = empty_request(
app.clone(), app.clone(),
@ -999,7 +999,7 @@ mod tests {
assert_eq!(response.status(), StatusCode::OK); assert_eq!(response.status(), StatusCode::OK);
let workers: RuntimeHttpWorkersResponse = read_json(response).await; let workers: RuntimeHttpWorkersResponse = read_json(response).await;
assert_eq!(workers.workers.len(), 1); assert_eq!(workers.workers.len(), 1);
assert_eq!(workers.workers[0].transcript_len, 2); assert_eq!(workers.workers[0].transcript_len, 1);
let response = empty_request(app, Method::GET, "/v1/runtime").await; let response = empty_request(app, Method::GET, "/v1/runtime").await;
assert_eq!(response.status(), StatusCode::OK); assert_eq!(response.status(), StatusCode::OK);

View File

@ -33,10 +33,6 @@ use tokio::sync::broadcast;
static NEXT_RUNTIME_SEQUENCE: AtomicU64 = AtomicU64::new(1); static NEXT_RUNTIME_SEQUENCE: AtomicU64 = AtomicU64::new(1);
fn providerless_embedded_response_text() -> &'static str {
"Embedded worker-runtime accepted the message. LLM execution is not connected for this worker yet."
}
/// Concrete embedded Runtime domain entity. /// Concrete embedded Runtime domain entity.
/// ///
/// The default implementation is memory-backed and tools/provider-less by /// The default implementation is memory-backed and tools/provider-less by
@ -319,36 +315,10 @@ impl Runtime {
}; };
state.push_worker_observation_event(worker_ref.clone(), payload); state.push_worker_observation_event(worker_ref.clone(), payload);
} }
let assistant_transcript_sequence = if matches!(role, TranscriptRole::User) {
let assistant_text = providerless_embedded_response_text().to_string();
let worker = state.worker_mut(worker_ref)?;
let assistant_sequence = worker.next_transcript_sequence;
worker.next_transcript_sequence += 1;
worker.transcript.push(TranscriptEntry {
sequence: assistant_sequence,
worker_ref: worker_ref.clone(),
role: TranscriptRole::Assistant,
content: assistant_text.clone(),
event_id,
});
#[cfg(feature = "ws-server")]
state.push_worker_observation_event(
worker_ref.clone(),
protocol::Event::TextDone {
text: assistant_text,
},
);
Some(assistant_sequence)
} else {
None
};
state.persist_runtime_snapshot()?; state.persist_runtime_snapshot()?;
state.persist_worker(&worker_ref.worker_id)?; state.persist_worker(&worker_ref.worker_id)?;
state.persist_event_by_id(event_id)?; state.persist_event_by_id(event_id)?;
state.persist_transcript_entry(&worker_ref.worker_id, transcript_sequence)?; state.persist_transcript_entry(&worker_ref.worker_id, transcript_sequence)?;
if let Some(sequence) = assistant_transcript_sequence {
state.persist_transcript_entry(&worker_ref.worker_id, sequence)?;
}
Ok(WorkerInteractionAck { Ok(WorkerInteractionAck {
worker_ref: worker_ref.clone(), worker_ref: worker_ref.clone(),
@ -1412,10 +1382,10 @@ mod tests {
let projection = runtime let projection = runtime
.transcript_projection(&detail.worker_ref, TranscriptQuery::new(0, 2)) .transcript_projection(&detail.worker_ref, TranscriptQuery::new(0, 2))
.unwrap(); .unwrap();
assert_eq!(projection.total_items, 5); assert_eq!(projection.total_items, 3);
assert_eq!(projection.items.len(), 2); assert_eq!(projection.items.len(), 2);
assert_eq!(projection.items[0].content, "hello"); assert_eq!(projection.items[0].content, "hello");
assert_eq!(projection.items[1].role, TranscriptRole::Assistant); assert_eq!(projection.items[1].role, TranscriptRole::System);
assert_eq!(projection.next_start, Some(2)); assert_eq!(projection.next_start, Some(2));
let err = runtime let err = runtime

View File

@ -1,19 +1,15 @@
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use chrono::Utc;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use worker_runtime::catalog::{CapabilityRequest, ProfileSelector}; use worker_runtime::catalog::{CapabilityRequest, ProfileSelector};
use crate::hosts::{ use crate::hosts::{
DiagnosticSeverity, RuntimeDiagnostic, RuntimeRegistry, WorkerInputKind, WorkerInputRequest, DiagnosticSeverity, RuntimeDiagnostic, RuntimeRegistry, WorkerOperationState,
WorkerOperationState, WorkerSpawnAcceptanceRequirement, WorkerSpawnIntent, WorkerSpawnRequest, WorkerSpawnAcceptanceRequirement, WorkerSpawnIntent, WorkerSpawnRequest, WorkerSummary,
WorkerSummary,
}; };
const COMPANION_RUNTIME_ID: &str = "embedded-worker-runtime"; const COMPANION_RUNTIME_ID: &str = "embedded-worker-runtime";
const MAX_MESSAGE_CHARS: usize = 8_000; const MAX_MESSAGE_CHARS: usize = 8_000;
const PROVIDERLESS_RESPONSE: &str =
include_str!("../../../resources/prompts/worker/web_companion_providerless.md");
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
@ -103,7 +99,6 @@ struct CompanionWorkerState {
} }
pub struct CompanionConsole { pub struct CompanionConsole {
runtime: Arc<RuntimeRegistry>,
worker: Mutex<CompanionWorkerState>, worker: Mutex<CompanionWorkerState>,
transcript: Mutex<CompanionTranscript>, transcript: Mutex<CompanionTranscript>,
} }
@ -112,7 +107,6 @@ impl CompanionConsole {
pub fn new(runtime: Arc<RuntimeRegistry>) -> Self { pub fn new(runtime: Arc<RuntimeRegistry>) -> Self {
let initial = spawn_companion_worker(&runtime); let initial = spawn_companion_worker(&runtime);
Self { Self {
runtime,
worker: Mutex::new(initial), worker: Mutex::new(initial),
transcript: Mutex::new(CompanionTranscript::default()), transcript: Mutex::new(CompanionTranscript::default()),
} }
@ -125,7 +119,7 @@ impl CompanionConsole {
return CompanionStatusResponse { return CompanionStatusResponse {
state: CompanionState::Error, state: CompanionState::Error,
worker: None, worker: None,
transport: providerless_transport(), transport: companion_transport(),
diagnostics: vec![diagnostic( diagnostics: vec![diagnostic(
"companion_state_unavailable", "companion_state_unavailable",
DiagnosticSeverity::Error, DiagnosticSeverity::Error,
@ -137,7 +131,7 @@ impl CompanionConsole {
CompanionStatusResponse { CompanionStatusResponse {
state: worker.state, state: worker.state,
worker: worker.worker.clone(), worker: worker.worker.clone(),
transport: providerless_transport(), transport: companion_transport(),
diagnostics: worker.diagnostics.clone(), diagnostics: worker.diagnostics.clone(),
} }
} }
@ -181,125 +175,18 @@ impl CompanionConsole {
)); ));
} }
let mut transcript = match self.transcript.try_lock() { self.rejected_message_response(diagnostic(
Ok(transcript) => transcript, "companion_llm_not_connected",
Err(std::sync::TryLockError::WouldBlock) => {
return self.busy_message_response();
}
Err(std::sync::TryLockError::Poisoned(_)) => {
return self.error_message_response(diagnostic(
"companion_transcript_unavailable",
DiagnosticSeverity::Error, DiagnosticSeverity::Error,
"Companion transcript is unavailable", "Workspace Companion input is disabled until it is connected to actual Worker/LLM execution",
)); ))
}
};
let (worker, mut diagnostics) = match self.current_worker() {
Ok((Some(worker), diagnostics)) => (worker, diagnostics),
Ok((None, diagnostics)) => {
return response_from_locked_transcript(
&transcript,
CompanionState::Error,
None,
None,
None,
0,
200,
diagnostics,
);
}
Err(diagnostic) => {
return response_from_locked_transcript(
&transcript,
CompanionState::Error,
None,
None,
None,
0,
200,
vec![diagnostic],
);
}
};
let user_item = transcript.push("user", content.clone(), "browser_request", "accepted");
match self.runtime.send_input(
&worker.runtime_id,
&worker.worker_id,
WorkerInputRequest {
kind: WorkerInputKind::User,
content,
},
) {
Ok(result) if result.state == WorkerOperationState::Accepted => {
diagnostics.extend(result.diagnostics);
}
Ok(result) => {
diagnostics.extend(result.diagnostics);
diagnostics.push(diagnostic(
"companion_runtime_input_rejected",
DiagnosticSeverity::Error,
"Embedded Companion Worker rejected the browser message",
));
return response_from_locked_transcript(
&transcript,
CompanionState::Error,
Some(worker),
Some(user_item),
None,
0,
200,
diagnostics,
);
}
Err(error) => {
diagnostics.push(diagnostic(
"companion_runtime_input_failed",
DiagnosticSeverity::Error,
format!("Embedded Companion Worker input failed: {error:?}"),
));
return response_from_locked_transcript(
&transcript,
CompanionState::Error,
Some(worker),
Some(user_item),
None,
0,
200,
diagnostics,
);
}
}
diagnostics.push(diagnostic(
"companion_providerless_boundary",
DiagnosticSeverity::Info,
"Real LLM completion is not connected in this MVP; response is the backend provider-less boundary text",
));
let assistant_item = transcript.push(
"assistant",
providerless_response_text(),
"backend_providerless_boundary",
"complete",
);
response_from_locked_transcript(
&transcript,
CompanionState::Accepted,
Some(worker),
Some(user_item),
Some(assistant_item),
0,
200,
diagnostics,
)
} }
pub fn cancel(&self, _request: CompanionCancelRequest) -> CompanionMessageResponse { pub fn cancel(&self, _request: CompanionCancelRequest) -> CompanionMessageResponse {
let diagnostics = vec![diagnostic( let diagnostics = vec![diagnostic(
"companion_cancel_no_active_run", "companion_cancel_no_active_run",
DiagnosticSeverity::Info, DiagnosticSeverity::Info,
"Provider-less Companion Console has no active generation to cancel", "Workspace Companion has no active generation to cancel",
)]; )];
match self.transcript.lock() { match self.transcript.lock() {
Ok(transcript) => response_from_locked_transcript( Ok(transcript) => response_from_locked_transcript(
@ -335,19 +222,6 @@ impl CompanionConsole {
} }
} }
fn current_worker(
&self,
) -> Result<(Option<WorkerSummary>, Vec<RuntimeDiagnostic>), RuntimeDiagnostic> {
let worker = self.worker.lock().map_err(|_| {
diagnostic(
"companion_state_unavailable",
DiagnosticSeverity::Error,
"Companion state is unavailable",
)
})?;
Ok((worker.worker.clone(), worker.diagnostics.clone()))
}
fn rejected_message_response(&self, diagnostic: RuntimeDiagnostic) -> CompanionMessageResponse { fn rejected_message_response(&self, diagnostic: RuntimeDiagnostic) -> CompanionMessageResponse {
match self.transcript.lock() { match self.transcript.lock() {
Ok(transcript) => response_from_locked_transcript( Ok(transcript) => response_from_locked_transcript(
@ -378,83 +252,6 @@ impl CompanionConsole {
}, },
} }
} }
fn busy_message_response(&self) -> CompanionMessageResponse {
let diagnostic = diagnostic(
"companion_busy",
DiagnosticSeverity::Warning,
"Companion Console is already processing a message",
);
match self.transcript.lock() {
Ok(transcript) => response_from_locked_transcript(
&transcript,
CompanionState::Busy,
self.status().worker,
None,
None,
0,
200,
vec![diagnostic],
),
Err(_) => CompanionMessageResponse {
state: CompanionState::Busy,
worker: self.status().worker,
user_item: None,
assistant_item: None,
transcript: CompanionTranscriptProjection {
state: CompanionState::Busy,
start: 0,
limit: 200,
total_items: 0,
next_start: None,
items: Vec::new(),
diagnostics: vec![diagnostic.clone()],
},
diagnostics: vec![diagnostic],
},
}
}
fn error_message_response(&self, diagnostic: RuntimeDiagnostic) -> CompanionMessageResponse {
CompanionMessageResponse {
state: CompanionState::Error,
worker: self.status().worker,
user_item: None,
assistant_item: None,
transcript: CompanionTranscriptProjection {
state: CompanionState::Error,
start: 0,
limit: 200,
total_items: 0,
next_start: None,
items: Vec::new(),
diagnostics: vec![diagnostic.clone()],
},
diagnostics: vec![diagnostic],
}
}
}
impl CompanionTranscript {
fn push(
&mut self,
role: impl Into<String>,
content: impl Into<String>,
source: impl Into<String>,
status: impl Into<String>,
) -> CompanionTranscriptItem {
self.next_sequence = self.next_sequence.saturating_add(1);
let item = CompanionTranscriptItem {
sequence: self.next_sequence,
role: role.into(),
content: content.into(),
created_at: Utc::now().to_rfc3339(),
source: source.into(),
status: status.into(),
};
self.items.push(item.clone());
item
}
} }
fn spawn_companion_worker(runtime: &RuntimeRegistry) -> CompanionWorkerState { fn spawn_companion_worker(runtime: &RuntimeRegistry) -> CompanionWorkerState {
@ -537,15 +334,11 @@ fn project_transcript(
} }
} }
fn providerless_response_text() -> String { fn companion_transport() -> CompanionTransportSummary {
PROVIDERLESS_RESPONSE.trim().to_string()
}
fn providerless_transport() -> CompanionTransportSummary {
CompanionTransportSummary { CompanionTransportSummary {
kind: "providerless_backend_internal".to_string(), kind: "embedded_worker_runtime".to_string(),
completion: "synchronous_request_response".to_string(), completion: "not_connected".to_string(),
limitation: "No provider-backed LLM generation is wired in this MVP; browser messages are recorded by a backend-internal tools-less Companion Worker and receive a resource-defined boundary response.".to_string(), limitation: "Workspace Companion is visible as an embedded Worker, but browser input is disabled until actual Worker/LLM execution is connected.".to_string(),
} }
} }
@ -567,7 +360,7 @@ mod tests {
use crate::hosts::{EmbeddedWorkerRuntime, RuntimeRegistry}; use crate::hosts::{EmbeddedWorkerRuntime, RuntimeRegistry};
#[test] #[test]
fn companion_spawns_visible_worker_and_records_providerless_turn() { fn companion_spawns_visible_worker_without_fake_turn() {
let registry = let registry =
RuntimeRegistry::for_workspace(EmbeddedWorkerRuntime::new_memory("local:test")); RuntimeRegistry::for_workspace(EmbeddedWorkerRuntime::new_memory("local:test"));
let registry = Arc::new(registry); let registry = Arc::new(registry);
@ -591,22 +384,19 @@ mod tests {
let response = companion.send_message(CompanionMessageRequest { let response = companion.send_message(CompanionMessageRequest {
content: "hello".to_string(), content: "hello".to_string(),
}); });
assert_eq!(response.state, CompanionState::Accepted); assert_eq!(response.state, CompanionState::Rejected);
assert_eq!(response.transcript.items.len(), 2); assert!(response.transcript.items.is_empty());
assert_eq!(response.transcript.items[0].role, "user");
assert_eq!(response.transcript.items[1].role, "assistant");
assert!( assert!(
response.transcript.items[1] response
.content .diagnostics
.contains("provider-less") .iter()
.any(|diagnostic| diagnostic.code == "companion_llm_not_connected")
); );
let runtime_transcript = registry let runtime_transcript = registry
.transcript(COMPANION_RUNTIME_ID, &worker.worker_id, 0, 10) .transcript(COMPANION_RUNTIME_ID, &worker.worker_id, 0, 10)
.unwrap(); .unwrap();
assert_eq!(runtime_transcript.items.len(), 2); assert!(runtime_transcript.items.is_empty());
assert_eq!(runtime_transcript.items[0].role, "user");
assert_eq!(runtime_transcript.items[1].role, "assistant");
let browser_payload = serde_json::to_string(&(status, response)).unwrap(); let browser_payload = serde_json::to_string(&(status, response)).unwrap();
for forbidden in [ for forbidden in [

View File

@ -957,7 +957,7 @@ impl EmbeddedWorkerRuntime {
display_hint: "backend-internal worker-runtime Worker".to_string(), display_hint: "backend-internal worker-runtime Worker".to_string(),
}, },
capabilities: WorkerCapabilitySummary { capabilities: WorkerCapabilitySummary {
can_accept_input: true, can_accept_input: false,
can_stop: false, can_stop: false,
can_spawn_followup: false, can_spawn_followup: false,
}, },
@ -989,7 +989,7 @@ impl EmbeddedWorkerRuntime {
display_hint: "backend-internal worker-runtime Worker".to_string(), display_hint: "backend-internal worker-runtime Worker".to_string(),
}, },
capabilities: WorkerCapabilitySummary { capabilities: WorkerCapabilitySummary {
can_accept_input: true, can_accept_input: false,
can_stop: false, can_stop: false,
can_spawn_followup: false, can_spawn_followup: false,
}, },
@ -1255,40 +1255,17 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
)) ))
} }
fn send_input(&self, worker_id: &str, request: WorkerInputRequest) -> WorkerInputResult { fn send_input(&self, worker_id: &str, _request: WorkerInputRequest) -> WorkerInputResult {
let Some(worker_ref) = self.worker_ref(worker_id) else { embedded_input_rejected(
return embedded_input_rejected(
&self.runtime_id, &self.runtime_id,
worker_id, worker_id,
diagnostic( diagnostic(
"embedded_worker_id_invalid", "embedded_worker_llm_not_connected",
DiagnosticSeverity::Warning, DiagnosticSeverity::Error,
"Worker id was empty and cannot be resolved".to_string(), "Embedded Worker input is disabled until actual Worker/LLM execution is connected"
.to_string(),
), ),
); )
};
let input = EmbeddedWorkerInput {
kind: match request.kind {
WorkerInputKind::User => EmbeddedWorkerInputKind::User,
WorkerInputKind::System => EmbeddedWorkerInputKind::System,
},
content: request.content,
};
match self.runtime.send_input(&worker_ref, input) {
Ok(ack) => WorkerInputResult {
state: WorkerOperationState::Accepted,
runtime_id: self.runtime_id.clone(),
worker_id: worker_id.to_string(),
transcript_sequence: Some(ack.transcript_sequence),
event_id: Some(ack.event_id),
diagnostics: Vec::new(),
},
Err(err) => embedded_input_rejected(
&self.runtime_id,
worker_id,
embedded_runtime_diagnostic(&err),
),
}
} }
fn transcript( fn transcript(
@ -1878,7 +1855,7 @@ fn embedded_runtime_capabilities(limit: usize, available: bool) -> RuntimeCapabi
can_get_worker: available, can_get_worker: available,
can_spawn_worker: available, can_spawn_worker: available,
can_stop_worker: false, can_stop_worker: false,
can_accept_input: available, can_accept_input: false,
has_workspace_fs: false, has_workspace_fs: false,
has_shell: false, has_shell: false,
has_git: false, has_git: false,
@ -2611,7 +2588,7 @@ mod tests {
); );
assert_eq!(embedded_summary.source.status, RuntimeSourceStatus::Active); assert_eq!(embedded_summary.source.status, RuntimeSourceStatus::Active);
assert!(embedded_summary.capabilities.can_spawn_worker); assert!(embedded_summary.capabilities.can_spawn_worker);
assert!(embedded_summary.capabilities.can_accept_input); assert!(!embedded_summary.capabilities.can_accept_input);
let spawned = registry let spawned = registry
.spawn_worker( .spawn_worker(
@ -2644,7 +2621,7 @@ mod tests {
assert_eq!(worker.workspace.identity, "runtime_registry_worker"); assert_eq!(worker.workspace.identity, "runtime_registry_worker");
assert_eq!(worker.implementation.kind, "embedded_worker_runtime"); assert_eq!(worker.implementation.kind, "embedded_worker_runtime");
assert_eq!(worker.profile.as_deref(), Some("builtin:coder")); assert_eq!(worker.profile.as_deref(), Some("builtin:coder"));
assert!(worker.capabilities.can_accept_input); assert!(!worker.capabilities.can_accept_input);
let input = registry let input = registry
.send_input( .send_input(
@ -2656,24 +2633,21 @@ mod tests {
}, },
) )
.unwrap(); .unwrap();
assert_eq!(input.state, WorkerOperationState::Accepted); assert_eq!(input.state, WorkerOperationState::Rejected);
assert_eq!(input.runtime_id, EMBEDDED_RUNTIME_ID); assert_eq!(input.runtime_id, EMBEDDED_RUNTIME_ID);
assert_eq!(input.worker_id, worker.worker_id); assert_eq!(input.worker_id, worker.worker_id);
assert_eq!(input.transcript_sequence, Some(1)); assert!(
input
.diagnostics
.iter()
.any(|diagnostic| diagnostic.code == "embedded_worker_llm_not_connected")
);
let transcript = registry let transcript = registry
.transcript(EMBEDDED_RUNTIME_ID, &worker.worker_id, 0, 10) .transcript(EMBEDDED_RUNTIME_ID, &worker.worker_id, 0, 10)
.unwrap(); .unwrap();
assert_eq!(transcript.state, WorkerOperationState::Accepted); assert_eq!(transcript.state, WorkerOperationState::Accepted);
assert_eq!(transcript.items.len(), 2); assert!(transcript.items.is_empty());
assert_eq!(transcript.items[0].role, "user");
assert_eq!(transcript.items[0].content, "hello embedded runtime");
assert_eq!(transcript.items[1].role, "assistant");
assert!(
transcript.items[1]
.content
.contains("LLM execution is not connected")
);
let json = serde_json::to_string(&(embedded_summary, worker, transcript)).unwrap(); let json = serde_json::to_string(&(embedded_summary, worker, transcript)).unwrap();
for forbidden in [ for forbidden in [

View File

@ -327,16 +327,16 @@ async fn get_workspace(State(api): State<WorkspaceApi>) -> ApiResult<Json<Worksp
extension_points: ExtensionPoints { extension_points: ExtensionPoints {
store: "sqlite".to_string(), store: "sqlite".to_string(),
event_stream: ExtensionPointState { event_stream: ExtensionPointState {
status: "reserved".to_string(), status: "backend_proxy".to_string(),
note: "No browser-to-Worker socket path is exposed in this bootstrap; any future stream must be a Workspace server proxy that resolves Worker identity and enforces method allow/block boundaries.".to_string(), note: "Worker observation streams are exposed only through the Workspace server proxy keyed by runtime_id + worker_id; browser clients never receive raw Runtime endpoints or socket paths.".to_string(),
}, },
host_worker_bridge: ExtensionPointState { host_worker_bridge: ExtensionPointState {
status: "read_only_local".to_string(), status: "runtime_registry".to_string(),
note: "Local Hosts and Workers are exposed as a read-only bridge over existing Worker metadata; no direct Worker socket, scheduling, or lifecycle control is implemented.".to_string(), note: "Hosts and Workers are projected from the Workspace RuntimeRegistry; raw Runtime endpoints, sockets, and local metadata paths are not exposed.".to_string(),
}, },
companion_console: ExtensionPointState { companion_console: ExtensionPointState {
status: "providerless_mvp".to_string(), status: "not_connected".to_string(),
note: "Backend-internal tools-less Companion Worker is available through Workspace API status/transcript/message endpoints; v0 records browser messages and returns a resource-defined provider-less response instead of direct LLM generation.".to_string(), note: "Workspace Companion is visible as an embedded Worker, but browser input is disabled until actual Worker/LLM execution is connected.".to_string(),
}, },
}, },
})) }))
@ -1051,7 +1051,7 @@ mod tests {
assert_eq!(workspace["record_authority"], "local_yoi_project_records"); assert_eq!(workspace["record_authority"], "local_yoi_project_records");
assert_eq!( assert_eq!(
workspace["extension_points"]["host_worker_bridge"]["status"], workspace["extension_points"]["host_worker_bridge"]["status"],
"read_only_local" "runtime_registry"
); );
let tickets = get_json(app.clone(), "/api/tickets").await; let tickets = get_json(app.clone(), "/api/tickets").await;
@ -1141,7 +1141,7 @@ mod tests {
assert_eq!(companion_status["worker"]["role"], "workspace_companion"); assert_eq!(companion_status["worker"]["role"], "workspace_companion");
assert_eq!( assert_eq!(
companion_status["transport"]["kind"], companion_status["transport"]["kind"],
"providerless_backend_internal" "embedded_worker_runtime"
); );
assert!(!companion_status.to_string().contains("/workspace/demo")); assert!(!companion_status.to_string().contains("/workspace/demo"));
@ -1151,23 +1151,24 @@ mod tests {
json!({ "content": "hello companion" }), json!({ "content": "hello companion" }),
) )
.await; .await;
assert_eq!(companion_message["state"], "accepted"); assert_eq!(companion_message["state"], "rejected");
assert_eq!(companion_message["transcript"]["items"][0]["role"], "user"); assert!(
assert_eq!( companion_message["transcript"]["items"]
companion_message["transcript"]["items"][1]["role"], .as_array()
"assistant" .unwrap()
.is_empty()
); );
assert!( assert!(
companion_message["transcript"]["items"][1]["content"] companion_message["diagnostics"]
.as_str() .as_array()
.unwrap() .unwrap()
.contains("provider-less") .iter()
.any(|diagnostic| diagnostic["code"] == "companion_llm_not_connected")
); );
assert!(!companion_message.to_string().contains("/workspace/demo")); assert!(!companion_message.to_string().contains("/workspace/demo"));
let companion_transcript = get_json(app.clone(), "/api/companion/transcript").await; let companion_transcript = get_json(app.clone(), "/api/companion/transcript").await;
assert_eq!(companion_transcript["total_items"], 2); assert_eq!(companion_transcript["total_items"], 0);
assert_eq!(companion_transcript["items"][1]["role"], "assistant");
let host_workers = get_json(app.clone(), &format!("/api/hosts/{host_id}/workers")).await; let host_workers = get_json(app.clone(), &format!("/api/hosts/{host_id}/workers")).await;
assert!( assert!(
@ -1328,10 +1329,16 @@ mod tests {
}), }),
) )
.await; .await;
assert_eq!(accepted["state"], "accepted"); assert_eq!(accepted["state"], "rejected");
assert_eq!(accepted["runtime_id"], "embedded-worker-runtime"); assert_eq!(accepted["runtime_id"], "embedded-worker-runtime");
assert_eq!(accepted["worker_id"], worker_id); assert_eq!(accepted["worker_id"], worker_id);
assert_eq!(accepted["transcript_sequence"], 1); assert!(
accepted["diagnostics"]
.as_array()
.unwrap()
.iter()
.any(|diagnostic| diagnostic["code"] == "embedded_worker_llm_not_connected")
);
let transcript = get_json( let transcript = get_json(
app.clone(), app.clone(),
@ -1339,11 +1346,7 @@ mod tests {
) )
.await; .await;
assert_eq!(transcript["state"], "accepted"); assert_eq!(transcript["state"], "accepted");
assert_eq!(transcript["items"][0]["role"], "user"); assert!(transcript["items"].as_array().unwrap().is_empty());
assert_eq!(
transcript["items"][0]["content"],
"hello from browser-facing api"
);
let wrong_runtime = app let wrong_runtime = app
.clone() .clone()

View File

@ -1,3 +0,0 @@
You are connected to the Yoi Workspace Web Console MVP provider-less boundary.
I received your browser message through the backend-internal Companion Worker, but this MVP does not yet run a provider-backed LLM completion from the workspace server. The transcript/status/send path is active, tools-less, and scoped to conversation projection only. A later integration can replace this resource-defined boundary response with real Worker engine output without giving the browser runtime credentials, sockets, session paths, or filesystem authority.