runtime: remove fake companion responses
This commit is contained in:
parent
67df7d1a53
commit
2fb7514daa
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 [
|
||||||
|
|
|
||||||
|
|
@ -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 [
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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.
|
|
||||||
Loading…
Reference in New Issue
Block a user