[E2E] session.todos_changed event + readSqlTodosWithDependencies (6 languages)#1622
Conversation
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Generated by SDK Consistency Review Agent for issue #1622 · sonnet46 1.7M
| let todos_changed = wait_for_event(session.subscribe(), "todos changed", |event| { | ||
| event.parsed_type() == SessionEventType::Unknown | ||
| && event.event_type == "session.todos_changed" |
There was a problem hiding this comment.
Inconsistency with generated SDK types: The predicate combines SessionEventType::Unknown with a string check, but session.todos_changed already has its own generated variant SessionEventType::SessionTodosChanged in the Rust enum. The Unknown branch will never fire for a properly-recognised event, so this condition is always false.
Every other Rust E2E test uses the typed variant directly — e.g. rpc_event_log.rs uses event.parsed_type() == SessionEventType::SessionPlanChanged. Suggest:
let todos_changed = wait_for_event(session.subscribe(), "todos changed", |event| {
event.parsed_type() == SessionEventType::SessionTodosChanged
});| let value = session | ||
| .client() | ||
| .call( | ||
| "session.plan.readSqlTodosWithDependencies", | ||
| Some(json!({ "sessionId": session.id() })), | ||
| ) | ||
| .await | ||
| .expect("read SQL todos with dependencies"); | ||
| let result: PlanReadSqlTodosWithDependenciesResult = | ||
| serde_json::from_value(value).expect("deserialize todos with dependencies"); |
There was a problem hiding this comment.
Inconsistency: raw client().call() instead of typed RPC: All other five SDK tests use the generated typed plan API (session.rpc.plan.readSqlTodosWithDependencies(), session.Rpc.Plan.ReadSqlTodosWithDependenciesAsync(), etc.). The Rust SDK has the same generated typed helper — session.rpc().plan().read_sql_todos_with_dependencies() — which returns the generated PlanReadSqlTodosWithDependenciesResult (rows as Vec<PlanSqlTodosRow>, deps as Vec<PlanSqlTodoDependency>).
The three local structs PlanReadSqlTodosWithDependenciesResult, PlanTodo, and PlanTodoDependency (lines 15–33) also duplicate what's already in github_copilot_sdk::rpc. Suggest replacing the raw call + local types with:
use github_copilot_sdk::rpc::PlanReadSqlTodosWithDependenciesResult;
// ...
let result: PlanReadSqlTodosWithDependenciesResult = session
.rpc()
.plan()
.read_sql_todos_with_dependencies()
.await
.expect("read SQL todos with dependencies");This also lets you remove the serde::Deserialize, serde_json::json, and three local struct definitions from the top of the file.
…encies Validates the new runtime APIs added in copilot-agent-runtime#10061: - session.todos_changed event fires when the sql tool mutates the prompted todos / todo_deps tables - session.plan.readSqlTodosWithDependencies returns structured rows and dependency edges suitable for progress UIs The generated rpc.ts and session-events.ts diffs were produced by re-running the Node codegen against the runtime PR's generated/ schemas; they will reproduce automatically once the runtime PR ships in the @github/copilot npm package. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Re-runs Node codegen against runtime PR's regenerated schemas so the rows/dependencies arrays use the newly-named PlanTodo / PlanTodoDependency types. - Simplifies the E2E prompt: the runtime already prompts the agent to create the todos / todo_deps tables, so the CREATE TABLE steps were noisy in the captured snapshot (SQLite returned 'table todos already exists'). The agent only needs to INSERT. - Re-records the snapshot. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Mirrors the existing nodejs test in all 4 other SDK languages: agent inserts 2 todos + 1 dependency edge via the sql tool, then we assert the session.todos_changed event fires and readSqlTodosWithDependencies returns the expected rows + dependencies. All 5 language tests share a single recorded CAPI snapshot. Renamed the existing snapshot from 'rows___dependencies' to 'rows_and_dependencies' and the nodejs test title to match, so the slug is identical across all languages without per-language slugification quirks. Also regenerates SDK bindings against the latest runtime schema so the new readSqlTodosWithDependencies RPC, PlanSqlTodosRow, and PlanSqlTodoDependency types are available via typed APIs in every language. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds the generated Java bindings for SessionTodosChangedEvent, PlanSqlTodoDependency, and SessionPlan.readSqlTodosWithDependencies, plus a Java E2E test mirroring the other 5 SDK languages. Status: the test currently fails locally - the runtime's session.todos_changed event is not reaching the Java listener even though the same scenario passes in Node/.NET/Go/Python/Rust against the identical CAPI snapshot. Needs follow-up investigation; pushed as-is so reviewers can see the full 6-language surface. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
68cf467 to
031ea1e
Compare
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Generated by SDK Consistency Review Agent for issue #1622 · sonnet46 1.5M
|
|
||
| let todos_changed = wait_for_event(session.subscribe(), "todos changed", |event| { | ||
| event.parsed_type() == SessionEventType::Unknown |
There was a problem hiding this comment.
Consistency gap vs other SDKs: All other 5 languages match the event using a typed SessionTodosChangedEvent class/variant. Here Rust falls back to SessionEventType::Unknown + a raw string check, suggesting session.todos_changed isn't yet in Rust's generated SessionEventType enum.\n\nThis is analogous to the Java WIP situation and worth tracking as a follow-up (or noting in the PR description) so it doesn't quietly persist post-merge.
|
|
||
| let value = session | ||
| .client() | ||
| .call( | ||
| "session.plan.readSqlTodosWithDependencies", | ||
| Some(json!({ "sessionId": session.id() })), | ||
| ) | ||
| .await | ||
| .expect("read SQL todos with dependencies"); | ||
| let result: PlanReadSqlTodosWithDependenciesResult = |
There was a problem hiding this comment.
Consistency gap vs other SDKs: All other 5 languages call a generated, typed accessor:\n- Node.js: session.rpc.plan.readSqlTodosWithDependencies()\n- .NET: session.Rpc.Plan.ReadSqlTodosWithDependenciesAsync()\n- Go: session.RPC.Plan.ReadSqlTodosWithDependencies(ctx)\n- Java: session.getRpc().plan.readSqlTodosWithDependencies()\n- Python: session.rpc.plan.read_sql_todos_with_dependencies()\n\nRust instead uses a raw client().call(...) with three locally defined structs. The Rust SDK's generated RPC types appear to not yet include the Plan.ReadSqlTodosWithDependencies accessor — similar to the Java WIP gap.
Re-running codegen against the published 1.0.62 schema picks up new provider and subagent settings APIs that were missing from the rebased generated files. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Generated by SDK Consistency Review Agent for issue #1622 · sonnet46 1.4M
| .expect("create session"); | ||
|
|
||
| let todos_changed = wait_for_event(session.subscribe(), "todos changed", |event| { | ||
| event.parsed_type() == SessionEventType::Unknown |
There was a problem hiding this comment.
Consistency gap (Rust): SessionEventType::Unknown workaround
The other SDKs that use typed event dispatch (.NET uses SessionTodosChangedEvent class, Java likewise) have a named type for this event. Using Unknown here is the correct workaround while the Rust codegen hasn't been re-run, but once the runtime PR lands and Rust generated types are updated, this should become something like:
let todos_changed = wait_for_event(session.subscribe(), "todos changed", |event| {
event.parsed_type() == SessionEventType::SessionTodosChanged
});Worth leaving a // TODO: replace Unknown once codegen runs comment here so it doesn't get forgotten.
| #[derive(Debug, Deserialize)] | ||
| #[serde(rename_all = "camelCase")] | ||
| struct PlanReadSqlTodosWithDependenciesResult { |
There was a problem hiding this comment.
Consistency gap (Rust): local structs + raw RPC call in place of generated types
All five other SDKs access readSqlTodosWithDependencies through a typed SDK accessor:
- Node.js:
session.rpc.plan.readSqlTodosWithDependencies() - .NET:
session.Rpc.Plan.ReadSqlTodosWithDependenciesAsync() - Go:
session.RPC.Plan.ReadSqlTodosWithDependencies(ctx) - Python:
session.rpc.plan.read_sql_todos_with_dependencies() - Java:
session.getRpc().plan.readSqlTodosWithDependencies()
The local PlanReadSqlTodosWithDependenciesResult, PlanTodo, and PlanTodoDependency structs here, plus the raw session.client().call("session.plan.readSqlTodosWithDependencies", ...) call below, are stand-ins because the Rust codegen hasn't run yet. Once generated types are available, these structs and the raw call should be replaced with the typed accessor.
| + "1. INSERT INTO todos (id, title, status) VALUES ('alpha', 'First todo', 'pending');\n" | ||
| + "2. INSERT INTO todos (id, title, status) VALUES ('beta', 'Second todo', 'done');\n" | ||
| + "3. INSERT INTO todo_deps (todo_id, depends_on) VALUES ('beta', 'alpha');\n" |
There was a problem hiding this comment.
Possible contributor to the known Java test failure: no post-send event wait
The session.todos_changed event can fire asynchronously after sendAndWait returns (e.g., once the runtime finishes persisting state). The .NET, Go, and Rust tests guard against this by setting up an explicit awaitable before the send and then awaiting it after with a timeout:
// .NET pattern
var todosChangedTask = TestHelper.GetNextEventOfTypeAsync<SessionTodosChangedEvent>(session, TimeSpan.FromSeconds(30));
await session.SendAndWaitAsync(...);
await todosChangedTask; // waits for event even if it fires after SendAndWait returnsThis test checks events immediately after sendAndWait with no additional wait. If the Java SDK's session.todos_changed notification arrives slightly after the response is received (which the PR description hints at), the assertTrue will fail even though the event does eventually arrive. Consider using a polling-wait or a CompletableFuture-based listener pattern similar to GetNextEventOfTypeAsync in .NET.
.Order() is .NET 7+ only; net472 needs .OrderBy(x => x, comparer). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…gnizes it After regenerating against @github/copilot 1.0.62, the SessionEvent enum now has a SessionTodosChanged variant. The earlier predicate matched parsed_type() == Unknown which no longer holds. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The test asserted that getCurrent() after a switchTo returns the original model, which contradicts switchTo's contract (it calls session.setSelectedModel and persists). The runtime in main now returns the live model consistently, exposing the bad assertion. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Generated by SDK Consistency Review Agent for issue #1622 · sonnet46 2.4M
| #[derive(Debug, Deserialize)] | ||
| #[serde(rename_all = "camelCase")] | ||
| struct PlanReadSqlTodosWithDependenciesResult { | ||
| rows: Vec<PlanTodo>, | ||
| dependencies: Vec<PlanTodoDependency>, | ||
| } | ||
|
|
||
| #[derive(Debug, Deserialize)] | ||
| #[serde(rename_all = "camelCase")] | ||
| struct PlanTodo { | ||
| id: Option<String>, | ||
| } | ||
|
|
||
| #[derive(Debug, Deserialize)] | ||
| #[serde(rename_all = "camelCase")] | ||
| struct PlanTodoDependency { | ||
| todo_id: String, | ||
| depends_on: String, | ||
| } | ||
|
|
||
| #[tokio::test] | ||
| async fn fires_session_todos_changed_and_exposes_rows_and_dependencies() { | ||
| with_e2e_context( | ||
| "session_todos_changed", | ||
| "fires_session_todos_changed_and_exposes_rows_and_dependencies", | ||
| |ctx| { | ||
| Box::pin(async move { | ||
| ctx.set_default_copilot_user(); | ||
| let client = ctx.start_client().await; | ||
| let session = client | ||
| .create_session(ctx.approve_all_session_config()) | ||
| .await | ||
| .expect("create session"); | ||
|
|
||
| let todos_changed = wait_for_event(session.subscribe(), "todos changed", |event| { | ||
| event.parsed_type() == SessionEventType::SessionTodosChanged | ||
| }); | ||
|
|
||
| session.send_and_wait(PROMPT).await.expect("send"); | ||
| todos_changed.await; | ||
|
|
||
| let value = session |
There was a problem hiding this comment.
The local struct definitions and this raw client().call(...) block can be replaced with the typed API that already exists in rust/src/generated/rpc.rs.
All five other SDKs use their typed accessor:
- Node.js:
session.rpc.plan.readSqlTodosWithDependencies() - Python:
session.rpc.plan.read_sql_todos_with_dependencies() - Go:
session.RPC.Plan.ReadSqlTodosWithDependencies(ctx) - .NET:
session.Rpc.Plan.ReadSqlTodosWithDependenciesAsync() - Java:
session.getRpc().plan.readSqlTodosWithDependencies()
Other Rust E2E tests follow the same typed pattern (e.g. session.rpc().plan().delete() in rpc_session_state.rs). Suggested replacement for lines 15–65:
use github_copilot_sdk::rpc::{PlanSqlTodoDependency, PlanSqlTodosRow};...and then in the test body:
let result = session
.rpc()
.plan()
.read_sql_todos_with_dependencies()
.await
.expect("read SQL todos with dependencies");
let mut ids: Vec<String> =
result.rows.into_iter().filter_map(|row| row.id).collect();This removes the three locally-defined structs (PlanReadSqlTodosWithDependenciesResult, PlanTodo, PlanTodoDependency) and the serde::Deserialize / serde_json::json imports that are only needed because of the raw call. The generated PlanReadSqlTodosWithDependenciesResult, PlanSqlTodosRow, and PlanSqlTodoDependency types from github_copilot_sdk::rpc have the same fields (including the #[serde(rename_all = "camelCase")] handling already baked in).
This reverts commit 001f6f4.
The previous assertion expected getCurrent() after switchTo to still return the original model, which is wrong: switchTo should persist. The test passed flakily because there's a race between switchTo resolving and getCurrent observing the new model. Poll getCurrent until it reflects the switch, then assert the new model. This expresses the desired behavior unambiguously and removes the race. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Previously, the cross-language assertions hedged or asserted the old behavior (after == before) under the assumption that switchTo was a non-mutating override resolution. The runtime now persists, so: - Python: was strict 'after == before', failed deterministically. - Node: was strict 'after == before', flaked by platform speed. - .NET / Go: lenient 'after == new || after == before', passed either way but masked the bug. Update all four to poll getCurrent for up to 5s and then assert getCurrent reflects the new model. Rust and Java already only check the switchTo result and need no change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Generated by SDK Consistency Review Agent for issue #1622 · sonnet46 2.3M
| #[derive(Debug, Deserialize)] | ||
| #[serde(rename_all = "camelCase")] | ||
| struct PlanReadSqlTodosWithDependenciesResult { | ||
| rows: Vec<PlanTodo>, | ||
| dependencies: Vec<PlanTodoDependency>, | ||
| } | ||
|
|
||
| #[derive(Debug, Deserialize)] | ||
| #[serde(rename_all = "camelCase")] | ||
| struct PlanTodo { | ||
| id: Option<String>, | ||
| } | ||
|
|
||
| #[derive(Debug, Deserialize)] | ||
| #[serde(rename_all = "camelCase")] | ||
| struct PlanTodoDependency { | ||
| todo_id: String, | ||
| depends_on: String, | ||
| } |
There was a problem hiding this comment.
These local struct types (PlanReadSqlTodosWithDependenciesResult, PlanTodo, PlanTodoDependency) duplicate types that already exist in the Rust SDK's generated code, and the call below at line 56 bypasses the typed RPC API.
All other five SDKs test this via their typed plan.readSqlTodosWithDependencies() method (e.g. Node: session.rpc.plan.readSqlTodosWithDependencies(), Go: session.RPC.Plan.ReadSqlTodosWithDependencies(ctx), .NET: session.Rpc.Plan.ReadSqlTodosWithDependenciesAsync()). The adjacent Rust E2E test rpc_session_state_extras.rs:168 already uses the equivalent pattern — session.rpc().plan().read_sql_todos() — confirming the typed API is accessible from tests.
Suggested change: drop the local structs and replace the session.client().call(...) block with:
let result = session
.rpc()
.plan()
.read_sql_todos_with_dependencies()
.await
.expect("read SQL todos with dependencies");This keeps Rust consistent with all other SDKs and avoids maintaining duplicate type definitions.
Drops local duplicate struct definitions and the untyped session.client().call() in favor of the generated session.rpc().plan().read_sql_todos_with_dependencies() method, matching the pattern used by all other SDK E2E tests and the adjacent rpc_session_state_extras.rs:168. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Generated by SDK Consistency Review Agent for issue #1622 · sonnet46 1.8M
| const todosEvents = events.filter((e) => e.type === "session.todos_changed"); | ||
| expect(todosEvents.length).toBeGreaterThanOrEqual(1); | ||
|
|
||
| const result = await session.rpc.plan.readSqlTodosWithDependencies(); |
There was a problem hiding this comment.
Minor consistency note: this test collects events passively and checks the list after sendAndWait, while the equivalent Go, .NET, and Rust tests explicitly await the session.todos_changed event with a dedicated helper (with timeout).
The Node.js test harness already has getNextEventOfType (used in e.g. tool_results.e2e.test.ts and permissions.e2e.test.ts) that sets up the listener before the operation and returns a timed Promise. Using it here would be consistent with the other SDKs and slightly more robust against any race where the event propagates after sendAndWait resolves:
import { getNextEventOfType } from "./harness/sdkTestHelper.js";
// ...
const todoChangedPromise = getNextEventOfType(session, "session.todos_changed");
await session.sendAndWait({ prompt: ... });
await todoChangedPromise;
// (remove the ad-hoc events array and filter)Not a blocking issue for replay tests, just a suggestion to keep the pattern consistent across all 6 SDKs.
| todos_events = [ | ||
| event for event in events if _event_type_value(event) == "session.todos_changed" | ||
| ] | ||
| assert len(todos_events) >= 1 |
There was a problem hiding this comment.
Same pattern note as the Node.js test: this collects events into a list and checks it after send_and_wait, whereas Go, .NET, and Rust all explicitly await the session.todos_changed event using a dedicated helper. The local _event_type_value helper is also doing work that the existing get_next_event_of_type utility already handles internally.
Using the existing harness helper would align with the other SDKs and eliminate the need for _event_type_value:
from .testharness import E2ETestContext, get_next_event_of_type
# ...
todos_changed_future = asyncio.ensure_future(
get_next_event_of_type(session, "session.todos_changed", timeout=30.0)
)
await session.send_and_wait(PROMPT, timeout=120.0)
await todos_changed_futureNot blocking for replay tests, just a suggestion to keep the event-awaiting pattern consistent across all 6 SDKs.
The runtime's EXCLUDED_MODELS_LIST (models_catalog.rs) now excludes gpt-4.1, so model resolution silently falls back to claude-sonnet-4.5 on the post-switch tool revalidation pass. Switching the tests to a current SUPPORTED_MODEL (gpt-5.4) makes them reliable across Node, Python, .NET, and Go, and lets us drop the 5s polling workaround that hid (but did not fix) the issue. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replaces the post-hoc events.filter() pattern with the existing getNextEventOfType helper (Node/Python) and a CompletableFuture event subscription (Java). This matches the pattern used by Go/.NET/Rust and removes a race window where the event could arrive after sendAndWait resolves but before the assertion is checked. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| get_next_event_of_type(session, "session.todos_changed", timeout=120.0) | ||
| ) | ||
| await session.send_and_wait(PROMPT, timeout=120.0) | ||
| await todos_changed |
This comment has been minimized.
This comment has been minimized.
…ntamination Both tests in rpc_session_state.e2e.test.ts share one long-lived CLI subprocess (one client created at describe scope). The runtime caches the /models response from whichever test calls it first. When 'getcurrent' ran first with a single-model snapshot, that response was cached; then 'switchto' would accept gpt-5.4 synchronously but the background tool-revalidation triggered by the model_change event saw only claude-sonnet-4.5 in the cached list, rejected gpt-5.4, and silently reverted _selectedModel. Listing both models in the getcurrent snapshot keeps the cached list compatible with switchto. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The runtime caches /models per (auth, base_url) for 30 minutes (capi_client.rs LIST_MODELS_CACHE). Tests in rpc_session_state.e2e.test.ts share one CLI subprocess and proxy URL, so the first snapshot's models list is reused by every later test. The switchTo test needs a model the other tests don't list, so polluting every other snapshot would be fragile (ordering-dependent). Instead, wrap the switchTo test in a nested describe with its own createSdkTestContext, which spawns a dedicated subprocess and proxy → its own cache key → its snapshot's models list is authoritative. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Same fix as Node (commit ebb2a90): the runtime caches /models per (auth, base_url) for 30 minutes (capi_client.rs LIST_MODELS_CACHE). Tests that share a CLI subprocess inherit the first snapshot's cached models list, so a later switchTo to a model the first snapshot doesn't list silently reverts to the cached default. - Go: switchTo subtest builds its own TestContext + client + ConfigureForTest -> own proxy URL -> own cache entry. - Python: switchTo test instantiates an isolated E2ETestContext (setup/teardown) so it gets its own subprocess and proxy. - .NET: switchTo test bypasses the class fixture and builds its own E2ETestContext + client. - Rust: each test already has its own with_e2e_context, so no isolation needed. Tightened the assertion to mirror the other SDKs: switch to gpt-5.4 and verify both the switch result and the post-switch getCurrent. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
Sanitization of test_should_call_session_rpc_model_switch_to produces should_call_session_rpc_model_switch_to (with underscore), but the shared snapshot file is should_call_session_rpc_model_switchto.yaml (no underscore — matches Node/Go naming). The autouse fixture pointed the proxy at a non-existent file → /models fell back to the default single-model list, the runtime cached that, switchTo to gpt-5.4 was filtered out, and _selectedModel silently reverted. Renaming the test method makes sanitization produce the matching filename. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Cross-SDK Consistency Review ✅This PR provides solid 6-language coverage for What's consistent ✅
Issues to address before merge1. Java test is WIP / currently failing (acknowledged in PR description) The Minor observations (optional improvements)2. Java: prefer the typed In session.on(event -> {
if (event instanceof SessionTodosChangedEvent todosEvent && !todosChanged.isDone()) {
todosChanged.complete(todosEvent);
}
});The Java SDK already has a typed overload (used in session.on(SessionTodosChangedEvent.class, todosEvent -> {
if (!todosChanged.isDone()) todosChanged.complete(todosEvent);
});The typed version is shorter and doesn't require an 3. Java test harness lacks an event-waiting utility All other 5 SDKs provide a test-harness helper for "wait for the next event of a given type" (e.g., Node's Overall the PR is well structured — one snapshot, six languages, consistent API shapes. The only blocking item is the known Java test failure.
|
Adds E2E coverage for the new runtime APIs in copilot-agent-runtime#10061:
session.todos_changedevent fires when the agent'ssqltool issues a write touching the promptedtodos/todo_depstables.session.plan.readSqlTodosWithDependenciesreturns the structured rows + dependency edges for progress UIs.Coverage
One scenario, one shared CAPI snapshot (
test/snapshots/session_todos_changed/fires_session_todos_changed_and_exposes_rows_and_dependencies.yaml), replayed across all 6 SDK languages. Each test instructs the agent to insert two known rows (alpha,beta) plus one dependency edge (beta -> alpha), then asserts:session.todos_changedevent was observed, andsession.plan.readSqlTodosWithDependencies()(typed RPC) returns ids[alpha, beta]and thebeta -> alphaedge.nodejs/test/e2e/session_todos_changed.e2e.test.tsdotnet/tests/GitHub.Copilot.Sdk.E2ETests/SessionTodosChangedTests.csgo/test/e2e/session_todos_changed_test.gopython/tests/e2e/test_session_todos_changed.pyrust/tests/session_todos_changed.rsjava/src/test/java/com/github/copilot/SessionTodosChangedTest.javaDraft notes
generated/tree were produced by re-running the per-language codegen against the runtime PR'sgenerated/*.schema.jsonfiles. They will reproduce automatically once the runtime PR ships in the@github/copilotnpm package.@github/copilotbump.