diff --git a/crates/protocol/src/lib.rs b/crates/protocol/src/lib.rs index 0c4ac348..74ede90d 100644 --- a/crates/protocol/src/lib.rs +++ b/crates/protocol/src/lib.rs @@ -1377,7 +1377,7 @@ mod tests { let parsed: serde_json::Value = serde_json::from_str(&json).unwrap(); assert_eq!(parsed["event"], "completions"); assert_eq!(parsed["data"]["kind"], "file"); - assert_eq!(parsed["data"]["entries"][0]["value"], "clear"); + assert_eq!(parsed["data"]["entries"][0]["value"], "src/main.rs"); // is_dir defaults to false on inbound payloads that omit it. let inbound = diff --git a/crates/tools/tests/edge_cases.rs b/crates/tools/tests/edge_cases.rs index 60a402fa..6e139d07 100644 --- a/crates/tools/tests/edge_cases.rs +++ b/crates/tools/tests/edge_cases.rs @@ -131,10 +131,11 @@ async fn symlink_to_outside_scope_is_rejected_for_write() { assert!( msg.contains("outside allowed read scope") || msg.contains("outside allowed write scope") + || msg.contains("outside allowed scope") || msg.contains("has not been read"), "symlink escape not rejected: {msg}" ); - if !msg.contains("has not been read") { + if msg.contains("outside allowed read scope") || msg.contains("outside allowed write scope") { assert!( msg.contains("add the symlink target"), "symlink escape diagnostic should include remediation: {msg}" @@ -233,12 +234,16 @@ async fn absolute_path_is_rejected() { ) .await .unwrap_err(); - assert!(format!("{err}").contains("invalid Workdir path")); + let msg = format!("{err}"); + assert!( + msg.contains("invalid logical filesystem path"), + "absolute path was not rejected as invalid: {msg}" + ); } #[tokio::test] async fn directory_target_is_rejected_for_read() { - let (dir, _spill, reg) = setup(); + let (_dir, _spill, reg) = setup(); let read = reg.get("Read"); let err = read .execute(&json!({ "file_path": "." }).to_string(), Default::default()) diff --git a/crates/tools/tests/integration.rs b/crates/tools/tests/integration.rs index 4f8e8aa6..f8761aa8 100644 --- a/crates/tools/tests/integration.rs +++ b/crates/tools/tests/integration.rs @@ -191,7 +191,7 @@ async fn write_then_grep_finds_content() { #[tokio::test] async fn glob_finds_written_files() { - let (dir, _spill, reg) = setup(); + let (_dir, _spill, reg) = setup(); let write = reg.get("Write"); let glob = reg.get("Glob"); @@ -229,7 +229,10 @@ async fn absolute_path_is_rejected() { .await; // Absolute paths are rejected at the logical WorkdirSession boundary. let msg = format!("{err}"); - assert!(msg.contains("invalid Workdir path"), "unexpected: {msg}"); + assert!( + msg.contains("invalid logical filesystem path"), + "unexpected: {msg}" + ); } #[tokio::test] @@ -340,7 +343,7 @@ async fn tracker_recent_files_tracks_read_write_edit() { )); let a = dir.path().join("a.txt"); - let b = dir.path().join("b.txt"); + let _b = dir.path().join("b.txt"); std::fs::write(&a, "one\n").unwrap(); // Read `a` — should appear in recency. diff --git a/crates/worker/tests/compact_events_test.rs b/crates/worker/tests/compact_events_test.rs index 973f080f..0df09529 100644 --- a/crates/worker/tests/compact_events_test.rs +++ b/crates/worker/tests/compact_events_test.rs @@ -533,6 +533,9 @@ model_id = "test-model" max_tokens = 100 [memory] +workspace_id = "test-workspace" +settings_revision = 1 +language = "English" extract_threshold = 1 [compaction] @@ -695,6 +698,9 @@ model_id = "test-model" max_tokens = 100 [memory] +workspace_id = "test-workspace" +settings_revision = 1 +language = "English" extract_threshold = 1 [[scope.allow]] diff --git a/crates/worker/tests/session_metrics_test.rs b/crates/worker/tests/session_metrics_test.rs index 53222f1b..11e46dcc 100644 --- a/crates/worker/tests/session_metrics_test.rs +++ b/crates/worker/tests/session_metrics_test.rs @@ -87,6 +87,7 @@ impl Tool for BigContentTool { Ok(ToolOutput { summary: self.summary.into(), content: Some(self.content.clone()), + attachments: Vec::new(), }) } } diff --git a/web/workspace/src/lib/workspace/console/worker-console.ui.test.ts b/web/workspace/src/lib/workspace/console/worker-console.ui.test.ts index 009cce36..a46eb180 100644 --- a/web/workspace/src/lib/workspace/console/worker-console.ui.test.ts +++ b/web/workspace/src/lib/workspace/console/worker-console.ui.test.ts @@ -115,14 +115,12 @@ Deno.test("workspace Worker list lives on the dedicated Workers page", async () "top workspace page should not own the Worker list", ); assert( - workersPage.includes("workerConsoleHref(worker, data.workspaceId)") && - workersPage.includes('') && - workersPage.includes( - "workerDisplayName = worker.display_name || worker.label", - ) && - workersPage.includes("worker {worker.worker_id}") && + workersPage.includes("workerHref") && + workersPage.includes("workers-table") && + workersPage.includes("workerDisplayName") && + workersPage.includes("worker.resource_key") && workersPage.includes("Delete ${workerDisplayName}"), - "dedicated Workers page should expose a table, console link target, and icon actions per Worker", + "dedicated Workers page should expose a table, canonical Worker link target, and icon actions per Worker", ); assert( workersNav.includes("href={`/w/${workspaceId}/workers`}") && @@ -218,14 +216,16 @@ Deno.test("workspace Tickets surface provides Kanban and lifecycle controls", as "Tickets and Objectives should each be a single sidebar link", ); assert( - ticketsLoad.includes("?limit=1000") && + ticketsLoad.includes("Object.entries(LANE_STATES)") && + ticketsLoad.includes('limit: "30"') && + ticketsLoad.includes('states: states.join(",")') && + ticketsLoad.includes("/tickets?${search}") && !ticketsLoad.includes("/tickets/query") && ticketsPage.includes('class="ticket-kanban"') && - ticketsPage.includes('class="ticket-lane-cards"') && - ticketsPage.includes("lane.tickets.slice(0, lane.visibleCount)") && - ticketsPage.includes("handleLaneScroll") && - ticketsPage.includes("revealNextTickets"), - "Tickets list should fetch lightweight summaries once and incrementally reveal each Kanban lane", + ticketsPage.includes("laneState") && + ticketsPage.includes("loadMore(lane.id)") && + ticketsPage.includes("handleLaneScroll(event, lane.id)"), + "Tickets list should fetch lightweight paginated summaries for each Kanban lane", ); assert( ticketPanelModel.includes('label: "Ready + Planning"') && @@ -250,16 +250,17 @@ Deno.test("workspace Tickets surface provides Kanban and lifecycle controls", as assert( ticketDetailLoad.includes("/repositories") && ticketDetailPage.includes('mutate("state", "/state"') && - ticketDetailPage.includes('mutate("queue", "/queue"') && + ticketDetailPage.includes("async function queueTicket") && + ticketDetailPage.includes("`${ticketPath}/queue`") && !ticketDetailPage.includes("/merge-request/merge") && ticketDetailPage.includes("mergeRequest.selector_from") && - ticketDetailPage.includes("currentReview?.kind") && - ticketDetailPage.includes("mergeEvent?.kind") && - !ticketDetailPage.includes('mutate("review", "/review"') && + ticketDetailPage.includes("mergeRequest.review_status") && + ticketDetailPage.includes("mergeRequestPagePath") && ticketDetailPage.includes('mutate("close", "/close"') && - ticketDetailPage.includes("ticketWorkerLaunchHref") && + ticketDetailPage.includes("mutateAssignment") && + ticketDetailPage.includes("can_start_manual_coder") && ticketDetailPage.includes("ticket.relations.outgoing"), - "Ticket detail should expose typed lifecycle actions, relations, target selection, and role Worker launch", + "Ticket detail should expose typed lifecycle actions, relations, target selection, assignments, and Merge Request navigation", ); }); @@ -319,17 +320,19 @@ Deno.test("workspace Memory surfaces use read-only scoped memory APIs", async () ); }); -Deno.test("root layout does not keep legacy unscoped route compatibility", async () => { +Deno.test("root layout keeps Workspace selection explicit", async () => { const layoutLoad = await Deno.readTextFile( new URL("./../../../routes/+layout.ts", import.meta.url), ); assert( - !layoutLoad.includes("scopedCompatibilityRoute") && - !layoutLoad.includes('pathname === "/runtimes"') && - !layoutLoad.includes("return workspaceRoute(workspaceId, pathname)") && - layoutLoad.includes("workspaceRoute(workspace.data.workspace_id)"), - "root layout should bootstrap the workspace entry only, not preserve legacy unscoped routes", + layoutLoad.includes("export const load") && + layoutLoad.includes("() => ({})") && + !layoutLoad.includes("scopedCompatibilityRoute") && + !layoutLoad.includes("/api/workspace") && + !layoutLoad.includes("workspaceRoute") && + !layoutLoad.includes("redirect("), + "root layout should not infer, bootstrap, or redirect through a singleton Workspace", ); }); @@ -811,9 +814,11 @@ Deno.test("Account UI owns browser passkey session state without workspace autho "SidebarOverride should register and clean up the child-provided sidebar snippet", ); assert( - rootLayoutLoad.includes('"/account"') && - rootLayoutLoad.includes('"/login/device"'), - "Root layout should not redirect account and device-login public routes to a workspace", + rootLayoutLoad.includes("export const load") && + rootLayoutLoad.includes("() => ({})") && + !rootLayoutLoad.includes("workspaceRoute") && + !rootLayoutLoad.includes("redirect("), + "Root layout should leave account and device-login routes public by avoiding Workspace redirects entirely", ); });