清理无调用方的客户端文件写入命令

删除旧文件写入命令、Tauri 注册及配置检查豁免
收缩相关测试,保留现役 checkpoint、UI 保存和记忆写入验证
同步埋点采集边界说明与共享决策记录
This commit is contained in:
2026-09-22 14:34:14 +00:00
parent a5f1ece021
commit 5f7c168841
7 changed files with 20 additions and 132 deletions
@@ -154,7 +154,6 @@ const allowedUncalledTauriCommands = [
'steer_game_creator_agent_runtime_task',
'write_local_agent_memory',
'write_local_game_memory',
'write_local_project_file',
// Agent 运行时会话 / 目标 / 协作命令由 native 侧与 CLI swarm 驱动,前端没有调用方。
'archive_game_creator_agent_session',
'clear_game_creator_agent_goal',
@@ -5715,20 +5715,6 @@ pub(crate) fn cancel_local_project_resource_preview_scope(
preview_manager.cancel_scope(&scope_id)
}
#[tauri::command]
pub(crate) fn write_local_project_file(
project_path: String,
relative_path: String,
content: String,
) -> Result<LocalProjectFileMutationResult, String> {
write_local_project_file_with_capture(
project_path,
relative_path,
content,
crate::analytics::gui::capture_writer_context(),
)
}
type ManualAnalyticsCapture = Option<(
crate::analytics::contract::Context,
crate::analytics::store::AnalyticsWriter,
@@ -5738,64 +5724,6 @@ type ManualAnalyticsCapture = Option<(
#[path = "commands_manual_analytics_tests.rs"]
mod manual_analytics_tests;
fn write_local_project_file_with_capture(
project_path: String,
relative_path: String,
content: String,
capture: ManualAnalyticsCapture,
) -> Result<LocalProjectFileMutationResult, String> {
use crate::analytics::{
contract::{ProjectSaved, RevisionCreated, RevisionSource, SaveSource, Source},
project,
};
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "file.write")?;
let _lock = acquire_project_write_lock(root, "file.write")?;
let project_id = capture
.as_ref()
.and_then(|_| read_existing_manifest_for_project(root).ok())
.map(|manifest| manifest.project_id);
let kind = normalize_relative_path(relative_path.trim())
.ok()
.and_then(|path| project::file_change_kind(&path));
let changed = project_id
.as_ref()
.and_then(|_| kind.as_ref())
.and_then(|_| {
project::file_content_changed(root, relative_path.trim(), content.as_bytes(), 1_500_000)
});
let operation_id = uuid::Uuid::new_v4().to_string();
let revision = advance_agent_runtime_project_revision_locked(root)?;
let result = write_local_project_file_at(root, relative_path.trim(), &content)?;
if let (Some(project_id), Some(kind)) = (project_id, kind) {
if changed == Some(true) {
project::revision(
capture.clone(),
&project_id,
Source::Manual,
RevisionCreated {
revision_id: revision.to_string(),
revision_source: RevisionSource::ManualEdit,
change_kind: kind,
files_changed_count: Some(1),
},
);
}
project::saved(
capture,
&project_id,
Source::Manual,
&operation_id,
ProjectSaved {
save_source: SaveSource::Manual,
revision_id: Some(revision.to_string()),
},
None,
);
}
Ok(result)
}
#[tauri::command]
pub(crate) fn read_local_game_memory(
project_path: String,
@@ -146,7 +146,7 @@ fn drain(config: &Path, context: &Context, writer: &AnalyticsWriter) -> Vec<serd
}
#[test]
fn manual_commands_record_committed_changes_explicit_saves_and_complete_checkpoint() {
fn manual_checkpoint_records_only_complete_save_with_frozen_identity() {
let temp = tempfile::tempdir().unwrap();
let root = temp.path().join("project");
init_local_game_project_at(&root, "manual-analytics", "人工操作采集").unwrap();
@@ -161,29 +161,8 @@ fn manual_commands_record_committed_changes_explicit_saves_and_complete_checkpoi
let captured = Some((current.clone(), writer.clone()));
current.route.user_id = Some("B".into());
let path = root.to_string_lossy().into_owned();
for _ in 0..2 {
write_local_project_file_with_capture(
path.clone(),
"game/result.js".into(),
"const result = 1;".into(),
captured.clone(),
)
.unwrap();
}
assert!(write_local_project_file_with_capture(
path.clone(),
"../escape.js".into(),
"bad".into(),
captured.clone()
)
.is_err());
write_local_project_file_with_capture(
path.clone(),
"notes.txt".into(),
"unclassified".into(),
captured.clone(),
)
.unwrap();
fs::create_dir_all(root.join("game")).unwrap();
fs::write(root.join("game/result.js"), "const result = 1;").unwrap();
let checkpoint =
create_local_project_checkpoint_with_capture(path.clone(), captured.clone()).unwrap();
assert!(checkpoint.file_count > 0);
@@ -196,33 +175,17 @@ fn manual_commands_record_committed_changes_explicit_saves_and_complete_checkpoi
)
.is_err());
let events = drain(&config, &current, &writer);
let revisions: Vec<_> = events
.iter()
.filter(|event| event["event_name"] == "project_revision_created")
.collect();
assert_eq!(revisions.len(), 1, "同内容写入与失败写入不产生新成果");
let saves: Vec<_> = events
.iter()
.filter(|event| event["event_name"] == "project_save")
.collect();
assert_eq!(
saves.len(),
3,
"two explicit writes plus one complete checkpoint"
);
assert_eq!(
saves
.iter()
.filter(|event| event["properties"]["save_source"] == "manual")
.count(),
2
);
assert_eq!(saves.len(), 1, "only the complete checkpoint is saved");
let checkpoint_event = saves
.iter()
.find(|event| event["properties"]["save_source"] == "checkpoint")
.unwrap();
assert!(checkpoint_event["properties"].get("revision_id").is_none());
for event in revisions.iter().chain(saves.iter()) {
for event in &saves {
assert_eq!(event["user_id"], "A");
assert_eq!(event["project_id"], "manual-analytics");
assert_eq!(event["source"], "manual");
@@ -2750,7 +2750,6 @@ fn main() {
read_local_project_structured_preview,
read_local_project_media_preview,
cancel_local_project_resource_preview_scope,
write_local_project_file,
read_local_game_memory,
read_local_agent_memory,
write_local_agent_memory,
@@ -2777,17 +2777,11 @@ fn agent_run_history_prunes_to_latest_hundred_traces() {
}
#[test]
fn developer_project_file_and_memory_writes_advance_project_revision() {
fn developer_memory_writes_advance_project_revision() {
let root = unique_project_path();
init_local_game_project_at(&root, "project-1", "开发面板 revision").expect("project init");
let project_path = root.to_string_lossy().into_owned();
write_local_project_file(
project_path.clone(),
"game/revision.txt".to_string(),
"v1".to_string(),
)
.expect("write project file through command");
write_local_agent_memory(
project_path.clone(),
"design-director".to_string(),
@@ -2804,7 +2798,7 @@ fn developer_project_file_and_memory_writes_advance_project_revision() {
read_game_creator_agent_runtime_project_revision(&root)
.expect("read developer mutation revision")
.revision,
3
2
);
fs::remove_dir_all(root).ok();
}
@@ -1,5 +1,10 @@
# 决策记录
## 2026-09-22 清理无调用方的 GUI 文件写入命令
- 确认 `write_local_project_file` 无现役前端或业务调用方后,删除命令、Tauri 注册及命令检查豁免;移除对应测试片段,保留 checkpoint、UI 保存和记忆写入的既有测试。
- Agent 使用的底层 `write_local_project_file_at` 保留。GUI 人工文件写入不再列为采集入口;不扩充其他文件操作埋点,不修改已有事件数据合同。
## 2026-09-22 客户端埋点后台按发生时间排序
- 按技术负责人要求,列表改为 `(event_time, event_id)` 倒序,历史补传按发生时间归位。入库时间仍用于固定分页快照,翻页期间新入库的事件在刷新后显示。
@@ -20,10 +25,10 @@
## 2026-09-21 客户端埋点方案进入团队共享文档
- 本期验收完成:原始需求与已确认口径的 12 类事件入口已核对,同一 writer/session/project/goal 的宿主组件链路通过真实文件、HTTP、checkpoint 与 JSONL 关联验证;最终 51 项 Rust、46 项前端测试和类型/格式/文档检查通过。仅测试辅助模拟创建投递、run 结果和构建产物,不宣称完整 GUI/Provider 端到端验证;未提交或发布。禁止把全部资源操作逐项接线重新当作本期必做范围。
- 最新口径:共 12 类启用事件;策划审批通过后实际进入下一阶段并成功持久化,复用 project_revision_createdrevision_id=design:<session_id>:<target_phase>、source=design_agent、revision_source=agent、change_kind=design_document。同会话同目标阶段幂等;不严格校验文档版本/差异,阶段内文件修改不逐次采集,重开不补历史。不新增独立策划进度事件或审批、澄清状态字段。Direct 文件/补丁、人工编辑、UI 成果、预览与保存已有定向验收,基础事件入口已核对,同一宿主组件链路验收已通过。
- 最新口径:共 12 类启用事件;策划审批通过后实际进入下一阶段并成功持久化,复用 project_revision_createdrevision_id=design:<session_id>:<target_phase>、source=design_agent、revision_source=agent、change_kind=design_document。同会话同目标阶段幂等;不严格校验文档版本/差异,阶段内文件修改不逐次采集,重开不补历史。不新增独立策划进度事件或审批、澄清状态字段。Direct 文件/补丁、UI 成果、预览与保存已有定向验收,基础事件入口已核对,同一宿主组件链路验收已通过。
- 两类 Agent run 元数据随真实受理保存,后台维护项目累计观测重试与双 Agent 当前终态槽位;重放不新建,恢复保留原身份且耗时未知,取消/不确定不伪造失败。Direct 自动认证刷新只确认最后一次原生尝试的有界内存候选,账号代次变化丢弃;缺失不回退旧失败,不为观测增加业务写盘等待。运行结果已通过独立验收,真实付费 Provider/完整 GUI run 尚未 smoke,现有 Direct 合同中断恢复行为未改变。
- Direct 宿主文件写入/正式补丁仅在已知成果内容变化且原事务 revision 成功提交后记成果,原 run 用户归属不变,末尾 projection 不重复记。当前 session 内存关联最新可信成果到 run;缺证据为 null,不据全局 fingerprint 推断作者。真实 writer、bundled patch 执行器和定向测试已通过。
- GUI 人工文件写入区分真实变化和显式保存,删除不记保存;完整项目 checkpoint 按真实 checkpoint_id 记保存。UI State 仅 Saved 记成果;手动 Saved/Unchanged 可记保存,自动保存仅 Saved,保存并生成需全操作成功。起点冻结身份,埋点失败不影响业务。63 项 Rust、41 项前端测试及独立验收通过;无完整 GUI 跨层保存 smoke。旧 Runtime 开发/CLI 文件及 UI workflow 不因存在代码就纳入正式 GUI 必需采集。
- GUI 人工文件写入命令已于 2026-09-22 清理,不再作为采集入口;完整项目 checkpoint 按真实 checkpoint_id 记保存。UI State 仅 Saved 记成果;手动 Saved/Unchanged 可记保存,自动保存仅 Saved,保存并生成需全操作成功。起点冻结身份,埋点失败不影响业务。63 项 Rust、41 项前端测试及独立验收通过;无完整 GUI 跨层保存 smoke。旧 Runtime 开发/CLI 文件及 UI workflow 不因存在代码就纳入正式 GUI 必需采集。
- 正式 Web preview_ready 由 GUI 用户持续预览和 Direct 临时浏览器预览接入,冻结原身份、版本与实例;异步2秒loopback GET禁代理/重定向,原入口及响应非空、版本/实例仍匹配才记录。实例停止/替换、验证结束或取消后丢弃迟到结果;可访问不等于JS/游戏验证成功。合并后78项Rust、104项前端测试及独立验收通过,未调用真实Provider或跑完整Chrome双端验证。资源操作全面接线计划已撤销;现有采集只表示已观测变化,不代表全部资源操作或项目全部修订。
- 首次提交按本地观测口径每个新项目最多一条:真实受理候选在后台持久消费 `.agent/analytics-goal.json` 资格后投递。资格跨批次清理保留,旧项目不初始化;此前候选丢失时允许后续真实受理消费,使用后者自己的用户与时间,不宣称绝对首次。消费后事件丢失可零条,重放与恢复不补历史;不得为埋点扫描双 Agent 完整历史或阻塞业务写盘。
@@ -1,6 +1,6 @@
# 客户端本地埋点与主站入库契约
Version: 0.28
Version: 0.29
Status: 本地采集及上传、入库、成功清理、后台查询已实现并完成本地隔离环境验收;未部署生产
Date: 2026-09-21
需求来源:[Game Agent 埋点设计原始方案](./【需求来源】GameAgent埋点设计原始方案-2026-09-05.md);原始方案与后续已确认决策有差异时,以本文为准。
@@ -266,7 +266,7 @@ Direct 的自动登录刷新重试仍属同一个 run。原生单次调用结束
- 策划阶段事件的 event_time 取本次阶段变更成功持久化时间,用户和平台取审批操作开始时捕获的身份,不能归入异步完成时的新账号。
- 去重事实键:project_id + revision_id + event_name。一个原子 revision 只记一次;多来源合并提交按真实提交范围填写,禁止任选最后一个来源。
- 外部编辑器的修改仅在宿主已有变化确认与 revision 提交时覆盖;本版不新建全盘监听,不宣称能看到所有磁盘改动。
- 采集覆盖边界:第一阶段记录基础创作链路中的项目变化,不要求逐项覆盖所有资源操作。资源上传、删除、重命名、派生、图片正规化、音频/画布生成、版本资源替换与批量导入,不因已有业务 revision 就自动成为本期必须接入的埋点入口。撤销此前为这些入口新增的全面接线计划,不新增资源账本采集字段。已接入的 Direct 文件/补丁、人工编辑与 UI 成果按上述真实变化规则记录;统计结果仅表示已观测变化,不能宣称是全量资源操作或项目全部修订次数。
- 采集覆盖边界:第一阶段记录基础创作链路中的项目变化,不要求逐项覆盖所有资源操作。资源上传、删除、重命名、派生、图片正规化、音频/画布生成、版本资源替换与批量导入,不因已有业务 revision 就自动成为本期必须接入的埋点入口。撤销此前为这些入口新增的全面接线计划,不新增资源账本采集字段。已接入的 Direct 文件/补丁与 UI 成果按上述真实变化规则记录;统计结果仅表示已观测变化,不能宣称是全量资源操作或项目全部修订次数。
### 6.12 preview_ready
@@ -287,7 +287,7 @@ Direct 的自动登录刷新重试仍属同一个 run。原生单次调用结束
- propertiessave_source=manual / auto / checkpointrevision_id 可选,取本次保存结果对应的真实 revision。
- Agent 会话检查点、审计记录、埋点写入、配置保存、云快照同步不算项目保存。独立素材导出也不自动等于项目保存。
- 无变化的自动保存不记;用户显式保存成功可记,但不能同时伪造 revision。失败或部分写入不记成功。
- GUI 人工文件写入在业务成功后记 manual 保存;同内容可以保存,但仅真实内容变化才记 manual_edit 成果。仅已识别成果文件参与统计。旧 `delete_local_project_file` 命令已随主分支退役,不恢复该入口或为其他删除入口扩充采集。
- 无现役调用方的 `write_local_project_file` GUI 命令及其人工文件写入埋点已删除;Agent 仍使用底层文件写入函数。当前 GUI 保存采集来自项目 checkpoint 与 UI 编辑器,不将已删除命令计为采集入口。旧 `delete_local_project_file` 命令已随主分支退役,不恢复这两个入口或扩充其他文件操作采集。
- GUI 手动项目 checkpoint 完整成功后记 source=manual、save_source=checkpoint,复用真实 checkpoint_id 作为操作身份,revision 不确定时省略;不同于 Agent 会话 checkpoint。其他工具调用该底层函数不自动归为人工。
- UI 编辑器普通保存与保存并生成是两个逻辑操作。State 的 Saved 结果可记 ui_editor 成果,Unchanged/Conflict 不记成果;普通手动保存 Saved/Unchanged 均可记一次保存,自动保存仅 Saved 记。保存并生成必须等代码生成也成功后才记一次保存,不能在中间 State 成功时提前记;其保存事件省略没有整体提交依据的 revision_id。原生 State 成功事件即使后续生成失败也保留。
- UI 逻辑保存起点捕获宿主身份和操作 UUID,收尾异步非等待回传;原生校验本 GUI 会话、字段和真实项目再投递。身份捕获、桥接或采集失败不改变业务结果。不增加 UI 业务状态机或保存专用持久化。
@@ -425,7 +425,7 @@ session.json 是本地恢复元数据,不是待上传事件;事件文件不
## 10. 实施拆分与验收依据
技术负责人已授权按计划落地;以下为实施里程碑边界。合同与本地队列、会话窗口与项目接入、策划阶段推进成果事件、首次提交、两类 Agent run、Direct 宿主文件/补丁成果、GUI 人工编辑、checkpoint 与 UI 保存,以及正式 Web 预览 ready 均已定向验收,并完成下述同一宿主组件链路的关联验证。全部资源操作的逐项接入不是本期完成条件。
技术负责人已授权按计划落地;以下为实施里程碑边界。合同与本地队列、会话窗口与项目接入、策划阶段推进成果事件、首次提交、两类 Agent run、Direct 宿主文件/补丁成果、checkpoint 与 UI 保存,以及正式 Web 预览 ready 均已定向验收,并完成下述同一宿主组件链路的关联验证。全部资源操作的逐项接入不是本期完成条件。
| 里程碑 | 范围 | 退出判据 |
| --- | --- | --- |
@@ -501,7 +501,7 @@ session.json 是本地恢复元数据,不是待上传事件;事件文件不
已做:原产品方案逐项对照;核对现役 Direct/Design 持久化、项目创建、revision、预览、UI 保存与 checkpoint 的源码入口。本文新增合同和参数均以草案标识,不作为已经上线的事实。
合同与本地队列、会话窗口与项目接入、策划阶段推进成果事件、首次提交、两类 Agent run、Direct 宿主文件/补丁成果、GUI 人工编辑、checkpoint 与 UI 保存,以及正式 Web 预览 ready 已完成实现、独立审查和定向验收。最终按原始需求和已确认口径核对全部 12 类事件入口,并补齐同一 writer、会话、项目和目标的宿主组件集成链路验证,本期本地采集验收通过。资源操作全面接线计划因超出原始需求撤销,未进入资源实现,不作为必做待办保留。本期无主站 API 或数据库行为;验收不表示已经提交、发布或上线。
合同与本地队列、会话窗口与项目接入、策划阶段推进成果事件、首次提交、两类 Agent run、Direct 宿主文件/补丁成果、checkpoint 与 UI 保存,以及正式 Web 预览 ready 已完成实现、独立审查和定向验收。最终按原始需求和已确认口径核对全部 12 类事件入口,并补齐同一 writer、会话、项目和目标的宿主组件集成链路验证,本期本地采集验收通过。资源操作全面接线计划因超出原始需求撤销,未进入资源实现,不作为必做待办保留。本期无主站 API 或数据库行为;验收不表示已经提交、发布或上线。
入口核查以正式 Direct/Design GUI 可达链路为准。`src/main.tsx` 的旧 agent-chat/supervisor-chat 入口受 DEV 限制,正式 Direct 项目跳过旧 Runtime 恢复;旧 Runtime 的 file_ops/project_ops/ui.workflow 仍由开发入口和 CLI 使用,但不因这些函数存在就扩张本期正式 GUI 采集范围。通用画布导入/同步也需核实正式面板可达性,不能仅凭 App 内保留命令分支认定已经接入或必须接入。
@@ -529,7 +529,7 @@ session.json 是本地恢复元数据,不是待上传事件;事件文件不
| Agent 运行结果生产入口与验收 | 最新 `cargo +stable check --locked --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --bin genarrative-ai-game-creator-shell`;fmt、编码、索引、差异检查;独立条款验收 | 全部通过;生产 check 有既存告警,无编译错误。未新增上传、加密、网络请求或业务埋点 I/O 等待;运行结果临时计划已融合删除 |
| Direct 宿主文件与补丁成果、原身份及 run 成果关联 | `cargo +stable test ... analytics -- --test-threads=1` 43 项;同正式测试可执行文件 `agent::direct_patch::tests::` 6 项、`bridge_write_file` 4 项、`queued_write_rechecks_the_original_lease_after_project_lock_release` 1 项、`agent::direct_execution::tests::` 23 项 | 去重 76 项全部通过。真实写许可/文件/JSONL验证新内容记成果、同内容不记、原用户 A 归属;真实 bundled patch 两文件成功仅一条/count=2,随后部分失败不覆盖成果。失败 run 的关联为 writer 集成测试加生产接线审查,未宣称完整 Provider 失败流程;两个成功版本取最新经代码审查 |
| Direct 成果生产编译、故障隔离及条款验收 | 最新生产 `cargo +stable check`、fmt/编码/索引/diff及独立代码与条款审查 | 全部通过;读取未知/队列拒绝不改变工具结果,不新增业务版本或同步埋点写盘。测试夹具已补真实合同与 config 父目录;Windows patch smoke 将既有 `target/debug/coding-agent` 完整复制至测试 exe 相邻的 `target/debug/deps/coding-agent`,使用校验通过的 0.155.1 执行器,未放宽版本校验或更改全局 Codex。成果采集临时计划已融合删除 |
| GUI 人工编辑、checkpoint 与原生 UI 成果 | 最新 `cargo +stable test ... analytics -- --test-threads=1` 45 项;同正式测试二进制 UI persistence 14 项、bridge_write_file 4 项 | 合计 63 项通过。新真实文件/writer 测试覆盖变化写入、同内容保存、删除与失败、完整及失败 checkpoint、UI Saved/Unchanged/Conflict/错误和原用户归属。checkpoint 失败夹具使用真实项目中被普通文件占用的检查点目录,不把业务允许初始化的缺失目录当作失败 |
| checkpoint 与原生 UI 成果 | 最新 `cargo +stable test ... analytics -- --test-threads=1` 45 项;同正式测试二进制 UI persistence 14 项、bridge_write_file 4 项 | 此前合计 63 项通过;其中已退役的 GUI 文件写入/删除测试不再作为现役入口证据。当前保留完整及失败 checkpoint、UI Saved/Unchanged/Conflict/错误和原用户归属验证。checkpoint 失败夹具使用真实项目中被普通文件占用的检查点目录,不把业务允许初始化的缺失目录当作失败 |
| UI 逻辑保存及故障隔离 | 前端 clientAnalytics/uiEditorPage 两文件 41 项、shell `tsc --noEmit`,生产 `cargo +stable check`、fmt/编码/索引/diff,独立代码审查及条款验收 | 全部通过;普通手动保存、自动保存、组合生成成功/失败、重复回调、桥接失败与冻结身份已验证。保存不等待埋点;没有更改业务 DTO 或上传/加密。临时计划已融合删除;未执行 GUI 交互或前端到原生 UI 保存命令的完整跨层 smoke |
| 正式 Web 预览实际 ready、实例寿命与身份 | 最新正式测试二进制 analytics 51 项(含新增六项真实 loopback/JSONL 测试)、既有本地预览及 registry/lifecycle 21 项、Direct validation 6 项 | 合计 78 项通过。覆盖真实 GUI helper 自动探测、user/agent 来源、同版本新实例、原用户、空/缺失入口、HTTP失败/空响应、请求期间版本漂移、停止/替换/取消。一个旧 Runtime 模拟模型测试在并发编译时触发原有 2 秒等待超时,编译结束后单独 exact 复跑通过,未修改断言 |
| 预览前端来源、生产编译及条款验收 | 合并 master 后 `appSurface.test.ts --threads=false -t 'preview\|预览\|play request\|run local\|local game'` 104 项、shell tsc、生产 `cargo +stable check`、fmt/编码/索引/diff及独立验收 | 全部通过;旧草稿自动预览不传 user,不据任意外部 URL 推断项目成果。Direct 原 run capture 与临时 guard 经独立接线审查;实测 host HTTP 可访问性不等价于完整 Chrome 双端验证。无上传、外部探测或新增业务版本;临时计划已融合删除 |