人格脑默认留在用户本地,远程服务仅显性选择

This commit is contained in:
铸渊 ICE-P-ZY001 2026-10-19 21:50:03 +08:00
commit a12ebd1bc0
13 changed files with 136 additions and 60 deletions

View file

@ -10,4 +10,4 @@ man={'schema':'guanghu.public-rule-file-manifest/v1','world_id':'GLW-2.0-0001','
with zipfile.ZipFile(p/'release.zip','w',compression=zipfile.ZIP_DEFLATED) as z:
for n in [x['path'] for x in files]+['MANIFEST.json']:
info=zipfile.ZipInfo('guanghu-world-entry/'+n,date_time=(2026,10,19,0,0,0));info.external_attr=(stat.S_IFREG|0o644)<<16;info.compress_type=zipfile.ZIP_DEFLATED;z.writestr(info,(p/n).read_bytes())
j={**man,'schema':'guanghu.public-rule-release/v1','bundle_url':'https://guanghulab.com/api/ai/v1/rule-bundle','bundle_sha256':sha((p/'release.zip').read_bytes()),'manifest_sha256':sha((p/'MANIFEST.json').read_bytes()),'repository':'https://guanghulab.com/code/bingshuo/guanghu-world-entry','private_brain_included':False,'complete_four_domain_corpus_included':False,'default_persona':None};(p/'CURRENT.json').write_text(json.dumps(j,ensure_ascii=False,indent=2)+'\n');print(json.dumps({'files':len(files),'bundle_bytes':(p/'release.zip').stat().st_size,'bundle_sha256':j['bundle_sha256']}))
j={**man,'schema':'guanghu.public-rule-release/v1','bundle_url':'https://guanghulab.com/api/ai/v1/rule-bundle','bundle_sha256':sha((p/'release.zip').read_bytes()),'manifest_sha256':sha((p/'MANIFEST.json').read_bytes()),'repository':'https://guanghulab.com/code/bingshuo/guanghu-world-entry','private_brain_included':False,'complete_four_domain_corpus_included':False,'default_persona':None,'deployment_policy':json.loads((p/'entry-data.json').read_text())['deployment_policy']};(p/'CURRENT.json').write_text(json.dumps(j,ensure_ascii=False,indent=2)+'\n');print(json.dumps({'files':len(files),'bundle_bytes':(p/'release.zip').stat().st_size,'bundle_sha256':j['bundle_sha256']}))

View file

@ -1,16 +1,21 @@
#!/usr/bin/env node
// HOST_NEUTRAL_TRANSPORT: verifies released files, reads status or asks with independent user credentials.
import {spawnSync} from 'node:child_process';import fs from 'node:fs';import path from 'node:path';import crypto from 'node:crypto';import {fileURLToPath} from 'node:url';
import {questionTarget} from './question-target.mjs';
const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..'),sha=x=>crypto.createHash('sha256').update(x).digest('hex'),manifest=JSON.parse(fs.readFileSync(path.join(root,'MANIFEST.json')));
const fail=c=>{throw Error(c)};
for(const x of manifest.files){if(path.isAbsolute(x.path)||x.path.split('/').includes('..'))fail('RELEASE_PATH_INVALID');const p=path.join(root,x.path);if(fs.realpathSync(p)!==p||!fs.statSync(p).isFile()||sha(fs.readFileSync(p))!==x.sha256)fail('RELEASE_FILE_HASH_MISMATCH');}
const args=process.argv.slice(2),cmd=args.shift()||'status',flag=k=>{const n=args.indexOf(k);if(n<0)return null;const v=args[n+1];if(!v)fail('ARGUMENT_REQUIRED');args.splice(n,2);return v;};
try{
if(cmd==='verify'){console.log(JSON.stringify({state:'PUBLIC_RELEASE_FILES_VERIFIED',version:manifest.version,files:manifest.files.length}));}
else if(cmd==='status'){const r=await fetch('https://guanghulab.com/api/ai/v1/status',{signal:AbortSignal.timeout(10000)});console.log(JSON.stringify(await r.json(),null,2));if(!r.ok)process.exitCode=2;}
else if(cmd==='rules-status'){if(args.length)fail('ARGUMENT_NOT_SUPPORTED');const r=await fetch('https://guanghulab.com/api/ai/v1/status',{redirect:'error',signal:AbortSignal.timeout(10000)});console.log(JSON.stringify(await r.json(),null,2));if(!r.ok)process.exitCode=2;}
else if(cmd==='status'){const localUrl=flag('--local-url'),remoteUrl=flag('--remote-url');if(args.length)fail('ARGUMENT_NOT_SUPPORTED');const t=questionTarget({localUrl:localUrl||(!remoteUrl?process.env.GUANGHU_LOCAL_ASK_URL:null),remoteUrl});const u=new URL(t.url);u.pathname=u.pathname.slice(0,-3)+'status';const r=await fetch(u,{redirect:'error',signal:AbortSignal.timeout(10000)});console.log(JSON.stringify(await r.json(),null,2));if(!r.ok)process.exitCode=2;}
else if(cmd==='ask'){
const persona=flag('--persona'),host=flag('--host'),session=flag('--session'),file=flag('--question-file');if(args.length||!persona||!host||!session||!file)fail('EXPLICIT_PERSONA_HOST_SESSION_AND_QUESTION_REQUIRED');
let token=process.env.GUANGHU_ASK_TOKEN;if(!token&&process.platform==='darwin'){const r=spawnSync('/usr/bin/security',['find-generic-password','-s','guanghu-persona-question','-a','owner','-w'],{encoding:'utf8',timeout:3000});if(r.status===0)token=r.stdout.trim();}if(!token)fail('PRIVATE_QUESTION_CREDENTIAL_REQUIRED');const question=fs.readFileSync(file,'utf8');const body={persona_id:persona,tool_host:host,session_id:session,request_id:crypto.randomUUID(),question};
const r=await fetch('https://guanghulab.com/api/ai/v1/ask',{method:'POST',headers:{'content-type':'application/json',authorization:'Bearer '+token},body:JSON.stringify(body),signal:AbortSignal.timeout(55000)});console.log(JSON.stringify(await r.json(),null,2));if(!r.ok)process.exitCode=2;
const persona=flag('--persona'),host=flag('--host'),session=flag('--session'),file=flag('--question-file');if(!persona||!host||!session||!file)fail('EXPLICIT_PERSONA_HOST_SESSION_AND_QUESTION_REQUIRED');
const localUrl=flag('--local-url'),remoteUrl=flag('--remote-url');if(args.length)fail('ARGUMENT_NOT_SUPPORTED');const target=questionTarget({localUrl:localUrl||(!remoteUrl?process.env.GUANGHU_LOCAL_ASK_URL:null),remoteUrl});
let token=target.remote?process.env.GUANGHU_ASK_TOKEN:process.env.GUANGHU_LOCAL_ASK_TOKEN;
if(!token&&target.remote&&target.url==='https://guanghulab.com/api/ai/v1/ask'&&process.platform==='darwin'){const r=spawnSync('/usr/bin/security',['find-generic-password','-s','guanghu-persona-question','-a','owner','-w'],{encoding:'utf8',timeout:3000});if(r.status===0)token=r.stdout.trim();}
if(target.remote&&!token)fail('PRIVATE_QUESTION_CREDENTIAL_REQUIRED');const question=fs.readFileSync(file,'utf8');const body={persona_id:persona,tool_host:host,session_id:session,request_id:crypto.randomUUID(),question};
const r=await fetch(target.url,{method:'POST',redirect:'error',headers:{'content-type':'application/json',...(token?{authorization:'Bearer '+token}:{})},body:JSON.stringify(body),signal:AbortSignal.timeout(55000)});const reply=await r.json();if(r.ok&&reply.persona_id!==persona)fail('PERSONA_REPLY_IDENTITY_MISMATCH');console.log(JSON.stringify(reply,null,2));if(!r.ok)process.exitCode=2;
}else fail('COMMAND_NOT_SUPPORTED');
}catch(e){console.log(JSON.stringify({state:'QUESTION_OR_RELEASE_UNAVAILABLE',code:/^[A-Z_]+$/.test(e.message)?e.message:'TRANSPORT_FAILED',no_execution_authority:true}));process.exitCode=2;}

View file

@ -0,0 +1,11 @@
// Mechanical transport policy: private persona services are local by default.
export function questionTarget({localUrl,remoteUrl}={}) {
if(localUrl&&remoteUrl)throw Error('QUESTION_TARGET_CONFLICT');
if(!localUrl&&!remoteUrl)throw Error('LOCAL_PERSONA_SERVICE_NOT_CONFIGURED');
let u;try{u=new URL(remoteUrl||localUrl);}catch{throw Error('QUESTION_TARGET_INVALID');}
if(u.username||u.password||u.hash||u.search||!u.pathname.endsWith('/v1/ask'))throw Error('QUESTION_TARGET_INVALID');
const loopback=['127.0.0.1','[::1]'].includes(u.hostname);
if(remoteUrl){if(u.protocol!=='https:'||loopback)throw Error('EXPLICIT_REMOTE_HTTPS_REQUIRED');}
else if(!loopback||!['http:','https:'].includes(u.protocol))throw Error('LOCAL_LOOPBACK_REQUIRED');
return {url:u.href,remote:!!remoteUrl,origin:u.origin};
}