Synced from monorepo

Synced from monorepo

Changes:
- Temporarily disable session share link creation in the TUI
- Do not approve plan on empty Enter from the revise prompt
- Expose chat product Skills via ACP available_commands_update
- Return immediately from a blocking wait on an already-completed ACP task
- Split headless pager module for clearer structure
- Stop git worktree prune from removing user registrations on resume
- Use compaction sampler tokenizer for item token counts
- Opt-in extra root CAs via GROK_EXTRA_CA_BUNDLE
- Cancel all session subagents when the user stops
- Let the session persistence actor exit when its session ends
- Make fullscreen terminal resize much cheaper on long sessions
- Report honestly from kill_task when an ACP task does not exist
- Hide /usage for external-auth deployments
- Forward the history-load trailer’s computer_reason to the client
- Remove ineffective no-op tool reminder
- Declare slash-command screen-mode support in one place
- Keep settings enum picker on the committed value until Enter
- Reap a PTY’s full process tree
- Stream tool calls from headless mode over ACP
- Bridge gateway task lifecycle to ACP for chat session background tasks
- Don’t warn about truncated history on a suppressed replay
- Fit full-replace summarizer input and recover on context-length errors
- Stop dropping agents over an unrecognized frontmatter color
- Add /undo as a slash alias for /rewind
- Harden sleep/wake token-refresh paths against forced re-login
- Add session/list ACP method
- Give each sampling backend its own conversion module
- Treat an unenrolled child process as a lint error
- Suppress the cancelled marker on send-now wake turns
- Stop tearing down Roslyn on every edit, and read C# diagnostics

Source-Revision: 2a28b4a86cfc4a4c133c35b7fc2a6a9964387c39
This commit is contained in:
grokkybara[bot] 2026-07-30 19:07:40 +00:00
commit dd04f397b1
367 changed files with 29489 additions and 10051 deletions

View file

@ -342,12 +342,13 @@ impl SessionFlags {
if meta.is_empty() { None } else { Some(meta) }
}
}
/// Workspace-bind `_meta` keys forbidden on chat create/load: backend owns
/// workspace for `kind=chat`; the client must not bind Direct/envId/attach.
/// Workspace-bind `_meta` keys **always** forbidden on chat create/load.
///
/// `x.ai/cloud_existing_workspace` is intentionally omitted: scrub keeps it
/// iff `x.ai/local_workspace.mode == "attach"`.
pub(super) const CHAT_FORBIDDEN_WORKSPACE_BIND_KEYS: &[&str] = &[
"envId",
"x.ai/cloud_server_id",
"x.ai/cloud_existing_workspace",
];
/// Stamp `_meta["x.ai/session"].kind = "chat"` and strip Build `agentProfile` (K12).
pub(super) fn apply_chat_kind_meta(meta: &mut Option<acp::Meta>) {
@ -355,7 +356,23 @@ pub(super) fn apply_chat_kind_meta(meta: &mut Option<acp::Meta>) {
obj.insert("x.ai/session".into(), serde_json::json!({ "kind": "chat" }));
obj.remove("agentProfile");
}
/// Shared chat create/load/worktree meta finalize: kind + local stamp + scrub.
pub(super) fn finalize_chat_session_meta(
meta: &mut Option<acp::Meta>,
is_chat_path: bool,
#[allow(unused_variables)]
session_flags: &SessionFlags,
) {
if !is_chat_path {
return;
}
apply_chat_kind_meta(meta);
scrub_chat_workspace_bind_meta(meta);
}
/// Remove client workspace-bind keys from chat create/load meta (defense in depth).
///
/// Narrow scrub exception: keep `x.ai/cloud_existing_workspace` when local
/// intent is attach. Never keep `envId` or Direct hub `x.ai/cloud_server_id`.
pub(super) fn scrub_chat_workspace_bind_meta(meta: &mut Option<acp::Meta>) {
let Some(obj) = meta.as_mut() else {
return;
@ -363,6 +380,9 @@ pub(super) fn scrub_chat_workspace_bind_meta(meta: &mut Option<acp::Meta>) {
for key in CHAT_FORBIDDEN_WORKSPACE_BIND_KEYS {
obj.remove(*key);
}
{
obj.remove("x.ai/cloud_existing_workspace");
}
}
/// Metadata returned from effect execution so the event loop can patch
/// state that requires a spawned task handle (e.g., auth AbortHandle).

View file

@ -142,9 +142,7 @@ pub(crate) fn execute(
#[allow(unused_mut)]
let mut meta = session_flags.to_meta();
let is_chat_path = chat_kind || session_flags.chat_mode;
if is_chat_path {
apply_chat_kind_meta(&mut meta);
}
finalize_chat_session_meta(&mut meta, is_chat_path, session_flags);
if let Some(ref mid) = model_id {
meta.get_or_insert_with(acp::Meta::new)
.insert("modelId".into(), serde_json::json!(mid.0));
@ -238,13 +236,11 @@ pub(crate) fn execute(
let tx = acp_tx.clone();
let cwd = cwd.to_path_buf();
let mut meta = session_flags.to_meta();
if chat_kind || session_flags.chat_mode {
meta.get_or_insert_with(acp::Meta::new)
.insert(
"x.ai/session".into(),
serde_json::json!({ "kind": "chat" }),
);
}
finalize_chat_session_meta(
&mut meta,
chat_kind || session_flags.chat_mode,
session_flags,
);
if let Some(ref mid) = model_id {
meta.get_or_insert_with(acp::Meta::new)
.insert("modelId".into(), serde_json::json!(mid.0));
@ -520,10 +516,7 @@ pub(crate) fn execute(
let tx = acp_tx.clone();
let mut meta = session_flags.to_meta();
let is_chat_path = chat_kind || session_flags.chat_mode;
if is_chat_path {
apply_chat_kind_meta(&mut meta);
scrub_chat_workspace_bind_meta(&mut meta);
}
finalize_chat_session_meta(&mut meta, is_chat_path, session_flags);
if let Some(true) = session_flags.restore_code {
meta.get_or_insert_with(acp::Meta::new)
.insert("x.ai/restore_code".into(), serde_json::Value::Bool(true));

View file

@ -1979,7 +1979,7 @@ fn to_meta_chat_mode_stamps_kind_and_omits_agent_profile() {
..Default::default()
};
let meta = flags.to_meta().expect("chat_mode must emit meta");
assert_eq!(meta["x.ai/session"]["kind"], "chat");
assert_eq!(meta["x.ai/session"] ["kind"], "chat");
assert!(
meta.get("agentProfile").is_none(),
"K12: chat mode must omit Build agentProfile"
@ -2006,7 +2006,7 @@ fn load_meta_chat_kind_alone_stamps_kind_and_strips_profile() {
scrub_chat_workspace_bind_meta(&mut meta);
}
let meta = meta.expect("chat_kind must produce meta");
assert_eq!(meta["x.ai/session"]["kind"], "chat");
assert_eq!(meta["x.ai/session"] ["kind"], "chat");
assert!(
meta.get("agentProfile").is_none(),
"entry chat_kind must strip Build agentProfile"
@ -2025,6 +2025,10 @@ fn assert_chat_meta_has_no_workspace_bind_keys(meta: &serde_json::Value) {
"chat meta must not include workspace-bind key {key:?}: {meta}"
);
}
assert!(
meta.get("x.ai/cloud_existing_workspace").is_none(),
"chat meta without attach must not include existing workspace: {meta}"
);
}
#[test]
fn chat_create_meta_never_includes_workspace_bind_keys_when_cloud_fields_set() {
@ -2036,7 +2040,7 @@ fn chat_create_meta_never_includes_workspace_bind_keys_when_cloud_fields_set() {
apply_chat_kind_meta(&mut meta);
scrub_chat_workspace_bind_meta(&mut meta);
let meta = meta.expect("chat create must emit meta");
assert_eq!(meta["x.ai/session"]["kind"], "chat");
assert_eq!(meta["x.ai/session"] ["kind"], "chat");
assert_chat_meta_has_no_workspace_bind_keys(
&serde_json::Value::Object(meta.clone()),
);
@ -2060,7 +2064,7 @@ fn chat_load_meta_never_includes_workspace_bind_keys() {
}
scrub_chat_workspace_bind_meta(&mut meta);
let meta = meta.expect("chat load must emit meta");
assert_eq!(meta["x.ai/session"]["kind"], "chat");
assert_eq!(meta["x.ai/session"] ["kind"], "chat");
assert_chat_meta_has_no_workspace_bind_keys(
&serde_json::Value::Object(meta.clone()),
);