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.
60 lines
4.6 KiB
JavaScript
60 lines
4.6 KiB
JavaScript
import fs from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import { execFileSync } from 'node:child_process';
|
|
import assert from 'node:assert/strict';
|
|
import { NativeCanvas } from '../src/native-bridge.mjs';
|
|
import { atomic } from '../src/store.mjs';
|
|
|
|
const output = path.resolve(process.argv[2]);
|
|
const label = process.argv[3] || 'initial';
|
|
if (!/^[a-z0-9-]{1,30}$/.test(label)) throw Error('验收阶段编号无效');
|
|
const nonce = label === 'initial' ? 'RECOVERY-1022-ONLY-ONCE' : `RECOVERY-1022-${label.toUpperCase()}`;
|
|
if (label === 'initial' && await fs.access(path.join(output,'interrupt-attempt.json')).then(()=>true,()=>false)) throw Error('初次验收已有现场,不能重复提交');
|
|
const native = JSON.parse(await fs.readFile(path.join(output, 'native-process.json'), 'utf8'));
|
|
const accounts = path.join(native.profile, 'jiansun/accounts');
|
|
const scopes = await fs.readdir(accounts); assert.equal(scopes.length,1);
|
|
const ids = await fs.readdir(path.join(accounts,scopes[0],'projects')); assert.equal(ids.length,1);
|
|
const requests = path.join(accounts, scopes[0], 'pi-validation', ids[0], 'requests');
|
|
const initialIds = new Set(await fs.readdir(requests));
|
|
const canvas = await NativeCanvas.connect({ port:native.port, tokenFile:path.join(output,'control.token') });
|
|
const records = async () => Promise.all((await fs.readdir(requests)).filter(n=>!initialIds.has(n)&&n.endsWith('.json')).map(async name=>({name,...JSON.parse(await fs.readFile(path.join(requests,name),'utf8'))})));
|
|
try {
|
|
const state = await canvas.call('jiansun.state');
|
|
assert.ok(!state.busy && !state.unknown && !state.queued);
|
|
await fs.writeFile(path.join(output,`interrupt-${label}-attempt.json`),JSON.stringify({at:new Date().toISOString(),nativePid:native.pid,kind:'controlled-local-host-interruption-after-original-response-is-durable',notRealUpstreamOutage:true}),{flag:'wx'});
|
|
const submitted = await canvas.action('agent',{text:`本轮只验证客户端中断恢复(${label})。不要使用任何工具、不要保存新事实、不要修改画布;只回复 ${nonce}。`});
|
|
assert.equal(submitted.submitted,true);
|
|
let pid, interrupted, record;
|
|
const deadline=Date.now()+180000;
|
|
while(Date.now()<deadline){
|
|
if(!pid){
|
|
const command=`Get-CimInstance Win32_Process -Filter "Name='node.exe'" | Where-Object { $_.ParentProcessId -eq ${native.pid} -and $_.CommandLine -like '*desktop-host.mjs*' } | Select-Object -ExpandProperty ProcessId | ConvertTo-Json -Compress`;
|
|
const value=execFileSync('powershell.exe',['-NoProfile','-Command',command],{encoding:'utf8',windowsHide:true}).trim();
|
|
if(value){const p=JSON.parse(value);assert.ok(Number.isInteger(p));pid=p;}
|
|
}
|
|
const found=await records();
|
|
if(!found.length){const status=await canvas.call('jiansun.state');if(!status.busy&&status.error)throw Error(`模型尚未派发,预检失败:${status.error}`);}
|
|
record=found.find(r=>r.phase==='received' && r.terminal===true && r.raw);
|
|
if(pid && record){process.kill(pid);interrupted=true;break;}
|
|
if(found.some(r=>r.phase==='unknown'))throw Error('原请求未知,停止;不得重新提交');
|
|
if(found.some(r=>r.phase==='complete'))throw Error('未捕捉到安全中断窗口,原请求已完成;不重发');
|
|
await new Promise(r=>setTimeout(r,15));
|
|
}
|
|
assert.equal(interrupted,true);
|
|
let pending;
|
|
for(let i=0;i<100;i++){pending=await canvas.call('jiansun.state');if(pending.unknown&&!pending.busy)break;await new Promise(r=>setTimeout(r,150));}
|
|
assert.equal(pending.unknown,true);
|
|
assert.equal((await records()).length,1);
|
|
await canvas.call('ui.screenshot',{path:'interrupt-pending.png',focus:false});
|
|
// The normal recovery button uses retained local bytes. It never calls POST again.
|
|
await canvas.action('recover');
|
|
let recovered;
|
|
for(let i=0;i<180;i++){recovered=await canvas.call('jiansun.state');if(!recovered.busy&&!recovered.unknown)break;await new Promise(r=>setTimeout(r,200));}
|
|
assert.equal(recovered.unknown,false); assert.ok(!recovered.busy);
|
|
const final=await records();assert.equal(final.length,1);assert.equal(final[0].id,record.id);assert.equal(final[0].phase,'complete');
|
|
assert.ok(recovered.messages.some(m=>m.role==='assistant'&&m.text?.includes(nonce)));
|
|
await canvas.call('ui.screenshot',{path:'interrupt-restored.png',focus:false});
|
|
const result={passed:true,controlledLocalInterruption:true,upstreamOutageSimulated:false,originalId:record.id,requestId:record.requestId,originalPosts:1,recoveryPosts:0,automaticCanvasWrites:0,restoredOriginalReply:true,phase:final[0].phase};
|
|
await atomic(path.join(output,'interrupt-recovery-passed.json'),result);console.log(JSON.stringify(result));
|
|
}finally{canvas.close();}
|