接入 AGC 独立 Agent 试验模式
新增 agent_runtime 配置并通过 stdio App Server 启动独立 Agent 接通后台、交互和 DirectProject 文本回合,保留旧 Codex 回退 对未接入的 function tool 明确失败,避免静默丢失工具调用 补充 AGC 配置回归与实施边界文档
This commit is contained in:
@@ -12,6 +12,7 @@ use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||
mod codex_app_server;
|
||||
mod codex_cli;
|
||||
mod codex_provider_proxy;
|
||||
mod agent_runtime_client;
|
||||
mod direct_codex_attachments;
|
||||
mod direct_codex_audit;
|
||||
mod direct_runtime;
|
||||
@@ -41,6 +42,7 @@ pub(crate) use direct_codex_audit::*;
|
||||
pub(crate) use direct_runtime::*;
|
||||
pub(crate) use direct_tool_bridge::*;
|
||||
pub(crate) use direct_tools_mcp::*;
|
||||
pub(crate) use agent_runtime_client::request_game_creator_agent_runtime;
|
||||
pub(crate) use generation::*;
|
||||
pub(crate) use interaction::*;
|
||||
pub(crate) use prompt::*;
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
//! AGC 到独立 Agent Runtime 的最小外部客户端。
|
||||
//!
|
||||
//! 该模块只替换“推理回合”的进程连接:项目工具、AGC Runtime 和完成门仍由
|
||||
//! AGC 掌控。协议是 agent 自有 JSON-RPC JSONL,不是 Codex App Server。
|
||||
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::process::{Command, Stdio};
|
||||
use std::time::Duration;
|
||||
|
||||
const AGENT_RUNTIME_PROGRAM_ENV: &str = "GENARRATIVE_AGENT_RUNTIME_PROGRAM";
|
||||
const AGENT_RUNTIME_MAX_LINE_BYTES: usize = 4 * 1024 * 1024;
|
||||
const AGENT_RUNTIME_TIMEOUT: Duration = Duration::from_secs(120);
|
||||
|
||||
struct ChildGuard(Option<std::process::Child>);
|
||||
|
||||
impl Drop for ChildGuard {
|
||||
fn drop(&mut self) {
|
||||
if let Some(mut child) = self.0.take() {
|
||||
let _ = child.kill();
|
||||
let _ = child.wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn request_game_creator_agent_runtime(
|
||||
llm: &GameCreatorLlmConfig,
|
||||
request: platform_llm::LlmRunRequest,
|
||||
) -> Result<platform_llm::LlmRunResponse, platform_llm::LlmError> {
|
||||
if !request.function_tools.is_empty() {
|
||||
return Err(platform_llm::LlmError::InvalidRequest(
|
||||
"agent_runtime 当前仅支持无工具文本回合;工具协议尚未接入".into(),
|
||||
));
|
||||
}
|
||||
let llm = llm.clone();
|
||||
tokio::task::spawn_blocking(move || request_game_creator_agent_runtime_blocking(&llm, request))
|
||||
.await
|
||||
.map_err(|_| platform_llm::LlmError::Transport("独立 Agent worker 线程异常退出".into()))?
|
||||
}
|
||||
|
||||
fn request_game_creator_agent_runtime_blocking(
|
||||
llm: &GameCreatorLlmConfig,
|
||||
request: platform_llm::LlmRunRequest,
|
||||
) -> Result<platform_llm::LlmRunResponse, platform_llm::LlmError> {
|
||||
let program = std::env::var_os(AGENT_RUNTIME_PROGRAM_ENV).unwrap_or_else(|| "agent".into());
|
||||
let temp = tempfile::Builder::new()
|
||||
.prefix("genarrative-agent-runtime-")
|
||||
.tempdir()
|
||||
.map_err(|error| {
|
||||
platform_llm::LlmError::Transport(format!("创建 Agent 临时目录失败:{error}"))
|
||||
})?;
|
||||
let db = temp.path().join("agent.db");
|
||||
let config = temp.path().join("agent.toml");
|
||||
std::fs::write(&config, "provider = \"openai\"\n").map_err(|error| {
|
||||
platform_llm::LlmError::Transport(format!("创建 Agent 配置失败:{error}"))
|
||||
})?;
|
||||
let home = temp.path().join("home");
|
||||
std::fs::create_dir_all(&home).map_err(|error| {
|
||||
platform_llm::LlmError::Transport(format!("创建 Agent HOME 失败:{error}"))
|
||||
})?;
|
||||
let mut child = ChildGuard(Some(
|
||||
Command::new(program)
|
||||
.args(["app-server", "--stdio"])
|
||||
.current_dir(temp.path())
|
||||
.env_clear()
|
||||
.env("PATH", std::env::var_os("PATH").unwrap_or_default())
|
||||
.env("HOME", &home)
|
||||
.env("TMPDIR", temp.path())
|
||||
.env("AGENT_CONFIG", &config)
|
||||
.env("AGENT_DB", &db)
|
||||
.env("AGENT_PROVIDER", "openai")
|
||||
.env("AGENT_MODEL", &llm.model)
|
||||
.env("OPENAI_BASE_URL", &llm.base_url)
|
||||
.env("OPENAI_API_KEY", &llm.api_key)
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::null())
|
||||
.spawn()
|
||||
.map_err(|error| {
|
||||
platform_llm::LlmError::Transport(format!("启动独立 Agent 失败:{error}"))
|
||||
})?,
|
||||
));
|
||||
let mut stdin = child
|
||||
.0
|
||||
.as_mut()
|
||||
.and_then(|child| child.stdin.take())
|
||||
.ok_or_else(|| platform_llm::LlmError::Transport("独立 Agent stdin 不可用".into()))?;
|
||||
let stdout = child
|
||||
.0
|
||||
.as_mut()
|
||||
.and_then(|child| child.stdout.take())
|
||||
.ok_or_else(|| platform_llm::LlmError::Transport("独立 Agent stdout 不可用".into()))?;
|
||||
let mut reader = BufReader::new(stdout);
|
||||
write_rpc(
|
||||
&mut stdin,
|
||||
json!({"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":1}}),
|
||||
)?;
|
||||
read_response(&mut reader, 1)?;
|
||||
let task = request
|
||||
.messages
|
||||
.iter()
|
||||
.map(|message| message.content.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n\n");
|
||||
if task.trim().is_empty() {
|
||||
return Err(platform_llm::LlmError::InvalidRequest(
|
||||
"独立 Agent 请求缺少文本消息".into(),
|
||||
));
|
||||
}
|
||||
write_rpc(
|
||||
&mut stdin,
|
||||
json!({"jsonrpc":"2.0","id":2,"method":"run/start","params":{"task":task,"stream":false}}),
|
||||
)?;
|
||||
let accepted = read_response(&mut reader, 2)?;
|
||||
let run_id = accepted
|
||||
.get("result")
|
||||
.and_then(|value| value.get("runId"))
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.ok_or_else(|| platform_llm::LlmError::Deserialize("独立 Agent 未返回 runId".into()))?
|
||||
.to_owned();
|
||||
let (line_sender, line_receiver) = std::sync::mpsc::sync_channel(8);
|
||||
std::thread::spawn(move || {
|
||||
while let Ok(line) = read_line_bounded(&mut reader) {
|
||||
if line_sender.send(line).is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
let deadline = std::time::Instant::now() + AGENT_RUNTIME_TIMEOUT;
|
||||
loop {
|
||||
let remaining = deadline.saturating_duration_since(std::time::Instant::now());
|
||||
if remaining.is_zero() {
|
||||
return Err(platform_llm::LlmError::Timeout { attempts: 1 });
|
||||
}
|
||||
let line = line_receiver
|
||||
.recv_timeout(remaining)
|
||||
.map_err(|error| match error {
|
||||
std::sync::mpsc::RecvTimeoutError::Timeout => {
|
||||
platform_llm::LlmError::Timeout { attempts: 1 }
|
||||
}
|
||||
std::sync::mpsc::RecvTimeoutError::Disconnected => {
|
||||
platform_llm::LlmError::Transport("独立 Agent 连接提前关闭".into())
|
||||
}
|
||||
})?;
|
||||
let value: serde_json::Value = serde_json::from_slice(&line)
|
||||
.map_err(|_| platform_llm::LlmError::Deserialize("独立 Agent 返回无效 JSON".into()))?;
|
||||
if value.get("method").and_then(serde_json::Value::as_str) == Some("run/completed")
|
||||
&& value
|
||||
.get("params")
|
||||
.and_then(|params| params.get("runId"))
|
||||
.and_then(serde_json::Value::as_str)
|
||||
== Some(run_id.as_str())
|
||||
{
|
||||
let output = value
|
||||
.get("params")
|
||||
.and_then(|params| params.get("result"))
|
||||
.and_then(|result| result.get("output"))
|
||||
.ok_or_else(|| {
|
||||
platform_llm::LlmError::Deserialize("独立 Agent 完成结果缺少 output".into())
|
||||
})?;
|
||||
let text = output
|
||||
.get("text")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.unwrap_or_default()
|
||||
.to_owned();
|
||||
let _ = write_rpc(
|
||||
&mut stdin,
|
||||
json!({"jsonrpc":"2.0","id":3,"method":"shutdown","params":{}}),
|
||||
);
|
||||
if let Some(mut child) = child.0.take() {
|
||||
let _ = child.wait();
|
||||
}
|
||||
return Ok(platform_llm::LlmRunResponse {
|
||||
provider: platform_llm::LlmProvider::OpenAiCompatible,
|
||||
model: llm.model.clone(),
|
||||
text,
|
||||
finish_reason: Some("completed".into()),
|
||||
response_id: Some(run_id),
|
||||
usage: None,
|
||||
tool_calls: Vec::new(),
|
||||
});
|
||||
}
|
||||
if value.get("method").and_then(serde_json::Value::as_str) == Some("run/failed") {
|
||||
return Err(platform_llm::LlmError::Transport(
|
||||
"独立 Agent 执行失败".into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_rpc(
|
||||
stdin: &mut impl Write,
|
||||
value: serde_json::Value,
|
||||
) -> Result<(), platform_llm::LlmError> {
|
||||
serde_json::to_writer(&mut *stdin, &value)
|
||||
.map_err(|_| platform_llm::LlmError::Transport("写入独立 Agent 请求失败".into()))?;
|
||||
stdin
|
||||
.write_all(b"\n")
|
||||
.and_then(|_| stdin.flush())
|
||||
.map_err(|_| platform_llm::LlmError::Transport("刷新独立 Agent 请求失败".into()))
|
||||
}
|
||||
|
||||
fn read_line_bounded(reader: &mut impl BufRead) -> Result<Vec<u8>, platform_llm::LlmError> {
|
||||
let mut line = Vec::new();
|
||||
reader
|
||||
.read_until(b'\n', &mut line)
|
||||
.map_err(|_| platform_llm::LlmError::Transport("读取独立 Agent 响应失败".into()))?;
|
||||
if line.is_empty() {
|
||||
return Err(platform_llm::LlmError::Transport(
|
||||
"独立 Agent 连接提前关闭".into(),
|
||||
));
|
||||
}
|
||||
if line.len() > AGENT_RUNTIME_MAX_LINE_BYTES {
|
||||
return Err(platform_llm::LlmError::Deserialize(
|
||||
"独立 Agent 响应超过大小限制".into(),
|
||||
));
|
||||
}
|
||||
Ok(line)
|
||||
}
|
||||
|
||||
fn read_response(
|
||||
reader: &mut impl BufRead,
|
||||
id: u64,
|
||||
) -> Result<serde_json::Value, platform_llm::LlmError> {
|
||||
let line = read_line_bounded(reader)?;
|
||||
let value: serde_json::Value = serde_json::from_slice(&line)
|
||||
.map_err(|_| platform_llm::LlmError::Deserialize("独立 Agent 返回无效 JSON".into()))?;
|
||||
if value.get("id").and_then(serde_json::Value::as_u64) != Some(id) {
|
||||
return Err(platform_llm::LlmError::Transport(
|
||||
"独立 Agent 响应 ID 不匹配".into(),
|
||||
));
|
||||
}
|
||||
if value.get("error").is_some() {
|
||||
return Err(platform_llm::LlmError::Upstream {
|
||||
status_code: 500,
|
||||
message: "独立 Agent 请求失败".into(),
|
||||
});
|
||||
}
|
||||
Ok(value)
|
||||
}
|
||||
@@ -2953,6 +2953,16 @@ pub(crate) async fn direct_game_creator_home_codex_chat(
|
||||
user_prompt: String,
|
||||
) -> Result<String, String> {
|
||||
let config = load_game_creator_app_config()?;
|
||||
if config.agent_mode == GAME_CREATOR_AGENT_MODE_AGENT_RUNTIME {
|
||||
let request = platform_llm::LlmRunRequest::single_turn(system_prompt, user_prompt)
|
||||
.with_api_kind(parse_game_creator_llm_api_kind(&config.llm.api_kind)?)
|
||||
.with_model(config.llm.model.clone())
|
||||
.with_request_timeout_ms(config.llm.request_timeout_ms);
|
||||
return request_game_creator_agent_runtime(&config.llm, request)
|
||||
.await
|
||||
.map(|response| response.text)
|
||||
.map_err(|error| error.to_string());
|
||||
}
|
||||
game_creator_codex_app_server_validate_llm_config(&config.llm)
|
||||
.map_err(|error| error.to_string())?;
|
||||
let snapshot = AgentRuntimeProviderRequestSnapshot {
|
||||
|
||||
@@ -3832,7 +3832,25 @@ async fn run_direct_game_creator_turn_inner(
|
||||
.map_err(|error| {
|
||||
DirectCodexTurnFailure::new(DirectCodexFailureStage::CodeGeneration, error)
|
||||
})?;
|
||||
let reply = if let Some(emitter) = turn_emitter {
|
||||
let config = load_game_creator_app_config().map_err(|error| {
|
||||
DirectCodexTurnFailure::new(DirectCodexFailureStage::CodeGeneration, error)
|
||||
})?;
|
||||
let reply = if config.agent_mode == GAME_CREATOR_AGENT_MODE_AGENT_RUNTIME {
|
||||
let request = platform_llm::LlmRunRequest::single_turn(system_prompt.clone(), prompt.to_string())
|
||||
.with_api_kind(parse_game_creator_llm_api_kind(&config.llm.api_kind).map_err(|error| {
|
||||
DirectCodexTurnFailure::new(DirectCodexFailureStage::CodeGeneration, error)
|
||||
})?)
|
||||
.with_model(config.llm.model.clone())
|
||||
.with_request_timeout_ms(config.llm.request_timeout_ms);
|
||||
let reply = request_game_creator_agent_runtime(&config.llm, request)
|
||||
.await
|
||||
.map_err(|error| DirectCodexTurnFailure::new(DirectCodexFailureStage::CodeGeneration, error.to_string()))?
|
||||
.text;
|
||||
if let Some(emitter) = turn_emitter {
|
||||
emitter.emit("finalizing", Some("response-finalization"), Some(reply.clone()));
|
||||
}
|
||||
Ok(reply)
|
||||
} else if let Some(emitter) = turn_emitter {
|
||||
let emitter = emitter.clone();
|
||||
let mut has_streamed = false;
|
||||
let mut latest_accumulated_text = None;
|
||||
|
||||
@@ -232,6 +232,7 @@ pub(crate) async fn decide_game_creator_agent_runtime_steer_at(
|
||||
request_game_creator_agent_codex_app_server(&decision_snapshot, &llm, request).await
|
||||
}
|
||||
GAME_CREATOR_AGENT_MODE_CODEX_CLI => request_game_creator_agent_codex_cli(request).await,
|
||||
GAME_CREATOR_AGENT_MODE_AGENT_RUNTIME => request_game_creator_agent_runtime(&llm, request).await,
|
||||
GAME_CREATOR_AGENT_MODE_PROVIDER => {
|
||||
build_game_creator_agent_runtime_llm_client(&llm, &config_path)?
|
||||
.run(request)
|
||||
|
||||
@@ -259,6 +259,12 @@ pub(in crate::agent) async fn request_game_creator_agent_background_final_reply_
|
||||
response_observations,
|
||||
);
|
||||
}
|
||||
if agent_mode == GAME_CREATOR_AGENT_MODE_AGENT_RUNTIME {
|
||||
return normalize_game_creator_agent_background_final_reply_response(
|
||||
request_game_creator_agent_runtime(&llm_for_request, attempt_request).await?,
|
||||
response_observations,
|
||||
);
|
||||
}
|
||||
let client = build_game_creator_agent_runtime_llm_client(
|
||||
&llm_for_request,
|
||||
&config_path_for_request,
|
||||
|
||||
@@ -1706,6 +1706,9 @@ pub(in crate::agent) async fn request_game_creator_agent_runtime_llm_with_persis
|
||||
GAME_CREATOR_AGENT_MODE_CODEX_CLI => {
|
||||
request_game_creator_agent_codex_cli(request).await
|
||||
}
|
||||
GAME_CREATOR_AGENT_MODE_AGENT_RUNTIME => {
|
||||
request_game_creator_agent_runtime(&llm_for_request, request).await
|
||||
}
|
||||
GAME_CREATOR_AGENT_MODE_PROVIDER => {
|
||||
let client = build_game_creator_agent_runtime_llm_client(
|
||||
&llm_for_request,
|
||||
@@ -1782,6 +1785,9 @@ pub(in crate::agent) async fn request_game_creator_agent_runtime_llm_with_transi
|
||||
GAME_CREATOR_AGENT_MODE_CODEX_CLI => {
|
||||
request_game_creator_agent_codex_cli(request.clone()).await
|
||||
}
|
||||
GAME_CREATOR_AGENT_MODE_AGENT_RUNTIME => {
|
||||
request_game_creator_agent_runtime(llm, request.clone()).await
|
||||
}
|
||||
GAME_CREATOR_AGENT_MODE_PROVIDER => {
|
||||
let client = client.expect("provider mode constructs an HTTP client");
|
||||
request_game_creator_agent_runtime_provider_llm(&client, llm, request.clone())
|
||||
|
||||
@@ -317,6 +317,9 @@ pub(crate) fn check_game_creator_llm_config_from_config() -> GameCreatorLlmConfi
|
||||
};
|
||||
}
|
||||
};
|
||||
if app_config.agent_mode == GAME_CREATOR_AGENT_MODE_AGENT_RUNTIME {
|
||||
return check_game_creator_agent_runtime_config(&app_config);
|
||||
}
|
||||
if app_config.agent_mode != GAME_CREATOR_AGENT_MODE_PROVIDER {
|
||||
return check_game_creator_codex_config(&app_config);
|
||||
}
|
||||
@@ -376,6 +379,16 @@ pub(crate) fn check_game_creator_llm_config_from_config() -> GameCreatorLlmConfi
|
||||
status
|
||||
}
|
||||
|
||||
fn check_game_creator_agent_runtime_config(
|
||||
app_config: &GameCreatorAppConfig,
|
||||
) -> GameCreatorLlmConfigStatus {
|
||||
let mut status = check_game_creator_llm_config_values(&app_config.llm, "llm");
|
||||
status.agent_mode = GAME_CREATOR_AGENT_MODE_AGENT_RUNTIME.to_string();
|
||||
// The standalone Agent process receives the already configured endpoint/key
|
||||
// through its private child environment; no Codex binary is required.
|
||||
status
|
||||
}
|
||||
|
||||
fn check_game_creator_codex_config(
|
||||
app_config: &GameCreatorAppConfig,
|
||||
) -> GameCreatorLlmConfigStatus {
|
||||
@@ -3750,9 +3763,10 @@ pub(crate) fn normalize_game_creator_agent_mode(value: &str) -> Result<String, S
|
||||
Ok(GAME_CREATOR_AGENT_MODE_CODEX_APP_SERVER.to_string())
|
||||
}
|
||||
GAME_CREATOR_AGENT_MODE_CODEX_CLI => Ok(GAME_CREATOR_AGENT_MODE_CODEX_CLI.to_string()),
|
||||
GAME_CREATOR_AGENT_MODE_AGENT_RUNTIME => Ok(GAME_CREATOR_AGENT_MODE_AGENT_RUNTIME.to_string()),
|
||||
GAME_CREATOR_AGENT_MODE_PROVIDER => Ok(GAME_CREATOR_AGENT_MODE_PROVIDER.to_string()),
|
||||
value => Err(format!(
|
||||
"配置项 agentMode 无效:{value},请使用 codex_app_server、codex_cli 或 provider"
|
||||
"配置项 agentMode 无效:{value},请使用 agent_runtime、codex_app_server、codex_cli 或 provider"
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1290,6 +1290,7 @@ const GAME_CREATOR_LOCAL_CONFIG_FILE_NAME: &str = "game-creator.config.local.jso
|
||||
const GAME_CREATOR_AGENT_MODE_CODEX_APP_SERVER: &str = "codex_app_server";
|
||||
const GAME_CREATOR_AGENT_MODE_CODEX_CLI: &str = "codex_cli";
|
||||
const GAME_CREATOR_AGENT_MODE_PROVIDER: &str = "provider";
|
||||
const GAME_CREATOR_AGENT_MODE_AGENT_RUNTIME: &str = "agent_runtime";
|
||||
const DEFAULT_GAME_CREATOR_LLM_BASE_URL: &str = "https://dev.genarrative.world/gpt/v1";
|
||||
const DEFAULT_GAME_CREATOR_LLM_MODEL: &str = "gpt-5.6-sol";
|
||||
const DEFAULT_GAME_CREATOR_LLM_API_KIND: &str = "openai_responses";
|
||||
|
||||
@@ -160,6 +160,11 @@ fn agent_mode_defaults_to_codex_app_server_and_preserves_explicit_modes() {
|
||||
normalize_game_creator_agent_mode(GAME_CREATOR_AGENT_MODE_CODEX_CLI).expect("cli mode"),
|
||||
GAME_CREATOR_AGENT_MODE_CODEX_CLI
|
||||
);
|
||||
assert_eq!(
|
||||
normalize_game_creator_agent_mode(GAME_CREATOR_AGENT_MODE_AGENT_RUNTIME)
|
||||
.expect("standalone runtime mode"),
|
||||
GAME_CREATOR_AGENT_MODE_AGENT_RUNTIME
|
||||
);
|
||||
assert!(normalize_game_creator_agent_mode("unknown")
|
||||
.expect_err("unknown mode")
|
||||
.contains("agentMode"));
|
||||
|
||||
@@ -656,6 +656,7 @@ export type GameCreatorLlmApiKind =
|
||||
| 'openai_chat'
|
||||
| 'anthropic';
|
||||
export type GameCreatorAgentMode =
|
||||
| 'agent_runtime'
|
||||
| 'codex_app_server'
|
||||
| 'codex_cli'
|
||||
| 'provider';
|
||||
|
||||
@@ -547,7 +547,7 @@ export function RuntimeConfigDialog({
|
||||
const config = normalizeRuntimeConfigDraft(
|
||||
{
|
||||
...runtimeConfigDraft,
|
||||
agentMode: 'codex_app_server',
|
||||
agentMode: runtimeConfigDraft.agentMode,
|
||||
editorApi: allowAdvancedExternalEditorConfig
|
||||
? runtimeConfigDraft.editorApi
|
||||
: defaultRuntimeConfigDraft.editorApi,
|
||||
|
||||
@@ -9018,6 +9018,17 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
|
||||
- 验证:CLI App Server 黑盒子进程测试 4/4、Host 80/80 与实时流 2/2 通过;workspace
|
||||
lock/check、Clippy、fmt、编码和 diff 门禁通过。
|
||||
|
||||
## 2026-09-10 AGC 接入独立 Agent 试验模式
|
||||
|
||||
- 决策:AGC 新增可选 `agent_runtime` 模式,通过 `GENARRATIVE_AGENT_RUNTIME_PROGRAM`
|
||||
启动独立 `agent app-server --stdio`;不把独立 workspace 作为 AGC Rust 依赖,也不把
|
||||
Codex App Server 作为中间协议。旧 `codex_app_server`/`codex_cli` 模式保留为回退路径。
|
||||
- 当前范围:已接通后台 Provider、交互回合和 DirectProject/首页文本回合;消息与模型配置
|
||||
经 JSONL 传给独立 Agent,完成结果映射回现有 LLM 响应。独立 Agent 自己拥有 Kernel、
|
||||
Engine、Provider 和 Runtime,AGC 暂不接管其工具/审批/持久化。
|
||||
- 未完成:AGC 工具动作的跨进程协议、流式事件回传、取消/审批映射和默认模式切换;本阶段
|
||||
不修改 Codex 版本或删除现有接线。
|
||||
|
||||
## 2026-09-09 AGC 改用独立 Agent 内核的替换边界
|
||||
|
||||
- 决策:后续目标是用独立 `rust/` workspace 的 Kernel/Engine 替换 AGC 的 Codex 执行循环,
|
||||
|
||||
@@ -5,19 +5,20 @@
|
||||
让 AGC 的 Codex 模式改由独立 `rust/` workspace 的 Agent Kernel/Engine 执行,
|
||||
同时保留 AGC 宿主对项目、工具权限、审批、Runtime 持久化、预览和完成门的控制。
|
||||
|
||||
## 第一阶段:兼容桥(当前)
|
||||
## 第一阶段:独立进程试验(已完成)
|
||||
|
||||
- 在 AGC 与独立 workspace 之间建立明确的 `AgentTurnAdapter` 边界,不让两套
|
||||
`agent-runtime-core` 类型直接互相泄漏。
|
||||
- 输入:AGC 的消息历史、模型配置、工具定义、当前 Agent 身份和取消信号。
|
||||
- 输出:文本增量、结构化 tool call、工具结果、终态错误和可审计事件。
|
||||
- AGC 继续执行工具动作并写入自己的 Runtime;新 Engine 不创建第二套项目状态机。
|
||||
- 同步 Engine 与 AGC Tokio 的连接通过独立受控线程/有界 channel,禁止在同一 Tokio
|
||||
worker 内直接 `block_on`。
|
||||
- `agent-cli app-server --stdio` 已提供独立 JSON-RPC JSONL 服务;AGC 通过
|
||||
`agent_runtime_client.rs` 以外部子进程连接,不共享 Rust 类型、SQLite 或 Codex。
|
||||
- `agent_runtime` 模式已接入后台 Provider 回合、交互回合和 DirectProject/首页直聊入口;
|
||||
文本消息会转换为新 Agent 的一次 `run/start`,结果再映射回现有 `LlmRunResponse`。
|
||||
- `GENARRATIVE_AGENT_RUNTIME_PROGRAM` 可指定 Agent 可执行文件,默认从 PATH 查找 `agent`;
|
||||
子进程使用独立临时目录、HOME、DB 和环境,旧 Codex 模式仍可回退。
|
||||
- 当前只替换无 function tool 的文本推理;带工具的 AGC 回合会明确失败,不伪装迁移
|
||||
AGC 工具调用、审批、项目 Runtime 或预览完成门。这些仍留在 AGC,后续按协议逐项接入。
|
||||
|
||||
## 第二阶段:实验模式
|
||||
## 第二阶段:工具协议实验(下一步)
|
||||
|
||||
- 新增 `agent_runtime` 模式,仅作为可回滚实验开关,默认仍使用现有 Codex 模式。
|
||||
- 保持 `agent_runtime` 为可回滚实验开关,默认仍使用现有 Codex 模式。
|
||||
- 先覆盖文本回复、单工具调用、工具结果回传、拒绝/取消/超时和事件顺序。
|
||||
- 使用现有 AGC fixture 对比旧 Codex 模式和新 Engine:消息、工具调用参数、Runtime
|
||||
事件、错误分类和终态必须一致。
|
||||
|
||||
Reference in New Issue
Block a user