接入单Agent原生工具目录
为 OpenAI Chat 与 Responses 广告独立 Runtime 和 MCP 函数。 支持持久计划 checkpoint、多动作顺序与严格协议拒绝。 补齐真实 Provider 协议门禁、确定性回归和 Runtime 文档。
This commit is contained in:
@@ -175,7 +175,11 @@ const pollIntervalMs = 750;
|
||||
const runTimeoutMs = 30 * 60 * 1000;
|
||||
const processRunnerKillStartTimeoutMs = 5 * 60 * 1000;
|
||||
const commandOutputLimit = 4 * 1024 * 1024;
|
||||
const supportedToolPlanProtocols = new Set(['native_function', 'text_json']);
|
||||
const supportedToolPlanProtocols = new Set([
|
||||
'native_runtime_tools',
|
||||
'native_function',
|
||||
'text_json',
|
||||
]);
|
||||
const processSessionSuites = new Set([
|
||||
'process-session',
|
||||
'process-session-runner-kill',
|
||||
@@ -4385,6 +4389,55 @@ async function validateProjectSkillEvidence() {
|
||||
|
||||
const providerLifecycle = validateProjectSkillProviderLifecycle(agentDb);
|
||||
const toolPlanProtocolCount = validateMainRunToolPlanProtocols(agentDb);
|
||||
const projectSkillToolPlanProtocols = agentDb.filter(
|
||||
(record) =>
|
||||
record.recordType === 'agent.runtime.tool_plan.protocol' &&
|
||||
record.agentId === mainAgentId &&
|
||||
record.runId === state.initialRunId,
|
||||
);
|
||||
const projectSkillToolPlanRepairs = agentDb.filter(
|
||||
(record) =>
|
||||
record.recordType === 'agent.runtime.tool_plan.repair' &&
|
||||
record.agentId === mainAgentId &&
|
||||
record.runId === state.initialRunId,
|
||||
);
|
||||
const nativeRuntimeToolPlanCount = projectSkillToolPlanProtocols.filter(
|
||||
(record) => record.protocol === 'native_runtime_tools',
|
||||
).length;
|
||||
const nativeRuntimeToolPlanRepairCount = projectSkillToolPlanRepairs.filter(
|
||||
(record) => record.protocol === 'native_runtime_tools',
|
||||
).length;
|
||||
const allToolPlanProtocolAudits = [
|
||||
...projectSkillToolPlanProtocols,
|
||||
...projectSkillToolPlanRepairs,
|
||||
];
|
||||
const wrapperToolPlanFallbackCount = allToolPlanProtocolAudits.filter(
|
||||
(record) => record.protocol === 'native_function',
|
||||
).length;
|
||||
const textJsonToolPlanFallbackCount = allToolPlanProtocolAudits.filter(
|
||||
(record) => record.protocol === 'text_json',
|
||||
).length;
|
||||
assert(
|
||||
nativeRuntimeToolPlanCount === toolPlanProtocolCount &&
|
||||
nativeRuntimeToolPlanRepairCount === projectSkillToolPlanRepairs.length &&
|
||||
wrapperToolPlanFallbackCount === 0 &&
|
||||
textJsonToolPlanFallbackCount === 0 &&
|
||||
projectSkillToolPlanProtocols.every(
|
||||
(record) =>
|
||||
Number.isSafeInteger(record.functionCallCount) &&
|
||||
record.functionCallCount > 0 &&
|
||||
Array.isArray(record.callIds) &&
|
||||
record.callIds.length === record.functionCallCount &&
|
||||
new Set(record.callIds).size === record.callIds.length &&
|
||||
Array.isArray(record.functionNames) &&
|
||||
record.functionNames.length === record.functionCallCount &&
|
||||
record.functionNames.every(
|
||||
(name) =>
|
||||
isNonEmptyString(name) && name !== 'submit_agent_tool_plan',
|
||||
),
|
||||
),
|
||||
'project-skill-native-tool-plan-protocol-required',
|
||||
);
|
||||
const publicSurfaces = {
|
||||
task: taskSnapshot.all,
|
||||
event: events,
|
||||
@@ -4467,6 +4520,11 @@ async function validateProjectSkillEvidence() {
|
||||
(record) => record.status === 'ok',
|
||||
).length,
|
||||
toolPlanProtocolCount,
|
||||
nativeRuntimeToolPlanCount,
|
||||
toolPlanRepairCount: projectSkillToolPlanRepairs.length,
|
||||
nativeRuntimeToolPlanRepairCount,
|
||||
wrapperToolPlanFallbackCount,
|
||||
textJsonToolPlanFallbackCount,
|
||||
providerRequestIdentityCount: providerLifecycle.requestIdentityCount,
|
||||
providerLifecycleStartedCount: providerLifecycle.startedCount,
|
||||
providerLifecycleTerminalCount: providerLifecycle.terminalCount,
|
||||
@@ -4523,6 +4581,19 @@ async function collectPartialProjectSkillEvidence() {
|
||||
record.runId === state.initialRunId &&
|
||||
record.status === 'ok',
|
||||
);
|
||||
const toolPlanRepairs = agentDb.filter(
|
||||
(record) =>
|
||||
record.recordType === 'agent.runtime.tool_plan.repair' &&
|
||||
record.agentId === mainAgentId &&
|
||||
record.runId === state.initialRunId,
|
||||
);
|
||||
const toolPlanProtocols = agentDb.filter(
|
||||
(record) =>
|
||||
record.recordType === 'agent.runtime.tool_plan.protocol' &&
|
||||
record.agentId === mainAgentId &&
|
||||
record.runId === state.initialRunId,
|
||||
);
|
||||
const allToolPlanProtocolAudits = [...toolPlanProtocols, ...toolPlanRepairs];
|
||||
const matchingSkillReads = successfulExecutions.filter(
|
||||
(record) =>
|
||||
record.tool === 'file.read' &&
|
||||
@@ -4588,6 +4659,19 @@ async function collectPartialProjectSkillEvidence() {
|
||||
confirmedActionCount: state.projectSkill.confirmedActionCount,
|
||||
verificationPassed,
|
||||
successfulToolExecutionCount: successfulExecutions.length,
|
||||
nativeRuntimeToolPlanCount: toolPlanProtocols.filter(
|
||||
(record) => record.protocol === 'native_runtime_tools',
|
||||
).length,
|
||||
toolPlanRepairCount: toolPlanRepairs.length,
|
||||
nativeRuntimeToolPlanRepairCount: toolPlanRepairs.filter(
|
||||
(record) => record.protocol === 'native_runtime_tools',
|
||||
).length,
|
||||
wrapperToolPlanFallbackCount: allToolPlanProtocolAudits.filter(
|
||||
(record) => record.protocol === 'native_function',
|
||||
).length,
|
||||
textJsonToolPlanFallbackCount: allToolPlanProtocolAudits.filter(
|
||||
(record) => record.protocol === 'text_json',
|
||||
).length,
|
||||
providerRequestIdentityCount: new Set(
|
||||
lifecycle.map((record) => record.requestId).filter(Boolean),
|
||||
).size,
|
||||
@@ -15093,6 +15177,11 @@ function emptyProjectSkillEvidence() {
|
||||
hostVerificationPassed: false,
|
||||
successfulToolExecutionCount: 0,
|
||||
toolPlanProtocolCount: 0,
|
||||
nativeRuntimeToolPlanCount: 0,
|
||||
toolPlanRepairCount: 0,
|
||||
nativeRuntimeToolPlanRepairCount: 0,
|
||||
wrapperToolPlanFallbackCount: 0,
|
||||
textJsonToolPlanFallbackCount: 0,
|
||||
providerRequestIdentityCount: 0,
|
||||
providerLifecycleStartedCount: 0,
|
||||
providerLifecycleTerminalCount: 0,
|
||||
|
||||
@@ -6930,7 +6930,7 @@ async fn run_game_creator_agent_background_task_pass_with_context(
|
||||
}
|
||||
|
||||
pub(crate) const AGENT_RUNTIME_BACKGROUND_LOOP_LIMIT: usize = 6;
|
||||
const AGENT_RUNTIME_BACKGROUND_TOOL_ACTION_LIMIT: usize = 3;
|
||||
pub(crate) const AGENT_RUNTIME_BACKGROUND_TOOL_ACTION_LIMIT: usize = 3;
|
||||
pub(crate) const AGENT_RUNTIME_FINALIZATION_SCHEMA_VERSION: &str =
|
||||
"game-creator-runtime-finalization.v3";
|
||||
const AGENT_RUNTIME_FINALIZATION_LEGACY_SCHEMA_VERSION: &str =
|
||||
@@ -7049,6 +7049,8 @@ pub(crate) struct ParsedAgentRuntimeToolPlan {
|
||||
pub(crate) protocol: &'static str,
|
||||
pub(crate) call_id: Option<String>,
|
||||
pub(crate) function_name: Option<String>,
|
||||
pub(crate) call_ids: Vec<String>,
|
||||
pub(crate) function_names: Vec<String>,
|
||||
}
|
||||
|
||||
struct RequestedAgentRuntimeToolPlan {
|
||||
@@ -14461,7 +14463,8 @@ async fn request_game_creator_agent_background_tool_plan_at(
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
match parse_game_creator_agent_tool_plan_llm_response(&response) {
|
||||
match parse_game_creator_agent_tool_plan_llm_response_with_catalog(&response, &mcp_catalog)
|
||||
{
|
||||
Ok(parsed) => {
|
||||
let mut plan = parsed.plan;
|
||||
enrich_game_creator_mcp_actions(&mut plan, &mcp_catalog)?;
|
||||
@@ -14476,6 +14479,9 @@ async fn request_game_creator_agent_background_tool_plan_at(
|
||||
"protocol": parsed.protocol,
|
||||
"callId": parsed.call_id,
|
||||
"functionName": parsed.function_name,
|
||||
"functionCallCount": parsed.call_ids.len(),
|
||||
"callIds": parsed.call_ids,
|
||||
"functionNames": parsed.function_names,
|
||||
"responseId": response.response_id,
|
||||
}),
|
||||
)?;
|
||||
@@ -14499,8 +14505,12 @@ async fn request_game_creator_agent_background_tool_plan_at(
|
||||
let protocol_error = sanitize_agent_runtime_text(&error, 400);
|
||||
let protocol = if response.tool_calls.is_empty() {
|
||||
"text_json"
|
||||
} else {
|
||||
} else if response.tool_calls.len() == 1
|
||||
&& response.tool_calls[0].name == AGENT_RUNTIME_TOOL_PLAN_FUNCTION_NAME
|
||||
{
|
||||
"native_function"
|
||||
} else {
|
||||
"native_runtime_tools"
|
||||
};
|
||||
let call_id = response.tool_calls.first().map(|call| call.id.clone());
|
||||
let function_name = response.tool_calls.first().map(|call| call.name.clone());
|
||||
@@ -14539,7 +14549,7 @@ async fn request_game_creator_agent_background_tool_plan_at(
|
||||
.messages
|
||||
.push(LlmMessage::assistant(response_preview));
|
||||
request.messages.push(LlmMessage::user(format!(
|
||||
"上一条输出不符合工具计划协议:{protocol_error}\n请修复格式。支持 function tool 时重新调用 {AGENT_RUNTIME_TOOL_PLAN_FUNCTION_NAME};只有上游不支持 function tool 时才返回一个完整 JSON object。不要解释,不要 markdown,不要代码围栏,也不要在 JSON 前后添加任何文本。"
|
||||
"上一条输出不符合工具计划协议:{protocol_error}\n请修复格式。若当前请求提供原生工具目录,请只调用 update_agent_plan、动作工具或 respond_to_user;只有请求未提供 function tools 时才返回一个完整 JSON object。不要解释,不要 markdown,不要代码围栏,也不要在 JSON 前后添加任何文本。"
|
||||
)));
|
||||
request.enable_web_search = false;
|
||||
}
|
||||
@@ -15553,21 +15563,22 @@ fn build_game_creator_agent_background_tool_plan_request(
|
||||
"command.start 使用 {\"program\":\"受信任 PATH 中的裸可执行名\"",
|
||||
);
|
||||
let api_kind = parse_game_creator_llm_api_kind(&llm.api_kind)?;
|
||||
let protocol_prompt = if api_kind == platform_llm::LlmApiKind::Anthropic {
|
||||
"当前 Provider 不提供 function tools,请返回上述 schema 的单个完整 JSON object;不要解释、markdown 或代码围栏。"
|
||||
} else {
|
||||
"必须直接调用当前请求提供的原生函数:需要更新持久计划时调用 update_agent_plan,需要行动时调用对应动作工具,已有观察足够时调用 respond_to_user。不要调用未广告的旧 submit_agent_tool_plan,也不要把计划或动作放在普通文本中。"
|
||||
};
|
||||
let mut request = LlmRunRequest::new(vec![
|
||||
LlmMessage::system(game_creator_agent_runtime_tool_plan_system_prompt_for_agent(
|
||||
agent_id,
|
||||
)),
|
||||
LlmMessage::system(game_creator_agent_runtime_tool_plan_system_prompt_for_agent(agent_id)),
|
||||
LlmMessage::user(prompt),
|
||||
LlmMessage::user(format!(
|
||||
"协议要求:支持 function tool 时必须调用 {AGENT_RUNTIME_TOOL_PLAN_FUNCTION_NAME},不要把计划放在普通文本中;只有上游不支持 function tool 时才返回同结构的单个 JSON 对象。"
|
||||
)),
|
||||
LlmMessage::user(protocol_prompt),
|
||||
])
|
||||
.with_api_kind(api_kind)
|
||||
.with_max_output_tokens(AGENT_RUNTIME_TOOL_PLAN_MAX_OUTPUT_TOKENS)
|
||||
.with_response_text_verbosity(platform_llm::LlmResponseTextVerbosity::Low);
|
||||
if api_kind != platform_llm::LlmApiKind::Anthropic {
|
||||
request = request
|
||||
.with_function_tools(vec![game_creator_agent_tool_plan_function_tool()])
|
||||
.with_function_tools(build_agent_runtime_native_function_tools(mcp_catalog)?)
|
||||
.with_tool_choice(platform_llm::LlmToolChoice::Required);
|
||||
}
|
||||
request = apply_game_creator_llm_web_search(
|
||||
@@ -15578,86 +15589,6 @@ fn build_game_creator_agent_background_tool_plan_request(
|
||||
Ok((llm, config_path, request, repository_context_fingerprint))
|
||||
}
|
||||
|
||||
fn game_creator_agent_tool_plan_function_tool() -> platform_llm::LlmFunctionTool {
|
||||
platform_llm::LlmFunctionTool::new(
|
||||
AGENT_RUNTIME_TOOL_PLAN_FUNCTION_NAME,
|
||||
"提交本轮 Agent 的任务理解、可选持久计划更新、白名单工具动作或最终回复。Runtime 只执行 arguments 中经过本地策略校验的动作。",
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"required": ["thinkingSummary", "planUpdate", "plan", "actions", "response"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"thinkingSummary": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"description": "一句话概括当前任务理解和决策依据"
|
||||
},
|
||||
"planUpdate": {
|
||||
"type": ["object", "null"],
|
||||
"description": "复杂任务的持久计划更新;没有真实进度变化时传 null",
|
||||
"required": ["explanation", "steps"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"explanation": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"steps": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"maxItems": AGENT_RUNTIME_PLAN_STEP_LIMIT,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["step", "status"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"step": { "type": "string", "minLength": 1 },
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["pending", "in_progress", "completed"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"plan": {
|
||||
"type": "array",
|
||||
"maxItems": AGENT_RUNTIME_PLAN_STEP_LIMIT,
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"actions": {
|
||||
"type": "array",
|
||||
"maxItems": AGENT_RUNTIME_BACKGROUND_TOOL_ACTION_LIMIT,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["tool", "reason", "input"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"tool": {
|
||||
"type": "string",
|
||||
"enum": agent_runtime_executable_tools()
|
||||
},
|
||||
"reason": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"input": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
"type": "string",
|
||||
"description": "actions 为空且已有观察足够时给开发者的最终回复,否则为空字符串"
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
.with_strict(true)
|
||||
}
|
||||
|
||||
fn build_game_creator_agent_background_final_reply_request(
|
||||
root: &Path,
|
||||
agent_id: &str,
|
||||
@@ -15829,6 +15760,20 @@ pub(crate) fn parse_game_creator_agent_tool_plan_response(
|
||||
|
||||
pub(crate) fn parse_game_creator_agent_tool_plan_llm_response(
|
||||
response: &platform_llm::LlmRunResponse,
|
||||
) -> Result<ParsedAgentRuntimeToolPlan, String> {
|
||||
parse_game_creator_agent_tool_plan_llm_response_with_catalog(
|
||||
response,
|
||||
&GameCreatorMcpCatalog {
|
||||
fingerprint: String::new(),
|
||||
servers: Vec::new(),
|
||||
tools: Vec::new(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn parse_game_creator_agent_tool_plan_llm_response_with_catalog(
|
||||
response: &platform_llm::LlmRunResponse,
|
||||
mcp_catalog: &GameCreatorMcpCatalog,
|
||||
) -> Result<ParsedAgentRuntimeToolPlan, String> {
|
||||
if response.tool_calls.is_empty() {
|
||||
return parse_game_creator_agent_tool_plan_response(response.text.as_str()).map(|plan| {
|
||||
@@ -15837,28 +15782,40 @@ pub(crate) fn parse_game_creator_agent_tool_plan_llm_response(
|
||||
protocol: "text_json",
|
||||
call_id: None,
|
||||
function_name: None,
|
||||
call_ids: Vec::new(),
|
||||
function_names: Vec::new(),
|
||||
}
|
||||
});
|
||||
}
|
||||
if response.tool_calls.len() != 1 {
|
||||
return Err(format!(
|
||||
"Agent 工具计划协议错误:必须恰好调用一次 {AGENT_RUNTIME_TOOL_PLAN_FUNCTION_NAME},实际收到 {} 次 function call",
|
||||
response.tool_calls.len()
|
||||
));
|
||||
if !response.text.trim().is_empty() {
|
||||
return Err(
|
||||
"Agent 原生工具协议错误:function calls 响应不能同时携带普通文本正文".to_string(),
|
||||
);
|
||||
}
|
||||
let call = &response.tool_calls[0];
|
||||
if call.name != AGENT_RUNTIME_TOOL_PLAN_FUNCTION_NAME {
|
||||
return Err(format!(
|
||||
"Agent 工具计划协议错误:必须调用 {AGENT_RUNTIME_TOOL_PLAN_FUNCTION_NAME},实际调用了非预期函数"
|
||||
));
|
||||
if response.tool_calls.len() == 1
|
||||
&& response.tool_calls[0].name == AGENT_RUNTIME_TOOL_PLAN_FUNCTION_NAME
|
||||
{
|
||||
let call = &response.tool_calls[0];
|
||||
let plan = parse_game_creator_agent_tool_plan_payload(call.arguments.as_str(), true)
|
||||
.map_err(|error| format!("{error};function arguments 解析失败"))?;
|
||||
return Ok(ParsedAgentRuntimeToolPlan {
|
||||
plan,
|
||||
protocol: "native_function",
|
||||
call_id: Some(call.id.clone()),
|
||||
function_name: Some(call.name.clone()),
|
||||
call_ids: vec![call.id.clone()],
|
||||
function_names: vec![call.name.clone()],
|
||||
});
|
||||
}
|
||||
let plan = parse_game_creator_agent_tool_plan_payload(call.arguments.as_str(), true)
|
||||
.map_err(|error| format!("{error};function arguments 解析失败"))?;
|
||||
let native = parse_agent_runtime_native_tool_calls(&response.tool_calls, mcp_catalog)?;
|
||||
let plan = normalize_game_creator_agent_tool_plan(native.plan)?;
|
||||
Ok(ParsedAgentRuntimeToolPlan {
|
||||
plan,
|
||||
protocol: "native_function",
|
||||
call_id: Some(call.id.clone()),
|
||||
function_name: Some(call.name.clone()),
|
||||
protocol: "native_runtime_tools",
|
||||
call_id: native.call_ids.first().cloned(),
|
||||
function_name: native.function_names.first().cloned(),
|
||||
call_ids: native.call_ids,
|
||||
function_names: native.function_names,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -15891,8 +15848,14 @@ fn parse_game_creator_agent_tool_plan_payload(
|
||||
);
|
||||
}
|
||||
}
|
||||
let mut plan = serde_json::from_str::<AgentRuntimeToolPlan>(payload)
|
||||
let plan = serde_json::from_str::<AgentRuntimeToolPlan>(payload)
|
||||
.map_err(|error| format!("解析 Agent 工具计划失败:{error}"))?;
|
||||
normalize_game_creator_agent_tool_plan(plan)
|
||||
}
|
||||
|
||||
fn normalize_game_creator_agent_tool_plan(
|
||||
mut plan: AgentRuntimeToolPlan,
|
||||
) -> Result<AgentRuntimeToolPlan, String> {
|
||||
if plan.thinking_summary.trim().is_empty() {
|
||||
return Err("Agent 工具计划协议错误:thinkingSummary 不能为空".to_string());
|
||||
}
|
||||
@@ -28704,7 +28667,7 @@ pub(crate) fn game_creator_agent_runtime_tool_plan_system_prompt_for_agent(
|
||||
}
|
||||
|
||||
pub(crate) fn game_creator_agent_runtime_tool_plan_system_prompt() -> String {
|
||||
let prompt = "你是 Genarrative AI 游戏创作多智能体 Runtime 中的专业 Agent。你必须在白名单工具内规划行动:先给一句 thinkingSummary,再给短计划,再决定是否请求工具。只能请求 memory.read、memory.write、conversation.read、asset.list、project.index、project.search、project.verify、project.checkpoint、project.restore、project.diff、file.list、file.read、file.write、file.patch、file.delete、task.list、task.create、task.update、command.run_limited、preview.start、canvas.asset_generate、blackboard.write、agent.message、agent.delegate、agent.schedule_ready、agent.run_status。处理代码任务时先用 project.search 定位,再用带行号的 file.read 获取足够上下文;优先使用 file.patch 做精确局部修改,只有确认文件已废弃时才请求 file.delete,批量修改前创建 project.checkpoint,修改后再次读取验证。每次成功执行 file.write、file.patch、file.delete 或 project.restore 都会产生新的项目 revision;最后一次修改后必须成功执行 project.verify,或成功执行 command.run_limited 的 game.static_smoke,才能返回空 actions 收束。文件回读不能替代可执行验证,验证后再次修改必须重新验证。需要执行 package.json 中的验证脚本时,先读取 package.json,再把真实脚本名和读到的完整命令原样提交给 project.verify;script 可以是 check、typecheck、test、lint、build,或使用 check:<name>、test:<name>(例如 test:unit)、lint:<name>、typecheck:<name>、build:<name>、verify:<name>、validate:<name> 形式的命名脚本,其中冒号后的每个非空段必须以字母或数字开头且只能包含字母、数字、连字符、下划线或点;不得猜测或改写 expectedCommand。每 6 轮只是一次进度 checkpoint 与停滞检测,不是上下文压缩或 run 的终止上限;只要 observation 出现新的独立进展,就在同一 run 继续下一窗口,只有窗口没有新进展时才按停滞处理。真正的上下文压缩仅由 token 阈值或显式 compact 触发。Agent 私有记忆只能由本人写入,跨 Agent 共享稳定结论用 blackboard.write,给单个 Agent 留上下文用 agent.message。不要假装工具已执行;工具结果会由 Runtime 作为 observation 返回。优先调用 submit_agent_tool_plan function tool 提交结构化计划;只有上游不支持 function tool 时才返回同结构的单个 JSON 对象。不要 markdown,不要泄露密钥。"
|
||||
let prompt = "你是 Genarrative AI 游戏创作多智能体 Runtime 中的专业 Agent。你必须在白名单工具内规划行动:先给一句 thinkingSummary,再给短计划,再决定是否请求工具。只能请求 memory.read、memory.write、conversation.read、asset.list、project.index、project.search、project.verify、project.checkpoint、project.restore、project.diff、file.list、file.read、file.write、file.patch、file.delete、task.list、task.create、task.update、command.run_limited、preview.start、canvas.asset_generate、blackboard.write、agent.message、agent.delegate、agent.schedule_ready、agent.run_status。处理代码任务时先用 project.search 定位,再用带行号的 file.read 获取足够上下文;优先使用 file.patch 做精确局部修改,只有确认文件已废弃时才请求 file.delete,批量修改前创建 project.checkpoint,修改后再次读取验证。每次成功执行 file.write、file.patch、file.delete 或 project.restore 都会产生新的项目 revision;最后一次修改后必须成功执行 project.verify,或成功执行 command.run_limited 的 game.static_smoke,才能返回空 actions 收束。文件回读不能替代可执行验证,验证后再次修改必须重新验证。需要执行 package.json 中的验证脚本时,先读取 package.json,再把真实脚本名和读到的完整命令原样提交给 project.verify;script 可以是 check、typecheck、test、lint、build,或使用 check:<name>、test:<name>(例如 test:unit)、lint:<name>、typecheck:<name>、build:<name>、verify:<name>、validate:<name> 形式的命名脚本,其中冒号后的每个非空段必须以字母或数字开头且只能包含字母、数字、连字符、下划线或点;不得猜测或改写 expectedCommand。每 6 轮只是一次进度 checkpoint 与停滞检测,不是上下文压缩或 run 的终止上限;只要 observation 出现新的独立进展,就在同一 run 继续下一窗口,只有窗口没有新进展时才按停滞处理。真正的上下文压缩仅由 token 阈值或显式 compact 触发。Agent 私有记忆只能由本人写入,跨 Agent 共享稳定结论用 blackboard.write,给单个 Agent 留上下文用 agent.message。不要假装工具已执行;工具结果会由 Runtime 作为 observation 返回。支持 function tools 时,直接调用 update_agent_plan、与白名单工具一一对应的动作函数或 respond_to_user;update_agent_plan 可单独作为持久进度 checkpoint,Runtime 记录后会继续下一轮,也可在同一响应中按顺序附带最多三个动作或最终回复,动作与最终回复不得共存。只有上游不支持 function tools 时才返回同结构的单个 JSON 对象。不要 markdown,不要泄露密钥。"
|
||||
.replace(
|
||||
"只能请求 memory.read",
|
||||
"只能请求 user.input_request、memory.read",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -41,6 +41,7 @@ use tauri_plugin_opener::OpenerExt;
|
||||
// 调试落盘模块(保存 LLM 原始输出 / 失败输入,排查截断、空返回等)放在 debug_drafts.rs。
|
||||
// 用 #[cfg] 编译期门控:仅开发(debug)且非测试构建编入;生产 release 与 cargo test 下整体剔除。
|
||||
mod agent;
|
||||
mod agent_native_tools;
|
||||
mod assets;
|
||||
mod browser;
|
||||
mod cli;
|
||||
@@ -71,6 +72,7 @@ mod user_input;
|
||||
mod windows;
|
||||
|
||||
use agent::*;
|
||||
use agent_native_tools::*;
|
||||
use assets::*;
|
||||
use browser::*;
|
||||
use cli::*;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,6 +16,15 @@
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-16 AI 游戏创作 Agent Runtime 使用 Provider 原生工具目录
|
||||
|
||||
- 背景:OpenAI-compatible planning 虽已使用 function calling,但只向 Provider 提供 `submit_agent_tool_plan` 包装函数,真实工具藏在 `actions[].tool + input` 中,具体工具名和参数主要依赖长提示词,Provider 不能按工具 schema 约束选择与输入。
|
||||
- 决策:OpenAI Chat / Responses 直接获得稳定的 `update_agent_plan`、`respond_to_user`、每个内置 Runtime action 和动态 MCP function。内置名称从规范 tool id 映射,MCP 名称从 server/tool 身份派生;真实 MCP binding 与 fingerprint 由 Runtime 注入。每个 action 携带非空 reason 与独立 input schema,一轮最多 1 次计划更新和 3 个动作,或计划更新加最终回复;动作与回复不得共存。plan-only 是合法持久 checkpoint,应用后继续同一 run planning,未完成计划和项目验证门禁继续阻止最终化。
|
||||
- 兼容与安全:新请求和 repair 不广告旧 wrapper;parser 只为 Anthropic、历史 fixture 和旧响应保留 wrapper/text JSON 兼容。未知函数、重复 call id、重复计划/回复、四个动作、正文与 function calls 共存、MCP binding 冲突和非法参数均在副作用前失败。公共审计只保存协议、call 数量、函数名和 call id,不保存 arguments、正文或 MCP 参数。
|
||||
- 影响范围:`agent_native_tools.rs`、后台 planning 请求/解析/repair、Runtime 协议审计、真实 E2E harness、AI 游戏创作 Runtime 与 App 实施计划。
|
||||
- 验证方式:确定性目录/parser/Runtime 回归覆盖 plan-only、计划加多动作、计划加回复、顺序与负向边界;正式 `openai_chat / gpt-5.5` 的 `project-skill` suite 最终 9/9 个成功计划和 6/6 个 repair 全为 `native_runtime_tools`,wrapper/text fallback 为 0,只读 Skill、单文件修改、Agent/宿主验证、唯一 lifecycle/assistant/completed、零泄漏和隔离清理全部通过。
|
||||
- 关联文档:`docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
|
||||
|
||||
## 2026-07-16 AI 游戏创作 Agent Runtime 使用项目 Skill 渐进加载
|
||||
|
||||
- 背景:单 Agent 已能按目录 scope 应用 `AGENTS.md`,但领域工作流如果全部预加载进每轮 prompt,会长期占用上下文并让无关说明干扰规划;只保存文件哈希又无法证明模型真正读取并遵循了匹配工作流。
|
||||
|
||||
@@ -989,6 +989,19 @@ V1.25 在 V1.24 仓库启动上下文上增加项目内 Skill catalog,但不
|
||||
|
||||
2026-07-16 正式 `openai_chat / gpt-5.5` 的 `project-skill` suite **PASS**。一次性项目的 hash-only 原始验收先真实失败;Agent 在首个项目变更前精确读取匹配 `.codex/skills/release-capsule/SKILL.md` 1 次,无关 Skill 读取为 0,以 1 个项目变更动作只修改 `game/release-capsule.txt`,随后 Agent `project.verify` 与宿主独立复验均通过。最终脚本复跑记录 25 条 task、41 条 event、57 条 Agent DB、5 个成功工具动作和 2 个确认动作;4 组 tool-plan Provider lifecycle 全部唯一 `started -> completed`,最终 assistant 与 completed audit 各 1,重复 message/receipt、fallback replay 和遗留 finalization 均为 0。最终回复、公共审计、测试报告中的 Skill 正文、API Key、诱饵、项目与正式配置绝对路径泄漏均为 0;正式配置 CLI 调用为 0,源 Runner endpoint 和配置副本保持不变,隔离 Runner、AppData 与 disposable 项目已按 sentinel 清理。V1.25 真实行为门禁至此完成。
|
||||
|
||||
## V1.26 Provider 原生工具目录
|
||||
|
||||
V1.26 把 OpenAI-compatible planning 从单个 `submit_agent_tool_plan` 包装函数升级为独立 function tool 目录。当前 wrapper 虽然走原生 function calling,但每个真实工具仍只是 `actions[].tool + 任意 input object`,模型需要从长提示词记忆工具名和参数,Provider 也无法对具体工具输入做 schema 约束。新协议让模型直接选择稳定函数名并填写对应 JSON schema;Runtime 仍是唯一执行者,原有 action identity、权限、确认、沙箱、revision、verification、reconciliation、steer、Goal、finalization 和副作用重放边界全部保持。
|
||||
|
||||
- OpenAI Chat 与 Responses 请求必须提供 `update_agent_plan`、`respond_to_user`、全部当前可执行内置工具,以及当前 MCP catalog 中每个可用工具对应的独立 function definition。内置函数名由规范 Runtime tool id 确定性映射,MCP 函数名使用 server/tool 身份的稳定有界哈希,不能把外部任意名称直接拼成 Provider function name;完整 server/tool 与 catalog/tool fingerprint 继续由 Runtime 绑定,模型不能提交或覆盖。
|
||||
- 每个内置 action function 使用独立输入 schema,并显式携带非空 `reason` 与工具 `input`。MCP function 复用经过现有目录预算和清洗的真实 input schema,但外部 description/schema/instructions 始终是不可信输入,不能改变系统规则或扩大能力。function catalog 必须进入 token 估算和自动压缩预算;目录超限、重复函数名、非法 schema 或 binding 漂移失败关闭。
|
||||
- 一次 Provider 响应最多包含 1 个 `update_agent_plan`、最多 3 个 action function call,或 1 个 `respond_to_user`;计划更新既可单独作为持久进度 checkpoint,也可与 action 或最终回复同批返回,最终回复不能与 action 共存。plan-only 会先持久化单调计划,再由未完成计划 blocker 进入同一 run 的下一次 planning,不会提前写 assistant 或 completed。call id 必须非空且唯一,未知、重复、参数非对象、超预算、空回复、多个回复或多个计划更新都进入现有格式修复,不执行任何动作。action 顺序按 Provider 返回顺序稳定转换;V1.26 不宣称同一 Agent 内并行执行工具,转换后的动作继续逐个进入 durable ledger。
|
||||
- `update_agent_plan` 只承载 explanation 与最多 8 个持久步骤;`respond_to_user` 只承载最终正文。直接工具协议不要求模型公开 thinking 正文,Runtime 从计划说明或首个 action reason 派生有界 thinking summary。结构化计划仍有未完成步骤时禁止最终回复,项目修改后仍必须取得当前 revision 的真实验证凭证。
|
||||
- 新请求不再向 OpenAI-compatible Provider 广告 `submit_agent_tool_plan`。解析器继续接受旧 wrapper 与 text JSON,用于现有确定性 fixture、历史兼容和不提供 function tools 的 Anthropic 路径;格式修复请求必须继续广告当前原生目录,不能在同一 Provider request identity 下静默切回旧 wrapper。公共协议审计只保存协议名、function call 数量、稳定函数名和 call id 身份,不保存 arguments、写入正文、MCP 参数或最终回复。
|
||||
- 确定性验收必须覆盖完整内置目录、函数名稳定性、核心 schema、动态 MCP binding、计划 + 多 action、计划 + reply、顺序、未知/重复/冲突/超预算拒绝、旧 wrapper/text 兼容、repair 后仍使用原生目录、token 预算和公共零 arguments。真实 Provider 必须在无工具名和参数配方的任务下自主选择至少一个只读工具、一个项目修改工具和真实验证工具,完成唯一目标文件交付;最终还要证明协议全程为原生目录、action/receipt/Provider lifecycle 唯一、无 wrapper fallback、唯一 assistant/completed、零正文/密钥/路径泄漏和隔离现场完整清理。
|
||||
|
||||
2026-07-16 正式 `openai_chat / gpt-5.5` 的 `project-skill` suite **PASS**。首轮真实执行在匹配 Skill 读取后暴露旧 parser 拒绝 plan-only,保留现场复验进一步证明模型会先单独调用 `update_agent_plan`;Runtime 空动作分支原本已能持久化计划并安全进入下一轮,因此移除矛盾的 parser 拒绝并补三轮确定性闭环。最终加强门禁复跑记录 31 条 task、57 条 event、94 条 Agent DB、6 个成功工具动作和 2 个确认动作;9/9 个成功工具计划与 6/6 个格式修复全部使用 `native_runtime_tools`,旧 wrapper 与 text JSON fallback 均为 0。Agent 在首个项目修改前读取匹配 Skill 1 次、无关 Skill 0 次,只修改 1 个目标文件,Agent `project.verify` 与宿主复验均通过;15 个 tool-plan 加 1 个 final-reply Provider lifecycle 全部唯一闭合,最终 assistant/completed 各 1,重复 message/receipt、遗留 finalization、Skill 正文、API Key、诱饵、项目/正式配置路径和报告泄漏均为 0,隔离 Runner、AppData 与一次性项目完整清理。确定性 Tauri 全量为 822 passed / 4 ignored;V1.26 真实行为门禁至此完成。
|
||||
|
||||
## 验收命令
|
||||
|
||||
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml structured_plan_ -- --nocapture`
|
||||
|
||||
@@ -572,4 +572,6 @@ game-project/
|
||||
- 2026-07-16 V1.24 已完成真实验收:正式 `openai_chat / gpt-5.5` 的 `scoped-agents` suite 在无规则正文、期望内容和工具配方的任务下,让同一 Agent 只修改 `alpha / beta` 两个兄弟目录交付文件;根、父、各自叶规则全部精确命中且兄弟串用为 0,Agent `project.verify` 与宿主复验均通过。最终脚本复跑的 8 组 Provider lifecycle 唯一闭合,最终 assistant/completed 各 1,重复持久化,以及最终回复/公共审计/报告中的规则正文、API Key、诱饵、项目/配置路径泄漏均为 0,隔离现场完整清理;不再把 prompt 可见性代替模型遵循证据。
|
||||
- 2026-07-16 起,同一 Runtime 文档的“V1.25 Codex 式项目 Skill 发现与渐进加载”作为项目工作流加载事实源。仓库启动上下文升级为 `repository-startup-context-v3`,只发现项目内 `.codex/skills/<name>/SKILL.md` 与 `.agents/skills/<name>/SKILL.md` 直接入口,同名时 `.codex` 优先;首轮 prompt 只注入清洗后的 `name / description / entryPath / contentSha256`,正文必须在任务命中后通过现有 `file.read` 按需读取。Skill 不能扩大工具、权限、确认、沙箱、隐私或完成门禁,与适用路径的 `AGENTS.md` 冲突时后者优先;active Skill 变化推进 repository fingerprint 并阻断旧 pending 动作后重规划。
|
||||
- 2026-07-16 V1.25 已完成真实验收:正式 `openai_chat / gpt-5.5` 的 `project-skill` suite 先让 hash-only 原始验收真实失败;Agent 在首个变更前精确读取匹配 Skill 1 次、无关 Skill 0 次,以 1 个变更动作只修改目标文件,Agent `project.verify` 与宿主复验均通过。最终脚本复跑记录 25 条 task、41 条 event、57 条 Agent DB 和 5 个成功工具动作;4 组 tool-plan Provider lifecycle 唯一闭合,最终 assistant/completed 各 1,Skill 正文、API Key、诱饵、项目/配置路径泄漏和重复持久化均为 0,隔离 Runner/AppData/项目完整清理。
|
||||
- 2026-07-16 起,同一 Runtime 文档的“V1.26 Provider 原生工具目录”作为 OpenAI-compatible planning 协议事实源。Chat / Responses 不再只广告 `submit_agent_tool_plan` 包装函数,而是直接提供 `update_agent_plan`、`respond_to_user`、全部内置 Runtime action 和动态 MCP function;每个函数使用独立 schema,Runtime 继续负责身份、权限、确认、沙箱、revision、验证、恢复与副作用防重放。Anthropic 与历史 fixture 保留 text JSON / wrapper 解析兼容,但新请求和 repair 不能静默降级。plan-only 是合法持久 checkpoint,未完成计划仍阻止最终化。
|
||||
- 2026-07-16 V1.26 已完成真实验收:正式 `openai_chat / gpt-5.5` 的 `project-skill` suite 中 9/9 个成功工具计划与 6/6 个格式修复全部使用 `native_runtime_tools`,wrapper/text fallback 均为 0。Agent 自主读取匹配 Skill、只改唯一目标文件并完成 Agent/宿主双重验证;15 个 tool-plan 和 1 个 final-reply lifecycle 唯一闭合,最终 assistant/completed 各 1,重复、Skill 正文、API Key、诱饵、项目/配置路径和报告泄漏均为 0,隔离 Runner/AppData/项目完整清理。
|
||||
- 开发模式可通过本地项目文件面板执行 `file.list/read/write/delete`,普通用户界面不暴露文件面板。
|
||||
|
||||
Reference in New Issue
Block a user