0270cd601e
- analytics/run.rs:删 Request::DirectCandidate / Request::Settle 与 settle();direct_finished 改收放行代次,终态再读一次 identity_generation,不同则整条不记,相同则直接落 Request::Terminal - analytics/store.rs:删 pending_runs / pending_run_bytes 与两条候选分支(连带 16 条上限),run_request 不再需要字节预算参数 - analytics/gui.rs + main.rs:删 settle_direct_run_analytics 命令与注册 - 放行侧:kick_queue_dispatch 在读 claim 之后读一次平台会话代次并随整轮传下去;user_input 命令不再收 analyticsAttemptId,队列条目不再持有埋点句柄 - 渲染层:删 beginDirectRunAnalytics、句柄表与结算 effect、settle 调用与命令参数;删除 tests/directRunAnalytics.test.ts,appSurface 两处断言同步 - 判据口径随放行搬家:从“发送时与终态同代”改成“放行时与终态同代”,入队后放行前的账号切换不再丢弃成绩 - 验证:cargo test -- analytics::(45 passed,含新增的直写与代际变化两条)、cargo test -- agent::(949 passed)、analytics_real_file_write 用例、npx vitest run tests/appSurface.test.ts(194 passed)、相关 chat/direct 单测 139 passed、typecheck 通过
101 lines
2.7 KiB
TypeScript
101 lines
2.7 KiB
TypeScript
import type { TauriInvoke } from '../app/types';
|
|
|
|
// 宿主签发的无凭据上下文:前端只传回原值,不从当前登录态补身份。
|
|
type AnalyticsContext = {
|
|
route: { user_id: string | null; destination_origin: string | null };
|
|
editor_session_id: string;
|
|
client_version: string;
|
|
};
|
|
|
|
export type ProjectOpenAnalytics = ReturnType<typeof beginProjectOpenAnalytics>;
|
|
|
|
export function beginProjectOpenAnalytics(
|
|
invoke: TauriInvoke,
|
|
openSource: 'create' | 'picker' | 'recent',
|
|
) {
|
|
try {
|
|
const operationId = crypto.randomUUID();
|
|
const context = invoke<AnalyticsContext | null>(
|
|
'capture_analytics_context',
|
|
).catch(() => null);
|
|
let recorded = false;
|
|
return {
|
|
record(projectPath: string) {
|
|
if (recorded) return;
|
|
recorded = true;
|
|
const eventTime = new Date().toISOString();
|
|
void context
|
|
.then((captured) => {
|
|
if (!captured) return;
|
|
return invoke('record_analytics_project_open', {
|
|
context: captured,
|
|
projectPath,
|
|
operationId,
|
|
openSource,
|
|
eventTime,
|
|
});
|
|
})
|
|
.catch(() => undefined);
|
|
},
|
|
};
|
|
} catch {
|
|
// UUID、桥接与写入失败都不能改变业务操作结果。
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function recordAnalyticsProjectLeave(
|
|
invoke: TauriInvoke,
|
|
projectPath: string,
|
|
) {
|
|
try {
|
|
void invoke('record_analytics_project_leave', { projectPath }).catch(
|
|
() => undefined,
|
|
);
|
|
} catch {
|
|
// 离开项目不能等待或依赖埋点。
|
|
}
|
|
}
|
|
|
|
// 保存开始冻结身份,完成时冻结时间;观察器不能影响保存结果。
|
|
export function beginUiSaveAnalytics(
|
|
invoke: TauriInvoke,
|
|
projectPath: string,
|
|
saveSource: 'manual' | 'auto',
|
|
) {
|
|
try {
|
|
const operationId = crypto.randomUUID();
|
|
const context = invoke<AnalyticsContext | null>(
|
|
'capture_analytics_context',
|
|
).catch(() => null);
|
|
let recorded = false;
|
|
return {
|
|
record(changed: boolean) {
|
|
if (recorded) return;
|
|
recorded = true;
|
|
if (saveSource === 'auto' && !changed) return;
|
|
try {
|
|
const eventTime = new Date().toISOString();
|
|
void context
|
|
.then((captured) => {
|
|
if (!captured) return;
|
|
return invoke('record_analytics_ui_save', {
|
|
context: captured,
|
|
projectPath,
|
|
operationId,
|
|
saveSource,
|
|
changed,
|
|
eventTime,
|
|
});
|
|
})
|
|
.catch(() => undefined);
|
|
} catch {
|
|
// 时间或桥接失败仅丢弃本条埋点。
|
|
}
|
|
},
|
|
};
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|