Compare commits
4 Commits
a8236ff3b4
...
44deacb3f4
| Author | SHA1 | Date | |
|---|---|---|---|
| 44deacb3f4 | |||
| fe2a39c12d | |||
| aa542b38d9 | |||
| c1ee1ff9e9 |
|
|
@ -1,12 +0,0 @@
|
|||
target/
|
||||
.git/
|
||||
.yoi/
|
||||
.direnv/
|
||||
result
|
||||
result-*
|
||||
.env
|
||||
.env.*
|
||||
**/node_modules/
|
||||
web/workspace/build/
|
||||
web/workspace/.svelte-kit/
|
||||
web/workspace/.vite/
|
||||
88
Dockerfile
88
Dockerfile
|
|
@ -1,88 +0,0 @@
|
|||
# syntax=docker/dockerfile:1.7
|
||||
|
||||
ARG RUST_VERSION=1.95.0
|
||||
ARG DEBIAN_VERSION=bookworm
|
||||
ARG DENO_VERSION=2.7.14
|
||||
ARG NGINX_VERSION=1.27-alpine
|
||||
|
||||
FROM rust:${RUST_VERSION}-${DEBIAN_VERSION} AS rust-source
|
||||
WORKDIR /src
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
git \
|
||||
libssl-dev \
|
||||
pkg-config \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY Cargo.toml Cargo.lock ./
|
||||
COPY crates ./crates
|
||||
COPY resources ./resources
|
||||
COPY tests ./tests
|
||||
|
||||
FROM rust-source AS runtime-builder
|
||||
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||
--mount=type=cache,target=/usr/local/cargo/git \
|
||||
--mount=type=cache,target=/src/target \
|
||||
cargo build --release -p worker-runtime --bin worker-runtime-rest-server --features ws-server,fs-store \
|
||||
&& mkdir -p /out \
|
||||
&& cp /src/target/release/worker-runtime-rest-server /out/worker-runtime-rest-server
|
||||
|
||||
FROM rust-source AS server-builder
|
||||
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||
--mount=type=cache,target=/usr/local/cargo/git \
|
||||
--mount=type=cache,target=/src/target \
|
||||
cargo build --release -p yoi-workspace-server --bin yoi-workspace-server \
|
||||
&& mkdir -p /out \
|
||||
&& cp /src/target/release/yoi-workspace-server /out/yoi-workspace-server
|
||||
|
||||
FROM denoland/deno:${DENO_VERSION} AS web-builder
|
||||
WORKDIR /src/web/workspace
|
||||
COPY web/workspace ./
|
||||
RUN deno task build
|
||||
|
||||
FROM debian:${DEBIAN_VERSION}-slim AS runtime
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
bash \
|
||||
ca-certificates \
|
||||
git \
|
||||
libssl3 \
|
||||
openssh-client \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& useradd --system --uid 10001 --gid root --home-dir /runtime-data --shell /usr/sbin/nologin yoi \
|
||||
&& mkdir -p /runtime-data /workdirs \
|
||||
&& chown -R yoi:root /runtime-data /workdirs
|
||||
|
||||
COPY --from=runtime-builder /out/worker-runtime-rest-server /usr/local/bin/worker-runtime-rest-server
|
||||
|
||||
USER yoi
|
||||
EXPOSE 38800
|
||||
VOLUME ["/runtime-data", "/workdirs"]
|
||||
ENTRYPOINT ["worker-runtime-rest-server"]
|
||||
CMD ["--bind", "0.0.0.0:38800", "--display-name", "Docker Runtime", "--fs-root", "/runtime-data", "--workdir-target", "/workdirs"]
|
||||
|
||||
FROM debian:${DEBIAN_VERSION}-slim AS server
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
git \
|
||||
libssl3 \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& useradd --system --uid 10002 --gid root --home-dir /server-data --shell /usr/sbin/nologin yoi \
|
||||
&& mkdir -p /server-data /workspace \
|
||||
&& chown -R yoi:root /server-data /workspace
|
||||
|
||||
COPY --from=server-builder /out/yoi-workspace-server /usr/local/bin/yoi-workspace-server
|
||||
|
||||
USER yoi
|
||||
EXPOSE 8787
|
||||
VOLUME ["/server-data", "/workspace"]
|
||||
ENTRYPOINT ["yoi-workspace-server"]
|
||||
CMD ["serve", "--workspace", "/workspace", "--db", "/server-data/workspace.db", "--listen", "0.0.0.0:8787"]
|
||||
|
||||
FROM nginx:${NGINX_VERSION} AS webui
|
||||
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY --from=web-builder /src/web/workspace/build /usr/share/nginx/html
|
||||
EXPOSE 80
|
||||
38
compose.yaml
Normal file
38
compose.yaml
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
name: yoi
|
||||
|
||||
services:
|
||||
runtime:
|
||||
image: yoi-runtime:latest
|
||||
pull_policy: never
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- "38800"
|
||||
volumes:
|
||||
- runtime-data:/runtime-data
|
||||
- runtime-workdirs:/workdirs
|
||||
|
||||
server:
|
||||
image: yoi-server:latest
|
||||
pull_policy: never
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- runtime
|
||||
expose:
|
||||
- "8787"
|
||||
volumes:
|
||||
- server-data:/server-data
|
||||
- ./docker/workspace:/workspace:ro
|
||||
|
||||
webui:
|
||||
image: yoi-webui:latest
|
||||
pull_policy: never
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- server
|
||||
ports:
|
||||
- "${YOI_WEBUI_PORT:-8080}:80"
|
||||
|
||||
volumes:
|
||||
runtime-data:
|
||||
runtime-workdirs:
|
||||
server-data:
|
||||
|
|
@ -123,8 +123,6 @@ pub struct BackendWorkingDirectorySummary {
|
|||
pub cleanliness: Option<String>,
|
||||
#[serde(default)]
|
||||
pub primary_worker_id: Option<String>,
|
||||
#[serde(default)]
|
||||
pub management_kind: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
|
||||
|
|
|
|||
|
|
@ -156,10 +156,6 @@ pub struct WorkingDirectorySummary {
|
|||
pub cleanliness: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub primary_worker_id: Option<WorkerId>,
|
||||
/// Backend projection metadata. Runtimes leave this absent; Workspace Browser
|
||||
/// APIs fill it with `backend_managed` or `runtime_unmanaged`.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub management_kind: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
|
|
|||
|
|
@ -48,7 +48,6 @@ impl WorkingDirectory {
|
|||
status: self.status.clone(),
|
||||
cleanliness: None,
|
||||
primary_worker_id: None,
|
||||
management_kind: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -222,7 +221,6 @@ impl LocalGitWorktreeMaterializer {
|
|||
status: WorkingDirectoryStatusKind::Corrupted,
|
||||
cleanliness: Some("unknown".to_string()),
|
||||
primary_worker_id: None,
|
||||
management_kind: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,15 +34,15 @@ impl WorkspaceHttpMemoryBackend {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn execute_operation(
|
||||
pub async fn execute_operation(
|
||||
&self,
|
||||
operation: MemoryBackendOperation,
|
||||
) -> Result<MemoryBackendOperationResult, WorkspaceMemoryBackendError> {
|
||||
execute_http_memory_backend(&self.workspace_id, &self.base_url, operation)
|
||||
execute_http_memory_backend(&self.workspace_id, &self.base_url, operation).await
|
||||
}
|
||||
|
||||
fn execute(&self, operation: MemoryBackendOperation) -> Result<ToolOutput, ToolError> {
|
||||
match self.execute_operation(operation) {
|
||||
async fn execute(&self, operation: MemoryBackendOperation) -> Result<ToolOutput, ToolError> {
|
||||
match self.execute_operation(operation).await {
|
||||
Ok(MemoryBackendOperationResult::ToolOutput(output)) => Ok(tool_output(output)),
|
||||
Ok(result) => Err(ToolError::ExecutionFailed(format!(
|
||||
"unexpected memory backend result for model-visible tool: {result:?}"
|
||||
|
|
@ -70,7 +70,7 @@ pub enum WorkspaceMemoryBackendError {
|
|||
}
|
||||
|
||||
impl WorkspaceClient {
|
||||
pub fn execute_memory_backend_operation(
|
||||
pub async fn execute_memory_backend_operation(
|
||||
&self,
|
||||
operation: MemoryBackendOperation,
|
||||
) -> Result<MemoryBackendOperationResult, WorkspaceMemoryBackendError> {
|
||||
|
|
@ -78,7 +78,7 @@ impl WorkspaceClient {
|
|||
WorkspaceClient::Http {
|
||||
workspace_id,
|
||||
base_url,
|
||||
} => execute_http_memory_backend(workspace_id, base_url, operation),
|
||||
} => execute_http_memory_backend(workspace_id, base_url, operation).await,
|
||||
WorkspaceClient::Available { kind } => Err(WorkspaceMemoryBackendError::Unavailable {
|
||||
reason: format!(
|
||||
"workspace client kind `{kind}` does not expose the Backend Workspace API"
|
||||
|
|
@ -93,7 +93,7 @@ impl WorkspaceClient {
|
|||
}
|
||||
}
|
||||
|
||||
fn execute_http_memory_backend(
|
||||
async fn execute_http_memory_backend(
|
||||
workspace_id: &str,
|
||||
base_url: &str,
|
||||
operation: MemoryBackendOperation,
|
||||
|
|
@ -103,12 +103,13 @@ fn execute_http_memory_backend(
|
|||
base_url.trim_end_matches('/'),
|
||||
workspace_id
|
||||
);
|
||||
let response = reqwest::blocking::Client::new()
|
||||
let response = reqwest::Client::new()
|
||||
.post(url)
|
||||
.json(&operation)
|
||||
.send()?;
|
||||
.send()
|
||||
.await?;
|
||||
let status = response.status();
|
||||
let body = response.text()?;
|
||||
let body = response.text().await?;
|
||||
if !status.is_success() {
|
||||
return Err(WorkspaceMemoryBackendError::Http { status, body });
|
||||
}
|
||||
|
|
@ -220,7 +221,7 @@ impl Tool for WorkspaceHttpMemoryTool {
|
|||
_ctx: ToolExecutionContext,
|
||||
) -> Result<ToolOutput, ToolError> {
|
||||
let operation = (self.build)(input_json)?;
|
||||
self.backend.execute(operation)
|
||||
self.backend.execute(operation).await
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -433,6 +433,7 @@ impl Tool for StageCandidateTool {
|
|||
source_refs,
|
||||
},
|
||||
))
|
||||
.await
|
||||
.map_err(|e| ToolError::ExecutionFailed(format!("write staging failed: {e}")))?;
|
||||
let ids = match result {
|
||||
MemoryBackendOperationResult::StagingWritten(output) if output.staging_count == 1 => {
|
||||
|
|
@ -713,7 +714,7 @@ mod tests {
|
|||
#[tokio::test]
|
||||
async fn stage_candidate_writes_staging_record_with_source_evidence() {
|
||||
let (client, request_rx) = stub_memory_backend_response(
|
||||
r#"{"Ok":{"result":{"StagingWritten":{"staging_count":1,"staging_ids":["00000000-0000-7000-8000-000000000001"]}}}}"#,
|
||||
r#"{"status":"ok","result":{"kind":"staging_written","staging_count":1,"staging_ids":["00000000-0000-7000-8000-000000000001"]}}"#,
|
||||
);
|
||||
let state = SessionExploreState::new(
|
||||
SessionReferenceView::new("segment-1", vec![Item::user_message("durable decision")]),
|
||||
|
|
@ -745,7 +746,7 @@ mod tests {
|
|||
vec!["00000000-0000-7000-8000-000000000001".to_string()]
|
||||
);
|
||||
let request = request_rx.recv().unwrap();
|
||||
assert!(request.contains("\"StageCandidate\""));
|
||||
assert!(request.contains("\"operation\":\"stage_candidate\""));
|
||||
assert!(request.contains("\"kind\":\"decision\""));
|
||||
assert!(request.contains("\"id\":\"M0000\""));
|
||||
assert!(request.contains("\"evidence_id\":\"M0000\""));
|
||||
|
|
|
|||
|
|
@ -897,12 +897,17 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
|
|||
self.workspace_context.client()
|
||||
}
|
||||
|
||||
fn resident_summary_from_workspace_authority(&self) -> Result<Option<String>, WorkerError> {
|
||||
let result = self.workspace_client().execute_memory_backend_operation(
|
||||
memory::backend::MemoryBackendOperation::ResidentSummary(
|
||||
memory::backend::MemoryResidentSummaryOperation::default(),
|
||||
),
|
||||
)?;
|
||||
async fn resident_summary_from_workspace_authority(
|
||||
&self,
|
||||
) -> Result<Option<String>, WorkerError> {
|
||||
let result = self
|
||||
.workspace_client()
|
||||
.execute_memory_backend_operation(
|
||||
memory::backend::MemoryBackendOperation::ResidentSummary(
|
||||
memory::backend::MemoryResidentSummaryOperation::default(),
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
match result {
|
||||
memory::backend::MemoryBackendOperationResult::ToolOutput(output) => Ok(output.content),
|
||||
other => Err(WorkerError::FeatureInstall(format!(
|
||||
|
|
@ -1465,7 +1470,7 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
|
|||
/// Subsequent invocations are no-ops: the template field is
|
||||
/// consumed with `Option::take()`, so the materialised value
|
||||
/// persists across all later turns and compaction.
|
||||
fn ensure_system_prompt_materialized(&mut self) -> Result<(), WorkerError> {
|
||||
async fn ensure_system_prompt_materialized(&mut self) -> Result<(), WorkerError> {
|
||||
let Some(template) = self.system_prompt_template.take() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
|
@ -1498,7 +1503,7 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
|
|||
.as_ref()
|
||||
.is_some_and(|m| m.inject_summary.unwrap_or(true));
|
||||
let resident_summary: Option<String> = if inject_summary {
|
||||
match self.resident_summary_from_workspace_authority() {
|
||||
match self.resident_summary_from_workspace_authority().await {
|
||||
Ok(summary) => summary,
|
||||
Err(error) => {
|
||||
tracing::debug!(%error, "resident memory summary unavailable");
|
||||
|
|
@ -1579,7 +1584,7 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
|
|||
/// first so extract sees a stable history range).
|
||||
async fn prepare_for_run(&mut self) -> Result<(), WorkerError> {
|
||||
self.ensure_interceptor_installed();
|
||||
self.ensure_system_prompt_materialized()?;
|
||||
self.ensure_system_prompt_materialized().await?;
|
||||
self.cleanup_finished_memory_task();
|
||||
self.ensure_segment_head()?;
|
||||
if self.should_pre_run_compact() {
|
||||
|
|
@ -2897,7 +2902,8 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
|
|||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
)
|
||||
.await;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
|
|
@ -2926,7 +2932,8 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
|
|||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
)
|
||||
.await;
|
||||
return Ok(());
|
||||
}
|
||||
let result = self.run_extract_once(&memory_cfg, threshold).await;
|
||||
|
|
@ -2997,7 +3004,7 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
|
|||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
).await;
|
||||
return Ok(ExtractDecision::Skipped);
|
||||
}
|
||||
|
||||
|
|
@ -3008,18 +3015,23 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
|
|||
.history()
|
||||
.len();
|
||||
if current_history_len <= processed_history_len {
|
||||
audit.emit(
|
||||
self.workspace_client(),
|
||||
event_tx,
|
||||
memory::audit::WorkerLifecycleStatus::Skipped,
|
||||
"no_new_history_items",
|
||||
None,
|
||||
Some(memory::audit::ExtractAudit {
|
||||
history_range: Some([processed_history_len as u64, current_history_len as u64]),
|
||||
..Default::default()
|
||||
}),
|
||||
None,
|
||||
);
|
||||
audit
|
||||
.emit(
|
||||
self.workspace_client(),
|
||||
event_tx,
|
||||
memory::audit::WorkerLifecycleStatus::Skipped,
|
||||
"no_new_history_items",
|
||||
None,
|
||||
Some(memory::audit::ExtractAudit {
|
||||
history_range: Some([
|
||||
processed_history_len as u64,
|
||||
current_history_len as u64,
|
||||
]),
|
||||
..Default::default()
|
||||
}),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
return Ok(ExtractDecision::Skipped);
|
||||
}
|
||||
|
||||
|
|
@ -3031,15 +3043,17 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
|
|||
.read_all(self.session_id(), self.segment_id())?
|
||||
.len();
|
||||
if entries_now == 0 {
|
||||
audit.emit(
|
||||
self.workspace_client(),
|
||||
event_tx,
|
||||
memory::audit::WorkerLifecycleStatus::Skipped,
|
||||
"empty_segment_log",
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
audit
|
||||
.emit(
|
||||
self.workspace_client(),
|
||||
event_tx,
|
||||
memory::audit::WorkerLifecycleStatus::Skipped,
|
||||
"empty_segment_log",
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
return Ok(ExtractDecision::Skipped);
|
||||
}
|
||||
let end_entry = entries_now - 1;
|
||||
|
|
@ -3048,21 +3062,26 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
|
|||
.map(|p| p.processed_through_entry + 1)
|
||||
.unwrap_or(0);
|
||||
if start_entry > end_entry {
|
||||
audit.emit(
|
||||
self.workspace_client(),
|
||||
event_tx,
|
||||
memory::audit::WorkerLifecycleStatus::Skipped,
|
||||
"no_new_segment_entries",
|
||||
None,
|
||||
Some(memory::audit::ExtractAudit {
|
||||
session_id: Some(self.session_id().to_string()),
|
||||
segment_id: Some(self.segment_id().to_string()),
|
||||
entry_range: Some([start_entry as u64, end_entry as u64]),
|
||||
history_range: Some([processed_history_len as u64, current_history_len as u64]),
|
||||
..Default::default()
|
||||
}),
|
||||
None,
|
||||
);
|
||||
audit
|
||||
.emit(
|
||||
self.workspace_client(),
|
||||
event_tx,
|
||||
memory::audit::WorkerLifecycleStatus::Skipped,
|
||||
"no_new_segment_entries",
|
||||
None,
|
||||
Some(memory::audit::ExtractAudit {
|
||||
session_id: Some(self.session_id().to_string()),
|
||||
segment_id: Some(self.segment_id().to_string()),
|
||||
entry_range: Some([start_entry as u64, end_entry as u64]),
|
||||
history_range: Some([
|
||||
processed_history_len as u64,
|
||||
current_history_len as u64,
|
||||
]),
|
||||
..Default::default()
|
||||
}),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
return Ok(ExtractDecision::Skipped);
|
||||
}
|
||||
|
||||
|
|
@ -3073,15 +3092,19 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
|
|||
history_range: Some([processed_history_len as u64, current_history_len as u64]),
|
||||
..Default::default()
|
||||
};
|
||||
audit.emit(
|
||||
self.workspace_client(),
|
||||
event_tx,
|
||||
memory::audit::WorkerLifecycleStatus::Started,
|
||||
format!("token_threshold_reached tokens_since={tokens_since} threshold={threshold}"),
|
||||
None,
|
||||
Some(extract_audit_base.clone()),
|
||||
None,
|
||||
);
|
||||
audit
|
||||
.emit(
|
||||
self.workspace_client(),
|
||||
event_tx,
|
||||
memory::audit::WorkerLifecycleStatus::Started,
|
||||
format!(
|
||||
"token_threshold_reached tokens_since={tokens_since} threshold={threshold}"
|
||||
),
|
||||
None,
|
||||
Some(extract_audit_base.clone()),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
let items_to_extract = self.engine.as_ref().expect("worker present").history()
|
||||
[processed_history_len..current_history_len]
|
||||
|
|
@ -3094,15 +3117,17 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
|
|||
let client = match self.build_extractor_client(memory_cfg) {
|
||||
Ok(client) => client,
|
||||
Err(err) => {
|
||||
audit.emit(
|
||||
self.workspace_client(),
|
||||
event_tx,
|
||||
memory::audit::WorkerLifecycleStatus::Failed,
|
||||
format!("client_build_failed: {err}"),
|
||||
None,
|
||||
Some(extract_audit_base),
|
||||
None,
|
||||
);
|
||||
audit
|
||||
.emit(
|
||||
self.workspace_client(),
|
||||
event_tx,
|
||||
memory::audit::WorkerLifecycleStatus::Failed,
|
||||
format!("client_build_failed: {err}"),
|
||||
None,
|
||||
Some(extract_audit_base),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
return Err(err);
|
||||
}
|
||||
};
|
||||
|
|
@ -3110,15 +3135,17 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
|
|||
let extract_system_prompt = match self.prompts.memory_extract_system(memory_language) {
|
||||
Ok(prompt) => prompt,
|
||||
Err(err) => {
|
||||
audit.emit(
|
||||
self.workspace_client(),
|
||||
event_tx,
|
||||
memory::audit::WorkerLifecycleStatus::Failed,
|
||||
format!("prompt_render_failed: {err}"),
|
||||
None,
|
||||
Some(extract_audit_base),
|
||||
None,
|
||||
);
|
||||
audit
|
||||
.emit(
|
||||
self.workspace_client(),
|
||||
event_tx,
|
||||
memory::audit::WorkerLifecycleStatus::Failed,
|
||||
format!("prompt_render_failed: {err}"),
|
||||
None,
|
||||
Some(extract_audit_base),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
return Err(WorkerError::PromptCatalog(err));
|
||||
}
|
||||
};
|
||||
|
|
@ -3151,15 +3178,17 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
|
|||
.iter()
|
||||
.any(|installed| installed == name)
|
||||
}) {
|
||||
audit.emit(
|
||||
self.workspace_client(),
|
||||
event_tx,
|
||||
memory::audit::WorkerLifecycleStatus::Failed,
|
||||
"session_explore_feature_install_failed",
|
||||
None,
|
||||
Some(extract_audit_base),
|
||||
None,
|
||||
);
|
||||
audit
|
||||
.emit(
|
||||
self.workspace_client(),
|
||||
event_tx,
|
||||
memory::audit::WorkerLifecycleStatus::Failed,
|
||||
"session_explore_feature_install_failed",
|
||||
None,
|
||||
Some(extract_audit_base),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
return Err(WorkerError::FeatureInstall(
|
||||
"session-explore feature install failed".to_string(),
|
||||
));
|
||||
|
|
@ -3178,15 +3207,17 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
|
|||
Ok(result) => result.usage.as_ref().map(usage_audit_from_event),
|
||||
Err(err) => {
|
||||
let usage = err.usage.as_ref().map(usage_audit_from_event);
|
||||
audit.emit(
|
||||
self.workspace_client(),
|
||||
event_tx,
|
||||
lifecycle_status_for_worker_error(&err.source),
|
||||
format!("worker_failed: {}", err.source),
|
||||
usage,
|
||||
Some(extract_audit_base),
|
||||
None,
|
||||
);
|
||||
audit
|
||||
.emit(
|
||||
self.workspace_client(),
|
||||
event_tx,
|
||||
lifecycle_status_for_worker_error(&err.source),
|
||||
format!("worker_failed: {}", err.source),
|
||||
usage,
|
||||
Some(extract_audit_base),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
return Err(WorkerError::Engine(err.source));
|
||||
}
|
||||
};
|
||||
|
|
@ -3228,15 +3259,17 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
|
|||
} else {
|
||||
"completed_staging_written"
|
||||
};
|
||||
audit.emit(
|
||||
self.workspace_client(),
|
||||
event_tx,
|
||||
memory::audit::WorkerLifecycleStatus::Completed,
|
||||
reason,
|
||||
usage,
|
||||
Some(extract_audit),
|
||||
None,
|
||||
);
|
||||
audit
|
||||
.emit(
|
||||
self.workspace_client(),
|
||||
event_tx,
|
||||
memory::audit::WorkerLifecycleStatus::Completed,
|
||||
reason,
|
||||
usage,
|
||||
Some(extract_audit),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(ExtractDecision::Completed)
|
||||
}
|
||||
|
|
@ -3274,7 +3307,8 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
|
|||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
)
|
||||
.await;
|
||||
if reason == "consolidation_backend_operation_unavailable" {
|
||||
tracing::debug!(
|
||||
"workspace memory consolidation skipped: backend operation is unavailable"
|
||||
|
|
@ -3357,7 +3391,7 @@ impl WorkerAuditBase {
|
|||
}
|
||||
}
|
||||
|
||||
fn emit(
|
||||
async fn emit(
|
||||
&self,
|
||||
workspace_client: &WorkspaceClient,
|
||||
event_tx: Option<&broadcast::Sender<Event>>,
|
||||
|
|
@ -3379,15 +3413,15 @@ impl WorkerAuditBase {
|
|||
extract,
|
||||
consolidation,
|
||||
};
|
||||
let _ = workspace_client.execute_memory_backend_operation(
|
||||
memory::backend::MemoryBackendOperation::AppendAudit(
|
||||
let _ = workspace_client
|
||||
.execute_memory_backend_operation(memory::backend::MemoryBackendOperation::AppendAudit(
|
||||
memory::backend::MemoryAppendAuditOperation {
|
||||
event: memory::audit::AuditEvent::new(
|
||||
memory::audit::AuditPayload::WorkerLifecycle(payload),
|
||||
),
|
||||
},
|
||||
),
|
||||
);
|
||||
))
|
||||
.await;
|
||||
if should_emit_memory_worker_event(self.worker, status, &reason) {
|
||||
emit_memory_worker_event(
|
||||
event_tx,
|
||||
|
|
@ -5552,7 +5586,7 @@ mod build_summary_prompt_tests {
|
|||
)
|
||||
.unwrap();
|
||||
worker.set_system_prompt_template(template);
|
||||
worker.ensure_system_prompt_materialized().unwrap();
|
||||
worker.ensure_system_prompt_materialized().await.unwrap();
|
||||
worker.engine().get_system_prompt().unwrap().to_string()
|
||||
}
|
||||
|
||||
|
|
@ -5583,17 +5617,15 @@ mod build_summary_prompt_tests {
|
|||
let mut buffer = [0_u8; 1024];
|
||||
let _ = stream.read(&mut buffer).unwrap();
|
||||
let body = serde_json::json!({
|
||||
"Ok": {
|
||||
"result": {
|
||||
"ToolOutput": {
|
||||
"summary": if content.is_some() {
|
||||
"resident memory summary collected"
|
||||
} else {
|
||||
"resident memory summary unavailable"
|
||||
},
|
||||
"content": content,
|
||||
}
|
||||
}
|
||||
"status": "ok",
|
||||
"result": {
|
||||
"kind": "tool_output",
|
||||
"summary": if content.is_some() {
|
||||
"resident memory summary collected"
|
||||
} else {
|
||||
"resident memory summary unavailable"
|
||||
},
|
||||
"content": content,
|
||||
}
|
||||
})
|
||||
.to_string();
|
||||
|
|
|
|||
|
|
@ -1587,7 +1587,6 @@ fn create_working_directory_for_runtime(
|
|||
resolved_commit: None,
|
||||
materialization_status: "pending".to_string(),
|
||||
cleanliness: "unknown".to_string(),
|
||||
management_kind: "backend_managed".to_string(),
|
||||
created_at: now_registry_timestamp(),
|
||||
updated_at: now_registry_timestamp(),
|
||||
};
|
||||
|
|
@ -1610,12 +1609,7 @@ fn create_working_directory_for_runtime(
|
|||
result.diagnostics,
|
||||
));
|
||||
};
|
||||
let record = workdir_record_from_summary(
|
||||
&api,
|
||||
&runtime_id,
|
||||
&working_directory.summary,
|
||||
"backend_managed",
|
||||
);
|
||||
let record = workdir_record_from_summary(&api, &runtime_id, &working_directory.summary);
|
||||
api.store.upsert_workdir_registry(&record)?;
|
||||
Ok(Json(BrowserWorkingDirectoryDetailResponse {
|
||||
workspace_id: api.config.workspace_id.clone(),
|
||||
|
|
@ -1634,17 +1628,7 @@ fn working_directory_detail_for_runtime(
|
|||
.working_directory(runtime_id, working_directory_id)
|
||||
.map_err(|err| err.into_error())?;
|
||||
if let Some(working_directory) = result.working_directory {
|
||||
let management_kind = api
|
||||
.store
|
||||
.get_workdir_registry(&api.config.workspace_id, working_directory_id)?
|
||||
.map(|record| record.management_kind)
|
||||
.unwrap_or_else(|| "runtime_unmanaged".to_string());
|
||||
let record = workdir_record_from_summary(
|
||||
&api,
|
||||
runtime_id,
|
||||
&working_directory.summary,
|
||||
management_kind.as_str(),
|
||||
);
|
||||
let record = workdir_record_from_summary(&api, runtime_id, &working_directory.summary);
|
||||
api.store.upsert_workdir_registry(&record)?;
|
||||
return Ok(Json(BrowserWorkingDirectoryDetailResponse {
|
||||
workspace_id: api.config.workspace_id.clone(),
|
||||
|
|
@ -1711,17 +1695,7 @@ fn cleanup_working_directory_for_runtime(
|
|||
result.diagnostics,
|
||||
));
|
||||
};
|
||||
let management_kind = api
|
||||
.store
|
||||
.get_workdir_registry(&api.config.workspace_id, working_directory_id)?
|
||||
.map(|record| record.management_kind)
|
||||
.unwrap_or_else(|| "runtime_unmanaged".to_string());
|
||||
let record = workdir_record_from_summary(
|
||||
&api,
|
||||
runtime_id,
|
||||
&working_directory.summary,
|
||||
management_kind.as_str(),
|
||||
);
|
||||
let record = workdir_record_from_summary(&api, runtime_id, &working_directory.summary);
|
||||
api.store.upsert_workdir_registry(&record)?;
|
||||
Ok(Json(BrowserWorkingDirectoryDetailResponse {
|
||||
workspace_id: api.config.workspace_id.clone(),
|
||||
|
|
@ -3010,20 +2984,8 @@ async fn create_workspace_worker(
|
|||
WorkerRegistryDisplayNamePolicy::UseProvided,
|
||||
)?;
|
||||
if let Some(working_directory) = worker.working_directory.as_ref() {
|
||||
let management_kind = api
|
||||
.store
|
||||
.get_workdir_registry(
|
||||
&api.config.workspace_id,
|
||||
&working_directory.working_directory_id,
|
||||
)?
|
||||
.map(|existing| existing.management_kind)
|
||||
.unwrap_or_else(|| "runtime_unmanaged".to_string());
|
||||
let workdir_record = workdir_record_from_summary(
|
||||
&api,
|
||||
worker.runtime_id.as_str(),
|
||||
working_directory,
|
||||
management_kind.as_str(),
|
||||
);
|
||||
let workdir_record =
|
||||
workdir_record_from_summary(&api, worker.runtime_id.as_str(), working_directory);
|
||||
api.store.upsert_workdir_registry(&workdir_record)?;
|
||||
link_worker_to_workdir(
|
||||
&api,
|
||||
|
|
@ -3047,7 +3009,6 @@ async fn create_workspace_worker(
|
|||
&api,
|
||||
worker.runtime_id.as_str(),
|
||||
&status.summary,
|
||||
"runtime_unmanaged",
|
||||
);
|
||||
api.store.upsert_workdir_registry(&record)?;
|
||||
}
|
||||
|
|
@ -4425,7 +4386,7 @@ fn working_directory_summaries(api: &WorkspaceApi) -> ApiResult<Vec<WorkingDirec
|
|||
let _ = sync_all_runtime_workdir_observations(api);
|
||||
let records = api
|
||||
.store
|
||||
.list_managed_workdir_registry(&api.config.workspace_id, 200)?;
|
||||
.list_workdir_registry(&api.config.workspace_id, 200)?;
|
||||
Ok(records
|
||||
.iter()
|
||||
.map(workdir_summary_from_record)
|
||||
|
|
@ -4619,20 +4580,8 @@ fn sync_worker_observation(
|
|||
WorkerRegistryDisplayNamePolicy::PreserveExisting,
|
||||
)?;
|
||||
if let Some(working_directory) = worker.working_directory.as_ref() {
|
||||
let management_kind = api
|
||||
.store
|
||||
.get_workdir_registry(
|
||||
&api.config.workspace_id,
|
||||
&working_directory.working_directory_id,
|
||||
)?
|
||||
.map(|existing| existing.management_kind)
|
||||
.unwrap_or_else(|| "runtime_unmanaged".to_string());
|
||||
let workdir_record = workdir_record_from_summary(
|
||||
api,
|
||||
worker.runtime_id.as_str(),
|
||||
working_directory,
|
||||
management_kind.as_str(),
|
||||
);
|
||||
let workdir_record =
|
||||
workdir_record_from_summary(api, worker.runtime_id.as_str(), working_directory);
|
||||
api.store.upsert_workdir_registry(&workdir_record)?;
|
||||
link_worker_to_workdir(api, &record, &working_directory.working_directory_id)?;
|
||||
}
|
||||
|
|
@ -4663,7 +4612,6 @@ fn upsert_pending_backend_workdir(
|
|||
resolved_commit: None,
|
||||
materialization_status: "pending".to_string(),
|
||||
cleanliness: "unknown".to_string(),
|
||||
management_kind: "backend_managed".to_string(),
|
||||
created_at: timestamp.clone(),
|
||||
updated_at: timestamp,
|
||||
})?;
|
||||
|
|
@ -4692,12 +4640,7 @@ fn sync_runtime_workdir_observations(
|
|||
&api.config.workspace_id,
|
||||
&status.summary.working_directory_id,
|
||||
)?;
|
||||
let management_kind = existing
|
||||
.as_ref()
|
||||
.map(|existing| existing.management_kind.clone())
|
||||
.unwrap_or_else(|| "runtime_unmanaged".to_string());
|
||||
let mut record =
|
||||
workdir_record_from_summary(api, runtime_id, &status.summary, management_kind.as_str());
|
||||
let mut record = workdir_record_from_summary(api, runtime_id, &status.summary);
|
||||
preserve_workdir_identity_for_corrupted_summary(&mut record, existing.as_ref());
|
||||
api.store.upsert_workdir_registry(&record)?;
|
||||
}
|
||||
|
|
@ -4719,13 +4662,8 @@ fn sync_runtime_workdir_observations(
|
|||
record.workdir_id.as_str(),
|
||||
)?;
|
||||
} else {
|
||||
let management_kind = record.management_kind.clone();
|
||||
let mut updated = workdir_record_from_summary(
|
||||
api,
|
||||
runtime_id,
|
||||
&status.summary,
|
||||
management_kind.as_str(),
|
||||
);
|
||||
let mut updated =
|
||||
workdir_record_from_summary(api, runtime_id, &status.summary);
|
||||
preserve_workdir_identity_for_corrupted_summary(
|
||||
&mut updated,
|
||||
Some(&record),
|
||||
|
|
@ -4792,17 +4730,7 @@ fn sync_linked_workdir_after_worker_stop(
|
|||
.working_directory(runtime_id, link.workdir_id.as_str())
|
||||
.map_err(|err| err.into_error())?;
|
||||
if let Some(status) = result.working_directory {
|
||||
let management_kind = api
|
||||
.store
|
||||
.get_workdir_registry(&api.config.workspace_id, link.workdir_id.as_str())?
|
||||
.map(|record| record.management_kind)
|
||||
.unwrap_or_else(|| "runtime_unmanaged".to_string());
|
||||
let record = workdir_record_from_summary(
|
||||
api,
|
||||
runtime_id,
|
||||
&status.summary,
|
||||
management_kind.as_str(),
|
||||
);
|
||||
let record = workdir_record_from_summary(api, runtime_id, &status.summary);
|
||||
api.store.upsert_workdir_registry(&record)?;
|
||||
} else if let Some(mut record) = api
|
||||
.store
|
||||
|
|
@ -4822,7 +4750,6 @@ fn workdir_record_from_summary(
|
|||
api: &WorkspaceApi,
|
||||
runtime_id: &str,
|
||||
summary: &WorkingDirectorySummary,
|
||||
management_kind: &str,
|
||||
) -> WorkdirRegistryRecord {
|
||||
let timestamp = now_registry_timestamp();
|
||||
WorkdirRegistryRecord {
|
||||
|
|
@ -4844,7 +4771,6 @@ fn workdir_record_from_summary(
|
|||
.cleanliness
|
||||
.clone()
|
||||
.unwrap_or_else(|| "unknown".to_string()),
|
||||
management_kind: management_kind.to_string(),
|
||||
created_at: timestamp.clone(),
|
||||
updated_at: timestamp,
|
||||
}
|
||||
|
|
@ -4895,7 +4821,6 @@ fn workdir_summary_from_record(record: &WorkdirRegistryRecord) -> WorkingDirecto
|
|||
status,
|
||||
cleanliness: Some(record.cleanliness.clone()),
|
||||
primary_worker_id: None,
|
||||
management_kind: Some(record.management_kind.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -5517,7 +5442,6 @@ mod tests {
|
|||
resolved_commit: Some("abcdef".to_string()),
|
||||
materialization_status: "missing".to_string(),
|
||||
cleanliness: "clean".to_string(),
|
||||
management_kind: "backend_managed".to_string(),
|
||||
created_at: "1".to_string(),
|
||||
updated_at: "3".to_string(),
|
||||
};
|
||||
|
|
@ -5538,22 +5462,13 @@ mod tests {
|
|||
projected.working_directory.as_ref().unwrap().status,
|
||||
WorkingDirectoryStatusKind::NotFound
|
||||
);
|
||||
assert_eq!(
|
||||
projected
|
||||
.working_directory
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.management_kind
|
||||
.as_deref(),
|
||||
Some("backend_managed")
|
||||
);
|
||||
let serialized = serde_json::to_string(&projected).unwrap();
|
||||
assert!(!serialized.contains("/tmp/"));
|
||||
assert!(!serialized.contains("materialized_path"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn workspace_managed_workdir_summaries_exclude_runtime_unmanaged_rows() {
|
||||
async fn workspace_workdir_summaries_include_runtime_observed_rows() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let api = test_api(dir.path()).await;
|
||||
api.store
|
||||
|
|
@ -5566,7 +5481,6 @@ mod tests {
|
|||
resolved_commit: None,
|
||||
materialization_status: "present".to_string(),
|
||||
cleanliness: "clean".to_string(),
|
||||
management_kind: "backend_managed".to_string(),
|
||||
created_at: "1".to_string(),
|
||||
updated_at: "1".to_string(),
|
||||
})
|
||||
|
|
@ -5581,29 +5495,29 @@ mod tests {
|
|||
resolved_commit: None,
|
||||
materialization_status: "present".to_string(),
|
||||
cleanliness: "unknown".to_string(),
|
||||
management_kind: "runtime_unmanaged".to_string(),
|
||||
created_at: "1".to_string(),
|
||||
updated_at: "2".to_string(),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let managed = working_directory_summaries(&api)
|
||||
let summaries = working_directory_summaries(&api)
|
||||
.unwrap_or_else(|err| panic!("working_directory_summaries failed: {}", err.error));
|
||||
assert_eq!(managed.len(), 1);
|
||||
assert_eq!(managed[0].working_directory_id, "managed");
|
||||
assert_eq!(
|
||||
managed[0].management_kind.as_deref(),
|
||||
Some("backend_managed")
|
||||
);
|
||||
let ids = summaries
|
||||
.iter()
|
||||
.map(|summary| summary.working_directory_id.as_str())
|
||||
.collect::<Vec<_>>();
|
||||
assert!(ids.contains(&"managed"));
|
||||
assert!(ids.contains(&"runtime-direct"));
|
||||
|
||||
let (runtime_projection, _) =
|
||||
runtime_working_directory_summaries(&api, EMBEDDED_WORKER_RUNTIME_ID).unwrap_or_else(
|
||||
|err| panic!("runtime_working_directory_summaries failed: {}", err.error),
|
||||
);
|
||||
assert!(runtime_projection.iter().any(|summary| {
|
||||
summary.working_directory_id == "runtime-direct"
|
||||
&& summary.management_kind.as_deref() == Some("runtime_unmanaged")
|
||||
}));
|
||||
assert!(
|
||||
runtime_projection
|
||||
.iter()
|
||||
.any(|summary| summary.working_directory_id == "runtime-direct")
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn unmanaged_runtime_workdir_projection_is_typed_and_diagnostic_safe() {
|
||||
|
|
@ -5616,7 +5530,6 @@ mod tests {
|
|||
resolved_commit: None,
|
||||
materialization_status: "present".to_string(),
|
||||
cleanliness: "unknown".to_string(),
|
||||
management_kind: "runtime_unmanaged".to_string(),
|
||||
created_at: "1".to_string(),
|
||||
updated_at: "2".to_string(),
|
||||
};
|
||||
|
|
@ -5624,10 +5537,6 @@ mod tests {
|
|||
let projected = workdir_summary_from_record(&workdir);
|
||||
|
||||
assert_eq!(projected.status, WorkingDirectoryStatusKind::Active);
|
||||
assert_eq!(
|
||||
projected.management_kind.as_deref(),
|
||||
Some("runtime_unmanaged")
|
||||
);
|
||||
let serialized = serde_json::to_string(&projected).unwrap();
|
||||
assert!(!serialized.contains("/tmp/"));
|
||||
assert!(!serialized.contains("materialized_path"));
|
||||
|
|
@ -6227,7 +6136,6 @@ mod tests {
|
|||
workdir_id: workdir_id.to_string(),
|
||||
runtime_id: "runtime-test".to_string(),
|
||||
repository_id: "repo-test".to_string(),
|
||||
management_kind: "backend_managed".to_string(),
|
||||
selector: Some("HEAD".to_string()),
|
||||
resolved_commit: None,
|
||||
materialization_status: status.to_string(),
|
||||
|
|
|
|||
|
|
@ -47,6 +47,11 @@ const MIGRATIONS: &[Migration] = &[
|
|||
name: "add workdir runtime observation states",
|
||||
apply: add_workdir_runtime_observation_states,
|
||||
},
|
||||
Migration {
|
||||
version: 7,
|
||||
name: "remove workdir registry management kind",
|
||||
apply: remove_workdir_registry_management_kind_column,
|
||||
},
|
||||
];
|
||||
|
||||
struct Migration {
|
||||
|
|
@ -91,8 +96,6 @@ pub struct WorkdirRegistryRecord {
|
|||
pub resolved_commit: Option<String>,
|
||||
pub materialization_status: String,
|
||||
pub cleanliness: String,
|
||||
/// `backend_managed` rows are authored by this Backend; `runtime_unmanaged` is for diagnostics only.
|
||||
pub management_kind: String,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
|
@ -152,11 +155,6 @@ pub trait ControlPlaneStore: Send + Sync {
|
|||
workspace_id: &str,
|
||||
limit: usize,
|
||||
) -> Result<Vec<WorkdirRegistryRecord>>;
|
||||
fn list_managed_workdir_registry(
|
||||
&self,
|
||||
workspace_id: &str,
|
||||
limit: usize,
|
||||
) -> Result<Vec<WorkdirRegistryRecord>>;
|
||||
fn delete_workdir_registry(&self, workspace_id: &str, workdir_id: &str) -> Result<bool>;
|
||||
|
||||
fn upsert_worker_workdir_link(&self, record: &WorkerWorkdirLinkRecord) -> Result<()>;
|
||||
|
|
@ -378,8 +376,8 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
|
|||
conn.execute(
|
||||
r#"INSERT INTO workdir_registry (
|
||||
workspace_id, workdir_id, runtime_id, repository_id, selector, resolved_commit,
|
||||
materialization_status, cleanliness, management_kind, created_at, updated_at
|
||||
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)
|
||||
materialization_status, cleanliness, created_at, updated_at
|
||||
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)
|
||||
ON CONFLICT(workspace_id, workdir_id) DO UPDATE SET
|
||||
runtime_id = excluded.runtime_id,
|
||||
repository_id = excluded.repository_id,
|
||||
|
|
@ -387,7 +385,6 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
|
|||
resolved_commit = excluded.resolved_commit,
|
||||
materialization_status = excluded.materialization_status,
|
||||
cleanliness = excluded.cleanliness,
|
||||
management_kind = excluded.management_kind,
|
||||
updated_at = excluded.updated_at"#,
|
||||
params![
|
||||
record.workspace_id,
|
||||
|
|
@ -398,7 +395,6 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
|
|||
record.resolved_commit,
|
||||
record.materialization_status,
|
||||
record.cleanliness,
|
||||
record.management_kind,
|
||||
record.created_at,
|
||||
record.updated_at,
|
||||
],
|
||||
|
|
@ -442,22 +438,6 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
|
|||
})
|
||||
}
|
||||
|
||||
fn list_managed_workdir_registry(
|
||||
&self,
|
||||
workspace_id: &str,
|
||||
limit: usize,
|
||||
) -> Result<Vec<WorkdirRegistryRecord>> {
|
||||
self.with_conn(|conn| {
|
||||
let sql = workdir_registry_select_sql(
|
||||
"WHERE workspace_id = ?1 AND management_kind = 'backend_managed' ORDER BY updated_at DESC LIMIT ?2",
|
||||
);
|
||||
let mut stmt = conn.prepare(sql.as_str())?;
|
||||
let rows = stmt.query_map(params![workspace_id, limit as i64], read_workdir_registry_record)?;
|
||||
rows.collect::<std::result::Result<Vec<_>, _>>()
|
||||
.map_err(Error::from)
|
||||
})
|
||||
}
|
||||
|
||||
fn delete_workdir_registry(&self, workspace_id: &str, workdir_id: &str) -> Result<bool> {
|
||||
self.with_conn(|conn| {
|
||||
let changed = conn.execute(
|
||||
|
|
@ -577,7 +557,7 @@ fn read_worker_registry_record(row: &rusqlite::Row<'_>) -> rusqlite::Result<Work
|
|||
fn workdir_registry_select_sql(where_clause: &str) -> String {
|
||||
format!(
|
||||
"SELECT workspace_id, workdir_id, runtime_id, repository_id, selector, resolved_commit, \
|
||||
materialization_status, cleanliness, management_kind, created_at, updated_at \
|
||||
materialization_status, cleanliness, created_at, updated_at \
|
||||
FROM workdir_registry {where_clause}"
|
||||
)
|
||||
}
|
||||
|
|
@ -594,9 +574,8 @@ fn read_workdir_registry_record(
|
|||
resolved_commit: row.get(5)?,
|
||||
materialization_status: row.get(6)?,
|
||||
cleanliness: row.get(7)?,
|
||||
management_kind: row.get(8)?,
|
||||
created_at: row.get(9)?,
|
||||
updated_at: row.get(10)?,
|
||||
created_at: row.get(8)?,
|
||||
updated_at: row.get(9)?,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -629,7 +608,6 @@ CREATE TABLE IF NOT EXISTS workdir_registry (
|
|||
resolved_commit TEXT,
|
||||
materialization_status TEXT NOT NULL CHECK (materialization_status IN ('pending', 'present', 'not_found', 'corrupted', 'unknown', 'failed')),
|
||||
cleanliness TEXT NOT NULL CHECK (cleanliness IN ('clean', 'dirty', 'unknown')),
|
||||
management_kind TEXT NOT NULL CHECK (management_kind IN ('backend_managed', 'runtime_unmanaged')),
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL,
|
||||
PRIMARY KEY (workspace_id, workdir_id),
|
||||
|
|
@ -948,7 +926,6 @@ fn add_workdir_runtime_observation_states(conn: &Connection) -> Result<()> {
|
|||
resolved_commit TEXT,
|
||||
materialization_status TEXT NOT NULL CHECK (materialization_status IN ('pending', 'present', 'not_found', 'corrupted', 'unknown', 'failed')),
|
||||
cleanliness TEXT NOT NULL CHECK (cleanliness IN ('clean', 'dirty', 'unknown')),
|
||||
management_kind TEXT NOT NULL CHECK (management_kind IN ('backend_managed', 'runtime_unmanaged')),
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL,
|
||||
PRIMARY KEY (workspace_id, workdir_id),
|
||||
|
|
@ -956,7 +933,7 @@ fn add_workdir_runtime_observation_states(conn: &Connection) -> Result<()> {
|
|||
);
|
||||
INSERT OR REPLACE INTO workdir_registry_v6 (
|
||||
workspace_id, workdir_id, runtime_id, repository_id, selector, resolved_commit,
|
||||
materialization_status, cleanliness, management_kind, created_at, updated_at
|
||||
materialization_status, cleanliness, created_at, updated_at
|
||||
)
|
||||
SELECT
|
||||
workspace_id, workdir_id, runtime_id, repository_id, selector, resolved_commit,
|
||||
|
|
@ -965,7 +942,7 @@ fn add_workdir_runtime_observation_states(conn: &Connection) -> Result<()> {
|
|||
WHEN 'removed' THEN 'not_found'
|
||||
ELSE materialization_status
|
||||
END,
|
||||
cleanliness, management_kind, created_at, updated_at
|
||||
cleanliness, created_at, updated_at
|
||||
FROM workdir_registry;
|
||||
DROP TABLE workdir_registry;
|
||||
ALTER TABLE workdir_registry_v6 RENAME TO workdir_registry;
|
||||
|
|
@ -976,6 +953,48 @@ fn add_workdir_runtime_observation_states(conn: &Connection) -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_workdir_registry_management_kind_column(conn: &Connection) -> Result<()> {
|
||||
if !table_exists(conn, "workdir_registry")?
|
||||
|| !table_columns(conn, "workdir_registry")?
|
||||
.iter()
|
||||
.any(|column| column == "management_kind")
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
conn.execute_batch(
|
||||
r#"
|
||||
CREATE TABLE workdir_registry_v7 (
|
||||
workspace_id TEXT NOT NULL,
|
||||
workdir_id TEXT NOT NULL,
|
||||
runtime_id TEXT NOT NULL,
|
||||
repository_id TEXT NOT NULL,
|
||||
selector TEXT,
|
||||
resolved_commit TEXT,
|
||||
materialization_status TEXT NOT NULL CHECK (materialization_status IN ('pending', 'present', 'not_found', 'corrupted', 'unknown', 'failed')),
|
||||
cleanliness TEXT NOT NULL CHECK (cleanliness IN ('clean', 'dirty', 'unknown')),
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL,
|
||||
PRIMARY KEY (workspace_id, workdir_id),
|
||||
FOREIGN KEY (workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE
|
||||
);
|
||||
INSERT OR REPLACE INTO workdir_registry_v7 (
|
||||
workspace_id, workdir_id, runtime_id, repository_id, selector, resolved_commit,
|
||||
materialization_status, cleanliness, created_at, updated_at
|
||||
)
|
||||
SELECT
|
||||
workspace_id, workdir_id, runtime_id, repository_id, selector, resolved_commit,
|
||||
materialization_status, cleanliness, created_at, updated_at
|
||||
FROM workdir_registry;
|
||||
DROP TABLE workdir_registry;
|
||||
ALTER TABLE workdir_registry_v7 RENAME TO workdir_registry;
|
||||
CREATE INDEX IF NOT EXISTS idx_workdir_registry_workspace_updated
|
||||
ON workdir_registry(workspace_id, updated_at DESC);
|
||||
"#,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_schema_v0_tables(conn: &Connection) -> Result<()> {
|
||||
conn.execute_batch(
|
||||
r#"
|
||||
|
|
@ -1199,7 +1218,7 @@ mod tests {
|
|||
let db = dir.path().join("control-plane.sqlite");
|
||||
let store = SqliteWorkspaceStore::open(&db).unwrap();
|
||||
|
||||
assert_eq!(store.schema_version().await.unwrap(), 6);
|
||||
assert_eq!(store.schema_version().await.unwrap(), 7);
|
||||
|
||||
let record = WorkspaceRecord {
|
||||
workspace_id: "local-dev".to_string(),
|
||||
|
|
@ -1211,7 +1230,7 @@ mod tests {
|
|||
store.upsert_workspace(&record).await.unwrap();
|
||||
|
||||
let reopened = SqliteWorkspaceStore::open(&db).unwrap();
|
||||
assert_eq!(reopened.schema_version().await.unwrap(), 6);
|
||||
assert_eq!(reopened.schema_version().await.unwrap(), 7);
|
||||
assert_eq!(
|
||||
reopened.get_workspace("local-dev").await.unwrap(),
|
||||
Some(record)
|
||||
|
|
@ -1420,7 +1439,7 @@ mod tests {
|
|||
.unwrap();
|
||||
|
||||
let store = SqliteWorkspaceStore::from_connection(conn).unwrap();
|
||||
assert_eq!(store.schema_version().await.unwrap(), 6);
|
||||
assert_eq!(store.schema_version().await.unwrap(), 7);
|
||||
|
||||
store
|
||||
.with_conn(|conn| {
|
||||
|
|
@ -1550,7 +1569,6 @@ mod tests {
|
|||
resolved_commit: Some("abcdef".to_string()),
|
||||
materialization_status: "not_found".to_string(),
|
||||
cleanliness: "clean".to_string(),
|
||||
management_kind: "backend_managed".to_string(),
|
||||
created_at: "2".to_string(),
|
||||
updated_at: "3".to_string(),
|
||||
};
|
||||
|
|
@ -1564,7 +1582,6 @@ mod tests {
|
|||
resolved_commit: Some("123456".to_string()),
|
||||
materialization_status: "present".to_string(),
|
||||
cleanliness: "unknown".to_string(),
|
||||
management_kind: "runtime_unmanaged".to_string(),
|
||||
created_at: "3".to_string(),
|
||||
updated_at: "4".to_string(),
|
||||
};
|
||||
|
|
@ -1597,12 +1614,6 @@ mod tests {
|
|||
store.list_workdir_registry("local-dev", 10).unwrap(),
|
||||
vec![unmanaged_workdir.clone(), workdir.clone()]
|
||||
);
|
||||
assert_eq!(
|
||||
store
|
||||
.list_managed_workdir_registry("local-dev", 10)
|
||||
.unwrap(),
|
||||
vec![workdir]
|
||||
);
|
||||
assert_eq!(
|
||||
store
|
||||
.list_worker_workdir_links("local-dev", "embedded", 1)
|
||||
|
|
|
|||
304
docker.nix
Normal file
304
docker.nix
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
{
|
||||
pkgs,
|
||||
yoi ? pkgs.callPackage ./package.nix { },
|
||||
}:
|
||||
|
||||
let
|
||||
lib = pkgs.lib;
|
||||
|
||||
imageTag = "latest";
|
||||
|
||||
mkRoot =
|
||||
name: paths: _extraPathsToLink:
|
||||
pkgs.symlinkJoin {
|
||||
inherit name paths;
|
||||
};
|
||||
|
||||
runtimeDirs = pkgs.runCommand "yoi-runtime-dirs" { } ''
|
||||
mkdir -p "$out/runtime-data" "$out/workdirs"
|
||||
chmod 0777 "$out/runtime-data" "$out/workdirs"
|
||||
'';
|
||||
|
||||
serverDirs = pkgs.runCommand "yoi-server-dirs" { } ''
|
||||
mkdir -p "$out/server-data" "$out/workspace"
|
||||
chmod 0777 "$out/server-data" "$out/workspace"
|
||||
'';
|
||||
|
||||
runtimeRoot =
|
||||
mkRoot "yoi-runtime-root"
|
||||
[
|
||||
yoi
|
||||
pkgs.bashInteractive
|
||||
pkgs.coreutils
|
||||
pkgs.cacert
|
||||
pkgs.git
|
||||
pkgs.openssh
|
||||
runtimeDirs
|
||||
]
|
||||
[
|
||||
"/runtime-data"
|
||||
"/workdirs"
|
||||
];
|
||||
|
||||
serverRoot =
|
||||
mkRoot "yoi-server-root"
|
||||
[
|
||||
yoi
|
||||
pkgs.coreutils
|
||||
pkgs.cacert
|
||||
pkgs.git
|
||||
serverDirs
|
||||
]
|
||||
[
|
||||
"/server-data"
|
||||
"/workspace"
|
||||
];
|
||||
|
||||
webuiSrc = lib.cleanSourceWith {
|
||||
src = ./web/workspace;
|
||||
filter =
|
||||
path: type:
|
||||
let
|
||||
baseName = baseNameOf path;
|
||||
in
|
||||
!(baseName == "node_modules" || baseName == ".svelte-kit" || baseName == "build");
|
||||
};
|
||||
|
||||
webuiDeps = pkgs.stdenvNoCC.mkDerivation {
|
||||
pname = "yoi-webui-deno-deps";
|
||||
version = "0.1.0";
|
||||
|
||||
src = webuiSrc;
|
||||
nativeBuildInputs = [ pkgs.deno ];
|
||||
|
||||
outputHashAlgo = "sha256";
|
||||
outputHashMode = "recursive";
|
||||
outputHash = "sha256-q+otr+ANR9gB8bRZKFZDfNpM6rnQ4B4wNd/s1QNeSA4=";
|
||||
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
|
||||
export HOME="$TMPDIR/home"
|
||||
export DENO_DIR="$TMPDIR/deno-cache"
|
||||
mkdir -p "$HOME" "$DENO_DIR"
|
||||
deno task build
|
||||
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
|
||||
mkdir -p "$out"
|
||||
cp -R "$DENO_DIR" "$out/deno-cache"
|
||||
if [ -d node_modules ]; then
|
||||
cp -R node_modules "$out/node_modules"
|
||||
fi
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
};
|
||||
|
||||
webuiStatic = pkgs.stdenvNoCC.mkDerivation {
|
||||
pname = "yoi-webui-static";
|
||||
version = "0.1.0";
|
||||
|
||||
src = webuiSrc;
|
||||
|
||||
nativeBuildInputs = [ pkgs.deno ];
|
||||
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
|
||||
export HOME="$TMPDIR/home"
|
||||
export DENO_DIR="$TMPDIR/deno-cache"
|
||||
mkdir -p "$HOME"
|
||||
cp -R ${webuiDeps}/deno-cache "$DENO_DIR"
|
||||
chmod -R u+w "$DENO_DIR"
|
||||
if [ -d ${webuiDeps}/node_modules ]; then
|
||||
cp -R ${webuiDeps}/node_modules node_modules
|
||||
chmod -R u+w node_modules
|
||||
fi
|
||||
deno task build
|
||||
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
|
||||
mkdir -p "$out"
|
||||
cp -R build/. "$out/"
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
};
|
||||
|
||||
webuiRoot = pkgs.runCommand "yoi-webui-root" { } ''
|
||||
mkdir -p "$out/usr/share/yoi-webui"
|
||||
cp -R ${webuiStatic}/. "$out/usr/share/yoi-webui/"
|
||||
'';
|
||||
|
||||
webuiDirs = pkgs.runCommand "yoi-webui-dirs" { } ''
|
||||
mkdir -p "$out/etc" "$out/tmp" "$out/var/cache/nginx" "$out/var/log/nginx"
|
||||
touch "$out/tmp/.keep" "$out/var/cache/nginx/.keep" "$out/var/log/nginx/.keep"
|
||||
cat > "$out/etc/passwd" <<'EOF'
|
||||
root:x:0:0:root:/root:/bin/sh
|
||||
nobody:x:65534:65534:nobody:/var/empty:/sbin/nologin
|
||||
EOF
|
||||
cat > "$out/etc/group" <<'EOF'
|
||||
root:x:0:
|
||||
nobody:x:65534:
|
||||
nogroup:x:65534:
|
||||
EOF
|
||||
'';
|
||||
|
||||
nginxConf = pkgs.writeText "yoi-webui-nginx.conf" ''
|
||||
pid /tmp/nginx.pid;
|
||||
error_log /dev/stderr info;
|
||||
|
||||
events {}
|
||||
|
||||
http {
|
||||
include ${pkgs.nginx}/conf/mime.types;
|
||||
access_log /dev/stdout;
|
||||
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
''' close;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /usr/share/yoi-webui;
|
||||
index index.html;
|
||||
|
||||
resolver 127.0.0.11 valid=30s ipv6=off;
|
||||
set $yoi_backend server:8787;
|
||||
|
||||
location = /api {
|
||||
proxy_pass http://$yoi_backend;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://$yoi_backend;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
}
|
||||
'';
|
||||
|
||||
webuiImageRoot =
|
||||
mkRoot "yoi-webui-root-env"
|
||||
[
|
||||
pkgs.nginx
|
||||
pkgs.cacert
|
||||
webuiRoot
|
||||
webuiDirs
|
||||
]
|
||||
[
|
||||
"/usr/share/yoi-webui"
|
||||
"/var"
|
||||
"/tmp"
|
||||
];
|
||||
|
||||
commonEnv = [
|
||||
"SSL_CERT_FILE=/etc/ssl/certs/ca-bundle.crt"
|
||||
"GIT_SSL_CAINFO=/etc/ssl/certs/ca-bundle.crt"
|
||||
];
|
||||
in
|
||||
{
|
||||
runtime = pkgs.dockerTools.buildImage {
|
||||
name = "yoi-runtime";
|
||||
tag = imageTag;
|
||||
copyToRoot = runtimeRoot;
|
||||
config = {
|
||||
Entrypoint = [ "/bin/worker-runtime-rest-server" ];
|
||||
Cmd = [
|
||||
"--bind"
|
||||
"0.0.0.0:38800"
|
||||
"--display-name"
|
||||
"Docker Runtime"
|
||||
"--fs-root"
|
||||
"/runtime-data"
|
||||
"--workdir-target"
|
||||
"/workdirs"
|
||||
];
|
||||
Env = [ "PATH=/bin" ] ++ commonEnv;
|
||||
ExposedPorts = {
|
||||
"38800/tcp" = { };
|
||||
};
|
||||
Volumes = {
|
||||
"/runtime-data" = { };
|
||||
"/workdirs" = { };
|
||||
};
|
||||
WorkingDir = "/runtime-data";
|
||||
};
|
||||
};
|
||||
|
||||
server = pkgs.dockerTools.buildImage {
|
||||
name = "yoi-server";
|
||||
tag = imageTag;
|
||||
copyToRoot = serverRoot;
|
||||
config = {
|
||||
Entrypoint = [ "/bin/yoi-workspace-server" ];
|
||||
Cmd = [
|
||||
"serve"
|
||||
"--workspace"
|
||||
"/workspace"
|
||||
"--db"
|
||||
"/server-data/workspace.db"
|
||||
"--listen"
|
||||
"0.0.0.0:8787"
|
||||
];
|
||||
Env = [ "PATH=/bin" ] ++ commonEnv;
|
||||
ExposedPorts = {
|
||||
"8787/tcp" = { };
|
||||
};
|
||||
Volumes = {
|
||||
"/server-data" = { };
|
||||
"/workspace" = { };
|
||||
};
|
||||
WorkingDir = "/server-data";
|
||||
};
|
||||
};
|
||||
|
||||
webui = pkgs.dockerTools.buildImage {
|
||||
name = "yoi-webui";
|
||||
tag = imageTag;
|
||||
copyToRoot = webuiImageRoot;
|
||||
config = {
|
||||
Entrypoint = [
|
||||
"/bin/nginx"
|
||||
"-c"
|
||||
"${nginxConf}"
|
||||
"-g"
|
||||
"daemon off;"
|
||||
];
|
||||
Env = [ "PATH=/bin" ] ++ commonEnv;
|
||||
ExposedPorts = {
|
||||
"80/tcp" = { };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
webui-static = webuiStatic;
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
resolver 127.0.0.11 valid=30s ipv6=off;
|
||||
set $yoi_backend server:8787;
|
||||
|
||||
location = /api {
|
||||
proxy_pass http://$yoi_backend;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://$yoi_backend;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
16
docker/workspace/.yoi/workspace-backend.local.toml
Normal file
16
docker/workspace/.yoi/workspace-backend.local.toml
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
[server]
|
||||
listen = "0.0.0.0:8787"
|
||||
frontend_url = "http://127.0.0.1:8080"
|
||||
|
||||
[data]
|
||||
root = "/server-data"
|
||||
workspace_database_path = "/server-data/workspace.db"
|
||||
embedded_runtime_store_root = "/server-data/embedded-runtime"
|
||||
|
||||
[limits]
|
||||
max_records = 200
|
||||
|
||||
[[runtimes.remote]]
|
||||
id = "docker-runtime"
|
||||
endpoint = "http://runtime:38800"
|
||||
display_name = "Docker Runtime"
|
||||
3
docker/workspace/.yoi/workspace.toml
Normal file
3
docker/workspace/.yoi/workspace.toml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
workspace_id = "019a1e4a-7c0d-7000-9000-000000000001"
|
||||
created_at = "2026-07-21T00:00:00Z"
|
||||
display_name = "Docker Workspace"
|
||||
19
flake.nix
19
flake.nix
|
|
@ -17,6 +17,13 @@
|
|||
let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
yoi = pkgs.callPackage ./package.nix { };
|
||||
dockerImages =
|
||||
if pkgs.stdenv.isLinux then
|
||||
import ./docker.nix {
|
||||
inherit pkgs yoi;
|
||||
}
|
||||
else
|
||||
{ };
|
||||
mkApp = name: description: {
|
||||
type = "app";
|
||||
program = "${yoi}/bin/${name}";
|
||||
|
|
@ -24,8 +31,16 @@
|
|||
};
|
||||
in
|
||||
{
|
||||
packages.default = yoi;
|
||||
packages.yoi = yoi;
|
||||
packages = {
|
||||
default = yoi;
|
||||
yoi = yoi;
|
||||
}
|
||||
// pkgs.lib.optionalAttrs pkgs.stdenv.isLinux {
|
||||
docker-runtime = dockerImages.runtime;
|
||||
docker-server = dockerImages.server;
|
||||
docker-webui = dockerImages.webui;
|
||||
webui-static = dockerImages.webui-static;
|
||||
};
|
||||
|
||||
apps.default = mkApp "yoi" "Run the Yoi terminal UI";
|
||||
apps.yoi = mkApp "yoi" "Run the Yoi terminal UI";
|
||||
|
|
|
|||
11
package.nix
11
package.nix
|
|
@ -43,7 +43,7 @@ rustPlatform.buildRustPackage rec {
|
|||
filter = sourceFilter;
|
||||
};
|
||||
|
||||
cargoHash = "sha256-MUEySmRu5Te8+GCwlt3qk7jxcumYIMvnuvj64o2XuDw=";
|
||||
cargoHash = "sha256-iZaTREhL/aLixn67A1+Gi9opqh2j/yyFuGMyBBvuATM=";
|
||||
|
||||
depsExtraArgs = {
|
||||
# Older fetchCargoVendor utilities used crates.io's API download endpoint,
|
||||
|
|
@ -93,6 +93,7 @@ rustPlatform.buildRustPackage rec {
|
|||
|
||||
postBuild = ''
|
||||
cargo build --offline --profile release -p yoi-workspace-server --bin yoi-workspace-server
|
||||
cargo build --offline --profile release -p worker-runtime --bin worker-runtime-rest-server --features ws-server,fs-store
|
||||
'';
|
||||
|
||||
# The package check is a credential-free install smoke check below. Running the
|
||||
|
|
@ -105,13 +106,15 @@ rustPlatform.buildRustPackage rec {
|
|||
|
||||
yoi_bin=$(find . -type f -name yoi | head -n 1)
|
||||
workspace_server_bin=$(find . -type f -name yoi-workspace-server | head -n 1)
|
||||
if [ -z "$yoi_bin" ] || [ -z "$workspace_server_bin" ]; then
|
||||
worker_runtime_bin=$(find . -type f -name worker-runtime-rest-server | head -n 1)
|
||||
if [ -z "$yoi_bin" ] || [ -z "$workspace_server_bin" ] || [ -z "$worker_runtime_bin" ]; then
|
||||
echo "built binaries not found" >&2
|
||||
find . -maxdepth 6 -type f \( -name yoi -o -name yoi-workspace-server \) -print >&2
|
||||
find . -maxdepth 6 -type f \( -name yoi -o -name yoi-workspace-server -o -name worker-runtime-rest-server \) -print >&2
|
||||
exit 1
|
||||
fi
|
||||
install -Dm755 "$yoi_bin" "$out/bin/yoi"
|
||||
install -Dm755 "$workspace_server_bin" "$out/bin/yoi-workspace-server"
|
||||
install -Dm755 "$worker_runtime_bin" "$out/bin/worker-runtime-rest-server"
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
|
|
@ -123,7 +126,9 @@ rustPlatform.buildRustPackage rec {
|
|||
"$out/bin/yoi" worker --help >/dev/null
|
||||
test -x "$out/bin/yoi"
|
||||
test -x "$out/bin/yoi-workspace-server"
|
||||
test -x "$out/bin/worker-runtime-rest-server"
|
||||
"$out/bin/yoi-workspace-server" --help >/dev/null
|
||||
"$out/bin/worker-runtime-rest-server" --help >/dev/null
|
||||
test ! -e "$out/bin/yoi-pod"
|
||||
test ! -e "$out/share/yoi/resources"
|
||||
if "$out/bin/yoi" --session not-a-uuid 2>yoi.err; then
|
||||
|
|
|
|||
|
|
@ -125,7 +125,6 @@ export type WorkingDirectorySummary = {
|
|||
status: string;
|
||||
cleanliness?: string | null;
|
||||
primary_worker_id?: number | null;
|
||||
management_kind?: "backend_managed" | "runtime_unmanaged" | string | null;
|
||||
cleanup_target: {
|
||||
kind: string;
|
||||
working_directory_id: string;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user