Agent工具调用规模化落地:从上下文膨胀到执行治理的完整方案
2026/10/20 4:17:53
参考自动还原(加速版)
参考无界面版
把2个程序放一起运行,即可得到各阶魔方的随机打乱和复原序列。
cube101-110
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>一百一十阶魔方</title> <style> :root { --bg1: #161b26; --bg2: #1f2635; --txt: #e8edf5; --dim: #8fa0bf; --panel: #1b2231; } * { box-sizing: border-box; } html, body { height: 100%; } body { margin: 0; font-family: "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; background: radial-gradient(1200px 700px at 50% -10%, var(--bg2), var(--bg1)); color: var(--txt); padding: 6px 8px 8px; overflow: hidden; height: 100vh; display: flex; flex-direction: column; } h1 { margin: 0 0 2px; font-size: 20px; letter-spacing: 2px; text-align: center; flex-shrink: 0; } #status { min-height: 18px; margin: 0 0 4px; font-size: 12px; color: var(--dim); transition: color .3s; text-align: center; flex-shrink: 0; } #status.solved { color: #7ee08a; font-weight: bold; animation: pop .55s ease; } #status.error { color: #ff7a7a; font-weight: bold; } @keyframes pop { 0% { transform: scale(.6); opacity: 0; } 70% { transform: scale(1.18); } 100% { transform: scale(1); opacity: 1; } } #main { display: flex; gap: 6px; flex: 1; min-height: 0; width: 100%; max-width: 100%; margin: 0 auto; } #leftCol { flex: 0 0 58%; min-width: 0; height: 100%; display: flex; flex-direction: column; } #stage { width: 100%; height: 100%; min-height: 0; background: transparent; border-radius: 8px; overflow: hidden; } #rightCol { flex: 1; min-width: 0; height: 100%; display: flex; flex-direction: column; gap: 4px; overflow: hidden; } #controls { flex-shrink: 0; display: flex; flex-direction: column; gap: 4px; align-items: center; padding: 4px 6px 6px; background: var(--panel); border-radius: 8px; border: 1px solid #2a3344; } .move-row { display: flex; gap: 4px; flex-wrap: wrap; justify-content: center; width: 100%; } button { border: none; border-radius: 6px; padding: 6px 10px; font-size: 13px; font-weight: 600; cursor: pointer; background: #37425c; color: #fff; box-shadow: 0 2px 0 #1a2130; transition: transform .06s, background .15s, box-shadow .06s; min-width: 38px; letter-spacing: .3px; flex-shrink: 0; } button:hover { background: #46557a; } button:active { transform: translateY(2px); box-shadow: 0 0 0 #1a2130; } button.tool { background: #2f7a55; } button.tool:hover { background: #3b9768; } button.tool.warn { background: #8a3d3d; } button.tool.warn:hover { background: #a84c4c; } button.copy { background: #2a6e8f; } button.copy:hover { background: #3588ad; } #autoRunBtn { background: #7a3f8a; } #autoRunBtn:hover { background: #9550a8; } #seqInput { flex: 1; min-width: 60px; padding: 5px 8px; border-radius: 6px; border: 1px solid #4a5a7a; background: #161b26; color: #e8edf5; font-size: 12px; font-family: "Cascadia Code", "Consolas", monospace; width: 100%; } #seqInput:focus { outline: none; border-color: #6d88c8; } #seqRow { flex-wrap: nowrap; width: 100%; } #recordBox { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: 2px; overflow: hidden; } #recordTitle { font-size: 13px; font-weight: 700; color: #cfd9ea; letter-spacing: 0.5px; text-align: center; flex-shrink: 0; margin: 0; } #recordPanel { background: var(--panel); border: 1px solid #2a3344; border-radius: 8px; padding: 6px 10px; font-family: "Cascadia Code", "Consolas", "Courier New", monospace; font-size: 11px; line-height: 1.5; color: #cfd9ea; white-space: pre-wrap; word-break: break-all; flex: 1; min-height: 0; overflow: auto; margin: 0; } #copyBtn { width: 100%; flex-shrink: 0; padding: 5px 0; font-size: 12px; } @media (max-width: 860px) { body { padding: 4px 6px 6px; } #main { flex-direction: column; gap: 6px; } #leftCol { flex: 0 0 55vh; width: 100%; } #rightCol { flex: 1; min-height: 260px; } #controls { padding: 4px; } button { padding: 5px 8px; font-size: 12px; min-width: 34px; } #stage { height: 100%; } #recordPanel { font-size: 10px; padding: 4px 8px; } h1 { font-size: 18px; } } @media (max-width: 480px) { #leftCol { flex: 0 0 45vh; } #seqRow { flex-wrap: wrap; } #seqInput { flex: 1 1 100%; } } </style> </head> <body> <h1>一百一十阶魔方</h1> <div id="status">加载中…</div> <div id="main"> <div id="leftCol"> <div id="stage"></div> </div> <div id="rightCol"> <div id="controls"> <div class="move-row"> <button class="tool" id="scrambleBtn">打乱</button> <button class="tool warn" id="resetBtn">复原</button> <button class="tool" id="autoRunBtn">自动运行</button> </div> <div class="move-row" id="seqRow"> <input id="seqInput" type="text" placeholder="操作序列,如 U2' F3 R109'" /> <button class="tool" id="seqBtn">执行</button> </div> </div> <div id="recordBox"> <div id="recordTitle">📋 棋谱</div> <button class="tool copy" id="copyBtn">📋 一键复制棋谱</button> <pre id="recordPanel"></pre> </div> </div> </div> <script type="importmap"> { "imports": { "three": "https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js" } } </script> <script type="module"> import * as THREE from 'three'; // ---------- 场景 ---------- const stage = document.getElementById('stage'); const statusEl = document.getElementById('status'); const recordPanel = document.getElementById('recordPanel'); const copyBtn = document.getElementById('copyBtn'); const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); function resizeRenderer() { const w = stage.clientWidth; const h = stage.clientHeight; if (w > 0 && h > 0) { renderer.setSize(w, h, false); camera.aspect = w / h; camera.updateProjectionMatrix(); } } stage.appendChild(renderer.domElement); const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(30, stage.clientWidth / stage.clientHeight, 0.1, 700); camera.position.set(174, 131, 217); camera.lookAt(0, 0, 0); scene.add(new THREE.AmbientLight(0xffffff, 0.9)); const light1 = new THREE.DirectionalLight(0xffffff, 1.3); light1.position.set(5, 9, 6); scene.add(light1); const light2 = new THREE.DirectionalLight(0xffffff, 0.45); light2.position.set(-4, -3, -2); scene.add(light2); // ---------- 颜色 ---------- const COL_HEX = { U: '#f5f5f5', D: '#ffd500', R: '#3b82f6', L: '#3aab4e', F: '#e33c3c', B: '#ff8c1a' }; const COL_CHAR = { U: 'W', D: 'Y', R: 'B', L: 'G', F: 'R', B: 'O' }; // ---------- 一百一十阶坐标 ---------- const N = 110; const OUT = 54.5; const POSITIONS = []; for (let i = 0; i < N; i++) { POSITIONS.push(-54.5 + i); } const SIZE = 0.82; const AXIS_MAP = { U: { axis: 'y', side: 1, vec: new THREE.Vector3(0, 1, 0) }, D: { axis: 'y', side: -1, vec: new THREE.Vector3(0, -1, 0) }, R: { axis: 'x', side: 1, vec: new THREE.Vector3(1, 0, 0) }, L: { axis: 'x', side: -1, vec: new THREE.Vector3(-1, 0, 0) }, F: { axis: 'z', side: 1, vec: new THREE.Vector3(0, 0, 1) }, B: { axis: 'z', side: -1, vec: new THREE.Vector3(0, 0, -1) } }; const FACE_SIGN = { 'R': -1, 'L': 1, 'U': -1, 'D': 1, 'F': -1, 'B': 1 }; function buildLayerDefs() { const defs = {}; const faces = ['U','D','R','L','F','B']; for (const f of faces) { const base = AXIS_MAP[f]; for (let layer = 1; layer <= N-1; layer++) { const key = layer === 1 ? f : f + layer; let targets; if (base.side === 1) { targets = POSITIONS.slice(N - layer); } else { targets = POSITIONS.slice(0, layer); } defs[key] = { axis: base.axis, targets: targets, face: f, sign: FACE_SIGN[f] }; } } return defs; } const LAYER_DEFS = buildLayerDefs(); const ROT_DEF = { x: { axis: 'x' }, y: { axis: 'z' }, z: { axis: 'y' } }; const FACE_ORDER = ['U','D','L','R','F','B']; // ---------- 贴纸纹理 ---------- function roundedRect(ctx, x, y, w, h, r) { ctx.beginPath(); ctx.moveTo(x + r, y); ctx.arcTo(x + w, y, x + w, y + h, r); ctx.arcTo(x + w, y + h, x, y + h, r); ctx.arcTo(x, y + h, x, y, r); ctx.arcTo(x, y, x + w, y, r); ctx.closePath(); } function makeStickerTexture(hex) { const size = 128; const canvas = document.createElement('canvas'); canvas.width = canvas.height = size; const ctx = canvas.getContext('2d'); ctx.fillStyle = '#0b0b0d'; roundedRect(ctx, 0, 0, size, size, 14); ctx.fill(); const m = 17; ctx.fillStyle = hex; roundedRect(ctx, m, m, size - 2 * m, size - 2 * m, 9); ctx.fill(); const tex = new THREE.CanvasTexture(canvas); tex.colorSpace = THREE.SRGBColorSpace; tex.anisotropy = 4; return tex; } const TEX = {}; for (const f in COL_HEX) TEX[f] = makeStickerTexture(COL_HEX[f]); const blackPlastic = new THREE.MeshStandardMaterial({ color: 0x0a0a0c, roughness: 0.62, metalness: 0.08 }); function stickerMaterial(face) { return new THREE.MeshStandardMaterial({ map: TEX[face], roughness: 0.3, metalness: 0.05 }); } // ---------- 表面块(含逻辑状态) ---------- let cubies = []; function createCubie(x, y, z) { const mats = [ x === OUT ? stickerMaterial('R') : blackPlastic, x === -OUT ? stickerMaterial('L') : blackPlastic, y === OUT ? stickerMaterial('U') : blackPlastic, y === -OUT ? stickerMaterial('D') : blackPlastic, z === OUT ? stickerMaterial('F') : blackPlastic, z === -OUT ? stickerMaterial('B') : blackPlastic ]; const geo = new THREE.BoxGeometry(SIZE, SIZE, SIZE); const mesh = new THREE.Mesh(geo, mats); mesh.position.set(x, y, z); scene.add(mesh); const colors = { px: x === OUT ? COL_CHAR.R : null, nx: x === -OUT ? COL_CHAR.L : null, py: y === OUT ? COL_CHAR.U : null, ny: y === -OUT ? COL_CHAR.D : null, pz: z === OUT ? COL_CHAR.F : null, nz: z === -OUT ? COL_CHAR.B : null }; return { mesh, home: new THREE.Vector3(x, y, z), colors, pos: new THREE.Vector3(x, y, z), quat: new THREE.Quaternion() }; } function buildCube() { cubies.forEach(c => scene.remove(c.mesh)); cubies = []; for (const x of POSITIONS) for (const y of POSITIONS) for (const z of POSITIONS) { if (Math.abs(x) === OUT || Math.abs(y) === OUT || Math.abs(z) === OUT) { cubies.push(createCubie(x, y, z)); } } } // ---------- 无动画即时旋转 ---------- const AXIS_VEC = { x: new THREE.Vector3(1, 0, 0), y: new THREE.Vector3(0, 1, 0), z: new THREE.Vector3(0, 0, 1) }; function snap(v) { return Math.round(v * 2) / 2; } function rotateCubie(c, axis, angle) { const q = new THREE.Quaternion().setFromAxisAngle(AXIS_VEC[axis], angle); c.pos.applyQuaternion(q); c.pos.x = snap(c.pos.x); c.pos.y = snap(c.pos.y); c.pos.z = snap(c.pos.z); c.quat.premultiply(q); } function selectCubiesForLayer(faceKey) { const def = LAYER_DEFS[faceKey]; if (!def) return []; return cubies.filter(c => { const val = c.pos[def.axis]; return def.targets.some(t => Math.abs(val - t) < 0.01); }); } function applyInstantMove(faceKey, dir) { if (ROT_DEF[faceKey]) { const axis = ROT_DEF[faceKey].axis; const angle = -dir * Math.PI / 2; cubies.forEach(c => rotateCubie(c, axis, angle)); return; } const def = LAYER_DEFS[faceKey]; if (!def) return; const selected = selectCubiesForLayer(faceKey); const angle = dir * def.sign * Math.PI / 2; selected.forEach(c => rotateCubie(c, def.axis, angle)); } function syncMeshes() { for (const c of cubies) { c.mesh.position.copy(c.pos); c.mesh.quaternion.copy(c.quat); } } // ---------- 工具函数 ---------- function axisToKey(v) { const ax = Math.abs(v.x), ay = Math.abs(v.y), az = Math.abs(v.z); if (ax >= ay && ax >= az) return v.x >= 0 ? 'px' : 'nx'; if (ay >= ax && ay >= az) return v.y >= 0 ? 'py' : 'ny'; return v.z >= 0 ? 'pz' : 'nz'; } function isSolved() { const st = getState(); for (const f of FACE_ORDER) { const s = st[f]; if (!s || s.length !== N*N) return false; for (let i = 1; i < N*N; i++) { if (s[i] !== s[0]) return false; } } return true; } // ---------- 局面读取(基于逻辑状态) ---------- function getFaceSlots(face) { const s = []; for (let r = 0; r < N; r++) for (let c = 0; c < N; c++) { const x = c - (N-1)/2; const yTop = (N-1)/2 - r; const z = r - (N-1)/2; let p; if (face === 'U') p = [x, OUT, z]; else if (face === 'D') p = [x, -OUT, z]; else if (face === 'F') p = [x, yTop, OUT]; else if (face === 'B') p = [-x, yTop, -OUT]; else if (face === 'R') p = [OUT, yTop, -x]; else p = [-OUT, yTop, x]; s.push(new THREE.Vector3(p[0], p[1], p[2])); } return s; } function faceColorString(face) { const slots = getFaceSlots(face); const n = AXIS_MAP[face].vec; let out = ''; for (const p of slots) { const cubie = cubies.find(c => c.pos.distanceTo(p) < 0.01); if (!cubie) { out += '?'; continue; } const n0 = n.clone().applyQuaternion(cubie.quat.clone().invert()); out += cubie.colors[axisToKey(n0)] || '?'; } return out; } function getState() { const state = {}; for (const f of FACE_ORDER) state[f] = faceColorString(f); return state; } function stateToText(state) { return FACE_ORDER.map(f => f + ': ' + state[f]).join('\n'); } function moveNotation(m) { return m.face + (m.dir === -1 ? "'" : ''); } // 每 1000 个操作换行一次,单个操作(如 R109')不被换行符打断 function seqToText(seq) { if (!seq.length) return '(无)'; const lines = []; let cur = []; for (const m of seq) { cur.push(moveNotation(m)); if (cur.length === 1000) { lines.push(cur.join(' ')); cur = []; } } if (cur.length) lines.push(cur.join(' ')); return lines.join('\n'); } // ---------- 棋谱 ---------- let moveCount = 0; let scrambleSeq = []; let userSeq = []; let initialState = null; function buildRecordText() { const lines = []; lines.push('一百一十阶魔方棋谱 · 层操作'); lines.push(''); lines.push('【打乱序列】'); lines.push(seqToText(scrambleSeq)); lines.push(''); lines.push('【初始局面(打乱后)】'); lines.push(initialState ? stateToText(initialState) : '(打乱中…)'); lines.push(''); lines.push('【当前局面】'); lines.push(stateToText(getState())); lines.push(''); lines.push('【完整操作序列】'); lines.push(seqToText([...scrambleSeq, ...userSeq])); return lines.join('\n'); } function updateRecordPanel() { recordPanel.textContent = buildRecordText(); } function refreshVisual() { syncMeshes(); updateRecordPanel(); } async function copyRecord() { const text = buildRecordText(); let ok = false; try { await navigator.clipboard.writeText(text); ok = true; } catch (e) { const ta = document.createElement('textarea'); ta.value = text; ta.style.position = 'fixed'; ta.style.opacity = '0'; document.body.appendChild(ta); ta.select(); try { ok = document.execCommand('copy'); } catch (_) {} ta.remove(); } const old = copyBtn.textContent; copyBtn.textContent = ok ? '✅ 已复制' : '❌ 复制失败'; setTimeout(() => { copyBtn.textContent = old; }, 1500); } // ---------- 序列解析与执行 ---------- function parseSequence(text) { const normalized = text.replace(/[’′]/g, "'"); const tokens = normalized.trim().split(/[\s,,]+/).filter(Boolean); const moves = []; const errors = []; for (const raw of tokens) { let m = raw.match(/^([URFDLB])(10[0-9]|9[0-9]|8[0-9]|7[0-9]|[1-6][0-9]|[2-9])?([''])?$/); if (m) { const letter = m[1]; const layerNum = m[2] || ''; const dir = m[3] === "'" ? -1 : 1; let faceKey = layerNum === '' ? letter : letter + layerNum; if (!LAYER_DEFS[faceKey]) { errors.push('未知操作: ' + raw); continue; } moves.push({ face: faceKey, dir }); continue; } const rotMatch = raw.match(/^([xyz])([''])?$/); if (rotMatch) { moves.push({ face: rotMatch[1], dir: rotMatch[2] === "'" ? -1 : 1 }); continue; } errors.push('无法解析: ' + raw); } return { moves, errors }; } async function executeSequence(text) { const { moves, errors } = parseSequence(text); if (errors.length > 0) { statusEl.textContent = '❌ 非法输入: ' + errors.join('; '); statusEl.classList.add('error'); statusEl.classList.remove('solved'); return; } if (moves.length === 0) { statusEl.textContent = '⚠️ 未检测到有效操作'; statusEl.classList.remove('solved', 'error'); return; } statusEl.classList.remove('error'); for (let i = 0; i < moves.length; i++) { const mv = moves[i]; const isRot = !!ROT_DEF[mv.face]; applyInstantMove(mv.face, mv.dir); userSeq.push({ face: mv.face, dir: mv.dir }); if (!isRot) moveCount++; const isChunkEnd = (i + 1) % 1000 === 0; const isLast = i === moves.length - 1; if (isChunkEnd || isLast) { refreshVisual(); await new Promise(r => setTimeout(r, 0)); } } if (isSolved()) { statusEl.textContent = '🎉 已复原!共用了 ' + moveCount + ' 步'; statusEl.classList.add('solved'); statusEl.classList.remove('error'); } else { statusEl.textContent = '序列执行完成(' + moves.length + ' 步操作)'; statusEl.classList.remove('solved', 'error'); } } // ---------- 自动运行 ---------- const SERVER = 'http://localhost:3000'; async function autoRun() { const stateText = stateToText(getState()); try { const res = await fetch(SERVER + '/write-input', { method: 'POST', headers: { 'Content-Type': 'text/plain; charset=utf-8' }, body: stateText }); if (!res.ok) throw new Error('write failed'); } catch (e) { statusEl.textContent = '❌ 无法连接本地服务,请先运行 server.js 并从 http://localhost:3000 打开页面'; statusEl.classList.add('error'); statusEl.classList.remove('solved'); return; } statusEl.textContent = '已写入 D:/cubeInput.txt,等待 D:/cubeOutput.txt…'; statusEl.classList.remove('solved', 'error'); let content = ''; const maxWait = 600000; // 10 分钟 const start = performance.now(); while (true) { if (performance.now() - start > maxWait) { statusEl.textContent = '等待 D:/cubeOutput.txt 超时'; statusEl.classList.add('error'); statusEl.classList.remove('solved'); return; } try { const res = await fetch(SERVER + '/read-output'); const data = await res.json(); content = (data.content || '').trim(); } catch (e) {} if (content) break; await new Promise(r => setTimeout(r, 500)); } await executeSequence(content); } // ---------- 打乱 / 复原 ---------- function resetCubeInternal() { cubies.forEach(c => { c.pos.copy(c.home); c.quat.identity(); }); syncMeshes(); } function generateScramble(n = 500) { const faces = Object.keys(LAYER_DEFS); const seq = []; let last = null; for (let i = 0; i < n; i++) { let f, d; do { f = faces[Math.floor(Math.random() * faces.length)]; d = Math.random() < 0.5 ? 1 : -1; } while (last && last === f); seq.push({ face: f, dir: d }); last = f; } return seq; } async function scramble() { resetCubeInternal(); userSeq = []; initialState = null; moveCount = 0; statusEl.textContent = '打乱中…'; statusEl.classList.remove('solved', 'error'); scrambleSeq = generateScramble(500); if (scrambleSeq.length === 0) { statusEl.textContent = '打乱序列为空'; return; } for (let i = 0; i < scrambleSeq.length; i++) { applyInstantMove(scrambleSeq[i].face, scrambleSeq[i].dir); const isChunkEnd = (i + 1) % 1000 === 0; const isLast = i === scrambleSeq.length - 1; if (isChunkEnd || isLast) { refreshVisual(); await new Promise(r => setTimeout(r, 0)); } } initialState = getState(); moveCount = 0; statusEl.textContent = '已打乱,开始复原吧'; statusEl.classList.remove('error'); } function resetCube() { resetCubeInternal(); moveCount = 0; scrambleSeq = []; userSeq = []; initialState = null; statusEl.textContent = '已还原到初始状态'; statusEl.classList.remove('solved', 'error'); refreshVisual(); } // ---------- 按钮 & 键盘 ---------- function buildButtons() { document.getElementById('scrambleBtn').addEventListener('click', scramble); document.getElementById('resetBtn').addEventListener('click', resetCube); copyBtn.addEventListener('click', copyRecord); const seqInput = document.getElementById('seqInput'); const seqBtn = document.getElementById('seqBtn'); seqBtn.addEventListener('click', () => executeSequence(seqInput.value)); seqInput.addEventListener('keydown', e => { if (e.key === 'Enter') { e.preventDefault(); executeSequence(seqInput.value); } }); document.getElementById('autoRunBtn').addEventListener('click', autoRun); window.addEventListener('keydown', e => { const key = e.key.toUpperCase(); if (/^[URFDLB]$/.test(key)) { const dir = e.shiftKey ? -1 : 1; applyInstantMove(key, dir); userSeq.push({ face: key, dir }); moveCount++; refreshVisual(); if (isSolved()) { statusEl.textContent = '🎉 已复原!共用了 ' + moveCount + ' 步'; statusEl.classList.add('solved'); statusEl.classList.remove('error'); } else { statusEl.textContent = '步数:' + moveCount; statusEl.classList.remove('solved', 'error'); } return; } if (/^[XYZ]$/.test(key)) { const face = key.toLowerCase(); const dir = e.shiftKey ? -1 : 1; applyInstantMove(face, dir); userSeq.push({ face, dir }); refreshVisual(); return; } }); } // ---------- 渲染循环 ---------- function animate() { requestAnimationFrame(animate); renderer.render(scene, camera); } // ---------- 窗口自适应 ---------- function onResize() { resizeRenderer(); } window.addEventListener('resize', onResize); const ro = new ResizeObserver(() => { resizeRenderer(); }); ro.observe(stage); // ---------- 启动 ---------- buildCube(); buildButtons(); statusEl.textContent = '已就绪'; updateRecordPanel(); setTimeout(() => { scramble(); }, 100); animate(); setTimeout(resizeRenderer, 50); </script> </body> </html>cube111-120
cube121-130