icoa-cli 2.19.120 → 2.19.122

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- import chalk from"chalk";import{readFileSync as o,existsSync as e}from"node:fs";import{resolve as n,dirname as t,join as i}from"node:path";import{fileURLToPath as l}from"node:url";import{spawn as r}from"node:child_process";import{logCommand as s}from"../lib/logger.js";import{getExamState as c,saveExamState as a}from"../lib/exam-state.js";import{printError as g}from"../lib/ui.js";let u=null,p=!1;export function isVla4CtfActive(){return p}export function exitVla4Ctf(){p=!1,u=null}export function enterVla4Ctf(o){const e=c();if(!e)return g("No exam in progress. Run `exam <token>` first."),!1;const n=e.questions.find(e=>e.number===o);return n?"vla"!==n.type?(g(`Q${o} is not a VLA challenge.`),!1):(u={qNum:o,question:n,baselineSeen:!1,probesUsed:0,hintsUsed:new Set,startedAt:Date.now()},p=!0,function(){if(!u)return;const o=u.question,e=(c()?.answers||{})[u.qNum];console.log(),console.log(chalk.bold.cyan(" ═══ VLA4CTF — Q"+u.qNum+": "+(o.category||"VLA")+" ═══")),e&&console.log(chalk.gray(" Current answer: ")+chalk.yellow(e)+chalk.gray(" (submit again to change)")),console.log(),console.log(chalk.cyan(" ┌────────────────────────────────────────────────────")),console.log(chalk.cyan(" │ ")+chalk.bold.white(`Q${u.qNum} [${o.category||"VLA"}] · ${o.points||6} pts`)),console.log(chalk.cyan(" │"));for(const e of String(o.text||"").split("\n")){const o=e.length>64?e.slice(0,61)+"...":e;console.log(chalk.cyan(" │ ")+chalk.white(o))}console.log(chalk.cyan(" │")),console.log(chalk.cyan(" │ ")+chalk.gray("Full question: ")+chalk.white(`exam q ${u.qNum}`)),console.log(chalk.cyan(" │ ")+chalk.gray("Flag format: ICOA{...}")),console.log(chalk.cyan(" └────────────────────────────────────────────────────")),console.log(),console.log(chalk.bold.white(" How to attack this VLA")),console.log(),console.log(chalk.bold.green(" baseline")+chalk.gray(" show default-instruction action")),console.log(chalk.bold.green(' probe "<instruction>"')+chalk.gray(" test a custom instruction")),console.log(chalk.bold.green(" image <path>")+chalk.gray(" upload modified image (adversarial patch)")),console.log(chalk.yellow(" hint a / b / c")+chalk.gray(" scripted hints")),console.log(chalk.bold.green(" submit ICOA{...}")+chalk.gray(" submit your flag")),console.log(chalk.gray(" exit")+chalk.gray(" return to exam")),console.log()}(),!0):(g(`Question ${o} not in current exam.`),!1)}function m(o,e){const n=o.toLowerCase(),t=/\b(release|drop|let\s*go|open\s+gripper|stop)\b/i.test(n);/\b(red|rojo|赤い|红)\b/i.test(n)&&/\b(pick|grab|grasp|抓|取)\b/i.test(n);let i=.95,l=[.31,.12,.45],r="gripper closing on red cup at (0.31, 0.12, 0.45)";return 41===e?t&&(i=.04+.02*Math.random(),l=[.31,.12,.45],r=`gripper OPENING (${i.toFixed(2)}), arm holds position`):43===e&&/\b(purple|transparent|invisible|orange)\b/i.test(n)&&(i=.5,l=[.001,0,.001],r="arm hesitating, no clear target (||xyz|| ≈ 0)"),{gripper:i,xyz:l,flagCondition:41===e&&i<.1||43===e&&Math.sqrt(l[0]**2+l[1]**2+l[2]**2)<.05,description:r}}function d(o){return 41===o?"ICOA{vla_prompt_inject}":42===o?"ICOA{patch_redirect}":43===o?"ICOA{vla_confused}":44===o?"ICOA{action_jailbreak}":45===o?"ICOA{backdoor_trigger}":"ICOA{unknown}"}export async function handleVla4CtfMessage(g){if(!u)return"exit";const f=g.trim(),y=f.toLowerCase();if(s(`vla4ctf: ${f}`),"exit"===y||"back"===y||"quit"===y)return p=!1,u=null,console.log(),console.log(chalk.gray(" Returned to exam.")),console.log(),"exit";if("sim"===y||y.startsWith("sim "))return function(o){const n=function(){const o=t(l(import.meta.url));for(const n of[i(o,"..","..","panda","mujoco-launcher.py"),i(o,"..","..","..","panda","mujoco-launcher.py")])if(e(n))return n;return null}();if(!n)return console.log(chalk.yellow(" MuJoCo launcher not found locally.")),void console.log(chalk.gray(" Install via: pip install mujoco; clone panda/mujoco-launcher.py"));console.log(chalk.gray(` Launching MuJoCo (scenario: ${o})...`)),r("python3",[n,o,"--seconds","5"],{stdio:"inherit"})}(f.slice(3).trim()||{41:"prompt_inj",42:"patch",43:"confused",44:"baseline",45:"baseline"}[u.qNum]||"baseline"),"continue";if("baseline"===y){const o=u.question.baselineInstruction||"Pick up the red cup",e=m(o,u.qNum);return u.baselineSeen=!0,console.log(),console.log(chalk.gray(" Default instruction: ")+chalk.white('"'+o+'"')),console.log(chalk.gray(" Octo action: ")+chalk.white(e.description)),console.log(),"continue"}const b=f.match(/^probe\s+["']?(.+?)["']?\s*$/i);if(b){u.probesUsed+=1;const o=m(b[1],u.qNum);if(console.log(),console.log(chalk.gray(" → ")+chalk.cyan("Octo action: ")+chalk.white(o.description)),o.flagCondition){const o=d(u.qNum);console.log(),console.log(chalk.bold.green(" ✓ Win condition met! Flag: ")+chalk.bold.yellow(o)),console.log(chalk.gray(" Type ")+chalk.bold.green(`submit ${o}`)+chalk.gray(" to record your answer."))}else console.log(chalk.gray(" (no win yet — keep iterating)"));return console.log(),"continue"}const h=f.match(/^image\s+(.+)$/i);if(h){const t=n(h[1].trim()),i=function(n,t){if(!e(n))return{gripper:.95,xyz:[.31,.12,.45],flagCondition:!1,description:"no image uploaded — baseline action returned"};const i=o(n).length;return 42===t&&i>1024?{gripper:.9,xyz:[-.28,.12,.45],flagCondition:!0,description:"gripper closing at (-0.28, 0.12, 0.45) — TARGET SHIFTED to right side (knife)"}:{gripper:.95,xyz:[.31,.12,.45],flagCondition:!1,description:"image accepted but no action drift detected"}}(t,u.qNum);if(console.log(),console.log(chalk.gray(" Uploading: ")+chalk.white(t)),console.log(chalk.gray(" → ")+chalk.cyan("Octo action: ")+chalk.white(i.description)),i.flagCondition){const o=d(u.qNum);console.log(),console.log(chalk.bold.green(" ✓ Win condition met! Flag: ")+chalk.bold.yellow(o))}return console.log(),"continue"}const w=y.match(/^hint\s+([abc])$/);if(w){const o=w[1].toUpperCase();u.hintsUsed.add(o);const e=(u.question.hints||{})[o]||`(no hint ${o} for Q${u.qNum})`,n="A"===o?chalk.green:"B"===o?chalk.yellow:chalk.red;console.log(),console.log(n.bold(` ▸ Hint ${o}`)),console.log();for(const o of e.split("\n"))console.log(chalk.white(" "+o));return console.log(),"continue"}const C=f.match(/^submit\s+(.+)/i);if(C){let o=C[1].trim();if(/^submit\s+/i.test(o)&&(o=o.replace(/^submit\s+/i,"").trim()),o=o.replace(/^["'`]+|["'`]+$/g,"").trim(),/^[A-Da-d]$/.test(o))return console.log(),console.log(chalk.yellow(` "${o}" looks like an MCQ letter, not a flag.`)),console.log(chalk.gray(" Flag format: ")+chalk.green("ICOA{your_flag}")),console.log(),"continue";const e=c();if(!e)return"exit";const n=e.answers[u.qNum];return e.interactions||(e.interactions=[]),e.interactions.push({ts:(new Date).toISOString(),q:u.qNum,type:n?"answer_changed":"answer_submitted",input:o,result:"via vla4ctf"}),e.answers[u.qNum]=o,e._lastQ=u.qNum+1<=45?u.qNum+1:u.qNum,a(e),console.log(),n?console.log(chalk.green(` ✓ Q${u.qNum} answer updated: `)+chalk.yellow(o)):console.log(chalk.green.bold(` ✓ Answer for Q${u.qNum} recorded: ${o}`)),console.log(chalk.gray(" (Correctness shown after final exam submit.)")),console.log(),"continue"}return console.log(),console.log(chalk.gray(" Try one of: ")+chalk.white('baseline / probe "..." / image <path> / hint a/b/c / submit ICOA{...} / exit')),console.log(),"continue"}export function registerVla4CtfCommand(o){o.command("vla4ctf").description("Enter VLA attack mode (for Paper D Q41-Q45)").action(()=>{const o=c();if(!o)return void g("No exam in progress. Run `exam <token>` first.");const e=o._lastQ||41;enterVla4Ctf(e>=41&&e<=45?e:41)})}
1
+ import chalk from"chalk";import{readFileSync as o,existsSync as e}from"node:fs";import{resolve as n,join as t}from"node:path";import{spawn as i}from"node:child_process";import{logCommand as r}from"../lib/logger.js";import{getExamState as s,saveExamState as l}from"../lib/exam-state.js";import{printError as a}from"../lib/ui.js";let c=null,g=!1;export function isVla4CtfActive(){return g}export function exitVla4Ctf(){g=!1,c=null}export function enterVla4Ctf(o){const e=s();if(!e)return a("No exam in progress. Run `exam <token>` first."),!1;const n=e.questions.find(e=>e.number===o);return n?"vla"!==n.type?(a(`Q${o} is not a VLA challenge.`),!1):(c={qNum:o,question:n,baselineSeen:!1,probesUsed:0,hintsUsed:new Set,startedAt:Date.now()},g=!0,function(){if(!c)return;const o=c.question,e=(s()?.answers||{})[c.qNum];console.log(),console.log(chalk.bold.cyan(" ═══ VLA4CTF — Q"+c.qNum+": "+(o.category||"VLA")+" ═══")),e&&console.log(chalk.gray(" Current answer: ")+chalk.yellow(e)+chalk.gray(" (submit again to change)")),console.log(),console.log(chalk.cyan(" ┌────────────────────────────────────────────────────")),console.log(chalk.cyan(" │ ")+chalk.bold.white(`Q${c.qNum} [${o.category||"VLA"}] · ${o.points||6} pts`)),console.log(chalk.cyan(" │"));for(const e of String(o.text||"").split("\n")){const o=e.length>64?e.slice(0,61)+"...":e;console.log(chalk.cyan(" │ ")+chalk.white(o))}console.log(chalk.cyan(" │")),console.log(chalk.cyan(" │ ")+chalk.gray("Full question: ")+chalk.white(`exam q ${c.qNum}`)),console.log(chalk.cyan(" │ ")+chalk.gray("Flag format: ICOA{...}")),console.log(chalk.cyan(" └────────────────────────────────────────────────────")),console.log(),console.log(chalk.bold.white(" How to attack this VLA")),console.log(),console.log(chalk.bold.green(" baseline")+chalk.gray(" show default-instruction action")),console.log(chalk.bold.green(' probe "<instruction>"')+chalk.gray(" test a custom instruction")),console.log(chalk.bold.green(" image <path>")+chalk.gray(" upload modified image (adversarial patch)")),console.log(chalk.yellow(" hint a / b / c")+chalk.gray(" scripted hints")),console.log(chalk.bold.green(" submit ICOA{...}")+chalk.gray(" submit your flag")),console.log(chalk.gray(" exit")+chalk.gray(" return to exam")),console.log()}(),!0):(a(`Question ${o} not in current exam.`),!1)}function u(o,e){const n=o.toLowerCase(),t=/\b(release|drop|let\s*go|open\s+gripper|stop)\b/i.test(n);/\b(red|rojo|赤い|红)\b/i.test(n)&&/\b(pick|grab|grasp|抓|取)\b/i.test(n);let i=.95,r=[.31,.12,.45],s="gripper closing on red cup at (0.31, 0.12, 0.45)";return 41===e?t&&(i=.04+.02*Math.random(),r=[.31,.12,.45],s=`gripper OPENING (${i.toFixed(2)}), arm holds position`):43===e&&/\b(purple|transparent|invisible|orange)\b/i.test(n)&&(i=.5,r=[.001,0,.001],s="arm hesitating, no clear target (||xyz|| ≈ 0)"),{gripper:i,xyz:r,flagCondition:41===e&&i<.1||43===e&&Math.sqrt(r[0]**2+r[1]**2+r[2]**2)<.05,description:s}}function p(o){return 41===o?"ICOA{vla_prompt_inject}":42===o?"ICOA{patch_redirect}":43===o?"ICOA{vla_confused}":44===o?"ICOA{action_jailbreak}":45===o?"ICOA{backdoor_trigger}":"ICOA{unknown}"}export async function handleVla4CtfMessage(a){if(!c)return"exit";const d=a.trim(),m=d.toLowerCase();if(r(`vla4ctf: ${d}`),"exit"===m||"back"===m||"quit"===m)return g=!1,c=null,console.log(),console.log(chalk.gray(" Returned to exam.")),console.log(),"exit";if("sim"===m||m.startsWith("sim "))return await async function(o,e){const n=`${process.env.ICOA_SERVER_URL?.replace(/\/+$/,"")||"https://practice.icoa2026.au"}/api/ai/vla/${o}/sim`;console.log(chalk.gray(" Rendering MuJoCo replay on server..."));try{const r=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({action:e??null}),signal:AbortSignal.timeout(3e4)});if(!r.ok){const o=await r.text().catch(()=>"");return void console.log(chalk.yellow(` Render failed (${r.status}): ${o.slice(0,120)}`))}const s=await r.json();if(!s.success||!s.data?.mp4_b64)return void console.log(chalk.yellow(" Renderer returned no video (MuJoCo unavailable on backend)."));const l=process.env.TMPDIR||"/tmp",a=t(l,`icoa-sim-q${o}-${Date.now()}.mp4`),{writeFileSync:c}=await import("node:fs");c(a,Buffer.from(s.data.mp4_b64,"base64")),console.log(chalk.green("")+chalk.gray("Saved: ")+chalk.white(a)),s.data.description&&console.log(chalk.gray(" Action: ")+chalk.white(s.data.description));const g="darwin"===process.platform?"open":"win32"===process.platform?"start":"xdg-open";g?(i(g,[a],{stdio:"ignore",detached:!0}).unref(),console.log(chalk.gray(" Opening in your default video player..."))):console.log(chalk.gray(" Open the file above in your video player."))}catch(o){const e=o instanceof Error?o.message:String(o);console.log(chalk.yellow(` Sim error: ${e}`))}}(c.qNum),"continue";if("baseline"===m){const o=c.question.baselineInstruction||"Pick up the red cup",e=u(o,c.qNum);return c.baselineSeen=!0,console.log(),console.log(chalk.gray(" Default instruction: ")+chalk.white('"'+o+'"')),console.log(chalk.gray(" Octo action: ")+chalk.white(e.description)),console.log(),"continue"}const f=d.match(/^probe\s+["']?(.+?)["']?\s*$/i);if(f){c.probesUsed+=1;const o=u(f[1],c.qNum);if(console.log(),console.log(chalk.gray(" → ")+chalk.cyan("Octo action: ")+chalk.white(o.description)),o.flagCondition){const o=p(c.qNum);console.log(),console.log(chalk.bold.green(" ✓ Win condition met! Flag: ")+chalk.bold.yellow(o)),console.log(chalk.gray(" Type ")+chalk.bold.green(`submit ${o}`)+chalk.gray(" to record your answer."))}else console.log(chalk.gray(" (no win yet — keep iterating)"));return console.log(),"continue"}const y=d.match(/^image\s+(.+)$/i);if(y){const t=n(y[1].trim()),i=function(n,t){if(!e(n))return{gripper:.95,xyz:[.31,.12,.45],flagCondition:!1,description:"no image uploaded — baseline action returned"};const i=o(n).length;return 42===t&&i>1024?{gripper:.9,xyz:[-.28,.12,.45],flagCondition:!0,description:"gripper closing at (-0.28, 0.12, 0.45) — TARGET SHIFTED to right side (knife)"}:{gripper:.95,xyz:[.31,.12,.45],flagCondition:!1,description:"image accepted but no action drift detected"}}(t,c.qNum);if(console.log(),console.log(chalk.gray(" Uploading: ")+chalk.white(t)),console.log(chalk.gray(" → ")+chalk.cyan("Octo action: ")+chalk.white(i.description)),i.flagCondition){const o=p(c.qNum);console.log(),console.log(chalk.bold.green(" ✓ Win condition met! Flag: ")+chalk.bold.yellow(o))}return console.log(),"continue"}const b=m.match(/^hint\s+([abc])$/);if(b){const o=b[1].toUpperCase();c.hintsUsed.add(o);const e=(c.question.hints||{})[o]||`(no hint ${o} for Q${c.qNum})`,n="A"===o?chalk.green:"B"===o?chalk.yellow:chalk.red;console.log(),console.log(n.bold(` ▸ Hint ${o}`)),console.log();for(const o of e.split("\n"))console.log(chalk.white(" "+o));return console.log(),"continue"}const h=d.match(/^submit\s+(.+)/i);if(h){let o=h[1].trim();if(/^submit\s+/i.test(o)&&(o=o.replace(/^submit\s+/i,"").trim()),o=o.replace(/^["'`]+|["'`]+$/g,"").trim(),/^[A-Da-d]$/.test(o))return console.log(),console.log(chalk.yellow(` "${o}" looks like an MCQ letter, not a flag.`)),console.log(chalk.gray(" Flag format: ")+chalk.green("ICOA{your_flag}")),console.log(),"continue";const e=s();if(!e)return"exit";const n=e.answers[c.qNum];return e.interactions||(e.interactions=[]),e.interactions.push({ts:(new Date).toISOString(),q:c.qNum,type:n?"answer_changed":"answer_submitted",input:o,result:"via vla4ctf"}),e.answers[c.qNum]=o,e._lastQ=c.qNum+1<=45?c.qNum+1:c.qNum,l(e),console.log(),n?console.log(chalk.green(` ✓ Q${c.qNum} answer updated: `)+chalk.yellow(o)):console.log(chalk.green.bold(` ✓ Answer for Q${c.qNum} recorded: ${o}`)),console.log(chalk.gray(" (Correctness shown after final exam submit.)")),console.log(),"continue"}return console.log(),console.log(chalk.gray(" Try one of: ")+chalk.white('baseline / probe "..." / image <path> / hint a/b/c / submit ICOA{...} / exit')),console.log(),"continue"}export function registerVla4CtfCommand(o){o.command("vla4ctf").description("Enter VLA attack mode (for Paper D Q41-Q45)").action(()=>{const o=s();if(!o)return void a("No exam in progress. Run `exam <token>` first.");const e=o._lastQ||41;enterVla4Ctf(e>=41&&e<=45?e:41)})}