Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c0db298449 |
@@ -2,8 +2,6 @@
|
||||
"schemaVersion": "game-creator-config.v2",
|
||||
"agentMode": "codex_app_server",
|
||||
"llm": {
|
||||
"customEnabled": false,
|
||||
"visibleModels": [],
|
||||
"apiKey": "",
|
||||
"baseUrl": "https://dev.genarrative.world/gpt/v1",
|
||||
"model": "gpt-6-astra",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@genarrative/ai-game-creator-shell",
|
||||
"private": true,
|
||||
"version": "0.1.45",
|
||||
"version": "0.1.29",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "node scripts/start-tauri-dev.mjs",
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { test } from 'node:test';
|
||||
import { setTimeout as delay } from 'node:timers/promises';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { createServer, loadConfigFromFile, normalizePath } from 'vite';
|
||||
|
||||
test(
|
||||
'AGC 排除 Rust 构建目录且保留源码与共享组件监听',
|
||||
{ timeout: 30_000 },
|
||||
async () => {
|
||||
const loaded = await loadConfigFromFile(
|
||||
{ command: 'serve', mode: 'development' },
|
||||
fileURLToPath(new URL('../vite.config.ts', import.meta.url)),
|
||||
);
|
||||
assert.ok(loaded);
|
||||
assert.notEqual(loaded.config.server?.watch, null);
|
||||
assert.notEqual(loaded.config.server?.hmr, false);
|
||||
assert.ok(
|
||||
[loaded.config.server?.watch?.ignored]
|
||||
.flat()
|
||||
.includes('**/src-tauri/target/**'),
|
||||
);
|
||||
|
||||
const fixture = await mkdtemp(join(tmpdir(), 'agc-vite-watch-'));
|
||||
const root = join(fixture, 'apps', 'ai-game-creator-shell');
|
||||
const source = join(root, 'src', 'main.js');
|
||||
const css = join(root, 'src', 'styles.css');
|
||||
const shared = join(fixture, 'packages', 'shared', 'src', 'component.js');
|
||||
const target = join(root, 'src-tauri', 'target');
|
||||
const artifact = join(target, 'debug', 'incremental', 'cache.bin');
|
||||
let server;
|
||||
try {
|
||||
for (const file of [source, css, shared, artifact]) {
|
||||
await mkdir(dirname(file), { recursive: true });
|
||||
await writeFile(
|
||||
file,
|
||||
file === css ? 'body { color: red; }' : 'export default 1;',
|
||||
);
|
||||
}
|
||||
// 使用真实 Vite watcher 和实际配置,仅将扫描根替换为小型夹具;
|
||||
// 不加载业务插件、后端或原生窗口,也不扫描开发机上的大型 target。
|
||||
server = await createServer({
|
||||
configFile: false,
|
||||
envFile: false,
|
||||
root,
|
||||
logLevel: 'silent',
|
||||
server: {
|
||||
watch: loaded.config.server?.watch,
|
||||
middlewareMode: true,
|
||||
hmr: false,
|
||||
fs: { allow: [fixture] },
|
||||
},
|
||||
optimizeDeps: { noDiscovery: true, include: [] },
|
||||
});
|
||||
const waitForWatchedFile = async (file) => {
|
||||
const normalized = normalizePath(file);
|
||||
for (let attempt = 0; attempt < 100; attempt += 1) {
|
||||
if (
|
||||
Object.entries(server.watcher.getWatched()).some(
|
||||
([directory, names]) =>
|
||||
names.some(
|
||||
(name) => normalizePath(join(directory, name)) === normalized,
|
||||
),
|
||||
)
|
||||
)
|
||||
return;
|
||||
await delay(50);
|
||||
}
|
||||
assert.fail(`源码必须仍被监听:${normalized}`);
|
||||
};
|
||||
await waitForWatchedFile(source);
|
||||
|
||||
// 真实模块转换应将 root 外的共享源码加入监听。
|
||||
await server.transformRequest(`/@fs/${normalizePath(shared)}`);
|
||||
for (const file of [source, css, shared]) {
|
||||
const normalized = normalizePath(file);
|
||||
await waitForWatchedFile(file);
|
||||
const changed = new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
server.watcher.off('change', onChange);
|
||||
reject(new Error(`未收到源码变更:${normalized}`));
|
||||
}, 5_000);
|
||||
function onChange(path) {
|
||||
if (normalizePath(path) !== normalized) return;
|
||||
clearTimeout(timer);
|
||||
server.watcher.off('change', onChange);
|
||||
resolve();
|
||||
}
|
||||
server.watcher.on('change', onChange);
|
||||
});
|
||||
await writeFile(
|
||||
file,
|
||||
file === css ? 'body { color: blue; }' : 'export default 2;',
|
||||
);
|
||||
await changed;
|
||||
}
|
||||
const targetPath = normalizePath(target);
|
||||
const targetDirectories = Object.keys(server.watcher.getWatched())
|
||||
.map(normalizePath)
|
||||
.filter(
|
||||
(path) => path === targetPath || path.startsWith(`${targetPath}/`),
|
||||
);
|
||||
assert.deepEqual(targetDirectories, [], 'Rust target 不应创建目录监听器');
|
||||
} finally {
|
||||
await server?.close();
|
||||
await rm(fixture, { recursive: true, force: true });
|
||||
}
|
||||
},
|
||||
);
|
||||
+1
-1
@@ -1725,7 +1725,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "genarrative-ai-game-creator-shell"
|
||||
version = "0.1.45"
|
||||
version = "0.1.29"
|
||||
dependencies = [
|
||||
"agent-runtime-core",
|
||||
"axum",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "genarrative-ai-game-creator-shell"
|
||||
version = "0.1.45"
|
||||
version = "0.1.29"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
|
||||
@@ -131,6 +131,7 @@ impl CodexAppServerCredential {
|
||||
) -> Option<(&'a str, &'a str)> {
|
||||
match self {
|
||||
Self::PlatformSession { .. } => None,
|
||||
#[cfg(test)]
|
||||
Self::AppDataKey { .. } => (!llm.api_key.trim().is_empty())
|
||||
.then_some((llm.base_url.trim_end_matches('/'), llm.api_key.trim())),
|
||||
#[cfg(test)]
|
||||
@@ -138,7 +139,7 @@ impl CodexAppServerCredential {
|
||||
.as_deref()
|
||||
.map(|api_key| (GAME_CREATOR_CODEX_AUTH_BRIDGE_API_BASE_URL, api_key)),
|
||||
#[cfg(not(test))]
|
||||
Self::AuthBridge { .. } => None,
|
||||
Self::AppDataKey { .. } | Self::AuthBridge { .. } => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2017,13 +2018,7 @@ impl CodexAppServerConnection {
|
||||
let codex_cli_version = game_creator_codex_cli_version_identity()
|
||||
.map_err(platform_llm::LlmError::InvalidConfig)?;
|
||||
let mut effective_llm = llm.clone();
|
||||
let credential = if llm.custom_enabled {
|
||||
crate::config::validate_custom_llm_connection(llm)
|
||||
.map_err(platform_llm::LlmError::InvalidConfig)?;
|
||||
CodexAppServerCredential::AppDataKey {
|
||||
fingerprint: format!("custom-key:{:x}", Sha256::digest(llm.api_key.as_bytes())),
|
||||
}
|
||||
} else if game_creator_official_llm_route_locked() {
|
||||
let credential = if game_creator_official_llm_route_locked() {
|
||||
let session = current_platform_session().ok_or_else(|| {
|
||||
platform_llm::LlmError::InvalidConfig(
|
||||
"authentication-required: 请先登录陶泥儿账号".to_string(),
|
||||
@@ -2191,8 +2186,7 @@ impl CodexAppServerConnection {
|
||||
true,
|
||||
),
|
||||
_ => (
|
||||
(llm.custom_enabled
|
||||
|| workspace_mode == CodexAppServerWorkspaceMode::DirectProject)
|
||||
(workspace_mode == CodexAppServerWorkspaceMode::DirectProject)
|
||||
.then(|| credential.direct_provider_route(llm))
|
||||
.flatten()
|
||||
.map(|(base_url, api_key)| (base_url.to_string(), api_key.to_string())),
|
||||
@@ -4749,8 +4743,6 @@ mod tests {
|
||||
|
||||
fn test_llm() -> GameCreatorLlmConfig {
|
||||
GameCreatorLlmConfig {
|
||||
custom_enabled: false,
|
||||
visible_models: Vec::new(),
|
||||
api_key: "fixture-secret".to_string(),
|
||||
base_url: "https://example.invalid/v1".to_string(),
|
||||
model: "fixture-model".to_string(),
|
||||
@@ -5536,59 +5528,6 @@ mod tests {
|
||||
assert_ne!(command_token, provider_key);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn custom_llm_broker_uses_configured_route_without_exposing_upstream_key() {
|
||||
let mut llm = test_llm();
|
||||
llm.custom_enabled = true;
|
||||
llm.api_key = "custom-upstream-fixture-secret".into();
|
||||
llm.base_url = "http://127.0.0.1:9/v1".into();
|
||||
llm.model = "vendor/model.v1:latest".into();
|
||||
llm.visible_models = vec![llm.model.clone()];
|
||||
let credential = CodexAppServerCredential::AppDataKey {
|
||||
fingerprint: "custom-fixture".into(),
|
||||
};
|
||||
let (base, key) = credential
|
||||
.direct_provider_route(&llm)
|
||||
.expect("custom route");
|
||||
assert_eq!(base, llm.base_url);
|
||||
assert_eq!(key, llm.api_key);
|
||||
let proxy = start_codex_provider_proxy(base, key, false).await.unwrap();
|
||||
for mode in [
|
||||
CodexAppServerWorkspaceMode::DirectProject,
|
||||
CodexAppServerWorkspaceMode::ToolHost,
|
||||
] {
|
||||
let mut command = tokio::process::Command::new("fixture");
|
||||
configure_game_creator_codex_app_server_command_for_mode(
|
||||
&mut command,
|
||||
&llm,
|
||||
mode,
|
||||
Some(&proxy),
|
||||
None,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
let arguments = command
|
||||
.as_std()
|
||||
.get_args()
|
||||
.map(|arg| arg.to_string_lossy())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
let params = codex_app_server_thread_start_params(
|
||||
&llm.model,
|
||||
std::path::Path::new("fixture-workspace"),
|
||||
mode,
|
||||
String::new(),
|
||||
true,
|
||||
);
|
||||
assert_eq!(params["model"], "vendor/model.v1:latest");
|
||||
assert!(!arguments.contains(&llm.api_key));
|
||||
assert!(!arguments.contains("/api/llm"));
|
||||
for (_, value) in command.as_std().get_envs() {
|
||||
assert!(!value.is_some_and(|value| value.to_string_lossy().contains(&llm.api_key)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[tokio::test]
|
||||
async fn direct_project_spawn_restores_broker_token_after_environment_isolation() {
|
||||
|
||||
@@ -3053,7 +3053,14 @@ async fn recover_direct_taonier_spritesheet_read_only_at(
|
||||
)?;
|
||||
let _platform_session_lease = access
|
||||
.frozen_platform_session()
|
||||
.map(|session| acquire_platform_session_identity_lease(&session.identity()))
|
||||
.map(|session| {
|
||||
acquire_validated_platform_session_fingerprint(
|
||||
&session.user_id,
|
||||
&session.api_base_url,
|
||||
session.generation,
|
||||
&format!("{:x}", Sha256::digest(session.access_token.as_bytes())),
|
||||
)
|
||||
})
|
||||
.transpose()?;
|
||||
// The network phase deliberately runs without the project write lock. Capture rollback state
|
||||
// only after acquiring the lock and revalidating the source identity, otherwise a failure can
|
||||
@@ -5322,8 +5329,6 @@ mod tests {
|
||||
|
||||
fn direct_test_llm() -> GameCreatorLlmConfig {
|
||||
GameCreatorLlmConfig {
|
||||
custom_enabled: false,
|
||||
visible_models: Vec::new(),
|
||||
api_key: "fixture-secret".to_string(),
|
||||
base_url: "https://example.invalid/v1".to_string(),
|
||||
model: "fixture-model".to_string(),
|
||||
|
||||
@@ -49,7 +49,7 @@ struct ExternalMcpHttpState {
|
||||
root: PathBuf,
|
||||
token: String,
|
||||
session_user_id: String,
|
||||
session_identity_generation: u64,
|
||||
session_generation: u64,
|
||||
}
|
||||
|
||||
pub(crate) fn direct_tools_mcp_mode_requested(args: &[String]) -> bool {
|
||||
@@ -1241,8 +1241,7 @@ fn external_mcp_session_id(root: &Path) -> String {
|
||||
material.push('\0');
|
||||
material.push_str(&session.user_id);
|
||||
material.push('\0');
|
||||
// 用身份代次而不是 token:同一账号续期不得让 MCP 会话身份漂移。
|
||||
material.push_str(&session.identity_generation.to_string());
|
||||
material.push_str(&session.generation.to_string());
|
||||
}
|
||||
format!("mcp-{:x}", Sha256::digest(material.as_bytes()))
|
||||
}
|
||||
@@ -1760,9 +1759,7 @@ async fn handle_external_mcp_http_request(
|
||||
let Some(session) = current_platform_session() else {
|
||||
return Err(StatusCode::UNAUTHORIZED);
|
||||
};
|
||||
if session.user_id != state.session_user_id
|
||||
|| session.identity_generation != state.session_identity_generation
|
||||
{
|
||||
if session.user_id != state.session_user_id || session.generation != state.session_generation {
|
||||
return Err(StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
let response = EXTERNAL_MCP_BRIDGE_URL
|
||||
@@ -1797,7 +1794,7 @@ pub(crate) async fn start_external_mcp_loopback(
|
||||
root,
|
||||
token: token.clone(),
|
||||
session_user_id: session.user_id,
|
||||
session_identity_generation: session.identity_generation,
|
||||
session_generation: session.generation,
|
||||
};
|
||||
let app = Router::new()
|
||||
.route(&route, post(handle_external_mcp_http_request))
|
||||
|
||||
@@ -11,10 +11,6 @@ use super::external_generation_state::{
|
||||
retain_platform_art_generation_runtime_accepted_result, PlatformArtGenerationRuntimeState,
|
||||
};
|
||||
use super::*;
|
||||
use crate::platform_session::{
|
||||
acquire_platform_session_identity_lease, validate_platform_session_identity,
|
||||
PlatformSessionIdentity,
|
||||
};
|
||||
use reqwest::multipart::{Form, Part};
|
||||
|
||||
const EXTERNAL_GENERATION_POLL_TIMEOUT: Duration = Duration::from_secs(35 * 60);
|
||||
@@ -1514,7 +1510,10 @@ struct PreparedPlatformArtAssetSlice {
|
||||
|
||||
#[derive(Clone)]
|
||||
struct PreparedPlatformSessionFence {
|
||||
identity: PlatformSessionIdentity,
|
||||
user_id: String,
|
||||
api_base_url: String,
|
||||
generation: u64,
|
||||
access_token_sha256: String,
|
||||
}
|
||||
|
||||
impl PreparedPlatformSessionFence {
|
||||
@@ -1522,17 +1521,41 @@ impl PreparedPlatformSessionFence {
|
||||
access
|
||||
.frozen_platform_session()
|
||||
.map(|session| PreparedPlatformSessionFence {
|
||||
identity: session.identity(),
|
||||
user_id: session.user_id.clone(),
|
||||
api_base_url: session.api_base_url.clone(),
|
||||
generation: session.generation,
|
||||
access_token_sha256: format!(
|
||||
"{:x}",
|
||||
Sha256::digest(session.access_token.as_bytes())
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
fn validate(&self) -> Result<(), String> {
|
||||
// 只比较身份:同一账号的 access token 轮换不得让在途生成 operation 失败。
|
||||
validate_platform_session_identity(&self.identity)
|
||||
let matches = current_platform_session().is_some_and(|session| {
|
||||
session.user_id == self.user_id
|
||||
&& session.api_base_url == self.api_base_url
|
||||
&& session.generation == self.generation
|
||||
&& format!("{:x}", Sha256::digest(session.access_token.as_bytes()))
|
||||
== self.access_token_sha256
|
||||
});
|
||||
if matches {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(
|
||||
"authentication-required: 陶泥儿登录态已变化,旧账号请求已停止,请使用当前账号重试"
|
||||
.to_string(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn acquire_lease(&self) -> Result<ValidatedPlatformSessionLease, String> {
|
||||
acquire_platform_session_identity_lease(&self.identity)
|
||||
acquire_validated_platform_session_fingerprint(
|
||||
&self.user_id,
|
||||
&self.api_base_url,
|
||||
self.generation,
|
||||
&self.access_token_sha256,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10613,7 +10636,7 @@ mod canvas_generation_tests {
|
||||
}
|
||||
drop(owner_a_access);
|
||||
drop(frozen_owner_a);
|
||||
install_platform_session("durable-owner-b", "durable-owner-b-token", &base_url, 2, 2)
|
||||
install_platform_session("durable-owner-b", "durable-owner-b-token", &base_url, 2)
|
||||
.expect("switch to owner B");
|
||||
|
||||
let error = match request_platform_art_asset_with_runtime_options_at(
|
||||
@@ -10738,14 +10761,8 @@ mod canvas_generation_tests {
|
||||
.recv_timeout(Duration::from_secs(3))
|
||||
.expect("wait for accepted response");
|
||||
std::thread::sleep(Duration::from_millis(50));
|
||||
install_platform_session(
|
||||
"post-202-user-b",
|
||||
"post-202-token-b",
|
||||
&switch_base_url,
|
||||
2,
|
||||
2,
|
||||
)
|
||||
.expect("switch platform account after accepted response");
|
||||
install_platform_session("post-202-user-b", "post-202-token-b", &switch_base_url, 2)
|
||||
.expect("switch platform account after accepted response");
|
||||
});
|
||||
let runtime_context = PlatformArtGenerationRuntimeContext {
|
||||
agent_id: "art-director".to_string(),
|
||||
|
||||
+1
-2
@@ -1431,13 +1431,12 @@ mod external_generation_state_tests {
|
||||
base_url,
|
||||
);
|
||||
let frozen_a = current_platform_session().expect("freeze owner A");
|
||||
validate_frozen_platform_session(&frozen_a).expect("owner A is current before switch");
|
||||
validate_platform_session_snapshot(&frozen_a).expect("owner A is current before switch");
|
||||
replace_platform_session_for_gui_owner(
|
||||
"fingerprint-owner-b",
|
||||
"fingerprint-token-b",
|
||||
base_url,
|
||||
2,
|
||||
2,
|
||||
)
|
||||
.expect("switch global session to owner B");
|
||||
let current_b = current_platform_session().expect("owner B is current after switch");
|
||||
|
||||
-18
@@ -605,8 +605,6 @@ fn finalization_cleanup_closes_entire_tool_plan_repair_chain_before_removal() {
|
||||
response_stream_fixture("finalization-tool-plan-repair-chain-run");
|
||||
let root = project.path();
|
||||
let llm = GameCreatorLlmConfig {
|
||||
custom_enabled: false,
|
||||
visible_models: Vec::new(),
|
||||
api_key: "finalization-tool-plan-key".to_string(),
|
||||
base_url: "http://127.0.0.1:1/v1".to_string(),
|
||||
model: "finalization-tool-plan-model".to_string(),
|
||||
@@ -970,8 +968,6 @@ async fn provider_handoff_identity_drift_closes_lifecycle_without_leaking_respon
|
||||
let root = project.path();
|
||||
let request = LlmRunRequest::new(vec![LlmMessage::user("验证 handoff 身份漂移")]);
|
||||
let old_llm = GameCreatorLlmConfig {
|
||||
custom_enabled: false,
|
||||
visible_models: Vec::new(),
|
||||
api_key: "old-provider-key".to_string(),
|
||||
base_url: "http://127.0.0.1:1/v1".to_string(),
|
||||
model: "old-provider-model".to_string(),
|
||||
@@ -1077,8 +1073,6 @@ async fn tool_plan_handoff_identity_drift_closes_entire_repair_chain_before_remo
|
||||
LlmMessage::user("修复格式"),
|
||||
]);
|
||||
let old_llm = GameCreatorLlmConfig {
|
||||
custom_enabled: false,
|
||||
visible_models: Vec::new(),
|
||||
api_key: "old-tool-plan-provider-key".to_string(),
|
||||
base_url: "http://127.0.0.1:1/v1".to_string(),
|
||||
model: "old-tool-plan-model".to_string(),
|
||||
@@ -1198,8 +1192,6 @@ async fn generic_retry_identity_drift_closes_tool_plan_repair_chain_before_remov
|
||||
response_stream_fixture("generic-retry-drift-tool-plan-chain-run");
|
||||
let root = project.path();
|
||||
let old_llm = GameCreatorLlmConfig {
|
||||
custom_enabled: false,
|
||||
visible_models: Vec::new(),
|
||||
api_key: "old-generic-retry-key".to_string(),
|
||||
base_url: "http://127.0.0.1:1/v1".to_string(),
|
||||
model: "old-generic-retry-model".to_string(),
|
||||
@@ -1285,8 +1277,6 @@ async fn tool_plan_capacity_gate_runs_before_provider_lifecycle_and_network() {
|
||||
response_stream_fixture("tool-plan-capacity-preflight-run");
|
||||
let root = project.path();
|
||||
let llm = GameCreatorLlmConfig {
|
||||
custom_enabled: false,
|
||||
visible_models: Vec::new(),
|
||||
api_key: "tool-plan-capacity-key".to_string(),
|
||||
base_url: "http://127.0.0.1:1/v1".to_string(),
|
||||
model: "tool-plan-capacity-model".to_string(),
|
||||
@@ -1410,8 +1400,6 @@ async fn tool_plan_handoff_durable_control_closes_entire_repair_chain_before_rem
|
||||
LlmMessage::user("修复格式"),
|
||||
]);
|
||||
let llm = GameCreatorLlmConfig {
|
||||
custom_enabled: false,
|
||||
visible_models: Vec::new(),
|
||||
api_key: "durable-control-tool-plan-key".to_string(),
|
||||
base_url: "http://127.0.0.1:1/v1".to_string(),
|
||||
model: "durable-control-tool-plan-model".to_string(),
|
||||
@@ -1537,8 +1525,6 @@ fn provider_recovery_cleanup_closes_tool_plan_lifecycle_before_removing_handoff(
|
||||
snapshot.request_slot = "loop-0-repair-0".to_string();
|
||||
let request = LlmRunRequest::new(vec![LlmMessage::user("等待 steer 或 cancel")]);
|
||||
let llm = GameCreatorLlmConfig {
|
||||
custom_enabled: false,
|
||||
visible_models: Vec::new(),
|
||||
api_key: "tool-plan-cleanup-key".to_string(),
|
||||
base_url: "http://127.0.0.1:1/v1".to_string(),
|
||||
model: "tool-plan-cleanup-model".to_string(),
|
||||
@@ -1611,8 +1597,6 @@ fn runtime_resume_scans_and_cleans_terminal_tool_plan_handoff() {
|
||||
snapshot.request_slot = "loop-0-repair-0".to_string();
|
||||
let request = LlmRunRequest::new(vec![LlmMessage::user("终态遗留 handoff")]);
|
||||
let llm = GameCreatorLlmConfig {
|
||||
custom_enabled: false,
|
||||
visible_models: Vec::new(),
|
||||
api_key: "terminal-handoff-key".to_string(),
|
||||
base_url: "http://127.0.0.1:1/v1".to_string(),
|
||||
model: "terminal-handoff-model".to_string(),
|
||||
@@ -1709,8 +1693,6 @@ async fn provider_handoff_retry_conflict_preserves_both_sidecars_for_reconciliat
|
||||
let root = project.path();
|
||||
let request = LlmRunRequest::new(vec![LlmMessage::user("验证 handoff/retry 冲突")]);
|
||||
let llm = GameCreatorLlmConfig {
|
||||
custom_enabled: false,
|
||||
visible_models: Vec::new(),
|
||||
api_key: "provider-key".to_string(),
|
||||
base_url: "http://127.0.0.1:1/v1".to_string(),
|
||||
model: "provider-model".to_string(),
|
||||
|
||||
@@ -1939,9 +1939,8 @@ pub(crate) async fn polish_local_project_prompt(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) fn read_platform_account_session_state(
|
||||
) -> crate::platform_session::PlatformSessionWriteState {
|
||||
crate::platform_session::current_platform_session_write_state()
|
||||
pub(crate) fn read_platform_account_session_generation() -> u64 {
|
||||
current_platform_session_generation()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -1949,45 +1948,28 @@ pub(crate) async fn install_platform_account_session(
|
||||
user_id: String,
|
||||
access_token: String,
|
||||
api_base_url: String,
|
||||
identity_generation: u64,
|
||||
revision: u64,
|
||||
generation: u64,
|
||||
) -> Result<(), String> {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
validate_platform_session_input(
|
||||
&user_id,
|
||||
&access_token,
|
||||
&api_base_url,
|
||||
identity_generation,
|
||||
revision,
|
||||
)?;
|
||||
validate_platform_session_input(&user_id, &access_token, &api_base_url, generation)?;
|
||||
install_external_agent_runner_platform_session(
|
||||
&user_id,
|
||||
&access_token,
|
||||
&api_base_url,
|
||||
identity_generation,
|
||||
revision,
|
||||
generation,
|
||||
)?;
|
||||
install_platform_session(
|
||||
&user_id,
|
||||
&access_token,
|
||||
&api_base_url,
|
||||
identity_generation,
|
||||
revision,
|
||||
)
|
||||
install_platform_session(&user_id, &access_token, &api_base_url, generation)
|
||||
})
|
||||
.await
|
||||
.map_err(|error| format!("安装本地运行时会话任务意外终止:{error}"))?
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) async fn clear_platform_account_session(
|
||||
identity_generation: u64,
|
||||
revision: u64,
|
||||
) -> Result<(), String> {
|
||||
pub(crate) async fn clear_platform_account_session(generation: u64) -> Result<(), String> {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
shutdown_game_creator_codex_app_servers()?;
|
||||
clear_external_agent_runner_platform_session(identity_generation, revision)?;
|
||||
clear_platform_session(identity_generation, revision);
|
||||
clear_external_agent_runner_platform_session(generation)?;
|
||||
clear_platform_session(generation);
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
@@ -2009,8 +1991,6 @@ pub(crate) fn write_game_creator_app_config(
|
||||
.lock()
|
||||
.map_err(|_| "配置写入锁不可用")?;
|
||||
let (current, overlays) = load_game_creator_app_config_for_write()?;
|
||||
// 自定义开关只能从本地配置文件开启,不能由渲染层越过配置门禁。
|
||||
config.llm.custom_enabled = current.llm.custom_enabled;
|
||||
config.selected_model_id = current.selected_model_id;
|
||||
config.selected_model_is_default = current.selected_model_is_default;
|
||||
persist_game_creator_app_config(config, overlays, false)
|
||||
@@ -2047,12 +2027,7 @@ pub(crate) fn select_game_creator_model(
|
||||
let _guard = GAME_CREATOR_CONFIG_WRITE_LOCK
|
||||
.lock()
|
||||
.map_err(|_| "配置写入锁不可用")?;
|
||||
let (mut config, overlays) = load_game_creator_app_config_for_write()?;
|
||||
if config.llm.custom_enabled {
|
||||
if !config.llm.visible_models.contains(&model_id) {
|
||||
return Err("所选模型未勾选或已移除,请刷新模型列表".into());
|
||||
}
|
||||
} else if model_id.is_empty()
|
||||
if model_id.is_empty()
|
||||
|| model_id.len() > 64
|
||||
|| !model_id
|
||||
.bytes()
|
||||
@@ -2060,21 +2035,12 @@ pub(crate) fn select_game_creator_model(
|
||||
{
|
||||
return Err("模型标识无效".into());
|
||||
}
|
||||
let (mut config, overlays) = load_game_creator_app_config_for_write()?;
|
||||
config.selected_model_id = model_id;
|
||||
config.selected_model_is_default = is_default;
|
||||
persist_game_creator_app_config(config, overlays, true)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) async fn discover_game_creator_llm_models(
|
||||
llm: GameCreatorLlmConfig,
|
||||
) -> Result<Vec<String>, String> {
|
||||
if !load_game_creator_app_config()?.llm.custom_enabled {
|
||||
return Err("请先在本地配置中开启 llm.customEnabled".to_string());
|
||||
}
|
||||
fetch_custom_llm_models(&llm).await
|
||||
}
|
||||
|
||||
fn persist_game_creator_app_config(
|
||||
config: GameCreatorAppConfig,
|
||||
overlays: Vec<(PathBuf, serde_json::Value)>,
|
||||
@@ -2090,8 +2056,8 @@ fn persist_game_creator_app_config(
|
||||
let previous = overlay.clone();
|
||||
if let Some(fields) = overlay.as_object_mut() {
|
||||
for (key, value) in fields.iter_mut() {
|
||||
if !model_only
|
||||
|| matches!(key.as_str(), "selectedModelId" | "selectedModelIsDefault")
|
||||
if matches!(key.as_str(), "selectedModelId" | "selectedModelIsDefault")
|
||||
== model_only
|
||||
{
|
||||
if let Some(saved_value) = saved.get(key) {
|
||||
// 仅同步已有覆盖项;其它字段继续保留原有覆盖语义。
|
||||
@@ -4265,7 +4231,14 @@ pub(crate) async fn import_account_editor_assets_for_agent(
|
||||
access.validate_frozen_session()?;
|
||||
let _platform_session_lease = frozen_session
|
||||
.as_ref()
|
||||
.map(|session| acquire_platform_session_identity_lease(&session.identity()))
|
||||
.map(|session| {
|
||||
acquire_validated_platform_session_fingerprint(
|
||||
&session.user_id,
|
||||
&session.api_base_url,
|
||||
session.generation,
|
||||
&format!("{:x}", Sha256::digest(session.access_token.as_bytes())),
|
||||
)
|
||||
})
|
||||
.transpose()?;
|
||||
let _lock = acquire_project_write_lock(root, "canvas.asset_import")?;
|
||||
access.validate_frozen_session()?;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1084,10 +1084,6 @@ struct GameCreatorAppConfigFile {
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct GameCreatorLlmConfigFile {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
custom_enabled: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
visible_models: Option<Vec<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
api_key: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -1143,10 +1139,6 @@ struct GameCreatorAppConfig {
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct GameCreatorLlmConfig {
|
||||
#[serde(default)]
|
||||
custom_enabled: bool,
|
||||
#[serde(default)]
|
||||
visible_models: Vec<String>,
|
||||
api_key: String,
|
||||
base_url: String,
|
||||
model: String,
|
||||
@@ -1663,8 +1655,6 @@ impl Default for GameCreatorAppConfig {
|
||||
impl Default for GameCreatorLlmConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
custom_enabled: false,
|
||||
visible_models: Vec::new(),
|
||||
api_key: String::new(),
|
||||
base_url: DEFAULT_GAME_CREATOR_LLM_BASE_URL.to_string(),
|
||||
model: DEFAULT_GAME_CREATOR_LLM_MODEL.to_string(),
|
||||
@@ -2725,13 +2715,12 @@ fn main() {
|
||||
confirm_resume_game_creator_agent_runtime_tasks,
|
||||
schedule_game_creator_agent_ready_tasks,
|
||||
check_game_creator_llm_config,
|
||||
read_platform_account_session_state,
|
||||
read_platform_account_session_generation,
|
||||
install_platform_account_session,
|
||||
clear_platform_account_session,
|
||||
read_game_creator_app_config,
|
||||
write_game_creator_app_config,
|
||||
select_game_creator_model,
|
||||
discover_game_creator_llm_models,
|
||||
upload_local_asset,
|
||||
register_local_asset,
|
||||
create_ui_design_resource,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -110,13 +110,11 @@ impl<'a> ExternalEditorBindingAccess<'a> {
|
||||
}
|
||||
|
||||
/// Call before and after every awaited remote action and immediately before installing a
|
||||
/// binding. 只比较身份:同一账号的 access token 轮换(长回合保活、401 续期)不得让
|
||||
/// 在途的生成、编辑、上传、确认或下载 operation 失效;换号、退出或 origin 变化仍然
|
||||
/// 失败关闭。Developer-key 模式没有进程级身份代次可比对。
|
||||
/// binding. Developer-key mode has no process-global account generation to compare.
|
||||
pub(crate) fn validate_frozen_session(&self) -> Result<(), String> {
|
||||
validate_external_editor_binding_access_shape(self)?;
|
||||
if let Some(session) = self.frozen_platform_session {
|
||||
validate_frozen_platform_session(session)?;
|
||||
validate_platform_session_snapshot(session)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1154,8 +1152,7 @@ mod tests {
|
||||
user_id: user_id.to_string(),
|
||||
access_token: token.to_string(),
|
||||
api_base_url: "https://dev.genarrative.world".to_string(),
|
||||
identity_generation: generation,
|
||||
revision: generation,
|
||||
generation,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4389,7 +4389,14 @@ fn with_frozen_resource_edit_platform_session<T>(
|
||||
let Some(platform_session) = platform_session else {
|
||||
return action();
|
||||
};
|
||||
with_validated_platform_session_identity(&platform_session.identity(), action)
|
||||
let access_token_sha256 = sha256_hex(platform_session.access_token.as_bytes());
|
||||
with_validated_platform_session_fingerprint(
|
||||
&platform_session.user_id,
|
||||
&platform_session.api_base_url,
|
||||
platform_session.generation,
|
||||
&access_token_sha256,
|
||||
action,
|
||||
)
|
||||
}
|
||||
|
||||
fn commit_resource_edit_asset_with_frozen_platform_session(
|
||||
@@ -4767,7 +4774,14 @@ pub(crate) fn list_pending_local_project_resource_edits_at(
|
||||
let current_platform_session = current_platform_session();
|
||||
let _platform_session_lease = current_platform_session
|
||||
.as_ref()
|
||||
.map(|session| acquire_platform_session_identity_lease(&session.identity()))
|
||||
.map(|session| {
|
||||
acquire_validated_platform_session_fingerprint(
|
||||
&session.user_id,
|
||||
&session.api_base_url,
|
||||
session.generation,
|
||||
&sha256_hex(session.access_token.as_bytes()),
|
||||
)
|
||||
})
|
||||
.transpose()?;
|
||||
let directory = resolve_local_project_path(root, &format!("{RESOURCE_EDIT_ROOT}/operations"))?;
|
||||
let entries = match fs::read_dir(&directory) {
|
||||
@@ -5070,8 +5084,12 @@ pub(crate) async fn archive_failed_local_project_resource_edit_at(
|
||||
Ok(())
|
||||
};
|
||||
if let Some(session) = platform_session {
|
||||
crate::platform_session::with_validated_platform_session_identity(
|
||||
&session.identity(),
|
||||
let access_token_sha256 = sha256_hex(session.access_token.as_bytes());
|
||||
crate::platform_session::with_validated_platform_session_fingerprint(
|
||||
&session.user_id,
|
||||
&session.api_base_url,
|
||||
session.generation,
|
||||
&access_token_sha256,
|
||||
archive,
|
||||
)?;
|
||||
} else {
|
||||
@@ -5482,8 +5500,7 @@ mod tests {
|
||||
user_id: "gui-owner".to_string(),
|
||||
access_token: "gui-token".to_string(),
|
||||
api_base_url: "https://dev.genarrative.world".to_string(),
|
||||
identity_generation: 7,
|
||||
revision: 7,
|
||||
generation: 7,
|
||||
};
|
||||
let developer_credentials = (
|
||||
"https://dev.genarrative.world".to_string(),
|
||||
@@ -5894,7 +5911,6 @@ mod tests {
|
||||
"source-binding-token-b",
|
||||
api_base_url,
|
||||
*generation,
|
||||
*generation,
|
||||
)
|
||||
.expect("switch account after source registration");
|
||||
}
|
||||
@@ -6909,7 +6925,7 @@ mod tests {
|
||||
listener,
|
||||
upload_url,
|
||||
false,
|
||||
Some((base_url.clone(), frozen_session.identity_generation + 1)),
|
||||
Some((base_url.clone(), frozen_session.generation + 1)),
|
||||
done_receiver,
|
||||
);
|
||||
let client = reqwest::Client::new();
|
||||
@@ -6936,8 +6952,7 @@ mod tests {
|
||||
"source-binding-owner-a",
|
||||
"source-binding-token-a",
|
||||
&base_url,
|
||||
frozen_session.identity_generation + 2,
|
||||
frozen_session.identity_generation + 2,
|
||||
frozen_session.generation + 2,
|
||||
)
|
||||
.expect("switch back to source binding owner A");
|
||||
let resumed_session = current_platform_session().expect("resumed source binding owner A");
|
||||
@@ -7003,7 +7018,7 @@ mod tests {
|
||||
install_test_platform_session("submission-owner-a", "submission-token-a", &base_url);
|
||||
let frozen_session = current_platform_session().expect("frozen owner A session");
|
||||
let switch_base_url = base_url.clone();
|
||||
let switch_generation = frozen_session.identity_generation + 1;
|
||||
let switch_generation = frozen_session.generation + 1;
|
||||
let server = std::thread::spawn(move || {
|
||||
let mut stream =
|
||||
accept_resource_editor_fixture_connection(&listener, "accepted switch fixture", 0);
|
||||
@@ -7017,7 +7032,6 @@ mod tests {
|
||||
"submission-token-b",
|
||||
&switch_base_url,
|
||||
switch_generation,
|
||||
switch_generation,
|
||||
)
|
||||
.expect("switch to owner B before returning accepted response");
|
||||
write_json(
|
||||
@@ -7433,14 +7447,8 @@ mod tests {
|
||||
ledger.access_scheme = None;
|
||||
initialize_resource_edit_access_identity(root, &mut ledger, base_url, Some(&frozen_a))
|
||||
.expect("write resource ledger for owner A");
|
||||
replace_platform_session_for_gui_owner(
|
||||
"resource-owner-b",
|
||||
"resource-token-b",
|
||||
base_url,
|
||||
2,
|
||||
2,
|
||||
)
|
||||
.expect("switch global resource session to owner B");
|
||||
replace_platform_session_for_gui_owner("resource-owner-b", "resource-token-b", base_url, 2)
|
||||
.expect("switch global resource session to owner B");
|
||||
|
||||
let error = prepare_resource_edit_service_identity(
|
||||
root,
|
||||
@@ -7501,8 +7509,7 @@ mod tests {
|
||||
user_id: "resource-identity-owner-b".to_string(),
|
||||
access_token: "resource-identity-token-b".to_string(),
|
||||
api_base_url: owner_a.api_base_url.clone(),
|
||||
identity_generation: owner_a.identity_generation + 1,
|
||||
revision: owner_a.revision + 1,
|
||||
generation: owner_a.generation + 1,
|
||||
};
|
||||
let mut ledger = ledger_for(&request, &source, ResourceEditLedgerPhase::Prepared);
|
||||
ledger.access_scheme = Some(RESOURCE_EDIT_PLATFORM_ACCESS_SCHEME.to_string());
|
||||
@@ -7528,8 +7535,7 @@ mod tests {
|
||||
&owner_b.user_id,
|
||||
&owner_b.access_token,
|
||||
&owner_b.api_base_url,
|
||||
owner_b.identity_generation,
|
||||
owner_b.revision,
|
||||
owner_b.generation,
|
||||
)
|
||||
.expect("switch to resource non-owner B");
|
||||
|
||||
@@ -7633,8 +7639,7 @@ mod tests {
|
||||
"resource-lease-owner-b",
|
||||
"resource-lease-token-b",
|
||||
api_base_url,
|
||||
frozen_a.identity_generation + 1,
|
||||
frozen_a.revision + 1,
|
||||
frozen_a.generation + 1,
|
||||
)
|
||||
.expect("switch resource lease owner");
|
||||
switched_sender.send(()).expect("signal resource switch");
|
||||
@@ -8254,8 +8259,7 @@ mod tests {
|
||||
"archive-owner-b",
|
||||
"archive-token-b",
|
||||
api_base_url,
|
||||
owner_a.identity_generation + 1,
|
||||
owner_a.revision + 1,
|
||||
owner_a.generation + 1,
|
||||
)
|
||||
.expect("switch to owner B");
|
||||
let error = archive_failed_local_project_resource_edit_at(
|
||||
@@ -8384,8 +8388,7 @@ mod tests {
|
||||
"pending-owner-b",
|
||||
"pending-token-b",
|
||||
api_base_url,
|
||||
owner_a.identity_generation + 1,
|
||||
owner_a.revision + 1,
|
||||
owner_a.generation + 1,
|
||||
)
|
||||
.expect("switch to pending owner B");
|
||||
let owner_b = current_platform_session().expect("pending owner B session");
|
||||
@@ -9344,7 +9347,7 @@ mod tests {
|
||||
let (attempted_sender, attempted_receiver) = mpsc::channel();
|
||||
let (completed_sender, completed_receiver) = mpsc::channel();
|
||||
let switch_api_base_url = api_base_url.to_string();
|
||||
let switch_generation = frozen_session.identity_generation + 1;
|
||||
let switch_generation = frozen_session.generation + 1;
|
||||
let switch_thread = std::thread::spawn(move || {
|
||||
begin_switch_receiver
|
||||
.recv()
|
||||
@@ -9357,7 +9360,6 @@ mod tests {
|
||||
"commit-token-b",
|
||||
&switch_api_base_url,
|
||||
switch_generation,
|
||||
switch_generation,
|
||||
)
|
||||
.expect("switch to commit owner B");
|
||||
completed_sender
|
||||
|
||||
@@ -987,10 +987,7 @@ pub(crate) fn attach_external_agent_runner_gui_owner(
|
||||
platform_api_base_url: platform_session
|
||||
.as_ref()
|
||||
.map(|session| session.api_base_url.clone()),
|
||||
platform_auth_generation: platform_session
|
||||
.as_ref()
|
||||
.map(|session| session.identity_generation),
|
||||
platform_auth_revision: platform_session.map(|session| session.revision),
|
||||
platform_auth_generation: platform_session.map(|session| session.generation),
|
||||
..ExternalAgentRunnerRequestParams::default()
|
||||
},
|
||||
)?;
|
||||
@@ -1001,8 +998,7 @@ pub(crate) fn install_external_agent_runner_platform_session(
|
||||
user_id: &str,
|
||||
access_token: &str,
|
||||
api_base_url: &str,
|
||||
identity_generation: u64,
|
||||
revision: u64,
|
||||
generation: u64,
|
||||
) -> Result<(), String> {
|
||||
let config_dir = external_agent_runner_config_dir()
|
||||
.ok_or_else(|| "外部 Agent Runner 尚未配置 AppData".to_string())?;
|
||||
@@ -1012,8 +1008,7 @@ pub(crate) fn install_external_agent_runner_platform_session(
|
||||
remember_external_agent_runner_platform_session(
|
||||
external_agent_runner_gui_owner_attachment_state(),
|
||||
Some((user_id, access_token, api_base_url)),
|
||||
identity_generation,
|
||||
revision,
|
||||
generation,
|
||||
)
|
||||
.and_then(|_| ensure_external_agent_runner(&config_dir))
|
||||
.and_then(|endpoint| {
|
||||
@@ -1022,8 +1017,7 @@ pub(crate) fn install_external_agent_runner_platform_session(
|
||||
&config_dir,
|
||||
&endpoint,
|
||||
Some((user_id, access_token, api_base_url)),
|
||||
identity_generation,
|
||||
revision,
|
||||
generation,
|
||||
)
|
||||
})
|
||||
},
|
||||
@@ -1031,10 +1025,7 @@ pub(crate) fn install_external_agent_runner_platform_session(
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn clear_external_agent_runner_platform_session(
|
||||
identity_generation: u64,
|
||||
revision: u64,
|
||||
) -> Result<(), String> {
|
||||
pub(crate) fn clear_external_agent_runner_platform_session(generation: u64) -> Result<(), String> {
|
||||
let Some(config_dir) = external_agent_runner_config_dir() else {
|
||||
return Ok(());
|
||||
};
|
||||
@@ -1044,8 +1035,7 @@ pub(crate) fn clear_external_agent_runner_platform_session(
|
||||
remember_external_agent_runner_platform_session(
|
||||
external_agent_runner_gui_owner_attachment_state(),
|
||||
None,
|
||||
identity_generation,
|
||||
revision,
|
||||
generation,
|
||||
)
|
||||
.and_then(|_| ensure_external_agent_runner(&config_dir))
|
||||
.and_then(|endpoint| {
|
||||
@@ -1054,8 +1044,7 @@ pub(crate) fn clear_external_agent_runner_platform_session(
|
||||
&config_dir,
|
||||
&endpoint,
|
||||
None,
|
||||
identity_generation,
|
||||
revision,
|
||||
generation,
|
||||
)
|
||||
})
|
||||
},
|
||||
@@ -1068,8 +1057,7 @@ fn validate_external_agent_runner_platform_session_attachment(
|
||||
config_dir: &Path,
|
||||
endpoint: &ExternalAgentRunnerEndpoint,
|
||||
session: Option<(&str, &str, &str)>,
|
||||
identity_generation: u64,
|
||||
revision: u64,
|
||||
generation: u64,
|
||||
) -> Result<(), String> {
|
||||
let state = lock_unpoisoned(state);
|
||||
let registration = state.registration.as_ref().ok_or_else(|| {
|
||||
@@ -1082,8 +1070,7 @@ fn validate_external_agent_runner_platform_session_attachment(
|
||||
|| registration.attached_boot_id.as_deref() != Some(endpoint.boot_id.as_str())
|
||||
|| registration.params.gui_owner_epoch.is_none()
|
||||
|| registration.params.gui_owner_session_revision != Some(registration.generation)
|
||||
|| registration.params.platform_auth_generation != Some(identity_generation)
|
||||
|| registration.params.platform_auth_revision != Some(revision)
|
||||
|| registration.params.platform_auth_generation != Some(generation)
|
||||
|| registration.params.platform_user_id.as_deref() != expected_user_id
|
||||
|| registration.params.platform_access_token.as_deref() != expected_access_token
|
||||
|| registration.params.platform_api_base_url.as_deref() != expected_api_base_url
|
||||
@@ -1114,14 +1101,12 @@ pub(super) fn synchronize_external_agent_runner_platform_session_with(
|
||||
pub(super) fn remember_external_agent_runner_platform_session(
|
||||
state: &Mutex<ExternalAgentRunnerGuiOwnerAttachmentState>,
|
||||
session: Option<(&str, &str, &str)>,
|
||||
identity_generation: u64,
|
||||
revision: u64,
|
||||
generation: u64,
|
||||
) -> Result<(), String> {
|
||||
remember_external_agent_runner_platform_session_with(
|
||||
state,
|
||||
session,
|
||||
identity_generation,
|
||||
revision,
|
||||
generation,
|
||||
write_external_agent_runner_gui_owner_claim_atomic,
|
||||
)
|
||||
}
|
||||
@@ -1129,26 +1114,21 @@ pub(super) fn remember_external_agent_runner_platform_session(
|
||||
pub(super) fn remember_external_agent_runner_platform_session_with(
|
||||
state: &Mutex<ExternalAgentRunnerGuiOwnerAttachmentState>,
|
||||
session: Option<(&str, &str, &str)>,
|
||||
identity_generation: u64,
|
||||
revision: u64,
|
||||
generation: u64,
|
||||
write_claim: impl FnOnce(&Path, &str, u64) -> Result<(), String>,
|
||||
) -> Result<(), String> {
|
||||
let mut state = lock_unpoisoned(state);
|
||||
let Some(registration) = state.registration.as_ref() else {
|
||||
return Ok(());
|
||||
};
|
||||
// 写入顺序只认 revision;身份代次只表达主体归属,同一账号续期会推进 revision
|
||||
// 但保持 identity generation 不变。
|
||||
let current_revision = registration.params.platform_auth_revision.unwrap_or(0);
|
||||
if revision < current_revision {
|
||||
let current_generation = registration.params.platform_auth_generation.unwrap_or(0);
|
||||
if generation < current_generation {
|
||||
return Ok(());
|
||||
}
|
||||
if revision == current_revision {
|
||||
if generation == current_generation {
|
||||
match session {
|
||||
Some((user_id, access_token, api_base_url))
|
||||
if registration.params.platform_user_id.as_deref() == Some(user_id)
|
||||
&& registration.params.platform_auth_generation
|
||||
== Some(identity_generation)
|
||||
&& registration.params.platform_access_token.as_deref()
|
||||
== Some(access_token)
|
||||
&& registration.params.platform_api_base_url.as_deref()
|
||||
@@ -1188,8 +1168,7 @@ pub(super) fn remember_external_agent_runner_platform_session_with(
|
||||
session.map(|(_, access_token, _)| access_token.to_string());
|
||||
registration.params.platform_api_base_url =
|
||||
session.map(|(_, _, api_base_url)| api_base_url.to_string());
|
||||
registration.params.platform_auth_generation = Some(identity_generation);
|
||||
registration.params.platform_auth_revision = Some(revision);
|
||||
registration.params.platform_auth_generation = Some(generation);
|
||||
registration.params.gui_owner_session_revision = Some(registration_generation);
|
||||
Ok(())
|
||||
}
|
||||
@@ -1493,7 +1472,6 @@ pub(super) fn send_external_agent_runner_runtime_request_with_stable_identity(
|
||||
platform_access_token: None,
|
||||
platform_api_base_url: None,
|
||||
platform_auth_generation: None,
|
||||
platform_auth_revision: None,
|
||||
};
|
||||
match stable_identity {
|
||||
Some(stable_identity) => {
|
||||
|
||||
@@ -133,46 +133,37 @@ fn apply_external_agent_runner_gui_owner_attachment(
|
||||
params.platform_access_token.as_deref(),
|
||||
params.platform_api_base_url.as_deref(),
|
||||
params.platform_auth_generation,
|
||||
params.platform_auth_revision,
|
||||
) {
|
||||
(
|
||||
Some(user_id),
|
||||
Some(access_token),
|
||||
Some(api_base_url),
|
||||
Some(identity_generation),
|
||||
Some(revision),
|
||||
) => {
|
||||
(Some(user_id), Some(access_token), Some(api_base_url), Some(generation)) => {
|
||||
if replace_claim {
|
||||
crate::replace_platform_session_for_gui_owner(
|
||||
user_id,
|
||||
access_token,
|
||||
api_base_url,
|
||||
identity_generation,
|
||||
revision,
|
||||
generation,
|
||||
)
|
||||
} else {
|
||||
crate::install_platform_session_checked(
|
||||
user_id,
|
||||
access_token,
|
||||
api_base_url,
|
||||
identity_generation,
|
||||
revision,
|
||||
generation,
|
||||
)
|
||||
}
|
||||
}
|
||||
(None, None, None, Some(identity_generation), Some(revision)) => {
|
||||
(None, None, None, Some(generation)) => {
|
||||
if replace_claim {
|
||||
crate::clear_platform_session_for_gui_owner(identity_generation, revision);
|
||||
crate::clear_platform_session_for_gui_owner(generation);
|
||||
Ok(())
|
||||
} else {
|
||||
crate::clear_platform_session_checked(identity_generation, revision)
|
||||
crate::clear_platform_session_checked(generation)
|
||||
}
|
||||
}
|
||||
(None, None, None, None, None) if replace_claim => {
|
||||
crate::clear_platform_session_for_gui_owner(0, 0);
|
||||
(None, None, None, None) if replace_claim => {
|
||||
crate::clear_platform_session_for_gui_owner(0);
|
||||
Ok(())
|
||||
}
|
||||
(None, None, None, None, None) => Ok(()),
|
||||
(None, None, None, None) => Ok(()),
|
||||
_ => Err("Agent Runner GUI owner 的平台登录态同步参数不完整".to_string()),
|
||||
};
|
||||
result?;
|
||||
@@ -180,7 +171,7 @@ fn apply_external_agent_runner_gui_owner_attachment(
|
||||
Ok(claim) => claim,
|
||||
Err(error) => {
|
||||
*active_claim = None;
|
||||
crate::clear_platform_session_for_gui_owner(0, 0);
|
||||
crate::clear_platform_session_for_gui_owner(0);
|
||||
return Err(format!(
|
||||
"Agent Runner GUI owner claim 在 attach 提交期间无法核验,平台登录态已隔离:{error}"
|
||||
));
|
||||
@@ -190,7 +181,7 @@ fn apply_external_agent_runner_gui_owner_attachment(
|
||||
|| committed_claim.session_revision != requested_revision
|
||||
{
|
||||
*active_claim = None;
|
||||
crate::clear_platform_session_for_gui_owner(0, 0);
|
||||
crate::clear_platform_session_for_gui_owner(0);
|
||||
return Err("Agent Runner GUI owner claim 在 attach 提交期间已变化".to_string());
|
||||
}
|
||||
if let Some(event_sink) = event_sink {
|
||||
@@ -216,7 +207,7 @@ pub(super) fn validate_external_agent_runner_gui_owner_claim_current(
|
||||
return Ok(());
|
||||
}
|
||||
*active_claim = None;
|
||||
crate::clear_platform_session_for_gui_owner(0, 0);
|
||||
crate::clear_platform_session_for_gui_owner(0);
|
||||
match durable_claim {
|
||||
Ok(_) => Err(
|
||||
"authentication-required: Agent Runner GUI owner claim 已变化,平台登录态已隔离"
|
||||
|
||||
@@ -280,10 +280,6 @@ pub(super) struct ExternalAgentRunnerRequestParams {
|
||||
pub(super) platform_api_base_url: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub(super) platform_auth_generation: Option<u64>,
|
||||
/// 原生写入 revision:只用于 install / clear 的顺序判定。同一身份的凭据轮换会推进
|
||||
/// revision,但不推进 `platform_auth_generation`(身份代次)。
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub(super) platform_auth_revision: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
|
||||
@@ -668,16 +668,14 @@ fn gui_owner_registration_replays_only_the_latest_platform_session() {
|
||||
&state,
|
||||
Some(("user-a", "token-a", "https://dev.genarrative.world")),
|
||||
4,
|
||||
4,
|
||||
)
|
||||
.expect("remember owner A session");
|
||||
remember_external_agent_runner_platform_session(&state, None, 5, 5)
|
||||
remember_external_agent_runner_platform_session(&state, None, 5)
|
||||
.expect("remember logged-out session");
|
||||
remember_external_agent_runner_platform_session(
|
||||
&state,
|
||||
Some(("user-a", "late-token-a", "https://dev.genarrative.world")),
|
||||
4,
|
||||
4,
|
||||
)
|
||||
.expect("ignore stale owner A session");
|
||||
remember_external_agent_runner_platform_session(
|
||||
@@ -688,14 +686,12 @@ fn gui_owner_registration_replays_only_the_latest_platform_session() {
|
||||
"https://dev.genarrative.world",
|
||||
)),
|
||||
5,
|
||||
5,
|
||||
)
|
||||
.expect("ignore conflicting same-generation session");
|
||||
remember_external_agent_runner_platform_session(
|
||||
&state,
|
||||
Some(("user-b", "token-b", "https://dev.genarrative.world")),
|
||||
6,
|
||||
6,
|
||||
)
|
||||
.expect("remember latest owner B session");
|
||||
|
||||
@@ -736,7 +732,6 @@ fn gui_owner_platform_session_change_marks_the_same_boot_for_reattach() {
|
||||
platform_access_token: Some("token-a".to_string()),
|
||||
platform_api_base_url: Some("https://dev.genarrative.world".to_string()),
|
||||
platform_auth_generation: Some(1),
|
||||
platform_auth_revision: Some(1),
|
||||
..ExternalAgentRunnerRequestParams::default()
|
||||
},
|
||||
)
|
||||
@@ -757,7 +752,7 @@ fn gui_owner_platform_session_change_marks_the_same_boot_for_reattach() {
|
||||
)
|
||||
.expect("attach owner A");
|
||||
|
||||
remember_external_agent_runner_platform_session(&state, None, 2, 2)
|
||||
remember_external_agent_runner_platform_session(&state, None, 2)
|
||||
.expect("remember logged-out session");
|
||||
attach_registered_external_agent_runner_gui_owner_if_needed_with(
|
||||
&state,
|
||||
@@ -786,7 +781,6 @@ fn stale_gui_owner_attach_completion_cannot_mark_new_session_as_attached() {
|
||||
platform_access_token: Some("token-a".to_string()),
|
||||
platform_api_base_url: Some("https://dev.genarrative.world".to_string()),
|
||||
platform_auth_generation: Some(1),
|
||||
platform_auth_revision: Some(1),
|
||||
..ExternalAgentRunnerRequestParams::default()
|
||||
},
|
||||
)
|
||||
@@ -806,7 +800,6 @@ fn stale_gui_owner_attach_completion_cannot_mark_new_session_as_attached() {
|
||||
&state,
|
||||
Some(("user-b", "token-b", "https://dev.genarrative.world")),
|
||||
2,
|
||||
2,
|
||||
)
|
||||
.expect("remember owner B while owner A attach is in flight");
|
||||
Ok(())
|
||||
@@ -851,7 +844,6 @@ fn gui_owner_platform_session_payload_clears_runner_session() {
|
||||
gui_owner_epoch: Some(owner.owner_epoch().to_string()),
|
||||
gui_owner_session_revision: Some(0),
|
||||
platform_auth_generation: Some(2),
|
||||
platform_auth_revision: Some(2),
|
||||
..ExternalAgentRunnerRequestParams::default()
|
||||
},
|
||||
)
|
||||
@@ -883,7 +875,6 @@ fn gui_owner_partial_platform_session_payload_fails_without_mutation() {
|
||||
platform_user_id: Some("runner-owner-b".to_string()),
|
||||
platform_api_base_url: Some("https://dev.genarrative.world".to_string()),
|
||||
platform_auth_generation: Some(2),
|
||||
platform_auth_revision: Some(2),
|
||||
..ExternalAgentRunnerRequestParams::default()
|
||||
},
|
||||
)
|
||||
@@ -917,7 +908,6 @@ fn new_gui_owner_epoch_replaces_higher_generation_runner_session_and_rejects_old
|
||||
platform_access_token: Some("runner-token-a".to_string()),
|
||||
platform_api_base_url: Some("https://dev.genarrative.world".to_string()),
|
||||
platform_auth_generation: Some(10),
|
||||
platform_auth_revision: Some(10),
|
||||
..ExternalAgentRunnerRequestParams::default()
|
||||
},
|
||||
)
|
||||
@@ -935,14 +925,12 @@ fn new_gui_owner_epoch_replaces_higher_generation_runner_session_and_rejects_old
|
||||
platform_access_token: Some("runner-token-b".to_string()),
|
||||
platform_api_base_url: Some("https://dev.genarrative.world".to_string()),
|
||||
platform_auth_generation: Some(1),
|
||||
platform_auth_revision: Some(1),
|
||||
..ExternalAgentRunnerRequestParams::default()
|
||||
},
|
||||
)
|
||||
.expect("new GUI epoch replaces higher-generation old owner");
|
||||
assert_eq!(
|
||||
crate::current_platform_session()
|
||||
.map(|session| (session.user_id, session.identity_generation)),
|
||||
crate::current_platform_session().map(|session| (session.user_id, session.generation)),
|
||||
Some(("runner-owner-b".to_string(), 1))
|
||||
);
|
||||
|
||||
@@ -955,7 +943,6 @@ fn new_gui_owner_epoch_replaces_higher_generation_runner_session_and_rejects_old
|
||||
platform_access_token: Some("runner-token-a".to_string()),
|
||||
platform_api_base_url: Some("https://dev.genarrative.world".to_string()),
|
||||
platform_auth_generation: Some(11),
|
||||
platform_auth_revision: Some(11),
|
||||
..ExternalAgentRunnerRequestParams::default()
|
||||
},
|
||||
)
|
||||
@@ -992,7 +979,6 @@ fn durable_claim_revision_change_clears_runner_session_and_blocks_runtime_until_
|
||||
platform_access_token: Some("runner-token-a".to_string()),
|
||||
platform_api_base_url: Some("https://dev.genarrative.world".to_string()),
|
||||
platform_auth_generation: Some(8),
|
||||
platform_auth_revision: Some(8),
|
||||
..ExternalAgentRunnerRequestParams::default()
|
||||
},
|
||||
)
|
||||
@@ -1016,7 +1002,6 @@ fn durable_claim_revision_change_clears_runner_session_and_blocks_runtime_until_
|
||||
platform_access_token: Some("runner-token-b".to_string()),
|
||||
platform_api_base_url: Some("https://dev.genarrative.world".to_string()),
|
||||
platform_auth_generation: Some(1),
|
||||
platform_auth_revision: Some(1),
|
||||
..ExternalAgentRunnerRequestParams::default()
|
||||
},
|
||||
)
|
||||
@@ -1024,8 +1009,7 @@ fn durable_claim_revision_change_clears_runner_session_and_blocks_runtime_until_
|
||||
validate_external_agent_runner_gui_owner_claim_current(&state)
|
||||
.expect("reattached owner B claim is current");
|
||||
assert_eq!(
|
||||
crate::current_platform_session()
|
||||
.map(|session| (session.user_id, session.identity_generation)),
|
||||
crate::current_platform_session().map(|session| (session.user_id, session.generation)),
|
||||
Some(("runner-owner-b".to_string(), 1))
|
||||
);
|
||||
}
|
||||
@@ -1069,7 +1053,6 @@ fn failed_gui_owner_claim_write_is_fenced_before_local_session_can_change() {
|
||||
platform_access_token: Some("runner-token-a".to_string()),
|
||||
platform_api_base_url: Some("https://dev.genarrative.world".to_string()),
|
||||
platform_auth_generation: Some(1),
|
||||
platform_auth_revision: Some(1),
|
||||
..ExternalAgentRunnerRequestParams::default()
|
||||
},
|
||||
)
|
||||
@@ -1086,7 +1069,6 @@ fn failed_gui_owner_claim_write_is_fenced_before_local_session_can_change() {
|
||||
"https://dev.genarrative.world",
|
||||
)),
|
||||
2,
|
||||
2,
|
||||
|_, _, _| Err("injected durable claim write failure".to_string()),
|
||||
)
|
||||
},
|
||||
|
||||
@@ -1,73 +1,5 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn legacy_official_config_gains_hand_editable_connection_keys_on_startup() {
|
||||
// 用户现有配置文件(官方路由、连接字段已被清掉)在启动迁移路径上必须补齐
|
||||
// 开关、模型列表与连接四要素,手写自定义连接时能看到完整字段。
|
||||
let mut config: GameCreatorAppConfigFile = serde_json::from_str(
|
||||
r#"{"schemaVersion":"game-creator-config.v2","agentMode":"codex_app_server","llm":{"reasoningEffort":"max","stream":true},"selectedModelId":"quality","selectedModelIsDefault":true}"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(ensure_game_creator_custom_llm_file_fields(&mut config));
|
||||
assert!(scrub_locked_game_creator_config_file(&mut config));
|
||||
let migrated: serde_json::Value = serde_json::to_value(&config).unwrap();
|
||||
assert_eq!(migrated["llm"]["customEnabled"], false);
|
||||
assert_eq!(migrated["llm"]["visibleModels"], serde_json::json!([]));
|
||||
assert_eq!(migrated["llm"]["apiKey"], "");
|
||||
assert_eq!(migrated["llm"]["baseUrl"], OFFICIAL_LLM_ROUTER_BASE_URL);
|
||||
assert_eq!(migrated["llm"]["model"], "quality");
|
||||
assert_eq!(
|
||||
migrated["llm"]["apiKind"],
|
||||
DEFAULT_GAME_CREATOR_LLM_API_KIND
|
||||
);
|
||||
assert_eq!(migrated["llm"]["reasoningEffort"], "max");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_llm_config_save_reload_and_selection_preserve_overlay_and_credentials() {
|
||||
let root = unique_project_path();
|
||||
fs::create_dir_all(&root).unwrap();
|
||||
let _guard = use_test_runtime_config_dir(root.clone());
|
||||
let primary = root.join(GAME_CREATOR_CONFIG_FILE_NAME);
|
||||
let overlay = root.join(GAME_CREATOR_LOCAL_CONFIG_FILE_NAME);
|
||||
write_game_creator_config_atomically(
|
||||
&primary,
|
||||
&serde_json::to_string(&GameCreatorAppConfig::default()).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
write_game_creator_config_atomically(&overlay, r#"{"llm":{"customEnabled":true,"apiKey":"fixture-key","baseUrl":"https://custom.example/v1","visibleModels":["a/v1","b:v2"]},"selectedModelId":"a/v1","selectedModelIsDefault":true}"#).unwrap();
|
||||
let mut config = read_game_creator_app_config().unwrap().config;
|
||||
assert_eq!(config.llm.model, "a/v1");
|
||||
let selected = select_game_creator_model("b:v2".into(), false).unwrap();
|
||||
assert_eq!(selected.config.llm.model, "b:v2");
|
||||
assert!(select_game_creator_model("not-listed".into(), false).is_err());
|
||||
config.llm.visible_models = vec!["b:v2".into()];
|
||||
config.llm.api_key = "changed-fixture-key".into();
|
||||
write_game_creator_app_config(config).unwrap();
|
||||
let reloaded = read_game_creator_app_config().unwrap().config;
|
||||
assert!(reloaded.llm.custom_enabled);
|
||||
assert_eq!(reloaded.llm.visible_models, ["b:v2"]);
|
||||
assert_eq!(reloaded.llm.model, "b:v2");
|
||||
assert_eq!(reloaded.llm.api_key, "changed-fixture-key");
|
||||
let persisted: serde_json::Value =
|
||||
serde_json::from_str(&fs::read_to_string(&primary).unwrap()).unwrap();
|
||||
for key in [
|
||||
"customEnabled",
|
||||
"visibleModels",
|
||||
"apiKey",
|
||||
"baseUrl",
|
||||
"model",
|
||||
"apiKind",
|
||||
"reasoningEffort",
|
||||
] {
|
||||
assert!(
|
||||
persisted["llm"].get(key).is_some(),
|
||||
"本地配置始终保留 {key},方便手写自定义连接:{persisted}"
|
||||
);
|
||||
}
|
||||
fs::remove_dir_all(root).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_file_overrides_defaults_without_env() {
|
||||
let root = unique_project_path();
|
||||
@@ -381,14 +313,10 @@ fn locked_config_scrub_removes_all_legacy_provider_credentials() {
|
||||
.llm
|
||||
.as_ref()
|
||||
.expect("global llm remains as non-sensitive tuning");
|
||||
// 连接字段保留在文件里(空 Key + 官方地址),便于手写自定义连接时对照。
|
||||
assert_eq!(llm.api_key.as_deref(), Some(""));
|
||||
assert_eq!(llm.base_url.as_deref(), Some(OFFICIAL_LLM_ROUTER_BASE_URL));
|
||||
assert_eq!(
|
||||
llm.api_kind.as_deref(),
|
||||
Some(DEFAULT_GAME_CREATOR_LLM_API_KIND)
|
||||
);
|
||||
assert!(llm.model.is_some());
|
||||
assert!(llm.api_key.is_none());
|
||||
assert!(llm.base_url.is_none());
|
||||
assert!(llm.model.is_none());
|
||||
assert!(llm.api_kind.is_none());
|
||||
let serialized = serde_json::to_string(&config).expect("serialize scrubbed config");
|
||||
assert!(!serialized.contains("legacy-global-key"));
|
||||
assert!(!serialized.contains("legacy-agent-key"));
|
||||
@@ -760,8 +688,6 @@ fn app_config_commands_write_runtime_config_file() {
|
||||
agent_llm.insert(
|
||||
" planner ".to_string(),
|
||||
GameCreatorLlmConfigFile {
|
||||
custom_enabled: None,
|
||||
visible_models: None,
|
||||
api_key: Some(" planner-key ".to_string()),
|
||||
base_url: Some(" https://planner.example.test/v1 ".to_string()),
|
||||
model: Some(" planner-model ".to_string()),
|
||||
@@ -783,8 +709,6 @@ fn app_config_commands_write_runtime_config_file() {
|
||||
schema_version: GAME_CREATOR_APP_CONFIG_SCHEMA_VERSION.to_string(),
|
||||
agent_mode: GAME_CREATOR_AGENT_MODE_PROVIDER.to_string(),
|
||||
llm: GameCreatorLlmConfig {
|
||||
custom_enabled: false,
|
||||
visible_models: Vec::new(),
|
||||
api_key: " unit-test-key ".to_string(),
|
||||
base_url: " https://runtime.example.test/v1 ".to_string(),
|
||||
model: " runtime-model ".to_string(),
|
||||
@@ -914,7 +838,7 @@ fn app_config_save_updates_conflicting_local_overlay() {
|
||||
fs::create_dir_all(&root).expect("config dir");
|
||||
let _guard = use_test_runtime_config_dir(root.clone());
|
||||
let overlay_path = root.join(GAME_CREATOR_LOCAL_CONFIG_FILE_NAME);
|
||||
write_game_creator_config_atomically(
|
||||
fs::write(
|
||||
&overlay_path,
|
||||
r#"{"llm":{"reasoningEffort":"low"},"selectedModelId":"existing","selectedModelIsDefault":true,"custom":{"keep":true}}"#,
|
||||
)
|
||||
@@ -947,7 +871,7 @@ fn app_config_model_selection_only_updates_model_overlay() {
|
||||
fs::create_dir_all(&root).expect("config dir");
|
||||
let _guard = use_test_runtime_config_dir(root.clone());
|
||||
let overlay_path = root.join(GAME_CREATOR_LOCAL_CONFIG_FILE_NAME);
|
||||
write_game_creator_config_atomically(
|
||||
fs::write(
|
||||
&overlay_path,
|
||||
r#"{"selectedModelId":"old","selectedModelIsDefault":false,"llm":{"reasoningEffort":"low"}}"#,
|
||||
)
|
||||
|
||||
@@ -5885,8 +5885,6 @@ async fn agent_loop_uses_per_agent_llm_overrides() {
|
||||
agent_llm.insert(
|
||||
"planner".to_string(),
|
||||
GameCreatorLlmConfigFile {
|
||||
custom_enabled: None,
|
||||
visible_models: None,
|
||||
api_key: Some("planner-key".to_string()),
|
||||
base_url: Some(planner_base_url),
|
||||
model: Some("planner-model".to_string()),
|
||||
@@ -5905,8 +5903,6 @@ async fn agent_loop_uses_per_agent_llm_overrides() {
|
||||
agent_llm.insert(
|
||||
"generator".to_string(),
|
||||
GameCreatorLlmConfigFile {
|
||||
custom_enabled: None,
|
||||
visible_models: None,
|
||||
api_key: Some("generator-key".to_string()),
|
||||
base_url: Some(generator_base_url),
|
||||
model: Some("generator-model".to_string()),
|
||||
@@ -5925,8 +5921,6 @@ async fn agent_loop_uses_per_agent_llm_overrides() {
|
||||
agent_llm.insert(
|
||||
"art-asset-plan".to_string(),
|
||||
GameCreatorLlmConfigFile {
|
||||
custom_enabled: None,
|
||||
visible_models: None,
|
||||
api_key: Some("art-key".to_string()),
|
||||
base_url: Some(art_base_url),
|
||||
model: Some("art-model".to_string()),
|
||||
|
||||
@@ -1494,7 +1494,7 @@ async fn background_agent_runtime_deletes_file_then_verifies_before_completion()
|
||||
assert!(prompt_input.contains("只删除项目内普通文件"));
|
||||
|
||||
let verification_request = receiver
|
||||
.recv_timeout(Duration::from_secs(10))
|
||||
.recv_timeout(Duration::from_secs(2))
|
||||
.expect("verification plan request");
|
||||
assert!(verification_request.contains("file.delete"));
|
||||
assert!(verification_request.contains("已删除 game/obsolete-runtime-file.txt"));
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "陶泥儿",
|
||||
"version": "0.1.45",
|
||||
"version": "0.1.29",
|
||||
"identifier": "world.genarrative.ai-game-creator",
|
||||
"build": {
|
||||
"beforeDevCommand": "npm --prefix ../.. run agc:serve",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user