diff --git a/TASKS.md b/TASKS.md index fb7b1fa..30a6dc4 100644 --- a/TASKS.md +++ b/TASKS.md @@ -2,19 +2,20 @@ ## Current -### `feat/alpine-api-keys` +### `feat/alpine-settings-workspace` Status: completed DoD: -- `API Keys` page читает live keys catalog из `admin-api` -- `API Keys` page использует live `create/revoke/delete` flow вместо `keys.json` -- scope model и тексты в UI выровнены с реальным backend +- `Workspace setup` page использует live create/edit workspace flow через `admin-api` +- список workspace обновляется после create/edit без локальных моков +- members и invitations читаются из live backend +- create workspace умеет отправлять initial invites - `test-ui` остается нетронутым как fallback ## Next -- `feat/alpine-logs-usage` +- `feat/alpine-login-auth-gap` ## Backlog diff --git a/apps/admin-api/src/service.rs b/apps/admin-api/src/service.rs index d808d0c..b59c991 100644 --- a/apps/admin-api/src/service.rs +++ b/apps/admin-api/src/service.rs @@ -317,6 +317,21 @@ pub struct OperationAgentRefView { pub display_name: String, } +struct InvocationRecordRequest<'a> { + workspace_id: &'a WorkspaceId, + agent_id: Option<&'a AgentId>, + operation: &'a RegistryOperation, + source: InvocationSource, + level: InvocationLevel, + status: InvocationStatus, + message: String, + status_code: Option, + error_kind: Option, + duration_ms: u64, + request_preview: Value, + response_preview: Value, +} + #[derive(Clone, Debug, Serialize)] pub struct OperationSummaryView { pub id: String, @@ -1110,20 +1125,20 @@ impl AdminService { match build_request_preview(&record.snapshot.input_mapping, &payload.input) { Ok(preview) => preview, Err(error) => { - self.record_invocation( + self.record_invocation(InvocationRecordRequest { workspace_id, - None, - &record.snapshot, - InvocationSource::AdminTestRun, - InvocationLevel::Error, - InvocationStatus::Error, - "mapping preview failed".to_owned(), - None, - Some("mapping".to_owned()), - 0, - Value::Null, - Value::Null, - ) + agent_id: None, + operation: &record.snapshot, + source: InvocationSource::AdminTestRun, + level: InvocationLevel::Error, + status: InvocationStatus::Error, + message: "mapping preview failed".to_owned(), + status_code: None, + error_kind: Some("mapping".to_owned()), + duration_ms: 0, + request_preview: Value::Null, + response_preview: Value::Null, + }) .await?; return Ok(TestRunResult { ok: false, @@ -1141,20 +1156,20 @@ impl AdminService { Ok(output) => { let duration_ms = u64::try_from(started_at.elapsed().as_millis()).unwrap_or(u64::MAX); - self.record_invocation( + self.record_invocation(InvocationRecordRequest { workspace_id, - None, - &record.snapshot, - InvocationSource::AdminTestRun, - InvocationLevel::Info, - InvocationStatus::Ok, - "admin test run completed".to_owned(), - None, - None, + agent_id: None, + operation: &record.snapshot, + source: InvocationSource::AdminTestRun, + level: InvocationLevel::Info, + status: InvocationStatus::Ok, + message: "admin test run completed".to_owned(), + status_code: None, + error_kind: None, duration_ms, - request_preview.clone(), - output.clone(), - ) + request_preview: request_preview.clone(), + response_preview: output.clone(), + }) .await?; Ok(TestRunResult { ok: true, @@ -1166,20 +1181,20 @@ impl AdminService { Err(error) => { let duration_ms = u64::try_from(started_at.elapsed().as_millis()).unwrap_or(u64::MAX); - self.record_invocation( + self.record_invocation(InvocationRecordRequest { workspace_id, - None, - &record.snapshot, - InvocationSource::AdminTestRun, - InvocationLevel::Error, - InvocationStatus::Error, - error.to_string(), - None, - Some(runtime_error_code(&error).to_owned()), + agent_id: None, + operation: &record.snapshot, + source: InvocationSource::AdminTestRun, + level: InvocationLevel::Error, + status: InvocationStatus::Error, + message: error.to_string(), + status_code: None, + error_kind: Some(runtime_error_code(&error).to_owned()), duration_ms, - request_preview.clone(), - Value::Null, - ) + request_preview: request_preview.clone(), + response_preview: Value::Null, + }) .await?; Ok(TestRunResult { ok: false, @@ -1996,35 +2011,24 @@ impl AdminService { async fn record_invocation( &self, - workspace_id: &WorkspaceId, - agent_id: Option<&AgentId>, - operation: &RegistryOperation, - source: InvocationSource, - level: InvocationLevel, - status: InvocationStatus, - message: String, - status_code: Option, - error_kind: Option, - duration_ms: u64, - request_preview: Value, - response_preview: Value, + request: InvocationRecordRequest<'_>, ) -> Result<(), ApiError> { let log = InvocationLog { id: InvocationLogId::new(new_prefixed_id("log")), - workspace_id: workspace_id.clone(), - agent_id: agent_id.cloned(), - operation_id: operation.id.clone(), - source, - level, - status, - tool_name: operation.name.clone(), - message, + workspace_id: request.workspace_id.clone(), + agent_id: request.agent_id.cloned(), + operation_id: request.operation.id.clone(), + source: request.source, + level: request.level, + status: request.status, + tool_name: request.operation.name.clone(), + message: request.message, request_id: None, - status_code, - duration_ms, - error_kind, - request_preview, - response_preview, + status_code: request.status_code, + duration_ms: request.duration_ms, + error_kind: request.error_kind, + request_preview: request.request_preview, + response_preview: request.response_preview, created_at: now_string()?, }; diff --git a/apps/mcp-server/src/app.rs b/apps/mcp-server/src/app.rs index 8fcd434..a1f5c5d 100644 --- a/apps/mcp-server/src/app.rs +++ b/apps/mcp-server/src/app.rs @@ -244,14 +244,16 @@ async fn mcp_post( let _ = persist_invocation( &state, &tool, - InvocationStatus::Ok, - InvocationLevel::Info, - "agent tool call completed", - None, - None, - started_at.elapsed(), - request_preview, - output.clone(), + InvocationRecord { + status: InvocationStatus::Ok, + level: InvocationLevel::Info, + message: "agent tool call completed", + status_code: None, + error_kind: None, + duration: started_at.elapsed(), + request_preview, + response_preview: output.clone(), + }, ) .await; jsonrpc_result( @@ -275,14 +277,16 @@ async fn mcp_post( let _ = persist_invocation( &state, &tool, - InvocationStatus::Error, - InvocationLevel::Error, - &error.to_string(), - None, - Some(runtime_error_code(&error)), - started_at.elapsed(), - request_preview, - Value::Null, + InvocationRecord { + status: InvocationStatus::Error, + level: InvocationLevel::Error, + message: &error.to_string(), + status_code: None, + error_kind: Some(runtime_error_code(&error)), + duration: started_at.elapsed(), + request_preview, + response_preview: Value::Null, + }, ) .await; json_response( @@ -550,38 +554,42 @@ fn build_request_preview( } } -async fn persist_invocation( - state: &Arc, - tool: &PublishedAgentTool, +struct InvocationRecord<'a> { status: InvocationStatus, level: InvocationLevel, - message: &str, + message: &'a str, status_code: Option, - error_kind: Option<&str>, + error_kind: Option<&'a str>, duration: Duration, request_preview: Value, response_preview: Value, +} + +async fn persist_invocation( + state: &Arc, + tool: &PublishedAgentTool, + record: InvocationRecord<'_>, ) -> Result<(), crank_registry::RegistryError> { let created_at = OffsetDateTime::now_utc() .format(&Rfc3339) .unwrap_or_else(|_| "1970-01-01T00:00:00Z".to_owned()); - let duration_ms = u64::try_from(duration.as_millis()).unwrap_or(u64::MAX); + let duration_ms = u64::try_from(record.duration.as_millis()).unwrap_or(u64::MAX); let log = InvocationLog { id: InvocationLogId::new(format!("log_{}", uuid::Uuid::now_v7().simple())), workspace_id: tool.workspace_id.clone(), agent_id: Some(tool.agent_id.clone()), operation_id: tool.operation.id.clone(), source: InvocationSource::AgentToolCall, - level, - status, + level: record.level, + status: record.status, tool_name: tool.tool_name.clone(), - message: message.to_owned(), + message: record.message.to_owned(), request_id: None, - status_code, + status_code: record.status_code, duration_ms, - error_kind: error_kind.map(ToOwned::to_owned), - request_preview, - response_preview, + error_kind: record.error_kind.map(ToOwned::to_owned), + request_preview: record.request_preview, + response_preview: record.response_preview, created_at, }; diff --git a/apps/ui/html/logs.html b/apps/ui/html/logs.html index efbdc0c..4752db5 100644 --- a/apps/ui/html/logs.html +++ b/apps/ui/html/logs.html @@ -13,6 +13,7 @@ + diff --git a/apps/ui/html/settings.html b/apps/ui/html/settings.html index b6a8d1c..f651034 100644 --- a/apps/ui/html/settings.html +++ b/apps/ui/html/settings.html @@ -13,6 +13,7 @@ + @@ -101,6 +102,12 @@