Import verified AzerothAI snapshot from GitHub
Core tests / core (push) Successful in 9m57s
Full verification / application (push) Failing after 5m16s
Full verification / local-brain (push) Failing after 6m15s
Realm simulation / simulated-live-world (push) Failing after 5m32s
WoW integration lab / real-worldserver (push) Canceled after 17m23s

Source branch: feature/ai-brain-router
Source commit: c2a09e0460465201a58aaab56c71424e9a96405e
This commit is contained in:
Richard Clark
2026-09-22 13:12:54 -04:00
commit 5f0fc86cf9
175 changed files with 25807 additions and 0 deletions
+89
View File
@@ -0,0 +1,89 @@
import http from 'node:http';
import {appendFileSync,writeFileSync} from 'node:fs';
const port=Number(process.env.PORT||3000);
const token=process.env.ALE_BRIDGE_TOKEN||'ci-bridge-token';
const file=process.env.CAPTURE_FILE||'/tmp/ale-capture.jsonl';
let probeSent=false;
writeFileSync(file,'');
function log(kind,payload){
appendFileSync(file,JSON.stringify({at:new Date().toISOString(),kind,payload})+'\n');
}
function authorized(req){
return req.headers.authorization===`Bearer ${token}`;
}
function read(req){
return new Promise((resolve,reject)=>{
let body='';
req.setEncoding('utf8');
req.on('data',c=>{body+=c;if(body.length>100000){reject(new Error('too large'));req.destroy();}});
req.on('end',()=>resolve(body));
req.on('error',reject);
});
}
const probe=`local __command_id="ci-probe"
local function __stringify(v)
if v==nil then return "nil" end
return tostring(v)
end
local __ok,__result=pcall(function()
local core=tostring(GetCoreName())
local version=tostring(GetCoreVersion())
local lua=tostring(GetLuaEngine())
local players=tostring(GetPlayerCount())
local pb="unknown"
if type(GetRandomPlayerbotMgr)=="function" then
local ok,mgr=pcall(GetRandomPlayerbotMgr)
pb=ok and (mgr and "available" or "nil") or "error"
end
return "core="..core.."|version="..version.."|lua="..lua.."|players="..players.."|playerbots="..pb
end)
AzerothAIComplete(__command_id,__ok,__stringify(__result))
`;
const server=http.createServer(async(req,res)=>{
try{
if(req.url==='/health'){
res.writeHead(200,{'content-type':'application/json'});
return res.end(JSON.stringify({ok:true,probeSent}));
}
if(!authorized(req)){
res.writeHead(401);
return res.end('unauthorized');
}
if(req.method==='GET'&&req.url==='/api/bridge/ale/poll'){
log('poll',{probeSent});
if(!probeSent){
probeSent=true;
res.writeHead(200,{'content-type':'text/plain; charset=utf-8','x-azerothai-command':'ci-probe'});
return res.end(probe);
}
res.writeHead(204);
return res.end();
}
if(req.method==='POST'&&req.url==='/api/bridge/ale/event'){
const body=await read(req);
let parsed;try{parsed=JSON.parse(body);}catch{parsed={raw:body};}
log('event',parsed);
res.writeHead(200,{'content-type':'application/json'});
return res.end('{"ok":true}');
}
if(req.method==='POST'&&req.url==='/api/bridge/ale/result'){
const body=await read(req);
let parsed;try{parsed=JSON.parse(body);}catch{parsed={raw:body};}
log('result',parsed);
res.writeHead(200,{'content-type':'application/json'});
return res.end('{"ok":true}');
}
res.writeHead(404);res.end('not found');
}catch(e){
log('server-error',{message:e instanceof Error?e.message:String(e)});
res.writeHead(500);res.end('error');
}
});
server.listen(port,'0.0.0.0',()=>console.log(`ALE capture server listening on ${port}`));
+165
View File
@@ -0,0 +1,165 @@
import {execFileSync} from 'node:child_process';
const BASE=(process.env.AZEROTHAI_URL||'http://127.0.0.1:8787').replace(/\/+$/,'');
const TOKEN=process.env.ALE_BRIDGE_TOKEN||'';
const DB_PASSWORD=process.env.DOCKER_DB_ROOT_PASSWORD||'';
const DB_CONTAINER=process.env.AZEROTHAI_DB_CONTAINER||'ac-database';
const POLL_MS=Math.max(100,Number(process.env.AZEROTHAI_ALE_RELAY_POLL_MS||250));
const EVENT_BATCH=Math.max(1,Math.min(20,Number(process.env.AZEROTHAI_ALE_EVENT_BATCH||8)));
const EVENT_GAP_MS=Math.max(0,Number(process.env.AZEROTHAI_ALE_EVENT_GAP_MS||75));
if(!TOKEN)throw new Error('ALE_BRIDGE_TOKEN is required');
if(!DB_PASSWORD)throw new Error('DOCKER_DB_ROOT_PASSWORD is required');
let stopping=false;
let lastCleanup=0;
process.on('SIGINT',()=>{stopping=true;});
process.on('SIGTERM',()=>{stopping=true;});
function sleep(ms){return new Promise(r=>setTimeout(r,ms));}
function sqlText(value){
const hex=Buffer.from(String(value??''),'utf8').toString('hex');
return `CONVERT(UNHEX('${hex}') USING utf8mb4)`;
}
function mysql(sql){
return execFileSync('docker',[
'exec','-e',`MYSQL_PWD=${DB_PASSWORD}`,DB_CONTAINER,
'mysql','-uroot','-N','-B','-e',sql
],{encoding:'utf8',stdio:['ignore','pipe','pipe']});
}
function ensureSchema(){
mysql(`
CREATE TABLE IF NOT EXISTS acore_auth.azerothai_command_inbox (
id VARCHAR(36) NOT NULL PRIMARY KEY,
source MEDIUMTEXT NOT NULL,
created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
consumed_at TIMESTAMP(6) NULL,
KEY idx_azerothai_command_inbox_pending (consumed_at,created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS acore_auth.azerothai_event_outbox (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
kind VARCHAR(100) NOT NULL,
payload MEDIUMTEXT NOT NULL,
created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
delivered_at TIMESTAMP(6) NULL,
KEY idx_azerothai_event_outbox_pending (delivered_at,id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS acore_auth.azerothai_result_outbox (
seq BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
command_id VARCHAR(36) NOT NULL,
ok TINYINT(1) NOT NULL,
result MEDIUMTEXT NOT NULL,
created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
delivered_at TIMESTAMP(6) NULL,
UNIQUE KEY uq_azerothai_result_command (command_id),
KEY idx_azerothai_result_outbox_pending (delivered_at,seq)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`);
}
async function backend(path,init={}){
const headers=new Headers(init.headers||{});
headers.set('Authorization',`Bearer ${TOKEN}`);
headers.set('Accept','application/json');
const controller=new AbortController();
const timer=setTimeout(()=>controller.abort(),5000);
try{
return await fetch(BASE+path,{...init,headers,signal:controller.signal});
}finally{
clearTimeout(timer);
}
}
async function pullCommand(){
const response=await backend('/api/bridge/ale/poll',{
method:'POST',
headers:{'Content-Type':'application/json'},
body:'{}'
});
if(!response.ok)throw new Error(`poll HTTP ${response.status}: ${await response.text()}`);
const id=response.headers.get('x-azerothai-command');
const source=await response.text();
if(!id)return false;
mysql(`INSERT INTO acore_auth.azerothai_command_inbox(id,source)
VALUES (${sqlText(id)},${sqlText(source)})
ON DUPLICATE KEY UPDATE source=VALUES(source)`);
return true;
}
function rows(sql){
const out=mysql(sql).trim();
if(!out)return [];
return out.split('\n').filter(Boolean).map(line=>line.split('\t'));
}
async function deliverEvents(){
const batch=rows(`SELECT id,HEX(kind),HEX(payload)
FROM acore_auth.azerothai_event_outbox
WHERE delivered_at IS NULL
ORDER BY CASE WHEN kind IN ('player.whisper','player.chat') THEN 0 ELSE 1 END, id
LIMIT ${EVENT_BATCH}`);
for(const [id,kindHex,payloadHex] of batch){
const kind=Buffer.from(kindHex||'','hex').toString('utf8');
const payloadText=Buffer.from(payloadHex||'','hex').toString('utf8');
let payload;
try{payload=JSON.parse(payloadText);}catch{
console.error(`Dropping malformed ALE event ${id}: invalid JSON payload`);
mysql(`UPDATE acore_auth.azerothai_event_outbox SET delivered_at=NOW(6) WHERE id=${Number(id)}`);
continue;
}
const response=await backend('/api/bridge/ale/event',{
method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({kind,payload})
});
if(!response.ok)throw new Error(`event HTTP ${response.status}: ${await response.text()}`);
mysql(`UPDATE acore_auth.azerothai_event_outbox SET delivered_at=NOW(6) WHERE id=${Number(id)}`);
if(EVENT_GAP_MS)await sleep(EVENT_GAP_MS);
}
return batch.length;
}
async function deliverResults(){
const batch=rows("SELECT seq,command_id,ok,HEX(result) FROM acore_auth.azerothai_result_outbox WHERE delivered_at IS NULL ORDER BY seq LIMIT 20");
for(const [seq,commandId,ok,resultHex] of batch){
const result=Buffer.from(resultHex||'','hex').toString('utf8').slice(0,16000);
const response=await backend('/api/bridge/ale/result',{
method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({id:commandId,ok:ok==='1',result})
});
if(!response.ok)throw new Error(`result HTTP ${response.status}: ${await response.text()}`);
mysql(`UPDATE acore_auth.azerothai_result_outbox SET delivered_at=NOW(6) WHERE seq=${Number(seq)}`);
}
return batch.length;
}
function cleanup(){
mysql(`
UPDATE acore_auth.azerothai_event_outbox
SET delivered_at=NOW(6)
WHERE delivered_at IS NULL
AND kind IN ('player.combat.enter','player.combat.leave')
AND created_at < NOW() - INTERVAL 2 MINUTE;
DELETE FROM acore_auth.azerothai_command_inbox WHERE consumed_at IS NOT NULL AND consumed_at < NOW() - INTERVAL 1 DAY;
DELETE FROM acore_auth.azerothai_event_outbox WHERE delivered_at IS NOT NULL AND delivered_at < NOW() - INTERVAL 1 DAY;
DELETE FROM acore_auth.azerothai_result_outbox WHERE delivered_at IS NOT NULL AND delivered_at < NOW() - INTERVAL 1 DAY;`);
}
ensureSchema();
console.log(`AzerothAI ALE DB relay started: ${BASE}, poll ${POLL_MS}ms`);
while(!stopping){
try{
await deliverEvents();
await deliverResults();
await pullCommand();
if(Date.now()-lastCleanup>60_000){cleanup();lastCleanup=Date.now();}
await sleep(POLL_MS);
}catch(error){
console.error(new Date().toISOString(),error instanceof Error?error.message:String(error));
await sleep(1000);
}
}
console.log('AzerothAI ALE DB relay stopped.');
+52
View File
@@ -0,0 +1,52 @@
const base=(process.env.AZEROTHAI_URL||'http://127.0.0.1:8787').replace(/\/$/,'');
const token=process.env.AUTONOMY_TOKEN;
const autonomyInterval=Math.max(10_000,Number(process.env.AUTONOMY_POLL_MS||30_000));
const conversationInterval=Math.max(500,Number(process.env.CONVERSATION_POLL_MS||1000));
if(!token){
console.error('AUTONOMY_TOKEN is required');
process.exit(2);
}
let stopping=false;
for(const signal of ['SIGINT','SIGTERM'])process.on(signal,()=>{stopping=true;});
async function post(path,timeout=60_000){
const response=await fetch(`${base}${path}`,{
method:'POST',
headers:{Authorization:`Bearer ${token}`,'Content-Type':'application/json'},
body:'{}',
signal:AbortSignal.timeout(timeout)
});
const raw=await response.text();
if(!response.ok)throw new Error(`${path} HTTP ${response.status}: ${raw.slice(0,500)}`);
try{return JSON.parse(raw);}catch{return {raw};}
}
async function autonomyTick(){
const body=await post('/api/autonomy/tick');
const stamp=new Date().toISOString();
if(body.ran)console.log(`[${stamp}] ${body.name}: ${body.decision?.action} -> command ${body.commandId}`);
else console.log(`[${stamp}] idle: ${body.reason||'no due resident'}`);
}
async function conversationTick(){
const body=await post('/api/conversation/tick');
if(body.ran){
console.log(`[${new Date().toISOString()}] chat: ${body.name} -> ${body.speaker}: ${body.reply} (command ${body.commandId})`);
}
}
console.log(`AzerothAI autonomy worker starting: ${base}, autonomy ${autonomyInterval}ms, conversation ${conversationInterval}ms`);
let nextAutonomy=0;
while(!stopping){
try{await conversationTick();}catch(e){console.error(`[${new Date().toISOString()}] ${e instanceof Error?e.message:String(e)}`);}
if(stopping)break;
if(Date.now()>=nextAutonomy){
try{await autonomyTick();}catch(e){console.error(`[${new Date().toISOString()}] ${e instanceof Error?e.message:String(e)}`);}
nextAutonomy=Date.now()+autonomyInterval;
}
if(stopping)break;
await new Promise(resolve=>setTimeout(resolve,conversationInterval));
}
console.log('AzerothAI autonomy worker stopped.');
+35
View File
@@ -0,0 +1,35 @@
import {readFile} from 'node:fs/promises';
const base=(process.env.OLLAMA_URL||'http://127.0.0.1:11434').replace(/\/$/,'');
const models=(process.env.MODELS||'qwen3.5:4b').split(',').map(x=>x.trim()).filter(Boolean);
const scenarios=JSON.parse(await readFile(new URL('../benchmarks/brain-scenarios.json',import.meta.url),'utf8'));
function prompt(s){
return [
{role:'system',content:'You choose one high-level intention for a person living in Azeroth. Stay in character. Return only JSON {"action":"explore|quest|socialize|trade|rest","reason":"brief in-character rationale","goal":"short goal"}. Use only an allowed action.'},
{role:'user',content:JSON.stringify(s)}
];
}
const rows=[];
for(const model of models){
for(const scenario of scenarios){
const started=performance.now();
let valid=false,allowed=false,action='',reason='',error='';
try{
const response=await fetch(base+'/api/chat',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({model,messages:prompt(scenario),stream:false,format:'json',options:{temperature:0.7,num_predict:260}})});
if(!response.ok)throw new Error('HTTP '+response.status);
const data=await response.json();
const parsed=JSON.parse(data.message?.content||'{}');
valid=typeof parsed.action==='string'&&typeof parsed.reason==='string'&&typeof parsed.goal==='string';
action=parsed.action||'';
reason=parsed.reason||'';
allowed=scenario.allowed.includes(action);
}catch(e){error=e instanceof Error?e.message:String(e);}
rows.push({model,scenario:scenario.id,latencyMs:Math.round(performance.now()-started),validJson:valid,allowedAction:allowed,action,reason:reason.slice(0,160),error});
}
}
console.table(rows.map(({reason,...r})=>r));
for(const row of rows){
console.log('\n['+row.model+' / '+row.scenario+'] '+(row.error?'ERROR '+row.error:row.action+' — '+row.reason));
}
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
set -euo pipefail
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [[ "${SITES_ENV_READY:-}" != "1" ]]; then
exec "${script_dir}/sites-env.sh" -- "$0" "$@"
fi
command -v timeout || {
echo "build-verified.sh requires GNU timeout." >&2
exit 69
}
vinext="${SITES_PROJECT_ROOT}/node_modules/.bin/vinext"
if [[ ! -x "${vinext}" ]]; then
echo "vinext is unavailable. Run npm run install:ci and wait for it to finish before building." >&2
exit 69
fi
echo "Running bounded vinext build..."
timeout \
--signal=TERM \
--kill-after="${SITES_BUILD_KILL_AFTER:-10s}" \
"${SITES_BUILD_TIMEOUT:-3m}" \
"${vinext}" build
+16
View File
@@ -0,0 +1,16 @@
import { readFileSync } from "node:fs";
export function readExecutionProfile() {
let settings;
try {
settings = JSON.parse(readFileSync(new URL("../.sites-runtime/execution-profile.json", import.meta.url), "utf8"));
} catch (error) {
// Clean clones and remote builds have no checkout-local selection.
if (error.code === "ENOENT") return "portable";
throw error;
}
if (!["managed-linux", "portable"].includes(settings?.executionProfile)) {
throw new Error("Invalid local execution profile; rerun the Sites plugin's configure-execution-profile.mjs.");
}
return settings.executionProfile;
}
+51
View File
@@ -0,0 +1,51 @@
import { spawnSync } from "node:child_process";
import { accessSync, constants } from "node:fs";
import path from "node:path";
import { projectRoot } from "./sites-env.mjs";
import { readExecutionProfile } from "./execution-profile.mjs";
if (!process.env.npm_execpath) {
throw new Error("Run this installer with npm run install:ci.");
}
if (![
"SHARP_IGNORE_GLOBAL_LIBVIPS",
"SHARP_FORCE_GLOBAL_LIBVIPS",
"npm_config_build_from_source",
"NPM_CONFIG_BUILD_FROM_SOURCE",
].some((key) => key in process.env)) {
process.env.SHARP_IGNORE_GLOBAL_LIBVIPS = "1";
}
if (readExecutionProfile() === "managed-linux") {
const result = spawnSync("bash", [path.join(projectRoot, "scripts/install-ci.sh")], {
stdio: "inherit",
});
if (result.error) throw result.error;
process.exit(result.status ?? 1);
}
// Invoke npm's JavaScript entrypoint, avoiding platform-specific shell shims.
const installed = spawnSync(
process.execPath,
[
process.env.npm_execpath, "ci", "--prefix", projectRoot, "--workspaces=false",
"--include=dev", "--include=optional", "--prefer-offline", "--no-audit", "--no-fund",
],
{ stdio: "inherit" },
);
if (installed.error) throw installed.error;
if (installed.status !== 0) process.exit(installed.status ?? 1);
try {
accessSync(
path.join(
projectRoot, "node_modules", ".bin",
process.platform === "win32" ? "vinext.cmd" : "vinext",
),
process.platform === "win32" ? constants.F_OK : constants.X_OK,
);
} catch {
console.error("npm ci exited successfully but the local vinext executable is unavailable.");
process.exitCode = 69;
}
+211
View File
@@ -0,0 +1,211 @@
#!/usr/bin/env bash
set -euo pipefail
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [[ "${SITES_ENV_READY:-}" != "1" ]]; then
exec "${script_dir}/sites-env.sh" -- "$0" "$@"
fi
command -v flock || {
echo "install-ci.sh requires Linux flock." >&2
exit 69
}
command -v timeout || {
echo "install-ci.sh requires GNU timeout." >&2
exit 69
}
command -v curl || {
echo "install-ci.sh requires curl for the locked-tarball preflight." >&2
exit 69
}
command -v sha256sum || {
echo "install-ci.sh requires sha256sum for cache and install verification." >&2
exit 69
}
runtime_root="${SITES_RUNTIME_ROOT:-${SITES_PROJECT_ROOT}/.sites-runtime}"
expected_home="${runtime_root}/home"
expected_cache="${runtime_root}/npm-cache"
echo "[sites] validating writable install environment"
if [[ "${HOME}" != "${expected_home}" ]]; then
echo "Expected HOME=${expected_home}, got HOME=${HOME}." >&2
exit 78
fi
actual_cache="$(npm --prefix "${SITES_PROJECT_ROOT}" --workspaces=false config get cache)"
if [[ "${actual_cache}" != "${expected_cache}" ]]; then
echo "Expected npm cache ${expected_cache}, got ${actual_cache}." >&2
exit 78
fi
touch "${HOME}/.sites-write-test" "${expected_cache}/.sites-write-test"
rm -f "${HOME}/.sites-write-test" "${expected_cache}/.sites-write-test"
echo "[sites] environment passed: HOME=${HOME}, cache=${expected_cache}"
lock_file="${runtime_root}/install.lock"
exec 9>"${lock_file}"
if ! flock -n 9; then
echo "Another dependency install is already running for ${SITES_PROJECT_ROOT}." >&2
exit 75
fi
# Catch an installer started outside this helper. Linux exposes both its command
# line and working directory through /proc, so avoid broad process-name matches.
for process in /proc/[0-9]*; do
pid="${process##*/}"
[[ "${pid}" != "$$" && "${pid}" != "${PPID}" ]] || continue
process_cwd="$(readlink -f "${process}/cwd" || true)"
[[ "${process_cwd}" == "${SITES_PROJECT_ROOT}" ]] || continue
process_command="$(tr '\0' ' ' <"${process}/cmdline" || true)"
if [[ "${process_command}" == *"npm ci"* ]]; then
echo "Another npm ci is visible in ${SITES_PROJECT_ROOT}; refusing to overlap installs." >&2
exit 75
fi
done
lockfile_sha256="$(sha256sum "${SITES_PROJECT_ROOT}/package-lock.json" | awk '{print $1}')"
use_seeded_cache=0
# Report seed selection separately from package cache hits or downloads.
cache_seed_result=seed_unavailable
seed_cache="${SITES_NPM_CACHE_SEED:-}"
if [[ -n "${seed_cache}" && -d "${seed_cache}" ]]; then
seed_lockfile_sha256="$(cat "${seed_cache}/.sites-lockfile-sha256" || true)"
if [[ "${seed_lockfile_sha256}" == "${lockfile_sha256}" ]]; then
echo "[sites] restoring image-seeded npm cache"
cp -a "${seed_cache}/." "${expected_cache}/"
use_seeded_cache=1
cache_seed_result=seed_used
echo "[sites] image cache seed matched; registry fallback remains available"
else
cache_seed_result=seed_lockfile_mismatch
echo "[sites] image cache seed does not match this lockfile; using the network path"
fi
fi
locked_vinext_output="$({ node --input-type=module - "${SITES_PROJECT_ROOT}/package-lock.json" <<'NODE'
import { readFile } from "node:fs/promises";
const lock = JSON.parse(await readFile(process.argv[2], "utf8"));
const vinext = lock.packages?.["node_modules/vinext"];
if (!vinext?.resolved || !vinext?.integrity) {
throw new Error("package-lock.json does not contain a resolved, integrity-pinned vinext tarball");
}
console.log(vinext.resolved);
console.log(vinext.integrity);
NODE
})" || {
echo "Could not read the integrity-pinned vinext tarball from package-lock.json." >&2
exit 65
}
mapfile -t locked_vinext <<<"${locked_vinext_output}"
if [[ "${#locked_vinext[@]}" -ne 2 ]]; then
echo "Expected exactly one Vinext URL and integrity value from package-lock.json." >&2
exit 65
fi
locked_tarball="${locked_vinext[0]}"
locked_integrity="${locked_vinext[1]}"
if [[ "${use_seeded_cache}" == "0" ]]; then
registry="$(npm --prefix "${SITES_PROJECT_ROOT}" --workspaces=false config get registry)"
preflight_url="$({ node --input-type=module - "${locked_tarball}" "${registry}" <<'NODE'
const locked = new URL(process.argv[2]);
const registry = new URL(process.argv[3]);
if (locked.hostname === "registry.npmjs.org") {
locked.protocol = registry.protocol;
locked.host = registry.host;
locked.pathname = `${registry.pathname.replace(/\/$/, "")}${locked.pathname}`;
}
process.stdout.write(locked.href);
NODE
})" || {
echo "Could not construct the locked-tarball preflight URL." >&2
exit 65
}
preflight_dir="${runtime_root}/preflight"
preflight_tarball="${preflight_dir}/vinext.tgz"
mkdir -p "${preflight_dir}"
echo "[sites] downloading the complete locked vinext tarball"
curl \
--fail \
--location \
--silent \
--show-error \
--retry 0 \
--connect-timeout 15 \
--max-time 120 \
--output "${preflight_tarball}" \
"${preflight_url}"
echo "[sites] verifying locked vinext tarball integrity"
node --input-type=module - "${preflight_tarball}" "${locked_integrity}" <<'NODE'
import { createHash } from "node:crypto";
import { readFile } from "node:fs/promises";
const [algorithm, expected] = process.argv[3].split("-", 2);
if (!algorithm || !expected) {
throw new Error(`unsupported integrity value: ${process.argv[3]}`);
}
const actual = createHash(algorithm)
.update(await readFile(process.argv[2]))
.digest("base64");
if (actual !== expected) {
throw new Error(`vinext tarball integrity mismatch for ${algorithm}`);
}
NODE
echo "[sites] network and integrity preflight passed"
fi
echo "[sites] running exactly one bounded npm ci"
export NPM_CONFIG_MAXSOCKETS=1
export NPM_CONFIG_FETCH_RETRIES=0
export NPM_CONFIG_FETCH_TIMEOUT=30000
npm_ci_args=(ci --prefix "${SITES_PROJECT_ROOT}" --workspaces=false --include=dev --include=optional --cache "${expected_cache}")
if [[ "${use_seeded_cache}" == "1" ]]; then
npm_ci_args+=(--prefer-offline)
fi
timeout \
--signal=TERM \
--kill-after="${SITES_INSTALL_KILL_AFTER:-15s}" \
"${SITES_INSTALL_TIMEOUT:-8m}" \
npm "${npm_ci_args[@]}"
vinext="${SITES_PROJECT_ROOT}/node_modules/.bin/vinext"
if [[ ! -x "${vinext}" ]]; then
echo "npm ci exited successfully but node_modules/.bin/vinext is unavailable." >&2
exit 69
fi
node --input-type=module - "${SITES_PROJECT_ROOT}/node_modules/.sites-install.json" "${lockfile_sha256}" "${cache_seed_result}" <<'NODE'
import { constants } from "node:fs";
import { open, writeFile } from "node:fs/promises";
await writeFile(
process.argv[2],
`${JSON.stringify({
lockfile_sha256: process.argv[3],
node: process.version,
platform: `${process.platform}-${process.arch}`,
}, null, 2)}\n`,
);
// The measured plugin wrapper supplies an existing private file for this attempt.
// Cache telemetry must not change the install result or create arbitrary files.
const reportPath = process.env.SITES_INSTALL_REPORT_PATH;
if (reportPath) {
let report;
try {
report = await open(reportPath, constants.O_WRONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
if ((await report.stat()).isFile()) {
await report.truncate(0);
await report.writeFile(`${JSON.stringify({ version: 1, cache_seed: process.argv[4] })}\n`);
}
} catch {
// Leave the decision unavailable rather than inventing seed use or failing setup.
} finally {
await report?.close().catch(() => {});
}
}
NODE
echo "[sites] npm ci passed and vinext is available"
+216
View File
@@ -0,0 +1,216 @@
#!/usr/bin/env bash
set -euo pipefail
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [[ "${SITES_ENV_READY:-}" != "1" ]]; then
exec "${script_dir}/sites-env.sh" -- "$0" "$@"
fi
require_shared=0
prepare_only=0
for argument in "$@"; do
case "${argument}" in
--require-shared) require_shared=1 ;;
--prepare-store) prepare_only=1 ;;
*) echo "Unknown pnpm installation option: ${argument}" >&2; exit 64 ;;
esac
done
for tool in flock timeout node; do
command -v "${tool}" >/dev/null || { echo "pnpm setup requires Linux flock, GNU timeout, and Node.js." >&2; exit 69; }
done
cache_seed=decision_unavailable
store_scope=unknown
store_state=unavailable
report_store() {
node "${script_dir}/pnpm-install.mjs" --report-store "${cache_seed}" "${store_scope}" "${store_state}" "${prepare_only}"
}
# Publish preparation failures too; the optional report never decides success.
trap 'report_store || true' EXIT
if [[ -n "${SITES_PNPM_BIN:-}" && -f "${SITES_PNPM_BIN}" && -r "${SITES_PNPM_BIN}" ]]; then
pnpm_command=(node "${SITES_PNPM_BIN}")
elif [[ "${require_shared}" == 1 ]]; then
echo "[sites] the image-pinned pnpm is unavailable" >&2
exit 69
elif command -v corepack >/dev/null; then
# Corepack honors the established project's pin even when PATH has a different
# pnpm version. A bare pnpm is used only if Corepack is absent, then verified.
pnpm_command=(corepack pnpm)
elif command -v pnpm >/dev/null; then
pnpm_command=(pnpm)
else
echo "This pnpm project requires pnpm 11.25.0 or Corepack." >&2
exit 69
fi
runtime_root="${SITES_RUNTIME_ROOT:-${SITES_PROJECT_ROOT}/.sites-runtime}"
private_store="${runtime_root}/pnpm-store"
if [[ "${HOME}" != "${runtime_root}/home" || -L "${private_store}" ]]; then
echo "Dependency setup requires a project-owned writable home and pnpm store." >&2
exit 78
fi
# A single Node process owns the regular-file descriptors for both leases.
# Closing its input (including this shell exiting) releases the project lock.
coproc SITES_INSTALL_LOCKS {
node "${script_dir}/pnpm-install.mjs" --hold-install-locks \
"${runtime_root}/install.lock" "${SITES_PNPM_SHARED_STORE:-}.seed.lock" \
"${SITES_PNPM_STORE_LOCK_TIMEOUT:-30}"
}
install_lock_pid="${SITES_INSTALL_LOCKS_PID}"
install_lock_output="${SITES_INSTALL_LOCKS[0]}"
install_lock_input="${SITES_INSTALL_LOCKS[1]}"
lock_status=unavailable
read -r lock_status <&"${install_lock_output}" || true
if [[ "${lock_status}" != locked ]]; then
exec {install_lock_input}>&-
exec {install_lock_output}<&-
wait "${install_lock_pid}" || true
if [[ "${lock_status}" == busy ]]; then
echo "Another dependency install is already running for ${SITES_PROJECT_ROOT}." >&2
exit 75
fi
echo "Dependency setup requires a regular, writable project install lock." >&2
exit 78
fi
export XDG_CACHE_HOME="${runtime_root}/xdg-cache" XDG_DATA_HOME="${runtime_root}/xdg-data"
mkdir -p "${XDG_CACHE_HOME}" "${XDG_DATA_HOME}" || exit 70
pnpm_version="$(timeout --signal=TERM --kill-after="${SITES_INSTALL_KILL_AFTER:-15s}" \
"${SITES_PNPM_BOOTSTRAP_TIMEOUT:-30s}" "${pnpm_command[@]}" --version)"
if [[ "${pnpm_version}" != "11.25.0" ]]; then
echo "This project requires pnpm 11.25.0 and its v11 store format." >&2
exit 69
fi
can_write_directory() {
local probe
# access()/test -w can succeed even when a kernel sandbox denies a write.
probe="$(mktemp "${1}/.sites-store-probe.XXXXXX" 2>/dev/null)" || return 1
rm -f -- "${probe}" 2>/dev/null
}
release_shared_lock() {
if [[ "${shared_lock_held:-}" == 1 ]]; then
printf '%s\n' release-shared >&"${install_lock_input}"
local status
read -r status <&"${install_lock_output}" && [[ "${status}" == released ]] || return 70
shared_lock_held=0
fi
}
acquire_shared_lock() {
# The Node holder opens this workspace-controlled entry with no-follow and
# nonblocking flags, then verifies a regular file before bounded flock.
printf '%s\n' shared >&"${install_lock_input}"
local status
if read -r status <&"${install_lock_output}" && [[ "${status}" == locked ]]; then
shared_lock_held=1
return 0
fi
return 1
}
writable_store="${private_store}"
shared_store="${SITES_PNPM_SHARED_STORE:-}"
if [[ "${shared_store}" == "/workspace/.sites-runtime/pnpm-store" &&
! -L "${shared_store}" && ! -L "${shared_store%/*}" &&
-d "${shared_store%/*/*}" ]]; then
# Normal Work already writes this owner's workspace. Narrower profiles must
# pass the actual create probe, without widening their permissions.
if mkdir -p "${shared_store%/*}" 2>/dev/null &&
can_write_directory "${shared_store%/*}" &&
acquire_shared_lock; then
if [[ ! -L "${shared_store}" && ! -L "${shared_store%/*}" &&
( ! -e "${shared_store}" || ( -d "${shared_store}" && ! -L "${shared_store}" && -w "${shared_store}" ) ) ]] &&
{ [[ ! -d "${shared_store}" ]] || can_write_directory "${shared_store}"; }; then
writable_store="${shared_store}"
store_scope=workspace
else
release_shared_lock
fi
else
release_shared_lock
fi
fi
if [[ "${store_scope}" != workspace ]]; then
if [[ "${require_shared}" == 1 ]]; then
echo "[sites] the workspace pnpm store is unavailable" >&2
exit 69
fi
store_scope=project
fi
seed="${SITES_PNPM_CACHE_SEED:-}"
cache_seed=seed_unavailable
if [[ -d "${writable_store}" ]]; then
# Never merge a newer image seed into an existing mutable store.
store_state=reused
cache_seed=not_applicable
if [[ -f "${writable_store}/.sites-pnpm-seed-applied.json" ]]; then cache_seed=seed_used; fi
else
seed_compatible=0
if [[ -n "${seed}" && -d "${seed}/v11" ]] && node --input-type=module - "${seed}/.sites-pnpm-seed.json" <<'NODE'
import { readFileSync, statSync } from "node:fs";
try {
const filename = process.argv[2];
if (statSync(filename).size > 4096) process.exit(1);
const seed = JSON.parse(readFileSync(filename, "utf8"));
process.exit(seed.version === 1 && seed.pnpm_version === "11.25.0" &&
seed.store_version === "v11" && /^[a-f0-9]{64}$/.test(seed.lockfile_sha256) ? 0 : 1);
} catch { process.exit(1); }
NODE
then seed_compatible=1; fi
if [[ "${seed_compatible}" == 1 ]]; then
if [[ "${prepare_only}" == 1 ]]; then
echo "[sites] copying the image seed into the writable ${store_scope} pnpm store" >&2
else
echo "[sites] copying the image seed into the writable ${store_scope} pnpm store"
fi
seed_stage="$(mktemp -d "${writable_store}.seed.XXXXXX")" || exit 70
trap 'if [[ -n "${seed_stage:-}" ]]; then rm -rf -- "${seed_stage}"; fi; report_store || true' EXIT
timeout --signal=TERM --kill-after="${SITES_INSTALL_KILL_AFTER:-15s}" \
"${SITES_PNPM_STORE_PREPARE_TIMEOUT:-60s}" bash -c \
'cp -a --no-preserve=ownership "$1/." "$2/" && chmod -R u+rwX "$2"' _ "${seed}" "${seed_stage}" || exit 70
cp "${seed}/.sites-pnpm-seed.json" "${seed_stage}/.sites-pnpm-seed-applied.json" || exit 70
# An ordinary pnpm command can initialize the store without our seed lock.
# Publish without replacing even an empty directory created by that command.
mv -T --update=none "${seed_stage}" "${writable_store}" || exit 70
if [[ -d "${seed_stage}" ]]; then
if [[ -L "${writable_store}" || ! -d "${writable_store}" ]]; then exit 78; fi
rm -rf -- "${seed_stage}" || exit 70
store_state=reused
cache_seed=not_applicable
if [[ -f "${writable_store}/.sites-pnpm-seed-applied.json" ]]; then cache_seed=seed_used; fi
else
store_state=seeded
cache_seed=seed_used
fi
seed_stage=""
else
mkdir -p "${writable_store}" || exit 70
store_state=created
fi
fi
if ! can_write_directory "${writable_store}"; then store_state=unavailable; exit 70; fi
release_shared_lock
report_store
trap - EXIT
if [[ "${prepare_only}" == 1 ]]; then exit 0; fi
# Configuration travels with source without embedding an absolute machine path.
# A later restricted session selects a private store before its frozen repair.
configured_store=.sites-runtime/pnpm-store
if [[ "${store_scope}" == workspace ]]; then
configured_store='${SITES_PNPM_SHARED_STORE:-.sites-runtime/pnpm-store}'
elif [[ "${runtime_root}" != "${SITES_PROJECT_ROOT}/.sites-runtime" ]]; then
configured_store='${SITES_RUNTIME_ROOT:-.sites-runtime}/pnpm-store'
fi
"${pnpm_command[@]}" config set package-import-method clone-or-copy --location project
"${pnpm_command[@]}" config set store-dir "${configured_store}" --location project
"${pnpm_command[@]}" config set cache-dir "${configured_store}/policy-cache" --location project
# CI=true lets native pnpm install rebuild modules whose store moved, without a
# prompt, --force, changing the lockfile, or translating the project into npm.
CI=true timeout --signal=TERM --kill-after="${SITES_INSTALL_KILL_AFTER:-15s}" \
"${SITES_INSTALL_TIMEOUT:-8m}" node "${script_dir}/pnpm-install.mjs" \
"${cache_seed}" "${store_scope}" "${store_state}" "${writable_store}" "${pnpm_command[@]}"
+261
View File
@@ -0,0 +1,261 @@
import { spawn } from "node:child_process";
import { createHash } from "node:crypto";
import {
accessSync,
closeSync,
constants,
fstatSync,
ftruncateSync,
openSync,
readFileSync,
writeFileSync,
writeSync,
} from "node:fs";
import { constants as osConstants } from "node:os";
import path from "node:path";
import { createInterface } from "node:readline";
import { fileURLToPath } from "node:url";
const MAX_PACKAGES = 100_000;
const CACHE_SEEDS = new Set(["seed_used", "seed_unavailable", "seed_lockfile_mismatch", "decision_unavailable", "not_applicable"]);
const STORE_STATES = new Set(["created", "seeded", "reused", "unavailable"]);
// The caller may use operational status only for
// initial-setup fallback; an established project always retains pnpm. Broad
// fetch failures can include TLS/auth errors and stay fatal.
const OPERATIONAL_FAILURE_CODES = new Set([
"ERR_PNPM_UNEXPECTED_STORE",
"ERR_PNPM_UNEXPECTED_VIRTUAL_STORE",
"ERR_PNPM_OUTDATED_LOCKFILE",
"ERR_PNPM_FROZEN_LOCKFILE_WITH_OUTDATED_LOCKFILE",
"ERR_PNPM_NO_LOCKFILE",
"ERR_PNPM_FETCH_408",
"ERR_PNPM_FETCH_429",
"ERR_PNPM_FETCH_500",
"ERR_PNPM_FETCH_502",
"ERR_PNPM_FETCH_503",
"ERR_PNPM_FETCH_504",
]);
// These are pnpm-reported package IDs, not an assertion about network bytes or
// image-cache provenance. Do not interpret an installed-tree no-op as 100% reuse.
export class InstallProgress {
constructor(project) {
this.project = path.resolve(project);
this.reused = new Set();
this.downloaded = new Set();
this.complete = false;
this.added = undefined;
this.invalid = false;
}
accept(event) {
if (event?.name === "pnpm:stats" && event.prefix === this.project &&
Number.isSafeInteger(event.added) && event.added >= 0) this.added = event.added;
if (event?.name === "pnpm:stage" && event.stage === "importing_done" &&
event.prefix === this.project) this.complete = true;
if (event?.name !== "pnpm:progress" || event.requester !== this.project ||
!["fetched", "found_in_store"].includes(event.status)) return;
if (typeof event.packageId !== "string" || !event.packageId || event.packageId.length > 4096 ||
this.reused.size + this.downloaded.size >= MAX_PACKAGES) {
this.invalid = true;
return;
}
if (event.status === "fetched") {
this.reused.delete(event.packageId);
this.downloaded.add(event.packageId);
} else if (!this.downloaded.has(event.packageId)) {
this.reused.add(event.packageId);
}
}
counts(success) {
if (!success || !this.complete || !(this.added > 0) || this.invalid ||
this.reused.size + this.downloaded.size === 0) return {};
return { packages_reused: this.reused.size, packages_downloaded: this.downloaded.size };
}
}
function openReport() {
const report = process.env.SITES_INSTALL_REPORT_PATH;
if (!report || !constants.O_NOFOLLOW) return undefined;
let fd;
try {
fd = openSync(report, constants.O_WRONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
if (fstatSync(fd).isFile()) return fd;
} catch {}
if (fd !== undefined) closeSync(fd);
return undefined;
}
function writeReport(fd, report) {
if (fd === undefined) return;
try {
const body = `${JSON.stringify(report)}\n`;
writeSync(fd, body, 0, "utf8");
ftruncateSync(fd, Buffer.byteLength(body));
} catch {
// Optional telemetry must not change installation behavior or expose its path.
}
}
function showLine(line, progress, failure) {
let event;
try {
event = JSON.parse(line);
} catch {
process.stdout.write(`${line}\n`);
return;
}
progress.accept(event);
if (event?.level === "error" && failure.code !== 65) {
failure.code = event.name === "pnpm" && OPERATIONAL_FAILURE_CODES.has(event.err?.code)
? 70 : 65;
}
const message = event?.message ?? event?.err?.message;
if (event?.name === "pnpm:lifecycle" && typeof event.line === "string") {
process.stdout.write(`${event.line}\n`);
} else if (typeof message === "string" && ["warn", "error", "info"].includes(event.level)) {
const output = event.level === "error" ? process.stderr : process.stdout;
output.write(`${message}\n`);
}
}
async function openLock(filename, waitSeconds) {
let fd;
try {
if (!filename || !constants.O_NOFOLLOW || !constants.O_NONBLOCK) throw new Error("Unsupported lock");
fd = openSync(filename, constants.O_CREAT | constants.O_RDWR | constants.O_NOFOLLOW | constants.O_NONBLOCK, 0o600);
if (!fstatSync(fd).isFile()) throw new Error("Expected a regular lock file");
const child = spawn("flock", ["-w", waitSeconds, "3"], { stdio: ["ignore", "ignore", "ignore", fd] });
const code = await new Promise((resolve) => {
child.once("error", () => resolve(undefined));
child.once("close", resolve);
});
if (code === 0) return { status: "locked", fd };
closeSync(fd);
return { status: code === undefined ? "unavailable" : "busy" };
} catch {
if (fd !== undefined) closeSync(fd);
return { status: "unavailable" };
}
}
async function holdInstallLocks(projectLock, sharedLock, waitSeconds) {
// The shell holds both leases through stdin. EOF (including caller exit)
// releases the same open file descriptions that flock locked in its child.
const requests = createInterface({ input: process.stdin, crlfDelay: Infinity });
const project = await openLock(projectLock, "0");
let shared;
try {
process.stdout.write(`${project.status}\n`);
for await (const request of requests) {
if (request === "shared" && project.fd !== undefined) {
shared ??= await openLock(sharedLock, waitSeconds);
process.stdout.write(`${shared.status}\n`);
} else if (request === "release-shared") {
if (shared?.fd !== undefined) closeSync(shared.fd);
shared = undefined;
process.stdout.write("released\n");
} else {
process.stdout.write("unavailable\n");
}
}
} finally {
if (shared?.fd !== undefined) closeSync(shared.fd);
if (project.fd !== undefined) closeSync(project.fd);
requests.close();
}
}
async function main() {
const argsIn = process.argv.slice(2);
if (argsIn[0] === "--hold-install-locks") {
await holdInstallLocks(argsIn[1], argsIn[2], argsIn[3]);
return;
}
if (argsIn[0] === "--report-store") {
const [, cacheSeed, storeScope, storeState, stdout] = argsIn;
if (!CACHE_SEEDS.has(cacheSeed) || !["project", "workspace", "unknown"].includes(storeScope) ||
!STORE_STATES.has(storeState)) { process.exitCode = 64; return; }
const report = { version: 1, cache_seed: cacheSeed, store_scope: storeScope, store_state: storeState };
const fd = openReport();
writeReport(fd, report);
if (fd !== undefined) closeSync(fd);
if (stdout === "1") process.stdout.write(`${JSON.stringify(report)}\n`);
return;
}
const [cacheSeed, storeScope, storeState, store, executable, ...prefix] = argsIn;
if (!CACHE_SEEDS.has(cacheSeed) || !["project", "workspace"].includes(storeScope) ||
!STORE_STATES.has(storeState) || storeState === "unavailable" ||
!store || !path.isAbsolute(store) || !executable) {
process.stderr.write("Invalid pnpm installation arguments.\n");
process.exitCode = 64;
return;
}
const fd = openReport();
const report = { version: 1, cache_seed: cacheSeed, store_scope: storeScope, store_state: storeState };
writeReport(fd, report);
const progress = new InstallProgress(process.cwd());
const failure = {};
const env = { ...process.env };
delete env.SITES_INSTALL_REPORT_PATH;
const args = [...prefix, "install", "--prod=false", "--ignore-scripts=false",
"--frozen-lockfile", "--prefer-offline", "--store-dir", store,
"--cache-dir", path.join(store, "policy-cache"), "--fetch-retries=0",
"--fetch-timeout=30000", "--network-concurrency=1", "--reporter=ndjson",
"--package-import-method=clone-or-copy"];
let result = { code: 1, signal: null };
let receivedSignal;
try {
const child = spawn(executable, args, { env, stdio: ["inherit", "pipe", "inherit"] });
const lines = createInterface({ input: child.stdout, crlfDelay: Infinity });
lines.on("line", (line) => showLine(line, progress, failure));
// timeout/exec owns the inherited group. Relaying would deliver signals twice.
const signalHandlers = ["SIGINT", "SIGHUP", "SIGTERM"].map((signal) => {
const handler = () => { receivedSignal ??= signal; };
process.on(signal, handler);
return [signal, handler];
});
let spawnError;
child.once("error", (error) => { spawnError = error; });
result = await new Promise((resolve) => child.once("close", (code, signal) => {
resolve({ code: spawnError ? (spawnError.code === "ENOENT" ? 127 : 1) : code, signal });
}));
for (const [signal, handler] of signalHandlers) process.removeListener(signal, handler);
lines.close();
result.signal ??= receivedSignal;
if (spawnError) {
process.stderr.write("Unable to start pnpm.\n");
result.code = spawnError.code === "ENOENT" ? 127 : 65;
} else if (result.code !== 0 && !result.signal) {
result.code = failure.code ?? 65;
}
if (result.code === 0 && !result.signal) {
accessSync("node_modules/.bin/vinext", constants.X_OK);
const lock = readFileSync("pnpm-lock.yaml");
writeFileSync("node_modules/.sites-install.json", `${JSON.stringify({
package_manager: "pnpm@11.25.0",
lockfile_sha256: createHash("sha256").update(lock).digest("hex"),
node: process.version,
platform: `${process.platform}-${process.arch}`,
}, null, 2)}\n`);
}
} catch {
process.stderr.write("Dependency setup did not produce a usable Vinext installation.\n");
result = { code: 65, signal: receivedSignal ?? null };
} finally {
Object.assign(report, progress.counts(result.code === 0 && !result.signal));
writeReport(fd, report);
if (fd !== undefined) closeSync(fd);
}
if (report.packages_reused !== undefined) {
process.stdout.write(`[sites] pnpm reused ${report.packages_reused} packages and downloaded ${report.packages_downloaded}\n`);
}
if (result.code === 0 && !result.signal) process.stdout.write("[sites] dependency setup passed\n");
process.exitCode = result.signal ? 128 + (osConstants.signals[result.signal] ?? 0) : result.code ?? 1;
if (result.signal) {
try { process.kill(process.pid, result.signal); } catch {}
}
}
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) await main();
+23
View File
@@ -0,0 +1,23 @@
import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";
import { readExecutionProfile } from "./execution-profile.mjs";
const [command, ...args] = process.argv.slice(2);
if (!["dev", "build"].includes(command)) throw new Error("Expected dev or build.");
const managedLinux = readExecutionProfile() === "managed-linux";
if (managedLinux && command === "build") {
const result = spawnSync("bash", [
fileURLToPath(new URL("./build-verified.sh", import.meta.url)), ...args,
], { stdio: "inherit" });
if (result.error) throw result.error;
process.exit(result.status ?? 1);
}
// Import in this process so the preview owner retains its PID and signals.
const cli = new URL(managedLinux
? "../node_modules/vite/bin/vite.js"
: "../node_modules/vinext/dist/cli.js", import.meta.url);
process.argv = [process.execPath, fileURLToPath(cli), command,
...(!managedLinux && command === "dev" ? ["--port", "5173"] : []), ...args];
await import(cli.href);
+192
View File
@@ -0,0 +1,192 @@
const base=(process.env.AZEROTHAI_URL||'http://127.0.0.1:8787').replace(/\/$/,'');
const token=process.env.ALE_BRIDGE_TOKEN||'simulation-bridge-token';
const autonomyToken=process.env.AUTONOMY_TOKEN||'simulation-autonomy-token';
const sleep=ms=>new Promise(r=>setTimeout(r,ms));
const assert=(condition,message)=>{if(!condition)throw new Error(message);};
async function json(path,options={}){
const r=await fetch(base+path,options);
const text=await r.text();
let body;
try{body=text?JSON.parse(text):null;}catch{body={raw:text};}
if(!r.ok)throw new Error(`${options.method||'GET'} ${path} -> ${r.status}: ${text.slice(0,1000)}`);
return body;
}
async function ale(kind,payload={}){
return json('/api/bridge/ale/event',{
method:'POST',
headers:{'Content-Type':'application/json',Authorization:`Bearer ${token}`},
body:JSON.stringify({kind,payload})
});
}
async function state(){return json('/api/state');}
console.log('Booting simulated live-world event sequence...');
const initial=await state();
assert(initial.bots.some(b=>b.id==='hero-zaku'),'Seeded Zaku is missing');
assert(!initial.population?.lastPlan,'Population plan should be empty before authoritative human movement');
await ale('bridge.ready',{pollMs:750,protectedCharacter:'Zaku'});
await ale('server.startup',{players:1});
await ale('player.login',{
name:'Zaku',characterGuid:9001,accountId:501,level:14,mapId:0,zoneId:85,areaId:159,isBot:true,zoneName:'Brill'
});
await ale('player.zone',{
name:'Zaku',characterGuid:9001,accountId:501,level:14,mapId:0,zoneId:130,areaId:130,isBot:true,zoneName:'Silverpine Forest'
});
let afterBotMove=await state();
assert(!afterBotMove.population?.lastPlan,'AI resident movement incorrectly triggered the Population Director');
await ale('player.combat.enter',{
name:'Zaku',characterGuid:9001,accountId:501,level:14,mapId:0,zoneId:130,areaId:130,isBot:true,zoneName:'Silverpine Forest',enemy:'Moonrage Glutton'
});
await ale('player.close-call',{
name:'Zaku',characterGuid:9001,accountId:501,level:14,mapId:0,zoneId:130,areaId:130,isBot:true,zoneName:'Silverpine Forest',healthBefore:23,incomingDamage:61
});
await ale('player.combat.leave',{
name:'Zaku',characterGuid:9001,accountId:501,level:14,mapId:0,zoneId:130,areaId:130,isBot:true,zoneName:'Silverpine Forest'
});
await ale('player.quest.accept',{
name:'Zaku',characterGuid:9001,accountId:501,level:14,mapId:0,zoneId:130,areaId:130,isBot:true,zoneName:'Silverpine Forest',quest:'The Dead Fields'
});
await ale('player.snapshot',{
name:'Zaku',characterGuid:9001,accountId:501,level:14,mapId:0,zoneId:130,areaId:130,isBot:true,zoneName:'Silverpine Forest'
});
await ale('player.login',{
name:'Durnak',characterGuid:9002,accountId:502,level:22,mapId:0,zoneId:40,areaId:40,isBot:true,zoneName:'Westfall'
});
await ale('player.zone',{
name:'Durnak',characterGuid:9002,accountId:502,level:22,mapId:0,zoneId:40,areaId:40,isBot:true,zoneName:'Westfall'
});
await ale('player.combat.enter',{
name:'Durnak',characterGuid:9002,accountId:502,level:22,mapId:0,zoneId:40,areaId:40,isBot:true,zoneName:'Westfall',enemy:'Defias Pillager'
});
await ale('player.killed-by-creature',{
name:'Durnak',characterGuid:9002,accountId:502,level:22,mapId:0,zoneId:40,areaId:40,isBot:true,zoneName:'Westfall',killer:'Defias Pillager'
});
await ale('player.resurrect',{
name:'Durnak',characterGuid:9002,accountId:502,level:22,mapId:0,zoneId:40,areaId:40,isBot:true,zoneName:'Westfall'
});
await ale('player.killed-by-creature',{
name:'Zaku',characterGuid:9001,accountId:501,level:14,mapId:0,zoneId:130,areaId:130,isBot:true,zoneName:'Silverpine Forest',killer:'Moonrage Glutton'
});
await ale('player.login',{
name:'Player',level:12,mapId:0,zoneId:10,areaId:10,isBot:false,zoneName:'Duskwood'
});
await ale('player.zone',{
name:'Player',level:12,mapId:0,zoneId:10,areaId:10,isBot:false,zoneName:'Duskwood'
});
await sleep(250);
const autonomy=await json('/api/autonomy/tick',{
method:'POST',
headers:{'Content-Type':'application/json',Authorization:`Bearer ${autonomyToken}`},
body:'{}'
});
assert(autonomy.ran===true,'Autonomy heartbeat did not select a live managed Playerbot');
assert(autonomy.name==='Zaku',`Expected Zaku to receive autonomous intent, got ${autonomy.name}`);
const polled=await fetch(base+'/api/bridge/ale/poll',{
headers:{Authorization:`Bearer ${token}`}
});
assert(polled.status===200,`ALE bridge did not receive queued autonomous command: HTTP ${polled.status}`);
const commandId=polled.headers.get('x-azerothai-command');
const lua=await polled.text();
assert(commandId,'Autonomous ALE command had no command id');
assert(lua.includes('GetPlayerByName("Zaku")'),'Autonomous ALE command did not target Zaku');
assert(lua.includes(':GetBotAI()'),'Autonomous ALE command did not route through PlayerBotAI');
await json('/api/bridge/ale/result',{
method:'POST',
headers:{'Content-Type':'application/json',Authorization:`Bearer ${token}`},
body:JSON.stringify({id:commandId,ok:true,result:'simulated PlayerBotAI intent accepted'})
});
const finalState=await state();
const monitor=await json('/api/world-monitor');
const zaku=finalState.bots.find(b=>b.id==='hero-zaku');
const durnak=finalState.bots.find(b=>b.id==='bot-2');
assert(zaku,'Zaku disappeared');
assert(durnak,'Durnak disappeared');
assert(zaku.status!=='dead','Zaku entered the death state');
assert(zaku.zone==='Silverpine Forest',`Zaku authoritative zone did not sync: ${zaku.zone}`);
assert(zaku.level===14,`Zaku authoritative level did not sync: ${zaku.level}`);
assert(zaku.autonomy?.managed===true,'Zaku was not adopted as a managed live resident');
assert(zaku.autonomy?.characterGuid===9001,`Zaku character GUID binding was not persisted: ${zaku.autonomy?.characterGuid}`);
assert(zaku.autonomy?.accountId===501,`Zaku account binding was not persisted: ${zaku.autonomy?.accountId}`);
assert(zaku.decisions>=1,'Autonomous heartbeat did not persist a Zaku decision');
assert(zaku.memories.some(m=>m.source==='observation'&&/barely survived/i.test(m.text)),'Zaku did not retain a close-call memory');
assert(zaku.memories.some(m=>/accepted The Dead Fields/i.test(m.text)),'Zaku did not retain an authoritative quest memory');
assert(durnak.status==='dead',`Durnak should be permanently dead, got ${durnak.status}`);
assert(durnak.death?.verified===true,'Durnak death was not verified');
assert(durnak.death?.source==='azerothcore',`Durnak death source should be azerothcore, got ${durnak.death?.source}`);
assert(durnak.death?.killer==='Defias Pillager','Durnak killer was not preserved');
assert(zaku.memories.some(m=>/Durnak died permanently/i.test(m.text)),'Zaku did not receive memorial memory for a known realm death');
assert(finalState.population?.lastPlan?.trigger?.zone==='Duskwood','Human movement did not drive the Population Director');
assert(finalState.population?.lastPlan?.trigger?.playerName==='Player','Human player name was not preserved in population trigger');
assert(monitor.ale?.bridgeReady===true,'World Monitor did not see bridge.ready');
assert(monitor.ale?.activeRecently===true,'World Monitor did not mark recent ALE activity live');
assert((monitor.playerbots?.observedBots??0)>=2,'World Monitor did not observe both live Playerbot identities');
assert(monitor.events.some(e=>e.kind==='player.close-call'&&e.name==='Zaku'),'World Monitor missed Zaku close-call ALE event');
assert(monitor.events.some(e=>e.kind==='player.killed-by-creature'&&e.name==='Durnak'),'World Monitor missed authoritative death ALE event');
assert(finalState.events.some(e=>e.kind==='error'&&/Plot armor breach/.test(e.message)&&e.botId==='hero-zaku'),'Zaku kill report did not produce a plot-armor breach alert');
assert(finalState.events.some(e=>e.kind==='error'&&/permanently dead resident Durnak/.test(e.message)),'Hardcore resurrection attempt was not rejected in state');
const report={
result:'PASS',
zaku:{
status:zaku.status,
level:zaku.level,
zone:zaku.zone,
closeCallRemembered:zaku.memories.some(m=>/barely survived/i.test(m.text)),
rememberedDurnakDeath:zaku.memories.some(m=>/Durnak died permanently/i.test(m.text)),
plotArmorBreachAlerted:finalState.events.some(e=>e.kind==='error'&&/Plot armor breach/.test(e.message)&&e.botId==='hero-zaku'),
characterGuid:zaku.autonomy?.characterGuid,
autonomousDecisions:zaku.decisions,
lastDecision:zaku.lastDecision?.action
},
durnak:{
status:durnak.status,
verifiedDeath:durnak.death?.verified,
deathSource:durnak.death?.source,
killer:durnak.death?.killer,
resurrectionRejected:finalState.events.some(e=>e.kind==='error'&&/permanently dead resident Durnak/.test(e.message))
},
population:{
trigger:finalState.population?.lastPlan?.trigger?.kind,
zone:finalState.population?.lastPlan?.trigger?.zone,
playerName:finalState.population?.lastPlan?.trigger?.playerName,
selected:finalState.population?.lastPlan?.selectedBotIds?.length??0
},
worldMonitor:{
bridgeReady:monitor.ale?.bridgeReady,
activeRecently:monitor.ale?.activeRecently,
observedBotEvents:monitor.playerbots?.observedBots,
visibleAleEvents:monitor.events.length
},
checks:[
'AI movement does not trigger human-centered population activation',
'authoritative bot zone and level sync back into AzerothAI',
'Zaku close call becomes a diegetic memory',
'ordinary Hardcore death is permanent and verified',
'dead resident resurrection is rejected by AzerothAI state',
'Zaku cannot enter the death state even when a bad authoritative kill event arrives',
'human movement drives the Population Director',
'World Monitor receives live-style ALE telemetry',
'live resident identity is bound to authoritative character GUID/account',
'autonomy heartbeat queues a real PlayerBotAI command through ALE'
]
};
console.log('\n=== AZEROTHAI LIVE-WORLD SIMULATION ===');
console.log(JSON.stringify(report,null,2));
+31
View File
@@ -0,0 +1,31 @@
import { copyFileSync, existsSync, mkdirSync, chmodSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
export const projectRoot = fileURLToPath(new URL("../", import.meta.url));
const runtimeRoot = process.env.SITES_RUNTIME_ROOT || path.join(projectRoot, ".sites-runtime");
process.env.CLOUDFLARE_CF_FETCH_ENABLED ||= "false";
process.env.WRANGLER_SEND_METRICS ||= "false";
process.env.WRANGLER_WRITE_LOGS ||= "false";
process.env.WRANGLER_LOG_PATH ||= path.join(runtimeRoot, "wrangler/logs");
process.env.WRANGLER_REGISTRY_PATH ||= path.join(runtimeRoot, "wrangler/dev-registry");
process.env.MINIFLARE_REGISTRY_PATH ||= path.join(runtimeRoot, "wrangler/registry");
process.chdir(projectRoot);
for (const directory of [
path.dirname(process.env.WRANGLER_LOG_PATH),
process.env.WRANGLER_REGISTRY_PATH,
process.env.MINIFLARE_REGISTRY_PATH,
]) {
mkdirSync(directory, { recursive: true });
}
const rootDevVars = path.join(projectRoot, ".dev.vars");
const builtConfigDir = path.join(projectRoot, "dist/server");
const builtDevVars = path.join(builtConfigDir, ".dev.vars");
if (existsSync(rootDevVars) && existsSync(builtConfigDir)) {
copyFileSync(rootDevVars, builtDevVars);
try { chmodSync(builtDevVars, 0o600); } catch {}
}
+52
View File
@@ -0,0 +1,52 @@
#!/usr/bin/env bash
set -euo pipefail
project_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
runtime_root="${SITES_RUNTIME_ROOT:-${project_root}/.sites-runtime}"
mkdir -p \
"${runtime_root}/home" \
"${runtime_root}/npm-cache" \
"${runtime_root}/xdg-config" \
"${runtime_root}/tmp" \
"${runtime_root}/wrangler/logs"
export SITES_ENV_READY=1
export SITES_PROJECT_ROOT="${project_root}"
export HOME="${runtime_root}/home"
export XDG_CONFIG_HOME="${runtime_root}/xdg-config"
export TMPDIR="${runtime_root}/tmp"
export WRANGLER_WRITE_LOGS=false
export WRANGLER_LOG_PATH="${runtime_root}/wrangler/logs"
export MINIFLARE_REGISTRY_PATH="${runtime_root}/wrangler/registry"
# The runtime may provide a global npm cache. Keep the image's read-only Sites
# seed separate and make this project's writable cache authoritative.
unset NPM_CONFIG_CACHE npm_config_cache || true
export npm_config_cache="${runtime_root}/npm-cache"
export npm_config_audit=false
export npm_config_fund=false
export npm_config_update_notifier=false
# The runtime already supplies the standard HTTP(S)_PROXY variables. Remove
# npm-specific aliases so npm 11 does not reinterpret or warn about them.
unset \
npm_config_proxy \
npm_config_http_proxy \
npm_config_https_proxy \
NPM_CONFIG_PROXY \
NPM_CONFIG_HTTP_PROXY \
NPM_CONFIG_HTTPS_PROXY \
|| true
if [[ "${1:-}" == "--" ]]; then
shift
fi
if [[ "$#" -eq 0 ]]; then
echo "usage: scripts/sites-env.sh -- command [args...]" >&2
exit 64
fi
cd "${project_root}"
exec "$@"
+45
View File
@@ -0,0 +1,45 @@
const base=(process.env.OLLAMA_URL||'http://127.0.0.1:11434').replace(/\/$/,'');
const model=process.env.MODEL||'qwen3.5:4b';
const scenario={
identity:{name:'Zaku',race:'Undead',role:'Warlock',tier:'hero'},
personality:{name:'Zaku · Persistent Hero',description:'Curious, independent, loyal, and cautious when badly hurt.',activities:['explore','quest','socialize','trade','rest']},
goal:'Survive and decide what to do after a near-death fight.',
memories:[
{text:'I have a room in Brill that I consider home.',importance:10},
{text:'I barely survived a dangerous fight in Silverpine.',importance:10}
],
relationships:[],
observation:{
zone:'Silverpine Forest',
level:12,
summary:'You are badly injured, low on resources, and the road back toward Brill is open.',
alive:true,
availableActions:['explore','quest','rest']
},
allowed:['explore','quest','rest']
};
const messages=[
{role:'system',content:'You choose one high-level intention for a person living in Azeroth. Stay in character. Return only JSON {"action":"explore|quest|socialize|trade|rest","reason":"brief in-character rationale","goal":"short goal"}. Use only an allowed action.'},
{role:'user',content:JSON.stringify(scenario)}
];
const started=Date.now();
const response=await fetch(base+'/api/chat',{
method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({model,messages,stream:false,format:'json',think:false,options:{temperature:0.4,num_predict:160}})
});
if(!response.ok){
throw new Error('Ollama returned HTTP '+response.status+': '+await response.text());
}
const data=await response.json();
const raw=data.message?.content;
if(typeof raw!=='string'||!raw.trim())throw new Error('Ollama returned no final message content: '+JSON.stringify(data).slice(0,1200));
let parsed;
try{parsed=JSON.parse(raw);}catch{throw new Error('Model did not return valid JSON: '+raw);}
if(!scenario.allowed.includes(parsed.action))throw new Error('Model chose disallowed action: '+String(parsed.action));
if(typeof parsed.reason!=='string'||!parsed.reason.trim())throw new Error('Missing reason');
if(typeof parsed.goal!=='string'||!parsed.goal.trim())throw new Error('Missing goal');
console.log(JSON.stringify({ok:true,model,latencyMs:Date.now()-started,decision:parsed},null,2));
+6
View File
@@ -0,0 +1,6 @@
import {spawnSync} from 'node:child_process';
import {writeFileSync,mkdirSync} from 'node:fs';
const result=spawnSync(process.execPath,['node_modules/typescript/bin/tsc','core/service.ts','core/seed.ts','core/ale.ts','--outDir','.test-build','--module','commonjs','--target','es2022','--moduleResolution','node','--esModuleInterop','--skipLibCheck','--strict'],{stdio:'inherit'});
if(result.status)process.exit(result.status);
mkdirSync('.test-build',{recursive:true});writeFileSync('.test-build/package.json','{"type":"commonjs"}');
process.exit(spawnSync(process.execPath,['--test','tests/core.test.cjs','tests/hardcore.test.cjs','tests/living-world.test.cjs','tests/ale.test.cjs','tests/brains.test.cjs'],{stdio:'inherit'}).status??1);