gongxiangp2/apps/detail-agent-pi/scripts/summarize-live-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

69 lines
5.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import fs from 'node:fs/promises';
import path from 'node:path';
import assert from 'node:assert/strict';
import { createHash } from 'node:crypto';
import { atomic, digest } from '../src/store.mjs';
import { auditResponse } from '../src/response-audit.mjs';
const root = path.resolve('output/pi-compatibility-20261021');
const model = JSON.parse(await fs.readFile(path.join(root, 'public-docs/grok-4.7.json'), 'utf8'))[0];
const inventory = new Set(model.invocation.supported_fields.map(f => f.name));
const configs = ['native-default-probe', 'long-context'];
const records = [], stages = [], transcript = ['# 本地Pi真实对话验收', '', '产品信息为明确标记的虚构验收资料;店铺、品类及规则来自真实素材库目录和企业知识库。另有80条构造历史未列入下文,也未计入真实API对话轮数。', ''];
let originalUnknown;
for (const name of configs) {
const dir = path.join(root, name), config = JSON.parse(await fs.readFile(path.join(dir, 'config.local.json'), 'utf8'));
const result = JSON.parse(await fs.readFile(path.join(dir, name === 'long-context' ? 'accepted-A.json' : 'result.json'), 'utf8'));
assert.notEqual(result.passed, false);
const sessionFile = result.sessionFile || result.result.sessionFile;
const relative = path.relative(config.output, sessionFile), scope = relative.split(path.sep)[0];
assert.match(scope, /^[a-f0-9]{64}$/);
const accountDir = path.join(config.broker.profile, 'jiansun/accounts', scope);
const old = JSON.parse(await fs.readFile(path.join(accountDir, 'pi-validation/4db803c6-9c09-42e7-bfb8-fbf819659210/requests/0447b17b-1c45-4902-a6a5-18357a3e2a3f.json'), 'utf8'));
assert.equal(old.phase, 'unknown');
originalUnknown = { id: old.id, requestId: old.requestId, phase: old.phase, createdAt: old.createdAt, recordSha256: digest(old), reposted: false };
const requestDir = path.join(accountDir, 'pi-validation', config.broker.project, 'requests');
const storeDir = path.dirname(path.dirname(sessionFile));
for (const file of await fs.readdir(requestDir)) {
const r = JSON.parse(await fs.readFile(path.join(requestDir, file), 'utf8'));
assert.equal(r.phase, 'complete'); assert.equal(r.terminal, true);
assert.notEqual(r.id, old.id); assert.notEqual(r.bodyHash, old.bodyHash);
const body = JSON.parse(await fs.readFile(path.join(storeDir, 'payloads', r.id + '.json'), 'utf8'));
assert.equal(createHash('sha256').update(JSON.stringify(body)).digest('hex'), r.bodyHash);
const unsupported = Object.keys(body).filter(k => !inventory.has(k));
assert.deepEqual(unsupported, []);
assert.deepEqual(Object.fromEntries(r.invocationHeaders || []), model.invocation.headers);
const raw = Buffer.from(r.raw, 'base64').toString('utf8'), audit = auditResponse(raw, r.model.protocol);
let usage;
for (const line of raw.split(/\r?\n/)) if (line.startsWith('data:')) { try { const v = JSON.parse(line.slice(5)); usage = v.usage || usage; } catch { /* SSE terminal. */ } }
records.push({ task: name, id: r.id, customerRequestId: r.requestId, phase: r.phase, bodyHashMatches: true, unsupportedFields: unsupported,
requestChars: JSON.stringify(body).length, requestBytes: Buffer.byteLength(JSON.stringify(body)), usage,
toolNames: audit.calls.map(c => c.name), finish: audit.finishes, nativeFunctionCalls: audit.calls.length, literalEos: audit.literalEos });
}
}
for (const phase of ['A', 'B', 'C']) {
const stage = JSON.parse(await fs.readFile(path.join(root, 'long-context', `accepted-${phase}.json`), 'utf8'));
stages.push({ phase, processId: stage.processId, realApiDialogueTurns: stage.realUserTurns, modelSteps: stage.turns.reduce((n, t) => n + t.rounds, 0), constructedHistoryMessages: stage.seededMessages,
compaction: Boolean(stage.compaction), tokensBeforeCompaction: stage.compaction?.tokensBefore, sessionFile: stage.sessionFile });
transcript.push(`## 阶段${phase} · 进程${stage.processId}`, '');
// A turn record stores the final response in text; the exact prompt stays in its attempt file.
for (const turn of stage.turns) {
const attempt = JSON.parse(await fs.readFile(path.join(root, 'long-context', `${phase}-${turn.id}-attempt.json`), 'utf8'));
transcript.push(`**用户 ${phase}-${turn.id}**:${attempt.text}`, '', `**Agent**:${turn.text}`, '');
}
if (stage.compaction) transcript.push('本阶段结束后已压缩会话,下一阶段由新进程读取同一会话文件。', '');
}
assert.equal(new Set(records.map(r => r.id)).size, records.length);
assert.equal(new Set(stages.map(s => s.processId)).size, 3);
assert.equal(new Set(stages.map(s => s.sessionFile)).size, 1);
const report = { at: new Date().toISOString(), model: model.id, protocol: 'native-tools', newGenerationRequests: records.length,
requestsWithNativeFunctionCall: records.filter(r => r.nativeFunctionCalls).length, newUnknownRequests: 0, originalUnknown,
realApiDialogueTurns: stages.reduce((n, s) => n + s.realApiDialogueTurns, 0), constructedHistoryMessages: stages.reduce((n, s) => n + s.constructedHistoryMessages, 0),
compactions: stages.filter(s => s.compaction).length, crossProcessRestarts: 2, maxPromptTokens: Math.max(...records.map(r => r.usage?.prompt_tokens || 0)),
contractSource: 'https://api.vip98k.com/api/v2/catalog/models', requestBodiesMatchNativeHash: true, publishedFieldsOnly: true, stages, records };
await atomic(path.join(root, 'final-live-summary.json'), report);
await fs.writeFile(path.join(root, 'long-context-transcript.md'), transcript.join('\n'));
console.log(JSON.stringify({ model: report.model, generationRequests: records.length, nativeFunctionRequests: report.requestsWithNativeFunctionCall, realDialogueTurns: report.realApiDialogueTurns,
constructedHistoryMessages: report.constructedHistoryMessages, compactions: report.compactions, restarts: report.crossProcessRestarts, maxPromptTokens: report.maxPromptTokens,
newUnknownRequests: 0, originalUnknownStillHeld: true, requestBodiesMatchNativeHash: true, publishedFieldsOnly: true }));