Files
Genarrative/apps/ai-game-creator-shell/src-tauri/src/runner/dispatch.rs
T
AIGameCreator App 0aaa191f8d 并行拆分客户端运行时与项目摘要模块
将 runtime_tools 拆为十五个职责模块并保留 Agent 可见性

将 Runner 拆为协议端点分发客户端与所有权模块

将进程会话拆为模型持久化生命周期 IO 恢复与测试模块

将项目摘要拆为十四个无环模块并保留一百一十二个导出

记录并行拆分边界与稳定树验收规则
2026-07-22 14:45:36 +08:00

988 lines
37 KiB
Rust

use super::{endpoint::*, project_owner::*, protocol::*, state::*};
use serde::Deserialize;
use serde_json::json;
use sha2::{Digest as _, Sha256};
use std::fs;
use std::io::{self, Read, Write};
use std::net::TcpStream;
use std::path::{Path, PathBuf};
use std::sync::atomic::Ordering;
use std::sync::Arc;
pub(super) fn read_external_agent_runner_frame<R: Read>(
reader: &mut R,
) -> Result<Vec<u8>, ExternalAgentRunnerFrameError> {
let mut prefix = [0_u8; 4];
reader.read_exact(&mut prefix)?;
let length = u32::from_be_bytes(prefix);
if length as usize > EXTERNAL_AGENT_RUNNER_MAX_FRAME_BYTES {
return Err(ExternalAgentRunnerFrameError::Oversize(length));
}
let mut payload = vec![0_u8; length as usize];
reader.read_exact(&mut payload)?;
Ok(payload)
}
pub(super) fn write_external_agent_runner_frame<W: Write>(
writer: &mut W,
payload: &[u8],
) -> Result<(), ExternalAgentRunnerFrameError> {
if payload.len() > EXTERNAL_AGENT_RUNNER_MAX_FRAME_BYTES {
let reported = u32::try_from(payload.len()).unwrap_or(u32::MAX);
return Err(ExternalAgentRunnerFrameError::Oversize(reported));
}
let length = u32::try_from(payload.len())
.map_err(|_| ExternalAgentRunnerFrameError::Oversize(u32::MAX))?;
writer.write_all(&length.to_be_bytes())?;
writer.write_all(payload)?;
Ok(())
}
pub(super) fn valid_external_agent_runner_request_id(request_id: &str) -> bool {
!request_id.is_empty()
&& request_id.len() <= 128
&& request_id
.bytes()
.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_' | b'.' | b':'))
}
pub(super) fn external_agent_runner_request_fingerprint(
request: &ExternalAgentRunnerRequest,
) -> String {
let params = serde_json::to_vec(&request.params).unwrap_or_default();
let mut digest = Sha256::new();
digest.update(request.protocol_version.to_be_bytes());
digest.update(request.method.as_bytes());
digest.update([0]);
digest.update(params);
hex_encode(&digest.finalize())
}
pub(super) fn external_agent_runner_request_root(
request: &ExternalAgentRunnerRequest,
) -> Result<PathBuf, String> {
let root = request
.params
.root
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
.ok_or_else(|| "Runtime 请求缺少 root".to_string())?;
let root = PathBuf::from(root);
if !root.is_absolute() {
return Err("Runtime 请求 root 必须是绝对路径".to_string());
}
Ok(root)
}
pub(super) fn external_agent_runner_request_agent(
request: &ExternalAgentRunnerRequest,
) -> Result<String, String> {
let agent = request
.params
.agent
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
.ok_or_else(|| "Runtime 请求缺少 agent".to_string())?;
if agent.len() > 256 {
return Err("Runtime 请求 agent 过长".to_string());
}
Ok(agent.to_string())
}
pub(super) fn external_agent_runner_request_session_id(
request: &ExternalAgentRunnerRequest,
) -> Result<Option<String>, String> {
let Some(session_id) = request
.params
.session_id
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
else {
return Ok(None);
};
if session_id.len() > 256 {
return Err("Runtime 请求 sessionId 过长".to_string());
}
Ok(Some(session_id.to_string()))
}
pub(super) fn external_agent_runner_request_run_id(
request: &ExternalAgentRunnerRequest,
) -> Result<String, String> {
let run_id = request
.params
.run_id
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
.ok_or_else(|| "Runtime 请求缺少 runId".to_string())?;
if run_id.len() > 256 {
return Err("Runtime 请求 runId 过长".to_string());
}
Ok(run_id.to_string())
}
pub(super) fn external_agent_runner_request_action_id(
request: &ExternalAgentRunnerRequest,
) -> Result<String, String> {
let action_id = request
.params
.action_id
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
.ok_or_else(|| "runtime.continue_action 请求缺少 actionId".to_string())?;
if action_id.len() > 256 {
return Err("runtime.continue_action actionId 过长".to_string());
}
Ok(action_id.to_string())
}
pub(super) fn external_agent_runner_request_steer_id(
request: &ExternalAgentRunnerRequest,
) -> Result<String, String> {
let steer_id = request
.params
.steer_id
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
.ok_or_else(|| "runtime.steer 请求缺少 steerId".to_string())?;
if steer_id.len() > 256 {
return Err("runtime.steer steerId 过长".to_string());
}
Ok(steer_id.to_string())
}
pub(super) fn external_agent_runner_request_wake_target(
request: &ExternalAgentRunnerRequest,
) -> Result<Option<(String, String)>, String> {
match (&request.params.agent, &request.params.run_id) {
(None, None) => Ok(None),
(Some(_), Some(_)) => Ok(Some((
external_agent_runner_request_agent(request)?,
external_agent_runner_request_run_id(request)?,
))),
_ => Err("runtime.wake_pending 定向请求必须同时提供 agent 和 runId".to_string()),
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(super) struct ExternalAgentRunnerTargetRunProbe {
pub(super) agent_id: String,
pub(super) run_id: String,
pub(super) status: String,
pub(super) phase: String,
}
impl ExternalAgentRunnerTargetRunProbe {
pub(super) fn matches(&self, agent_id: &str, run_id: &str) -> bool {
self.agent_id == agent_id && self.run_id == run_id
}
pub(super) fn still_requires_wake(&self) -> bool {
self.status == "pending"
|| (self.status == "running"
&& matches!(
self.phase.as_str(),
"waiting-for-delegate-receipts" | "waiting-for-provider-retry"
))
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(super) enum ExternalAgentRunnerTargetWakeRetry {
NotObserved,
StillPending,
}
pub(super) fn external_agent_runner_target_run_probe(
runtime: &crate::AgentRuntimeResult,
agent_id: &str,
run_id: &str,
) -> Option<ExternalAgentRunnerTargetRunProbe> {
runtime
.recent_tasks
.iter()
.rev()
.find(|task| task.agent_id == agent_id && task.run_id == run_id)
.map(|task| ExternalAgentRunnerTargetRunProbe {
agent_id: task.agent_id.clone(),
run_id: task.run_id.clone(),
status: task.status.clone(),
phase: task.phase.clone(),
})
.or_else(|| {
let state = &runtime.state;
(state.agent_id == agent_id && state.run_id == run_id).then(|| {
ExternalAgentRunnerTargetRunProbe {
agent_id: state.agent_id.clone(),
run_id: state.run_id.clone(),
status: state.status.clone(),
phase: state.phase.clone(),
}
})
})
}
pub(super) fn classify_external_agent_runner_target_wake(
agent_id: &str,
run_id: &str,
scan_probes: &[ExternalAgentRunnerTargetRunProbe],
current_probe: Option<&ExternalAgentRunnerTargetRunProbe>,
) -> Result<(), ExternalAgentRunnerTargetWakeRetry> {
let scan_probe = scan_probes
.iter()
.find(|probe| probe.matches(agent_id, run_id));
if scan_probe.is_some_and(|probe| !probe.still_requires_wake())
|| current_probe
.is_some_and(|probe| probe.matches(agent_id, run_id) && !probe.still_requires_wake())
{
return Ok(());
}
if scan_probe.is_some() || current_probe.is_some_and(|probe| probe.matches(agent_id, run_id)) {
Err(ExternalAgentRunnerTargetWakeRetry::StillPending)
} else {
Err(ExternalAgentRunnerTargetWakeRetry::NotObserved)
}
}
pub(super) fn external_agent_runner_target_wake_retryable_response(
request_id: &str,
retry: ExternalAgentRunnerTargetWakeRetry,
) -> ExternalAgentRunnerResponse {
let message = match retry {
ExternalAgentRunnerTargetWakeRetry::NotObserved => {
"定向 wake 暂时未观察到目标 run,请复用同一 requestId 重试"
}
ExternalAgentRunnerTargetWakeRetry::StillPending => {
"目标 run 仍在等待推进,execution lane 可能正在占用,请复用同一 requestId 重试"
}
};
ExternalAgentRunnerResponse::failure(
request_id,
EXTERNAL_AGENT_RUNNER_RETRYABLE_WAKE_ERROR_CODE,
message,
)
}
pub(super) fn external_agent_runner_target_wake_error_is_retryable(error: &str) -> bool {
let normalized = error.to_ascii_lowercase();
error.contains("正在")
|| error.contains("暂时")
|| normalized.contains("would block")
|| normalized.contains("timed out")
|| normalized.contains("timeout")
|| normalized.contains("sharing violation")
}
pub(super) fn dispatch_external_agent_runner_wake_pending_request(
request: &ExternalAgentRunnerRequest,
root: &Path,
token: &str,
) -> ExternalAgentRunnerResponse {
let target = match external_agent_runner_request_wake_target(request) {
Ok(target) => target,
Err(error) => {
return ExternalAgentRunnerResponse::failure(
&request.request_id,
"invalid-params",
error,
);
}
};
let resumed = match crate::wake_pending_game_creator_agent_background_tasks_at(root) {
Ok(resumed) => resumed,
Err(error) => {
let error = redact_runner_secret(&error, token);
if target.is_some() && external_agent_runner_target_wake_error_is_retryable(&error) {
return ExternalAgentRunnerResponse::failure(
&request.request_id,
EXTERNAL_AGENT_RUNNER_RETRYABLE_WAKE_ERROR_CODE,
format!("定向 wake 暂时无法完成,请复用同一 requestId 重试:{error}"),
);
}
return ExternalAgentRunnerResponse::failure(
&request.request_id,
"runtime-error",
error,
);
}
};
let Some((agent_id, run_id)) = target else {
return ExternalAgentRunnerResponse::success(
&request.request_id,
json!({ "accepted": true }),
);
};
let scan_probes = resumed
.iter()
.filter_map(|runtime| external_agent_runner_target_run_probe(runtime, &agent_id, &run_id))
.collect::<Vec<_>>();
if classify_external_agent_runner_target_wake(&agent_id, &run_id, &scan_probes, None).is_ok() {
return ExternalAgentRunnerResponse::success(
&request.request_id,
json!({ "accepted": true }),
);
}
let current = match crate::read_game_creator_agent_runtime_at(root, &agent_id) {
Ok(current) => current,
Err(error) => {
let error = redact_runner_secret(&error, token);
if external_agent_runner_target_wake_error_is_retryable(&error) {
return ExternalAgentRunnerResponse::failure(
&request.request_id,
EXTERNAL_AGENT_RUNNER_RETRYABLE_WAKE_ERROR_CODE,
format!("定向 wake 后暂时无法确认目标 run,请复用同一 requestId 重试:{error}"),
);
}
return ExternalAgentRunnerResponse::failure(
&request.request_id,
"runtime-error",
error,
);
}
};
let current_probe = external_agent_runner_target_run_probe(&current, &agent_id, &run_id);
match classify_external_agent_runner_target_wake(
&agent_id,
&run_id,
&scan_probes,
current_probe.as_ref(),
) {
Ok(()) => {
ExternalAgentRunnerResponse::success(&request.request_id, json!({ "accepted": true }))
}
Err(retry) => {
external_agent_runner_target_wake_retryable_response(&request.request_id, retry)
}
}
}
pub(super) fn external_agent_runner_response_is_cacheable(
response: &ExternalAgentRunnerResponse,
) -> bool {
response
.error
.as_ref()
.is_none_or(|error| error.code != EXTERNAL_AGENT_RUNNER_RETRYABLE_WAKE_ERROR_CODE)
}
pub(super) fn cache_external_agent_runner_response_if_cacheable(
cache: &mut ExternalAgentRunnerRequestCache,
request_id: &str,
fingerprint: &str,
response: &ExternalAgentRunnerResponse,
) {
if external_agent_runner_response_is_cacheable(response) {
cache.insert(
request_id.to_string(),
fingerprint.to_string(),
response.clone(),
);
}
}
pub(super) fn dispatch_external_agent_runner_runtime_request(
request: &ExternalAgentRunnerRequest,
state: &ExternalAgentRunnerServerState,
) -> ExternalAgentRunnerResponse {
let fingerprint = external_agent_runner_request_fingerprint(request);
let mut cache = lock_unpoisoned(&state.write_request_cache);
if let Some(cached) = cache.find(&request.request_id) {
if cached.fingerprint == fingerprint {
return cached.response.clone();
}
return ExternalAgentRunnerResponse::failure(
&request.request_id,
"request-id-conflict",
"同一 requestId 不能用于不同请求",
);
}
if matches!(
request.method.as_str(),
"runtime.wake_pending"
| "runtime.resume"
| "runtime.continue_action"
| "runtime.steer"
| "runtime.pause"
| "runtime.cancel"
| "runtime.compact"
) && state.draining.load(Ordering::Acquire)
{
return ExternalAgentRunnerResponse::failure(
&request.request_id,
"runner-draining",
"Agent Runner 正在排空并准备退出,拒绝新的写请求",
);
}
let token = state.endpoint_snapshot().token;
let response = match request.method.as_str() {
"runtime.wake_pending"
| "runtime.resume"
| "runtime.continue_action"
| "runtime.steer"
| "runtime.pause"
| "runtime.cancel"
| "runtime.compact" => {
let root = match external_agent_runner_request_root(request) {
Ok(root) => root,
Err(error) => {
return ExternalAgentRunnerResponse::failure(
&request.request_id,
"invalid-params",
error,
);
}
};
let root = match state.claim_project_execution_owner(&root) {
Ok(root) => root,
Err(error) => {
return ExternalAgentRunnerResponse::failure(
&request.request_id,
"project-execution-owned",
error,
);
}
};
if request.method == "runtime.wake_pending" {
dispatch_external_agent_runner_wake_pending_request(request, &root, &token)
} else {
let result = match request.method.as_str() {
"runtime.resume" => crate::resume_game_creator_agent_background_tasks_at(&root)
.map(|_| json!({ "accepted": true }))
.map_err(|error| error.to_string()),
"runtime.compact" => (|| {
let agent = external_agent_runner_request_agent(request)?;
let session_id = external_agent_runner_request_session_id(request)?;
let result = tauri::async_runtime::block_on(
crate::compact_game_creator_agent_runtime_session_at(
&root,
&agent,
session_id.as_deref(),
),
)?;
serde_json::to_value(result)
.map_err(|error| format!("序列化上下文压缩结果失败:{error}"))
})(),
"runtime.continue_action" => (|| {
let agent = external_agent_runner_request_agent(request)?;
let run_id = external_agent_runner_request_run_id(request)?;
let action_id = external_agent_runner_request_action_id(request)?;
crate::resume_game_creator_agent_pending_action_for_agent_at(
&root, &agent, &run_id, &action_id,
)
.map(|_| json!({ "accepted": true }))
.map_err(|error| error.to_string())
})(),
"runtime.steer" => (|| {
let agent = external_agent_runner_request_agent(request)?;
let run_id = external_agent_runner_request_run_id(request)?;
let steer_id = external_agent_runner_request_steer_id(request)?;
crate::validate_game_creator_agent_runtime_steer_notification_at(
&root, &agent, &run_id, &steer_id,
)?;
let provider_interrupted =
crate::interrupt_game_creator_agent_runtime_provider_request_at(
&root, &agent, &run_id,
)?;
crate::wake_pending_game_creator_agent_background_tasks_at(&root)
.map_err(|error| error.to_string())?;
Ok(json!({
"accepted": true,
"providerInterrupted": provider_interrupted,
}))
})(),
"runtime.pause" => (|| {
let agent = external_agent_runner_request_agent(request)?;
let run_id = external_agent_runner_request_run_id(request)?;
let runtime = crate::read_game_creator_agent_runtime_at(&root, &agent)?;
if runtime.state.run_id != run_id {
return Err("runtime.pause 与当前 Agent runId 不匹配".to_string());
}
let goal = crate::read_game_creator_agent_goal_at(
&root,
&agent,
&runtime.state.session_id,
)?
.ok_or_else(|| "runtime.pause 未找到当前 Session Goal".to_string())?;
if goal.run_id != run_id
|| goal.status != crate::AGENT_GOAL_STATUS_PAUSE_REQUESTED
{
return Err(
"runtime.pause 缺少精确的 durable pause request".to_string()
);
}
let provider_interrupted =
crate::interrupt_game_creator_agent_runtime_provider_request_at(
&root, &agent, &run_id,
)?;
let runtime =
crate::pause_game_creator_agent_runtime_for_goal_at(&root, &goal)?;
Ok(json!({
"accepted": true,
"providerInterrupted": provider_interrupted,
"status": runtime.state.status,
"phase": runtime.state.phase,
}))
})(),
"runtime.cancel" => (|| {
let agent = external_agent_runner_request_agent(request)?;
let run_id = external_agent_runner_request_run_id(request)?;
let runtime = crate::read_game_creator_agent_runtime_at(&root, &agent)?;
if runtime.state.run_id != run_id {
return Err("runtime.cancel 与当前 Agent runId 不匹配".to_string());
}
let goal = crate::read_game_creator_agent_goal_at(
&root,
&agent,
&runtime.state.session_id,
)?
.ok_or_else(|| "runtime.cancel 未找到当前 Session Goal".to_string())?;
if goal.run_id != run_id || goal.status != crate::AGENT_GOAL_STATUS_CLEARING
{
return Err(
"runtime.cancel 缺少精确的 durable Goal clear request".to_string()
);
}
crate::write_game_creator_agent_runtime_cancel_request(
&root,
&agent,
&run_id,
"开发者清理持久 Goal",
)?;
let provider_interrupted =
crate::interrupt_game_creator_agent_runtime_provider_request_at(
&root, &agent, &run_id,
)?;
let runtime = crate::cancel_game_creator_agent_runtime_task_at(
&root, &agent, &run_id,
)?;
Ok(json!({
"accepted": true,
"providerInterrupted": provider_interrupted,
"status": runtime.state.status,
"phase": runtime.state.phase,
}))
})(),
_ => unreachable!(),
};
match result {
Ok(result) => ExternalAgentRunnerResponse::success(&request.request_id, result),
Err(error) => ExternalAgentRunnerResponse::failure(
&request.request_id,
"runtime-error",
redact_external_agent_runner_runtime_error(&root, &error, &token),
),
}
}
}
"runner.shutdown_if_idle" | "shutdown_if_idle" => {
if request.params.root.is_some() {
match external_agent_runner_request_root(request) {
Ok(root) => match canonicalize_external_agent_runner_project_root(&root) {
Ok(root) => state.remember_root(&root),
Err(error) => {
return ExternalAgentRunnerResponse::failure(
&request.request_id,
"invalid-params",
error,
);
}
},
Err(error) => {
return ExternalAgentRunnerResponse::failure(
&request.request_id,
"invalid-params",
error,
);
}
}
}
if state
.draining
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
.is_err()
{
ExternalAgentRunnerResponse::failure(
&request.request_id,
"runner-draining",
"Agent Runner 已在排空",
)
} else if state.active_connections.load(Ordering::Acquire) > 1 {
state.draining.store(false, Ordering::Release);
ExternalAgentRunnerResponse::success(
&request.request_id,
json!({ "idle": false, "willShutdown": false }),
)
} else {
match external_agent_runner_known_roots_are_idle(state) {
Ok(false) => {
state.draining.store(false, Ordering::Release);
ExternalAgentRunnerResponse::success(
&request.request_id,
json!({ "idle": false, "willShutdown": false }),
)
}
Ok(true) => {
state.shutdown_requested.store(true, Ordering::Release);
ExternalAgentRunnerResponse::success(
&request.request_id,
json!({ "idle": true, "willShutdown": true }),
)
}
Err(error) => {
state.draining.store(false, Ordering::Release);
ExternalAgentRunnerResponse::failure(
&request.request_id,
"runtime-state-unreadable",
redact_runner_secret(&error, &token),
)
}
}
}
}
_ => ExternalAgentRunnerResponse::failure(
&request.request_id,
"method-not-found",
"Agent Runner 不支持该方法",
),
};
cache_external_agent_runner_response_if_cacheable(
&mut cache,
&request.request_id,
&fingerprint,
&response,
);
response
}
pub(super) fn handle_external_agent_runner_request(
request: ExternalAgentRunnerRequest,
state: &ExternalAgentRunnerServerState,
) -> ExternalAgentRunnerResponse {
if !valid_external_agent_runner_request_id(&request.request_id) {
return ExternalAgentRunnerResponse::failure(
"",
"invalid-request-id",
"Agent Runner requestId 无效",
);
}
if request.protocol_version != EXTERNAL_AGENT_RUNNER_PROTOCOL_VERSION {
return ExternalAgentRunnerResponse::failure(
&request.request_id,
"protocol-version-mismatch",
"Agent Runner 协议版本不兼容",
);
}
let expected_token = state.endpoint_snapshot().token;
if !constant_time_eq(request.token.as_bytes(), expected_token.as_bytes()) {
return ExternalAgentRunnerResponse::failure(
&request.request_id,
"unauthorized",
"Agent Runner 请求未授权",
);
}
if request.method.len() > 128 {
return ExternalAgentRunnerResponse::failure(
&request.request_id,
"invalid-method",
"Agent Runner method 无效",
);
}
match request.method.as_str() {
"runner.ping" => ExternalAgentRunnerResponse::success(
&request.request_id,
json!({
"status": "ok",
"pid": std::process::id(),
"bootId": state.endpoint_snapshot().boot_id,
}),
),
"runner.status" => match serde_json::to_value(state.public_status()) {
Ok(status) => ExternalAgentRunnerResponse::success(&request.request_id, status),
Err(_) => ExternalAgentRunnerResponse::failure(
&request.request_id,
"status-serialization-failed",
"序列化 Agent Runner 状态失败",
),
},
"mcp.status" => {
if state.draining.load(Ordering::Acquire) {
return ExternalAgentRunnerResponse::failure(
&request.request_id,
"runner-draining",
"Agent Runner 正在排空并准备退出,拒绝新的 MCP 状态请求",
);
}
let result = (|| {
let root = external_agent_runner_request_root(&request)?;
let root = canonicalize_external_agent_runner_project_root(&root)?;
state.remember_root(&root);
let catalog =
tauri::async_runtime::block_on(crate::read_game_creator_mcp_catalog_at(&root))?;
serde_json::to_value(catalog)
.map_err(|error| format!("序列化 MCP catalog 失败:{error}"))
})();
match result {
Ok(catalog) => ExternalAgentRunnerResponse::success(&request.request_id, catalog),
Err(error) => ExternalAgentRunnerResponse::failure(
&request.request_id,
"mcp-status-failed",
redact_runner_secret(&error, &expected_token),
),
}
}
"runtime.wake_pending"
| "runtime.resume"
| "runtime.continue_action"
| "runtime.steer"
| "runtime.pause"
| "runtime.cancel"
| "runtime.compact"
| "runner.shutdown_if_idle"
| "shutdown_if_idle" => dispatch_external_agent_runner_runtime_request(&request, state),
_ => ExternalAgentRunnerResponse::failure(
&request.request_id,
"method-not-found",
"Agent Runner 不支持该方法",
),
}
}
pub(super) fn external_agent_runner_runtime_state_is_idle(status: &str, phase: &str) -> bool {
if matches!(phase, "completed" | "cancelled" | "failed" | "paused") {
return true;
}
matches!(status, "idle" | "failed" | "cancelled" | "paused")
}
#[derive(Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(super) struct ExternalAgentRunnerTaskQueueProbe {
#[serde(default)]
pub(super) pending: u64,
#[serde(default, alias = "waiting")]
pub(super) waiting_for_confirmation: u64,
#[serde(default)]
pub(super) waiting_for_user_input: u64,
#[serde(default)]
pub(super) running: u64,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub(super) struct ExternalAgentRunnerRuntimeStateProbe {
#[serde(default)]
pub(super) status: String,
#[serde(default)]
pub(super) phase: String,
#[serde(default)]
pub(super) task_queue: ExternalAgentRunnerTaskQueueProbe,
}
pub(super) fn external_agent_runner_directory_has_durable_files(
path: &Path,
) -> Result<bool, String> {
let entries = match fs::read_dir(path) {
Ok(entries) => entries,
Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(false),
Err(error) => {
return Err(format!(
"读取 Agent Runtime durable 目录失败:{}: {error}",
path.display()
));
}
};
for entry in entries {
let entry = entry.map_err(|error| {
format!(
"读取 Agent Runtime durable 目录项失败:{}: {error}",
path.display()
)
})?;
let file_type = entry.file_type().map_err(|error| {
format!(
"读取 Agent Runtime durable 项类型失败:{}: {error}",
entry.path().display()
)
})?;
if file_type.is_symlink() {
return Err(format!(
"Agent Runtime durable 目录不允许符号链接:{}",
entry.path().display()
));
}
if file_type.is_file()
|| (file_type.is_dir()
&& external_agent_runner_directory_has_durable_files(&entry.path())?)
{
return Ok(true);
}
}
Ok(false)
}
pub(super) fn external_agent_runner_root_is_idle(root: &Path) -> Result<bool, String> {
if crate::has_active_process_sessions_at(root)? {
return Ok(false);
}
for durable_dir in [
root.join(".agent/runtime/pending-actions"),
root.join(".agent/runtime/finalizations"),
root.join(".agent/runtime/provider-handoffs"),
root.join(".agent/runtime/provider-retries"),
root.join(".agent/runtime/tool-plan-handoffs"),
] {
if external_agent_runner_directory_has_durable_files(&durable_dir)? {
return Ok(false);
}
}
let agents_dir = root.join(".agent/runtime/agents");
let entries = match fs::read_dir(&agents_dir) {
Ok(entries) => entries,
Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(true),
Err(error) => {
return Err(format!(
"读取 Agent Runtime 状态目录失败:{}: {error}",
agents_dir.display()
));
}
};
for entry in entries {
let entry = entry.map_err(|error| {
format!(
"读取 Agent Runtime 状态目录项失败:{}: {error}",
agents_dir.display()
)
})?;
let file_type = entry.file_type().map_err(|error| {
format!(
"读取 Agent Runtime 状态类型失败:{}: {error}",
entry.path().display()
)
})?;
if !file_type.is_file()
|| entry.path().extension().and_then(|value| value.to_str()) != Some("json")
{
continue;
}
let metadata = entry.metadata().map_err(|error| {
format!(
"读取 Agent Runtime 状态元数据失败:{}: {error}",
entry.path().display()
)
})?;
if metadata.len() > EXTERNAL_AGENT_RUNNER_MAX_FRAME_BYTES as u64 {
return Err(format!(
"Agent Runtime 状态文件超过读取上限:{}",
entry.path().display()
));
}
let content = fs::read(entry.path()).map_err(|error| {
format!(
"读取 Agent Runtime 状态失败:{}: {error}",
entry.path().display()
)
})?;
let runtime = serde_json::from_slice::<ExternalAgentRunnerRuntimeStateProbe>(&content)
.map_err(|_| format!("解析 Agent Runtime 状态失败:{}", entry.path().display()))?;
if runtime.task_queue.pending > 0
|| runtime.task_queue.waiting_for_confirmation > 0
|| runtime.task_queue.waiting_for_user_input > 0
|| runtime.task_queue.running > 0
{
return Ok(false);
}
if !external_agent_runner_runtime_state_is_idle(&runtime.status, &runtime.phase) {
return Ok(false);
}
}
Ok(true)
}
pub(super) fn external_agent_runner_known_roots_are_idle(
state: &ExternalAgentRunnerServerState,
) -> Result<bool, String> {
let roots = lock_unpoisoned(&state.known_roots)
.iter()
.cloned()
.collect::<Vec<_>>();
for root in roots {
if !external_agent_runner_root_is_idle(&root)? {
return Ok(false);
}
}
Ok(true)
}
pub(super) fn write_external_agent_runner_response(
stream: &mut TcpStream,
response: &ExternalAgentRunnerResponse,
) -> Result<(), String> {
let payload =
serde_json::to_vec(response).map_err(|_| "序列化 Agent Runner 响应失败".to_string())?;
write_external_agent_runner_frame(stream, &payload)
.map_err(|error| format!("写入 Agent Runner 响应失败:{error}"))?;
stream
.flush()
.map_err(|error| format!("刷新 Agent Runner 响应失败:{error}"))
}
pub(super) fn handle_external_agent_runner_connection(
mut stream: TcpStream,
state: Arc<ExternalAgentRunnerServerState>,
) -> Result<(), String> {
let _active = ExternalAgentRunnerActiveConnection { state: &state };
stream
.set_read_timeout(Some(EXTERNAL_AGENT_RUNNER_IO_TIMEOUT))
.and_then(|_| stream.set_write_timeout(Some(EXTERNAL_AGENT_RUNNER_IO_TIMEOUT)))
.map_err(|error| format!("配置 Agent Runner 连接超时失败:{error}"))?;
let payload = match read_external_agent_runner_frame(&mut stream) {
Ok(payload) => payload,
Err(ExternalAgentRunnerFrameError::Oversize(_)) => {
let response = ExternalAgentRunnerResponse::failure(
"",
"frame-too-large",
"Agent Runner 请求超过 1 MiB 上限",
);
return write_external_agent_runner_response(&mut stream, &response);
}
Err(ExternalAgentRunnerFrameError::Io(error))
if matches!(
error.kind(),
io::ErrorKind::UnexpectedEof
| io::ErrorKind::ConnectionReset
| io::ErrorKind::TimedOut
| io::ErrorKind::WouldBlock
) =>
{
return Ok(());
}
Err(error) => return Err(format!("读取 Agent Runner 请求失败:{error}")),
};
let request = match serde_json::from_slice::<ExternalAgentRunnerRequest>(&payload) {
Ok(request) => request,
Err(_) => {
let response = ExternalAgentRunnerResponse::failure(
"",
"invalid-json",
"Agent Runner 请求 JSON 无效",
);
return write_external_agent_runner_response(&mut stream, &response);
}
};
let response = handle_external_agent_runner_request(request, &state);
write_external_agent_runner_response(&mut stream, &response)
}