gongxiangp2/apps/detail-agent-pi/scripts/summarize-client-validation.mjs
WuYongJie 1b6425b552 feat(desktop): connect native Pi sessions with scoped rules and recovery
Track the fixed PhotoCraft source baseline with the existing native client integration. Use the actual Pi SDK for normal sends, durable session recovery, demand-loaded enterprise rules and checked undoable canvas tools. Retain request identities and never resend unknown inference. Include local packaging and real-call acceptance evidence.
2026-10-22 10:49:58 +08:00

51 lines
4.0 KiB
JavaScript

import fs from 'node:fs/promises';
import path from 'node:path';
import assert from 'node:assert/strict';
import { atomic, digest } from '../src/store.mjs';
import { responseUsage } from '../src/usage.mjs';
import { auditResponse } from '../src/response-audit.mjs';
// Read-only evidence roll-up: this script cannot dispatch any model request.
const output = path.resolve(process.argv[2]);
const native = JSON.parse(await fs.readFile(path.join(output, 'native-process.json'), 'utf8'));
const scopes = await fs.readdir(path.join(native.profile, 'jiansun/accounts')); assert.equal(scopes.length, 1);
const account = path.join(native.profile, 'jiansun/accounts', scopes[0]);
const projects = await fs.readdir(path.join(account, 'projects')); assert.equal(projects.length, 1);
const project = projects[0], storeRoot = path.join(account, 'projects', project, 'agent', scopes[0], project);
const current = JSON.parse(await fs.readFile(path.join(storeRoot, 'current.json'), 'utf8'));
const store = path.join(storeRoot, current.bindingHash);
const events = (await fs.readFile(path.join(store, 'events.jsonl'), 'utf8')).trim().split('\n').map(JSON.parse);
const requestDir = path.join(account, 'pi-validation', project, 'requests'), records = [];
for (const file of await fs.readdir(requestDir)) {
const receipt = JSON.parse(await fs.readFile(path.join(requestDir, file), 'utf8'));
assert.equal(receipt.phase, 'complete'); assert.equal(receipt.terminal, true);
const payload = JSON.parse(await fs.readFile(path.join(store, 'payloads', receipt.id + '.json'), 'utf8'));
assert.equal(digest(payload), receipt.bodyHash);
const raw = Buffer.from(receipt.raw, 'base64').toString('utf8');
const usage = responseUsage(raw), audit = auditResponse(raw, receipt.model.protocol);
records.push({ id: receipt.id, requestId: receipt.requestId, model: receipt.model.id, bodyHashMatches: true, phase: receipt.phase,
...usage, requestChars: JSON.stringify(payload).length, toolNames: audit.calls.map(c => c.name) });
}
const known = records.filter(r => r.inputTokens !== null && r.cachedTokens !== null);
const total = known.reduce((v, r) => ({ input: v.input + r.inputTokens, cache: v.cache + r.cachedTokens }), { input: 0, cache: 0 });
const seed = JSON.parse(await fs.readFile(path.join(output, 'long-prepare-passed.json'), 'utf8'));
const phases = {};
for (const phase of ['correction', 'outline', 'canvas', 'long-recall', 'restart-recall', 'handover', 'tool-live']) {
const result = JSON.parse(await fs.readFile(path.join(output, `${phase}-passed.json`), 'utf8'));
assert.notEqual(result.passed, false); phases[phase] = { passed: true };
}
const recovery = JSON.parse(await fs.readFile(path.join(output, 'interrupt-recovery-passed.json'), 'utf8'));
assert.equal(recovery.recoveryPosts, 0);
const summary = { at: new Date().toISOString(), model: 'grok-4.7', normalClientSendUsesPi: true, realUserInstructionsWithGeneration: 9,
preflightFailureWithoutModelPost: 1, generationRequests: records.length, completedRequests: records.length, unknownNewRequests: 0,
nativeRequestBodyHashMatches: true, constructedHistoryMessages: seed.constructedHistoryMessages,
automaticCompactions: events.filter(e => e.type === 'automatic_compaction_completed').length,
actualGuiRestart: true, phases, recovery, bindingHash: current.bindingHash,
maxInputTokens: Math.max(...records.map(r => r.inputTokens || 0)),
maxProjectedInputChars: Math.max(...events.filter(e => e.type === 'context_budget').map(e => e.inputChars)),
cache: { source: '98k-returned-usage', requestsWithMetrics: known.length, requestsWithoutMetrics: records.length - known.length,
totalInputTokens: total.input, cachedTokens: total.cache, weightedRate: total.input ? total.cache / total.input : null,
minRequestRate: Math.min(...known.map(r => r.cacheRate)), maxRequestRate: Math.max(...known.map(r => r.cacheRate)), billingVerified: false }, records };
await atomic(path.join(output, 'final-client-summary.json'), summary);
console.log(JSON.stringify({ ...summary, records: undefined }));