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>,
|
pub cleanliness: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub primary_worker_id: Option<String>,
|
pub primary_worker_id: Option<String>,
|
||||||
#[serde(default)]
|
|
||||||
pub management_kind: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
|
||||||
|
|
|
||||||
|
|
@ -156,10 +156,6 @@ pub struct WorkingDirectorySummary {
|
||||||
pub cleanliness: Option<String>,
|
pub cleanliness: Option<String>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub primary_worker_id: Option<WorkerId>,
|
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)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,6 @@ impl WorkingDirectory {
|
||||||
status: self.status.clone(),
|
status: self.status.clone(),
|
||||||
cleanliness: None,
|
cleanliness: None,
|
||||||
primary_worker_id: None,
|
primary_worker_id: None,
|
||||||
management_kind: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -222,7 +221,6 @@ impl LocalGitWorktreeMaterializer {
|
||||||
status: WorkingDirectoryStatusKind::Corrupted,
|
status: WorkingDirectoryStatusKind::Corrupted,
|
||||||
cleanliness: Some("unknown".to_string()),
|
cleanliness: Some("unknown".to_string()),
|
||||||
primary_worker_id: None,
|
primary_worker_id: None,
|
||||||
management_kind: None,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,15 +34,15 @@ impl WorkspaceHttpMemoryBackend {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn execute_operation(
|
pub async fn execute_operation(
|
||||||
&self,
|
&self,
|
||||||
operation: MemoryBackendOperation,
|
operation: MemoryBackendOperation,
|
||||||
) -> Result<MemoryBackendOperationResult, WorkspaceMemoryBackendError> {
|
) -> 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> {
|
async fn execute(&self, operation: MemoryBackendOperation) -> Result<ToolOutput, ToolError> {
|
||||||
match self.execute_operation(operation) {
|
match self.execute_operation(operation).await {
|
||||||
Ok(MemoryBackendOperationResult::ToolOutput(output)) => Ok(tool_output(output)),
|
Ok(MemoryBackendOperationResult::ToolOutput(output)) => Ok(tool_output(output)),
|
||||||
Ok(result) => Err(ToolError::ExecutionFailed(format!(
|
Ok(result) => Err(ToolError::ExecutionFailed(format!(
|
||||||
"unexpected memory backend result for model-visible tool: {result:?}"
|
"unexpected memory backend result for model-visible tool: {result:?}"
|
||||||
|
|
@ -70,7 +70,7 @@ pub enum WorkspaceMemoryBackendError {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WorkspaceClient {
|
impl WorkspaceClient {
|
||||||
pub fn execute_memory_backend_operation(
|
pub async fn execute_memory_backend_operation(
|
||||||
&self,
|
&self,
|
||||||
operation: MemoryBackendOperation,
|
operation: MemoryBackendOperation,
|
||||||
) -> Result<MemoryBackendOperationResult, WorkspaceMemoryBackendError> {
|
) -> Result<MemoryBackendOperationResult, WorkspaceMemoryBackendError> {
|
||||||
|
|
@ -78,7 +78,7 @@ impl WorkspaceClient {
|
||||||
WorkspaceClient::Http {
|
WorkspaceClient::Http {
|
||||||
workspace_id,
|
workspace_id,
|
||||||
base_url,
|
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 {
|
WorkspaceClient::Available { kind } => Err(WorkspaceMemoryBackendError::Unavailable {
|
||||||
reason: format!(
|
reason: format!(
|
||||||
"workspace client kind `{kind}` does not expose the Backend Workspace API"
|
"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,
|
workspace_id: &str,
|
||||||
base_url: &str,
|
base_url: &str,
|
||||||
operation: MemoryBackendOperation,
|
operation: MemoryBackendOperation,
|
||||||
|
|
@ -103,12 +103,13 @@ fn execute_http_memory_backend(
|
||||||
base_url.trim_end_matches('/'),
|
base_url.trim_end_matches('/'),
|
||||||
workspace_id
|
workspace_id
|
||||||
);
|
);
|
||||||
let response = reqwest::blocking::Client::new()
|
let response = reqwest::Client::new()
|
||||||
.post(url)
|
.post(url)
|
||||||
.json(&operation)
|
.json(&operation)
|
||||||
.send()?;
|
.send()
|
||||||
|
.await?;
|
||||||
let status = response.status();
|
let status = response.status();
|
||||||
let body = response.text()?;
|
let body = response.text().await?;
|
||||||
if !status.is_success() {
|
if !status.is_success() {
|
||||||
return Err(WorkspaceMemoryBackendError::Http { status, body });
|
return Err(WorkspaceMemoryBackendError::Http { status, body });
|
||||||
}
|
}
|
||||||
|
|
@ -220,7 +221,7 @@ impl Tool for WorkspaceHttpMemoryTool {
|
||||||
_ctx: ToolExecutionContext,
|
_ctx: ToolExecutionContext,
|
||||||
) -> Result<ToolOutput, ToolError> {
|
) -> Result<ToolOutput, ToolError> {
|
||||||
let operation = (self.build)(input_json)?;
|
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,
|
source_refs,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
|
.await
|
||||||
.map_err(|e| ToolError::ExecutionFailed(format!("write staging failed: {e}")))?;
|
.map_err(|e| ToolError::ExecutionFailed(format!("write staging failed: {e}")))?;
|
||||||
let ids = match result {
|
let ids = match result {
|
||||||
MemoryBackendOperationResult::StagingWritten(output) if output.staging_count == 1 => {
|
MemoryBackendOperationResult::StagingWritten(output) if output.staging_count == 1 => {
|
||||||
|
|
@ -713,7 +714,7 @@ mod tests {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn stage_candidate_writes_staging_record_with_source_evidence() {
|
async fn stage_candidate_writes_staging_record_with_source_evidence() {
|
||||||
let (client, request_rx) = stub_memory_backend_response(
|
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(
|
let state = SessionExploreState::new(
|
||||||
SessionReferenceView::new("segment-1", vec![Item::user_message("durable decision")]),
|
SessionReferenceView::new("segment-1", vec![Item::user_message("durable decision")]),
|
||||||
|
|
@ -745,7 +746,7 @@ mod tests {
|
||||||
vec!["00000000-0000-7000-8000-000000000001".to_string()]
|
vec!["00000000-0000-7000-8000-000000000001".to_string()]
|
||||||
);
|
);
|
||||||
let request = request_rx.recv().unwrap();
|
let request = request_rx.recv().unwrap();
|
||||||
assert!(request.contains("\"StageCandidate\""));
|
assert!(request.contains("\"operation\":\"stage_candidate\""));
|
||||||
assert!(request.contains("\"kind\":\"decision\""));
|
assert!(request.contains("\"kind\":\"decision\""));
|
||||||
assert!(request.contains("\"id\":\"M0000\""));
|
assert!(request.contains("\"id\":\"M0000\""));
|
||||||
assert!(request.contains("\"evidence_id\":\"M0000\""));
|
assert!(request.contains("\"evidence_id\":\"M0000\""));
|
||||||
|
|
|
||||||
|
|
@ -897,12 +897,17 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
|
||||||
self.workspace_context.client()
|
self.workspace_context.client()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resident_summary_from_workspace_authority(&self) -> Result<Option<String>, WorkerError> {
|
async fn resident_summary_from_workspace_authority(
|
||||||
let result = self.workspace_client().execute_memory_backend_operation(
|
&self,
|
||||||
memory::backend::MemoryBackendOperation::ResidentSummary(
|
) -> Result<Option<String>, WorkerError> {
|
||||||
memory::backend::MemoryResidentSummaryOperation::default(),
|
let result = self
|
||||||
),
|
.workspace_client()
|
||||||
)?;
|
.execute_memory_backend_operation(
|
||||||
|
memory::backend::MemoryBackendOperation::ResidentSummary(
|
||||||
|
memory::backend::MemoryResidentSummaryOperation::default(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
match result {
|
match result {
|
||||||
memory::backend::MemoryBackendOperationResult::ToolOutput(output) => Ok(output.content),
|
memory::backend::MemoryBackendOperationResult::ToolOutput(output) => Ok(output.content),
|
||||||
other => Err(WorkerError::FeatureInstall(format!(
|
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
|
/// Subsequent invocations are no-ops: the template field is
|
||||||
/// consumed with `Option::take()`, so the materialised value
|
/// consumed with `Option::take()`, so the materialised value
|
||||||
/// persists across all later turns and compaction.
|
/// 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 {
|
let Some(template) = self.system_prompt_template.take() else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
@ -1498,7 +1503,7 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.is_some_and(|m| m.inject_summary.unwrap_or(true));
|
.is_some_and(|m| m.inject_summary.unwrap_or(true));
|
||||||
let resident_summary: Option<String> = if inject_summary {
|
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,
|
Ok(summary) => summary,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
tracing::debug!(%error, "resident memory summary unavailable");
|
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).
|
/// first so extract sees a stable history range).
|
||||||
async fn prepare_for_run(&mut self) -> Result<(), WorkerError> {
|
async fn prepare_for_run(&mut self) -> Result<(), WorkerError> {
|
||||||
self.ensure_interceptor_installed();
|
self.ensure_interceptor_installed();
|
||||||
self.ensure_system_prompt_materialized()?;
|
self.ensure_system_prompt_materialized().await?;
|
||||||
self.cleanup_finished_memory_task();
|
self.cleanup_finished_memory_task();
|
||||||
self.ensure_segment_head()?;
|
self.ensure_segment_head()?;
|
||||||
if self.should_pre_run_compact() {
|
if self.should_pre_run_compact() {
|
||||||
|
|
@ -2897,7 +2902,8 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
);
|
)
|
||||||
|
.await;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -2926,7 +2932,8 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
);
|
)
|
||||||
|
.await;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let result = self.run_extract_once(&memory_cfg, threshold).await;
|
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,
|
None,
|
||||||
None,
|
None,
|
||||||
);
|
).await;
|
||||||
return Ok(ExtractDecision::Skipped);
|
return Ok(ExtractDecision::Skipped);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3008,18 +3015,23 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
|
||||||
.history()
|
.history()
|
||||||
.len();
|
.len();
|
||||||
if current_history_len <= processed_history_len {
|
if current_history_len <= processed_history_len {
|
||||||
audit.emit(
|
audit
|
||||||
self.workspace_client(),
|
.emit(
|
||||||
event_tx,
|
self.workspace_client(),
|
||||||
memory::audit::WorkerLifecycleStatus::Skipped,
|
event_tx,
|
||||||
"no_new_history_items",
|
memory::audit::WorkerLifecycleStatus::Skipped,
|
||||||
None,
|
"no_new_history_items",
|
||||||
Some(memory::audit::ExtractAudit {
|
None,
|
||||||
history_range: Some([processed_history_len as u64, current_history_len as u64]),
|
Some(memory::audit::ExtractAudit {
|
||||||
..Default::default()
|
history_range: Some([
|
||||||
}),
|
processed_history_len as u64,
|
||||||
None,
|
current_history_len as u64,
|
||||||
);
|
]),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
return Ok(ExtractDecision::Skipped);
|
return Ok(ExtractDecision::Skipped);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3031,15 +3043,17 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
|
||||||
.read_all(self.session_id(), self.segment_id())?
|
.read_all(self.session_id(), self.segment_id())?
|
||||||
.len();
|
.len();
|
||||||
if entries_now == 0 {
|
if entries_now == 0 {
|
||||||
audit.emit(
|
audit
|
||||||
self.workspace_client(),
|
.emit(
|
||||||
event_tx,
|
self.workspace_client(),
|
||||||
memory::audit::WorkerLifecycleStatus::Skipped,
|
event_tx,
|
||||||
"empty_segment_log",
|
memory::audit::WorkerLifecycleStatus::Skipped,
|
||||||
None,
|
"empty_segment_log",
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
);
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
return Ok(ExtractDecision::Skipped);
|
return Ok(ExtractDecision::Skipped);
|
||||||
}
|
}
|
||||||
let end_entry = entries_now - 1;
|
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)
|
.map(|p| p.processed_through_entry + 1)
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
if start_entry > end_entry {
|
if start_entry > end_entry {
|
||||||
audit.emit(
|
audit
|
||||||
self.workspace_client(),
|
.emit(
|
||||||
event_tx,
|
self.workspace_client(),
|
||||||
memory::audit::WorkerLifecycleStatus::Skipped,
|
event_tx,
|
||||||
"no_new_segment_entries",
|
memory::audit::WorkerLifecycleStatus::Skipped,
|
||||||
None,
|
"no_new_segment_entries",
|
||||||
Some(memory::audit::ExtractAudit {
|
None,
|
||||||
session_id: Some(self.session_id().to_string()),
|
Some(memory::audit::ExtractAudit {
|
||||||
segment_id: Some(self.segment_id().to_string()),
|
session_id: Some(self.session_id().to_string()),
|
||||||
entry_range: Some([start_entry as u64, end_entry as u64]),
|
segment_id: Some(self.segment_id().to_string()),
|
||||||
history_range: Some([processed_history_len as u64, current_history_len as u64]),
|
entry_range: Some([start_entry as u64, end_entry as u64]),
|
||||||
..Default::default()
|
history_range: Some([
|
||||||
}),
|
processed_history_len as u64,
|
||||||
None,
|
current_history_len as u64,
|
||||||
);
|
]),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
return Ok(ExtractDecision::Skipped);
|
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]),
|
history_range: Some([processed_history_len as u64, current_history_len as u64]),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
audit.emit(
|
audit
|
||||||
self.workspace_client(),
|
.emit(
|
||||||
event_tx,
|
self.workspace_client(),
|
||||||
memory::audit::WorkerLifecycleStatus::Started,
|
event_tx,
|
||||||
format!("token_threshold_reached tokens_since={tokens_since} threshold={threshold}"),
|
memory::audit::WorkerLifecycleStatus::Started,
|
||||||
None,
|
format!(
|
||||||
Some(extract_audit_base.clone()),
|
"token_threshold_reached tokens_since={tokens_since} threshold={threshold}"
|
||||||
None,
|
),
|
||||||
);
|
None,
|
||||||
|
Some(extract_audit_base.clone()),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
let items_to_extract = self.engine.as_ref().expect("worker present").history()
|
let items_to_extract = self.engine.as_ref().expect("worker present").history()
|
||||||
[processed_history_len..current_history_len]
|
[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) {
|
let client = match self.build_extractor_client(memory_cfg) {
|
||||||
Ok(client) => client,
|
Ok(client) => client,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
audit.emit(
|
audit
|
||||||
self.workspace_client(),
|
.emit(
|
||||||
event_tx,
|
self.workspace_client(),
|
||||||
memory::audit::WorkerLifecycleStatus::Failed,
|
event_tx,
|
||||||
format!("client_build_failed: {err}"),
|
memory::audit::WorkerLifecycleStatus::Failed,
|
||||||
None,
|
format!("client_build_failed: {err}"),
|
||||||
Some(extract_audit_base),
|
None,
|
||||||
None,
|
Some(extract_audit_base),
|
||||||
);
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
return Err(err);
|
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) {
|
let extract_system_prompt = match self.prompts.memory_extract_system(memory_language) {
|
||||||
Ok(prompt) => prompt,
|
Ok(prompt) => prompt,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
audit.emit(
|
audit
|
||||||
self.workspace_client(),
|
.emit(
|
||||||
event_tx,
|
self.workspace_client(),
|
||||||
memory::audit::WorkerLifecycleStatus::Failed,
|
event_tx,
|
||||||
format!("prompt_render_failed: {err}"),
|
memory::audit::WorkerLifecycleStatus::Failed,
|
||||||
None,
|
format!("prompt_render_failed: {err}"),
|
||||||
Some(extract_audit_base),
|
None,
|
||||||
None,
|
Some(extract_audit_base),
|
||||||
);
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
return Err(WorkerError::PromptCatalog(err));
|
return Err(WorkerError::PromptCatalog(err));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -3151,15 +3178,17 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
|
||||||
.iter()
|
.iter()
|
||||||
.any(|installed| installed == name)
|
.any(|installed| installed == name)
|
||||||
}) {
|
}) {
|
||||||
audit.emit(
|
audit
|
||||||
self.workspace_client(),
|
.emit(
|
||||||
event_tx,
|
self.workspace_client(),
|
||||||
memory::audit::WorkerLifecycleStatus::Failed,
|
event_tx,
|
||||||
"session_explore_feature_install_failed",
|
memory::audit::WorkerLifecycleStatus::Failed,
|
||||||
None,
|
"session_explore_feature_install_failed",
|
||||||
Some(extract_audit_base),
|
None,
|
||||||
None,
|
Some(extract_audit_base),
|
||||||
);
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
return Err(WorkerError::FeatureInstall(
|
return Err(WorkerError::FeatureInstall(
|
||||||
"session-explore feature install failed".to_string(),
|
"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),
|
Ok(result) => result.usage.as_ref().map(usage_audit_from_event),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let usage = err.usage.as_ref().map(usage_audit_from_event);
|
let usage = err.usage.as_ref().map(usage_audit_from_event);
|
||||||
audit.emit(
|
audit
|
||||||
self.workspace_client(),
|
.emit(
|
||||||
event_tx,
|
self.workspace_client(),
|
||||||
lifecycle_status_for_worker_error(&err.source),
|
event_tx,
|
||||||
format!("worker_failed: {}", err.source),
|
lifecycle_status_for_worker_error(&err.source),
|
||||||
usage,
|
format!("worker_failed: {}", err.source),
|
||||||
Some(extract_audit_base),
|
usage,
|
||||||
None,
|
Some(extract_audit_base),
|
||||||
);
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
return Err(WorkerError::Engine(err.source));
|
return Err(WorkerError::Engine(err.source));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -3228,15 +3259,17 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
|
||||||
} else {
|
} else {
|
||||||
"completed_staging_written"
|
"completed_staging_written"
|
||||||
};
|
};
|
||||||
audit.emit(
|
audit
|
||||||
self.workspace_client(),
|
.emit(
|
||||||
event_tx,
|
self.workspace_client(),
|
||||||
memory::audit::WorkerLifecycleStatus::Completed,
|
event_tx,
|
||||||
reason,
|
memory::audit::WorkerLifecycleStatus::Completed,
|
||||||
usage,
|
reason,
|
||||||
Some(extract_audit),
|
usage,
|
||||||
None,
|
Some(extract_audit),
|
||||||
);
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
Ok(ExtractDecision::Completed)
|
Ok(ExtractDecision::Completed)
|
||||||
}
|
}
|
||||||
|
|
@ -3274,7 +3307,8 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
);
|
)
|
||||||
|
.await;
|
||||||
if reason == "consolidation_backend_operation_unavailable" {
|
if reason == "consolidation_backend_operation_unavailable" {
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
"workspace memory consolidation skipped: backend operation is unavailable"
|
"workspace memory consolidation skipped: backend operation is unavailable"
|
||||||
|
|
@ -3357,7 +3391,7 @@ impl WorkerAuditBase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emit(
|
async fn emit(
|
||||||
&self,
|
&self,
|
||||||
workspace_client: &WorkspaceClient,
|
workspace_client: &WorkspaceClient,
|
||||||
event_tx: Option<&broadcast::Sender<Event>>,
|
event_tx: Option<&broadcast::Sender<Event>>,
|
||||||
|
|
@ -3379,15 +3413,15 @@ impl WorkerAuditBase {
|
||||||
extract,
|
extract,
|
||||||
consolidation,
|
consolidation,
|
||||||
};
|
};
|
||||||
let _ = workspace_client.execute_memory_backend_operation(
|
let _ = workspace_client
|
||||||
memory::backend::MemoryBackendOperation::AppendAudit(
|
.execute_memory_backend_operation(memory::backend::MemoryBackendOperation::AppendAudit(
|
||||||
memory::backend::MemoryAppendAuditOperation {
|
memory::backend::MemoryAppendAuditOperation {
|
||||||
event: memory::audit::AuditEvent::new(
|
event: memory::audit::AuditEvent::new(
|
||||||
memory::audit::AuditPayload::WorkerLifecycle(payload),
|
memory::audit::AuditPayload::WorkerLifecycle(payload),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
),
|
))
|
||||||
);
|
.await;
|
||||||
if should_emit_memory_worker_event(self.worker, status, &reason) {
|
if should_emit_memory_worker_event(self.worker, status, &reason) {
|
||||||
emit_memory_worker_event(
|
emit_memory_worker_event(
|
||||||
event_tx,
|
event_tx,
|
||||||
|
|
@ -5552,7 +5586,7 @@ mod build_summary_prompt_tests {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
worker.set_system_prompt_template(template);
|
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()
|
worker.engine().get_system_prompt().unwrap().to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5583,17 +5617,15 @@ mod build_summary_prompt_tests {
|
||||||
let mut buffer = [0_u8; 1024];
|
let mut buffer = [0_u8; 1024];
|
||||||
let _ = stream.read(&mut buffer).unwrap();
|
let _ = stream.read(&mut buffer).unwrap();
|
||||||
let body = serde_json::json!({
|
let body = serde_json::json!({
|
||||||
"Ok": {
|
"status": "ok",
|
||||||
"result": {
|
"result": {
|
||||||
"ToolOutput": {
|
"kind": "tool_output",
|
||||||
"summary": if content.is_some() {
|
"summary": if content.is_some() {
|
||||||
"resident memory summary collected"
|
"resident memory summary collected"
|
||||||
} else {
|
} else {
|
||||||
"resident memory summary unavailable"
|
"resident memory summary unavailable"
|
||||||
},
|
},
|
||||||
"content": content,
|
"content": content,
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
|
||||||
|
|
@ -1587,7 +1587,6 @@ fn create_working_directory_for_runtime(
|
||||||
resolved_commit: None,
|
resolved_commit: None,
|
||||||
materialization_status: "pending".to_string(),
|
materialization_status: "pending".to_string(),
|
||||||
cleanliness: "unknown".to_string(),
|
cleanliness: "unknown".to_string(),
|
||||||
management_kind: "backend_managed".to_string(),
|
|
||||||
created_at: now_registry_timestamp(),
|
created_at: now_registry_timestamp(),
|
||||||
updated_at: now_registry_timestamp(),
|
updated_at: now_registry_timestamp(),
|
||||||
};
|
};
|
||||||
|
|
@ -1610,12 +1609,7 @@ fn create_working_directory_for_runtime(
|
||||||
result.diagnostics,
|
result.diagnostics,
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
let record = workdir_record_from_summary(
|
let record = workdir_record_from_summary(&api, &runtime_id, &working_directory.summary);
|
||||||
&api,
|
|
||||||
&runtime_id,
|
|
||||||
&working_directory.summary,
|
|
||||||
"backend_managed",
|
|
||||||
);
|
|
||||||
api.store.upsert_workdir_registry(&record)?;
|
api.store.upsert_workdir_registry(&record)?;
|
||||||
Ok(Json(BrowserWorkingDirectoryDetailResponse {
|
Ok(Json(BrowserWorkingDirectoryDetailResponse {
|
||||||
workspace_id: api.config.workspace_id.clone(),
|
workspace_id: api.config.workspace_id.clone(),
|
||||||
|
|
@ -1634,17 +1628,7 @@ fn working_directory_detail_for_runtime(
|
||||||
.working_directory(runtime_id, working_directory_id)
|
.working_directory(runtime_id, working_directory_id)
|
||||||
.map_err(|err| err.into_error())?;
|
.map_err(|err| err.into_error())?;
|
||||||
if let Some(working_directory) = result.working_directory {
|
if let Some(working_directory) = result.working_directory {
|
||||||
let management_kind = api
|
let record = workdir_record_from_summary(&api, runtime_id, &working_directory.summary);
|
||||||
.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(),
|
|
||||||
);
|
|
||||||
api.store.upsert_workdir_registry(&record)?;
|
api.store.upsert_workdir_registry(&record)?;
|
||||||
return Ok(Json(BrowserWorkingDirectoryDetailResponse {
|
return Ok(Json(BrowserWorkingDirectoryDetailResponse {
|
||||||
workspace_id: api.config.workspace_id.clone(),
|
workspace_id: api.config.workspace_id.clone(),
|
||||||
|
|
@ -1711,17 +1695,7 @@ fn cleanup_working_directory_for_runtime(
|
||||||
result.diagnostics,
|
result.diagnostics,
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
let management_kind = api
|
let record = workdir_record_from_summary(&api, runtime_id, &working_directory.summary);
|
||||||
.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(),
|
|
||||||
);
|
|
||||||
api.store.upsert_workdir_registry(&record)?;
|
api.store.upsert_workdir_registry(&record)?;
|
||||||
Ok(Json(BrowserWorkingDirectoryDetailResponse {
|
Ok(Json(BrowserWorkingDirectoryDetailResponse {
|
||||||
workspace_id: api.config.workspace_id.clone(),
|
workspace_id: api.config.workspace_id.clone(),
|
||||||
|
|
@ -3010,20 +2984,8 @@ async fn create_workspace_worker(
|
||||||
WorkerRegistryDisplayNamePolicy::UseProvided,
|
WorkerRegistryDisplayNamePolicy::UseProvided,
|
||||||
)?;
|
)?;
|
||||||
if let Some(working_directory) = worker.working_directory.as_ref() {
|
if let Some(working_directory) = worker.working_directory.as_ref() {
|
||||||
let management_kind = api
|
let workdir_record =
|
||||||
.store
|
workdir_record_from_summary(&api, worker.runtime_id.as_str(), working_directory);
|
||||||
.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(),
|
|
||||||
);
|
|
||||||
api.store.upsert_workdir_registry(&workdir_record)?;
|
api.store.upsert_workdir_registry(&workdir_record)?;
|
||||||
link_worker_to_workdir(
|
link_worker_to_workdir(
|
||||||
&api,
|
&api,
|
||||||
|
|
@ -3047,7 +3009,6 @@ async fn create_workspace_worker(
|
||||||
&api,
|
&api,
|
||||||
worker.runtime_id.as_str(),
|
worker.runtime_id.as_str(),
|
||||||
&status.summary,
|
&status.summary,
|
||||||
"runtime_unmanaged",
|
|
||||||
);
|
);
|
||||||
api.store.upsert_workdir_registry(&record)?;
|
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 _ = sync_all_runtime_workdir_observations(api);
|
||||||
let records = api
|
let records = api
|
||||||
.store
|
.store
|
||||||
.list_managed_workdir_registry(&api.config.workspace_id, 200)?;
|
.list_workdir_registry(&api.config.workspace_id, 200)?;
|
||||||
Ok(records
|
Ok(records
|
||||||
.iter()
|
.iter()
|
||||||
.map(workdir_summary_from_record)
|
.map(workdir_summary_from_record)
|
||||||
|
|
@ -4619,20 +4580,8 @@ fn sync_worker_observation(
|
||||||
WorkerRegistryDisplayNamePolicy::PreserveExisting,
|
WorkerRegistryDisplayNamePolicy::PreserveExisting,
|
||||||
)?;
|
)?;
|
||||||
if let Some(working_directory) = worker.working_directory.as_ref() {
|
if let Some(working_directory) = worker.working_directory.as_ref() {
|
||||||
let management_kind = api
|
let workdir_record =
|
||||||
.store
|
workdir_record_from_summary(api, worker.runtime_id.as_str(), working_directory);
|
||||||
.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(),
|
|
||||||
);
|
|
||||||
api.store.upsert_workdir_registry(&workdir_record)?;
|
api.store.upsert_workdir_registry(&workdir_record)?;
|
||||||
link_worker_to_workdir(api, &record, &working_directory.working_directory_id)?;
|
link_worker_to_workdir(api, &record, &working_directory.working_directory_id)?;
|
||||||
}
|
}
|
||||||
|
|
@ -4663,7 +4612,6 @@ fn upsert_pending_backend_workdir(
|
||||||
resolved_commit: None,
|
resolved_commit: None,
|
||||||
materialization_status: "pending".to_string(),
|
materialization_status: "pending".to_string(),
|
||||||
cleanliness: "unknown".to_string(),
|
cleanliness: "unknown".to_string(),
|
||||||
management_kind: "backend_managed".to_string(),
|
|
||||||
created_at: timestamp.clone(),
|
created_at: timestamp.clone(),
|
||||||
updated_at: timestamp,
|
updated_at: timestamp,
|
||||||
})?;
|
})?;
|
||||||
|
|
@ -4692,12 +4640,7 @@ fn sync_runtime_workdir_observations(
|
||||||
&api.config.workspace_id,
|
&api.config.workspace_id,
|
||||||
&status.summary.working_directory_id,
|
&status.summary.working_directory_id,
|
||||||
)?;
|
)?;
|
||||||
let management_kind = existing
|
let mut record = workdir_record_from_summary(api, runtime_id, &status.summary);
|
||||||
.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());
|
|
||||||
preserve_workdir_identity_for_corrupted_summary(&mut record, existing.as_ref());
|
preserve_workdir_identity_for_corrupted_summary(&mut record, existing.as_ref());
|
||||||
api.store.upsert_workdir_registry(&record)?;
|
api.store.upsert_workdir_registry(&record)?;
|
||||||
}
|
}
|
||||||
|
|
@ -4719,13 +4662,8 @@ fn sync_runtime_workdir_observations(
|
||||||
record.workdir_id.as_str(),
|
record.workdir_id.as_str(),
|
||||||
)?;
|
)?;
|
||||||
} else {
|
} else {
|
||||||
let management_kind = record.management_kind.clone();
|
let mut updated =
|
||||||
let mut updated = workdir_record_from_summary(
|
workdir_record_from_summary(api, runtime_id, &status.summary);
|
||||||
api,
|
|
||||||
runtime_id,
|
|
||||||
&status.summary,
|
|
||||||
management_kind.as_str(),
|
|
||||||
);
|
|
||||||
preserve_workdir_identity_for_corrupted_summary(
|
preserve_workdir_identity_for_corrupted_summary(
|
||||||
&mut updated,
|
&mut updated,
|
||||||
Some(&record),
|
Some(&record),
|
||||||
|
|
@ -4792,17 +4730,7 @@ fn sync_linked_workdir_after_worker_stop(
|
||||||
.working_directory(runtime_id, link.workdir_id.as_str())
|
.working_directory(runtime_id, link.workdir_id.as_str())
|
||||||
.map_err(|err| err.into_error())?;
|
.map_err(|err| err.into_error())?;
|
||||||
if let Some(status) = result.working_directory {
|
if let Some(status) = result.working_directory {
|
||||||
let management_kind = api
|
let record = workdir_record_from_summary(api, runtime_id, &status.summary);
|
||||||
.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(),
|
|
||||||
);
|
|
||||||
api.store.upsert_workdir_registry(&record)?;
|
api.store.upsert_workdir_registry(&record)?;
|
||||||
} else if let Some(mut record) = api
|
} else if let Some(mut record) = api
|
||||||
.store
|
.store
|
||||||
|
|
@ -4822,7 +4750,6 @@ fn workdir_record_from_summary(
|
||||||
api: &WorkspaceApi,
|
api: &WorkspaceApi,
|
||||||
runtime_id: &str,
|
runtime_id: &str,
|
||||||
summary: &WorkingDirectorySummary,
|
summary: &WorkingDirectorySummary,
|
||||||
management_kind: &str,
|
|
||||||
) -> WorkdirRegistryRecord {
|
) -> WorkdirRegistryRecord {
|
||||||
let timestamp = now_registry_timestamp();
|
let timestamp = now_registry_timestamp();
|
||||||
WorkdirRegistryRecord {
|
WorkdirRegistryRecord {
|
||||||
|
|
@ -4844,7 +4771,6 @@ fn workdir_record_from_summary(
|
||||||
.cleanliness
|
.cleanliness
|
||||||
.clone()
|
.clone()
|
||||||
.unwrap_or_else(|| "unknown".to_string()),
|
.unwrap_or_else(|| "unknown".to_string()),
|
||||||
management_kind: management_kind.to_string(),
|
|
||||||
created_at: timestamp.clone(),
|
created_at: timestamp.clone(),
|
||||||
updated_at: timestamp,
|
updated_at: timestamp,
|
||||||
}
|
}
|
||||||
|
|
@ -4895,7 +4821,6 @@ fn workdir_summary_from_record(record: &WorkdirRegistryRecord) -> WorkingDirecto
|
||||||
status,
|
status,
|
||||||
cleanliness: Some(record.cleanliness.clone()),
|
cleanliness: Some(record.cleanliness.clone()),
|
||||||
primary_worker_id: None,
|
primary_worker_id: None,
|
||||||
management_kind: Some(record.management_kind.clone()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5517,7 +5442,6 @@ mod tests {
|
||||||
resolved_commit: Some("abcdef".to_string()),
|
resolved_commit: Some("abcdef".to_string()),
|
||||||
materialization_status: "missing".to_string(),
|
materialization_status: "missing".to_string(),
|
||||||
cleanliness: "clean".to_string(),
|
cleanliness: "clean".to_string(),
|
||||||
management_kind: "backend_managed".to_string(),
|
|
||||||
created_at: "1".to_string(),
|
created_at: "1".to_string(),
|
||||||
updated_at: "3".to_string(),
|
updated_at: "3".to_string(),
|
||||||
};
|
};
|
||||||
|
|
@ -5538,22 +5462,13 @@ mod tests {
|
||||||
projected.working_directory.as_ref().unwrap().status,
|
projected.working_directory.as_ref().unwrap().status,
|
||||||
WorkingDirectoryStatusKind::NotFound
|
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();
|
let serialized = serde_json::to_string(&projected).unwrap();
|
||||||
assert!(!serialized.contains("/tmp/"));
|
assert!(!serialized.contains("/tmp/"));
|
||||||
assert!(!serialized.contains("materialized_path"));
|
assert!(!serialized.contains("materialized_path"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[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 dir = tempfile::tempdir().unwrap();
|
||||||
let api = test_api(dir.path()).await;
|
let api = test_api(dir.path()).await;
|
||||||
api.store
|
api.store
|
||||||
|
|
@ -5566,7 +5481,6 @@ mod tests {
|
||||||
resolved_commit: None,
|
resolved_commit: None,
|
||||||
materialization_status: "present".to_string(),
|
materialization_status: "present".to_string(),
|
||||||
cleanliness: "clean".to_string(),
|
cleanliness: "clean".to_string(),
|
||||||
management_kind: "backend_managed".to_string(),
|
|
||||||
created_at: "1".to_string(),
|
created_at: "1".to_string(),
|
||||||
updated_at: "1".to_string(),
|
updated_at: "1".to_string(),
|
||||||
})
|
})
|
||||||
|
|
@ -5581,29 +5495,29 @@ mod tests {
|
||||||
resolved_commit: None,
|
resolved_commit: None,
|
||||||
materialization_status: "present".to_string(),
|
materialization_status: "present".to_string(),
|
||||||
cleanliness: "unknown".to_string(),
|
cleanliness: "unknown".to_string(),
|
||||||
management_kind: "runtime_unmanaged".to_string(),
|
|
||||||
created_at: "1".to_string(),
|
created_at: "1".to_string(),
|
||||||
updated_at: "2".to_string(),
|
updated_at: "2".to_string(),
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let managed = working_directory_summaries(&api)
|
let summaries = working_directory_summaries(&api)
|
||||||
.unwrap_or_else(|err| panic!("working_directory_summaries failed: {}", err.error));
|
.unwrap_or_else(|err| panic!("working_directory_summaries failed: {}", err.error));
|
||||||
assert_eq!(managed.len(), 1);
|
let ids = summaries
|
||||||
assert_eq!(managed[0].working_directory_id, "managed");
|
.iter()
|
||||||
assert_eq!(
|
.map(|summary| summary.working_directory_id.as_str())
|
||||||
managed[0].management_kind.as_deref(),
|
.collect::<Vec<_>>();
|
||||||
Some("backend_managed")
|
assert!(ids.contains(&"managed"));
|
||||||
);
|
assert!(ids.contains(&"runtime-direct"));
|
||||||
|
|
||||||
let (runtime_projection, _) =
|
let (runtime_projection, _) =
|
||||||
runtime_working_directory_summaries(&api, EMBEDDED_WORKER_RUNTIME_ID).unwrap_or_else(
|
runtime_working_directory_summaries(&api, EMBEDDED_WORKER_RUNTIME_ID).unwrap_or_else(
|
||||||
|err| panic!("runtime_working_directory_summaries failed: {}", err.error),
|
|err| panic!("runtime_working_directory_summaries failed: {}", err.error),
|
||||||
);
|
);
|
||||||
assert!(runtime_projection.iter().any(|summary| {
|
assert!(
|
||||||
summary.working_directory_id == "runtime-direct"
|
runtime_projection
|
||||||
&& summary.management_kind.as_deref() == Some("runtime_unmanaged")
|
.iter()
|
||||||
}));
|
.any(|summary| summary.working_directory_id == "runtime-direct")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn unmanaged_runtime_workdir_projection_is_typed_and_diagnostic_safe() {
|
fn unmanaged_runtime_workdir_projection_is_typed_and_diagnostic_safe() {
|
||||||
|
|
@ -5616,7 +5530,6 @@ mod tests {
|
||||||
resolved_commit: None,
|
resolved_commit: None,
|
||||||
materialization_status: "present".to_string(),
|
materialization_status: "present".to_string(),
|
||||||
cleanliness: "unknown".to_string(),
|
cleanliness: "unknown".to_string(),
|
||||||
management_kind: "runtime_unmanaged".to_string(),
|
|
||||||
created_at: "1".to_string(),
|
created_at: "1".to_string(),
|
||||||
updated_at: "2".to_string(),
|
updated_at: "2".to_string(),
|
||||||
};
|
};
|
||||||
|
|
@ -5624,10 +5537,6 @@ mod tests {
|
||||||
let projected = workdir_summary_from_record(&workdir);
|
let projected = workdir_summary_from_record(&workdir);
|
||||||
|
|
||||||
assert_eq!(projected.status, WorkingDirectoryStatusKind::Active);
|
assert_eq!(projected.status, WorkingDirectoryStatusKind::Active);
|
||||||
assert_eq!(
|
|
||||||
projected.management_kind.as_deref(),
|
|
||||||
Some("runtime_unmanaged")
|
|
||||||
);
|
|
||||||
let serialized = serde_json::to_string(&projected).unwrap();
|
let serialized = serde_json::to_string(&projected).unwrap();
|
||||||
assert!(!serialized.contains("/tmp/"));
|
assert!(!serialized.contains("/tmp/"));
|
||||||
assert!(!serialized.contains("materialized_path"));
|
assert!(!serialized.contains("materialized_path"));
|
||||||
|
|
@ -6227,7 +6136,6 @@ mod tests {
|
||||||
workdir_id: workdir_id.to_string(),
|
workdir_id: workdir_id.to_string(),
|
||||||
runtime_id: "runtime-test".to_string(),
|
runtime_id: "runtime-test".to_string(),
|
||||||
repository_id: "repo-test".to_string(),
|
repository_id: "repo-test".to_string(),
|
||||||
management_kind: "backend_managed".to_string(),
|
|
||||||
selector: Some("HEAD".to_string()),
|
selector: Some("HEAD".to_string()),
|
||||||
resolved_commit: None,
|
resolved_commit: None,
|
||||||
materialization_status: status.to_string(),
|
materialization_status: status.to_string(),
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,11 @@ const MIGRATIONS: &[Migration] = &[
|
||||||
name: "add workdir runtime observation states",
|
name: "add workdir runtime observation states",
|
||||||
apply: 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 {
|
struct Migration {
|
||||||
|
|
@ -91,8 +96,6 @@ pub struct WorkdirRegistryRecord {
|
||||||
pub resolved_commit: Option<String>,
|
pub resolved_commit: Option<String>,
|
||||||
pub materialization_status: String,
|
pub materialization_status: String,
|
||||||
pub cleanliness: 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 created_at: String,
|
||||||
pub updated_at: String,
|
pub updated_at: String,
|
||||||
}
|
}
|
||||||
|
|
@ -152,11 +155,6 @@ pub trait ControlPlaneStore: Send + Sync {
|
||||||
workspace_id: &str,
|
workspace_id: &str,
|
||||||
limit: usize,
|
limit: usize,
|
||||||
) -> Result<Vec<WorkdirRegistryRecord>>;
|
) -> 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 delete_workdir_registry(&self, workspace_id: &str, workdir_id: &str) -> Result<bool>;
|
||||||
|
|
||||||
fn upsert_worker_workdir_link(&self, record: &WorkerWorkdirLinkRecord) -> Result<()>;
|
fn upsert_worker_workdir_link(&self, record: &WorkerWorkdirLinkRecord) -> Result<()>;
|
||||||
|
|
@ -378,8 +376,8 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
|
||||||
conn.execute(
|
conn.execute(
|
||||||
r#"INSERT INTO workdir_registry (
|
r#"INSERT INTO workdir_registry (
|
||||||
workspace_id, workdir_id, runtime_id, repository_id, selector, resolved_commit,
|
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
|
||||||
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)
|
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)
|
||||||
ON CONFLICT(workspace_id, workdir_id) DO UPDATE SET
|
ON CONFLICT(workspace_id, workdir_id) DO UPDATE SET
|
||||||
runtime_id = excluded.runtime_id,
|
runtime_id = excluded.runtime_id,
|
||||||
repository_id = excluded.repository_id,
|
repository_id = excluded.repository_id,
|
||||||
|
|
@ -387,7 +385,6 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
|
||||||
resolved_commit = excluded.resolved_commit,
|
resolved_commit = excluded.resolved_commit,
|
||||||
materialization_status = excluded.materialization_status,
|
materialization_status = excluded.materialization_status,
|
||||||
cleanliness = excluded.cleanliness,
|
cleanliness = excluded.cleanliness,
|
||||||
management_kind = excluded.management_kind,
|
|
||||||
updated_at = excluded.updated_at"#,
|
updated_at = excluded.updated_at"#,
|
||||||
params![
|
params![
|
||||||
record.workspace_id,
|
record.workspace_id,
|
||||||
|
|
@ -398,7 +395,6 @@ impl ControlPlaneStore for SqliteWorkspaceStore {
|
||||||
record.resolved_commit,
|
record.resolved_commit,
|
||||||
record.materialization_status,
|
record.materialization_status,
|
||||||
record.cleanliness,
|
record.cleanliness,
|
||||||
record.management_kind,
|
|
||||||
record.created_at,
|
record.created_at,
|
||||||
record.updated_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> {
|
fn delete_workdir_registry(&self, workspace_id: &str, workdir_id: &str) -> Result<bool> {
|
||||||
self.with_conn(|conn| {
|
self.with_conn(|conn| {
|
||||||
let changed = conn.execute(
|
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 {
|
fn workdir_registry_select_sql(where_clause: &str) -> String {
|
||||||
format!(
|
format!(
|
||||||
"SELECT workspace_id, workdir_id, runtime_id, repository_id, selector, resolved_commit, \
|
"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}"
|
FROM workdir_registry {where_clause}"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -594,9 +574,8 @@ fn read_workdir_registry_record(
|
||||||
resolved_commit: row.get(5)?,
|
resolved_commit: row.get(5)?,
|
||||||
materialization_status: row.get(6)?,
|
materialization_status: row.get(6)?,
|
||||||
cleanliness: row.get(7)?,
|
cleanliness: row.get(7)?,
|
||||||
management_kind: row.get(8)?,
|
created_at: row.get(8)?,
|
||||||
created_at: row.get(9)?,
|
updated_at: row.get(9)?,
|
||||||
updated_at: row.get(10)?,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -629,7 +608,6 @@ CREATE TABLE IF NOT EXISTS workdir_registry (
|
||||||
resolved_commit TEXT,
|
resolved_commit TEXT,
|
||||||
materialization_status TEXT NOT NULL CHECK (materialization_status IN ('pending', 'present', 'not_found', 'corrupted', 'unknown', 'failed')),
|
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')),
|
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,
|
created_at TEXT NOT NULL,
|
||||||
updated_at TEXT NOT NULL,
|
updated_at TEXT NOT NULL,
|
||||||
PRIMARY KEY (workspace_id, workdir_id),
|
PRIMARY KEY (workspace_id, workdir_id),
|
||||||
|
|
@ -948,7 +926,6 @@ fn add_workdir_runtime_observation_states(conn: &Connection) -> Result<()> {
|
||||||
resolved_commit TEXT,
|
resolved_commit TEXT,
|
||||||
materialization_status TEXT NOT NULL CHECK (materialization_status IN ('pending', 'present', 'not_found', 'corrupted', 'unknown', 'failed')),
|
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')),
|
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,
|
created_at TEXT NOT NULL,
|
||||||
updated_at TEXT NOT NULL,
|
updated_at TEXT NOT NULL,
|
||||||
PRIMARY KEY (workspace_id, workdir_id),
|
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 (
|
INSERT OR REPLACE INTO workdir_registry_v6 (
|
||||||
workspace_id, workdir_id, runtime_id, repository_id, selector, resolved_commit,
|
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
|
SELECT
|
||||||
workspace_id, workdir_id, runtime_id, repository_id, selector, resolved_commit,
|
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'
|
WHEN 'removed' THEN 'not_found'
|
||||||
ELSE materialization_status
|
ELSE materialization_status
|
||||||
END,
|
END,
|
||||||
cleanliness, management_kind, created_at, updated_at
|
cleanliness, created_at, updated_at
|
||||||
FROM workdir_registry;
|
FROM workdir_registry;
|
||||||
DROP TABLE workdir_registry;
|
DROP TABLE workdir_registry;
|
||||||
ALTER TABLE workdir_registry_v6 RENAME TO 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(())
|
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<()> {
|
fn create_schema_v0_tables(conn: &Connection) -> Result<()> {
|
||||||
conn.execute_batch(
|
conn.execute_batch(
|
||||||
r#"
|
r#"
|
||||||
|
|
@ -1199,7 +1218,7 @@ mod tests {
|
||||||
let db = dir.path().join("control-plane.sqlite");
|
let db = dir.path().join("control-plane.sqlite");
|
||||||
let store = SqliteWorkspaceStore::open(&db).unwrap();
|
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 {
|
let record = WorkspaceRecord {
|
||||||
workspace_id: "local-dev".to_string(),
|
workspace_id: "local-dev".to_string(),
|
||||||
|
|
@ -1211,7 +1230,7 @@ mod tests {
|
||||||
store.upsert_workspace(&record).await.unwrap();
|
store.upsert_workspace(&record).await.unwrap();
|
||||||
|
|
||||||
let reopened = SqliteWorkspaceStore::open(&db).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!(
|
assert_eq!(
|
||||||
reopened.get_workspace("local-dev").await.unwrap(),
|
reopened.get_workspace("local-dev").await.unwrap(),
|
||||||
Some(record)
|
Some(record)
|
||||||
|
|
@ -1420,7 +1439,7 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let store = SqliteWorkspaceStore::from_connection(conn).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
|
store
|
||||||
.with_conn(|conn| {
|
.with_conn(|conn| {
|
||||||
|
|
@ -1550,7 +1569,6 @@ mod tests {
|
||||||
resolved_commit: Some("abcdef".to_string()),
|
resolved_commit: Some("abcdef".to_string()),
|
||||||
materialization_status: "not_found".to_string(),
|
materialization_status: "not_found".to_string(),
|
||||||
cleanliness: "clean".to_string(),
|
cleanliness: "clean".to_string(),
|
||||||
management_kind: "backend_managed".to_string(),
|
|
||||||
created_at: "2".to_string(),
|
created_at: "2".to_string(),
|
||||||
updated_at: "3".to_string(),
|
updated_at: "3".to_string(),
|
||||||
};
|
};
|
||||||
|
|
@ -1564,7 +1582,6 @@ mod tests {
|
||||||
resolved_commit: Some("123456".to_string()),
|
resolved_commit: Some("123456".to_string()),
|
||||||
materialization_status: "present".to_string(),
|
materialization_status: "present".to_string(),
|
||||||
cleanliness: "unknown".to_string(),
|
cleanliness: "unknown".to_string(),
|
||||||
management_kind: "runtime_unmanaged".to_string(),
|
|
||||||
created_at: "3".to_string(),
|
created_at: "3".to_string(),
|
||||||
updated_at: "4".to_string(),
|
updated_at: "4".to_string(),
|
||||||
};
|
};
|
||||||
|
|
@ -1597,12 +1614,6 @@ mod tests {
|
||||||
store.list_workdir_registry("local-dev", 10).unwrap(),
|
store.list_workdir_registry("local-dev", 10).unwrap(),
|
||||||
vec![unmanaged_workdir.clone(), workdir.clone()]
|
vec![unmanaged_workdir.clone(), workdir.clone()]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
|
||||||
store
|
|
||||||
.list_managed_workdir_registry("local-dev", 10)
|
|
||||||
.unwrap(),
|
|
||||||
vec![workdir]
|
|
||||||
);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
store
|
store
|
||||||
.list_worker_workdir_links("local-dev", "embedded", 1)
|
.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
|
let
|
||||||
pkgs = nixpkgs.legacyPackages.${system};
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
yoi = pkgs.callPackage ./package.nix { };
|
yoi = pkgs.callPackage ./package.nix { };
|
||||||
|
dockerImages =
|
||||||
|
if pkgs.stdenv.isLinux then
|
||||||
|
import ./docker.nix {
|
||||||
|
inherit pkgs yoi;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ };
|
||||||
mkApp = name: description: {
|
mkApp = name: description: {
|
||||||
type = "app";
|
type = "app";
|
||||||
program = "${yoi}/bin/${name}";
|
program = "${yoi}/bin/${name}";
|
||||||
|
|
@ -24,8 +31,16 @@
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
packages.default = yoi;
|
packages = {
|
||||||
packages.yoi = yoi;
|
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.default = mkApp "yoi" "Run the Yoi terminal UI";
|
||||||
apps.yoi = 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;
|
filter = sourceFilter;
|
||||||
};
|
};
|
||||||
|
|
||||||
cargoHash = "sha256-MUEySmRu5Te8+GCwlt3qk7jxcumYIMvnuvj64o2XuDw=";
|
cargoHash = "sha256-iZaTREhL/aLixn67A1+Gi9opqh2j/yyFuGMyBBvuATM=";
|
||||||
|
|
||||||
depsExtraArgs = {
|
depsExtraArgs = {
|
||||||
# Older fetchCargoVendor utilities used crates.io's API download endpoint,
|
# Older fetchCargoVendor utilities used crates.io's API download endpoint,
|
||||||
|
|
@ -93,6 +93,7 @@ rustPlatform.buildRustPackage rec {
|
||||||
|
|
||||||
postBuild = ''
|
postBuild = ''
|
||||||
cargo build --offline --profile release -p yoi-workspace-server --bin yoi-workspace-server
|
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
|
# 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)
|
yoi_bin=$(find . -type f -name yoi | head -n 1)
|
||||||
workspace_server_bin=$(find . -type f -name yoi-workspace-server | 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
|
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
|
exit 1
|
||||||
fi
|
fi
|
||||||
install -Dm755 "$yoi_bin" "$out/bin/yoi"
|
install -Dm755 "$yoi_bin" "$out/bin/yoi"
|
||||||
install -Dm755 "$workspace_server_bin" "$out/bin/yoi-workspace-server"
|
install -Dm755 "$workspace_server_bin" "$out/bin/yoi-workspace-server"
|
||||||
|
install -Dm755 "$worker_runtime_bin" "$out/bin/worker-runtime-rest-server"
|
||||||
|
|
||||||
runHook postInstall
|
runHook postInstall
|
||||||
'';
|
'';
|
||||||
|
|
@ -123,7 +126,9 @@ rustPlatform.buildRustPackage rec {
|
||||||
"$out/bin/yoi" worker --help >/dev/null
|
"$out/bin/yoi" worker --help >/dev/null
|
||||||
test -x "$out/bin/yoi"
|
test -x "$out/bin/yoi"
|
||||||
test -x "$out/bin/yoi-workspace-server"
|
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/yoi-workspace-server" --help >/dev/null
|
||||||
|
"$out/bin/worker-runtime-rest-server" --help >/dev/null
|
||||||
test ! -e "$out/bin/yoi-pod"
|
test ! -e "$out/bin/yoi-pod"
|
||||||
test ! -e "$out/share/yoi/resources"
|
test ! -e "$out/share/yoi/resources"
|
||||||
if "$out/bin/yoi" --session not-a-uuid 2>yoi.err; then
|
if "$out/bin/yoi" --session not-a-uuid 2>yoi.err; then
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,6 @@ export type WorkingDirectorySummary = {
|
||||||
status: string;
|
status: string;
|
||||||
cleanliness?: string | null;
|
cleanliness?: string | null;
|
||||||
primary_worker_id?: number | null;
|
primary_worker_id?: number | null;
|
||||||
management_kind?: "backend_managed" | "runtime_unmanaged" | string | null;
|
|
||||||
cleanup_target: {
|
cleanup_target: {
|
||||||
kind: string;
|
kind: string;
|
||||||
working_directory_id: string;
|
working_directory_id: string;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user