Add goal app-server API (2 / 5) (#18074)

Adds the app-server v2 goal API on top of the persisted goal state from
PR 1.

## Why

Clients need a stable app-server surface for reading and controlling
materialized thread goals before the model tools and TUI can use them.
Goal changes also need to be observable by app-server clients, including
clients that resume an existing thread.

## What changed

- Added v2 `thread/goal/get`, `thread/goal/set`, and `thread/goal/clear`
RPCs for materialized threads.
- Added `thread/goal/updated` and `thread/goal/cleared` notifications so
clients can keep local goal state in sync.
- Added resume/snapshot wiring so reconnecting clients see the current
goal state for a thread.
- Added app-server handlers that reconcile persisted rollout state
before direct goal mutations.
- Updated the app-server README plus generated JSON and TypeScript
schema fixtures for the new API surface.

## Verification

- Added app-server v2 coverage for goal get/set/clear behavior,
notification emission, resume snapshots, and non-local thread-store
interactions.
This commit is contained in:
Eric Traut
2026-04-24 20:53:41 -07:00
committed by GitHub
Unverified
parent 0ee737cea6
commit 6c874f9b34
29 changed files with 1973 additions and 5 deletions
@@ -350,6 +350,12 @@ fn server_notification_thread_target(
ServerNotification::ThreadTokenUsageUpdated(notification) => {
Some(notification.thread_id.as_str())
}
ServerNotification::ThreadGoalUpdated(notification) => {
Some(notification.thread_id.as_str())
}
ServerNotification::ThreadGoalCleared(notification) => {
Some(notification.thread_id.as_str())
}
ServerNotification::TurnStarted(notification) => Some(notification.thread_id.as_str()),
ServerNotification::HookStarted(notification) => Some(notification.thread_id.as_str()),
ServerNotification::TurnCompleted(notification) => Some(notification.thread_id.as_str()),
+6
View File
@@ -6533,6 +6533,8 @@ impl ChatWidget {
notification.token_usage,
)));
}
ServerNotification::ThreadGoalUpdated(_) => {}
ServerNotification::ThreadGoalCleared(_) => {}
ServerNotification::ThreadNameUpdated(notification) => {
match ThreadId::from_string(&notification.thread_id) {
Ok(thread_id) => self.on_thread_name_updated(
@@ -7089,6 +7091,7 @@ impl ChatWidget {
match msg {
EventMsg::SessionConfigured(e) => self.on_session_configured(e),
EventMsg::ThreadNameUpdated(e) => self.on_thread_name_updated(e),
EventMsg::ThreadGoalUpdated(_) => {}
// NOTE: All three AgentMessage arms feed `record_agent_markdown` even
// when the message is otherwise not rendered (thread-snapshot replay,
// non-review live messages). This ensures the copy source stays
@@ -7200,6 +7203,9 @@ impl ChatWidget {
TurnAbortReason::ReviewEnded => {
self.on_interrupted_turn(ev.reason);
}
TurnAbortReason::BudgetLimited => {
self.on_interrupted_turn(ev.reason);
}
},
EventMsg::PlanUpdate(update) => self.on_plan_update(update),
EventMsg::ExecApprovalRequest(ev) => {