worker: support typed initial submit on spawn

This commit is contained in:
2026-08-09 23:19:29 +09:00
parent 068ba9afa5
commit b9dadb6a08
13 changed files with 367 additions and 94 deletions
+2
View File
@@ -8,8 +8,10 @@ license.workspace = true
default = ["stream"]
stream = ["dep:tokio"]
typescript = ["dep:ts-rs"]
json-schema = ["dep:schemars"]
[dependencies]
schemars = { workspace = true, optional = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
tokio = { workspace = true, features = ["io-util"], optional = true }
+1
View File
@@ -183,6 +183,7 @@ impl WorkerEvent {
/// the dropped intent.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum Segment {
/// Free-form text. The fallback every client can produce.
+37
View File
@@ -3427,6 +3427,43 @@ mod tests {
);
}
#[test]
fn restore_does_not_redispatch_spawn_initial_submit() {
let backend = Arc::new(TestExecutionBackend::default());
let runtime = Runtime::with_execution_backend(
RuntimeOptions {
..RuntimeOptions::default()
},
backend.clone(),
)
.unwrap();
runtime.store_config_bundle(test_bundle()).unwrap();
let mut request = task_request("flow restore");
request.initial_input = Some(WorkerInput {
kind: WorkerInputKind::User,
content: String::new(),
segments: Some(vec![
protocol::Segment::Flow {
selector: "builtin:coder-review".to_string(),
},
protocol::Segment::text("Implement Ticket 00001"),
]),
});
let detail = runtime.create_worker(request).unwrap();
assert_eq!(backend.dispatched_inputs.lock().unwrap().len(), 1);
runtime
.stop_worker(&detail.worker_ref, Some("restore test".to_string()))
.unwrap();
runtime.restore_worker(&detail.worker_ref).unwrap();
assert_eq!(
backend.dispatched_inputs.lock().unwrap().len(),
1,
"restore must continue durable Worker state without replaying spawn initial input"
);
}
#[test]
fn send_input_dispatches_segment_only_flow_submission() {
let backend = Arc::new(TestExecutionBackend::default());
+1 -1
View File
@@ -16,7 +16,7 @@ session-store = { workspace = true }
secrets = { workspace = true }
manifest = { workspace = true }
mcp = { workspace = true }
protocol = { workspace = true }
protocol = { workspace = true, features = ["json-schema"] }
client = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
@@ -9,6 +9,8 @@ use llm_engine::tool::{
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use protocol::Segment;
use crate::feature::{
FeatureDescriptor, FeatureInstallContext, FeatureInstallError, FeatureModule, ToolContribution,
ToolDeclaration,
@@ -96,8 +98,10 @@ struct WorkerSpawnInput {
profile: String,
#[serde(default)]
display_name: Option<String>,
/// Normal typed initial user submission delivered after spawn. An empty
/// vector starts the Worker without initial input.
#[serde(default)]
initial_text: Option<String>,
initial_submit: Vec<Segment>,
#[serde(default)]
relative_cwd: Option<String>,
}
@@ -107,7 +111,7 @@ struct WorkerSpawnRequest {
runtime_id: String,
display_name: String,
profile: String,
initial_text: String,
initial_submit: Vec<Segment>,
working_directory: WorkerWorkingDirectorySelection,
}
@@ -197,7 +201,7 @@ impl Tool for WorkspaceWorkerTool {
.filter(|value| !value.trim().is_empty())
.unwrap_or_else(|| "Workspace Worker".to_string()),
profile: non_empty(input.profile, "profile")?,
initial_text: input.initial_text.unwrap_or_default(),
initial_submit: input.initial_submit,
working_directory: WorkerWorkingDirectorySelection {
working_directory_id: authority_id(
&input.working_directory_id,
@@ -320,7 +324,78 @@ fn validate_relative_cwd(value: &str) -> Result<String, ToolError> {
#[cfg(test)]
mod tests {
use std::sync::Mutex;
use super::*;
use crate::worker::{WorkspaceClientError, WorkspaceResponse};
#[derive(Debug, Default)]
struct RecordingWorkspaceClient {
requests: Mutex<Vec<WorkspaceRequest>>,
}
impl WorkspaceClient for RecordingWorkspaceClient {
fn workspace_id(&self) -> Option<&str> {
Some("workspace/test")
}
fn kind(&self) -> &str {
"recording"
}
fn is_available(&self) -> bool {
true
}
fn execute(
&self,
request: WorkspaceRequest,
) -> Result<WorkspaceResponse, WorkspaceClientError> {
self.requests.lock().unwrap().push(request);
Ok(WorkspaceResponse {
status: 200,
body: "{}".to_string(),
})
}
}
#[tokio::test]
async fn worker_spawn_forwards_typed_initial_submit_to_workspace_api() {
let client = Arc::new(RecordingWorkspaceClient::default());
let tool = WorkspaceWorkerTool {
operation: WorkerOperation::Spawn,
client: client.clone(),
workspace_id: "workspace%2Ftest".to_string(),
};
tool.execute(
&serde_json::json!({
"runtime_id": "runtime-1",
"working_directory_id": "workdir-1",
"profile": "builtin:coder",
"initial_submit": [
{ "kind": "flow", "selector": "builtin:coder-review" },
{ "kind": "text", "content": "Implement Ticket 00001" }
]
})
.to_string(),
ToolExecutionContext::direct(),
)
.await
.unwrap();
let requests = client.requests.lock().unwrap();
assert_eq!(requests.len(), 1);
assert_eq!(requests[0].path, "/api/w/workspace%2Ftest/workers");
let body: serde_json::Value =
serde_json::from_str(requests[0].body.as_deref().unwrap()).unwrap();
assert_eq!(body["initial_submit"][0]["kind"], "flow");
assert_eq!(
body["initial_submit"][0]["selector"],
"builtin:coder-review"
);
assert_eq!(body["initial_submit"][1]["kind"], "text");
assert!(body.get("initial_text").is_none());
}
#[test]
fn worker_tool_family_is_distinct_from_sub_worker_tools() {
@@ -330,13 +405,28 @@ mod tests {
);
}
#[test]
fn worker_spawn_schema_exposes_normal_typed_segment_variants() {
let schema = serde_json::to_value(schemars::schema_for!(WorkerSpawnInput)).unwrap();
let text = serde_json::to_string(&schema).unwrap();
assert!(text.contains("initial_submit"));
assert!(text.contains("selector"));
assert!(text.contains("flow"));
assert!(!text.contains("initial_text"));
}
#[test]
fn worker_spawn_request_uses_authority_ids_without_runtime_paths() {
let request = WorkerSpawnRequest {
runtime_id: "runtime-1".to_string(),
display_name: "Coder".to_string(),
profile: "builtin:coder".to_string(),
initial_text: "Implement the Ticket".to_string(),
initial_submit: vec![
Segment::Flow {
selector: "builtin:coder-review".to_string(),
},
Segment::text("Implement the Ticket"),
],
working_directory: WorkerWorkingDirectorySelection {
working_directory_id: "wd-1".to_string(),
relative_cwd: Some("repo".to_string()),
@@ -348,6 +438,13 @@ mod tests {
assert!(value.get("cwd").is_none());
assert!(value.get("runtime_url").is_none());
assert!(value["working_directory"].get("mode").is_none());
assert_eq!(value["initial_submit"][0]["kind"], "flow");
assert_eq!(
value["initial_submit"][0]["selector"],
"builtin:coder-review"
);
assert_eq!(value["initial_submit"][1]["kind"], "text");
assert!(value.get("initial_text").is_none());
}
#[test]
+83 -28
View File
@@ -1,6 +1,7 @@
use crate::Error;
use crate::resource_broker::{BackendResourceBroker, BackendResourceTarget};
use chrono::Utc;
use protocol::Segment;
use reqwest::blocking::{Client as BlockingHttpClient, RequestBuilder};
use reqwest::header::{AUTHORIZATION, CONTENT_TYPE};
use reqwest::{Client as AsyncHttpClient, StatusCode, Url};
@@ -359,8 +360,8 @@ pub struct WorkerSpawnRequest {
pub profile: ProfileSelector,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ticket_assignment: Option<WorkerTicketAssignmentRequest>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub initial_input: Option<EmbeddedWorkerInput>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub initial_submit: Vec<Segment>,
/// Optional safe working-directory creation request. The Workspace server resolves
/// this into a runtime-internal `WorkingDirectoryRequest` from configured
/// repositories before calling a host.
@@ -410,6 +411,17 @@ pub enum WorkerSpawnAcceptanceRequirement {
RunAccepted { expected_segments: usize },
}
fn initial_worker_input(segments: &[Segment]) -> Option<EmbeddedWorkerInput> {
if segments.is_empty() {
return None;
}
Some(EmbeddedWorkerInput {
kind: EmbeddedWorkerInputKind::User,
content: Segment::flatten_to_text(segments),
segments: Some(segments.to_vec()),
})
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct WorkerSpawnResult {
pub state: WorkerOperationState,
@@ -1141,6 +1153,30 @@ impl RuntimeRegistry {
request: WorkerSpawnRequest,
) -> Result<WorkerSpawnResult, RuntimeRegistryError> {
validate_backend_identifier("runtime_id", runtime_id)?;
match request.acceptance {
WorkerSpawnAcceptanceRequirement::RunAccepted { expected_segments }
if expected_segments != request.initial_submit.len() =>
{
return Err(RuntimeRegistryError::RuntimeOperationFailed {
runtime_id: runtime_id.to_string(),
code: "worker_initial_segment_count_mismatch".to_string(),
message: format!(
"spawn acceptance expects {expected_segments} initial segment(s), request carries {}",
request.initial_submit.len()
),
});
}
WorkerSpawnAcceptanceRequirement::SocketReady if !request.initial_submit.is_empty() => {
return Err(RuntimeRegistryError::RuntimeOperationFailed {
runtime_id: runtime_id.to_string(),
code: "worker_initial_submit_require_run_acceptance".to_string(),
message:
"spawn requests with initial segments must require RunAccepted acceptance"
.to_string(),
});
}
_ => {}
}
let runtime = self.runtime(runtime_id)?;
Ok(runtime.spawn_worker(request))
}
@@ -1865,7 +1901,7 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
display_name: request.requested_worker_name.clone(),
config_bundle: None,
profile_source,
initial_input: request.initial_input.clone(),
initial_input: initial_worker_input(&request.initial_submit),
working_directory_request: request.resolved_working_directory_request.clone(),
working_directory: request.resolved_working_directory.clone(),
worker_observation_enabled: request.resolved_worker_observation_enabled,
@@ -2970,7 +3006,7 @@ impl WorkspaceWorkerRuntime for RemoteWorkerRuntime {
display_name: request.requested_worker_name.clone(),
config_bundle: None,
profile_source,
initial_input: request.initial_input.clone(),
initial_input: initial_worker_input(&request.initial_submit),
working_directory_request: request.resolved_working_directory_request.clone(),
working_directory: request.resolved_working_directory.clone(),
worker_observation_enabled: request.resolved_worker_observation_enabled,
@@ -4551,7 +4587,7 @@ mod tests {
},
profile: ProfileSelector::Builtin("builtin:coder".to_string()),
ticket_assignment: None,
initial_input: None,
initial_submit: Vec::new(),
working_directory_request: None,
resolved_working_directory_request: None,
resolved_working_directory: None,
@@ -4601,29 +4637,48 @@ mod tests {
}
#[test]
fn embedded_runtime_rejects_system_initial_input_without_worker_projection() {
let runtime = EmbeddedWorkerRuntime::new_memory_with_execution_backend(
"local:test",
Arc::new(AcceptingExecutionBackend::default()),
)
.expect("test backend should connect");
fn worker_spawn_idempotency_fingerprint_covers_canonical_initial_submit() {
let mut request = embedded_spawn_request();
request.initial_input = Some(EmbeddedWorkerInput {
kind: EmbeddedWorkerInputKind::Notify,
content: "system/role instruction belongs in profile".to_string(),
segments: None,
request.ticket_assignment = Some(WorkerTicketAssignmentRequest {
ticket_id: "00001KVZSGT0Q".to_string(),
operation_id: "operation-1".to_string(),
});
request.initial_submit = vec![
Segment::Flow {
selector: "builtin:coder-review".to_string(),
},
Segment::text("Implement Ticket 00001KVZSGT0Q"),
];
request.acceptance = WorkerSpawnAcceptanceRequirement::RunAccepted {
expected_segments: request.initial_submit.len(),
};
let spawned = runtime.spawn_worker(request);
assert_eq!(spawned.state, WorkerOperationState::Rejected);
assert!(spawned.worker.is_none());
assert!(spawned.diagnostics.iter().any(|diagnostic| {
diagnostic.code == "embedded_worker_initial_input_kind_invalid"
&& diagnostic
.message
.contains("initial worker input must be user input")
}));
assert!(runtime.list_workers(10).items.is_empty());
let first = worker_spawn_idempotency(&request).unwrap().unwrap();
let repeated = worker_spawn_idempotency(&request).unwrap().unwrap();
assert_eq!(first, repeated);
assert_eq!(first.0, "operation-1");
let mut changed = request.clone();
changed.initial_submit[1] = Segment::text("Different instruction");
let changed = worker_spawn_idempotency(&changed).unwrap().unwrap();
assert_ne!(first.1, changed.1);
}
#[test]
fn shared_spawn_projects_typed_initial_submit_to_runtime_user_input() {
let segments = vec![
Segment::Flow {
selector: "builtin:coder-review".to_string(),
},
Segment::text("Implement Ticket 00001"),
];
let input = initial_worker_input(&segments).expect("typed initial input");
assert_eq!(input.kind, EmbeddedWorkerInputKind::User);
assert_eq!(input.content, Segment::flatten_to_text(&segments));
assert_eq!(input.segments, Some(segments));
assert!(initial_worker_input(&[]).is_none());
}
#[test]
@@ -4702,7 +4757,7 @@ mod tests {
},
profile: ProfileSelector::Builtin("builtin:coder".to_string()),
ticket_assignment: None,
initial_input: None,
initial_submit: Vec::new(),
working_directory_request: None,
resolved_working_directory_request: None,
resolved_working_directory: None,
@@ -4799,7 +4854,7 @@ mod tests {
},
profile: ProfileSelector::Builtin("builtin:coder".to_string()),
ticket_assignment: None,
initial_input: None,
initial_submit: Vec::new(),
working_directory_request: None,
resolved_working_directory_request: None,
resolved_working_directory: None,
@@ -4835,7 +4890,7 @@ mod tests {
acceptance: WorkerSpawnAcceptanceRequirement::SocketReady,
profile: ProfileSelector::Builtin("builtin:companion".to_string()),
ticket_assignment: None,
initial_input: None,
initial_submit: Vec::new(),
working_directory_request: None,
resolved_working_directory_request: None,
resolved_working_directory: None,
+104 -45
View File
@@ -18,6 +18,7 @@ use memory::backend::{
MemoryBackendHttpResponse, MemoryBackendOperation, MemoryConsolidateStagingOperation,
MemoryConsolidationOutput,
};
use protocol::Segment;
use protocol::stream::{decode_method, encode_event};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
@@ -114,9 +115,6 @@ use worker_runtime::http_server::{
RuntimeHttpSummaryResponse, RuntimeHttpWorkerResponse, RuntimeHttpWorkersResponse,
};
use worker_runtime::identity::RuntimeWorkerRef;
use worker_runtime::interaction::{
WorkerInput as EmbeddedWorkerInput, WorkerInputKind as EmbeddedWorkerInputKind,
};
const EMBEDDED_WORKER_RUNTIME_ID: &str = "embedded-worker-runtime";
@@ -1558,12 +1556,13 @@ pub struct BrowserWorkspaceOrchestratorResponse {
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct BrowserCreateWorkerRequest {
pub struct CreateWorkspaceWorkerRequest {
pub runtime_id: String,
pub display_name: String,
#[serde(default)]
pub profile: Option<String>,
pub initial_text: String,
#[serde(default)]
pub initial_submit: Vec<Segment>,
#[serde(default)]
pub working_directory: Option<BrowserWorkerWorkingDirectorySelection>,
}
@@ -3949,11 +3948,7 @@ fn start_memory_staging_consolidation(
&api.config.workspace_created_at,
MEMORY_CONSOLIDATION_PROFILE,
)?;
let input = EmbeddedWorkerInput {
kind: EmbeddedWorkerInputKind::User,
content: input_content,
segments: None,
};
let initial_submit = vec![Segment::text(input_content)];
let result = api.spawn_workspace_worker(
&runtime_id,
WorkerSpawnRequest {
@@ -3964,7 +3959,7 @@ fn start_memory_staging_consolidation(
},
profile: profile_selector,
ticket_assignment: None,
initial_input: Some(input),
initial_submit,
working_directory_request: None,
resolved_working_directory_request: None,
resolved_working_directory: None,
@@ -4510,7 +4505,7 @@ async fn scoped_start_workspace_orchestrator(
},
profile: ProfileSelector::Builtin("builtin:orchestrator".to_string()),
ticket_assignment: None,
initial_input: None,
initial_submit: Vec::new(),
working_directory_request: None,
resolved_working_directory_request: None,
resolved_working_directory: None,
@@ -4574,7 +4569,7 @@ fn workspace_orchestrator_is_online(worker: &WorkerSummary) -> bool {
async fn scoped_create_workspace_worker(
State(api): State<WorkspaceApi>,
AxumPath(path): AxumPath<ScopedWorkspacePath>,
Json(request): Json<BrowserCreateWorkerRequest>,
Json(request): Json<CreateWorkspaceWorkerRequest>,
) -> ApiResult<Json<BrowserCreateWorkerResponse>> {
validate_workspace_scope(&api, &path.workspace_id)?;
create_workspace_worker(State(api), Json(request)).await
@@ -6832,9 +6827,38 @@ fn configured_working_directory_request(
))
}
fn validate_worker_initial_submit(segments: &[Segment]) -> Result<()> {
let flow_selectors = segments
.iter()
.filter_map(|segment| match segment {
Segment::Flow { selector } => Some(selector),
_ => None,
})
.collect::<Vec<_>>();
if flow_selectors.len() > 1 {
return Err(Error::InvalidInput(
"initial_submit may contain at most one Flow segment".to_string(),
));
}
if let Some(selector) = flow_selectors.first() {
selector
.parse::<flow::FlowSelector>()
.map_err(|error| Error::InvalidInput(error.to_string()))?;
}
if segments
.iter()
.any(|segment| matches!(segment, Segment::Unknown))
{
return Err(Error::InvalidInput(
"initial_submit must not contain unknown segment variants".to_string(),
));
}
Ok(())
}
async fn create_workspace_worker(
State(api): State<WorkspaceApi>,
Json(request): Json<BrowserCreateWorkerRequest>,
Json(request): Json<CreateWorkspaceWorkerRequest>,
) -> ApiResult<Json<BrowserCreateWorkerResponse>> {
let profile = request
.profile
@@ -6883,16 +6907,9 @@ async fn create_workspace_worker(
if display_name == crate::hosts::WORKSPACE_ORCHESTRATOR_SINGLETON_KEY {
return Err(Error::ReservedWorkerName(display_name).into());
}
let initial_text = request.initial_text.trim().to_string();
let initial_input = if initial_text.is_empty() {
None
} else {
Some(EmbeddedWorkerInput {
kind: EmbeddedWorkerInputKind::User,
content: initial_text.clone(),
segments: None,
})
};
let initial_submit = request.initial_submit;
validate_worker_initial_submit(&initial_submit)?;
let expected_segments = initial_submit.len();
let selected_working_directory_id = request
.working_directory
.as_ref()
@@ -6914,12 +6931,10 @@ async fn create_workspace_worker(
WorkerSpawnRequest {
requested_worker_name: Some(display_name.clone()),
intent: WorkerSpawnIntent::WorkspaceCoding,
acceptance: WorkerSpawnAcceptanceRequirement::RunAccepted {
expected_segments: if initial_input.is_some() { 1 } else { 0 },
},
acceptance: WorkerSpawnAcceptanceRequirement::RunAccepted { expected_segments },
profile: profile_selector,
ticket_assignment: None,
initial_input,
initial_submit,
working_directory_request: None,
resolved_working_directory_request: None,
resolved_working_directory,
@@ -10212,6 +10227,50 @@ mod tests {
);
}
#[test]
fn worker_initial_submit_validate_flow_shape_before_spawn() {
let valid = vec![
Segment::Flow {
selector: "builtin:coder-review".to_string(),
},
Segment::text("Implement Ticket 00001"),
];
assert!(validate_worker_initial_submit(&valid).is_ok());
assert!(validate_worker_initial_submit(&[]).is_ok());
let duplicate = vec![
Segment::Flow {
selector: "builtin:coder-review".to_string(),
},
Segment::Flow {
selector: "workspace:coder-review".to_string(),
},
];
assert!(matches!(
validate_worker_initial_submit(&duplicate),
Err(Error::InvalidInput(message)) if message.contains("at most one")
));
assert!(matches!(
validate_worker_initial_submit(&[Segment::Flow {
selector: "coder-review".to_string(),
}]),
Err(Error::InvalidInput(_))
));
assert!(matches!(
validate_worker_initial_submit(&[Segment::Unknown]),
Err(Error::InvalidInput(message)) if message.contains("unknown")
));
assert!(
serde_json::from_value::<CreateWorkspaceWorkerRequest>(serde_json::json!({
"runtime_id": "runtime-1",
"display_name": "coder",
"initial_text": "legacy parallel authority",
"working_directory": { "kind": "without_workspace" }
}))
.is_err()
);
}
#[tokio::test]
async fn worker_source_auth_rejects_cross_workspace_mutation() {
let workspace = tempfile::tempdir().unwrap();
@@ -10219,11 +10278,11 @@ mod tests {
let api = test_api(workspace.path()).await;
let Json(created) = create_workspace_worker(
State(api.clone()),
Json(BrowserCreateWorkerRequest {
Json(CreateWorkspaceWorkerRequest {
runtime_id: EMBEDDED_WORKER_RUNTIME_ID.to_string(),
display_name: "Scoped Worker".to_string(),
profile: Some("builtin:coder".to_string()),
initial_text: String::new(),
initial_submit: Vec::new(),
working_directory: None,
}),
)
@@ -10252,11 +10311,11 @@ mod tests {
let Json(generic) = create_workspace_worker(
State(api.clone()),
Json(BrowserCreateWorkerRequest {
Json(CreateWorkspaceWorkerRequest {
runtime_id: EMBEDDED_WORKER_RUNTIME_ID.to_string(),
display_name: "Generic Orchestrator Profile Worker".to_string(),
profile: Some("builtin:orchestrator".to_string()),
initial_text: String::new(),
initial_submit: Vec::new(),
working_directory: None,
}),
)
@@ -10266,11 +10325,11 @@ mod tests {
assert!(find_workspace_orchestrator(&api).is_none());
let reserved = create_workspace_worker(
State(api.clone()),
Json(BrowserCreateWorkerRequest {
Json(CreateWorkspaceWorkerRequest {
runtime_id: EMBEDDED_WORKER_RUNTIME_ID.to_string(),
display_name: crate::hosts::WORKSPACE_ORCHESTRATOR_SINGLETON_KEY.to_string(),
profile: Some("builtin:orchestrator".to_string()),
initial_text: String::new(),
initial_submit: Vec::new(),
working_directory: None,
}),
)
@@ -11123,7 +11182,7 @@ mod tests {
},
profile: ProfileSelector::Builtin(MEMORY_CONSOLIDATION_PROFILE.to_string()),
ticket_assignment: None,
initial_input: None,
initial_submit: Vec::new(),
working_directory_request: None,
resolved_working_directory_request: None,
resolved_working_directory: None,
@@ -11305,7 +11364,7 @@ mod tests {
},
profile: ProfileSelector::Builtin("builtin:coder".to_string()),
ticket_assignment: None,
initial_input: None,
initial_submit: Vec::new(),
working_directory_request: None,
resolved_working_directory_request: None,
resolved_working_directory: None,
@@ -11521,7 +11580,7 @@ mod tests {
},
profile: ProfileSelector::Builtin("builtin:coder".to_string()),
ticket_assignment: None,
initial_input: None,
initial_submit: Vec::new(),
working_directory_request: None,
resolved_working_directory_request: None,
resolved_working_directory: None,
@@ -11663,7 +11722,7 @@ mod tests {
ticket_id: first_ticket.id.clone(),
operation_id: "spawn-assignment-operation".to_string(),
}),
initial_input: None,
initial_submit: Vec::new(),
working_directory_request: None,
resolved_working_directory_request: None,
resolved_working_directory: None,
@@ -11895,7 +11954,7 @@ mod tests {
},
profile: ProfileSelector::Builtin("builtin:coder".to_string()),
ticket_assignment: Some(assignment.clone()),
initial_input: None,
initial_submit: Vec::new(),
working_directory_request: None,
resolved_working_directory_request: None,
resolved_working_directory: None,
@@ -12812,7 +12871,7 @@ mod tests {
"runtime_id": "remote-runtime",
"display_name": "Coding Worker",
"profile": "builtin:coder",
"initial_text": "",
"initial_submit": [],
"working_directory": {
"working_directory_id": working_directory_id,
"relative_cwd": "../escape"
@@ -13173,7 +13232,7 @@ mod tests {
serde_json::json!({
"runtime_id": "embedded-worker-runtime",
"display_name": "",
"initial_text": ""
"initial_submit": []
}),
)
.await;
@@ -13239,7 +13298,7 @@ mod tests {
"runtime_id": "remote-runtime",
"display_name": "Remote Worker",
"profile": "builtin:companion",
"initial_text": ""
"initial_submit": []
})),
StatusCode::BAD_REQUEST,
)
@@ -13352,7 +13411,7 @@ mod tests {
"runtime_id": "embedded-worker-runtime",
"display_name": "Coding Worker",
"profile": "builtin:coder",
"initial_text": "",
"initial_submit": [],
"kind": "internal"
})),
StatusCode::UNPROCESSABLE_ENTITY,
@@ -13898,7 +13957,7 @@ mod tests {
},
profile: ProfileSelector::Builtin("builtin:coder".to_string()),
ticket_assignment: None,
initial_input: None,
initial_submit: Vec::new(),
working_directory_request: None,
resolved_working_directory_request: None,
resolved_working_directory: None,
@@ -14418,7 +14477,7 @@ mod tests {
"builtin:companion".to_string(),
),
ticket_assignment: None,
initial_input: None,
initial_submit: Vec::new(),
working_directory_request: None,
resolved_working_directory_request: None,
resolved_working_directory: None,