collab-stack: import collab stack with local repairs

Start from the pinned `origin/dev/friel/collab-stack` snapshot and fold in the
local follow-up repairs that made that imported stack usable here: watchdog
spawn/registration plumbing, deferred-tool fallback behavior, collab discovery
fixture alignment, schema mirroring, and subagent-panel fixes.

Original imported source:
- source ref: `refs/remotes/origin/dev/friel/collab-stack`
- source tip: `599ed9dc05eafd116192184bd54a2a55a2c49366`
- original base: `c1d18ceb6f22ae3acd67bbd6badad0f475b31dfc`
This commit is contained in:
dank-openai
2026-04-14 15:19:41 -04:00
parent 7c5b89bb3f
commit adfb50bf4e
171 changed files with 12947 additions and 588 deletions

View File

@@ -613,6 +613,11 @@ pub enum ResponseInputItem {
#[ts(optional)]
phase: Option<MessagePhase>,
},
FunctionCall {
name: String,
arguments: String,
call_id: String,
},
FunctionCallOutput {
call_id: String,
#[ts(as = "FunctionCallOutputBody")]
@@ -1047,6 +1052,17 @@ impl From<ResponseInputItem> for ResponseItem {
id: None,
phase,
},
ResponseInputItem::FunctionCall {
name,
arguments,
call_id,
} => Self::FunctionCall {
id: None,
name,
namespace: None,
arguments,
call_id,
},
ResponseInputItem::FunctionCallOutput { call_id, output } => {
Self::FunctionCallOutput { call_id, output }
}

View File

@@ -247,6 +247,11 @@ const fn default_effective_context_window_percent() -> i64 {
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, TS, JsonSchema)]
pub struct ModelInfo {
pub slug: String,
/// Provider-facing model slug to send on API requests.
///
/// When unset, `slug` is used.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub request_model: Option<String>,
pub display_name: String,
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
@@ -307,6 +312,10 @@ impl ModelInfo {
self.context_window.or(self.max_context_window)
}
pub fn request_model_slug(&self) -> &str {
self.request_model.as_deref().unwrap_or(self.slug.as_str())
}
pub fn auto_compact_token_limit(&self) -> Option<i64> {
let context_limit = self
.resolved_context_window()
@@ -539,6 +548,7 @@ mod tests {
fn test_model(spec: Option<ModelMessages>) -> ModelInfo {
ModelInfo {
slug: "test-model".to_string(),
request_model: None,
display_name: "Test Model".to_string(),
description: None,
default_reasoning_level: None,

View File

@@ -103,6 +103,27 @@ pub const COLLABORATION_MODE_CLOSE_TAG: &str = "</collaboration_mode>";
pub const REALTIME_CONVERSATION_OPEN_TAG: &str = "<realtime_conversation>";
pub const REALTIME_CONVERSATION_CLOSE_TAG: &str = "</realtime_conversation>";
pub const USER_MESSAGE_BEGIN: &str = "## My request for Codex:";
pub const AGENT_INBOX_KIND: &str = "agent_inbox";
pub const AGENT_INBOX_MESSAGE_PREFIX: &str = "[agent_inbox:";
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema)]
pub struct AgentInboxPayload {
pub injected: bool,
pub kind: String,
pub sender_thread_id: ThreadId,
pub message: String,
}
impl AgentInboxPayload {
pub fn new(sender_thread_id: ThreadId, message: String) -> Self {
Self {
injected: true,
kind: AGENT_INBOX_KIND.to_string(),
sender_thread_id,
message,
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema)]
pub struct TurnEnvironmentSelection {
@@ -406,6 +427,9 @@ pub enum Op {
/// This server sends [`EventMsg::TurnAborted`] in response.
Interrupt,
/// Mark owner-side input activity without starting or steering a turn.
NoteOwnerActivity,
/// Terminate all running background terminal processes for this thread.
/// Use this when callers intentionally want to stop long-lived background shells.
CleanBackgroundTerminals,
@@ -516,6 +540,9 @@ pub enum Op {
personality: Option<Personality>,
},
/// Inject non-user response items into an existing turn, or start a turn if needed.
InjectResponseItems { items: Vec<ResponseInputItem> },
/// Similar to [`Op::UserInput`], but contains additional context required
/// for a turn of a [`crate::codex_thread::CodexThread`].
UserTurn {
@@ -876,6 +903,7 @@ impl Op {
pub fn kind(&self) -> &'static str {
match self {
Self::Interrupt => "interrupt",
Self::NoteOwnerActivity => "note_owner_activity",
Self::CleanBackgroundTerminals => "clean_background_terminals",
Self::RealtimeConversationStart(_) => "realtime_conversation_start",
Self::RealtimeConversationAudio(_) => "realtime_conversation_audio",
@@ -893,6 +921,7 @@ impl Op {
Self::UserInputAnswer { .. } => "user_input_answer",
Self::RequestPermissionsResponse { .. } => "request_permissions_response",
Self::DynamicToolResponse { .. } => "dynamic_tool_response",
Self::InjectResponseItems { .. } => "inject_response_items",
Self::AddToHistory { .. } => "add_to_history",
Self::GetHistoryEntryRequest { .. } => "get_history_entry_request",
Self::ListMcpTools => "list_mcp_tools",
@@ -2454,12 +2483,20 @@ impl InitialHistory {
InitialHistory::Resumed(resumed) => {
resumed.history.iter().find_map(|item| match item {
RolloutItem::SessionMeta(meta_line) => meta_line.meta.forked_from_id,
_ => None,
RolloutItem::ForkReference(_)
| RolloutItem::ResponseItem(_)
| RolloutItem::Compacted(_)
| RolloutItem::TurnContext(_)
| RolloutItem::EventMsg(_) => None,
})
}
InitialHistory::Forked(items) => items.iter().find_map(|item| match item {
RolloutItem::SessionMeta(meta_line) => Some(meta_line.meta.id),
_ => None,
RolloutItem::ForkReference(_)
| RolloutItem::ResponseItem(_)
| RolloutItem::Compacted(_)
| RolloutItem::TurnContext(_)
| RolloutItem::EventMsg(_) => None,
}),
}
}
@@ -2489,7 +2526,11 @@ impl InitialHistory {
.iter()
.filter_map(|ri| match ri {
RolloutItem::EventMsg(ev) => Some(ev.clone()),
_ => None,
RolloutItem::SessionMeta(_)
| RolloutItem::ForkReference(_)
| RolloutItem::ResponseItem(_)
| RolloutItem::Compacted(_)
| RolloutItem::TurnContext(_) => None,
})
.collect(),
),
@@ -2498,7 +2539,11 @@ impl InitialHistory {
.iter()
.filter_map(|ri| match ri {
RolloutItem::EventMsg(ev) => Some(ev.clone()),
_ => None,
RolloutItem::SessionMeta(_)
| RolloutItem::ForkReference(_)
| RolloutItem::ResponseItem(_)
| RolloutItem::Compacted(_)
| RolloutItem::TurnContext(_) => None,
})
.collect(),
),
@@ -2512,12 +2557,20 @@ impl InitialHistory {
InitialHistory::Resumed(resumed) => {
resumed.history.iter().find_map(|item| match item {
RolloutItem::SessionMeta(meta_line) => meta_line.meta.base_instructions.clone(),
_ => None,
RolloutItem::ForkReference(_)
| RolloutItem::ResponseItem(_)
| RolloutItem::Compacted(_)
| RolloutItem::TurnContext(_)
| RolloutItem::EventMsg(_) => None,
})
}
InitialHistory::Forked(items) => items.iter().find_map(|item| match item {
RolloutItem::SessionMeta(meta_line) => meta_line.meta.base_instructions.clone(),
_ => None,
RolloutItem::ForkReference(_)
| RolloutItem::ResponseItem(_)
| RolloutItem::Compacted(_)
| RolloutItem::TurnContext(_)
| RolloutItem::EventMsg(_) => None,
}),
}
}
@@ -2528,12 +2581,20 @@ impl InitialHistory {
InitialHistory::Resumed(resumed) => {
resumed.history.iter().find_map(|item| match item {
RolloutItem::SessionMeta(meta_line) => meta_line.meta.dynamic_tools.clone(),
_ => None,
RolloutItem::ForkReference(_)
| RolloutItem::ResponseItem(_)
| RolloutItem::Compacted(_)
| RolloutItem::TurnContext(_)
| RolloutItem::EventMsg(_) => None,
})
}
InitialHistory::Forked(items) => items.iter().find_map(|item| match item {
RolloutItem::SessionMeta(meta_line) => meta_line.meta.dynamic_tools.clone(),
_ => None,
RolloutItem::ForkReference(_)
| RolloutItem::ResponseItem(_)
| RolloutItem::Compacted(_)
| RolloutItem::TurnContext(_)
| RolloutItem::EventMsg(_) => None,
}),
}
}
@@ -2542,7 +2603,11 @@ impl InitialHistory {
fn session_cwd_from_items(items: &[RolloutItem]) -> Option<PathBuf> {
items.iter().find_map(|item| match item {
RolloutItem::SessionMeta(meta_line) => Some(meta_line.meta.cwd.clone()),
_ => None,
RolloutItem::ForkReference(_)
| RolloutItem::ResponseItem(_)
| RolloutItem::Compacted(_)
| RolloutItem::TurnContext(_)
| RolloutItem::EventMsg(_) => None,
})
}
@@ -2781,10 +2846,45 @@ pub struct SessionMetaLine {
pub git: Option<GitInfo>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
pub struct ForkReferenceItem {
pub rollout_path: PathBuf,
#[serde(
deserialize_with = "deserialize_fork_reference_nth_user_message",
default
)]
pub nth_user_message: i64,
}
fn deserialize_fork_reference_nth_user_message<'de, D>(deserializer: D) -> Result<i64, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = Value::deserialize(deserializer)?;
let Value::Number(number) = value else {
return Err(serde::de::Error::custom(
"expected integer fork reference boundary",
));
};
if let Some(nth_user_message) = number.as_i64() {
return Ok(nth_user_message);
}
if number.as_u64().is_some() {
return Ok(i64::MAX);
}
Err(serde::de::Error::custom(
"expected integer fork reference boundary",
))
}
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, TS)]
#[serde(tag = "type", content = "payload", rename_all = "snake_case")]
pub enum RolloutItem {
SessionMeta(SessionMetaLine),
ForkReference(ForkReferenceItem),
ResponseItem(ResponseItem),
Compacted(CompactedItem),
TurnContext(TurnContextItem),
@@ -4099,6 +4199,23 @@ mod tests {
);
}
#[test]
fn fork_reference_item_deserializes_legacy_usize_max_boundary() {
let item: ForkReferenceItem = serde_json::from_value(json!({
"rollout_path": "/tmp/rollout.jsonl",
"nth_user_message": u64::MAX,
}))
.expect("legacy fork reference item should deserialize");
assert_eq!(
item,
ForkReferenceItem {
rollout_path: PathBuf::from("/tmp/rollout.jsonl"),
nth_user_message: i64::MAX,
}
);
}
#[test]
fn session_source_restriction_product_does_not_guess_subagent_products() {
assert_eq!(