diff --git a/.gitignore b/.gitignore index f204169..2e9bf09 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,8 @@ npm-debug.log* # Data data/ + +# Claude +.claude/ +CLAUDE.md +diagnostics/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..6f7f805 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,294 @@ +# AGENTS.md + +Guidance for coding agents working in `D:\projects\mc-server-bot`. + +## Repository Shape + +- This repo has two TypeScript apps: +- Backend bot sidecar in the repository root (`src/`, compiled to `dist/`). +- Frontend dashboard in `web/` (Next.js App Router). +- Core backend domains: +- `src/bot/` bot lifecycle and Mineflayer connection handling. +- `src/voyager/` task planning, code execution, critic loop, skill storage. +- `src/actions/` primitive bot actions. +- `src/personality/` affinity and conversation systems. +- `src/social/` bot-to-bot messaging and memory. +- `src/control/` fleet control platform (commands, missions, markers, squads, roles, commander). +- `src/server/` Express + Socket.IO API. + +## Source Of Truth + +- Follow existing code over generic style advice. +- Respect `CLAUDE.md` in the repo root; it contains operational project notes. +- No `.cursorrules`, `.cursor/rules/`, or `.github/copilot-instructions.md` files were present when this file was written. +- There was no existing repo-root `AGENTS.md`; this file is the canonical agent guide. + +## Environment And Setup + +- Install backend deps from the repo root with `npm install`. +- Install frontend deps with `npm install --prefix web` if needed. +- Copy `.env.example` to `.env` and set `GOOGLE_API_KEY` for AI-enabled bot behavior. +- Main runtime config lives in `config.yml`. +- Persistent data is stored under `data/` and learned skills under `skills/`. + +## Build, Lint, Test, Run + +### Backend (repo root) + +- Build: `npm run build` +- Dev run: `npm run dev` +- Production run: `npm start` +- Preferred production run with log capture: `node dist/index.js > /tmp/dyobot.log 2>&1 & disown` +- Before restarting a server process, kill the old listener first: `lsof -ti:3001 | xargs kill -9 2>/dev/null; sleep 2` + +### Frontend (`web/`) + +- Dev run: `npm run dev --prefix web` +- Build: `npm run build --prefix web` +- Start built app: `npm run start --prefix web` +- Lint entire frontend: `npm run lint --prefix web` +- Lint a single file: `npm run lint --prefix web -- src/app/page.tsx` +- Lint a folder: `npm run lint --prefix web -- src/components` + +### Testing + +Run all backend tests: +```bash +npm test +``` + +Run backend tests in watch mode: +```bash +npm run test:watch +``` + +Run a specific test file: +```bash +npx vitest run test/control/CommandCenter.test.ts +``` + +Run all control platform tests: +```bash +npx vitest run test/control/ +``` + +Available test files: +- `test/control/CommandCenter.test.ts` - command dispatch, cancellation, timeout, fan-out +- `test/control/MissionManager.test.ts` - mission lifecycle, VoyagerLoop bridge, dependencies, queues +- `test/control/MarkerStore.test.ts` - markers, zones, routes, spatial lookup, zone containment +- `test/control/SquadManager.test.ts` - squad CRUD, membership, getSquadsForBot +- `test/control/RoleManager.test.ts` - role assignments, one-role-per-bot, overrides, expiry +- `test/control/CommanderService.test.ts` - NL parsing (with/without LLM), plan execution +- `test/control/integration.test.ts` - cross-service integration (commands, missions, markers, squads) + +Run frontend tests: +```bash +cd web && npm test +``` + +### Useful Runtime Checks + +- Bot API status: `curl -s http://127.0.0.1:3001/api/status` +- List bots: `curl -s http://127.0.0.1:3001/api/bots` +- Stream logs: `tail -f /tmp/dyobot.log` +- Filter important backend log events: `grep -E "task proposed|Execution result|task evaluated" /tmp/dyobot.log` + +## Control Platform Services (`src/control/`) + +The control platform provides centralized fleet management: + +- **CommandCenter** (`CommandCenter.ts`) - Dispatches immediate bot commands (pause, resume, stop, move, follow, guard, patrol, unstuck). Handles fan-out for multi-bot commands, timeout detection, concurrent command protection, and cancellation with pathfinder cleanup. +- **MissionManager** (`MissionManager.ts`) - Manages longer-running missions with full lifecycle (draft, queued, running, paused, completed, failed, cancelled). Bridges to VoyagerLoop for `queue_task` missions, checks command dependencies before starting, detects stale missions, and maintains per-bot priority queues. +- **MarkerStore** (`MarkerStore.ts`) - Persists world markers (named 3D positions), zones (rectangular or circular 2D areas), and routes (ordered waypoint sequences). Provides spatial helpers: `findNearestMarker` and `isInsideZone`. +- **SquadManager** (`SquadManager.ts`) - CRUD for squads with bot membership management. Supports `getSquadsForBot` lookup. +- **RoleManager** (`RoleManager.ts`) - One-role-per-bot assignment system with autonomy levels (manual, assisted, autonomous). Tracks manual overrides with 5-minute auto-expiry. +- **CommanderService** (`CommanderService.ts`) - Natural language command parsing via LLM. Produces structured plans with confidence scores, then executes plans by dispatching commands and creating missions. + +## API Endpoint Summary + +### Bot Management +- `GET /api/status` - server status +- `GET/POST/DELETE /api/bots` - list, create, delete all bots +- `GET/DELETE /api/bots/:name` - get or delete a specific bot +- `POST /api/bots/:name/mode` - change bot mode +- `GET /api/bots/:name/detailed` - detailed bot info +- `GET /api/bots/:name/inventory` - bot inventory +- `GET /api/bots/:name/relationships` - bot relationships +- `GET /api/bots/:name/conversations` - bot conversation history +- `GET /api/bots/:name/tasks` - bot task history +- `POST /api/bots/:name/chat` - send chat as bot +- `POST /api/bots/:name/task` - queue a task + +### Bot Actions (convenience shortcuts) +- `POST /api/bots/:name/pause` - pause voyager +- `POST /api/bots/:name/resume` - resume voyager +- `POST /api/bots/:name/stop` - stop movement +- `POST /api/bots/:name/follow` - follow a player +- `POST /api/bots/:name/walkto` - walk to coordinates + +### Commands +- `POST/GET /api/commands` - create and list commands +- `GET /api/commands/:id` - get a command +- `POST /api/commands/:id/cancel` - cancel a command + +### Missions +- `POST/GET /api/missions` - create and list missions +- `GET /api/missions/:id` - get a mission +- `POST /api/missions/:id/start|pause|resume|cancel|retry` - lifecycle actions +- `GET/PATCH /api/bots/:name/mission-queue` - per-bot mission queue + +### World (Markers, Zones, Routes) +- `GET/POST /api/markers` - list and create markers +- `PATCH/DELETE /api/markers/:id` - update and delete +- `GET/POST /api/zones` - list and create zones +- `PATCH/DELETE /api/zones/:id` - update and delete +- `GET/POST /api/routes` - list and create routes +- `PATCH/DELETE /api/routes/:id` - update and delete + +### Squads +- `GET/POST /api/squads` - list and create squads +- `GET/PATCH/DELETE /api/squads/:id` - CRUD +- `POST /api/squads/:id/members` - add bot +- `DELETE /api/squads/:id/members/:botName` - remove bot + +### Roles +- `GET /api/roles` - list all role assignments +- `POST /api/roles/assignments` - create assignment +- `GET/PATCH/DELETE /api/roles/assignments/:id` - CRUD +- `GET/DELETE /api/bots/:name/override` - get/clear override + +### Commander (NL parsing) +- `POST /api/commander/parse` - parse natural language into a plan +- `POST /api/commander/execute` - execute a parsed plan + +### Other +- `GET /api/relationships` - all bot relationships +- `GET /api/skills` - list skills +- `GET /api/skills/:name` - get a skill +- `GET /api/world` - world state +- `GET /api/blackboard` - shared blackboard +- `GET /api/activity` - activity log +- `POST /api/swarm` - spawn multiple bots +- `POST /api/events/chat|player-join|player-leave` - event hooks + +## Running the Dashboard + +Backend (port 3001): +```bash +npm run build && npm start +``` + +Frontend (port 3000, in a separate terminal): +```bash +npm run dev --prefix web +``` + +The frontend connects to the backend API at `http://localhost:3001` and uses Socket.IO for real-time updates. + +## Verified Commands + +- `npm run build` in the repo root succeeds. +- `npm run lint --prefix web` currently reports existing frontend warnings and errors. +- Do not assume the frontend is lint-clean before making changes; check whether failures are pre-existing. + +## TypeScript And Build Expectations + +- Backend TypeScript is strict (`strict: true`) and compiles with `tsc` to `dist/`. +- Backend module target is CommonJS. +- Frontend TypeScript is also strict and uses Next.js bundler resolution. +- Frontend path alias `@/*` maps to `web/src/*`. +- Avoid introducing new tsconfig relaxations unless absolutely necessary. + +## Import Conventions + +- Keep imports at the top of the file. +- Backend usually groups imports as: external packages, then local relative imports. +- Frontend usually prefers project alias imports like `@/components/...` and `@/lib/...` over deep relative paths. +- Use `import type` for type-only imports when practical; the repo already does this in multiple places. +- Prefer named exports for utilities, functions, classes, and interfaces. +- Re-export small action surfaces through barrel files only where the repo already does so, such as `src/actions/index.ts`. + +## Formatting Conventions + +- Backend files predominantly use single quotes and semicolons. +- Frontend files are mixed, but many current files also use single quotes; preserve the style of the file you touch. +- Use 2-space indentation. +- Keep object literals and JSX props multiline when they become dense. +- Prefer trailing commas in multiline objects, arrays, params, and JSX where existing formatting already uses them. +- Do not reformat unrelated files just to normalize quote style. + +## Naming Conventions + +- Classes, interfaces, type aliases, enums: `PascalCase`. +- Functions, methods, variables, object keys: `camelCase`. +- Constants that are true constants or config arrays: `UPPER_SNAKE_CASE`. +- Filenames for backend classes and domain modules often use `PascalCase.ts` (`BotManager.ts`, `VoyagerLoop.ts`). +- Filenames for simple action helpers often use `camelCase.ts` (`mineBlock.ts`, `walkTo.ts`). +- Route/page files in Next.js must follow framework naming (`page.tsx`, `layout.tsx`). + +## Types And Data Modeling + +- Prefer explicit interfaces and type aliases for API shapes and domain records. +- Reuse existing exported types instead of recreating parallel shapes. +- Keep backend request and response payloads structurally simple and JSON-friendly. +- Prefer `Record` for map-like JSON data already persisted or returned by APIs. +- Minimize `any`; existing backend code uses `any` at third-party or parsing boundaries, but new code should prefer narrowing. +- In the frontend, ESLint currently enforces `@typescript-eslint/no-explicit-any`; avoid introducing new `any` there. +- Use union string literals for finite states, modes, and statuses when practical. + +## Error Handling + +- Fail early on invalid input and return structured errors. +- In Express handlers, validate request data first and respond with `400`, `404`, `409`, or `500` as appropriate. +- After sending an Express response in a guard branch, `return` immediately. +- Log operational failures with the shared `logger` from `src/util/logger.ts`. +- Include contextual fields in logs (`bot`, `player`, `filename`, etc.) when they aid diagnosis. +- Throw `Error` objects for fatal backend failures; return `{ success: false, message }` for action-style helper results. +- Preserve existing user-facing phrasing unless there is a reason to improve clarity. + +## Backend Coding Patterns + +- Keep bot action helpers small and outcome-oriented; they usually return `{ success, message, data? }`. +- Normalize bot lookup keys with `name.toLowerCase()` when interacting with `BotManager` maps. +- Prefer synchronous filesystem access only in startup/load/save paths where the repo already does that. +- Keep API route logic thin; push behavior into coordinators, managers, or domain classes when it grows. +- Use the shared singleton logger instead of ad hoc `console.log`. +- Preserve Mineflayer and Socket.IO integration patterns already established in the repo. + +## Frontend Coding Patterns + +- Add `'use client';` only when a component actually needs client-side hooks or browser APIs. +- Prefer Zustand store access through selectors (`useBotStore((s) => s.botList)`). +- Keep API access centralized in `web/src/lib/api.ts`. +- Prefer typed props and typed API responses. +- Use existing visual language and Tailwind utility patterns rather than inventing a separate design system. +- Keep pages focused on orchestration and rendering; move reusable UI into `web/src/components/`. + +## State, Side Effects, And React + +- Keep effects for I/O, subscriptions, and synchronization work. +- Avoid introducing new lint violations around `setState` inside effects, ref mutation during render, or missing dependencies. +- Derive UI state from props/store when possible instead of duplicating it locally. +- Memoize callbacks only when it meaningfully helps dependency stability or expensive rendering. + +## Working In A Dirty Repo + +- The working tree may contain user changes. +- Never revert or overwrite unrelated edits you did not make. +- If a file already has unrelated modifications, make the smallest safe change that solves the task. +- When reporting results, distinguish your changes from pre-existing lint or code issues. + +## Files And Generated Artifacts + +- Do not hand-edit `dist/` unless the user explicitly asks. +- Make source changes in `src/` and `web/src/`. +- Treat `data/` and `skills/` as runtime artifacts unless the task is specifically about their contents. +- Avoid committing secrets from `.env` or other local-only files. + +## Suggested Agent Workflow + +- Read the relevant source files first and infer local conventions before editing. +- For backend changes, run `npm run build` from the repo root. +- For frontend changes, run `npm run lint --prefix web` on touched files or the full app when practical. +- If you add a new command, script, or workflow, update this file. +- If you add tests, include both full-suite and single-test commands here. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..94eadf8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,114 @@ +# Changelog + +All notable changes to DyoBot are documented in this file. + +--- + +## [Unreleased] — 2026-03-25 + +### Bug Fixes +- **Fix player task responsiveness** — Player chat tasks now interrupt autonomous tasks instead of queuing behind them. Auto-resumes voyager loop if paused. Removed task decomposition for chat requests (was turning "scout for an island" into "mine logs → craft planks → craft boat"). +- **Fix memory leaks in BotInstance** — Reflection interval, pendingConnectTimeout, reconnectTimer now cleared on disconnect. BotComms listener unregistered. Duplicate message listeners on reconnect prevented. Chat cooldown map cleared. Inventory debounce timer cleared. EventEmitter listeners cleaned up. +- **Fix container/furnace never closing on error** — All container and furnace operations now use try-finally to guarantee close() runs even on exceptions. +- **Wire up affinity system** — `onHit()` now fires when players attack bots (affinity penalty). `isHostile()` checked before auto-attacking players. `onGift()` callback added to giveItem action. +- **Fix file write race conditions** — Added 2-second debounced writes with atomic temp-file-then-rename to BlackboardManager, AffinityManager, SocialMemory, StatsTracker, BlockerMemory, and WorldMemory. Added shutdown flush chain from index.ts through all managers. +- **Fix walkTo race condition** — Added `finished` guard to prevent double promise resolution. Added `pathfinder.stop()` on noPath. Proper listener cleanup. +- **Fix attack.ts double resolve** — Added `finished` guard in `finish()` to prevent multiple resolve/reject calls from concurrent code paths. +- **Fix path traversal vulnerability** — Schematic filename validation now rejects `..`, `/`, `\` in GET and POST endpoints. +- **Fix override expiry never checked** — `checkOverrideTimeouts()` now runs on 30-second interval in RoleManager. +- **Fix voyager pause flag desync** — Replaced boolean `voyagerPausedByInstinct` with Set-based `pauseReasons` system. `forceResume()` clears all reasons for player/dashboard overrides. +- **Fix squad mission resolution** — Missions with `assigneeType: 'squad'` now resolve squad IDs to actual bot names. `cancelMission()` also resolves correctly. Empty squads fail immediately instead of creating zombie missions. +- **Fix multi-bot mission progress** — Progress check now waits for ALL assignees to complete instead of completing on first bot finish. +- **Fix inventory listener crash on startup** — Moved `inventory.on('updateSlot')` into spawn handler since mineflayer inventory isn't available until after spawn. +- **Fix bot-to-bot message loop risk** — Tightened keyword detector to require direct request phrases ("please", "can you", etc.) to avoid matching status chatter. +- **Auto-cleanup build bots** — Bots created specifically for a build job are automatically removed when the build completes. + +### Features +- **Personality-specific task selection** — Each personality type (farmer, merchant, builder, guard, explorer, blacksmith, elder) now has a weighted task pool. 65% chance to pick personality-appropriate tasks before falling back to generic progression. +- **Emotional state drives behavior** — Bot mood now affects ambient chat frequency: lonely bots chat more (5-10 min), annoyed bots chat less (20-40 min), scared bots are quiet (15-30 min). Idle detection triggers `idle_long` → lonely mood after 10 minutes of no interaction. +- **Bot-to-bot task coordination** — Bots can now process incoming inter-bot messages and queue tasks from direct requests (e.g., "can you mine some iron"). +- **Event-driven socket updates** — Position, health, state, and inventory changes now emit immediately via EventEmitter instead of relying solely on 2-second polling. Polling interval reduced to 10-second fallback. +- **Dashboard control platform consolidation** — Unified frontend command/mission state, normalized dashboard API contracts, and live `command:*` / `mission:*` socket updates now drive tactical UI state more consistently. +- **Mission and history UX upgrade** — Mission queue actions now support real retry/cancel/reorder behavior, and history/commander surfaces are tied more closely to shared command and mission records. +- **Fleet, roles, and commander polish** — Fleet actions now use shared commands, role management supports override visibility and policy fields, and commander drafts/history persist locally with richer execution audit views. +- **Map-first control expansion** — Map supports marker/zone/route creation and editing, selected-object actions for missions and commands, squad and mission overlays, and canvas-level selection for more world objects. + +### Maintenance +- **Planning docs refreshed** — Updated `dev/dashrevamp/plan/` with current-state health checks, milestone/epic status, and next-sprint guidance. +- **Frontend tests expanded** — Replaced placeholder web tests with shared control store/helper coverage and added more tactical control assertions. + +### Performance +- **Trim ActionAgent prompt** — Consolidated duplicate rules sections, ~60% line reduction. +- **Trim Critic prompt** — Reduced examples from 11 to 5 covering distinct evaluation patterns. + +--- + +## 2026-03-24 + +### Maintenance +- Add `diagnostics/` to .gitignore. + +--- + +## 2026-03-23 + +### Bug Fixes +- Fix OOM on schematic loading: skip parsing for large files, add volume guard (max 2M voxels, 10MB file size). +- Increase Node heap to 8GB, raise schematic limits, add file size guard. +- Restore missing social memory, bot comms, and players API endpoints. +- Restore build/schematic/chain endpoints lost during dashboard revamp merges. +- Guard marker.position access for markers with missing position data. +- Fix duplicate /fleet nav item and null guard on pendingCommands.targets. +- Fix runtime guards for persisted data with missing fields in metrics. +- Fix Phase 3 integration: resolve merge conflicts and type errors. +- Fix Phase 2 frontend integration: restore build/chain store, fix map/role type refs. +- Fix remaining merge conflict markers across control services. +- Fix CommanderService merge conflicts and duplicate log lines. + +### Features — Dashboard Revamp (Phases 1-4) +- **Phase 4**: QA, telemetry, polish, and launch prep. Comprehensive tests, health endpoint, standardized logging, graceful shutdown. +- **Phase 3**: Visual schematic placement with inline mini-map, footprint preview, and click-to-place. Map rendering polish, keyboard shortcut help. Fleet batch operations, empty states, squad management UX. +- **Phase 2**: Commander page with natural language input, plan preview, and execution. Mission queue panel, command history panel, history page. Role badges on bot cards, build/chain connected to missions. Squad overlays, mission indicators, build site overlay on map. Manual override tracking, role-command integration, override UI. +- **Phase 1**: CommandCenter service with dispatch, persistence, and endpoint migration. MissionManager with lifecycle tracking and VoyagerLoop bridge. MarkerStore with CRUD, persistence, and world planning endpoints. SquadManager with CRUD and squad endpoints. RoleManager with CRUD, persistence, and role endpoints. CommanderService with NL parsing and execution. Unit tests for all control platform services. +- Add metrics endpoint. + +--- + +## 2026-03-22 + +### Features +- **Control platform foundation** — Shared type system, control/mission store slices, SocketProvider upgrade. +- **BlackboardManager** — Shared task blackboard with swarm goals, bot goals, task claiming, resource reservations, and message posting. +- **Swarm directives** — Anthropic Claude support for multi-bot coordination via swarm override system. +- **Supply chain automation** — Multi-stage supply chains with input/output chests, stage sequencing, loop support, and templates. +- **Multi-bot blueprint building** — Schematic loading, Y-layer partitioning across bots, parallel block placement via /setblock commands. +- **Social AI system** — Bot-to-bot communication, social memory with decay, emotional state tracking, periodic reflections. +- **Web dashboard** — Next.js App Router dashboard with bot cards, map, activity feed, chat, stats, skills, relationships, and build pages. Zustand stores, Socket.IO real-time updates. +- **"Create Bots for Task" on Build page** — Auto-spawn builder bots with staggered connections. +- **Vitest test infrastructure** — Backend and frontend test setup. + +### Bug Fixes +- Fix build system: balanced Y-layer partitioning, spam protection, bot control. +- Fix Gemini thinking config, auth class selection, and build page errors. +- Fix null-safety crashes in BotInstance. +- Build system hardening and bot management improvements. +- Fix out-of-memory errors (multiple commits). + +--- + +## 2026-03-21 + +### Features +- **Initial release** — DyoBot AI-powered Minecraft bot with Voyager-style code generation. +- **Mineflayer integration** — Bot lifecycle, connection management, pathfinding, combat. +- **LLM code generation** — Gemini-powered action generation, curriculum agent, critic evaluation, skill library. +- **Personality system** — 6 personality types (merchant, guard, explorer, farmer, blacksmith, elder) with affinity tracking. +- **Instinct system** — Attack, hazard, and drowning instincts with automatic threat response. +- **Primitive actions** — Walk, mine, craft, smelt, attack, place blocks, use containers, patrol, follow, give items. +- **Web dashboard backend** — Express API with Socket.IO for real-time bot monitoring. +- **Conversation system** — Per-player chat history with LLM-driven responses and sentiment analysis. + +### Bug Fixes +- Fix chat truncation and improve follow reliability. +- Fix broken conversations and add building capabilities. +- Fix primitives reliability. diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 7ca238d..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,70 +0,0 @@ -# DyoBot - -## Project Overview - -DyoBot is a Voyager-style AI-powered Minecraft bot sidecar for DyoCraft. It connects mineflayer bots to a Minecraft server and uses an LLM to autonomously plan and execute tasks through code generation, with personality and social relationship systems. - -## Build & Run - -```bash -npm run build -npm run dev -npm start -``` - -Always start production runs with log capture so logs can be inspected: - -```bash -node dist/index.js > /tmp/dyobot.log 2>&1 & disown -``` - -Before restarting, kill existing instances first: - -```bash -lsof -ti:3001 | xargs kill -9 2>/dev/null; sleep 2 -``` - -Useful log commands: - -```bash -tail -f /tmp/dyobot.log -grep -E "task proposed|Execution result|task evaluated" /tmp/dyobot.log -``` - -## Setup - -1. Copy `.env.example` to `.env` and set the API key for the configured provider (`ANTHROPIC_API_KEY` for Anthropic, `GOOGLE_API_KEY` for Gemini) -2. Configure `config.yml` -3. Run `npm install && npm run build && npm start` - -## Spawning Bots - -```bash -curl -s -X POST http://127.0.0.1:3001/api/bots \ - -H 'Content-Type: application/json' \ - -d '{"name":"BotName","personality":"farmer","mode":"codegen"}' -``` - -Available personalities: merchant, guard, explorer, farmer, blacksmith, elder - -## Checking Status - -```bash -curl -s http://127.0.0.1:3001/api/bots -``` - -## Architecture - -- `src/bot/` - bot lifecycle and Mineflayer connection management -- `src/voyager/` - curriculum, action, critic, skill library, execution loop -- `src/actions/` - primitive movement, mining, crafting, combat, container actions -- `src/ai/` - Gemini client and prompt logic -- `src/personality/` - affinity, conversation, personality behavior -- `src/server/api.ts` - Express API for bot CRUD and control - -## Data - -- `data/bots.json` -- `data/affinities.json` -- `skills/` -- `config.yml` diff --git a/config.yml b/config.yml index a05e3dd..7e8bfa9 100644 --- a/config.yml +++ b/config.yml @@ -53,8 +53,8 @@ voyager: criticLLMCalls: true llm: - provider: "anthropic" - model: "claude-haiku-4-5" + provider: "gemini" + model: "gemini-3-flash-preview" temperature: 0.7 chatMaxTokens: 2048 codeGenMaxTokens: 8192 diff --git a/dashboard/app.js b/dashboard/app.js deleted file mode 100644 index 805c4f6..0000000 --- a/dashboard/app.js +++ /dev/null @@ -1,259 +0,0 @@ -const state = { - selectedBot: null, - bots: [], - detailed: null, - tasks: null, - inventory: [], - relationships: [], - conversations: [], - world: null, - activity: [], - blackboard: null, -}; - -const els = { - botList: document.getElementById('bot-list'), - worldPill: document.getElementById('world-pill'), - worldTime: document.getElementById('world-time'), - worldSummary: document.getElementById('world-summary'), - activityFeed: document.getElementById('activity-feed'), - heroName: document.getElementById('hero-name'), - heroSubtitle: document.getElementById('hero-subtitle'), - statusCard: document.getElementById('status-card'), - directiveCard: document.getElementById('directive-card'), - executionCard: document.getElementById('execution-card'), - tasksPanel: document.getElementById('tasks-panel'), - inventoryPanel: document.getElementById('inventory-panel'), - relationshipsPanel: document.getElementById('relationships-panel'), - conversationsPanel: document.getElementById('conversations-panel'), - blackboardPanel: document.getElementById('blackboard-panel'), - blackboardMessages: document.getElementById('blackboard-messages'), - taskForm: document.getElementById('task-form'), - taskInput: document.getElementById('task-input'), - swarmForm: document.getElementById('swarm-form'), - swarmInput: document.getElementById('swarm-input'), -}; - -async function getJSON(url) { - const res = await fetch(url); - if (!res.ok) throw new Error(`Request failed: ${url}`); - return res.json(); -} - -async function refreshBots() { - const data = await getJSON('/api/bots'); - state.bots = data.bots || []; - if (!state.selectedBot && state.bots[0]) state.selectedBot = state.bots[0].name; - renderBotList(); -} - -async function refreshWorld() { - state.world = await getJSON('/api/world'); - renderWorld(); -} - -async function refreshActivity() { - const data = await getJSON('/api/activity?limit=18'); - state.activity = data.events || []; - renderActivity(); -} - -async function refreshBlackboard() { - const data = await getJSON('/api/blackboard'); - state.blackboard = data.blackboard || null; - renderBlackboard(); -} - -async function refreshSelectedBot() { - if (!state.selectedBot) return; - const [detailed, tasks, inventory, relationships, conversations] = await Promise.all([ - getJSON(`/api/bots/${encodeURIComponent(state.selectedBot)}/detailed`), - getJSON(`/api/bots/${encodeURIComponent(state.selectedBot)}/tasks`), - getJSON(`/api/bots/${encodeURIComponent(state.selectedBot)}/inventory`), - getJSON(`/api/bots/${encodeURIComponent(state.selectedBot)}/relationships`), - getJSON(`/api/bots/${encodeURIComponent(state.selectedBot)}/conversations`), - ]); - state.detailed = detailed.bot; - state.tasks = tasks; - state.inventory = inventory.inventory || []; - state.relationships = relationships.relationships || []; - state.conversations = conversations.conversations || []; - renderSelectedBot(); -} - -function renderBotList() { - els.botList.innerHTML = ''; - if (!state.bots.length) { - els.botList.innerHTML = '
No bots connected.
'; - return; - } - state.bots.forEach((bot) => { - const button = document.createElement('button'); - button.className = `bot-button${bot.name === state.selectedBot ? ' active' : ''}`; - button.innerHTML = `${bot.name}
${bot.personality} · ${bot.mode} · ${bot.state}
`; - button.onclick = async () => { - state.selectedBot = bot.name; - renderBotList(); - await refreshSelectedBot(); - }; - els.botList.appendChild(button); - }); -} - -function renderWorld() { - const world = state.world || {}; - els.worldPill.textContent = world.onlineBots ? `${world.onlineBots} bot${world.onlineBots === 1 ? '' : 's'} online` : 'No bots online'; - els.worldTime.textContent = world.timeOfDay || '--'; - els.worldSummary.innerHTML = [ - ['Day', world.day ?? '--'], - ['Weather', world.isRaining ? 'Rain' : 'Clear'], - ['Ticks', world.timeOfDayTicks ?? '--'], - ].map(([k, v]) => `
${k}${v}
`).join(''); -} - -function renderActivity() { - els.activityFeed.innerHTML = state.activity.length - ? state.activity.map((event) => `
${event.botName || event.type}

${event.description}

`).join('') - : '
No recent activity.
'; -} - -function renderSelectedBot() { - const bot = state.detailed; - if (!bot) { - els.heroName.textContent = 'Select a bot'; - return; - } - - els.heroName.textContent = bot.name; - els.heroSubtitle.textContent = `${bot.personalityDisplayName} · ${bot.mode} mode · ${bot.state}`; - - const voyager = bot.voyager || {}; - els.statusCard.innerHTML = [ - ['Mode', bot.mode], - ['State', bot.state], - ['Position', bot.position ? `${bot.position.x}, ${bot.position.y}, ${bot.position.z}` : '--'], - ['Health', bot.health ?? '--'], - ['Food', bot.food ?? '--'], - ['Biome', bot.world?.biome || '--'], - ].map(row).join(''); - - const goal = voyager.longTermGoal; - els.directiveCard.innerHTML = goal - ? `${row(['Goal', goal.rawRequest])}${row(['Kind', goal.kind])}${row(['Status', goal.status])}${row(['Build State', goal.buildState || '--'])}
${(goal.pendingSubtasks || []).slice(0, 8).map(tag).join('')}
` - : '
No active long-term directive.
'; - - els.executionCard.innerHTML = `${row(['Current', voyager.currentTask || '--'])}${row(['Queued', (voyager.queuedTasks || []).length])}${row(['Completed', (voyager.completedTasks || []).length])}${row(['Failed', (voyager.failedTasks || []).length])}${row(['Running', voyager.isRunning ? 'Yes' : 'No'])}${row(['Paused', voyager.isPaused ? 'Yes' : 'No'])}`; - - const tasks = state.tasks || {}; - els.tasksPanel.innerHTML = [ - sectionList('Current', tasks.currentTask ? [tasks.currentTask] : []), - sectionList('Queued', tasks.queuedTasks || []), - sectionList('Completed', (tasks.completedTasks || []).slice(-8).reverse()), - sectionList('Failed', (tasks.failedTasks || []).slice(-8).reverse()), - ].join(''); - - els.inventoryPanel.innerHTML = state.inventory.length - ? state.inventory.map((item) => `
${item.name}x${item.count}
`).join('') - : '
Inventory empty.
'; - - els.relationshipsPanel.innerHTML = state.relationships.length - ? state.relationships.map((rel) => `
${rel.playerName || rel.player || 'Player'}

Affinity ${rel.score ?? rel.affinity ?? '--'}

`).join('') - : '
No relationship data.
'; - - els.conversationsPanel.innerHTML = state.conversations.length - ? state.conversations.slice(0, 10).map((conv) => `
${conv.playerName || conv.player || 'Conversation'}

${summarizeConversation(conv.messages || conv.history || [])}

`).join('') - : '
No recent conversation history.
'; -} - -function renderBlackboard() { - const board = state.blackboard; - if (!board) { - els.blackboardPanel.innerHTML = '
No blackboard state.
'; - els.blackboardMessages.innerHTML = '
No coordination messages.
'; - return; - } - - const swarmGoal = board.swarmGoal - ? `${row(['Swarm Goal', board.swarmGoal.rawRequest])}${row(['Status', board.swarmGoal.status])}` - : '
No active swarm goal.
'; - - const claimed = (board.tasks || []).filter((task) => task.status === 'claimed'); - const blocked = (board.tasks || []).filter((task) => task.status === 'blocked'); - const reservations = board.reservations || []; - - els.blackboardPanel.innerHTML = ` - ${swarmGoal} -
Claimed Tasks${claimed.length ? `
${claimed.map((task) => tag(`${task.assignedBot || 'unassigned'} -> ${task.description}`)).join('')}
` : '

None

'}
-
Blocked Tasks${blocked.length ? `
${blocked.map((task) => tag(`${task.description}: ${task.blocker || 'blocked'}`)).join('')}
` : '

None

'}
-
Reservations${reservations.length ? `
${reservations.slice(0, 20).map((res) => tag(`${res.botName}: ${res.type} ${res.key}`)).join('')}
` : '

None

'}
- `; - - els.blackboardMessages.innerHTML = (board.messages || []).length - ? board.messages.slice(-16).reverse().map((msg) => `
${msg.botName}

${msg.kind}

${escapeHTML(msg.text)}

`).join('') - : '
No coordination messages.
'; -} - -function row([label, value]) { - return `
${label}${escapeHTML(String(value))}
`; -} - -function sectionList(title, items) { - return `
${title}${items.length ? `
${items.map(tag).join('')}
` : '

None

'}
`; -} - -function tag(text) { - return `${escapeHTML(String(text))}`; -} - -function summarizeConversation(messages) { - return messages.slice(-3).map((msg) => typeof msg === 'string' ? msg : `${msg.role || msg.speaker || 'msg'}: ${msg.content || msg.text || ''}`).join(' | '); -} - -function escapeHTML(text) { - return text.replace(/[&<>"]/g, (ch) => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[ch])); -} - -async function boot() { - await Promise.all([refreshBots(), refreshWorld(), refreshActivity(), refreshBlackboard()]); - await refreshSelectedBot(); - - const socket = io(); - ['bot:spawn', 'bot:disconnect', 'bot:position', 'bot:health', 'bot:state', 'bot:inventory', 'activity', 'world:time'].forEach((eventName) => { - socket.on(eventName, async () => { - await Promise.all([refreshBots(), refreshWorld(), refreshActivity(), refreshBlackboard()]); - await refreshSelectedBot(); - }); - }); - - els.taskForm.addEventListener('submit', async (event) => { - event.preventDefault(); - if (!state.selectedBot || !els.taskInput.value.trim()) return; - await fetch(`/api/bots/${encodeURIComponent(state.selectedBot)}/task`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ description: els.taskInput.value.trim() }), - }); - els.taskInput.value = ''; - await refreshSelectedBot(); - await refreshActivity(); - await refreshBlackboard(); - }); - - els.swarmForm.addEventListener('submit', async (event) => { - event.preventDefault(); - const description = els.swarmInput.value.trim(); - if (!description || !state.selectedBot) return; - await fetch(`/api/swarm`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ description, requestedBy: 'dashboard' }), - }); - els.swarmInput.value = ''; - await Promise.all([refreshBots(), refreshSelectedBot(), refreshActivity(), refreshBlackboard()]); - }); -} - -boot().catch((err) => { - console.error(err); - els.heroSubtitle.textContent = `Dashboard failed to load: ${err.message}`; -}); diff --git a/dashboard/index.html b/dashboard/index.html deleted file mode 100644 index 1697602..0000000 --- a/dashboard/index.html +++ /dev/null @@ -1,108 +0,0 @@ - - - - - - DyoBot Monitor - - - -
- - -
-
-
-

Focused Bot

-

Select a bot

-

Track directives, current execution, inventory, memory, and live events.

-
-
-
- - -
-
- - -
-
-
- -
-
-
Status
-
-
-
-
Long-Term Directive
-
-
-
-
Execution
-
-
-
- -
-
-
Queued + Recent Tasks
-
-
-
-
Inventory
-
-
-
- -
-
-
Relationships
-
-
-
-
Conversations
-
-
-
- -
-
-
Swarm Blackboard
-
-
-
-
Coordination Feed
-
-
-
-
-
- - - - - diff --git a/dashboard/styles.css b/dashboard/styles.css deleted file mode 100644 index 3cb5a27..0000000 --- a/dashboard/styles.css +++ /dev/null @@ -1,210 +0,0 @@ -:root { - --onyx-950: #000000; - --onyx-900: #09090b; - --onyx-850: #121212; - --onyx-800: #18181b; - --onyx-750: #202024; - --onyx-700: #27272a; - --onyx-600: #3f3f46; - --onyx-500: #71717a; - --onyx-400: #a1a1aa; - --onyx-300: #d4d4d8; - --onyx-200: #e4e4e7; - --cyan: #67e8f9; - --emerald: #34d399; - --amber: #fbbf24; - --rose: #fb7185; - --panel: rgba(9, 9, 11, 0.92); - --panel-2: rgba(18, 18, 18, 0.96); - --border: rgba(63, 63, 70, 0.75); - --glow: rgba(103, 232, 249, 0.14); -} - -* { box-sizing: border-box; } -html, body { margin: 0; min-height: 100%; } -body { - font-family: Inter, ui-sans-serif, system-ui, sans-serif; - color: var(--onyx-200); - background: - radial-gradient(circle at top left, rgba(103, 232, 249, 0.09), transparent 30%), - radial-gradient(circle at top right, rgba(251, 191, 36, 0.08), transparent 26%), - linear-gradient(180deg, #020203 0%, #09090b 100%); -} - -.shell { - display: grid; - grid-template-columns: 320px minmax(0, 1fr); - min-height: 100vh; -} - -.panel { - background: linear-gradient(180deg, rgba(18, 18, 18, 0.94), rgba(9, 9, 11, 0.98)); - border: 1px solid var(--border); - box-shadow: 0 18px 50px rgba(0, 0, 0, 0.34); -} - -.sidebar { - border-right: 1px solid var(--border); - padding: 18px; - display: flex; - flex-direction: column; - gap: 18px; - background: linear-gradient(180deg, rgba(9, 9, 11, 0.98), rgba(9, 9, 11, 0.92)); -} - -.sidebar-header, .label-row, .hero { - display: flex; - justify-content: space-between; - align-items: flex-start; - gap: 16px; -} - -.eyebrow { - margin: 0 0 6px; - color: var(--onyx-500); - font-size: 11px; - letter-spacing: 0.18em; - text-transform: uppercase; -} - -h1, h2, h3, p { margin: 0; } -h1 { font-size: 24px; } -h2 { font-size: 28px; line-height: 1.05; } - -.pill { - border: 1px solid rgba(103, 232, 249, 0.3); - background: rgba(103, 232, 249, 0.08); - color: var(--cyan); - border-radius: 999px; - padding: 8px 12px; - font-size: 12px; -} - -.sidebar-block, .card, .section { - background: var(--panel-2); - border: 1px solid var(--border); - border-radius: 18px; -} - -.sidebar-block { padding: 14px; } -.label { display: block; font-size: 12px; color: var(--onyx-400); text-transform: uppercase; letter-spacing: 0.12em; margin-bottom: 10px; } -.muted { color: var(--onyx-500); font-size: 12px; } - -.bot-list, .activity-feed, .list, .conversations, .inventory-grid { display: grid; gap: 10px; } - -.bot-button { - width: 100%; - text-align: left; - border: 1px solid var(--border); - background: rgba(24, 24, 27, 0.9); - color: var(--onyx-200); - border-radius: 14px; - padding: 12px; - cursor: pointer; -} - -.bot-button.active { border-color: rgba(103, 232, 249, 0.5); box-shadow: inset 0 0 0 1px rgba(103, 232, 249, 0.24), 0 0 0 1px rgba(103, 232, 249, 0.16); } - -.main { - padding: 20px; - display: grid; - gap: 18px; -} - -.hero { - padding: 18px 22px; - border-radius: 24px; - background: - linear-gradient(135deg, rgba(103, 232, 249, 0.09), transparent 35%), - linear-gradient(180deg, rgba(18, 18, 18, 0.96), rgba(9, 9, 11, 0.98)); -} - -.hero-subtitle { color: var(--onyx-400); margin-top: 8px; max-width: 70ch; } - -.inline-form { display: flex; gap: 10px; width: min(520px, 100%); } -.inline-form input { - flex: 1; - min-width: 0; - background: rgba(24, 24, 27, 0.96); - border: 1px solid var(--border); - border-radius: 14px; - padding: 12px 14px; - color: var(--onyx-200); -} -.inline-form button { - border: 0; - border-radius: 14px; - background: var(--onyx-200); - color: var(--onyx-950); - font-weight: 600; - padding: 0 16px; - cursor: pointer; -} - -.grid { display: grid; gap: 18px; } -.top-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } -.mid-grid { grid-template-columns: 1.1fr 0.9fr; } -.lower-grid { grid-template-columns: 0.8fr 1.2fr; } - -.card, .section { overflow: hidden; } -.card-header, .section-header { - padding: 14px 16px; - border-bottom: 1px solid var(--border); - text-transform: uppercase; - letter-spacing: 0.1em; - font-size: 12px; - color: var(--onyx-400); -} -.card-body, .section-body { padding: 16px; } - -.accent-cyan .card-header { box-shadow: inset 0 1px 0 rgba(103, 232, 249, 0.28); } -.accent-amber .card-header { box-shadow: inset 0 1px 0 rgba(251, 191, 36, 0.28); } -.accent-emerald .card-header { box-shadow: inset 0 1px 0 rgba(52, 211, 153, 0.28); } - -.stat-row, .meta-grid { - display: grid; - gap: 10px; -} - -.kv { - display: flex; - justify-content: space-between; - gap: 12px; - color: var(--onyx-300); - border-bottom: 1px solid rgba(63, 63, 70, 0.32); - padding-bottom: 8px; -} - -.value-strong { color: white; font-weight: 600; } -.tag { - display: inline-flex; - align-items: center; - gap: 6px; - font-size: 11px; - border-radius: 999px; - padding: 6px 10px; - border: 1px solid rgba(63, 63, 70, 0.7); - background: rgba(24, 24, 27, 0.9); - color: var(--onyx-300); -} - -.chips { display: flex; flex-wrap: wrap; gap: 8px; } - -.activity-item, .conversation, .inventory-item, .list-item { - background: rgba(24, 24, 27, 0.9); - border: 1px solid rgba(63, 63, 70, 0.5); - border-radius: 14px; - padding: 12px; -} - -.inventory-grid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); } -.inventory-item strong { display: block; font-size: 14px; color: white; } -.inventory-item span { color: var(--onyx-500); font-size: 12px; } - -.conversation p + p { margin-top: 8px; } -.wide { min-width: 0; } - -@media (max-width: 1180px) { - .shell { grid-template-columns: 1fr; } - .top-grid, .mid-grid, .lower-grid { grid-template-columns: 1fr; } -} diff --git a/dev/PACKETLOSS404-DEV-NOTES.md b/dev/PACKETLOSS404-DEV-NOTES.md new file mode 100644 index 0000000..f8914c6 --- /dev/null +++ b/dev/PACKETLOSS404-DEV-NOTES.md @@ -0,0 +1,127 @@ +How to Use Building Features: + + 1. Drop .schem or .schematic files into D:\projects\mc-server-bot\schematics\ + 2. In-game, say: list schematics — packetsloth will list available files + 3. Say: build bunker.schem — it'll build the schematic at its current position + + It builds block-by-block. If the bot doesn't have the blocks in inventory, it falls back to /setblock commands (needs the bot to have server permissions for that). If the server allows the bots to use /setblock, it can build anything regardless of inventory. + +--- + +# DyoBot Codebase Review — 2026-03-25 + +Full codebase review by 10 parallel agents. Findings organized by priority. + +--- + +## Critical Bugs + +### Memory Leaks in BotInstance +- **Reflection interval** (`BotInstance.ts:193-199`) — `setInterval` for reflection never stored or cleared on disconnect. Accumulates with each reconnect. +- **`pendingConnectTimeout`** (`BotInstance.ts:56, 1233`) — not cleared in `disconnect()`. Can fire after bot is destroyed. +- **`reconnectTimer`** (`BotInstance.ts:430, 507-526, 1233`) — not cleared on disconnect. Orphaned reconnect callbacks. +- **BotComms listener** (`BotInstance.ts:185-191, 1233`) — `registerListener()` called on spawn but `unregisterListener()` never called on disconnect. Stale listeners accumulate. +- **Duplicate message listeners** (`BotInstance.ts:177-182`) — raw `bot.on('message', ...)` added on every reconnect without removing old ones. N reconnects = N duplicate listeners. + +### Container/Furnace Never Closed on Error +- `container.ts:66-72, 87-93` — if `withdraw()`/`deposit()` throws, catch block returns without `container.close()`. Container stays locked. +- `smelt.ts:45-84` — same issue with furnace. Furnace never closed on error path. + +### Affinity System Half-Wired +- `onHit()` defined (`AffinityManager.ts:52`) but never called. `BotInstance.ts:247-249` logs attacker name but doesn't update affinity. +- `onGift()` defined (`AffinityManager.ts:62`) but never called anywhere. `giveItem.ts` doesn't trigger it. +- `isHostile()` defined (`AffinityManager.ts:68-70`) but never called. Bots never check hostility before acting. +- `trustThreshold` and `hostileThreshold` config values completely unused. + +### File Write Race Conditions +- 19 data managers use `writeFileSync` with no debouncing, locking, or atomic writes. +- **Worst offenders:** + - `BlackboardManager.ts:250` — persists on every mutation (13 call sites), can burst 50+ writes + - `AffinityManager.ts:156` — every chat/hit/gift triggers immediate write + - `SocialMemory.ts:273-276` — `addMemory()`, `reflect()`, `updateEmotionalState()`, `decayMemories()` all write immediately + - `StatsTracker.ts:115` — every stat update triggers write + - `BlockerMemory.ts:89` — every failure record triggers write + - `CurriculumAgent.ts:434-439` — writes 4 files sequentially; crash between writes = inconsistent state +- **No shutdown flush** on AffinityManager, BlackboardManager, SocialMemory, StatsTracker, WorldMemory, BlockerMemory + +### Build System False Success +- `BuildCoordinator.ts:542-546` — `/setblock` commands fire-and-forget, counter increments regardless of success +- `BuildCoordinator.ts:485-490` — if all bots disconnect, job reports "completed" with 0 blocks placed +- No block placement verification, no bot disconnect recovery + +### Path Traversal in Schematic Endpoint +- `api.ts:956` — `req.params.filename` passed to `path.join(schematicsDir, filename)` without validation + +--- + +## High Priority Bugs + +- **walkTo race condition** (`walkTo.ts:18-23`) — `noPath` doesn't call `pathfinder.stop()` +- **attack.ts multiple finish()** (`attack.ts:26-94`) — can resolve/reject promise multiple times +- **Patrol not implemented** (`CommandCenter.ts:690-717`) — only moves to first waypoint +- **Squad missions broken** (`MissionManager.ts:110-114`) — iterates squad IDs not bot names +- **Multi-bot missions** (`MissionManager.ts:471-509`) — completes on first bot finish +- **Override expiry never checked** (`RoleManager.ts:60-73`) — `checkOverrideTimeouts()` never called +- **Autonomy levels cosmetic** (`RoleManager.ts:138-150`) — stored but never enforced +- **Chat cooldown map unbounded** (`BotInstance.ts:62`) — never pruned +- **Voyager pause flag desync** (`BotInstance.ts:814-816, 1040`) — boolean flag, should be ref-counted + +--- + +## Token / Performance Waste + +- **ActionAgent prompt ~25% redundant** (`ActionAgent.ts:20-91`) — 3 sections repeat same rules +- **Critic has 11 examples, needs 4** (`CriticAgent.ts:16-119`) — ~3-4K wasted tokens per eval +- **Curriculum/critic ignore config maxTokens** (`CurriculumAgent.ts:352`, `CriticAgent.ts:246`) — hardcoded 1000 +- **QA embeddings dead** (`GeminiClient.ts:103-129`) — commented out, fallback is exact string match +- **Socket.IO 2s polling** (`socketEvents.ts:23-91`) — should be event-driven +- **Unused config values:** `ambientChatMinSec`, `ambientChatMaxSec`, `maxConcurrentRequests` +- **No debouncing** on most data managers + +--- + +## Dead / Half-Built Features + +- **Bot-to-bot messaging** — `getUnread()` never called (`BotComms.ts:51-59`) +- **`cooperation`/`help_request` events** — defined, never recorded (`AffinityManager.ts:123-124`) +- **Emotional sociability** — tracked, never used (`SocialMemory.ts:28`) +- **`idle_long` event** — defined, never triggered (`SocialMemory.ts:216-219`) +- **Deposit inventory command** — returns stub (`CommandCenter.ts:720-731`) +- **`activeMissionId` on squads** — field exists, never set (`FleetTypes.ts:7`) + +--- + +## Feature Ideas + +### Personality & Social +- Wire up affinity (hit/gift/hostile gating) +- Personality-specific task selection (farmers farm, merchants trade) +- LLM-driven reflections that feed back into behavior +- Emotional state affects behavior (lonely → chat more, scared → stay near base) +- Affinity-gated combat (warn high-affinity players before attacking) + +### Fleet Intelligence +- Process bot-to-bot messages (parse, respond, coordinate) +- Squad-aware missions (resolve membership to bot names) +- Role-based mission filtering (use preferredMissionTypes) +- Enforce autonomy levels + +### Build System +- Block placement verification +- Bot disconnect recovery (reassign blocks) +- Configurable placement speed + +### Dashboard +- Mission step drilldown +- Commander plan confidence/warnings display +- Full stats breakdown (mined/crafted/killed by type) +- Experience bars on bot cards +- Equipment hotbar visualization +- Mission dependency graph + +### Infrastructure +- Atomic file writes (temp + rename) +- Debounce all data managers +- Persist EventLog to disk +- Event-driven socket updates +- LLM circuit breaker (fallback on high failure rate) diff --git a/dev/dashrevamp/plan/README.md b/dev/dashrevamp/plan/README.md new file mode 100644 index 0000000..09ff387 --- /dev/null +++ b/dev/dashrevamp/plan/README.md @@ -0,0 +1,89 @@ +# Dashboard Revamp Plan + +This folder is the working planning package for the web dashboard control revamp for `mc-server-bot`. + +The package started as a forward-looking implementation plan. It now also doubles as a current-state reference. + +Read `current-state.md` first if you want to know what is already built versus what is still planned. + +It is tailored to the current repo shape: + +- Backend control and bot runtime live in `src/` +- Dashboard UI lives in `web/src/` +- Existing control endpoints are concentrated in `src/server/api.ts` +- Live telemetry is emitted from `src/server/socketEvents.ts` +- Current dashboard state is centralized in `web/src/lib/store.ts` +- Existing orchestration already exists in `src/build/BuildCoordinator.ts` and `src/supplychain/ChainCoordinator.ts` + +## Planning Goals + +- Turn the dashboard into the primary control plane for bots +- Support all requested control directions: command center, mission planner, map-first control, squad control, role automation, and chat-as-control +- Unify these features behind shared control primitives instead of building isolated one-off flows +- Get the work ready for engineering execution with enough detail for backend, frontend, product, and QA + +## Planning Package + +- `current-state.md` - current implementation health check, milestone checklist, and stale-doc corrections +- `next-sprint.md` - highest-value next slice of work based on the current repo state +- `vision.md` - product goals, personas, and control philosophy +- `features.md` - complete feature inventory and requirements +- `milestones.md` - delivery milestones and acceptance criteria +- `roadmap.md` - rollout sequence from MVP to advanced control +- `frontend-architecture.md` - UI architecture, store evolution, and interaction patterns +- `backlog.md` - execution backlog with epics, stories, tasks, and PM-style ownership lanes +- `epics/` - one markdown file per epic for easier execution tracking +- `user-flows.md` - core operator workflows +- `telemetry.md` - metrics, logging, and operational visibility plan + +## Ten PM Workstreams + +To "manage the hell out of it," the revamp is split into 10 PM-style workstreams. They are coordination tracks, not required headcount. + +1. Control model and command lifecycle +2. Mission planner and queueing +3. Map interactions and world planning +4. Fleet and squad operations +5. Role automation and guardrails +6. Commander console and natural language planning +7. Bot diagnostics, history, and recovery tooling +8. Backend architecture and persistence +9. Frontend state, UX system, and design consistency +10. QA, telemetry, rollout, and release operations + +## Current-State Summary + +The repo now includes a large portion of the planned control platform: + +- shared backend control services under `src/control/` +- command and mission APIs in `src/server/api.ts` +- map world-object persistence and CRUD +- squad, role, and commander backend services +- new dashboard pages for fleet, roles, commander, and history +- frontend tactical components for command, mission queue, and selection UX +- backend control tests and basic telemetry/metrics + +The main remaining gap is no longer raw feature absence. It is integration quality: + +- frontend state is split across overlapping stores +- command and mission lifecycle sockets are not the primary UI source of truth yet +- several UI panels still use older activity/task data instead of shared control records +- role automation, routines/templates, and some diagnostics flows remain incomplete + +The core product model is still organized around 4 shared concepts: + +- `Command` - an immediate action applied to one or more bots +- `Mission` - a structured, trackable multi-step objective +- `Marker` - a named location, route, or zone in the Minecraft world +- `Assignment` - a persistent relationship between bots and automation systems such as squads, roles, or routines + +## Definition Of Done For Planning + +This package is ready for engineering when: + +- Backend engineers can start creating modules and endpoints without major product ambiguity +- Frontend engineers can map the screens and state model without re-inventing control concepts +- PM and QA can track scope, milestones, and acceptance criteria +- The plan explains how to evolve the current repo without breaking existing dashboard behavior + +For current execution status, use `current-state.md` and the status sections in `features.md`, `milestones.md`, `roadmap.md`, and `backlog.md`. diff --git a/dev/dashrevamp/plan/backlog.md b/dev/dashrevamp/plan/backlog.md new file mode 100644 index 0000000..fc2051b --- /dev/null +++ b/dev/dashrevamp/plan/backlog.md @@ -0,0 +1,82 @@ +# Implementation Backlog + +This backlog translates the planning package into execution-ready epics, stories, and engineering tasks for this repo. + +The table below is now also used as a status board for the current repo state. + +## Operating Model + +The program is split into 10 PM-style workstreams. These are planning lanes that can be owned by one person or shared across a small team. + +| PM | Workstream | Status | Primary Outcome | Main Docs | +|---|---|---|---|---| +| PM-01 | Control Platform | Mostly done | Shared command lifecycle and backend control foundation | `features.md`, `current-state.md` | +| PM-02 | Tactical Controls | Partial | Upgraded command center and manual override UX | `features.md`, `current-state.md` | +| PM-03 | Mission Planner | Partial | Visible queueing, planning, retries, and task history | `features.md`, `milestones.md` | +| PM-04 | Spatial Control | Partial | Markers, zones, routes, and map-first control | `features.md`, `current-state.md` | +| PM-05 | Fleet Ops | Partial | Multi-select, squads, and batch operations | `features.md`, `user-flows.md` | +| PM-06 | Role Automation | Partial | Persistent roles, policy settings, and override rules | `features.md`, `frontend-architecture.md` | +| PM-07 | Commander | Partial | Natural language planning and confirmation workflows | `vision.md`, `user-flows.md` | +| PM-08 | Diagnostics | Partial | History, blockers, recovery, and operator confidence | `telemetry.md`, `features.md` | +| PM-09 | Frontend System | Partial | Store, socket, and component architecture cleanup | `frontend-architecture.md`, `current-state.md` | +| PM-10 | QA And Release | Partial | Tests, telemetry, rollout, and migration | `telemetry.md`, `current-state.md` | + +## Epic Files + +- [E1 Shared control platform](epics/E1-shared-control-platform.md) +- [E2 Tactical command center revamp](epics/E2-tactical-command-center-revamp.md) +- [E3 Mission planner and queue visibility](epics/E3-mission-planner-and-queue-visibility.md) +- [E4 World planning and map control](epics/E4-world-planning-and-map-control.md) +- [E5 Fleet selection and squads](epics/E5-fleet-selection-and-squads.md) +- [E6 Roles and automation policies](epics/E6-roles-and-automation-policies.md) +- [E7 Commander console](epics/E7-commander-console.md) +- [E8 Diagnostics and history](epics/E8-diagnostics-and-history.md) +- [E9 Frontend architecture hardening](epics/E9-frontend-architecture-hardening.md) +- [E10 QA, telemetry, and launch prep](epics/E10-qa-telemetry-and-launch-prep.md) + +Each epic file contains: + +- goal +- PM workstream ownership +- repo impact +- story-level acceptance criteria +- engineering task list +- major dependencies + +## Suggested Build Order + +The original build order is now mostly historical. The highest-value remaining order is: + +1. E9 Frontend architecture hardening +2. E3 Mission planner and queue visibility +3. E8 Diagnostics and history +4. E5 Fleet selection and squads +5. E6 Roles and automation policies +6. E7 Commander console +7. E10 QA, telemetry, and launch prep + +Why this order now: + +- the backend control platform is already largely present +- the biggest risks are integration quality and duplicate frontend state +- commander, roles, and diagnostics depend on shared frontend control data behaving consistently + +## Highest-Priority Open Work + +- unify command, mission, and selection state across frontend stores +- wire `command:*` and `mission:*` socket events into frontend state as the main update path +- finish mission queue actions end to end: reorder, retry, reprioritize, and interrupt behavior +- complete zone/route authoring and richer map command flows +- wire role policies and autonomy enforcement into mission generation +- align commander frontend/backend contracts and persist history/drafts +- expand frontend and integration test coverage + +## High-Risk Dependencies + +- E2 depends on E1 +- E3 depends on E1 and partial `VoyagerLoop` exposure +- E4 depends on E9 map cleanup and E1 command creation +- E5 depends on E1 and E9 shared selection state +- E6 depends on E3 mission infrastructure and E4 world planning objects +- E7 depends on stable E1 and E3 models +- E10 depends on every previous epic reaching stable interfaces diff --git a/dev/dashrevamp/plan/current-state.md b/dev/dashrevamp/plan/current-state.md new file mode 100644 index 0000000..eab6b7b --- /dev/null +++ b/dev/dashrevamp/plan/current-state.md @@ -0,0 +1,171 @@ +# Dashboard Revamp Current State + +This document is the current health check for the dashboard revamp planning package. + +It compares the original plan to what is actually present in the repo now. + +## Overall Status + +- Backend control foundations are mostly implemented. +- Frontend page surfaces are mostly present. +- Integration between backend lifecycle models and frontend live state is still incomplete. +- Role automation, reusable routines/templates, and some diagnostics/recovery flows remain unfinished. + +## Status By Area + +| Area | Status | Notes | +|---|---|---| +| Shared command model | Implemented | Command types, lifecycle, APIs, persistence, and cancellation are in place. | +| Mission system | Partial | Mission models and APIs exist, but queue manipulation is split between mission records and raw Voyager queue access. | +| Tactical UI | Partial | Bot detail, dashboard, history page, mission panel, and command panel exist, but several controls are placeholders or use older activity data. | +| Map-first control | Partial | Marker/zone/route persistence and rendering exist; zone/route authoring and richer command flows are incomplete. | +| Fleet and squads | Partial | Squad backend and fleet page exist; selection state and backend integration are inconsistent. | +| Roles and automation | Partial | Role CRUD and UI exist; policy execution and autonomy enforcement are not wired. | +| Commander | Partial | Parse/execute backend and UI exist; contract drift and missing persistent history/drafts remain. | +| Diagnostics/history | Partial | History and stats surfaces exist, but not all are driven by shared command/mission records. | +| Tests and telemetry | Partial | Backend coverage and metrics exist; frontend tests and standardized control telemetry are still thin. | +| Routines/templates | Missing | No general command macros or reusable mission template system exists yet. | + +## Milestone Checklist + +### M1 - Shared Control Model + +Status: mostly implemented + +- Done + - `src/control/CommandTypes.ts` + - `src/control/CommandCenter.ts` + - `POST/GET /api/commands` and cancel endpoints in `src/server/api.ts` + - legacy pause/resume/stop/follow/walkto routes now create commands internally +- Partial + - frontend pending/completed command state exists, but is split across stores + - `command:*` lifecycle events exist on the backend, but frontend socket handling is incomplete +- Remaining + - unify frontend command state under one store model + - use command lifecycle sockets as the primary source of truth + +### M2 - Mission Queue And Planner + +Status: partial + +- Done + - `src/control/MissionTypes.ts` + - `src/control/MissionManager.ts` + - mission REST endpoints in `src/server/api.ts` + - queue inspection helpers added to `src/voyager/VoyagerLoop.ts` +- Partial + - bot detail page shows mission queue/history surfaces + - queue visibility is split between mission records and Voyager task queue +- Remaining + - first-class reorder/prepend/retry/clear flows + - consistent mission history driven by mission records + - richer bot detail diagnostics around mission failures and interrupt semantics + +### M3 - Spatial Control + +Status: partial + +- Done + - `src/control/MarkerStore.ts` + - marker/zone/route CRUD APIs in `src/server/api.ts` + - map page renders markers, zones, and routes + - marker editing and context menu surfaces exist +- Partial + - map click-to-command exists in a limited form + - world objects are reused in some screens +- Remaining + - zone drawing/editor UX + - route authoring/editor UX + - mission assignment from selected map objects + - squad overlays, mission overlays, and richer map-first control flows + +### M4 - Fleet And Squads + +Status: partial + +- Done + - `src/control/SquadManager.ts` + - squad CRUD endpoints in `src/server/api.ts` + - `web/src/app/fleet/page.tsx` + - `web/src/components/FleetSelectionBar.tsx` + - backend batch command fan-out exists in `src/control/CommandCenter.ts` +- Partial + - multi-select exists but is split across store implementations + - fleet page has squad/batch UX, but not all flows are backend-backed +- Remaining + - unified cross-page selection state + - squad mission UI and partial-result tracking polish + - real use of `activeMissionId` on squads + +### M5 - Roles And Automation + +Status: partial + +- Done + - `src/control/RoleManager.ts` + - roles endpoints in `src/server/api.ts` + - `web/src/app/roles/page.tsx` + - `web/src/components/RoleAssignmentPanel.tsx` +- Partial + - role assignment, autonomy level, home marker, and allowed zones are present + - override tracking exists in backend data +- Remaining + - policy evaluation that creates missions + - autonomy enforcement + - interrupt policy/loadout policy implementation + - visible role conflict/health views + +### M6 - Commander Console + +Status: partial + +- Done + - `src/control/CommanderService.ts` + - parse/execute commander APIs in `src/server/api.ts` + - `web/src/app/commander/page.tsx` + - `web/src/components/CommanderPanel.tsx` +- Partial + - preview, warnings, confidence, and confirm/cancel UX exist + - execution history exists only as local page state +- Remaining + - resolve frontend/backend contract drift + - ambiguity clarification flow beyond warnings + - persistent commander drafts/history + +### M7 - Hardening And Release + +Status: partial + +- Done + - backend Vitest setup exists + - control test files exist under `test/control/` + - basic command/mission metrics and metrics API are present +- Partial + - telemetry is present but not fully standardized across control services + - frontend test setup exists but is mostly placeholder coverage +- Remaining + - deeper frontend coverage + - more complete regression coverage for role/fleet/map integrations + - finish cleanup of legacy UI flows and duplicated stores + +## Dev Notes Cross-Check + +The old `dev/PACKETLOSS404-DEV-NOTES.md` list is partly stale. + +- Still open + - deposit inventory command is still a stub + - `activeMissionId` on squads still appears unused + - `cooperation` and `help_request` events still appear unrecorded + - `getUnread()` still appears unused in bot-to-bot comms +- No longer fully accurate + - `idle_long` is now triggered + - bot-to-bot messaging is at least partially processed + +## Recommended Next Documentation Source Of Truth + +Use these files together: + +- `dev/dashrevamp/plan/current-state.md` for what is done now +- `dev/dashrevamp/plan/milestones.md` for milestone-by-milestone status and remaining work +- `dev/dashrevamp/plan/features.md` for feature scope with implementation notes +- `dev/dashrevamp/plan/roadmap.md` for forward-looking sequencing from the current repo state diff --git a/dev/dashrevamp/plan/epics/E1-shared-control-platform.md b/dev/dashrevamp/plan/epics/E1-shared-control-platform.md new file mode 100644 index 0000000..8afbae3 --- /dev/null +++ b/dev/dashrevamp/plan/epics/E1-shared-control-platform.md @@ -0,0 +1,103 @@ +# E1 Shared Control Platform + +Current status: mostly implemented + +Current-state note: + +- shared command types, command service, APIs, persistence, and cancellation are in the repo now +- the main remaining gap is frontend adoption of lifecycle events and unified command state + +## Goal + +Create the common backend and event foundation that every control surface uses. + +## PM Workstream + +- PM-01 Control Platform + +## Repo Impact + +- new `src/control/` module family +- `src/server/api.ts` +- `src/server/socketEvents.ts` +- `web/src/lib/api.ts` +- `web/src/lib/store.ts` +- `web/src/components/SocketProvider.tsx` + +## Stories + +### E1-S1 Define shared control types + +Status: done + +Acceptance criteria: + +- repo has typed command status, scope, payload, and result models +- types align with the current backend control types under `src/control/` +- frontend and backend import from a stable shared contract location or parallel equivalent models + +Tasks: + +- create `src/control/CommandTypes.ts` +- define command kind unions for current and planned command set +- define validation-friendly payload shapes +- document compatibility mapping from old endpoints to new command types + +### E1-S2 Create `CommandCenter` + +Status: mostly done + +Acceptance criteria: + +- commands can be created, started, completed, failed, and cancelled +- commands emit lifecycle events +- command records are queryable after execution + +Tasks: + +- create `src/control/CommandCenter.ts` +- implement in-memory plus JSON-backed command record persistence in `data/commands.json` +- add command dispatch handlers for existing bot actions: pause, resume, stop, follow, walk +- add structured logging with `commandId`, `bot`, and `source` + +### E1-S3 Migrate current direct APIs to shared control service + +Status: done + +Acceptance criteria: + +- existing endpoints remain externally compatible +- `src/server/api.ts` delegates behavior instead of owning it + +Tasks: + +- wrap `POST /api/bots/:name/pause` +- wrap `POST /api/bots/:name/resume` +- wrap `POST /api/bots/:name/stop` +- wrap `POST /api/bots/:name/follow` +- wrap `POST /api/bots/:name/walkto` +- add `POST /api/commands` +- add `GET /api/commands` +- add `GET /api/commands/:id` + +### E1-S4 Emit command lifecycle over Socket.IO + +Status: partial + +Acceptance criteria: + +- frontend can observe queued, started, succeeded, failed, and cancelled events +- event payloads are stable and typed + +Tasks: + +- extend event emission beyond `src/server/socketEvents.ts` polling model +- emit `command:queued` +- emit `command:started` +- emit `command:succeeded` +- emit `command:failed` +- emit `command:cancelled` + +## Dependencies + +- foundational epic for E2, E3, E4, E5, and E7 diff --git a/dev/dashrevamp/plan/epics/E10-qa-telemetry-and-launch-prep.md b/dev/dashrevamp/plan/epics/E10-qa-telemetry-and-launch-prep.md new file mode 100644 index 0000000..742b250 --- /dev/null +++ b/dev/dashrevamp/plan/epics/E10-qa-telemetry-and-launch-prep.md @@ -0,0 +1,73 @@ +# E10 QA, Telemetry, And Launch Prep + +Current status: partial + +Current-state note: + +- backend tests and telemetry are present now +- frontend coverage, cross-feature integration tests, telemetry standardization, and migration cleanup still need work + +## Goal + +Ship the revamp with enough testing and observability to be credible. + +## PM Workstream + +- PM-10 QA And Release + +## Repo Impact + +- new test runner setup +- telemetry instrumentation +- release docs and cleanup + +## Stories + +### E10-S1 Add test foundations + +Status: partial + +Acceptance criteria: + +- repo has a committed test runner for backend and frontend +- at least the highest-risk control flows are covered + +Tasks: + +- choose test stack +- add scripts to `package.json` and `web/package.json` +- add first tests for command lifecycle, mission queue, and squad dispatch +- update `AGENTS.md` with real test and single-test commands + +### E10-S2 Add telemetry and logging + +Status: partial + +Acceptance criteria: + +- command and mission execution can be measured and debugged + +Tasks: + +- instrument command and mission metrics +- standardize logger fields +- add operator-facing status insights where useful + +### E10-S3 Launch prep and migration cleanup + +Status: partial + +Acceptance criteria: + +- old and new control paths are reconciled cleanly +- key docs are current + +Tasks: + +- remove redundant ad hoc UI flows after replacement +- finish migration checklists +- document release notes and operator usage guidance + +## Dependencies + +- depends on every previous epic reaching stable interfaces diff --git a/dev/dashrevamp/plan/epics/E2-tactical-command-center-revamp.md b/dev/dashrevamp/plan/epics/E2-tactical-command-center-revamp.md new file mode 100644 index 0000000..7148302 --- /dev/null +++ b/dev/dashrevamp/plan/epics/E2-tactical-command-center-revamp.md @@ -0,0 +1,74 @@ +# E2 Tactical Command Center Revamp + +Current status: partial + +Current-state note: + +- `BotCommandCenter`, bot detail, and dashboard tactical surfaces exist +- command center uses shared command creation in places, but lifecycle wiring, override visibility, and richer quick actions are still incomplete + +## Goal + +Turn the current command buttons into a richer, reusable tactical control system. + +## PM Workstream + +- PM-02 Tactical Controls + +## Repo Impact + +- `web/src/components/BotCommandCenter.tsx` +- `web/src/app/bots/[name]/page.tsx` +- `web/src/app/page.tsx` +- `web/src/app/manage/page.tsx` + +## Stories + +### E2-S1 Refactor command center to use shared command API + +Status: partial + +Acceptance criteria: + +- command center no longer relies on page-local loading assumptions only +- per-command status reflects socket lifecycle events + +Tasks: + +- replace ad hoc `exec()` implementation with command creation + store tracking +- show pending state, active state, success, and failure +- show last command result in bot detail page + +### E2-S2 Add high-value quick actions + +Status: partial + +Acceptance criteria: + +- command center includes at least 5 new useful actions beyond pause/stop/follow/walk +- buttons degrade gracefully when unsupported or bot is disconnected + +Tasks: + +- implement backend command handlers for `move_to_marker`, `return_to_base`, `regroup`, `guard_zone`, `unstuck` +- design button grouping for movement, override, and recovery actions +- add confirmation only for high-impact actions + +### E2-S3 Add manual override visibility + +Status: partial + +Acceptance criteria: + +- operator can tell when a bot is under manual control +- the source and age of the override are visible + +Tasks: + +- extend `BotInstance.getDetailedStatus()` +- add override metadata to bot detail page +- add override indicator to cards or fleet lists + +## Dependencies + +- depends on E1 diff --git a/dev/dashrevamp/plan/epics/E3-mission-planner-and-queue-visibility.md b/dev/dashrevamp/plan/epics/E3-mission-planner-and-queue-visibility.md new file mode 100644 index 0000000..292231e --- /dev/null +++ b/dev/dashrevamp/plan/epics/E3-mission-planner-and-queue-visibility.md @@ -0,0 +1,77 @@ +# E3 Mission Planner And Queue Visibility + +Current status: partial + +Current-state note: + +- mission types, mission manager, mission APIs, and queue inspection are present +- queue control and queue/history UX are still split between shared mission records and raw Voyager task state + +## Goal + +Expose and control planned work instead of hiding queue state inside `VoyagerLoop`. + +## PM Workstream + +- PM-03 Mission Planner + +## Repo Impact + +- `src/voyager/VoyagerLoop.ts` +- `src/bot/BotInstance.ts` +- `web/src/app/bots/[name]/page.tsx` +- `web/src/lib/api.ts` +- `web/src/lib/store.ts` + +## Stories + +### E3-S1 Add explicit mission models and persistence + +Status: done + +Acceptance criteria: + +- mission records exist independently of UI refreshes +- mission states align with planning docs + +Tasks: + +- create `src/control/MissionTypes.ts` +- create `src/control/MissionManager.ts` +- persist mission records to `data/missions.json` +- model relationships between mission and queued voyager tasks + +### E3-S2 Expose `VoyagerLoop` queue safely + +Status: partial + +Acceptance criteria: + +- queued tasks are inspectable without mutating internal arrays unsafely +- queue supports prepend, append, remove, clear, and reorder operations + +Tasks: + +- add queue accessors to `src/voyager/VoyagerLoop.ts` +- add queue item IDs and timestamps +- preserve current decompose-and-queue behavior while surfacing intermediate status + +### E3-S3 Replace ad hoc task entry UI with mission queue panel + +Status: partial + +Acceptance criteria: + +- bot detail page shows current mission, queued missions, recent failures, and completions +- operator can retry, cancel, reorder, and interrupt work + +Tasks: + +- add `MissionQueuePanel` component +- add queue mutation APIs +- show `Do now` vs `Do next` +- show blocked reason and retry shortcut + +## Dependencies + +- depends on E1 and partial `VoyagerLoop` exposure diff --git a/dev/dashrevamp/plan/epics/E4-world-planning-and-map-control.md b/dev/dashrevamp/plan/epics/E4-world-planning-and-map-control.md new file mode 100644 index 0000000..7ec6cb8 --- /dev/null +++ b/dev/dashrevamp/plan/epics/E4-world-planning-and-map-control.md @@ -0,0 +1,76 @@ +# E4 World Planning And Map Control + +Current status: partial + +Current-state note: + +- marker, zone, and route persistence are in place, and the map renders world objects now +- the remaining work is mostly around creation/editing UX and richer command flows from the map + +## Goal + +Make the map a first-class command surface. + +## PM Workstream + +- PM-04 Spatial Control + +## Repo Impact + +- `web/src/app/map/page.tsx` +- `web/src/lib/store.ts` +- `web/src/lib/api.ts` +- new backend world-planning services + +## Stories + +### E4-S1 Refactor map page for extensibility + +Status: mostly done + +Acceptance criteria: + +- map page stops mutating refs during render +- toolbar state and canvas state are easier to extend + +Tasks: + +- fix existing render-time ref patterns +- separate canvas renderer from interaction state +- add selected object state and mode state + +### E4-S2 Add markers, zones, and routes + +Status: partial + +Acceptance criteria: + +- operators can create, edit, and delete markers, zones, and routes +- objects persist across refreshes + +Tasks: + +- create `MarkerStore` backend module +- add REST APIs for markers, zones, and routes +- add map drawing tools and editors +- add list or detail panel for world objects + +### E4-S3 Add click-to-command and context menus + +Status: partial + +Acceptance criteria: + +- operator can select bot(s) and issue move or guard commands directly from the map +- selected map objects can be reused in missions and roles + +Tasks: + +- add terrain context menu +- add entity context menu +- connect map actions to command creation +- display command results on map or side panel + +## Dependencies + +- depends on E9 map cleanup and E1 command creation diff --git a/dev/dashrevamp/plan/epics/E5-fleet-selection-and-squads.md b/dev/dashrevamp/plan/epics/E5-fleet-selection-and-squads.md new file mode 100644 index 0000000..5ca9dda --- /dev/null +++ b/dev/dashrevamp/plan/epics/E5-fleet-selection-and-squads.md @@ -0,0 +1,73 @@ +# E5 Fleet Selection And Squads + +Current status: partial + +Current-state note: + +- squad backend, fleet page, and selection bar exist now +- the remaining work is mostly about shared selection state, complete backend-backed squad flows, and better batch result UX + +## Goal + +Provide intentional multi-bot control. + +## PM Workstream + +- PM-05 Fleet Ops + +## Repo Impact + +- new fleet page +- `web/src/lib/store.ts` +- backend squad APIs and services + +## Stories + +### E5-S1 Add selection model + +Status: partial + +Acceptance criteria: + +- operator can select multiple bots from dashboard, map, or fleet page +- selection state is shared across relevant screens + +Tasks: + +- add selected bot IDs to store +- add bulk action toolbar +- add shared selection UX pattern + +### E5-S2 Add squad persistence + +Status: mostly done + +Acceptance criteria: + +- squads can be created, renamed, edited, and deleted +- squads persist in `data/squads.json` + +Tasks: + +- implement `SquadManager` +- add squad REST APIs +- add squad editor UI + +### E5-S3 Add batch command execution + +Status: partial + +Acceptance criteria: + +- one UI action can create a command affecting multiple bots +- UI shows per-bot success and failure details + +Tasks: + +- add squad or selection command fan-out logic in `CommandCenter` +- add aggregated command result model +- add batch result panel in fleet page + +## Dependencies + +- depends on E1 and E9 shared selection state diff --git a/dev/dashrevamp/plan/epics/E6-roles-and-automation-policies.md b/dev/dashrevamp/plan/epics/E6-roles-and-automation-policies.md new file mode 100644 index 0000000..335e785 --- /dev/null +++ b/dev/dashrevamp/plan/epics/E6-roles-and-automation-policies.md @@ -0,0 +1,72 @@ +# E6 Roles And Automation Policies + +Current status: partial + +Current-state note: + +- role persistence, APIs, page, and assignment panel are present +- policy execution, autonomy enforcement, and strong override semantics are still the major unfinished parts + +## Goal + +Give bots durable, visible dashboard-managed operating roles. + +## PM Workstream + +- PM-06 Role Automation + +## Repo Impact + +- new role service and APIs +- new roles page +- updates to bot detail and fleet views + +## Stories + +### E6-S1 Add role assignment data model + +Status: mostly done + +Acceptance criteria: + +- role assignments match the schema plan +- each bot can have a visible role and autonomy level + +Tasks: + +- create `RoleManager` +- persist role assignments to `data/roles.json` +- add CRUD APIs + +### E6-S2 Add role management UI + +Status: partial + +Acceptance criteria: + +- operator can assign role, home marker, allowed zones, and autonomy level without leaving the dashboard + +Tasks: + +- create roles page +- add role assignment editor to bot detail page +- show role summaries on dashboard/fleet cards + +### E6-S3 Add policy execution and override rules + +Status: missing + +Acceptance criteria: + +- role-generated work is visible as missions +- manual override semantics are respected + +Tasks: + +- define arbitration rules between role missions and manual commands +- generate role-origin missions through `MissionManager` +- add visible badge for role-generated missions + +## Dependencies + +- depends on E3 mission infrastructure and E4 world planning objects diff --git a/dev/dashrevamp/plan/epics/E7-commander-console.md b/dev/dashrevamp/plan/epics/E7-commander-console.md new file mode 100644 index 0000000..8cef9db --- /dev/null +++ b/dev/dashrevamp/plan/epics/E7-commander-console.md @@ -0,0 +1,73 @@ +# E7 Commander Console + +Current status: partial + +Current-state note: + +- commander parse/execute backend and UI are implemented +- the biggest remaining work is contract alignment, persistent history/drafts, and stronger ambiguity handling + +## Goal + +Support natural language planning without losing transparency or safety. + +## PM Workstream + +- PM-07 Commander + +## Repo Impact + +- new commander page +- parser backend service +- command and mission preview models + +## Stories + +### E7-S1 Add parse-only planner endpoint + +Status: mostly done + +Acceptance criteria: + +- free text returns a typed plan preview without executing +- warnings and confidence are visible + +Tasks: + +- implement `CommanderService.parse` +- add `POST /api/commander/parse` +- map intents to command and mission records + +### E7-S2 Add commander UI + +Status: partial + +Acceptance criteria: + +- operator can edit, confirm, or cancel a proposed plan +- execution goes through the normal command and mission services + +Tasks: + +- create commander page +- add preview panel and warning UI +- add execution history and drafts + +### E7-S3 Handle ambiguity and safety + +Status: partial + +Acceptance criteria: + +- ambiguous or risky commands require clarification or confirmation +- safe simple commands can still feel fast + +Tasks: + +- define ambiguity rules +- define destructive-action confirmations +- add structured warning list to plan object + +## Dependencies + +- depends on stable E1 and E3 models diff --git a/dev/dashrevamp/plan/epics/E8-diagnostics-and-history.md b/dev/dashrevamp/plan/epics/E8-diagnostics-and-history.md new file mode 100644 index 0000000..b784f7f --- /dev/null +++ b/dev/dashrevamp/plan/epics/E8-diagnostics-and-history.md @@ -0,0 +1,57 @@ +# E8 Diagnostics And History + +Current status: partial + +Current-state note: + +- history and diagnostics surfaces exist, including a history page and bot detail diagnostics sections +- the major remaining gap is fidelity: these screens still rely too much on older activity/task data instead of shared command and mission records + +## Goal + +Make the dashboard trustworthy when bots fail or behave unexpectedly. + +## PM Workstream + +- PM-08 Diagnostics + +## Repo Impact + +- bot detail page +- new history page +- event and command storage + +## Stories + +### E8-S1 Add command and mission history views + +Status: partial + +Acceptance criteria: + +- operator can inspect recent command and mission activity across bots +- history can be filtered by bot, type, and status + +Tasks: + +- create history page +- add command history queries +- add mission history queries + +### E8-S2 Add blockers and recovery suggestions + +Status: partial + +Acceptance criteria: + +- bot detail page shows why the bot is stuck and what actions are suggested + +Tasks: + +- expose blocker context from voyager-related services +- show last failure, blocked reason, and suggested actions +- add retry and unstuck shortcuts + +## Dependencies + +- benefits from E1 and E3, but can start once those interfaces are stable diff --git a/dev/dashrevamp/plan/epics/E9-frontend-architecture-hardening.md b/dev/dashrevamp/plan/epics/E9-frontend-architecture-hardening.md new file mode 100644 index 0000000..413458c --- /dev/null +++ b/dev/dashrevamp/plan/epics/E9-frontend-architecture-hardening.md @@ -0,0 +1,73 @@ +# E9 Frontend Architecture Hardening + +Current status: partial + +Current-state note: + +- reusable components were added, but frontend architecture is still one of the biggest open risks +- duplicated stores and incomplete socket-first control syncing are the main blockers for the rest of the revamp + +## Goal + +Keep the frontend maintainable while feature count rises. + +## PM Workstream + +- PM-09 Frontend System + +## Repo Impact + +- `web/src/lib/store.ts` +- `web/src/components/SocketProvider.tsx` +- major app pages and new reusable components + +## Stories + +### E9-S1 Split store into logical slices + +Status: partial + +Acceptance criteria: + +- store supports telemetry, control, missions, world planning, fleet, and roles cleanly +- new features do not pile unrelated state into one flat object + +Tasks: + +- refactor store shape +- add typed selectors +- avoid regression in existing live bot views + +### E9-S2 Upgrade socket provider to event-first control sync + +Status: partial + +Acceptance criteria: + +- command and mission state updates come primarily from sockets +- polling remains fallback only + +Tasks: + +- subscribe to `command:*`, `mission:*`, `marker:*`, `squad:*`, `role:*` +- normalize event handling into store updates + +### E9-S3 Create reusable control components + +Status: mostly done + +Acceptance criteria: + +- queue, command, selection, and editor UIs are shared instead of page-specific clones + +Tasks: + +- create `MissionQueuePanel` +- create `CommandHistoryPanel` +- create `FleetSelectionBar` +- create `MapContextMenu` +- create `RoleAssignmentPanel` + +## Dependencies + +- should begin early; E4 especially depends on this cleanup diff --git a/dev/dashrevamp/plan/features.md b/dev/dashrevamp/plan/features.md new file mode 100644 index 0000000..12e45af --- /dev/null +++ b/dev/dashrevamp/plan/features.md @@ -0,0 +1,305 @@ +# Feature Inventory + +This file now tracks both scope and implementation status. + +Status legend: + +- Implemented - present and broadly working +- Partial - present but incomplete, inconsistent, or not fully wired +- Missing - not meaningfully implemented yet + +## Scope Summary + +This plan covers the full dashboard control program across six control directions: + +- command center +- mission planner +- map-first control +- squad control +- role-based automation +- chat-as-control + +## 1. Command Center + +Implementation status: partial + +### Goals + +- let operators intervene instantly +- minimize clicks for common actions +- provide visible command feedback + +### Core features + +- per-bot quick actions - implemented +- bulk quick actions for selected bots - partial +- command pending/running/success/failure states - partial +- inline feedback and history - partial +- manual override indicators - partial + +### Initial command set + +- stop current movement +- pause voyager +- resume voyager +- follow player +- walk to coordinates +- move to marker +- return to base +- regroup to selected leader or marker +- guard area +- patrol route +- deposit inventory +- withdraw loadout +- equip best gear +- eat if needed +- unstuck / recover +- clear override + +Current notes: + +- backend command handlers cover much of this list +- frontend tactical UI currently exposes only a subset cleanly +- `deposit_inventory` is still stubbed + +### Future command set + +- escort player +- transfer items between bots +- reserve a chest or station +- assist another bot +- scout route +- avoid zone + +## 2. Mission Planner + +Implementation status: partial + +### Goals + +- make planned work visible and manageable +- expose queue operations currently hidden inside `VoyagerLoop` +- support interrupt vs append semantics + +### Core features + +- visible current mission and queued missions - implemented +- mission priorities - partial +- append, prepend, cancel, retry, clear, reorder - partial +- blocked / failed / completed states - partial +- mission templates - missing +- mission dependencies - partial +- per-bot and multi-bot missions - partial + +Current notes: + +- mission records exist, but UI/history is not fully driven by them yet +- queue management is still split between `MissionManager` and raw Voyager queue access + +### Mission types + +- gather item(s) +- craft item(s) +- smelt batch +- deposit / withdraw +- move to marker / zone +- escort / follow +- patrol area +- build schematic +- supply chain run +- custom typed mission composed from subtasks + +## 3. Map-First Control + +Implementation status: partial + +### Goals + +- make the map a command surface, not just a viewer +- turn world planning into reusable data + +### Core features + +- click-to-move - partial +- right-click action menu - partial +- drag-to-define zone - missing +- save named markers - implemented +- create patrol routes - partial +- assign mission from a selected map object - missing +- display squad locations, trails, areas, build sites, and danger zones - partial + +### World objects + +- point markers: `Base`, `Mine`, `Village`, `Storage A` +- zones: circle or rectangle +- routes: ordered waypoints +- build sites +- resource nodes +- hazard zones + +Current notes: + +- marker/zone/route persistence exists +- route and zone editing UX is still incomplete +- map-first command flows lag behind backend command capabilities + +## 4. Squad Control + +Implementation status: partial + +### Goals + +- operate on many bots at once without copy-pasting commands +- support durable teams for shared jobs + +### Core features + +- multi-select bots - partial +- ad hoc groups from current selection - partial +- named squads - implemented +- squad default roles - missing +- batch commands with per-bot result tracking - partial +- squad missions - partial +- regroup and formation helpers - missing + +Current notes: + +- squads backend exists and fleet page exists +- selection state is split across multiple frontend stores +- `activeMissionId` on squads still appears unused + +### Example squads + +- Builders +- Guards +- Miners +- Haulers +- Scouts + +## 5. Role-Based Automation + +Implementation status: partial + +### Goals + +- reduce repetitive operator work +- support persistent bot specialization + +### Core features + +- assign role to bot - implemented +- role configuration panel - implemented +- role home marker - implemented +- allowed / preferred zones - partial +- restock policy - missing +- interruption policy - missing +- autonomy level - partial +- visible role health and status - missing + +Current notes: + +- CRUD exists, but policy execution and autonomy enforcement are still missing +- overrides are tracked in backend state but not yet surfaced consistently in UI + +### Proposed roles + +- guard +- builder +- hauler +- farmer +- miner +- scout +- merchant +- free agent + +## 6. Chat-As-Control + +Implementation status: partial + +### Goals + +- support fast high-level operator intent +- preserve visibility and safety by turning text into typed plans + +### Core features + +- command console input - implemented +- parser output preview - partial +- confirmation before execution for impactful actions - implemented +- structured plan display - partial +- execution history - partial +- suggested clarifications for ambiguous inputs - missing + +Current notes: + +- commander backend and page exist +- frontend/backend response contracts need alignment +- history is currently page-local, not a persistent shared record + +### Example prompts + +- send all guards to the village +- have Ada smelt all iron and deposit it at Storage A +- pause every codegen bot except builders +- move the builders to Build Site 2 and start the schematic + +## 7. Recovery, Diagnostics, and Ops Tooling + +Implementation status: partial + +### Goals + +- make failures actionable +- reduce time spent guessing why a bot is stuck + +### Core features + +- last command result - partial +- last mission failure and reason - partial +- blocked state explanation - partial +- retry suggestions - missing +- quick recovery actions - partial +- command and mission history - partial +- bot detail diagnostics timeline - missing + +Current notes: + +- diagnostics and history surfaces exist +- several still rely on activity/task history instead of shared command/mission records + +## 8. Saved Routines And Templates + +Implementation status: missing + +### Goals + +- make repeatable control easy + +### Core features + +- command macros - missing +- reusable mission templates - missing +- preset squad operations - missing +- named loadouts - missing + +Current notes: + +- only narrow template-like behavior exists today, such as chain templates + +### Examples + +- Resupply builder +- Guard perimeter +- Smelt all ore +- Harvest wheat loop +- Return inventory to base + +## Acceptance Lens + +Every feature must answer: + +- What object does it create or mutate? +- How does the operator see progress and failure? +- How does it behave when voyager is running? +- Can it be applied to one bot and many bots? +- Can it be triggered from another surface such as the map or commander? diff --git a/dev/dashrevamp/plan/frontend-architecture.md b/dev/dashrevamp/plan/frontend-architecture.md new file mode 100644 index 0000000..920e8d8 --- /dev/null +++ b/dev/dashrevamp/plan/frontend-architecture.md @@ -0,0 +1,118 @@ +# Frontend Architecture Plan + +## Current State + +The dashboard already has a strong base: + +- route structure under `web/src/app/` +- shared API client in `web/src/lib/api.ts` +- Zustand state in `web/src/lib/store.ts` +- live synchronization in `web/src/components/SocketProvider.tsx` + +Today the store is telemetry-focused. The revamp needs it to become operator-focused too. + +## Frontend Design Goals + +- one interaction model across pages +- reusable control primitives +- live state and mutation history in one place +- no page-specific command logic that cannot be reused elsewhere + +## Store Evolution + +Extend `web/src/lib/store.ts` with new slices. + +### `control` slice + +- pending commands by id +- recent command history +- selected bot ids +- active manual overrides +- current commander draft plan + +### `missions` slice + +- mission records by id +- mission ids by bot +- mission ids by squad +- filters and active view state + +### `worldPlanning` slice + +- markers +- zones +- routes +- selected map object +- drawing mode + +### `fleet` slice + +- squads +- current selection set +- bulk action state + +### `roles` slice + +- role assignments +- policy edit state + +## Component System + +Create reusable control components under `web/src/components/`. + +### Proposed components + +- `CommandButtonGroup` +- `CommandHistoryPanel` +- `MissionQueuePanel` +- `MissionComposer` +- `FleetSelectionBar` +- `MapContextMenu` +- `MarkerEditor` +- `ZoneEditor` +- `RoleAssignmentPanel` +- `CommanderPanel` + +## Page Strategy + +### Evolve existing pages + +- `web/src/app/page.tsx` becomes the fleet overview and exception dashboard +- `web/src/app/bots/[name]/page.tsx` becomes the deep individual control page +- `web/src/app/map/page.tsx` becomes the primary tactical map surface +- `web/src/app/manage/page.tsx` becomes admin setup and lower-frequency bot management, not the main control page +- `web/src/app/build/page.tsx` and `web/src/app/chains/page.tsx` become specialized mission surfaces integrated with the shared mission model + +### Add pages + +- `web/src/app/fleet/page.tsx` +- `web/src/app/roles/page.tsx` +- `web/src/app/commander/page.tsx` +- `web/src/app/history/page.tsx` + +## Data Flow + +### Read path + +- initial load from REST +- live updates from Socket.IO +- selective polling only for fallbacks or slow-changing objects + +### Write path + +- page triggers API mutation through `web/src/lib/api.ts` +- optimistic UI only for clearly safe operations +- final state reconciled by socket lifecycle events + +## UX Guidance + +- surface command and mission state visibly where the action was launched +- support keyboard workflows for power users +- preserve low-friction one-click actions +- keep confirmation prompts for high-impact operations only + +## Repo-Specific Notes + +- `web/src/app/map/page.tsx` has existing lint issues around ref usage; budget refactor time before layering on major interaction features +- `web/src/components/SocketProvider.tsx` should subscribe to new `command:*`, `mission:*`, `marker:*`, `squad:*`, and `role:*` events +- `web/src/lib/api.ts` should become the typed source of truth for new control APIs diff --git a/dev/dashrevamp/plan/milestones.md b/dev/dashrevamp/plan/milestones.md new file mode 100644 index 0000000..e42fc45 --- /dev/null +++ b/dev/dashrevamp/plan/milestones.md @@ -0,0 +1,171 @@ +# Milestones + +## Milestone Strategy + +Deliver the revamp in layers so each milestone builds reusable infrastructure for the next one. + +## M1 - Shared Control Model + +### Current status + +Mostly implemented + +### Outcome + +The repo has a unified command model and explicit command lifecycle events. + +### Scope + +- add control domain types +- create backend command service +- expose command APIs +- emit `command:*` socket events +- add command history and pending state in frontend store +- convert current direct controls to the shared model + +### Exit criteria + +- direct commands no longer depend on one-off handlers only - done +- per-bot commands emit visible lifecycle updates - partial +- frontend can show pending and completed command states - partial + +## M2 - Mission Queue And Planner + +### Current status + +Partial + +### Outcome + +Queued work becomes a first-class, inspectable mission system. + +### Scope + +- add mission domain models +- expose queued player tasks from `VoyagerLoop` +- add queue CRUD and priority operations +- update bot detail page to show queue, history, failures, and interrupt modes + +### Exit criteria + +- operators can view, add, cancel, reprioritize, and retry missions - partial +- mission state survives refresh and is visible in the UI - partial + +## M3 - Spatial Control + +### Current status + +Partial + +### Outcome + +Map interactions can create markers, zones, and commands. + +### Scope + +- marker and zone persistence +- map click actions +- map context menu +- route creation +- marker-aware commands and missions + +### Exit criteria + +- operator can command a bot or squad from the map - partial +- world objects are reusable across pages - partial + +## M4 - Fleet And Squads + +### Current status + +Partial + +### Outcome + +Multi-bot control is an intentional product surface. + +### Scope + +- multi-select model +- named squads +- batch commands +- squad missions +- fleet page and squad detail panel + +### Exit criteria + +- the operator can issue a single operation to many bots and track partial success - partial + +## M5 - Roles And Automation + +### Current status + +Partial + +### Outcome + +Bots can hold persistent role assignments with visible policy configuration. + +### Scope + +- role models and persistence +- role editor UI +- automation policies +- manual override integration + +### Exit criteria + +- bots can run with durable dashboard-managed roles and visible autonomy state - partial + +## M6 - Commander Console + +### Current status + +Partial + +### Outcome + +Natural language commands become a safe shell over typed commands and missions. + +### Scope + +- intent parser service +- preview and confirmation UI +- ambiguity resolution +- execution through shared command and mission services + +### Exit criteria + +- no natural-language action executes without producing a structured plan first - partial + +## M7 - Hardening And Release + +### Current status + +Partial + +### Outcome + +The control platform is production-ready for active use. + +### Scope + +- telemetry and alerting +- QA suite +- docs and operator guides +- cleanup of old control code paths +- performance and lint stabilization in touched areas + +### Exit criteria + +- feature set is stable enough for real operator workflows - partial +- critical flows are tested - partial +- old and new systems are reconciled cleanly - partial + +## Current Priority Gaps + +- unify frontend control state and lifecycle subscriptions +- finish mission queue actions and history fidelity +- complete map/fleet/role integrations on top of shared control records +- align commander contracts and persistence +- deepen automated test coverage, especially frontend and cross-feature flows diff --git a/dev/dashrevamp/plan/next-sprint.md b/dev/dashrevamp/plan/next-sprint.md new file mode 100644 index 0000000..b37b2e9 --- /dev/null +++ b/dev/dashrevamp/plan/next-sprint.md @@ -0,0 +1,113 @@ +# Next Sprint Plan + +This sprint plan is derived from `current-state.md` and the updated milestone backlog. + +Goal: make the revamp feel coherent end to end, not just broadly present. + +## Sprint Outcome + +By the end of this sprint: + +- command and mission lifecycle state should update the UI from shared records +- mission queue actions should work for real +- selection state should behave consistently across dashboard, fleet, and map +- commander and role flows should stop drifting from backend contracts + +## Priority 1 - Frontend Control State Unification + +Why: + +- this is the biggest blocker for tactical UI, diagnostics, fleet UX, and commander polish + +Targets: + +- consolidate overlapping state in `web/src/lib/store.ts`, `web/src/lib/controlStore.ts`, and `web/src/lib/missionStore.ts` +- make one source of truth for: + - selected bots + - pending/active/completed commands + - mission records and queue state + - override state + +Definition of done: + +- dashboard, bot detail, fleet, and map use the same selection and command/mission state + +## Priority 2 - Socket-Driven Command And Mission Updates + +Why: + +- the backend already emits lifecycle events, but the frontend still falls back to older polling/data paths too often + +Targets: + +- wire `command:*` and `mission:*` event families into `web/src/components/SocketProvider.tsx` +- normalize events into the shared frontend store +- update tactical views to render from shared records instead of ad hoc activity/task history where possible + +Definition of done: + +- creating or updating a command/mission is reflected in the UI without manual refresh or custom page-local state + +## Priority 3 - Mission Queue End-To-End Actions + +Why: + +- queue visibility exists, but the revamp still lacks trustworthy queue control + +Targets: + +- make `MissionQueuePanel` controls functional for retry, cancel, reorder, and priority changes +- align queue APIs and UI behavior around actual mission records where possible +- expose clearer blocked/failure reasons and interrupt semantics + +Definition of done: + +- an operator can view and mutate queue order/work state from the bot detail surface with predictable results + +## Priority 4 - Fleet Selection Consistency + +Why: + +- multi-bot UX exists but is fragmented because state is split across pages/stores + +Targets: + +- unify selection across dashboard, fleet, and map +- ensure `FleetSelectionBar` reflects the same selection everywhere +- align batch command UX with backend fan-out results + +Definition of done: + +- selecting bots on one control surface immediately carries to the others + +## Priority 5 - Commander And Role Integration Cleanup + +Why: + +- both features exist, but they are not yet dependable enough to treat as finished surfaces + +Targets: + +- align commander parse/execute frontend contracts with backend responses +- persist commander history or draft state beyond page-local memory +- fix role assignment update/delete flow mismatches +- start wiring role policy execution through `MissionManager` + +Definition of done: + +- commander preview/execute works cleanly, and roles no longer feel CRUD-only + +## Stretch Goals + +- add zone editor and route authoring UX on the map +- replace history/activity fallbacks with true command/mission record views +- add regression tests for commander, fleet selection, and mission queue actions + +## Suggested Task Order + +1. unify frontend stores +2. wire `command:*` and `mission:*` socket handlers +3. make mission queue actions real +4. unify selection across dashboard/fleet/map +5. fix commander contracts and role assignment flow +6. add focused tests for the new shared flows diff --git a/dev/dashrevamp/plan/roadmap.md b/dev/dashrevamp/plan/roadmap.md new file mode 100644 index 0000000..a112391 --- /dev/null +++ b/dev/dashrevamp/plan/roadmap.md @@ -0,0 +1,102 @@ +# Delivery Roadmap + +This roadmap is now forward-looking from the current repo state, not from a blank revamp starting point. + +## Phase 0 - Preparation + +- status: completed +- shipped: shared control vocabulary, persistence strategy under `data/`, and core page/service ownership in practice +- note: this phase is historical; the repo has already moved beyond planning-only work + +## Phase 1 - MVP Control Platform + +- status: mostly completed +- shipped + - shared command model + - command lifecycle events on the backend + - command APIs and legacy endpoint migration +- remaining + - frontend command history should be fully sourced from shared command records + - command lifecycle socket handling still needs full frontend adoption + +## Phase 2 - Visible Work Planning + +- status: partial +- shipped + - mission models and mission APIs + - mission queue visibility surfaces on bot detail +- remaining + - reprioritization/retry controls need full end-to-end behavior + - unified mission history and interrupt semantics still need polish + - diagnostics should rely on shared mission/control state instead of mixed sources + +## Phase 3 - Spatial Operations + +- status: partial +- shipped + - markers, zones, routes, and CRUD APIs + - map rendering and marker editing +- remaining + - richer click-to-command flows + - zone and route authoring UX + - map overlays for squads, missions, and hazards/build sites + +## Phase 4 - Fleet Operations + +- status: partial +- shipped + - squads backend + - fleet page and selection bar + - backend batch command fan-out +- remaining + - shared selection state across pages + - backend-backed fleet UX for all squad operations + - richer squad mission assignment and result tracking + +## Phase 5 - Persistent Automation + +- status: partial +- shipped + - role persistence and CRUD + - roles page and assignment panel + - autonomy settings and zone/home-marker fields +- remaining + - policy execution + - autonomy enforcement + - loadout/restock/interruption behavior + +## Phase 6 - High-Level Control + +- status: partial +- shipped + - commander parse/execute backend + - commander page with preview/warnings/confirm flow +- remaining + - resolve API/UI contract drift + - add persistent commander drafts/history + - implement command templates and routines + +## Phase 7 - Production Hardening + +- status: partial +- shipped + - backend tests for major control services + - basic telemetry and metrics surfaces +- remaining + - stronger frontend coverage + - standardize telemetry/log fields across control services + - finish duplicated-store and legacy-flow cleanup + +## Suggested First Release Boundary + +The old release boundary was "after Phase 3." The repo is now beyond that point in raw surface area but not yet fully hardened. + +The more realistic current release boundary is after these gaps are closed: + +- command/mission lifecycle is fully wired into the frontend +- mission queue controls are fully functional +- fleet selection and roles behave consistently across pages +- commander contracts are aligned +- key diagnostics/history views are based on shared records + +That would make the revamp feel cohesive instead of just broadly present. diff --git a/dev/dashrevamp/plan/telemetry.md b/dev/dashrevamp/plan/telemetry.md new file mode 100644 index 0000000..c1b8179 --- /dev/null +++ b/dev/dashrevamp/plan/telemetry.md @@ -0,0 +1,60 @@ +# Telemetry And Ops Visibility + +## Goals + +- understand operator usage +- debug command and mission failures quickly +- measure whether the revamp improves controllability + +## Metrics + +### Command metrics + +- commands created per type +- command success rate +- command failure rate by type +- median command start latency +- median command completion latency + +### Mission metrics + +- missions created per type +- mission completion rate +- mission retries per type +- mission blocked rate +- mission cancellation rate + +### Fleet metrics + +- bots under manual override +- bots with active roles +- squads active per hour +- operators using map commands vs button commands + +### Commander metrics + +- parse success rate +- confirmation rate +- abandonment rate +- ambiguity prompts triggered + +## Logging Guidance + +Use the shared logger in `src/util/logger.ts` and log structured fields like: + +- `commandId` +- `missionId` +- `bot` +- `squadId` +- `role` +- `markerId` +- `source` + +## Dashboard Health Panels + +The UI should eventually show: + +- commands failing most often +- bots needing attention +- most-used markers and missions +- stuck role assignments diff --git a/dev/dashrevamp/plan/user-flows.md b/dev/dashrevamp/plan/user-flows.md new file mode 100644 index 0000000..40d9de7 --- /dev/null +++ b/dev/dashrevamp/plan/user-flows.md @@ -0,0 +1,63 @@ +# User Flows + +## Flow 1 - Tactical Single-Bot Command + +1. Operator opens bot detail page +2. Operator clicks `Return To Base` +3. Frontend creates a command via `POST /api/commands` +4. Backend validates the target bot and marker +5. Backend emits `command:queued` and `command:started` +6. UI shows pending state on the command center and in history +7. Backend emits success or failure event +8. UI stores result and offers retry if needed + +## Flow 2 - Queue A Mission + +1. Operator enters a mission on bot detail page +2. Operator chooses `Do now` or `Do next` +3. Frontend creates a mission or queue entry +4. Backend links mission to `VoyagerLoop` +5. Mission state becomes visible on the queue panel +6. On decomposition, subtasks update the mission view +7. If blocked, UI shows reason and recovery actions + +## Flow 3 - Map-First Move Command + +1. Operator selects one or more bots on the map +2. Operator clicks a location +3. Map context menu offers `Move`, `Guard here`, `Create marker`, `Build here` +4. Operator chooses `Move` +5. Frontend submits a command with map coordinates or marker reference +6. Live events update command and bot movement state + +## Flow 4 - Create And Use A Marker + +1. Operator enters marker mode on the map +2. Operator places a marker and names it `Storage A` +3. Marker is persisted and broadcast +4. Marker appears in command dropdowns, role settings, and mission creation flows + +## Flow 5 - Create Squad And Run Batch Command + +1. Operator selects four bots in fleet view +2. Operator saves them as squad `Builders` +3. Operator chooses `Move to Build Site 2` +4. Backend creates one squad-scoped command and child bot results +5. UI shows aggregated progress and partial failures + +## Flow 6 - Assign Role + +1. Operator opens roles page +2. Operator assigns bot `Cy` to role `Hauler` +3. Operator picks home marker and allowed zones +4. Backend stores assignment and enables role policy evaluation +5. UI shows current role, autonomy level, and active policy-generated missions + +## Flow 7 - Natural Language Command + +1. Operator types: `send all guards to the village` +2. Frontend calls `POST /api/commander/parse` +3. Backend returns a typed plan preview +4. Operator confirms execution +5. Commands and missions are created normally through shared services +6. UI tracks them like any other action diff --git a/dev/dashrevamp/plan/vision.md b/dev/dashrevamp/plan/vision.md new file mode 100644 index 0000000..7b7ff59 --- /dev/null +++ b/dev/dashrevamp/plan/vision.md @@ -0,0 +1,112 @@ +# Product Vision + +## Vision Statement + +Transform the dashboard from a monitoring panel into a real-time operations console for managing autonomous Minecraft bots individually and as a fleet. + +The dashboard should let an operator do three things well: + +- Understand what every bot is doing and why +- Intervene quickly with safe, visible control tools +- Compose bigger coordinated behaviors without dropping into code or raw backend calls + +## Product Principles + +### Human intent, bot execution + +Operators should specify intent at the right altitude. The system should support: + +- direct commands for urgent tactical actions +- structured missions for repeatable work +- automation policies for steady-state behavior +- natural-language planning for power users + +### Visible autonomy + +The dashboard must not feel magical or opaque. Every important bot action should reveal: + +- what was requested +- what is executing now +- what is queued next +- what failed +- what the bot needs to recover + +### Manual override is sacred + +If a human says "do this now," the system should have a clear override model. Autonomy should resume only when the operator wants it to. + +### One control model, many surfaces + +Map clicks, quick actions, role automations, squad commands, and chat commands should all land on the same backend command and mission systems. + +### Safety over cleverness + +The system should prefer explicit, reviewable command plans over opaque execution. Especially for multi-bot, destructive, or player-facing operations. + +## Primary Personas + +### Solo operator + +- Runs 1-3 bots +- Wants quick tactical control and visibility +- Cares about easy recovery, movement, and chat interactions + +### Fleet operator + +- Runs many bots with different roles +- Wants batch control, squads, queues, and automation rules +- Needs status summaries and exception management + +### Builder / world organizer + +- Coordinates build sites, chests, routes, and shared tasks +- Needs map-first controls, markers, zones, and resource movement + +### Power user / AI tinkerer + +- Wants high-level command authoring and natural-language planning +- Cares about history, diagnostics, and command transparency + +## Product Pillars + +### 1. Tactical control + +Fast actions like follow, stop, regroup, move, return home, deposit inventory, or unstick a bot. + +### 2. Structured execution + +Tasks and missions should be explicit objects with state, ownership, retries, and visibility. + +### 3. Spatial control + +Minecraft is spatial. Markers, zones, routes, and map interactions should become first-class. + +### 4. Fleet operations + +Multi-bot work should feel intentional, not stitched together from single-bot actions. + +### 5. Automation with guardrails + +Persistent roles and routines should reduce micromanagement without hiding behavior. + +## Success Criteria + +The revamp is successful if the dashboard can support all of these without leaving the UI: + +- move a bot or squad to a meaningful destination +- queue, inspect, reprioritize, and cancel work +- define places and areas in the world and reuse them across workflows +- assign bots to stable operating roles +- recover bots that are stuck or interrupted +- issue a typed or natural-language command and see a structured execution plan + +## Non-Goals For The Initial Revamp + +- replacing the entire voyager/autonomy system +- building a full RTS combat AI layer +- introducing multiplayer operator auth or permissions beyond simple admin assumptions +- solving every gameplay primitive before the control model exists + +## Product Thesis + +This repo becomes much more valuable when the dashboard stops being a pretty status view and becomes the operating system for the bots. diff --git a/package-lock.json b/package-lock.json index 39bd2a6..ff56ea4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,7 +33,8 @@ "@types/js-yaml": "^4.0.9", "@types/node": "^22.0.0", "tsx": "^4.19.0", - "typescript": "^5.7.0" + "typescript": "^5.7.0", + "vitest": "^4.1.0" } }, "node_modules/@azure/msal-common": { @@ -199,6 +200,40 @@ "node": ">=6.9.0" } }, + "node_modules/@emnapi/core": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz", + "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", + "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", + "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.4", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", @@ -676,18 +711,325 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", + "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.120.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.120.0.tgz", + "integrity": "sha512-k1YNu55DuvAip/MGE1FTsIuU3FUCn6v/ujG9V7Nq5Df/kX2CWb13hhwD0lmJGMGqE+bE1MXvv9SZVnMzEXlWcg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, "node_modules/@pinojs/redact": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", "license": "MIT" }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.10.tgz", + "integrity": "sha512-jOHxwXhxmFKuXztiu1ORieJeTbx5vrTkcOkkkn2d35726+iwhrY1w/+nYY/AGgF12thg33qC3R1LMBF5tHTZHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.10.tgz", + "integrity": "sha512-gED05Teg/vtTZbIJBc4VNMAxAFDUPkuO/rAIyyxZjTj1a1/s6z5TII/5yMGZ0uLRCifEtwUQn8OlYzuYc0m70w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.10.tgz", + "integrity": "sha512-rI15NcM1mA48lqrIxVkHfAqcyFLcQwyXWThy+BQ5+mkKKPvSO26ir+ZDp36AgYoYVkqvMcdS8zOE6SeBsR9e8A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.10.tgz", + "integrity": "sha512-XZRXHdTa+4ME1MuDVp021+doQ+z6Ei4CCFmNc5/sKbqb8YmkiJdj8QKlV3rCI0AJtAeSB5n0WGPuJWNL9p/L2w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.10.tgz", + "integrity": "sha512-R0SQMRluISSLzFE20sPWYHVmJdDQnRyc/FzSCN72BqQmh2SOZUFG+N3/vBZpR4C6WpEUVYJLrYUXaj43sJsNLA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.10.tgz", + "integrity": "sha512-Y1reMrV/o+cwpduYhJuOE3OMKx32RMYCidf14y+HssARRmhDuWXJ4yVguDg2R/8SyyGNo+auzz64LnPK9Hq6jg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.10.tgz", + "integrity": "sha512-vELN+HNb2IzuzSBUOD4NHmP9yrGwl1DVM29wlQvx1OLSclL0NgVWnVDKl/8tEks79EFek/kebQKnNJkIAA4W2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.10.tgz", + "integrity": "sha512-ZqrufYTgzxbHwpqOjzSsb0UV/aV2TFIY5rP8HdsiPTv/CuAgCRjM6s9cYFwQ4CNH+hf9Y4erHW1GjZuZ7WoI7w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.10.tgz", + "integrity": "sha512-gSlmVS1FZJSRicA6IyjoRoKAFK7IIHBs7xJuHRSmjImqk3mPPWbR7RhbnfH2G6bcmMEllCt2vQ/7u9e6bBnByg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.10.tgz", + "integrity": "sha512-eOCKUpluKgfObT2pHjztnaWEIbUabWzk3qPZ5PuacuPmr4+JtQG4k2vGTY0H15edaTnicgU428XW/IH6AimcQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.10.tgz", + "integrity": "sha512-Xdf2jQbfQowJnLcgYfD/m0Uu0Qj5OdxKallD78/IPPfzaiaI4KRAwZzHcKQ4ig1gtg1SuzC7jovNiM2TzQsBXA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.10.tgz", + "integrity": "sha512-o1hYe8hLi1EY6jgPFyxQgQ1wcycX+qz8eEbVmot2hFkgUzPxy9+kF0u0NIQBeDq+Mko47AkaFFaChcvZa9UX9Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.10.tgz", + "integrity": "sha512-Ugv9o7qYJudqQO5Y5y2N2SOo6S4WiqiNOpuQyoPInnhVzCY+wi/GHltcLHypG9DEUYMB0iTB/huJrpadiAcNcA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.1.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.10.tgz", + "integrity": "sha512-7UODQb4fQUNT/vmgDZBl3XOBAIOutP5R3O/rkxg0aLfEGQ4opbCgU5vOw/scPe4xOqBwL9fw7/RP1vAMZ6QlAQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.10.tgz", + "integrity": "sha512-PYxKHMVHOb5NJuDL53vBUl1VwUjymDcYI6rzpIni0C9+9mTiJedvUxSk7/RPp7OOAm3v+EjgMu9bIy3N6b408w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.10.tgz", + "integrity": "sha512-UkVDEFk1w3mveXeKgaTuYfKWtPbvgck1dT8TUG3bnccrH0XtLTuAyfCoks4Q/M5ZGToSVJTIQYCzy2g/atAOeg==", + "dev": true, + "license": "MIT" + }, "node_modules/@socket.io/component-emitter": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", "license": "MIT" }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/babel__generator": { "version": "7.27.0", "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", @@ -719,6 +1061,17 @@ "@types/node": "*" } }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", @@ -738,6 +1091,20 @@ "@types/node": "*" } }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/express": { "version": "5.0.6", "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", @@ -848,6 +1215,119 @@ "@types/node": "*" } }, + "node_modules/@vitest/expect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.0.tgz", + "integrity": "sha512-EIxG7k4wlWweuCLG9Y5InKFwpMEOyrMb6ZJ1ihYu02LVj/bzUwn2VMU+13PinsjRW75XnITeFrQBMH5+dLvCDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.0", + "@vitest/utils": "4.1.0", + "chai": "^6.2.2", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.0.tgz", + "integrity": "sha512-evxREh+Hork43+Y4IOhTo+h5lGmVRyjqI739Rz4RlUPqwrkFFDF6EMvOOYjTx4E8Tl6gyCLRL8Mu7Ry12a13Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.0", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.0.tgz", + "integrity": "sha512-3RZLZlh88Ib0J7NQTRATfc/3ZPOnSUn2uDBUoGNn5T36+bALixmzphN26OUD3LRXWkJu4H0s5vvUeqBiw+kS0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.0.tgz", + "integrity": "sha512-Duvx2OzQ7d6OjchL+trw+aSrb9idh7pnNfxrklo14p3zmNL4qPCDeIJAK+eBKYjkIwG96Bc6vYuxhqDXQOWpoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.0", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.0.tgz", + "integrity": "sha512-0Vy9euT1kgsnj1CHttwi9i9o+4rRLEaPRSOJ5gyv579GJkNpgJK+B4HSv/rAWixx2wdAFci1X4CEPjiu2bXIMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.0", + "@vitest/utils": "4.1.0", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.0.tgz", + "integrity": "sha512-pz77k+PgNpyMDv2FV6qmk5ZVau6c3R8HC8v342T2xlFxQKTrSeYw9waIJG8KgV9fFwAtTu4ceRzMivPTH6wSxw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.0.tgz", + "integrity": "sha512-XfPXT6a8TZY3dcGY8EdwsBulFCIw+BeeX0RZn2x/BtiY/75YGh8FeWGG8QISN/WhaqSrE2OrlDgtF8q5uhOTmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.0", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@xboxreplay/errors": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/@xboxreplay/errors/-/errors-0.1.0.tgz", @@ -929,6 +1409,16 @@ "integrity": "sha512-6i37w/+EhlWlGUJff3T/Q8u1RGmP5wgbiwYnOnbOqvtrPxT63/sYFyP9RcpxtxGymtfA075IvmOnL7ycNOWl3w==", "license": "MIT" }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/atomic-sleep": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", @@ -1080,6 +1570,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", @@ -1113,6 +1613,13 @@ "node": ">= 0.6" } }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, "node_modules/cookie": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", @@ -1182,6 +1689,16 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/discontinuous-range": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", @@ -1324,6 +1841,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -1384,6 +1908,16 @@ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -1411,6 +1945,16 @@ "node": ">=0.8.x" } }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/express": { "version": "4.22.1", "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", @@ -1481,6 +2025,24 @@ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "license": "MIT" }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/finalhandler": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", @@ -1787,31 +2349,292 @@ "npm": ">=6" } }, - "node_modules/jsonwebtoken/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/jwa": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", - "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", - "license": "MIT", - "dependencies": { - "buffer-equal-constant-time": "^1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/jws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", - "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", - "license": "MIT", - "dependencies": { - "jwa": "^2.0.1", - "safe-buffer": "^5.0.1" + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, "node_modules/lodash.includes": { @@ -1874,6 +2697,16 @@ "integrity": "sha512-i8xVWoUjj2woYU8kbpQby86Kq7uF7xl2brtKREXUBWpfgqx1fKXEeYzDiVMVxA/IufC1d3xxwJRHtFCX+9IspA==", "license": "MIT" }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -2104,6 +2937,25 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/nearley": { "version": "2.20.1", "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz", @@ -2185,6 +3037,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/on-exit-leak-free": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", @@ -2230,12 +3093,32 @@ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/pino": { "version": "9.14.0", "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", @@ -2306,6 +3189,35 @@ "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", "license": "MIT" }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/prismarine-auth": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/prismarine-auth/-/prismarine-auth-2.7.0.tgz", @@ -2727,6 +3639,40 @@ "node": ">=0.12" } }, + "node_modules/rolldown": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.10.tgz", + "integrity": "sha512-q7j6vvarRFmKpgJUT8HCAUljkgzEp4LAhPlJUvQhA5LA1SUL36s5QCysMutErzL3EbNOZOkoziSx9iZC4FddKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.120.0", + "@rolldown/pluginutils": "1.0.0-rc.10" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.10", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.10", + "@rolldown/binding-darwin-x64": "1.0.0-rc.10", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.10", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.10", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.10", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.10", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.10", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.10", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.10", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.10", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.10", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.10", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.10", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.10" + } + }, "node_modules/rxjs": { "version": "7.8.2", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", @@ -2923,6 +3869,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -3052,6 +4005,16 @@ "atomic-sleep": "^1.0.0" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/split2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", @@ -3061,6 +4024,13 @@ "node": ">= 10.x" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -3070,6 +4040,13 @@ "node": ">= 0.8" } }, + "node_modules/std-env": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", + "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", + "dev": true, + "license": "MIT" + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -3100,6 +4077,50 @@ "real-require": "^0.2.0" } }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", + "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -3247,6 +4268,166 @@ "integrity": "sha512-Sr1U3mYtMqCOonGd3LAN9iqy0qF6C+Gjil92awyK/i2OwiUo9bm7PnLgFpafymun50mOjnDcg4ToTgRssrlTcw==", "license": "BSD" }, + "node_modules/vite": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.1.tgz", + "integrity": "sha512-wt+Z2qIhfFt85uiyRt5LPU4oVEJBXj8hZNWKeqFG4gRG/0RaRGJ7njQCwzFVjO+v4+Ipmf5CY7VdmZRAYYBPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.10", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.0", + "esbuild": "^0.27.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.0.tgz", + "integrity": "sha512-YbDrMF9jM2Lqc++2530UourxZHmkKLxrs4+mYhEwqWS97WJ7wOYEkcr+QfRgJ3PW9wz3odRijLZjHEaRLTNbqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.0", + "@vitest/mocker": "4.1.0", + "@vitest/pretty-format": "4.1.0", + "@vitest/runner": "4.1.0", + "@vitest/snapshot": "4.1.0", + "@vitest/spy": "4.1.0", + "@vitest/utils": "4.1.0", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.0", + "@vitest/browser-preview": "4.1.0", + "@vitest/browser-webdriverio": "4.1.0", + "@vitest/ui": "4.1.0", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -3263,6 +4444,23 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index 59bfeba..70c8900 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,9 @@ "scripts": { "build": "tsc", "dev": "tsx src/index.ts", - "start": "node dist/index.js" + "start": "node --max-old-space-size=8192 dist/index.js", + "test": "vitest run", + "test:watch": "vitest" }, "dependencies": { "@babel/generator": "^7.29.1", @@ -34,6 +36,7 @@ "@types/js-yaml": "^4.0.9", "@types/node": "^22.0.0", "tsx": "^4.19.0", - "typescript": "^5.7.0" + "typescript": "^5.7.0", + "vitest": "^4.1.0" } } diff --git a/schematics/Cute house.schem b/schematics/Cute house.schem new file mode 100644 index 0000000..5d2e495 Binary files /dev/null and b/schematics/Cute house.schem differ diff --git a/schematics/Disneyland Space Mountain.schem b/schematics/Disneyland Space Mountain.schem new file mode 100644 index 0000000..8d8070e Binary files /dev/null and b/schematics/Disneyland Space Mountain.schem differ diff --git "a/schematics/Pok\303\251mon Temple Arena.schem" "b/schematics/Pok\303\251mon Temple Arena.schem" new file mode 100644 index 0000000..0538f5f Binary files /dev/null and "b/schematics/Pok\303\251mon Temple Arena.schem" differ diff --git a/schematics/birch house.schem b/schematics/birch house.schem new file mode 100644 index 0000000..847fd9b Binary files /dev/null and b/schematics/birch house.schem differ diff --git a/schematics/md castle 2.schem b/schematics/md castle 2.schem new file mode 100644 index 0000000..65db9e7 Binary files /dev/null and b/schematics/md castle 2.schem differ diff --git a/schematics/victorian palace.schem b/schematics/victorian palace.schem new file mode 100644 index 0000000..967d31d Binary files /dev/null and b/schematics/victorian palace.schem differ diff --git a/skills/build_a_small_3x3_cobblestone.js b/skills/build_a_small_3x3_cobblestone.js new file mode 100644 index 0000000..c0ccc4a --- /dev/null +++ b/skills/build_a_small_3x3_cobblestone.js @@ -0,0 +1,35 @@ +async function buildSmallCobblestoneShelter(bot) { + const getCobbleCount = () => { + const item = bot.inventory.items().find(i => i.name === 'cobblestone'); + return item ? item.count : 0; + }; + const needed = 16; + if (getCobbleCount() < needed) { + await mineBlock('stone', needed - getCobbleCount()); + } + const pos = bot.entity.position.floored(); + const ox = pos.x + 2; + const oy = pos.y; + const oz = pos.z + 2; + const structure = []; + // Build 2 layers of a 3x3 outer ring + for (let y = 0; y < 2; y++) { + for (let x = 0; x < 3; x++) { + for (let z = 0; z < 3; z++) { + // Only the perimeter + if (x === 0 || x === 2 || z === 0 || z === 2) { + // Leave a gap for a door at (1, 0) + if (x === 1 && z === 0) continue; + structure.push({ + x: ox + x, + y: oy + y, + z: oz + z + }); + } + } + } + } + for (const p of structure) { + await placeItem('cobblestone', p.x, p.y, p.z); + } +} \ No newline at end of file diff --git a/skills/collect_5_iron_ingots_from.js b/skills/collect_5_iron_ingots_from.js new file mode 100644 index 0000000..86b9a5a --- /dev/null +++ b/skills/collect_5_iron_ingots_from.js @@ -0,0 +1,7 @@ +async function collectIronIngotsFromChest(bot) { + const chestX = 858; + const chestY = 65; + const chestZ = 254; + await moveTo(chestX, chestY, chestZ, 2, 60); + await withdrawItem('chest', 'iron_ingot', 5); +} \ No newline at end of file diff --git a/skills/collect_the_wooden_pickaxe_and.js b/skills/collect_the_wooden_pickaxe_and.js new file mode 100644 index 0000000..cba9173 --- /dev/null +++ b/skills/collect_the_wooden_pickaxe_and.js @@ -0,0 +1,10 @@ +async function collectItemsFromChest(bot) { + const chestPos = { + x: 855, + y: 64, + z: 259 + }; + await moveTo(chestPos.x, chestPos.y, chestPos.z, 2, 60); + await withdrawItem('chest', 'wooden_pickaxe', 1); + await withdrawItem('chest', 'oak_door', 2); +} \ No newline at end of file diff --git a/skills/craft_12_oak_planks.js b/skills/craft_12_oak_planks.js new file mode 100644 index 0000000..3df2dcc --- /dev/null +++ b/skills/craft_12_oak_planks.js @@ -0,0 +1,18 @@ +async function craftTwelveOakPlanks(bot) { + const plankName = 'oak_planks'; + const logName = 'oak_log'; + const targetCount = 12; + const currentPlanks = bot.inventory.items().find(i => i.name === plankName); + const currentPlankCount = currentPlanks ? currentPlanks.count : 0; + if (currentPlankCount >= targetCount) { + return; + } + const neededPlanks = targetCount - currentPlankCount; + const neededLogs = Math.ceil(neededPlanks / 4); + const currentLogs = bot.inventory.items().find(i => i.name === logName); + const currentLogCount = currentLogs ? currentLogs.count : 0; + if (currentLogCount < neededLogs) { + await mineBlock(logName, neededLogs - currentLogCount); + } + await craftItem(plankName, targetCount); +} \ No newline at end of file diff --git a/skills/craft_12_spruce_planks_at.js b/skills/craft_12_spruce_planks_at.js new file mode 100644 index 0000000..1bf8134 --- /dev/null +++ b/skills/craft_12_spruce_planks_at.js @@ -0,0 +1,21 @@ +async function craftTwelveSprucePlanksAtTable(bot) { + const tablePos = { + x: 807, + y: 64, + z: 232 + }; + + // 1. Check if we already have enough spruce logs (3 logs = 12 planks) + let spruceLogs = bot.inventory.items().find(i => i.name === 'spruce_log'); + let currentLogs = spruceLogs ? spruceLogs.count : 0; + if (currentLogs < 3) { + await mineBlock('spruce_log', 3 - currentLogs); + } + + // 2. Move to the specific crafting table location + await moveTo(tablePos.x, tablePos.y, tablePos.z, 3, 120); + + // 3. Craft 12 spruce planks + // craftItem will handle using the crafting table if it's nearby. + await craftItem('spruce_planks', 12); +} \ No newline at end of file diff --git a/skills/craft_1_crafting_table.js b/skills/craft_1_crafting_table.js index 29399b5..40d2c25 100644 --- a/skills/craft_1_crafting_table.js +++ b/skills/craft_1_crafting_table.js @@ -1,18 +1,17 @@ -async function craftCraftingTable(bot) { - const tableItem = bot.inventory.items().find(i => i.name === 'crafting_table'); - if (tableItem && tableItem.count >= 1) { +async function craftOneCraftingTable(bot) { + const craftingTable = bot.inventory.items().find(i => i.name === 'crafting_table'); + if (craftingTable) { return; } - const planksItem = bot.inventory.items().find(i => i.name.endsWith('_planks')); - let planksCount = planksItem ? planksItem.count : 0; - if (planksCount < 4) { - const logItem = bot.inventory.items().find(i => i.name.endsWith('_log')); - if (!logItem) { + let planks = bot.inventory.items().find(i => i.name.endsWith('_planks')); + if (!planks || planks.count < 4) { + let logs = bot.inventory.items().find(i => i.name.endsWith('_log')); + if (!logs) { await mineBlock('oak_log', 1); + logs = bot.inventory.items().find(i => i.name.endsWith('_log')); } - const logName = bot.inventory.items().find(i => i.name.endsWith('_log')).name; - const plankName = logName.replace('_log', '_planks'); - await craftItem(plankName, 4); + const plankType = logs.name.replace('_log', '_planks'); + await craftItem(plankType, 1); } await craftItem('crafting_table', 1); } \ No newline at end of file diff --git a/skills/craft_1_crafting_table_and.js b/skills/craft_1_crafting_table_and.js new file mode 100644 index 0000000..cac3aa1 --- /dev/null +++ b/skills/craft_1_crafting_table_and.js @@ -0,0 +1,36 @@ +async function craftTableAndEightSticks(bot) { + const getStickCount = () => bot.inventory.items().filter(i => i.name === 'stick').reduce((acc, i) => acc + i.count, 0); + const table = bot.inventory.items().find(i => i.name === 'crafting_table'); + const sticks = getStickCount(); + if (table && sticks >= 8) return; + + // Prerequisite: Ensure we have enough planks. + // 1 crafting table (4 planks) + 8 sticks (4 planks) = 8 planks total. + let planks = bot.inventory.items().find(i => i.name === 'oak_planks'); + let currentPlanks = planks ? planks.count : 0; + if (currentPlanks < 8) { + let logs = bot.inventory.items().find(i => i.name === 'oak_log'); + // We need up to 2 logs to get 8 planks. + if (!logs || logs.count < 2) { + const neededLogs = 2 - (logs ? logs.count : 0); + await mineBlock('oak_log', neededLogs); + } + // Re-check logs after mining + logs = bot.inventory.items().find(i => i.name === 'oak_log'); + if (logs && logs.count > 0) { + // Craft enough planks to reach at least 8. + await craftItem('oak_planks', 8); + } + } + + // Craft the crafting table if missing. + if (!bot.inventory.items().find(i => i.name === 'crafting_table')) { + await craftItem('crafting_table', 1); + } + + // Craft sticks until we have at least 8. + const currentSticks = getStickCount(); + if (currentSticks < 8) { + await craftItem('stick', 8); + } +} \ No newline at end of file diff --git a/skills/craft_1_crafting_table_from.js b/skills/craft_1_crafting_table_from.js new file mode 100644 index 0000000..7b41f82 --- /dev/null +++ b/skills/craft_1_crafting_table_from.js @@ -0,0 +1,16 @@ +async function craftOneCraftingTableFromOakPlanks(bot) { + const existingTable = bot.inventory.items().find(i => i.name === 'crafting_table'); + if (existingTable) { + return; + } + let oakPlanks = bot.inventory.items().find(i => i.name === 'oak_planks'); + let plankCount = oakPlanks ? oakPlanks.count : 0; + if (plankCount < 4) { + let oakLog = bot.inventory.items().find(i => i.name === 'oak_log'); + if (!oakLog) { + await mineBlock('oak_log', 1); + } + await craftItem('oak_planks', 1); + } + await craftItem('crafting_table', 1); +} \ No newline at end of file diff --git a/skills/craft_1_crafting_table_using.js b/skills/craft_1_crafting_table_using.js index 5d4be84..9f37850 100644 --- a/skills/craft_1_crafting_table_using.js +++ b/skills/craft_1_crafting_table_using.js @@ -1,10 +1,10 @@ -async function craftOneCraftingTable(bot) { - let tableItem = bot.inventory.items().find(i => i.name === 'crafting_table'); - if (tableItem) return; - let planks = bot.inventory.items().find(i => i.name === 'spruce_planks'); - if (!planks || planks.count < 4) { - let logs = bot.inventory.items().find(i => i.name === 'spruce_log'); - if (!logs || logs.count < 1) { +async function craftCraftingTableFromSprucePlanks(bot) { + const craftingTable = bot.inventory.items().find(i => i.name === 'crafting_table'); + if (craftingTable) return; + let sprucePlanks = bot.inventory.items().find(i => i.name === 'spruce_planks'); + if (!sprucePlanks || sprucePlanks.count < 4) { + let spruceLog = bot.inventory.items().find(i => i.name === 'spruce_log'); + if (!spruceLog) { await mineBlock('spruce_log', 1); } await craftItem('spruce_planks', 1); diff --git a/skills/craft_1_furnace.js b/skills/craft_1_furnace.js index 231f7e9..153645d 100644 --- a/skills/craft_1_furnace.js +++ b/skills/craft_1_furnace.js @@ -1,36 +1,43 @@ async function craftOneFurnace(bot) { - let cobblestone = bot.inventory.items().find(i => i.name === 'cobblestone'); - let cobbleCount = cobblestone ? cobblestone.count : 0; - if (cobbleCount < 8) { - await mineBlock('stone', 8 - cobbleCount); + const furnace = bot.inventory.items().find(i => i.name === 'furnace'); + if (furnace) return; + const cobblestone = bot.inventory.items().find(i => i.name === 'cobblestone'); + const count = cobblestone ? cobblestone.count : 0; + if (count < 8) { + await mineBlock('stone', 8 - count); } - let tableBlock = bot.findBlock({ + let craftingTable = bot.findBlock({ matching: b => b.name === 'crafting_table', maxDistance: 32 }); - if (!tableBlock) { - let tableItem = bot.inventory.items().find(i => i.name === 'crafting_table'); - if (!tableItem) { - let planks = bot.inventory.items().find(i => i.name.endsWith('_planks')); + if (!craftingTable) { + const tableItem = bot.inventory.items().find(i => i.name === 'crafting_table'); + if (tableItem) { + const pos = bot.entity.position.offset(1, 0, 0).floored(); + await placeItem('crafting_table', pos.x, pos.y, pos.z); + craftingTable = bot.findBlock({ + matching: b => b.name === 'crafting_table', + maxDistance: 32 + }); + } else { + const planks = bot.inventory.items().find(i => i.name.endsWith('_planks')); if (!planks || planks.count < 4) { - await mineBlock('oak_log', 1); - await craftItem('oak_planks', 1); + const logs = bot.inventory.items().find(i => i.name.endsWith('_log')); + if (logs) { + await craftItem(logs.name.replace('_log', '_planks'), 1); + } else { + await mineBlock('oak_log', 1); + await craftItem('oak_planks', 1); + } } await craftItem('crafting_table', 1); + const pos = bot.entity.position.offset(1, 0, 0).floored(); + await placeItem('crafting_table', pos.x, pos.y, pos.z); + craftingTable = bot.findBlock({ + matching: b => b.name === 'crafting_table', + maxDistance: 32 + }); } - const refBlock = bot.findBlock({ - matching: b => b.name !== 'air' && b.name !== 'water' && b.name !== 'lava' && b.boundingBox === 'block', - maxDistance: 4 - }); - const pos = refBlock ? refBlock.position.offset(0, 1, 0) : bot.entity.position.offset(1, 0, 0).floored(); - await placeItem('crafting_table', pos.x, pos.y, pos.z); - tableBlock = bot.findBlock({ - matching: b => b.name === 'crafting_table', - maxDistance: 32 - }); - } - if (tableBlock) { - await moveTo(tableBlock.position.x, tableBlock.position.y, tableBlock.position.z, 3); } await craftItem('furnace', 1); } \ No newline at end of file diff --git a/skills/craft_1_furnace_using_8.js b/skills/craft_1_furnace_using_8.js new file mode 100644 index 0000000..467f5cc --- /dev/null +++ b/skills/craft_1_furnace_using_8.js @@ -0,0 +1,22 @@ +async function craftFurnace(bot) { + const cobblestoneCount = bot.inventory.items().filter(i => i.name === 'cobblestone').reduce((acc, i) => acc + i.count, 0); + if (cobblestoneCount < 8) { + await mineBlock('stone', 8 - cobblestoneCount); + } + let craftingTable = bot.findBlock({ + matching: b => b.name === 'crafting_table', + maxDistance: 32 + }); + if (!craftingTable) { + const tableItem = bot.inventory.items().find(i => i.name === 'crafting_table'); + if (tableItem) { + const pos = bot.entity.position.offset(1, -1, 1).floored(); + await placeItem('crafting_table', pos.x, pos.y, pos.z); + craftingTable = bot.findBlock({ + matching: b => b.name === 'crafting_table', + maxDistance: 32 + }); + } + } + await craftItem('furnace', 1); +} \ No newline at end of file diff --git a/skills/craft_1_oak_boat.js b/skills/craft_1_oak_boat.js new file mode 100644 index 0000000..ee000d9 --- /dev/null +++ b/skills/craft_1_oak_boat.js @@ -0,0 +1,12 @@ +async function craftOneOakBoat(bot) { + const boatName = 'oak_boat'; + const existingBoat = bot.inventory.items().find(i => i.name === boatName); + if (existingBoat) { + return; + } + const planks = bot.inventory.items().find(i => i.name === 'oak_planks'); + if (!planks || planks.count < 5) { + await craftTwelveOakPlanks(bot); + } + await craftItem(boatName, 1); +} \ No newline at end of file diff --git a/skills/craft_1_stone_pickaxe_at.js b/skills/craft_1_stone_pickaxe_at.js new file mode 100644 index 0000000..6d801c7 --- /dev/null +++ b/skills/craft_1_stone_pickaxe_at.js @@ -0,0 +1,31 @@ +async function craftStonePickaxeAtLocation(bot) { + const targetX = 848; + const targetY = 68; + const targetZ = 199; + + // Ensure we have the required materials: 3 cobblestone and 2 sticks + let cobblestone = bot.inventory.items().find(i => i.name === 'cobblestone'); + let sticks = bot.inventory.items().find(i => i.name === 'stick'); + if (!cobblestone || cobblestone.count < 3) { + await mineBlock('stone', 3); + } + if (!sticks || sticks.count < 2) { + let planks = bot.inventory.items().find(i => i.name.endsWith('_planks')); + if (!planks) { + let logs = bot.inventory.items().find(i => i.name.endsWith('_log')); + if (!logs) { + await mineBlock('oak_log', 1); + logs = bot.inventory.items().find(i => i.name.endsWith('_log')); + } + await craftItem(logs.name.replace('_log', '_planks'), 1); + planks = bot.inventory.items().find(i => i.name.endsWith('_planks')); + } + await craftItem('stick', 1); + } + + // Move to the specified crafting table location + await moveTo(targetX, targetY, targetZ, 2); + + // Craft the stone pickaxe + await craftItem('stone_pickaxe', 1); +} \ No newline at end of file diff --git a/skills/craft_1_stone_pickaxe_using.js b/skills/craft_1_stone_pickaxe_using.js new file mode 100644 index 0000000..0d9cfb8 --- /dev/null +++ b/skills/craft_1_stone_pickaxe_using.js @@ -0,0 +1,46 @@ +async function craftStonePickaxe(bot) { + const existingPickaxe = bot.inventory.items().find(i => i.name === 'stone_pickaxe'); + if (existingPickaxe) return; + const cobblestone = bot.inventory.items().find(i => i.name === 'cobblestone'); + if (!cobblestone || cobblestone.count < 3) { + await mineBlock('stone', 3 - (cobblestone ? cobblestone.count : 0)); + } + const sticks = bot.inventory.items().find(i => i.name === 'stick'); + if (!sticks || sticks.count < 2) { + const planks = bot.inventory.items().find(i => i.name.endsWith('_planks')); + if (!planks) { + const logs = bot.inventory.items().find(i => i.name.endsWith('_log')); + if (!logs) { + await mineBlock('oak_log', 1); + } + const logToUse = bot.inventory.items().find(i => i.name.endsWith('_log')); + await craftItem(logToUse.name.replace('_log', '_planks'), 1); + } + await craftItem('stick', 1); + } + let craftingTable = bot.findBlock({ + matching: b => b.name === 'crafting_table', + maxDistance: 32 + }); + if (!craftingTable) { + const tableItem = bot.inventory.items().find(i => i.name === 'crafting_table'); + if (tableItem) { + const pos = bot.entity.position.offset(1, 0, 0); + await placeItem('crafting_table', pos.x, pos.y, pos.z); + craftingTable = bot.findBlock({ + matching: b => b.name === 'crafting_table', + maxDistance: 32 + }); + } else { + await craftItem('crafting_table', 1); + const pos = bot.entity.position.offset(1, 0, 0); + await placeItem('crafting_table', pos.x, pos.y, pos.z); + craftingTable = bot.findBlock({ + matching: b => b.name === 'crafting_table', + maxDistance: 32 + }); + } + } + await moveTo(craftingTable.position.x, craftingTable.position.y, craftingTable.position.z, 3, 10); + await craftItem('stone_pickaxe', 1); +} \ No newline at end of file diff --git a/skills/craft_20_oak_planks.js b/skills/craft_20_oak_planks.js new file mode 100644 index 0000000..60f73f6 --- /dev/null +++ b/skills/craft_20_oak_planks.js @@ -0,0 +1,14 @@ +async function craftTwentyOakPlanks(bot) { + const targetPlanks = 20; + const planks = bot.inventory.items().find(i => i.name === 'oak_planks'); + const currentPlanks = planks ? planks.count : 0; + if (currentPlanks >= targetPlanks) return; + const neededPlanks = targetPlanks - currentPlanks; + const recipesToCraft = Math.ceil(neededPlanks / 4); + const logs = bot.inventory.items().find(i => i.name === 'oak_log'); + const currentLogs = logs ? logs.count : 0; + if (currentLogs < recipesToCraft) { + await mineBlock('oak_log', recipesToCraft - currentLogs); + } + await craftItem('oak_planks', recipesToCraft); +} \ No newline at end of file diff --git a/skills/craft_24_oak_planks_from.js b/skills/craft_24_oak_planks_from.js new file mode 100644 index 0000000..b748e02 --- /dev/null +++ b/skills/craft_24_oak_planks_from.js @@ -0,0 +1,13 @@ +async function craftTwentyFourOakPlanksFromLogs(bot) { + const logName = 'oak_log'; + const plankName = 'oak_planks'; + const totalPlanksNeeded = 24; + const logsNeeded = 6; + const currentPlanks = bot.inventory.items().find(i => i.name === plankName)?.count || 0; + if (currentPlanks >= totalPlanksNeeded) return; + const currentLogs = bot.inventory.items().find(i => i.name === logName)?.count || 0; + if (currentLogs < logsNeeded) { + await mineBlock(logName, logsNeeded - currentLogs); + } + await craftItem(plankName, totalPlanksNeeded - currentPlanks); +} \ No newline at end of file diff --git a/skills/craft_4_oak_planks_from.js b/skills/craft_4_oak_planks_from.js new file mode 100644 index 0000000..bc3478e --- /dev/null +++ b/skills/craft_4_oak_planks_from.js @@ -0,0 +1,9 @@ +async function craftFourOakPlanksFromLog(bot) { + const logName = 'oak_log'; + const plankName = 'oak_planks'; + const logItem = bot.inventory.items().find(i => i.name === logName); + if (!logItem || logItem.count < 1) { + await mineBlock(logName, 1); + } + await craftItem(plankName, 4); +} \ No newline at end of file diff --git a/skills/craft_4_spruce_planks_from.js b/skills/craft_4_spruce_planks_from.js new file mode 100644 index 0000000..198f275 --- /dev/null +++ b/skills/craft_4_spruce_planks_from.js @@ -0,0 +1,17 @@ +async function craftFourSprucePlanksAtTable(bot) { + const tablePos = { + x: 750, + y: 70, + z: 227 + }; + const spruceLog = bot.inventory.items().find(i => i.name === 'spruce_log'); + if (!spruceLog || spruceLog.count < 1) { + await mineBlock('spruce_log', 1); + } + await moveTo(tablePos.x, tablePos.y, tablePos.z, 3, 60); + const table = bot.findBlock({ + matching: b => b.name === 'crafting_table', + maxDistance: 32 + }); + await craftItem('spruce_planks', 4); +} \ No newline at end of file diff --git a/skills/craft_4_sticks_using_2.js b/skills/craft_4_sticks_using_2.js new file mode 100644 index 0000000..4199327 --- /dev/null +++ b/skills/craft_4_sticks_using_2.js @@ -0,0 +1,35 @@ +async function craftSticksFromSprucePlanks(bot) { + const getStickCount = () => bot.inventory.items().filter(i => i.name === 'stick').reduce((acc, i) => acc + i.count, 0); + const initialSticks = getStickCount(); + let sprucePlanks = bot.inventory.items().find(i => i.name === 'spruce_planks'); + if (!sprucePlanks || sprucePlanks.count < 2) { + let spruceLog = bot.inventory.items().find(i => i.name === 'spruce_log'); + if (!spruceLog) { + const spruceLogBlock = bot.findBlock({ + matching: b => b.name === 'spruce_log', + maxDistance: 32 + }); + if (!spruceLogBlock) { + await exploreUntil('north', 60, () => bot.findBlock({ + matching: b => b.name === 'spruce_log', + maxDistance: 32 + })); + } + await mineBlock('spruce_log', 1); + spruceLog = bot.inventory.items().find(i => i.name === 'spruce_log'); + } + if (!spruceLog) { + throw new Error("Could not find or mine a spruce log."); + } + await craftItem('spruce_planks', 1); + sprucePlanks = bot.inventory.items().find(i => i.name === 'spruce_planks'); + } + if (!sprucePlanks || sprucePlanks.count < 2) { + throw new Error(`Insufficient spruce planks. Found: ${sprucePlanks ? sprucePlanks.count : 0}`); + } + await craftItem('stick', 1); + const finalSticks = getStickCount(); + if (finalSticks < initialSticks + 4) { + throw new Error(`Crafting failed: expected at least ${initialSticks + 4} sticks, but found ${finalSticks}.`); + } +} \ No newline at end of file diff --git a/skills/craft_4_sticks_using_spruce.js b/skills/craft_4_sticks_using_spruce.js new file mode 100644 index 0000000..73fdda7 --- /dev/null +++ b/skills/craft_4_sticks_using_spruce.js @@ -0,0 +1,12 @@ +async function craftFourSticksFromSpruce(bot) { + let sprucePlanks = bot.inventory.items().find(i => i.name === 'spruce_planks'); + let planksCount = sprucePlanks ? sprucePlanks.count : 0; + if (planksCount < 2) { + let spruceLog = bot.inventory.items().find(i => i.name === 'spruce_log'); + if (!spruceLog) { + await mineBlock('spruce_log', 1); + } + await craftItem('spruce_planks', 1); + } + await craftItem('stick', 1); +} \ No newline at end of file diff --git a/skills/craft_8_oak_planks.js b/skills/craft_8_oak_planks.js new file mode 100644 index 0000000..4135ee3 --- /dev/null +++ b/skills/craft_8_oak_planks.js @@ -0,0 +1,18 @@ +async function craftEightOakPlanks(bot) { + const plankName = 'oak_planks'; + const logName = 'oak_log'; + const targetTotal = 8; + const currentPlanks = bot.inventory.items().find(i => i.name === plankName); + const currentPlankCount = currentPlanks ? currentPlanks.count : 0; + if (currentPlankCount >= targetTotal) { + return; + } + const neededPlanks = targetTotal - currentPlankCount; + const neededLogs = Math.ceil(neededPlanks / 4); + const currentLogs = bot.inventory.items().find(i => i.name === logName); + const currentLogCount = currentLogs ? currentLogs.count : 0; + if (currentLogCount < neededLogs) { + await mineBlock(logName, neededLogs - currentLogCount); + } + await craftItem(plankName, targetTotal); +} \ No newline at end of file diff --git a/skills/craft_a_crafting_table_using.js b/skills/craft_a_crafting_table_using.js new file mode 100644 index 0000000..794a1a6 --- /dev/null +++ b/skills/craft_a_crafting_table_using.js @@ -0,0 +1,13 @@ +async function craftCraftingTableFromOak(bot) { + const craftingTable = bot.inventory.items().find(i => i.name === 'crafting_table'); + if (craftingTable) return; + let oakPlanks = bot.inventory.items().find(i => i.name === 'oak_planks'); + if (!oakPlanks || oakPlanks.count < 4) { + let oakLog = bot.inventory.items().find(i => i.name === 'oak_log'); + if (!oakLog) { + await mineBlock('oak_log', 1); + } + await craftItem('oak_planks', 1); + } + await craftItem('crafting_table', 1); +} \ No newline at end of file diff --git a/skills/craft_a_furnace_using_8.js b/skills/craft_a_furnace_using_8.js new file mode 100644 index 0000000..18d4cfd --- /dev/null +++ b/skills/craft_a_furnace_using_8.js @@ -0,0 +1,7 @@ +async function craftAFurnace(bot) { + const cobblestoneCount = bot.inventory.items().filter(i => i.name === 'cobblestone').reduce((acc, i) => acc + i.count, 0); + if (cobblestoneCount < 8) { + await mineBlock('stone', 8 - cobblestoneCount); + } + await craftItem('furnace', 1); +} \ No newline at end of file diff --git a/skills/craft_a_stone_axe.js b/skills/craft_a_stone_axe.js new file mode 100644 index 0000000..e8b5f94 --- /dev/null +++ b/skills/craft_a_stone_axe.js @@ -0,0 +1,68 @@ +async function craftStoneAxe(bot) { + const existingAxe = bot.inventory.items().find(i => i.name === 'stone_axe'); + if (existingAxe) return; + const cobble = bot.inventory.items().find(i => i.name === 'cobblestone'); + const sticks = bot.inventory.items().find(i => i.name === 'stick'); + if (!cobble || cobble.count < 3) { + await mineBlock('stone', 3 - (cobble ? cobble.count : 0)); + } + if (!sticks || sticks.count < 2) { + const planks = bot.inventory.items().find(i => i.name.endsWith('_planks')); + if (planks && planks.count >= 1) { + await craftItem('stick', 1); + } else { + await mineBlock('oak_log', 1); + await craftItem('oak_planks', 1); + await craftItem('stick', 1); + } + } + let table = bot.findBlock({ + matching: b => b.name === 'crafting_table', + maxDistance: 32 + }); + if (!table) { + let tableItem = bot.inventory.items().find(i => i.name === 'crafting_table'); + if (!tableItem) { + const planks = bot.inventory.items().find(i => i.name.endsWith('_planks')); + if (!planks || planks.count < 4) { + await mineBlock('oak_log', 1); + await craftItem('oak_planks', 1); + } + await craftItem('crafting_table', 1); + tableItem = bot.inventory.items().find(i => i.name === 'crafting_table'); + } + const searchRange = 4; + let placePos = null; + for (let x = -searchRange; x <= searchRange; x++) { + for (let y = -1; y <= 1; y++) { + for (let z = -searchRange; z <= searchRange; z++) { + const pos = bot.entity.position.offset(x, y, z).floored(); + const block = bot.blockAt(pos); + const below = bot.blockAt(pos.offset(0, -1, 0)); + if (block && block.name === 'air' && below && below.boundingBox === 'block' && below.name !== 'air') { + placePos = pos; + break; + } + } + if (placePos) break; + } + if (placePos) break; + } + if (!placePos) { + placePos = bot.entity.position.offset(1, 0, 0).floored(); + } + await placeItem('crafting_table', placePos.x, placePos.y, placePos.z); + table = bot.findBlock({ + matching: b => b.name === 'crafting_table', + maxDistance: 32 + }); + } + if (table) { + await moveTo(table.position.x, table.position.y, table.position.z, 3); + } + await craftItem('stone_axe', 1); + const finalAxe = bot.inventory.items().find(i => i.name === 'stone_axe'); + if (!finalAxe) { + throw new Error('Failed to craft stone_axe.'); + } +} \ No newline at end of file diff --git a/skills/craft_a_stone_pickaxe.js b/skills/craft_a_stone_pickaxe.js index b77487a..af92bf2 100644 --- a/skills/craft_a_stone_pickaxe.js +++ b/skills/craft_a_stone_pickaxe.js @@ -1,48 +1,69 @@ -async function craftStonePickaxeTask(bot) { - let tableBlock = bot.findBlock({ - matching: b => b.name === 'crafting_table', - maxDistance: 32 - }); - if (!tableBlock) { - let tableItem = bot.inventory.items().find(i => i.name === 'crafting_table'); - if (!tableItem) { - let planks = bot.inventory.items().find(i => i.name.endsWith('_planks')); - if (!planks || planks.count < 4) { - let logs = bot.inventory.items().find(i => i.name.endsWith('_log')); - if (logs) { - await craftItem(logs.name.replace('_log', '_planks'), 1); - } else { - await mineBlock('oak_log', 1); - await craftItem('oak_planks', 1); - } - } - await craftItem('crafting_table', 1); +async function craftStonePickaxe(bot) { + const existingPickaxe = bot.inventory.items().find(i => i.name === 'stone_pickaxe'); + if (existingPickaxe) return; + const woodenPickaxe = bot.inventory.items().find(i => i.name === 'wooden_pickaxe'); + if (!woodenPickaxe) { + const sticks = bot.inventory.items().find(i => i.name === 'stick'); + const planks = bot.inventory.items().find(i => i.name.endsWith('_planks')); + if (!planks || planks.count < 3 || !sticks || sticks.count < 2) { + await mineBlock('oak_log', 1); + const log = bot.inventory.items().find(i => i.name.endsWith('_log')); + await craftItem(log.name.replace('_log', '_planks'), 1); } - const referenceBlock = bot.findBlock({ - matching: b => b.name !== 'air' && b.name !== 'water' && b.name !== 'lava' && b.boundingBox === 'block', - maxDistance: 4 - }); - const pos = referenceBlock ? referenceBlock.position.offset(0, 1, 0) : bot.entity.position.offset(1, 0, 0).floored(); - await placeItem('crafting_table', pos.x, pos.y, pos.z); - tableBlock = bot.findBlock({ + const updatedPlanks = bot.inventory.items().find(i => i.name.endsWith('_planks')); + const updatedSticks = bot.inventory.items().find(i => i.name === 'stick'); + if (!updatedSticks || updatedSticks.count < 2) { + await craftItem('stick', 1); + } + let table = bot.findBlock({ matching: b => b.name === 'crafting_table', maxDistance: 32 }); - } - if (tableBlock) { - const dist = bot.entity.position.distanceTo(tableBlock.position); - if (dist > 3) { - await moveTo(tableBlock.position.x, tableBlock.position.y, tableBlock.position.z, 2, 10); + if (!table) { + const tableItem = bot.inventory.items().find(i => i.name === 'crafting_table'); + if (tableItem) { + await placeItem('crafting_table', bot.entity.position.offset(1, 0, 0)); + table = bot.findBlock({ + matching: b => b.name === 'crafting_table', + maxDistance: 32 + }); + } else { + await craftItem('crafting_table', 1); + await placeItem('crafting_table', bot.entity.position.offset(1, 0, 0)); + table = bot.findBlock({ + matching: b => b.name === 'crafting_table', + maxDistance: 32 + }); + } } + await craftItem('wooden_pickaxe', 1); + } + const cobblestone = bot.inventory.items().find(i => i.name === 'cobblestone'); + if (!cobblestone || cobblestone.count < 3) { + await mineBlock('stone', 3); } - let sticks = bot.inventory.items().find(i => i.name === 'stick'); - if (!sticks || sticks.count < 2) { + const finalSticks = bot.inventory.items().find(i => i.name === 'stick'); + if (!finalSticks || finalSticks.count < 2) { + const planksForSticks = bot.inventory.items().find(i => i.name.endsWith('_planks')); + if (!planksForSticks) { + await mineBlock('oak_log', 1); + const log = bot.inventory.items().find(i => i.name.endsWith('_log')); + await craftItem(log.name.replace('_log', '_planks'), 1); + } await craftItem('stick', 1); } - let cobblestone = bot.inventory.items().find(i => i.name === 'cobblestone'); - let cobbleCount = cobblestone ? cobblestone.count : 0; - if (cobbleCount < 3) { - await mineBlock('stone', 3 - cobbleCount); + let table = bot.findBlock({ + matching: b => b.name === 'crafting_table', + maxDistance: 32 + }); + if (!table) { + const tableItem = bot.inventory.items().find(i => i.name === 'crafting_table'); + if (tableItem) { + await placeItem('crafting_table', bot.entity.position.offset(1, 0, 0)); + } else { + await craftItem('crafting_table', 1); + await placeItem('crafting_table', bot.entity.position.offset(1, 0, 0)); + } } await craftItem('stone_pickaxe', 1); } \ No newline at end of file diff --git a/skills/craft_a_stone_sword.js b/skills/craft_a_stone_sword.js index 80c7a3f..8e1b2cd 100644 --- a/skills/craft_a_stone_sword.js +++ b/skills/craft_a_stone_sword.js @@ -1,57 +1,38 @@ -async function craftStoneSword(bot) { - // 1. Check if we already have a stone sword +async function craftStoneSwordTask(bot) { const existingSword = bot.inventory.items().find(i => i.name === 'stone_sword'); if (existingSword) return; - // 2. Ensure inventory space by tossing some seeds - const seeds = bot.inventory.items().find(i => i.name === 'wheat_seeds'); - if (seeds) { - await bot.toss(seeds.type, null, seeds.count); + // 1. Ensure materials: 2 cobblestone, 1 stick + const cobble = bot.inventory.items().find(i => i.name === 'cobblestone'); + if (!cobble || cobble.count < 2) { + await mineBlock('stone', 2); } - - // 3. Ensure we have the materials: 2 cobblestone and 1 stick - let cobblestone = bot.inventory.items().find(i => i.name === 'cobblestone'); - let cobbleCount = cobblestone ? cobblestone.count : 0; - if (cobbleCount < 2) { - await mineBlock('stone', 2 - cobbleCount); - } - let sticks = bot.inventory.items().find(i => i.name === 'stick'); + const sticks = bot.inventory.items().find(i => i.name === 'stick'); if (!sticks || sticks.count < 1) { const planks = bot.inventory.items().find(i => i.name.endsWith('_planks')); if (planks && planks.count >= 2) { await craftItem('stick', 1); } else { - const log = bot.inventory.items().find(i => i.name.endsWith('_log')); - if (log) { - await craftItem(log.name.replace('_log', '_planks'), 1); - await craftItem('stick', 1); - } else { - await mineBlock('oak_log', 1); - await craftItem('oak_planks', 1); - await craftItem('stick', 1); - } + await mineBlock('spruce_log', 1); + await craftItem('spruce_planks', 1); + await craftItem('stick', 1); } } - // 4. Handle Crafting Table - let tableBlock = bot.findBlock({ + // 2. Find or create a crafting table + let table = bot.findBlock({ matching: b => b.name === 'crafting_table', maxDistance: 32 }); - if (!tableBlock) { + if (!table) { let tableItem = bot.inventory.items().find(i => i.name === 'crafting_table'); if (!tableItem) { - // Craft a table + // Need 4 planks for a table let planks = bot.inventory.items().find(i => i.name.endsWith('_planks')); - if (!planks || planks.count < 4) { - const log = bot.inventory.items().find(i => i.name.endsWith('_log')); - if (log) { - await craftItem(log.name.replace('_log', '_planks'), 1); - } else { - await mineBlock('oak_log', 1); - await craftItem('oak_planks', 1); - } - planks = bot.inventory.items().find(i => i.name.endsWith('_planks')); + let totalPlanks = planks ? planks.count : 0; + if (totalPlanks < 4) { + await mineBlock('spruce_log', 1); + await craftItem('spruce_planks', 1); } await craftItem('crafting_table', 1); tableItem = bot.inventory.items().find(i => i.name === 'crafting_table'); @@ -59,23 +40,26 @@ async function craftStoneSword(bot) { // Place the crafting table const referenceBlock = bot.findBlock({ - matching: b => b.name !== 'air' && b.name !== 'water' && b.name !== 'lava' && b.boundingBox === 'block', + matching: b => b.name !== 'air' && b.name !== 'water' && b.boundingBox === 'block', maxDistance: 4 }); const pos = referenceBlock ? referenceBlock.position.offset(0, 1, 0) : bot.entity.position.offset(1, 0, 0).floored(); await placeItem('crafting_table', pos.x, pos.y, pos.z); - tableBlock = bot.findBlock({ + table = bot.findBlock({ matching: b => b.name === 'crafting_table', maxDistance: 32 }); } - // 5. Move to the crafting table and craft the sword - if (tableBlock) { - await moveTo(tableBlock.position.x, tableBlock.position.y, tableBlock.position.z, 3); - await craftItem('stone_sword', 1); - } else { - // Fallback if placement failed or table not found - await craftItem('stone_sword', 1); + // 3. Move to table and craft + if (table) { + await moveTo(table.position.x, table.position.y, table.position.z, 3); + } + await craftItem('stone_sword', 1); + + // 4. Verify + const finalSword = bot.inventory.items().find(i => i.name === 'stone_sword'); + if (!finalSword) { + throw new Error('Stone sword not found in inventory after crafting.'); } } \ No newline at end of file diff --git a/skills/craft_a_wooden_pickaxe.js b/skills/craft_a_wooden_pickaxe.js index 2674ec6..708ce94 100644 --- a/skills/craft_a_wooden_pickaxe.js +++ b/skills/craft_a_wooden_pickaxe.js @@ -1,51 +1,36 @@ async function craftWoodenPickaxe(bot) { const existingPickaxe = bot.inventory.items().find(i => i.name === 'wooden_pickaxe'); if (existingPickaxe) return; - - // 1. Ensure we have the necessary materials (3 planks, 2 sticks) - // Current inventory: spruce_planks x8, stick x8. We are good. - - // 2. Check for a crafting table nearby - let tableBlock = bot.findBlock({ - matching: b => b.name === 'crafting_table', + let planks = bot.inventory.items().find(i => i.name.endsWith('_planks')); + let plankCount = planks ? planks.count : 0; + if (plankCount < 3) { + let log = bot.inventory.items().find(i => i.name.endsWith('_log')); + if (!log) { + await mineBlock('oak_log', 1); + log = bot.inventory.items().find(i => i.name.endsWith('_log')); + } + await craftItem(log.name.replace('_log', '_planks'), 1); + planks = bot.inventory.items().find(i => i.name.endsWith('_planks')); + } + let sticks = bot.inventory.items().find(i => i.name === 'stick'); + if (!sticks || sticks.count < 2) { + await craftItem('stick', 1); + } + let craftingTable = bot.findBlock({ + matching: block => block.name === 'crafting_table', maxDistance: 32 }); - - // 3. If no table nearby, place the one from inventory - if (!tableBlock) { - const tableItem = bot.inventory.items().find(i => i.name === 'crafting_table'); - if (!tableItem) { - // If for some reason we lost it, craft a new one - await craftOneCraftingTable(bot); + if (!craftingTable) { + const tableInInv = bot.inventory.items().find(i => i.name === 'crafting_table'); + if (!tableInInv) { + await craftItem('crafting_table', 1); } - - // Find a solid block to place it on - const referenceBlock = bot.findBlock({ - matching: b => b.name !== 'air' && b.name !== 'water' && b.name !== 'lava' && b.boundingBox === 'block', - maxDistance: 4 - }); - if (referenceBlock) { - const placePos = referenceBlock.position.offset(0, 1, 0); - await placeItem('crafting_table', placePos.x, placePos.y, placePos.z); - } else { - // Fallback placement - const pos = bot.entity.position.offset(1, 0, 0).floored(); - await placeItem('crafting_table', pos.x, pos.y, pos.z); - } - - // Re-verify table location - tableBlock = bot.findBlock({ - matching: b => b.name === 'crafting_table', + const pos = bot.entity.position.offset(1, 0, 0); + await placeItem('crafting_table', pos.x, pos.y, pos.z); + craftingTable = bot.findBlock({ + matching: block => block.name === 'crafting_table', maxDistance: 32 }); } - - // 4. Move to the crafting table to ensure we are in range - if (tableBlock) { - await moveTo(tableBlock.position.x, tableBlock.position.y, tableBlock.position.z, 3, 10); - } - - // 5. Craft the wooden pickaxe - // Note: craftItem handles the table interaction if one is nearby await craftItem('wooden_pickaxe', 1); } \ No newline at end of file diff --git a/skills/explore_100_blocks_to_find.js b/skills/explore_100_blocks_to_find.js new file mode 100644 index 0000000..e70b430 --- /dev/null +++ b/skills/explore_100_blocks_to_find.js @@ -0,0 +1,16 @@ +async function exploreToFindVillage(bot) { + const startZ = bot.entity.position.z; + const targetZ = startZ + 100; + const villageWorkstations = ['bell', 'composter', 'lectern', 'fletching_table', 'grindstone', 'smithing_table', 'smoker', 'blast_furnace', 'cartography_table', 'brewing_stand', 'cauldron', 'loom', 'stonecutter']; + await exploreUntil({ + x: 0, + y: 0, + z: 1 + }, 300, () => { + const foundBlock = bot.findBlock({ + matching: b => villageWorkstations.includes(b.name), + maxDistance: 32 + }); + return !!foundBlock || bot.entity.position.z >= targetZ; + }); +} \ No newline at end of file diff --git a/skills/explore_100_blocks_to_the.js b/skills/explore_100_blocks_to_the.js new file mode 100644 index 0000000..bf73153 --- /dev/null +++ b/skills/explore_100_blocks_to_the.js @@ -0,0 +1,9 @@ +async function exploreOneHundredBlocksWest(bot) { + const startX = bot.entity.position.x; + const targetX = startX - 100; + await exploreUntil({ + x: -1, + y: 0, + z: 0 + }, 200, () => bot.entity.position.x <= targetX); +} \ No newline at end of file diff --git a/skills/explore_50_blocks_to_the.js b/skills/explore_50_blocks_to_the.js index d6cb8ce..78b7a14 100644 --- a/skills/explore_50_blocks_to_the.js +++ b/skills/explore_50_blocks_to_the.js @@ -1,7 +1,3 @@ -async function exploreWest50Blocks(bot) { - const currentPos = bot.entity.position; - const targetX = currentPos.x - 50; - const targetY = currentPos.y; - const targetZ = currentPos.z; - await moveTo(targetX, targetY, targetZ, 2, 60); +async function exploreFiftyBlocksWestTask(bot) { + await exploreFiftyBlocksWest(bot); } \ No newline at end of file diff --git a/skills/explore_and_find_a_cave.js b/skills/explore_and_find_a_cave.js new file mode 100644 index 0000000..41eaa17 --- /dev/null +++ b/skills/explore_and_find_a_cave.js @@ -0,0 +1,16 @@ +async function findCaveEntrance(bot) { + const north = { + x: 0, + y: 0, + z: -1 + }; + const caveEntrance = await exploreUntil(north, 120, () => { + return bot.findBlock({ + matching: block => block.name === 'cave_air', + maxDistance: 32 + }); + }); + if (caveEntrance) { + await moveTo(caveEntrance.position.x, caveEntrance.position.y, caveEntrance.position.z, 2); + } +} \ No newline at end of file diff --git a/skills/explore_the_nearby_structure_at.js b/skills/explore_the_nearby_structure_at.js new file mode 100644 index 0000000..d168332 --- /dev/null +++ b/skills/explore_the_nearby_structure_at.js @@ -0,0 +1,46 @@ +async function exploreStructureAt847(bot) { + const centerX = 847; + const centerY = 70; + const centerZ = 200; + + // Move to the initial structure center + await moveTo(centerX, centerY, centerZ, 2, 20); + + // Define exploration points around the structure + const offsets = [{ + x: 8, + z: 8 + }, { + x: -8, + z: -8 + }, { + x: 8, + z: -8 + }, { + x: -8, + z: 8 + }]; + for (const offset of offsets) { + const targetX = centerX + offset.x; + const targetZ = centerZ + offset.z; + + // Move to exploration point + await moveTo(targetX, centerY, targetZ, 3, 20); + + // Look for containers to inspect + const container = bot.findBlock({ + matching: block => ['chest', 'barrel', 'shulker_box', 'dispenser', 'dropper'].includes(block.name), + maxDistance: 16 + }); + if (container) { + await moveTo(container.position.x, container.position.y, container.position.z, 2, 15); + await inspectContainer(container.name); + } + } + + // Pick up items that might be lying around the structure + await pickUpTenNearbyItems(bot); + + // Return to center + await moveTo(centerX, centerY, centerZ, 2, 20); +} \ No newline at end of file diff --git a/skills/explore_the_ocean_to_find.js b/skills/explore_the_ocean_to_find.js new file mode 100644 index 0000000..7b452ad --- /dev/null +++ b/skills/explore_the_ocean_to_find.js @@ -0,0 +1,55 @@ +async function exploreOceanToFindIsland(bot) { + const startPos = bot.entity.position.clone(); + const searchDirection = { + x: 0, + y: 0, + z: -1 + }; + + // Step 1: Find water and move to it + let waterBlock = bot.findBlock({ + matching: b => b.name === 'water', + maxDistance: 32 + }); + if (!waterBlock) { + await exploreUntil(searchDirection, 30, () => { + return bot.findBlock({ + matching: b => b.name === 'water', + maxDistance: 32 + }); + }); + waterBlock = bot.findBlock({ + matching: b => b.name === 'water', + maxDistance: 32 + }); + } + if (waterBlock) { + await moveTo(waterBlock.position.x, waterBlock.position.y, waterBlock.position.z, 2); + } + + // Step 2: Explore across the water to find a new landmass (potential island) + // We look for land blocks (grass, sand, dirt, logs) that are a significant distance away from the start + await exploreUntil(searchDirection, 120, () => { + const landBlock = bot.findBlock({ + matching: b => ['grass_block', 'sand', 'dirt', 'oak_log', 'stone', 'gravel'].includes(b.name), + maxDistance: 32 + }); + if (landBlock) { + const distance = landBlock.position.distanceTo(startPos); + // If we found land more than 64 blocks away from start, it's likely a new island or shore + if (distance > 64) { + return landBlock; + } + } + return null; + }); + + // Step 3: Move onto the found land + const islandBlock = bot.findBlock({ + matching: b => ['grass_block', 'sand', 'dirt', 'oak_log', 'stone', 'gravel'].includes(b.name), + maxDistance: 32 + }); + if (islandBlock) { + await moveTo(islandBlock.position.x, islandBlock.position.y, islandBlock.position.z, 1); + } +} \ No newline at end of file diff --git a/skills/explore_the_wilderness_to_find.js b/skills/explore_the_wilderness_to_find.js new file mode 100644 index 0000000..d21923a --- /dev/null +++ b/skills/explore_the_wilderness_to_find.js @@ -0,0 +1,16 @@ +async function findDecorativeBlockForHall(bot) { + const decorativeBlocks = ['moss_block', 'azalea', 'flowering_azalea', 'calcite', 'amethyst_block', 'deepslate']; + const targetBlock = await exploreUntil({ + x: 1, + y: 0, + z: 1 + }, 60, () => { + return bot.findBlock({ + matching: b => decorativeBlocks.includes(b.name), + maxDistance: 32 + }); + }); + if (targetBlock) { + await mineBlock(targetBlock.name, 1); + } +} \ No newline at end of file diff --git a/skills/explore_to_find_an_island.js b/skills/explore_to_find_an_island.js new file mode 100644 index 0000000..d0cc874 --- /dev/null +++ b/skills/explore_to_find_an_island.js @@ -0,0 +1,51 @@ +async function exploreToFindIsland(bot) { + const north = { + x: 0, + y: 0, + z: -1 + }; + const startPosition = bot.entity.position.clone(); + let waterBlock = bot.findBlock({ + matching: b => b.name === 'water', + maxDistance: 32 + }); + if (!waterBlock) { + waterBlock = await exploreUntil(north, 60, () => { + return bot.findBlock({ + matching: b => b.name === 'water', + maxDistance: 32 + }); + }); + } + if (waterBlock) { + await moveTo(waterBlock.position.x, waterBlock.position.y, waterBlock.position.z, 2); + } + const boatItem = bot.inventory.items().find(i => i.name === 'oak_boat'); + if (boatItem && waterBlock) { + await placeItem('oak_boat', waterBlock.position.x, waterBlock.position.y + 1, waterBlock.position.z); + const boatEntity = bot.nearestEntity(e => e.name === 'boat' || e.name === 'oak_boat'); + if (boatEntity) { + await bot.mount(boatEntity); + } + } + await exploreUntil(north, 300, () => { + const landBlock = bot.findBlock({ + matching: b => ['grass_block', 'sand', 'dirt', 'stone', 'gravel', 'clay'].includes(b.name), + maxDistance: 32 + }); + if (landBlock && landBlock.position.distanceTo(startPosition) > 100) { + return landBlock; + } + return null; + }); + if (bot.vehicle) { + await bot.dismount(); + } + const destination = bot.findBlock({ + matching: b => ['grass_block', 'sand', 'dirt', 'stone', 'gravel', 'clay'].includes(b.name), + maxDistance: 32 + }); + if (destination) { + await moveTo(destination.position.x, destination.position.y, destination.position.z, 2); + } +} \ No newline at end of file diff --git a/skills/find_a_water_source.js b/skills/find_a_water_source.js index d77a92c..9078620 100644 --- a/skills/find_a_water_source.js +++ b/skills/find_a_water_source.js @@ -1,17 +1,21 @@ -async function findWaterSource(bot) { - try { - bot.chat("Looking for water..."); - const water = bot.findBlock({ - matching: (block) => block.name === "water", - maxDistance: 32, +async function findAWaterSource(bot) { + let water = bot.findBlock({ + matching: b => b.name === 'water', + maxDistance: 32 + }); + if (!water) { + water = await exploreUntil({ + x: 0, + y: 0, + z: -1 + }, 60, () => { + return bot.findBlock({ + matching: b => b.name === 'water', + maxDistance: 32 + }); }); - - if (water) { - bot.chat(`Found water at ${water.position.x}, ${water.position.y}, ${water.position.z}`); - } else { - bot.chat("Could not find water nearby."); - } - } catch (err) { - bot.chat(`Error finding water: ${err}`); + } + if (water) { + await moveTo(water.position.x, water.position.y, water.position.z, 3, 60); } } \ No newline at end of file diff --git a/skills/follow_lilly_to_the_ridge.js b/skills/follow_lilly_to_the_ridge.js new file mode 100644 index 0000000..86ac2d3 --- /dev/null +++ b/skills/follow_lilly_to_the_ridge.js @@ -0,0 +1,28 @@ +async function followLillyToTheRidgeTask(bot) { + const findLilly = () => { + return bot.players['Lilly']?.entity || Object.values(bot.players).find(p => p.username.toLowerCase().includes('lilly'))?.entity; + }; + + // Attempt to follow Lilly for a set number of iterations to reach the ridge + for (let i = 0; i < 15; i++) { + let lilly = findLilly(); + if (!lilly) { + // If Lilly is not visible, explore to find her + lilly = await exploreUntil('north', 30, () => findLilly()); + } + if (lilly) { + const distance = bot.entity.position.distanceTo(lilly.position); + if (distance > 3) { + // Move towards Lilly's current position + // Use a reasonable timeout and range to keep up + await moveTo(lilly.position.x, lilly.position.y, lilly.position.z, 3, 20); + } else { + // If close enough, wait briefly for her to move further + await bot.waitForTicks(40); + } + } else { + // If still not found after exploration, stop + break; + } + } +} \ No newline at end of file diff --git a/skills/get_to_work_mining_materials.js b/skills/get_to_work_mining_materials.js new file mode 100644 index 0000000..2c8a894 --- /dev/null +++ b/skills/get_to_work_mining_materials.js @@ -0,0 +1,17 @@ +async function getToWorkMiningMaterials(bot) { + const targets = ['coal_ore', 'iron_ore']; + for (const target of targets) { + const findTarget = () => bot.findBlock({ + matching: b => b.name === target || b.name === `deepslate_${target}`, + maxDistance: 32 + }); + let block = findTarget(); + if (!block) { + await exploreUntil('north', 60, () => findTarget()); + } + block = findTarget(); + if (block) { + await mineBlock(block.name, 5); + } + } +} \ No newline at end of file diff --git a/skills/index.json b/skills/index.json index e8c0c63..575a725 100644 --- a/skills/index.json +++ b/skills/index.json @@ -1,8 +1,8 @@ [ { - "quality": 0.7, + "quality": 0.62, "successCount": 0, - "failureCount": 0, + "failureCount": 1, "name": "wander_randomly", "description": "Walk to a random nearby position", "keywords": [ @@ -12,265 +12,7 @@ "random", "move" ], - "file": "wander_randomly.js", - "embedding": [ - -0.01663934, - 0.009648324, - 0.012873302, - -0.07294563, - -0.017943844, - 0.008446176, - -0.0002096937, - -0.0042481916, - 0.031035272, - 0.0038130975, - 0.011961365, - 0.0058269696, - -0.012915205, - 0.016923008, - 0.11835333, - -0.026721284, - -0.0042496924, - 0.001492959, - 0.0074447966, - -0.014802699, - -0.018702898, - -0.009162368, - -0.020455219, - -0.035441108, - -0.022942139, - 0.00039664822, - 0.029502565, - 0.01600189, - 0.053741757, - -0.013069742, - 0.011626789, - 0.038731143, - 0.010767352, - 0.02740071, - -0.00740069, - 0.017204342, - 0.031558245, - 0.004694606, - 0.009066056, - 0.0185303, - -0.01651315, - -0.008744178, - 0.000894814, - 0.0050406656, - 0.014414783, - 0.008384747, - -0.013076707, - -0.017745119, - -0.011793165, - 0.017240813, - 0.013104634, - 0.024546321, - 0.009921551, - -0.21696338, - 0.020346828, - 0.020221742, - 0.0128704, - 0.005410513, - 0.0013367536, - -0.024130806, - -0.027351217, - 0.013645526, - -0.024343459, - -0.0019008418, - 0.039912496, - 0.006592518, - 0.01829739, - 0.00364916, - -0.054699287, - 0.01052191, - 0.0013720593, - -0.006294439, - 0.0052545182, - -0.030569045, - -0.015133956, - 0.007159199, - 0.0034456635, - -0.0013252854, - 0.006515211, - 0.048642453, - -0.007460472, - -0.016922882, - -0.011281635, - -0.01307471, - 0.019124288, - 0.0014017997, - -0.01055138, - -0.0030407375, - -0.01978312, - -0.004049891, - -0.019487059, - 0.01904572, - 0.021836944, - 0.018728174, - 0.0123973675, - 0.012792698, - -0.0064693997, - 0.00013218084, - -0.0022647397, - 0.0038423762, - -0.0023687736, - -0.027866323, - -0.009742444, - -0.0152464425, - 0.0013953944, - -0.009724835, - -0.00712371, - 0.01288311, - -0.011585022, - -0.009101579, - 0.014178084, - 0.0032434445, - -0.0072167795, - 0.0014686714, - 0.0037943895, - -0.19420183, - 0.0048076618, - 0.004178703, - 0.002161126, - -0.0006544159, - -0.02829194, - 0.01042945, - 0.013866065, - 0.00588938, - 0.0036976554, - 0.0018252151, - 0.012973059, - -0.0068636863, - 0.0047519845, - 0.009435503, - 0.010210016, - 0.017045127, - 0.0065414617, - 0.016926575, - -0.011173541, - 0.04322485, - -0.020229347, - 0.0017900874, - 0.0033658394, - -0.017395962, - -0.00005323881, - 0.027361007, - -0.006952369, - 0.0063639265, - -0.02411484, - -0.007835753, - -0.0024421343, - 0.022358414, - -0.00984243, - -0.014940351, - -0.011791947, - -0.011229078, - -0.013959772, - -0.020396693, - 0.003724241, - -0.04200928, - 0.02284242, - 0.014937008, - 0.0011102867, - 0.013290657, - 0.019521715, - -0.0053209485, - 0.0015055981, - -0.0024913156, - 0.015058444, - -0.019207444, - -0.005761617, - -0.008087267, - 0.014321079, - 0.030110363, - -0.0063427477, - -0.011050785, - -0.014281683, - 0.0050043985, - -0.0048425407, - -0.0059154243, - -0.013003476, - 0.028844059, - -0.02284192, - -0.0141237425, - 0.032930624, - 0.0054709185, - -0.0025296137, - -0.0078081503, - 0.000060888207, - -0.01902519, - -0.023999395, - 0.0095568, - 0.0058107693, - -0.005331028, - -0.0009853607, - -0.0028535137, - 0.0021635331, - -0.0076487535, - -0.010804723, - -0.019402687, - 0.007799398, - -0.015359515, - 0.016108913, - 0.004412797, - 0.015263542, - -0.00477033, - 0.015503177, - -0.019623837, - 0.013323562, - -0.011844561, - -0.011331552, - -0.0068906895, - -0.0072942455, - 0.008232394, - 0.012093173, - -0.00031067597, - 0.012905755, - -0.0030241082, - 0.019145697, - 0.0032166094, - -0.0037886244, - -0.014395576, - 0.014284085, - -0.015197016, - 0.0011911641, - -0.015903672, - 0.0077499365, - 0.011451302, - 0.0027954928, - -0.0066907536, - 0.0011575245, - -0.0008896619, - -0.010098771, - 0.01405499, - 0.013648559, - 0.011526997, - -0.008525128, - -0.0008600424, - 0.0020799742, - 0.000009989131, - -0.0093038045, - 0.027823152, - 0.0132596, - -0.017457971, - -0.00875024, - -0.016850319, - -0.0006640755, - 0.015962197, - 0.014814791, - -0.019017428, - 0.015219619, - 0.030288793, - -0.0058293873, - -0.010323936, - -0.010493825, - -0.0048656818, - 0.00075092644, - -0.007910783, - -0.003183676, - 0.012978817 - ] + "file": "wander_randomly.js" }, { "quality": 0.7, @@ -284,265 +26,7 @@ "watch", "observe" ], - "file": "look_at_nearest_player.js", - "embedding": [ - -0.041727368, - 0.009232708, - 0.01127904, - -0.08035633, - -0.038932875, - 0.014634642, - -0.013846937, - -0.0020779541, - 0.014262144, - 0.00717109, - -0.0081794625, - 0.008953833, - -0.0044837454, - 0.010639649, - 0.11210994, - -0.038786765, - -0.021095041, - -0.003402687, - 0.0071669947, - 0.0043966705, - -0.021517022, - -0.023423137, - -0.002332159, - -0.03176298, - -0.02410219, - -0.011680667, - 0.013379272, - 0.019301241, - 0.032323763, - 0.028920768, - -0.00035301005, - 0.030373238, - 0.025755398, - 0.010627397, - -0.01860285, - 0.034512945, - 0.023033533, - 0.0006973002, - -0.0049191355, - 0.0147230495, - -0.0043273447, - -0.01040942, - -0.0042816442, - 0.0096933795, - 0.023606895, - 0.016341075, - 0.004119454, - -0.028712196, - -0.009518034, - 0.026192088, - -0.00796984, - 0.015933475, - -0.018009938, - -0.18229997, - 0.006373233, - 0.0015265688, - 0.0035599258, - -0.0054967115, - 0.013428297, - 0.00646266, - -0.005651107, - 0.029041871, - -0.009963795, - -0.013986445, - 0.028674334, - 0.011916664, - 0.0033511578, - 0.017487936, - -0.035844907, - 0.004941901, - -0.008824877, - -0.016127432, - -0.008929135, - -0.0070157163, - -0.005175312, - 0.012891408, - 0.020780649, - -0.005484866, - 0.018191632, - 0.017398825, - -0.0034714066, - 0.0064727827, - -0.009963088, - -0.024334675, - 0.02030184, - -0.010701408, - -0.0076289107, - 0.022707801, - -0.021246947, - -0.0024179749, - -0.012257563, - 0.005796985, - 0.012707887, - 0.0058511915, - 0.015113451, - 0.0045682224, - -0.015930021, - -0.02463821, - -0.0317119, - 0.0063676112, - -0.037817456, - -0.015357959, - 0.005324698, - 0.015079724, - -0.008245867, - 0.0015783544, - 0.012251174, - 0.018132573, - -0.029143285, - 0.008791333, - 0.016688174, - -0.0061464715, - 0.0063912384, - 0.005901382, - 0.026956357, - -0.18900281, - -0.004889515, - -0.010398723, - 0.021991989, - 0.020925088, - -0.024598032, - -0.014622644, - -0.008471745, - 0.011737997, - 0.013051748, - 0.00013209198, - 0.010643976, - -0.012747098, - 0.010589209, - -0.0069660167, - -0.0026888517, - 0.012518359, - -0.005037546, - 0.0003404794, - -0.022033423, - 0.03414998, - -0.030598681, - -0.015190999, - -0.012055958, - -0.00009336617, - -0.004241938, - 0.0045086127, - -0.012925029, - -0.0064840573, - -0.007107359, - -0.013992505, - -0.01868554, - 0.022791412, - 0.0014073398, - -0.014041787, - 0.011180872, - -0.010691561, - -0.0053308704, - -0.013817891, - 0.007351948, - -0.0354664, - -0.0001652629, - -0.0076424885, - 0.0027271046, - -0.0019460563, - 0.02488395, - 0.019665519, - 0.007993529, - 0.01725466, - 0.011466176, - 0.004990951, - -0.027332835, - -0.025103554, - -0.0054833167, - 0.017453926, - -0.027112935, - 0.0010653831, - 0.013964833, - 0.0012098036, - 0.017043754, - -0.027195591, - 0.0039114216, - 0.009319093, - -0.025125036, - -0.0048862314, - 0.04094275, - -0.012032788, - 0.0074495054, - -0.011519753, - 0.008858368, - -0.028317649, - -0.0120077925, - -0.013435239, - 0.0071231904, - -0.023574283, - -0.00838804, - -0.014885421, - 0.006130366, - -0.018920993, - 0.001975035, - -0.015947828, - -0.02801139, - -0.016761625, - 0.008041783, - -0.01614694, - 0.0020884855, - -0.020599566, - 0.017587626, - -0.017496997, - 0.01741114, - -0.014664663, - 0.017792128, - -0.0027950038, - 0.0020773672, - 0.03140426, - 0.0070709884, - -0.020564465, - 0.02265335, - -0.011558872, - 0.014770507, - -0.0016710873, - 0.00013595553, - -0.03771729, - 0.034565005, - -0.015958872, - 0.011799695, - 0.0064491555, - 0.026242996, - 0.007295878, - 0.009375145, - 0.001009076, - -0.012892772, - 0.0005805931, - 0.0018469028, - 0.0040668, - 0.00041952403, - -0.017901693, - 0.009418024, - -0.009597771, - -0.014216021, - -0.017892318, - -0.01628752, - 0.013825383, - 0.025407795, - 0.0043466776, - 0.0024959515, - 0.000112561065, - 0.016084043, - 0.021841014, - 0.012545844, - -0.018138044, - 0.013873261, - 0.009315477, - -0.018014539, - -0.022552365, - -0.004204757, - 0.00003027689, - -0.013843153, - 0.0024558923, - -0.015303592, - -0.004492676 - ] + "file": "look_at_nearest_player.js" }, { "quality": 0.7, @@ -558,265 +42,7 @@ "chat", "sell" ], - "file": "announce_wares.js", - "embedding": [ - -0.033506785, - -0.001780698, - 0.010709312, - -0.0954195, - -0.01567291, - 0.016962271, - 0.0012061184, - 0.010970193, - 0.00080242316, - 0.016660178, - -0.009998506, - -0.017951576, - -0.0011366645, - -0.017096307, - 0.10763981, - -0.04034093, - 0.016427178, - -0.0038874592, - 0.0053213527, - -0.0047252593, - -0.024721175, - -0.009224983, - -0.0014339915, - -0.025679983, - -0.0066799284, - -0.0009484504, - 0.02640632, - 0.0101992395, - 0.028181879, - -0.020384662, - 0.007644539, - 0.027799929, - -0.005282495, - 0.013896326, - 0.0011575501, - 0.016822172, - 0.01847188, - 0.002396544, - 0.009835485, - -0.0013292342, - -0.019032052, - 0.029341366, - 0.0020591517, - 0.0067499126, - 0.000053551954, - 0.025980966, - 0.005172584, - -0.024420638, - -0.004884309, - 0.027559554, - -0.00474129, - 0.0037101356, - -0.009595459, - -0.16252671, - -0.013991099, - 0.0071822004, - 0.0077541517, - -0.006834712, - 0.006672951, - -0.022844309, - -0.018596124, - 0.0014133664, - -0.008585723, - -0.034164473, - 0.013848455, - 0.0008599562, - -0.011813638, - 0.008793248, - -0.03773843, - -0.0022505585, - -0.007408894, - -0.019770004, - -0.0069144005, - -0.014283932, - -0.0034391296, - -0.025618633, - 0.006155877, - -0.004137304, - 0.02291952, - 0.03835149, - -0.0030636708, - -0.021059897, - -0.0061526364, - -0.025178205, - -0.0058010356, - 0.0055676647, - -0.010257343, - 0.0074450723, - -0.01855448, - 0.0038533781, - -0.009800477, - 0.008192776, - 0.018696697, - 0.03464157, - 0.021648305, - 0.009648432, - 0.0026753747, - -0.033220988, - -0.016781678, - -0.027787456, - -0.0013381551, - 0.0020945687, - -0.009384718, - 0.004162205, - -0.012636991, - -0.017060064, - -0.01199364, - 0.0145226745, - -0.020054584, - 0.008194358, - 0.016453031, - 0.0037420746, - 0.010678075, - 0.004946162, - 0.008548305, - -0.17965811, - 0.022900924, - 0.005703297, - -0.021097034, - 0.010113024, - -0.021082181, - -0.007589865, - 0.0003761951, - 0.00019661727, - -0.0030828754, - 0.035299882, - 0.011970411, - -0.011012595, - -0.016095208, - -0.00025559717, - -0.010449882, - -0.00014068553, - 0.019274436, - 0.032817878, - 0.0005544649, - 0.04528364, - -0.0330326, - 0.0046597566, - -0.009272553, - -0.008638816, - 0.010107132, - 0.0144999735, - -0.0031303063, - -0.003107348, - 0.00030765173, - -0.0055948454, - -0.0027484293, - -0.0035687077, - -0.0101698255, - -0.015356604, - 0.00902829, - -0.021837894, - 0.0042346576, - -0.013922535, - 0.0027760575, - -0.04969524, - 0.0044361823, - 0.009227068, - -0.0049941186, - 0.016360793, - 0.005601231, - 0.022365285, - -0.012339582, - 0.003257109, - -0.012252208, - -0.019824462, - 0.01996449, - -0.014689805, - -0.005924451, - 0.03343968, - -0.022451822, - -0.004605146, - 0.012509394, - 0.012262457, - -0.014468041, - 0.008398971, - 0.000034718832, - 0.008296119, - -0.00194074, - -0.00191537, - 0.018061258, - 0.005196123, - 0.011686455, - -0.0045708795, - 0.017595509, - -0.02113259, - -0.0028052754, - -0.019137755, - 0.0024091383, - -0.0014227427, - 0.007194799, - 0.0026052587, - -0.008823437, - -0.011041712, - -0.011068939, - -0.01801763, - -0.047727622, - -0.0007996638, - 0.019306026, - 0.0064442735, - 0.012368819, - -0.0076197837, - 0.012509088, - -0.012498055, - 0.015392102, - -0.013569805, - -0.0031833178, - -0.011965667, - 0.004273221, - 0.019285966, - 0.0019015705, - -0.023420373, - -0.0035109955, - 0.0061728726, - 0.0011779252, - 0.017984735, - 0.020859666, - -0.010189439, - -0.0012725455, - -0.0015295793, - 0.0070106443, - -0.009610514, - 0.025265945, - 0.004170225, - 0.010456285, - 0.0046849526, - -0.017334245, - -0.015752962, - -0.03075271, - 0.004371751, - 0.009079878, - 0.002710391, - -0.004553091, - -0.011571368, - -0.00902057, - -0.012892323, - -0.0035776407, - 0.00026926224, - -0.0015077397, - 0.006821895, - 0.006864748, - 0.009367074, - 0.004538436, - -0.008891095, - 0.0071926718, - -0.004783239, - 0.026356574, - 0.0090672895, - 0.0070528593, - -0.005844426, - -0.017249925, - 0.007136173, - -0.009393084, - -0.005845239, - -0.03921787, - -0.001751638 - ] + "file": "announce_wares.js" }, { "quality": 0.7, @@ -831,265 +57,7 @@ "square", "perimeter" ], - "file": "patrol_square.js", - "embedding": [ - -0.012653586, - 0.0052314824, - 0.021893926, - -0.058666352, - -0.0066607124, - 0.0118828425, - 0.008682642, - -0.006380373, - 0.027164329, - 0.009578885, - -0.0013346031, - -0.004492163, - -0.019972153, - 0.005908566, - 0.11781567, - -0.018900493, - 0.0057188882, - -0.00637952, - 0.013915184, - -0.0014867514, - -0.016075969, - -0.008127092, - -0.005510955, - -0.02572638, - -0.0021291021, - -0.007609371, - 0.022330092, - 0.028994659, - 0.053954143, - -0.0008358792, - 0.0034291828, - 0.0315983, - 0.0048091584, - 0.004157363, - -0.015316733, - 0.00346412, - 0.013252782, - -0.010309648, - -0.011702281, - 0.014941783, - -0.0318028, - -0.027948413, - 0.0003980014, - 0.019825548, - 0.014019002, - 0.012715022, - -0.00797193, - -0.012838506, - -0.0152316345, - 0.035250664, - 0.014598119, - 0.004500649, - -0.01880743, - -0.19562812, - 0.0034961784, - 0.032687057, - -0.004154882, - -0.0014882055, - 0.016915416, - -0.013254221, - -0.010880898, - 0.024466867, - 0.0028959631, - 0.004649765, - 0.019099573, - -0.00039401228, - 0.016262239, - 0.025094986, - -0.05079687, - 0.00909544, - 0.009139976, - 0.013098869, - -0.00072418776, - -0.009666865, - -0.011743342, - 0.004128166, - -0.006370908, - 0.0028334856, - 0.0026468677, - 0.02216233, - -0.004845989, - -0.019601839, - 0.004699864, - -0.015398257, - 0.0056455657, - -0.021027945, - -0.014628055, - 0.0035946306, - -0.010970396, - 0.006409355, - -0.014839323, - 0.011870661, - -0.0018944372, - -0.00028180174, - 0.01594441, - 0.011744118, - -0.023208015, - -0.015597832, - -0.005887422, - -0.0147732925, - -0.01302787, - -0.02162342, - -0.012992764, - 0.008791446, - 0.00971673, - -0.0024329782, - 0.0000017418064, - 0.00089013606, - -0.0035448724, - -0.008038157, - 0.025341798, - 0.0022984857, - 0.018363671, - 0.022983784, - -0.0028070894, - -0.17578535, - 0.012272856, - 0.020835612, - 0.033027127, - 0.015392158, - -0.036462586, - 0.004275589, - 0.012705093, - 0.027820786, - 0.011512931, - 0.0015871618, - -0.007670329, - -0.028477674, - 0.016997576, - 0.015779905, - -0.01110781, - -0.0047707553, - 0.017880328, - 0.015871221, - -0.008328397, - 0.04288724, - -0.017271569, - 0.011972265, - -0.014588273, - -0.0078104804, - 0.016129911, - 0.018488908, - 0.017164174, - 0.009124045, - -0.022171594, - -0.003271085, - -0.0046995124, - 0.027172245, - -0.004094924, - -0.018628707, - -0.003062439, - -0.021304745, - 0.014840595, - -0.02520658, - -0.0018177866, - -0.061433166, - 0.013970877, - -0.0044528255, - -0.010465573, - 0.02108551, - 0.028009726, - 0.00011038889, - 0.0016299875, - 0.0035737993, - 0.008295189, - 0.0012845566, - 0.0053375815, - -0.01153823, - 0.006211975, - 0.042871453, - -0.00846728, - 0.0014795745, - -0.019133836, - -0.021405468, - -0.007196826, - -0.017081752, - 0.00080959103, - 0.02454841, - -0.015903866, - -0.001969509, - 0.0199385, - 0.019584527, - 0.002636784, - -0.0030900547, - -0.0048959656, - -0.011741646, - -0.0365969, - 0.005520581, - 0.013900332, - -0.014393926, - 0.0044609383, - -0.00901877, - 0.0008913552, - -0.014100907, - -0.009218558, - -0.028092181, - 0.0072463714, - -0.01622739, - 0.019154103, - 0.000067583984, - 0.022956822, - -0.014725381, - -0.0001154965, - -0.034654647, - 0.009771681, - -0.02472124, - 0.001926878, - -0.026299661, - -0.0006903771, - 0.024673209, - -0.012087663, - -0.027023237, - 0.0068266867, - -0.004669536, - 0.019759402, - -0.027661806, - 0.008067469, - -0.00857119, - 0.00047023097, - -0.01728807, - 0.014747124, - -0.009240526, - 0.0074716657, - 0.008273287, - 0.0072553707, - -0.009317644, - 0.020105023, - 0.004863533, - -0.0057468745, - 0.0191173, - 0.008479582, - -0.01581216, - 0.0060140067, - -0.0062655304, - 0.008091903, - -0.0090898555, - 0.014874165, - 0.025005966, - 0.01463875, - -0.0035391627, - -0.0015036471, - -0.011170879, - -0.008331041, - 0.006379113, - 0.025533885, - 0.00834087, - -0.013358291, - 0.023538996, - -0.0014735276, - -0.012120296, - -0.01656088, - 0.008198876, - 0.008426099, - -0.020689618, - -0.020613972, - 0.021161605 - ] + "file": "patrol_square.js" }, { "quality": 0.7, @@ -1104,265 +72,7 @@ "look", "rest" ], - "file": "idle_and_look_around.js", - "embedding": [ - -0.02873111, - 0.026872737, - 0.0017763126, - -0.09395727, - -0.0066157505, - 0.013784679, - 0.0004239562, - -0.0033937686, - 0.014150393, - 0.007318015, - -0.00030751532, - -0.0086775655, - -0.008087413, - 0.013691069, - 0.112362735, - -0.023884874, - -0.00073832745, - 0.0012738659, - 0.0058745337, - -0.015097177, - -0.001028099, - -0.026523555, - 0.002779929, - -0.023814505, - -0.017304763, - 0.020351794, - 0.021388385, - 0.027462177, - 0.071714655, - 0.00802078, - 0.0050785965, - 0.026250329, - 0.0047797766, - 0.030337268, - -0.016454272, - 0.014086845, - 0.02203798, - -0.020336919, - -0.0021375397, - 0.013546084, - -0.0025409283, - 0.0005754589, - -0.008042263, - 0.013405931, - 0.017451625, - 0.0143381385, - 0.0015476064, - -0.0015232837, - -0.01381638, - 0.026820105, - 0.007982924, - 0.02152876, - -0.012064309, - -0.20913869, - 0.004093202, - 0.015513793, - 0.0026402804, - 0.009994776, - 0.015730398, - -0.025307767, - -0.006308515, - 0.009385042, - -0.008808931, - -0.009741337, - 0.0102106575, - -0.00010513765, - 0.023047434, - -0.0077801985, - -0.03320862, - -0.004100243, - -0.016889552, - 0.012794139, - 0.018900549, - -0.016375577, - -0.014061476, - -0.029031886, - 0.0048181643, - 0.009488637, - 0.0068295086, - 0.023661591, - -0.00030630137, - -0.021426259, - -0.005566308, - -0.011542584, - -0.0016809268, - -0.007816357, - -0.009095838, - 0.0072604506, - 0.0005351753, - 0.00007550192, - -0.016070006, - 0.009321376, - -0.0015099482, - 0.0062984764, - 0.0059209093, - -0.011947976, - -0.017345028, - -0.020667408, - -0.029832147, - 0.004426133, - -0.009247216, - -0.032471385, - 0.0018037632, - 0.022053277, - 0.014405017, - -0.009179187, - 0.0028585591, - 0.018771844, - -0.0032365862, - 0.002915573, - 0.019204417, - -0.010636928, - 0.005539255, - 0.009001473, - -0.01580643, - -0.18816862, - 0.01576361, - -0.022660311, - -0.0055522984, - 0.0033854642, - -0.027887497, - -0.006442081, - 0.0069267997, - 0.0121401725, - 0.0010158361, - -0.02330761, - 0.018501181, - 0.009095147, - -0.018683515, - 0.021745592, - -0.00763284, - 0.0066488246, - -0.003970717, - 0.025183572, - -0.0041602943, - 0.025221828, - -0.015373011, - -0.004605141, - 0.0036915743, - 0.016634822, - 0.0067470493, - 0.02901414, - 0.024016168, - 0.006194863, - -0.020334404, - -0.0021260958, - -0.01698249, - 0.009768685, - -0.016395006, - -0.013339708, - 0.0036541754, - -0.011461175, - -0.011640634, - -0.013707603, - 0.02301117, - -0.054137636, - 0.0065299547, - 0.0024624055, - -0.008900956, - 0.026273267, - 0.011360097, - -0.0028201437, - 0.0048089824, - 0.036348004, - 0.00921806, - -0.020942273, - 0.0032838935, - -0.026712492, - 0.0078103566, - 0.03901553, - -0.009353489, - 0.0012331893, - 0.014649567, - 0.0026253932, - -0.005846235, - 0.00031686708, - 0.00079269067, - -0.0041292096, - -0.010501861, - 0.002147254, - 0.031604946, - -0.018758854, - -0.0028757667, - -0.021312945, - -0.00054648926, - -0.0040544695, - -0.032359052, - 0.006534953, - -0.016578544, - 0.0042788456, - 0.0044643143, - -0.011840158, - 0.003009374, - -0.015514136, - -0.0043015243, - -0.0320326, - -0.010117547, - -0.028612763, - -0.009054126, - 0.018172722, - -0.008324241, - -0.0018066482, - 0.029886082, - -0.011282028, - 0.025200246, - -0.019374944, - -0.0077784504, - 0.0041985987, - -0.034219924, - 0.025769206, - -0.007038012, - -0.0047511132, - 0.008027546, - -0.0031448663, - 0.008720006, - 0.015739618, - -0.014020754, - -0.023493249, - 0.029030439, - -0.018869728, - -0.00833044, - -0.005915277, - 0.015101334, - 0.01925349, - -0.0046307524, - -0.010692688, - -0.0023690667, - -0.011956766, - -0.010334515, - 0.006223117, - 0.00021093166, - -0.022158405, - -0.0045448393, - 0.008797598, - -0.0095973, - -0.02632653, - -0.012037962, - 0.030346595, - 0.027994277, - 0.006297676, - 0.0021713558, - -0.023901504, - 0.0025486767, - 0.020027747, - 0.024990704, - 0.0070877536, - 0.014666512, - 0.023939345, - -0.009008616, - -0.012794415, - 0.0032956419, - 0.008393339, - -0.0013487702, - -0.008389083, - -0.01233447, - -0.0051182467 - ] + "file": "idle_and_look_around.js" }, { "quality": 0.7, @@ -1377,265 +87,7 @@ "move", "go" ], - "file": "walk_to_nearest_player.js", - "embedding": [ - -0.023683699, - -0.0044742664, - 0.021624846, - -0.062072232, - -0.033910174, - 0.011295209, - -0.0085363835, - -0.0022137943, - 0.025243597, - 0.009214778, - -0.004748719, - -0.013997835, - -0.01024749, - 0.01450289, - 0.11253391, - -0.04229546, - 0.0013032077, - 0.0072637126, - 0.012551055, - -0.0010516364, - -0.03157095, - 0.0001656612, - -0.013859629, - -0.021091105, - -0.025950449, - 0.011058424, - 0.016497266, - 0.029869711, - 0.048056174, - -0.0023770386, - 0.002394122, - 0.021800173, - 0.006721641, - 0.011487542, - -0.0036039713, - 0.024916427, - 0.036230695, - -0.00447138, - 0.004449743, - 0.010567431, - 0.008831315, - 0.002713915, - -0.004935552, - 0.0063445927, - 0.019503064, - 0.00678706, - 0.0022677602, - -0.025470361, - -0.00087179185, - 0.024978917, - 0.0056719906, - 0.0038378064, - -0.010283937, - -0.19774662, - 0.025226863, - 0.018280415, - 0.002993542, - -0.0005560936, - -0.0009811819, - -0.0033200316, - -0.016396832, - 0.0303904, - -0.021065557, - 0.0018946797, - 0.027407445, - 0.005510658, - 0.0212822, - 0.020926202, - -0.0368395, - 0.004260243, - -0.0025845112, - -0.011133812, - -0.01703187, - -0.016787997, - -0.010320629, - 0.0070641604, - -0.0030462765, - 0.011142837, - 0.012479899, - 0.029702703, - 0.0018241782, - -0.00044338225, - -0.017447775, - -0.03639424, - 0.022442408, - -0.018683331, - -0.000828274, - -0.006554405, - -0.020742446, - -0.015214775, - -0.008875182, - 0.017018897, - 0.02303092, - 0.01944341, - 0.0153958015, - 0.00019354482, - -0.0049399994, - -0.02621622, - -0.008731223, - 0.012743513, - -0.020171091, - -0.016815424, - -0.004470494, - 0.0019440312, - -0.009987179, - 0.0008038197, - 0.014457599, - 0.0036010605, - -0.025627244, - -0.0055528316, - 0.018848782, - 0.008357667, - -0.019062005, - 0.00826902, - 0.033890318, - -0.1863513, - 0.0034675307, - 0.013661296, - 0.0010132854, - -0.000024160112, - -0.031456254, - -0.0076061515, - -0.0058029275, - 0.018711135, - 0.018516667, - 0.00045100276, - 0.0030634427, - -0.021684585, - 0.0018344423, - -0.0051466404, - 0.011142442, - 0.0045419103, - -0.0034018424, - 0.0101969065, - -0.007191133, - 0.037922904, - -0.019294357, - -0.004306008, - -0.012517779, - -0.016674832, - -0.0075230906, - 0.022272453, - -0.020535864, - 0.0075010834, - -0.007867636, - -0.010982327, - -0.012059059, - 0.01743922, - 0.011084285, - -0.008161335, - -0.008975755, - -0.0006800749, - -0.005250215, - -0.03171645, - -0.0054418035, - -0.043488994, - 0.010928396, - -0.0044643586, - 0.00002920139, - 0.012755665, - 0.0120835975, - 0.008012765, - -0.0099959355, - -0.0009626426, - 0.015334299, - -0.0038215371, - -0.018458625, - -0.012904829, - 0.0035548701, - 0.020674802, - -0.012981268, - -0.014312833, - -0.009724171, - 0.0018572225, - 0.015266487, - -0.012293325, - -0.011244216, - 0.013758155, - -0.032636516, - -0.011227067, - 0.039979756, - 0.008432916, - 0.002867359, - -0.0034290743, - 0.005119405, - -0.031134771, - -0.022351133, - -0.012309017, - 0.009946697, - -0.028985111, - -0.0003353311, - 0.0064167604, - 0.009421066, - -0.008607589, - -0.014591818, - -0.023108082, - -0.00406091, - -0.0069228853, - 0.015397801, - -0.013362966, - 0.010181447, - -0.010454384, - 0.015845247, - -0.024699748, - 0.0003630362, - -0.021995666, - 0.021402048, - -0.016419137, - 0.009560926, - 0.019965986, - -0.001233661, - -0.03390883, - 0.018262403, - -0.0012242974, - 0.024312278, - -0.010468944, - 0.0068398365, - -0.028216397, - 0.013262288, - -0.018155769, - 0.00975496, - -0.013367362, - 0.023285775, - -0.00026417038, - 0.0053257807, - 0.012802397, - 0.0032082438, - 0.0070618386, - 0.0029233512, - 0.016360382, - 0.0020133248, - -0.015745705, - -0.00704407, - -0.002414052, - -0.0039361664, - -0.007224421, - -0.013999534, - 0.02158469, - 0.0108186845, - -0.00822071, - -0.0035675294, - -0.001116152, - 0.015756585, - 0.011166932, - 0.01162627, - -0.019548781, - 0.0016388809, - 0.010598375, - -0.01916534, - -0.020616375, - 0.0057768957, - -0.003870696, - -0.02636813, - 0.00043461588, - -0.0058679776, - -0.010691282 - ] + "file": "walk_to_nearest_player.js" }, { "quality": 0.7, @@ -1650,265 +102,7 @@ "player", "halt" ], - "file": "challenge_stranger.js", - "embedding": [ - -0.042490594, - 0.009554897, - 0.008845445, - -0.06706492, - 0.004489015, - 0.008041883, - 0.014447305, - 0.018208642, - 0.01648459, - -0.001177092, - 0.0038606978, - 0.0035952749, - -0.0068588117, - 0.013194235, - 0.11493996, - -0.022697862, - -0.010020002, - -0.004659068, - 0.010758836, - -0.0021641052, - -0.021464007, - -0.012770914, - -0.015855005, - -0.015560577, - 0.0075368523, - -0.0105215935, - 0.017784463, - 0.024566134, - 0.031726655, - -0.003070195, - 0.003667973, - 0.014526875, - -0.0075777923, - 0.004625478, - -0.0031961936, - 0.018166278, - 0.028032405, - -0.01553877, - -0.009529668, - 0.0050082635, - -0.018292276, - 0.00787056, - 0.00037184544, - 0.0015085723, - 0.012094469, - 0.019431563, - -0.009402776, - -0.015543194, - -0.0021602677, - 0.027964618, - 0.0040562125, - 0.028011112, - -0.02009253, - -0.1701924, - -0.0050602457, - 0.025300067, - 0.006513376, - 0.011064991, - 0.017012136, - -0.0036432722, - -0.010432922, - 0.011104043, - -0.011257959, - -0.02062369, - 0.011421297, - 0.013008305, - 0.0032891962, - 0.007828972, - -0.05423345, - -0.015049693, - 0.0040721344, - 0.008364223, - -0.022671046, - -0.04120717, - -0.0150577035, - -0.028000487, - 0.009204884, - -0.015233646, - 0.04140298, - 0.01976396, - -0.008301864, - -0.045211997, - 0.0104333395, - -0.010215533, - 0.0024479355, - -0.007641992, - 0.011819615, - 0.010752957, - -0.030930573, - 0.0017858123, - -0.021808641, - 0.01219219, - 0.026008388, - 0.020303227, - 0.0040744552, - 0.007927696, - -0.0008490718, - -0.016174205, - -0.018453583, - -0.010977524, - -0.019569706, - -0.018878775, - 0.0028069988, - 0.012767692, - 0.0060386676, - -0.013483896, - 0.010504471, - 0.020289185, - -0.016555084, - 0.024681129, - 0.009912047, - 0.010257998, - -0.010177313, - 0.0068374802, - 0.005222125, - -0.19275847, - 0.00042497827, - 0.017933259, - 0.0054169465, - 0.021233758, - -0.012621702, - -0.015669655, - 0.013559143, - 0.01648146, - 0.009241388, - 0.014732786, - 0.016116269, - 0.0078123524, - 0.020209614, - -0.009001065, - 0.025274687, - 0.0049955477, - 0.012026156, - -0.0045106197, - 0.0026916151, - 0.038890515, - -0.01154172, - -0.01602809, - -0.0057283654, - -0.022773314, - 0.010989077, - 0.008664309, - 0.005649363, - 0.00553826, - -0.0072096474, - 0.002609781, - 0.0030493846, - 0.025340684, - 0.000013642538, - -0.031499516, - 0.013521244, - -0.018688483, - 0.0005060127, - -0.022441918, - -0.0006167386, - -0.07191577, - 0.014895749, - -0.010005912, - 0.009068764, - 0.01543042, - 0.024852399, - -0.009610746, - -0.015765497, - -0.0015271032, - 0.009042179, - -0.0068655843, - -0.001472994, - -0.023119556, - -0.024142655, - 0.023156138, - -0.008732534, - -0.0045372644, - -0.0014549197, - 0.0039332127, - 0.0009439147, - 0.003747985, - -0.003193337, - 0.019806065, - -0.046518996, - -0.017163053, - 0.0061724605, - 0.007591957, - 0.0045229746, - 0.0019011386, - 0.0067802286, - -0.017163973, - -0.005193597, - -0.0023046762, - 0.005602846, - -0.008856734, - -0.015773427, - 0.011018248, - -0.02020056, - 0.02411529, - -0.003969421, - -0.033786558, - -0.022426892, - -0.021640923, - -0.011652938, - 0.013660313, - 0.0067232065, - -0.014937435, - -0.0077781263, - -0.0085357195, - 0.022156922, - -0.015791783, - 0.013131019, - -0.0059280423, - 0.0023389175, - 0.036269423, - 0.00049320393, - -0.009562182, - 0.002537446, - 0.011097038, - 0.022967, - 0.008668367, - -0.0085843885, - -0.029612768, - 0.0006462934, - 0.0051103975, - 0.02140709, - 0.004332702, - 0.015381858, - 0.0036695604, - -0.0033264176, - -0.0039092666, - 0.0038032671, - 0.00022424605, - -0.016066844, - 0.00799767, - 0.014727543, - -0.015181358, - -0.02852497, - -0.010528826, - 0.0022055288, - -0.010824562, - -0.008552261, - 0.033831317, - 0.01147098, - 0.016253699, - -0.013368748, - -0.00012528016, - 0.020048765, - -0.012960253, - 0.014611686, - -0.01232315, - 0.013949364, - 0.012411585, - -0.018287705, - -0.020701835, - -0.008419987, - 0.022836192, - 0.00536547, - -0.016900426, - -0.021670012, - -0.0075437855 - ] + "file": "challenge_stranger.js" }, { "quality": 0.7, @@ -1923,265 +117,7 @@ "say", "proverb" ], - "file": "share_wisdom.js", - "embedding": [ - -0.031753123, - 0.006001994, - 0.0052339924, - -0.056815132, - 0.0075066723, - 0.004287961, - 0.0010010543, - 0.01152971, - 0.006781428, - 0.011526384, - -0.003938345, - -0.009084376, - -0.003673675, - 0.021342808, - 0.12783873, - 0.004452111, - 0.002426054, - 0.00073035713, - 0.0067359847, - -0.010981496, - -0.007897517, - -0.0028905724, - -0.019628048, - -0.02128631, - -0.02187936, - -0.007854197, - 0.016210351, - 0.0065806103, - 0.016378002, - -0.0039162734, - -0.0070654773, - 0.014078339, - -0.010744933, - 0.00030297006, - -0.027307333, - 0.03781253, - 0.010602929, - 0.010442537, - 0.0001636404, - 0.015018616, - -0.03059099, - 0.020184204, - -0.017047305, - -0.018270288, - -0.008399134, - 0.023329511, - -0.001976478, - -0.021322588, - -0.0066543464, - 0.028395077, - 0.005723503, - 0.014766891, - -0.003905527, - -0.16883238, - -0.011693732, - 0.001421513, - -0.004534659, - -0.016615117, - -0.005975788, - -0.01386685, - -0.011674388, - 0.03489232, - -0.012034367, - -0.026284406, - 0.02762749, - 0.012522895, - 0.014642531, - 0.0011570118, - 0.006867636, - -0.013110908, - -0.017998643, - -0.0070453915, - -0.04165348, - -0.024827687, - 0.0006637591, - -0.0045783385, - 0.011180565, - -0.006950635, - 0.009824784, - 0.034120843, - 0.0015128604, - 0.008784812, - -0.007870661, - -0.020972718, - -0.021231636, - 0.027519891, - -0.009630837, - 0.009033583, - -0.02254773, - 0.0036729686, - -0.007027989, - -0.000025381418, - -0.005003344, - 0.03897871, - 0.007541624, - 0.020437451, - -0.016483031, - -0.035465002, - -0.024067739, - -0.018175377, - -0.024028277, - 0.00094435766, - -0.009708696, - -0.0063771196, - 0.009714069, - -0.025807248, - -0.010149869, - 0.013906925, - -0.008038995, - 0.024197506, - -0.0032288025, - -0.0097690355, - 0.0010777236, - 0.025473721, - -0.000003100979, - -0.19469939, - 0.018179996, - 0.0046636676, - -0.000049752674, - 0.0368506, - 0.027485203, - -0.0064466414, - 0.016931081, - 0.0034235527, - 0.007030641, - 0.014729592, - -0.00021281278, - -0.0025383497, - 0.00036621152, - -0.000004938466, - -0.0049944203, - -0.0028772058, - 0.0012512364, - 0.014339722, - 0.003212759, - 0.012126617, - -0.0029282414, - -0.02105817, - -0.013018073, - -0.005351343, - -0.00054552633, - -0.00030348747, - 0.0019442734, - 0.010641865, - -0.004981591, - -0.016943716, - -0.0023276915, - 0.021565786, - -0.0016675822, - -0.016506225, - -0.017179705, - -0.018303152, - 0.0070001595, - 0.01566142, - 0.008523071, - -0.06348876, - -0.012773417, - -0.003976955, - 0.029235648, - 0.0025412056, - 0.0048118494, - 0.0050045955, - 0.020557262, - 0.022275357, - -0.018743576, - -0.02249564, - 0.003990047, - -0.014448728, - -0.004578975, - 0.0068653286, - -0.00007837436, - -0.015749644, - -0.0033156679, - -0.014476332, - 0.011349415, - 0.02775943, - 0.009722738, - -0.0051583173, - -0.0023625807, - -0.021468533, - 0.0028657464, - -0.01570754, - 0.023054142, - -0.031351432, - -0.019476574, - 0.0077620912, - -0.01955939, - 0.0035526552, - 0.037994787, - 0.002473819, - -0.004642824, - -0.005089539, - -0.011906083, - 0.007432675, - -0.0037858088, - -0.012939707, - -0.00506267, - 0.0041202484, - 0.023207704, - -0.0013376316, - 0.023371544, - 0.02009025, - 0.028894112, - -0.0038418495, - -0.0035588741, - -0.01326774, - 0.0041712145, - -0.007389648, - 0.010670536, - 0.022146832, - 0.015240772, - -0.024067847, - -0.0032974517, - 0.0012319166, - 0.013942906, - 0.0016000621, - -0.003298758, - -0.019074403, - 0.021160379, - 0.011689531, - 0.004194958, - 0.005769211, - 0.027271898, - -0.015084194, - -0.0075192703, - -0.0030685868, - -0.016875412, - -0.013444447, - -0.013209357, - 0.020216772, - 0.016380189, - 0.02145561, - -0.013088837, - 0.0003680885, - -0.03105022, - 0.017151011, - -0.019992664, - 0.0036268241, - 0.01593378, - 0.017810728, - -0.0033167163, - 0.0015242764, - -0.001818342, - 0.013952084, - 0.00992629, - -0.002273214, - 0.008090744, - 0.006957717, - -0.005668379, - -0.015109129, - 0.017922748, - 0.0049359337, - 0.012652406, - 0.0013914931, - -0.0130351335, - 0.001764947 - ] + "file": "share_wisdom.js" }, { "quality": 0.7, @@ -2196,265 +132,7 @@ "oak", "log" ], - "file": "mine_the_nearest_oak_log.js", - "embedding": [ - -0.006895635, - 0.0059833764, - -0.004850682, - -0.08211356, - 0.0033433423, - -0.007175678, - 0.0016111863, - -0.0023895022, - 0.020625219, - 0.0060772006, - 0.0048658825, - -0.010213061, - -0.007924871, - 0.023589244, - 0.13924108, - -0.030792937, - -0.012369248, - -0.001955379, - -0.013916603, - -0.018862247, - -0.021915443, - -0.010277744, - 0.010277034, - -0.0017436543, - -0.017205156, - 0.011951977, - 0.014316575, - 0.035826247, - 0.039440036, - -0.018807348, - -0.013334751, - 0.026244069, - 0.019901609, - 0.004560582, - -0.008392523, - 0.031087715, - 0.0011780935, - -0.0064812223, - -0.0010323322, - 0.010001825, - -0.0036285052, - 0.009681051, - -0.010124615, - -0.024854623, - 0.014165872, - 0.026548794, - 0.0028648316, - -0.011693738, - 0.0030394439, - 0.005205128, - 0.009339678, - 0.011553144, - -0.0154124275, - -0.17907837, - 0.008722148, - 0.0059256996, - -0.0011805875, - 0.006989345, - 0.029072573, - -0.005628592, - -0.015434556, - 0.029440735, - -0.0142419515, - -0.0032834657, - 0.0106349625, - 0.0035489106, - 0.014507978, - 0.006118679, - -0.024043648, - -0.016053667, - -0.022505017, - 0.0008963629, - -0.017741889, - -0.02438402, - -0.023497757, - -0.0063318433, - 0.00209746, - 0.013175832, - -0.0030771662, - 0.045753818, - -0.005136082, - -0.025322732, - -0.013911893, - 0.002740926, - 0.0019231702, - -0.008181527, - -0.013450384, - -0.009758893, - -0.029226456, - -0.017036486, - -0.009332608, - 0.026091771, - -0.0069117765, - 0.018962735, - 0.011129433, - 0.013310713, - -0.014406659, - -0.0112345675, - 0.006101808, - -0.0072730314, - -0.016791334, - -0.009929904, - -0.0029359995, - -0.01746064, - 0.0035482207, - -0.019973997, - 0.020483004, - 0.0065497095, - -0.0046600294, - 0.019047955, - -0.0024235703, - -0.0023571707, - 0.0034113494, - 0.029964665, - 0.02294823, - -0.19556741, - 0.01132527, - -0.00493229, - 0.008707278, - 0.0071954057, - 0.003562729, - -0.005431691, - 0.003951325, - 0.02029301, - -0.004600773, - 0.0075868764, - -0.0069325776, - -0.011564084, - 0.005839294, - -0.011827868, - -0.003974721, - 0.0012184915, - 0.0002458775, - 0.0100005455, - -0.0123340525, - 0.025187623, - -0.0005066263, - -0.0035230648, - -0.00842951, - -0.009811123, - -0.00009441865, - 0.0027405566, - -0.0071276487, - 0.0029083707, - -0.009475282, - -0.015196359, - 0.014976342, - 0.00065307826, - 0.0011029529, - -0.024078896, - 0.0022220716, - -0.03253311, - -0.0024631592, - -0.018910762, - -0.023694327, - -0.058057994, - -0.0034582536, - -0.008201074, - -0.00997935, - -0.005451697, - 0.011108195, - 0.00076384994, - 0.010929367, - 0.0002704202, - 0.0071324175, - -0.008800098, - 0.0025506055, - 0.008808839, - 0.007118108, - 0.0069277636, - -0.0032580183, - -0.030987712, - 0.012540508, - -0.011321242, - 0.018446628, - -0.0126316445, - 0.0046724617, - 0.009591804, - 0.018850686, - 0.013986011, - 0.016570037, - 0.0050447755, - 0.00061986444, - -0.0056892536, - 0.011548821, - -0.018756216, - -0.008636825, - -0.018005526, - 0.006189991, - -0.013053064, - 0.0033499203, - -0.0016362385, - -0.017646044, - -0.016539807, - -0.012245148, - -0.007681119, - -0.023112036, - -0.005031308, - 0.026698863, - -0.0064924983, - -0.0032373194, - -0.0017008379, - 0.014985374, - 0.012352226, - 0.014434069, - 0.008842637, - 0.0022757011, - -0.0019821036, - 0.011861649, - 0.046839524, - 0.030070728, - -0.03546606, - -0.002401088, - -0.006898513, - 0.015455051, - -0.02378753, - -0.00004767478, - 0.001760513, - 0.04092671, - 0.017881073, - 0.01452271, - -0.020549882, - 0.048001774, - -0.0018136238, - -0.0010055919, - -0.001712754, - -0.01007883, - 0.0019722013, - 0.0015983712, - 0.029111845, - -0.0010811028, - 0.0052994643, - 0.011735751, - -0.018048782, - -0.026661757, - -0.010216147, - -0.01959675, - 0.039131124, - 0.0010969755, - -0.01336989, - -0.002402066, - -0.022467073, - 0.0014149321, - -0.016530402, - 0.028948631, - -0.02136518, - -0.008410745, - 0.006063698, - -0.015960544, - -0.02720666, - 0.00026335602, - 0.0032245752, - -0.017154492, - -0.008600873, - 0.012270588, - -0.013139007 - ] + "file": "mine_the_nearest_oak_log.js" }, { "quality": 0.7, @@ -2470,265 +148,7 @@ "player", "looking" ], - "file": "mine_the_block_the_player.js", - "embedding": [ - -0.026517915, - 0.004511088, - 0.003270792, - -0.0829724, - -0.010001128, - 0.0095358705, - 0.0016134278, - -0.009443757, - 0.008965489, - -0.0041015865, - -0.0027035112, - -0.0019438233, - 0.009311624, - 0.009459885, - 0.11974791, - -0.035382442, - -0.031157603, - -0.00071913004, - -0.018414292, - 0.0054535726, - -0.017088842, - -0.012982316, - -0.008515176, - -0.01352609, - -0.008344501, - 0.003517164, - 0.019677993, - 0.014861481, - 0.030620907, - 0.0036077101, - -0.019514775, - 0.012312105, - 0.044029392, - 0.009195048, - -0.008411046, - 0.023428693, - 0.0013966267, - 0.016960846, - 0.007533226, - 0.013853097, - -0.0197895, - 0.014162329, - 0.005125008, - 0.010038964, - 0.013054307, - 0.018903349, - -0.0030026513, - -0.01334722, - 0.000043880158, - 0.004558283, - 0.0023476481, - 0.015659854, - -0.01005942, - -0.17455843, - 0.021719366, - 0.009832651, - 0.008013659, - 0.004964029, - 0.018833091, - -0.007120607, - -0.009671164, - 0.034810722, - 0.0074505555, - -0.0064727073, - 0.025567967, - 0.011579177, - 0.005344718, - 0.008040847, - -0.014311676, - -0.017645147, - -0.028128184, - -0.014287578, - -0.00583952, - -0.017820695, - -0.022614751, - -0.009204213, - 0.017592136, - 0.0019297823, - -0.009337678, - 0.038821522, - -0.019441498, - -0.008218894, - -0.008662609, - -0.008702524, - 0.017425148, - 0.012500466, - -0.027132995, - -0.0007686062, - -0.025932059, - 0.0067759547, - -0.025912585, - 0.014604596, - 0.0039561098, - 0.0039168717, - 0.0075813746, - 0.022271581, - -0.014218592, - 0.0007647724, - -0.020451488, - -0.023925288, - -0.02674668, - -0.004664045, - -0.006608971, - -0.000059039096, - 0.01580338, - -0.008665288, - 0.02006265, - 0.009207756, - -0.009165497, - 0.016419303, - 0.012913387, - 0.0052127563, - 0.0053862315, - 0.020812823, - 0.024579795, - -0.20277227, - 0.011903183, - -0.0024524143, - 0.02430697, - 0.011057014, - 0.0029940614, - -0.025157867, - -0.0041251853, - 0.029514274, - 0.022377867, - 0.02141844, - -0.008089405, - -0.0073689935, - -0.009141863, - -0.0055562523, - -0.01020504, - 0.01892301, - -0.013582368, - 0.019132059, - 0.004110398, - 0.02186299, - -0.02448635, - -0.02527812, - -0.005724332, - -0.008533607, - 0.0020770074, - -0.0061173863, - -0.007372852, - -0.0034752951, - 0.0011530735, - 0.007460396, - 0.013249654, - 0.0026921527, - -0.018932682, - -0.01507328, - -0.013918613, - -0.030338524, - 0.001355162, - -0.012619673, - -0.00663746, - -0.052952938, - -0.011782047, - 0.001973382, - -0.008895725, - 0.002418307, - 0.030529587, - 0.008510765, - 0.015637957, - 0.015570422, - -0.00016952647, - 0.0038728607, - -0.010434048, - -0.0037723442, - 0.0014338364, - 0.021129474, - -0.0035711296, - -0.024290135, - 0.00068542355, - -0.010506068, - 0.033650618, - -0.01224884, - -0.0023329812, - 0.00783204, - 0.0063365865, - 0.012098466, - 0.020266525, - -0.0036866476, - -0.0012316466, - -0.016743692, - 0.006554704, - -0.035723846, - -0.0093442835, - -0.020035211, - 0.016267153, - -0.011760296, - -0.0154456645, - 0.008487559, - -0.0170324, - -0.03636922, - -0.0062018046, - -0.01732221, - -0.044272125, - -0.018206581, - 0.01980228, - -0.008089352, - -0.0138175795, - -0.010633872, - -0.002809943, - -0.0051605497, - -0.0023485117, - 0.021222267, - 0.0012081504, - 0.008703687, - 0.017017677, - 0.026035815, - 0.0052782274, - -0.035919156, - 0.0013686463, - -0.009986679, - 0.019472301, - -0.020701425, - -0.013412182, - -0.00606905, - 0.028317178, - 0.0018732023, - 0.01465093, - -0.012599923, - 0.036607277, - 0.0043250355, - 0.007322621, - 0.010829757, - -0.008241478, - -0.010991392, - -0.0070039583, - -0.0060771555, - -0.019707207, - -0.010152007, - -0.013483023, - -0.022880638, - -0.04120323, - 0.0045648008, - -0.0029146273, - 0.027333273, - 0.017582938, - -0.0016433472, - 0.0071629914, - -0.009481172, - 0.011607397, - -0.0028471805, - 0.010015128, - -0.015725011, - 0.00817734, - 0.028237589, - -0.017807964, - -0.03409542, - 0.008282806, - -0.014124003, - -0.01635591, - -0.012986586, - 0.013852867, - 0.0010321146 - ] + "file": "mine_the_block_the_player.js" }, { "quality": 0.7, @@ -2745,1349 +165,55 @@ "nearest", "forest" ], - "file": "point_the_player_towards_the.js", - "embedding": [ - -0.0139555605, - -0.017432552, - 0.0063469387, - -0.08370804, - -0.02560542, - -0.0055994606, - -0.008595713, - 0.009913783, - 0.030905291, - 0.016575314, - 0.008097733, - 0.010173796, - -0.013653839, - 0.03417465, - 0.11617988, - -0.030963475, - 0.0050192275, - -0.018581405, - -0.007078198, - -0.008255603, - -0.019523412, - -0.013237311, - -0.004868351, - -0.035530314, - -0.0005384499, - -0.00884197, - 0.0044549555, - 0.018587971, - 0.027262993, - -0.01618172, - -0.006552205, - 0.02731225, - 0.021889877, - 0.0334984, - -0.01906609, - 0.03465483, - 0.017643629, - 0.0062609357, - 0.00072915625, - -0.005101972, - -0.0004488478, - 0.013925365, - 0.0048071737, - 0.016543463, - -0.014122138, - 0.025529865, - 0.019297795, - -0.032373358, - -0.0005844327, - 0.02332188, - -0.00050174876, - 0.003198556, - -0.0108661065, - -0.19011573, - 0.009003715, - 0.004259467, - 0.012840753, - 0.0052913222, - -0.00536071, - -0.0146329785, - -0.010420108, - 0.028131263, - -0.00447698, - -0.012343297, - 0.018989215, - 0.005213307, - 0.0013995578, - -0.0075826044, - -0.017290864, - -0.0049596643, - -0.017820885, - -0.009461135, - -0.011528399, - -0.031750355, - -0.0047049853, - -0.003995475, - 0.023334393, - -0.0076354123, - 0.03427316, - 0.038811106, - -0.004302157, - -0.0010221201, - -0.036962144, - -0.020111784, - -0.0005236059, - -0.01607101, - -0.018698366, - 0.004270224, - -0.00584606, - -0.014027977, - -0.022404222, - 0.026825992, - 0.009114281, - 0.014742562, - 0.0029596156, - 0.024645604, - -0.010329698, - -0.0037351353, - -0.0051813326, - 0.006096876, - -0.010395215, - 0.0059423666, - -0.00930086, - -0.009932003, - -0.0018860901, - 0.014917695, - 0.011703961, - -0.009874486, - -0.0072375108, - -0.007918161, - 0.008591143, - 0.015194199, - 0.001039294, - 0.0017331209, - 0.017298087, - -0.184108, - 0.0030573867, - 0.0007275035, - 0.017596705, - 0.0038752682, - -0.020072613, - -0.02458581, - 0.016221892, - 0.029971154, - 0.006528472, - 0.005749334, - 0.009920764, - -0.018243551, - 0.010950768, - -0.012784271, - 0.021595972, - 0.0045753713, - 0.0033136152, - 0.013604827, - 0.000107325606, - 0.022001155, - -0.00852174, - -0.015877578, - -0.003058364, - -0.014818044, - -0.023891004, - 0.010264017, - -0.0025685935, - -0.006414293, - 0.014120558, - -0.025578665, - 0.002657391, - 0.020696228, - 0.022023866, - -0.01559348, - -0.017639745, - -0.026846513, - -0.0007076144, - -0.016262468, - -0.0016942418, - -0.036401678, - 0.00644781, - -0.00369514, - 0.0014045782, - 0.020151144, - 0.028786665, - -0.005094142, - 0.020706285, - -0.0017002235, - 0.0137358215, - 0.01275568, - 0.0042026816, - -0.009687777, - 0.012666906, - 0.021971896, - -0.010316534, - -0.0121168345, - 0.011427925, - 0.013231869, - 0.013105289, - -0.001377663, - 0.0026684946, - 0.020350458, - -0.013075907, - -0.005283644, - 0.019346457, - -0.01039684, - 0.0003209354, - 0.013096188, - 0.012614536, - -0.024522046, - -0.00879392, - -0.024797136, - 0.009143481, - -0.0008063336, - -0.016834801, - 0.009511327, - 0.005816716, - -0.00955014, - -0.0027665028, - -0.015230542, - -0.004661894, - -0.0021175153, - 0.02189222, - -0.026787112, - 0.00816417, - 0.003804951, - -0.0010243683, - -0.028208837, - 0.007862802, - -0.014889423, - 0.004984083, - -0.016432604, - 0.01688454, - 0.0167545, - 0.019583978, - -0.03945905, - -0.009791415, - -0.0005710156, - 0.020344812, - -0.013932503, - 0.013028202, - -0.025373928, - 0.012775268, - -0.014413626, - 0.014874451, - -0.010792668, - 0.020159967, - 0.02764715, - 0.0016003092, - 0.017390758, - -0.00053839776, - 0.010271859, - -0.0119268885, - 0.0012894124, - -0.003010623, - -0.011281849, - 0.010635322, - -0.021211673, - -0.0011590231, - -0.017631922, - -0.0006030075, - 0.033192895, - -0.00002157313, - -0.013291421, - 0.000087245535, - -0.017037096, - 0.021323599, - 0.009747024, - 0.004877596, - -0.022018451, - 0.005313095, - 0.012753291, - -0.013902147, - -0.024647022, - 0.003569978, - -0.0005831105, - -0.007890049, - 0.0005071272, - -0.01961465, - -0.015722776 - ] + "file": "point_the_player_towards_the.js" }, { - "quality": 0.7, - "successCount": 0, - "failureCount": 0, "name": "mine_3_blocks_of_grass", "description": "mine 3 blocks of grass", "keywords": [ - "mine", - "blocks", - "grass" + "grass", + "seeds", + "parrot" ], "file": "mine_3_blocks_of_grass.js", - "embedding": [ - -0.008995323, - 0.0015909804, - 0.008241113, - -0.04636901, - 0.013304681, - -0.004067259, - 0.014896366, - 0.0003893071, - 0.0006547415, - 0.016020838, - 0.0066925166, - -0.005156688, - -0.0005610258, - 0.023546418, - 0.13644092, - -0.005500835, - -0.021677237, - -0.00704959, - -0.020393189, - -0.000003080488, - -0.008672527, - -0.013772561, - -0.0038161827, - -0.0033632151, - -0.007858087, - -0.0048204735, - 0.015425104, - 0.03627746, - 0.019541642, - -0.014972294, - -0.0128778, - 0.027666938, - 0.014053124, - 0.0017778444, - 0.0057759336, - 0.020842692, - 0.0017025952, - -0.012786774, - 0.008141556, - 0.0124267135, - -0.009139418, - 0.016488189, - 0.0035593247, - 0.0054126782, - 0.012225885, - 0.030414734, - -0.002344843, - -0.0113920765, - 0.009168384, - 0.0011347317, - 0.0133676445, - 0.006971089, - -0.01354802, - -0.1784978, - -0.012687243, - -0.008044145, - 0.001240302, - -0.012377368, - 0.014029645, - -0.007502663, - -0.0020320665, - 0.03388266, - -0.0196346, - -0.019359121, - 0.007363274, - -0.0024041762, - 0.010521243, - -0.004624813, - -0.018622369, - -0.025553564, - -0.022952428, - 0.006457585, - 0.017272513, - -0.023139825, - -0.022384657, - 0.00008256829, - 0.0059276074, - 0.008644163, - 0.00035296156, - 0.051699214, - -0.0050704926, - -0.02919882, - -0.011883991, - 0.0040332456, - -0.0006099102, - -0.008536795, - -0.022969378, - 0.010195858, - -0.018867707, - -0.005584228, - -0.034368183, - 0.018824723, - -0.0083834585, - 0.016431088, - 0.012477393, - 0.021839442, - -0.004270532, - -0.009414568, - 0.0051853014, - -0.0021323173, - -0.022281088, - -0.012266499, - -0.010403302, - -0.029096896, - 0.029270671, - -0.029673338, - 0.016339382, - 0.006327336, - 0.0024653443, - 0.02260356, - 0.011887646, - 0.008867175, - -0.0013426555, - 0.026244748, - 0.015196738, - -0.18290824, - 0.01753676, - 0.011460776, - 0.007695021, - 0.01434947, - 0.0053896643, - -0.000012339041, - 0.00856697, - 0.03359825, - 0.018952752, - 0.010084269, - -0.01171152, - -0.0019265766, - -0.011411655, - -0.010363657, - -0.009771226, - 0.0026826484, - -0.0024790925, - 0.00021448734, - -0.0041415803, - 0.017279381, - -0.013074847, - -0.013272107, - -0.03196775, - -0.010656063, - -0.010542742, - -0.0009117181, - -0.007884446, - -0.0099327825, - -0.0007288027, - -0.032875996, - 0.002172968, - 0.006267728, - -0.0037433302, - -0.018545317, - -0.01929764, - -0.044701718, - 0.026708499, - -0.014181461, - -0.015039564, - -0.06111703, - -0.006848807, - 0.00853091, - -0.009538664, - -0.008910291, - 0.0003754629, - 0.014015351, - 0.018757235, - 0.013496503, - 0.0083373105, - -0.012285491, - 0.00170627, - 0.01810539, - 0.0006900949, - 0.014076835, - 0.0010024231, - -0.0018056011, - 0.007352293, - -0.010553378, - 0.013196731, - -0.026667165, - -0.009958473, - 0.02219371, - 0.030999305, - 0.009477912, - -6.7350345e-7, - -0.0068663657, - -0.010144133, - -0.009293036, - 0.012171318, - -0.022667702, - 0.008791452, - -0.013600116, - 0.0051226313, - -0.005788167, - 0.0018045541, - 0.012670052, - -0.025462218, - -0.029301139, - 0.014318558, - -0.021817874, - -0.022417177, - -0.008854116, - 0.014437764, - -0.013946386, - 0.003769704, - 0.00858692, - 0.00580092, - -0.0051943925, - -0.004099383, - 0.018433353, - -0.0069276956, - -0.0016495829, - 0.008858609, - 0.023845287, - 0.014753051, - -0.023928128, - -0.012468152, - 0.009070038, - 0.0030205818, - -0.01630039, - -0.0013494809, - -0.002826887, - 0.00080362376, - 0.0028319287, - 0.00061553385, - 0.0038110192, - 0.032004647, - 0.016359651, - -0.018670328, - 0.0085810935, - 0.0041584875, - -0.016173633, - -0.0059006475, - 0.0140124345, - -0.002595594, - -0.0024918995, - -0.027657649, - -0.025431145, - -0.022266388, - 0.004173795, - -0.0042950613, - 0.039307907, - 0.019251252, - -0.0031429566, - -0.016663564, - -0.017171416, - 0.002846178, - -0.016211664, - 0.019244099, - -0.006808978, - -0.0023152202, - 0.029247724, - -0.007271642, - -0.022450807, - 0.0013624906, - -0.003878959, - -0.0011290488, - -0.0136218285, - 0.016829545, - -0.030007174 - ] + "quality": 0.8, + "successCount": 1, + "failureCount": 0 }, { - "quality": 0.7, - "successCount": 0, + "quality": 0.9, + "successCount": 3, "failureCount": 0, "name": "mine_1_oak_log", - "description": "Mine 1 oak log", + "description": "mine 1 oak log", "keywords": [ - "mine", - "oak_log", - "wood" + "wood", + "mining", + "basic" ], - "file": "mine_1_oak_log.js", - "embedding": [ - -0.004074195, - 0.00222228, - 0.00535541, - -0.09154606, - 0.0036188737, - 0.013206415, - 0.0099744145, - -0.0065386607, - 0.00452771, - 0.0068887584, - 0.0060283467, - -0.010272657, - -0.002907234, - 0.012882808, - 0.13980412, - -0.032736547, - 0.008399691, - -0.007298692, - -0.013881602, - -0.020411527, - -0.016425513, - -0.0148648955, - 0.018815614, - -0.0018819603, - -0.01684801, - 0.003333054, - 0.015047088, - 0.030813651, - 0.03540849, - -0.026814898, - -0.01786793, - 0.02098451, - 0.011441559, - -0.012250249, - -0.0012732474, - 0.036489308, - -0.0006048849, - -0.008418259, - -0.018863881, - 0.0047656605, - -0.0102203, - 0.014470197, - -0.0060876757, - -0.021044057, - 0.001768317, - 0.030915627, - -0.0058182543, - -0.013194992, - 0.005237401, - 0.009056048, - 0.0049872603, - 0.021525528, - -0.014908653, - -0.19336483, - -0.005423297, - 0.009881786, - -0.0025511698, - -0.0010541636, - 0.02440248, - -0.004486651, - -0.016760265, - 0.022702524, - -0.008082397, - -0.011431072, - -0.0010608427, - -0.006705973, - 0.013531964, - -0.0031158673, - -0.015550797, - -0.017271888, - -0.014082918, - 0.0072189136, - -0.018029355, - -0.029709822, - -0.01831391, - -0.013781828, - 0.0098934835, - 0.00813487, - -0.012999251, - 0.04749163, - -0.0075681177, - -0.029810168, - -0.027378663, - 0.007016536, - -0.0014304349, - -0.0024909761, - -0.030410603, - -0.013280318, - -0.025097653, - 0.005099267, - -0.0071519422, - 0.02224669, - -0.016570976, - 0.022835245, - 0.0048556444, - 0.008239896, - -0.00062759704, - -0.0067257085, - 0.007512164, - -0.010272123, - -0.007380678, - -0.004589073, - -0.011202436, - -0.007023478, - 0.019892002, - -0.024368955, - 0.003874793, - 0.008453118, - -0.010339415, - 0.022186259, - -0.0056153727, - -0.0032906842, - 0.015435092, - 0.027129272, - 0.018984243, - -0.20101225, - 0.021131191, - -0.0059234235, - 0.005334111, - 0.0014991544, - -0.008936433, - 0.005320533, - -0.0040163863, - 0.014922536, - -0.006507638, - 0.0013383221, - -0.0041144216, - -0.00013206854, - 0.014802901, - -0.01176581, - -0.011442203, - -0.00012389146, - -0.011084221, - 0.023611076, - -0.0021664624, - 0.020937778, - -0.005553483, - 0.0008245935, - -0.004903061, - -0.0016545411, - 0.011906342, - -0.011831437, - -0.0059409025, - 0.005828103, - -0.019897249, - -0.011461171, - 0.033268083, - -0.005737456, - -0.014363082, - -0.011683529, - 0.0019650748, - -0.029049912, - 0.0018667388, - -0.0033202285, - -0.020003304, - -0.044673618, - -0.00026130045, - -0.0016769237, - -0.02263129, - 0.001691199, - 0.009352914, - 0.008767385, - 0.0134112295, - 0.0093289465, - -0.0005265754, - -0.018267639, - 0.0045570736, - 0.018040422, - 0.011152618, - 0.013160891, - -0.0018912747, - -0.014733425, - 0.019484255, - -0.014540357, - 0.023438942, - 0.005870336, - 0.006741945, - 0.0033473414, - 0.011582946, - 0.019148305, - 0.0111801615, - -0.00217407, - -0.010584074, - -0.0063705295, - 0.00083239534, - -0.013250107, - -0.008852495, - -0.017898085, - 0.023514558, - -0.005435623, - 0.010392603, - -0.001992339, - -0.0074625756, - -0.018137423, - -0.01056428, - -0.0034278766, - -0.028891731, - -0.003912574, - 0.037473857, - 0.004942556, - -0.004090982, - -0.002746716, - 0.0062674503, - 0.018096585, - 0.0057893223, - 0.015384121, - -0.009502448, - -0.011141094, - 0.008061326, - 0.049626064, - 0.029419407, - -0.029091876, - -0.004639699, - -0.0009147053, - 0.010685092, - -0.011050451, - -0.016061567, - 0.010287056, - 0.02945595, - 0.011509282, - -0.0057194936, - -0.009111036, - 0.038726013, - -0.0017977873, - -0.00025882004, - 0.00006663769, - -0.021779913, - -0.008288552, - -0.0083835805, - 0.02767126, - -0.01522927, - -0.0018801783, - -0.0027168782, - -0.02023703, - -0.012769884, - -0.012646367, - -0.020895392, - 0.028284784, - 0.006834668, - -0.008045118, - -0.0034763888, - -0.03414441, - -0.008427611, - -0.01867818, - 0.026496327, - -0.003205153, - -0.003271297, - 0.01218489, - -0.0027391869, - -0.022599384, - -0.009562752, - 0.004499296, - -0.008776525, - 0.0045697093, - 0.013895022, - -0.011697585 - ] + "file": "mine_1_oak_log.js" }, { - "quality": 0.7, - "successCount": 0, + "quality": 0.8, + "successCount": 2, "failureCount": 0, "name": "walk_to_the_nearest_player", "description": "Walk to the nearest player", "keywords": [ - "guard", - "protect", - "player" + "walk", + "player", + "approach" ], - "file": "walk_to_the_nearest_player.js", - "embedding": [ - -0.016333962, - 0.002986138, - 0.015086148, - -0.061056815, - -0.021743158, - 0.01366363, - 0.0008608996, - -0.0007338427, - 0.032377444, - -0.0034341945, - 0.0009749617, - -0.013731746, - -0.010076552, - 0.023044555, - 0.12026507, - -0.019537106, - 0.0044896854, - -0.0016606131, - 0.010176853, - 0.0015580441, - -0.02030397, - -0.002469627, - -0.013680094, - -0.021812815, - -0.023158086, - -0.0055056126, - 0.02047813, - 0.045816615, - 0.04477778, - 0.009436188, - 0.011498146, - 0.027342191, - 0.006798603, - 0.01611382, - -0.007589305, - 0.023832768, - 0.02345156, - -0.004790529, - -0.0026198705, - 0.011182993, - 0.0030216225, - 0.006478396, - -0.008968385, - 0.020105936, - 0.02689582, - 0.0022529708, - 0.007526403, - -0.020712737, - -0.0034747128, - 0.026829388, - 0.0063776523, - -0.0024564476, - -0.016201174, - -0.20167781, - 0.024228005, - 0.023611551, - 0.0023131159, - -0.01675658, - 0.00010850001, - 0.0032152617, - -0.012464099, - 0.0420812, - -0.0154903, - 0.0023199483, - 0.03683756, - 0.011507334, - 0.030197008, - 0.01366808, - -0.041261673, - 0.0025546341, - -0.010220933, - 0.0037660624, - -0.013191366, - -0.017064901, - -0.020069612, - 0.006086326, - -0.00984777, - 0.001969851, - 0.009794765, - 0.023855845, - -0.015459816, - 0.003529721, - -0.009901112, - -0.031123716, - 0.011990329, - -0.01762557, - 0.016615422, - -0.005319269, - -0.029169988, - -0.010401429, - -0.019984467, - 0.012074635, - 0.017387534, - 0.024283916, - 0.011516518, - 0.0141327735, - -0.009590033, - -0.02662565, - -0.0009682517, - 0.007908083, - -0.022056678, - -0.012869367, - -0.00921406, - 0.0069339816, - -0.0032598164, - 0.009817445, - 0.013935633, - -0.012884265, - -0.02031929, - 0.006771382, - 0.019832287, - 0.0059080548, - -0.009203212, - 0.011243393, - 0.016310444, - -0.18716866, - -0.0035387704, - 0.009364378, - 0.00087105244, - 0.011199377, - -0.019559566, - -0.0030583926, - 0.004813886, - 0.027262589, - 0.012588515, - 0.0013795719, - -0.0085938, - -0.008708381, - 0.0035957338, - -0.0014298722, - -0.0064236005, - -0.005646608, - 0.010620708, - 0.0084917545, - -0.0067596296, - 0.04727566, - -0.017590636, - -0.011393301, - -0.016728228, - -0.013499318, - -0.005133535, - 0.034236517, - -0.012832058, - 0.0016501015, - 0.0028986828, - -0.012688818, - -0.008263781, - 0.01755967, - 0.010140819, - -0.022467399, - 0.0020463928, - 0.003803048, - -0.012103226, - -0.023854833, - 0.00053198036, - -0.054620944, - 0.011117574, - -0.019731471, - 0.0010608954, - 0.008316655, - 0.01757765, - 0.0027231078, - -0.0011257007, - 0.0025118438, - 0.011528975, - -0.009799352, - -0.014469443, - -0.013978992, - 0.0005434939, - 0.019321397, - -0.011987583, - -0.030150436, - -0.0019128465, - 0.0130940955, - 0.017654818, - -0.010486158, - -0.008564595, - 0.01607197, - -0.026135212, - 0.004199029, - 0.027598644, - 0.006990409, - 0.0030097882, - 0.0031536592, - -0.005561208, - -0.017508453, - -0.010390586, - -0.0059028827, - 0.007910226, - -0.026547207, - 0.004918065, - 0.0033574386, - 0.009041874, - -0.012828882, - -0.015190014, - -0.013906588, - -0.006488358, - -0.020438576, - 0.010801212, - -0.009426182, - 0.016925165, - -0.009844871, - 0.014070042, - -0.030031344, - 0.00033991807, - -0.018079383, - 0.016711975, - -0.018650798, - -0.0046728984, - 0.024028614, - -0.0050093182, - -0.024195125, - 0.019786788, - 0.0046557183, - 0.035343252, - -0.017326802, - 0.0152474195, - -0.022348855, - 0.0062013883, - -0.004069736, - 0.01304436, - -0.013621635, - 0.017834824, - -0.0037342918, - 0.0112058725, - 0.0010621597, - 0.0109081855, - 0.009590696, - -0.0018971636, - 0.023062443, - 0.012139081, - -0.023799798, - 0.005414051, - -0.0057113445, - 0.0022539573, - -0.013209763, - -0.005347725, - 0.02308781, - 0.012969787, - 0.005019358, - -0.009105307, - -0.003618928, - 0.003825, - 0.028380556, - 0.007199314, - -0.012178064, - -0.0055306815, - 0.016194029, - -0.023058742, - -0.028828187, - 0.0065622083, - 0.0035626525, - 0.00029105766, - -0.014146348, - -0.01081714, - 0.0011747528 - ] + "file": "walk_to_the_nearest_player.js" }, { - "quality": 0.7, - "successCount": 0, + "quality": 0.8, + "successCount": 1, "failureCount": 0, "name": "find_a_water_source", "description": "find a water source", - "keywords": [ - "find", - "water", - "source" - ], - "file": "find_a_water_source.js", - "embedding": [ - -0.021538453, - -0.01759721, - 0.004737637, - -0.06616177, - 0.001375998, - -0.016878124, - -0.013558871, - 0.006382874, - 0.002528492, - 0.010806101, - -0.0010508747, - -0.005711203, - 0.012507641, - 0.009140798, - 0.13913237, - -0.008588977, - -0.006370631, - -0.0052993377, - 0.006568736, - 0.0014002178, - -0.023136528, - 0.0010648388, - 0.0071119233, - -0.015571504, - -0.00196059, - 0.00056587567, - 0.01936505, - 0.03323162, - 0.024069233, - -0.017687146, - 0.0024072398, - 0.017864287, - 0.013279128, - 0.026379334, - 0.00571096, - 0.023219803, - 0.004711421, - -0.018684521, - 0.012862926, - -0.016885135, - -0.007127209, - -0.016347919, - -0.020528201, - 0.0054519614, - -0.0039708167, - -0.012119971, - -0.0011270563, - -0.020656882, - -0.009919452, - 0.010281286, - 0.02620283, - 0.015614196, - -0.008449696, - -0.23237959, - -0.009639725, - 0.0051911008, - 0.0014428757, - 0.004092916, - 0.018177694, - 0.0007337203, - -0.0010059178, - 0.010906654, - -0.023029406, - 0.005339219, - -0.0002551573, - 0.002951888, - 0.004466937, - 0.010214854, - -0.02778143, - -0.0022694187, - -0.006269164, - 0.0031931049, - -0.018107904, - -0.022868989, - -0.0016925975, - 0.004047667, - -0.00034966125, - 0.007765531, - 0.009754571, - 0.03847192, - 0.0071225404, - -0.005846815, - -0.0000106709485, - -0.009400965, - 0.00025677428, - 0.004955229, - -0.011751575, - 0.0068042614, - -0.034849536, - 0.013070606, - 0.0012699873, - 0.01841479, - -0.0051589613, - 0.013260927, - 0.014559038, - 0.007240465, - -0.028331656, - -0.014580974, - 0.012323056, - -0.0018917426, - -0.014678711, - -0.017859349, - 0.0029967919, - 0.025539111, - 0.007739145, - -0.022243759, - -0.004449985, - 0.00833214, - -0.012054793, - -0.015450074, - 0.0063029192, - 0.01604963, - -0.02529146, - 0.0002752799, - 0.021578101, - -0.20242739, - 0.002976095, - -0.023245862, - 0.0022005425, - 0.00023210942, - -0.03074558, - -0.012687082, - 0.015931958, - -0.0005664755, - 0.011173431, - 0.009506754, - -0.0030551313, - -0.019630361, - 0.02088352, - 0.016520802, - 0.039093092, - 0.02671636, - 0.025912033, - -0.0130414115, - -0.0059482832, - -0.009494847, - -0.016013501, - -0.016246542, - -0.013243062, - -0.01461735, - -0.015304328, - 0.029365264, - 0.011124427, - -0.021721713, - -0.020920983, - -0.0022195524, - -0.00953306, - 0.014350007, - 0.015294983, - -0.0003638601, - -0.013710546, - -0.016948503, - 0.008947989, - -0.005262975, - -0.007247494, - -0.03807161, - -0.00881052, - 0.014908579, - -0.0027647228, - 0.0059678894, - 0.021405844, - 0.0037417426, - 0.017838992, - -0.011265176, - -0.0022878593, - -0.006661146, - -0.008838999, - -0.011678898, - 0.009466941, - 0.007734445, - -0.00040580245, - -0.0053530047, - -0.0051901643, - -0.013960125, - 0.0056271036, - 0.01087283, - 0.004565062, - -0.010500475, - -0.013712407, - -0.00028699523, - -0.0006696075, - -0.016371563, - 0.0014805122, - -0.027203292, - -0.011069112, - -0.009676234, - 0.0035041801, - -0.005153078, - 0.021544969, - -0.011738226, - -0.018557426, - 0.0023941335, - 0.0019444292, - 0.004464997, - -0.004389378, - -0.033458054, - -0.016747775, - -0.0033144506, - 0.0007413595, - -0.007092272, - 0.009002628, - -0.011482001, - 0.002105671, - 0.012219718, - -0.0004381086, - 0.006600099, - -0.002975416, - -0.0026394213, - -0.013651235, - 0.015555908, - -0.008868917, - -0.010991838, - 0.015562756, - -0.003141398, - 0.010898255, - -0.018710151, - 0.016752716, - -0.0123778535, - 0.007229243, - -0.011963463, - -0.0156010855, - -0.017228702, - 0.015808597, - 0.012838201, - -0.0052254368, - -0.009257349, - -0.0026558072, - -0.00054672116, - -0.022530176, - 0.013624753, - -0.011220865, - 0.003649213, - -0.010128545, - -0.013019221, - 0.0024586923, - 0.0066438452, - -0.019654838, - 0.01400187, - -0.00020421392, - 0.011194781, - 0.009849289, - -0.039920066, - 0.011244495, - -0.0068348837, - 0.008737051, - -0.02985726, - 0.025977772, - 0.013596754, - 0.0043549775, - -0.00050988677, - 0.013805626, - -0.008465038, - 0.014742025, - 0.010968174, - 0.009505945, - -0.005420559 - ] + "keywords": [], + "file": "find_a_water_source.js" }, { "quality": 0.7, @@ -4100,265 +226,7 @@ "guard", "player" ], - "file": "challengestranger.js", - "embedding": [ - -0.048747934, - -0.00072389445, - 0.015772203, - -0.06840678, - -0.00083530275, - 0.014012371, - 0.017978352, - 0.017989933, - 0.011740411, - 0.004897661, - 0.0020148263, - 0.0025948298, - -0.0013471094, - 0.02941238, - 0.13337258, - 0.0013373366, - 0.0007060924, - -0.018557644, - -0.0008420852, - -0.0010785803, - -0.025768638, - 0.0013839239, - -0.013653011, - -0.009121986, - -0.004827654, - -0.02710829, - 0.022494974, - 0.008677711, - 0.030416273, - 0.0042183637, - 0.008826086, - 0.0012054082, - -0.00023610675, - 0.011909754, - -0.01613009, - -0.0011073792, - 0.016960293, - -0.024516484, - -0.011664598, - -0.01789979, - -0.015213791, - 0.011843018, - 0.015132796, - 0.010598868, - 0.015076721, - 0.0023140658, - -0.004302158, - -0.010440134, - -0.0016305763, - 0.02738564, - 0.0042181117, - 0.022609308, - -0.030497381, - -0.20310062, - -0.0013329948, - 0.023912678, - 0.008236478, - 0.0048568854, - 0.011477438, - -0.0064014713, - -0.007069729, - 0.017352529, - -0.0031385229, - -0.018973248, - 0.0084773125, - 0.0059207845, - 0.013637216, - 0.00880499, - -0.056600217, - -0.008385954, - 0.014916418, - 0.010007811, - -0.028889079, - -0.03196677, - 0.0052469326, - -0.015548952, - 0.0076139234, - -0.013750117, - 0.030029928, - -0.0009885983, - -0.015042273, - -0.025247512, - 0.0073316935, - -0.008805698, - 0.020596176, - -0.021785134, - 0.016407507, - -0.0044031893, - -0.026577085, - 0.0031122381, - -0.020607086, - 0.014510516, - 0.032526195, - 0.0109611135, - 0.0040356037, - 0.01771661, - -0.011696392, - 0.0032473935, - 0.011339893, - 0.00439714, - -0.014616499, - -0.013147368, - 0.0025678517, - 0.01143146, - 0.012420606, - -0.004116818, - 0.00902898, - 0.004772307, - 0.0064954655, - 0.032672074, - 0.006083635, - -0.0012799671, - 0.00023102624, - 0.0009157609, - 0.008840241, - -0.18434484, - -0.0074073696, - 0.018448992, - 0.02009809, - 0.016486945, - -0.0062290714, - -0.011493127, - 0.015644, - 0.015011197, - 0.00024787305, - 0.008090248, - 0.022724207, - 0.006091677, - 0.03752815, - -0.007141616, - 0.022274699, - 0.018750673, - 0.024758264, - -0.011245614, - 0.0071302205, - 0.02970402, - -0.012586105, - -0.018959679, - -0.0041790563, - -0.022940818, - 0.0139043555, - 0.015463951, - -0.0065326123, - 0.00049298705, - 0.0032734296, - -0.011532949, - 0.0052066017, - 0.02324816, - 0.014444845, - -0.037531815, - 0.004982736, - -0.009851669, - 0.0057474393, - -0.011879692, - 0.015247239, - -0.07249575, - 0.02175394, - -0.009132259, - -0.01807583, - 0.00951285, - 0.019571641, - -0.00785452, - -0.013159165, - 0.008524589, - 0.0215821, - -0.00023098248, - -0.00004662483, - -0.0045962236, - -0.004013816, - 0.01977659, - -0.0016569858, - 0.003356719, - -0.0013415719, - 0.021244004, - 0.008949959, - 0.008907653, - -0.0056397566, - 0.011100269, - -0.03932525, - 0.0017132248, - -0.003526479, - -0.0055067064, - -0.011048728, - 0.010525092, - 0.0011677139, - -0.015735118, - 0.00871769, - 0.011279882, - 0.004545536, - -0.010966285, - -0.01117587, - 0.0010443641, - -0.018041305, - 0.023203043, - 0.005137514, - -0.022767533, - -0.007870957, - -0.013305397, - -0.018777495, - 0.0076059815, - -0.0022680988, - 0.009510218, - -0.008251165, - 0.011063974, - 0.0049198116, - -0.012877959, - 0.017666735, - -0.009295392, - -0.0051699765, - 0.027384356, - -0.0042651254, - -0.0003494143, - 0.010459883, - 0.023224847, - 0.013173735, - -0.0050077303, - -0.00758302, - -0.036728356, - -0.013652991, - 0.005462136, - 0.027624108, - -0.00611314, - 0.012339557, - 0.0023010375, - 0.012676096, - -0.017576268, - -0.003348459, - 0.008823323, - -0.03709061, - 0.017392335, - 0.029882291, - -0.013252174, - -0.024335276, - -0.012419648, - 0.01617131, - -0.014502328, - -0.007827154, - 0.02913942, - 0.015012104, - 0.020188082, - -0.0146696735, - 0.0064437073, - 0.021456359, - -0.0065367413, - 0.007969547, - -0.0064125084, - 0.008616393, - 0.0031610762, - -0.01807796, - -0.0052224183, - -0.0140949, - 0.013456769, - -0.00087180553, - -0.014986609, - 0.009501396, - -0.01303559 - ] + "file": "challengestranger.js" }, { "quality": 0.7, @@ -4373,265 +241,7 @@ "water", "source" ], - "file": "find_the_nearest_water_source.js", - "embedding": [ - -0.020727456, - -0.017306756, - 0.006480787, - -0.07872511, - -0.013828539, - -0.012289835, - -0.016484542, - 0.005257531, - 0.011226193, - 0.01162063, - -0.000017072114, - -0.005634339, - 0.013043318, - 0.020164052, - 0.13776787, - -0.013485877, - -0.011378825, - -0.014215406, - 0.009245243, - -0.0037416422, - -0.025983602, - 0.0044372594, - 0.0033157088, - -0.020888373, - 0.005103643, - 0.007890479, - 0.016667351, - 0.026061352, - 0.02659856, - -0.008451293, - -0.0019192077, - 0.014658089, - 0.0071449634, - 0.026679048, - 0.003114598, - 0.01345135, - 0.0127592925, - -0.014048631, - 0.0056564184, - -0.015852608, - -0.0038444113, - -0.019671796, - -0.008944849, - 0.004253322, - -0.009196113, - -0.012491607, - 0.0010243324, - -0.036999013, - -0.009608304, - 0.0081136655, - 0.026180401, - 0.011516176, - -0.016356988, - -0.21768463, - 0.006406763, - 0.0077846018, - -0.00077001785, - -0.0032140578, - 0.01411272, - -0.0033475421, - -0.00060185936, - 0.022309434, - -0.020168463, - 0.0062855217, - -0.0029286963, - 0.010860223, - -0.003187125, - 0.019158017, - -0.033999603, - 0.004619458, - -0.018233003, - 0.005528822, - -0.01694757, - -0.02335761, - 0.0022584444, - 0.0028995604, - 0.0027959913, - 0.0099054035, - 0.017279914, - 0.036466043, - -0.00047951617, - -0.01236254, - 0.0017547683, - -0.013875639, - 0.009292437, - 0.002434025, - -0.003366233, - 0.008059394, - -0.026361272, - -0.0032042786, - -0.0015333819, - 0.023632154, - -0.0027136528, - 0.013014501, - 0.0063192733, - 0.006524776, - -0.03074158, - -0.009095844, - 0.0065779686, - -0.00013015246, - -0.019487225, - -0.014501381, - 0.003246332, - 0.017233146, - 0.0016694745, - -0.011192711, - -0.004939588, - 0.0030903036, - -0.014651284, - -0.019504229, - 0.010259691, - 0.009158418, - -0.019240864, - -0.0045622094, - 0.02748228, - -0.19360201, - -0.006092687, - -0.02971056, - 0.0074859085, - 0.0001688842, - -0.03151307, - -0.017391363, - 0.018015817, - -0.002233853, - 0.010303484, - 0.010656015, - -0.0026396764, - -0.017408984, - 0.0128277065, - 0.023141347, - 0.03757076, - 0.026823513, - 0.026939958, - -0.021224411, - -0.0035597284, - -0.003120471, - -0.000567781, - -0.01422608, - -0.0074471626, - -0.016377494, - -0.009947137, - 0.026291963, - 0.004527923, - -0.01100922, - -0.015780978, - -0.014590368, - -0.023971824, - 0.013694227, - 0.013236824, - -0.018138003, - -0.0065780687, - -0.014851631, - 0.0025926393, - -0.015926, - -0.007120579, - -0.041153923, - -0.011309612, - 0.0116062565, - 0.00094392576, - 0.010333539, - 0.01658228, - 0.0028470743, - 0.013841035, - -0.012537605, - -0.0014613121, - -0.005340217, - -0.0143649345, - -0.0075669386, - 0.013442311, - 0.00092089095, - -0.0057907295, - -0.0194886, - -0.012660946, - -0.010257322, - 0.009669954, - 0.0020650744, - 0.006110501, - -0.0016537188, - -0.016564563, - -0.004052488, - 0.003423702, - -0.006100256, - 0.008259032, - -0.01814872, - -0.0064916722, - -0.00082845695, - 0.007201595, - -0.0056894263, - 0.016965844, - -0.012697066, - -0.016586872, - -0.0009884413, - -0.0022293665, - -0.006166866, - -0.011442467, - -0.030798284, - -0.015861321, - 0.001893108, - 0.00086602755, - -0.013477751, - 0.007437765, - -0.008031425, - 0.013758113, - -0.0040988587, - 0.0016955279, - -0.0024782093, - 0.011788046, - -0.0044215154, - -0.007080831, - 0.01593883, - -0.012364553, - -0.028155228, - 0.015466031, - -0.013475741, - 0.02296121, - -0.025646582, - 0.015584789, - -0.01705514, - 0.011252244, - -0.011003809, - -0.006767098, - -0.020761121, - 0.019171396, - 0.0016516747, - 0.003960994, - -0.0039144238, - -0.005470572, - 0.0059166304, - -0.010410914, - 0.013360191, - -0.0013143609, - 0.010623713, - 0.007534435, - -0.0069678733, - 0.004407724, - -0.007888999, - -0.014998626, - 0.013334492, - 0.001187345, - 0.009929029, - 0.011254024, - -0.030093199, - 0.015564434, - -0.006110559, - 0.01224706, - -0.032084696, - 0.020080432, - 0.002511395, - 0.001851688, - -0.009709446, - 0.013984089, - -0.011388122, - 0.0070150653, - 0.005689263, - 0.017973319, - -0.011719574 - ] + "file": "find_the_nearest_water_source.js" }, { "quality": 0.7, @@ -4644,265 +254,7 @@ "trade", "announcement" ], - "file": "announce_trading_wares.js", - "embedding": [ - -0.01957408, - -0.0049753394, - 0.014884989, - -0.096282355, - -0.015258634, - 0.009633673, - -0.0032146666, - 0.002878879, - -0.00023374226, - 0.012172932, - -0.0012581507, - -0.011359977, - 0.009085729, - -0.02202748, - 0.10974895, - -0.026603505, - 0.025924513, - -0.018601842, - 0.008965922, - 0.003742929, - -0.026364295, - -0.018228149, - -0.0041875434, - -0.03093469, - -0.0061214855, - -0.0025748042, - 0.021394381, - 0.0067769904, - 0.0347713, - -0.02253995, - 0.00028179272, - 0.027113609, - -0.010018403, - 0.01900578, - 0.012286415, - 0.01555907, - 0.010461492, - 0.007132994, - 0.004804014, - -0.016789718, - -0.022798933, - 0.043399114, - 0.006727814, - 0.005867087, - 0.009055876, - 0.019203829, - 0.018245867, - -0.016237713, - -0.010244972, - 0.027746571, - -0.0126174595, - -0.00019511598, - -0.008336841, - -0.16541247, - -0.023064591, - 0.00716078, - 0.024770474, - -0.019900503, - 0.013318162, - -0.01650385, - -0.013680239, - 0.022429228, - -0.007417379, - -0.034945346, - 0.012225751, - -0.0018877086, - -0.014416013, - -0.0012298194, - -0.022782104, - -0.011270104, - 0.010741457, - -0.015715765, - 0.000579039, - -0.018116651, - 0.0065994267, - -0.029294025, - 0.005214583, - 0.009996933, - 0.01224846, - 0.020323545, - -0.01095804, - -0.024848288, - -0.008324763, - -0.020905042, - -0.012789265, - -0.0015759004, - -0.0078089167, - -0.010324121, - -0.020044193, - 0.00340614, - -0.00459867, - 0.011316813, - 0.016918445, - 0.02674685, - 0.017560096, - 0.018446304, - 0.007360894, - -0.03597483, - -0.018535977, - -0.03708191, - 0.0044273348, - -0.010553505, - -0.014548482, - 0.002278864, - -0.007531976, - -0.011390947, - -0.004752879, - 0.0033763782, - -0.017056426, - 0.010962191, - 0.018383518, - 0.005434677, - 0.0060243616, - 0.012758446, - 0.01717928, - -0.1706219, - 0.011316425, - 0.012207083, - -0.021619568, - 0.01310026, - -0.038960412, - -0.0057280934, - 0.0033905273, - 0.005445673, - -0.005050262, - 0.01750913, - 0.016458705, - -0.006321259, - -0.013704661, - 0.003387403, - -0.0059840446, - -0.0063990867, - 0.005333447, - 0.026152367, - 0.0028480066, - 0.03191856, - -0.034002427, - 0.009761604, - -0.014612971, - -0.0019440202, - 0.018092565, - 0.011566163, - -0.009351312, - -0.0011225037, - -0.003331427, - -0.0032334398, - -0.010518829, - 0.0077168182, - -0.012456239, - -0.0075149536, - 0.013727811, - -0.010049842, - -0.00062684, - -0.01000569, - -0.0053230575, - -0.03673808, - -0.007892833, - 0.0075635305, - -0.02252507, - 0.023359412, - -0.01049154, - 0.016741794, - -0.003339079, - 0.004513741, - -0.0047765537, - -0.013672723, - 0.015707145, - -0.007997638, - -0.0010551979, - 0.029246632, - -0.020297436, - -0.011821945, - 0.007203595, - 0.018918917, - -0.02610669, - 0.006242817, - -0.0011126004, - 0.006179822, - 0.0020385548, - -0.004155903, - 0.009596627, - -0.0020402744, - 0.02522601, - -0.0016282102, - 0.03613642, - -0.01952061, - 0.0008502163, - -0.010288426, - 0.012345986, - 0.004471844, - 0.018458158, - -0.0036714775, - 0.000074335854, - -0.016168615, - -0.006637371, - -0.016660804, - -0.036992762, - -0.0048882086, - 0.008510551, - 0.007909689, - 0.0045372997, - 0.0038018932, - 0.014654078, - -0.004734188, - 0.027760988, - -0.020555926, - 0.008130159, - -0.0007122571, - -0.0044776932, - 0.030213501, - -0.004283379, - -0.021513863, - -0.015550503, - -0.019642431, - 0.0075625544, - 0.018720128, - 0.022183713, - -0.0019004392, - -0.004600501, - 0.0011824286, - 0.013852961, - -0.021694966, - 0.014438738, - 0.017747708, - 0.010563144, - 0.006193399, - -0.0049884054, - -0.0013297822, - -0.027873972, - 0.015391483, - 0.0024701804, - -0.012151357, - 0.008214785, - -0.016896874, - 0.0009590998, - -0.016369022, - 0.006926629, - 0.002096798, - -0.007682721, - -0.0029926598, - -0.000028193606, - -0.0020077173, - -0.021690238, - -0.0061162505, - -0.0018866707, - -0.0026168155, - 0.030843053, - 0.007227296, - 0.02127633, - -0.004081285, - -0.013583431, - 0.000357146, - -0.00624392, - 0.01315787, - -0.041346237, - -0.0069581764 - ] + "file": "announce_trading_wares.js" }, { "quality": 0.7, @@ -4914,265 +266,7 @@ "forest", "direction" ], - "file": "pointtheplayertowardsthenearestforest.js", - "embedding": [ - -0.0124662565, - -0.028848628, - 0.0073264805, - -0.077651314, - -0.016255537, - 0.009015448, - -0.005263752, - 0.010620724, - 0.032320134, - 0.013853619, - 0.0017026998, - 0.009141169, - -0.006377257, - 0.048461586, - 0.11530099, - -0.033694565, - 0.0040186625, - -0.016545711, - -0.009780325, - -0.014116524, - -0.010994323, - -0.010184681, - -0.009526016, - -0.03292246, - -0.0060840948, - -0.0066324864, - 0.004550459, - 0.01570368, - 0.036255922, - -0.014582151, - -0.008213772, - 0.03239797, - 0.018225053, - 0.028510371, - -0.020996401, - 0.030055383, - 0.016609991, - -0.00392166, - 0.008524176, - -0.0060397955, - -0.013606245, - 0.005733634, - 0.013487437, - 0.014333707, - -0.003355361, - 0.027544722, - 0.0056535793, - -0.046058487, - -0.000010242588, - 0.03143839, - 0.0009385158, - 0.010529188, - -0.014080275, - -0.19468275, - 0.006187978, - -0.0009672698, - 0.0089872135, - 0.004208836, - -0.012058154, - -0.020873552, - -0.013668606, - 0.026095776, - 0.0025829857, - -0.021114958, - 0.021430567, - 0.002738377, - 0.0030120094, - -0.0039905757, - -0.023617774, - -0.002508871, - -0.008803076, - -0.010673105, - -0.019631684, - -0.035805095, - -0.008216869, - -0.001616536, - 0.025457002, - -0.0034670727, - 0.02214064, - 0.03686312, - -0.0028110445, - 0.006712581, - -0.036320135, - -0.021048732, - -0.010878251, - -0.015920287, - -0.027265238, - 0.00016756594, - -0.0074911937, - -0.013054286, - -0.027129652, - 0.027329782, - 0.0076408316, - 0.009310779, - 0.00015567458, - 0.019832775, - -0.010226967, - -0.0063710744, - 0.0043751574, - 0.003423978, - -0.013958033, - 0.0058088345, - 0.0028769965, - -0.00474936, - 0.0018727797, - 0.008351675, - 0.016068788, - -0.0031442193, - -0.003185511, - -0.010787104, - 0.024139237, - 0.012934665, - -0.0033711176, - 0.003618285, - 0.020756824, - -0.18762253, - -0.0018406012, - -0.011321712, - 0.011071993, - 0.0057317745, - -0.017851712, - -0.020032493, - 0.020599114, - 0.03005857, - 0.007845992, - 0.009316608, - 0.012153894, - -0.016758505, - 0.015591741, - -0.007615631, - 0.023139803, - 0.003383474, - 0.0075094253, - 0.008273489, - 0.009024003, - 0.01463138, - 0.0027112039, - -0.0057595666, - -0.0051802876, - -0.0113144275, - -0.020292155, - 0.006823698, - -0.015557449, - -0.005600202, - -0.0007982476, - -0.031545077, - -0.0009718034, - 0.0188231, - 0.0069607073, - -0.022253659, - -0.026735099, - -0.02292197, - 0.011339375, - -0.019545168, - 0.008646395, - -0.04058139, - 0.0117636435, - -0.00081154093, - -0.004004554, - 0.019826723, - 0.028795252, - -0.000482484, - 0.014274544, - -0.00081319746, - 0.014867275, - 0.016200516, - 0.0031470773, - -0.005354273, - 0.014123891, - 0.0195289, - -0.009785895, - -0.004292067, - 0.013862042, - 0.0038969263, - 0.006627129, - -0.009626607, - 0.0010053336, - 0.035034504, - 0.0023546328, - -0.0063611576, - 0.020715704, - -0.0127731785, - -0.0043494, - 0.012715917, - 0.0075567365, - -0.016352838, - -0.005955199, - -0.018258948, - 0.017502133, - -0.004975347, - -0.014544464, - 0.012359668, - 0.01335248, - -0.01827602, - -0.0029065597, - -0.020485185, - 0.0054274495, - 0.004493694, - 0.021377327, - -0.034964427, - 0.0064408085, - 0.0073970994, - -0.0015953156, - -0.028667469, - 0.002144831, - -0.017362457, - -0.005017496, - -0.01644262, - 0.014773863, - 0.0058312933, - 0.016669488, - -0.027344953, - -0.013285628, - -0.0026920156, - 0.021946216, - -0.012351051, - 0.016739365, - -0.032596525, - 0.0076725185, - -0.016679676, - 0.0027491478, - -0.009836332, - 0.025911579, - 0.030157117, - 0.0053622616, - 0.014457863, - -0.0034178568, - 0.014958308, - -0.015800223, - -0.0012514163, - 0.004602719, - -0.009673595, - 0.0035799681, - -0.019843986, - -0.00001696485, - -0.0045582796, - -0.0002671991, - 0.029175255, - 0.0030402637, - -0.005335078, - -0.003094583, - -0.018037088, - 0.011917603, - 0.0136430925, - 0.008545044, - -0.01585949, - 0.006593628, - 0.016998187, - -0.0066619767, - -0.018247759, - 0.004063831, - -0.0032344926, - -0.007490639, - -0.0077496325, - -0.018614996, - -0.017876182 - ] + "file": "pointtheplayertowardsthenearestforest.js" }, { "quality": 0.7, @@ -5185,265 +279,7 @@ "wisdom", "player" ], - "file": "share_wisdom_with_nearby_player.js", - "embedding": [ - -0.02292001, - -0.008720879, - 0.01288027, - -0.076049976, - -0.016456416, - -0.009193408, - -0.026238406, - 0.011471585, - 0.015422917, - 0.012324911, - 0.010029004, - -0.007605475, - 0.0046365415, - 0.01012881, - 0.11411356, - -0.015888706, - -0.013306056, - 0.01546451, - 0.018082257, - 0.014157122, - -0.015358445, - -0.002772623, - -0.018466713, - -0.06031831, - -0.019019136, - -0.019650986, - 0.019994292, - 0.008134619, - 0.029474406, - -0.0129671665, - 0.0038948765, - 0.022382284, - -0.005126198, - 0.012844225, - -0.010762041, - 0.03554665, - 0.027214846, - 0.009038316, - -0.00014309323, - -0.0010665073, - -0.01974896, - 0.022589896, - -0.009047776, - 0.015422199, - -0.0119416695, - 0.010881001, - 0.0010812503, - -0.029913304, - -0.012464809, - 0.028738813, - -0.0004029613, - 0.012652735, - -0.012980425, - -0.18923216, - 0.0012382856, - 0.008990666, - 0.0097648995, - -0.0057511465, - 0.004764599, - -0.011599337, - -0.017221503, - 0.016318044, - -0.010394067, - -0.025036229, - 0.007880584, - -0.0028538948, - 0.004067719, - -0.007850928, - -0.023220683, - -0.00007818865, - -0.02410913, - -0.025103316, - -0.01620674, - -0.016275067, - -0.002083458, - -0.0037603453, - 0.0053976797, - -0.014208564, - 0.020636484, - 0.024000023, - -0.01829811, - -0.013591721, - -0.008300669, - -0.018248884, - -0.008967639, - 0.0010388806, - 0.006054408, - -0.0024331096, - -0.034476876, - -0.017109396, - -0.018932464, - 0.02350212, - 0.015613267, - 0.043043032, - 0.028607294, - 0.031446896, - -0.008197538, - -0.020808468, - -0.011088302, - -0.0051277867, - -0.005266436, - -0.015569418, - -0.023415165, - 0.00019574519, - -0.023775252, - -0.026812388, - 0.011848061, - 0.011769435, - -0.009430983, - 0.021322522, - -0.013601698, - -0.00009989118, - -0.011821943, - -0.012639697, - 0.018082414, - -0.18458907, - 0.019865299, - 0.009715157, - -0.008219453, - 0.013726935, - 0.001777355, - -0.024697404, - -0.0005063637, - 0.014837191, - 0.022532051, - 0.01084814, - 0.0034133815, - 0.006319408, - -0.004974914, - -0.012219048, - 0.010072557, - 0.013924963, - 0.0018643448, - 0.017488422, - -0.012216877, - 0.027473075, - -0.011000676, - -0.027740773, - -0.015928695, - 0.009820185, - 0.0034740607, - 0.0062948535, - 0.0012278598, - 0.002747782, - 0.0048277844, - -0.01971236, - -0.020958524, - 0.02006164, - 0.0075867726, - -0.011040995, - 0.009558445, - -0.009179363, - 0.0009794186, - -0.010501121, - -0.006198142, - -0.060302597, - 0.011007931, - -0.0058536306, - 0.008097392, - 0.022041913, - 0.00501901, - 0.015885258, - 0.018061643, - 0.033793062, - 0.007872972, - -0.01668535, - 0.0061971247, - -0.027025133, - -0.0041619367, - 0.017919676, - -0.009796647, - 0.0028085369, - -0.008556039, - 0.016458657, - 0.0020755923, - 0.021609763, - -0.017645098, - 0.012736685, - -0.027565729, - -0.021652848, - 0.020921351, - -0.0011992895, - 0.0074989437, - -0.0059659956, - -0.007778323, - -0.015642488, - -0.009612131, - -0.017636985, - 0.02415844, - -0.030956896, - -0.006900258, - 0.008072346, - 0.0016251102, - -0.0066554565, - -0.019515611, - -0.017871581, - -0.016273977, - -0.00014131042, - 0.015590908, - -0.0024303577, - 0.0071123918, - 0.006356714, - 0.016700897, - -0.0133721335, - -0.008312328, - -0.018340262, - 0.011433525, - -0.010915022, - 0.012994424, - 0.040733267, - 0.008981351, - -0.016724972, - 0.0072492016, - -0.0026872002, - 0.0029990203, - -0.008566841, - -0.006336176, - -0.022692496, - 0.0072242543, - -0.01887643, - 0.015219396, - 0.0026991575, - 0.022386929, - -0.0002208891, - 0.0056733573, - 0.008221996, - -0.014926415, - -0.015059546, - -0.002553834, - 0.0057880445, - 0.019111143, - 0.020555522, - -0.02462995, - -0.019128967, - -0.020790165, - -0.0143424785, - -0.022024622, - 0.0081744455, - 0.004033821, - 0.012145226, - -0.001820913, - -0.0078056254, - 0.008417504, - 0.0030259541, - 0.010996714, - -0.00026687965, - 0.01682314, - 0.0146126095, - 0.011413398, - -0.010661918, - 0.018523982, - -0.0023598613, - 0.004116677, - 0.00070866744, - -0.01698726, - -0.019334976 - ] + "file": "share_wisdom_with_nearby_player.js" }, { "quality": 0.7, @@ -5456,269 +292,11 @@ "seeds", "wheat" ], - "file": "explore_and_find_wheat_seeds.js", - "embedding": [ - -0.035974413, - 0.015977219, - 0.019242056, - -0.046874125, - -0.004604174, - -0.001975218, - -0.01191111, - 0.0027385484, - 0.0153559325, - 0.03143355, - -0.005127629, - 0.012764488, - -0.0034324892, - 0.025161406, - 0.13872716, - -0.025253018, - 0.0024725264, - -0.022836627, - 0.007340807, - 0.0013762905, - -0.010735353, - -0.018287795, - 0.011457301, - -0.024276048, - -0.020678723, - -0.020765994, - 0.0252589, - 0.0049910233, - 0.024691885, - -0.015581635, - 0.0012598789, - 0.032024067, - -0.008388433, - 0.017564258, - -0.00070373673, - 0.0384009, - 0.012114836, - -0.0059686736, - 0.004346329, - 0.010036687, - -0.015217063, - 0.015455245, - 0.008016515, - 0.0013861521, - 0.01751385, - 0.014690738, - -0.0031676064, - -0.009501406, - -0.01788327, - 0.014540205, - 0.013514784, - 0.0010520536, - -0.009494966, - -0.19968362, - -0.023495175, - -0.007884712, - 0.0077285436, - -0.007813788, - 0.0053777983, - -0.02533848, - -0.0035872026, - 0.04159576, - -0.04398435, - -0.017430885, - 0.01702642, - -0.0014698283, - 0.011694692, - -0.006976928, - -0.028056204, - 0.004791941, - 0.0089956075, - -0.005177027, - 0.009710186, - -0.038402025, - -0.0020299468, - -0.017347848, - 0.0066925953, - -0.01326347, - -0.0027807343, - 0.029674912, - 0.0088332845, - -0.034398153, - -0.015056203, - -0.019233502, - -0.025270859, - -0.005649606, - -0.026302744, - -0.0011137245, - -0.019364342, - 0.007004537, - -0.026862798, - 0.02559437, - -0.0023057135, - 0.015294767, - 0.015504808, - 0.008411201, - -0.009122632, - -0.013422508, - 0.001087102, - 0.004672242, - -0.039209455, - -0.04883864, - -0.020420652, - -0.0037251557, - 0.014111718, - 0.005797204, - 0.013516547, - 0.0023326268, - 0.0037344028, - 0.009114878, - 0.012470576, - -0.00019014427, - -0.016626958, - 0.0067066015, - 0.0031418216, - -0.17912835, - 0.02327845, - 0.013668351, - -0.00034672752, - 0.009116986, - -0.032595355, - -0.0012444769, - 0.020480081, - -0.0121430615, - 0.0011077948, - 0.014546171, - 0.0030793673, - -0.00937947, - 0.00941814, - -0.008357442, - 0.008200637, - 0.0144764, - -0.00088828057, - 0.009097007, - -0.0063767526, - 0.0063291467, - -0.016199369, - -0.012121715, - -0.012268348, - -0.005993453, - -0.012647104, - 0.009633688, - -0.0042398716, - -0.0052756765, - -0.023958003, - -0.017748768, - -0.00934423, - 0.03182856, - 0.009074612, - -0.011149651, - -0.02109014, - 0.008062286, - -0.012609801, - -0.027366962, - 0.008517113, - -0.05203926, - -0.0067900745, - -0.00089003163, - -0.024800703, - 0.008611226, - 0.012490189, - -0.021219138, - 0.010960264, - 0.03203469, - -0.001777171, - -0.024149325, - -0.007043526, - 0.0020283847, - 0.017675748, - 0.024873583, - -0.015740912, - -0.012282374, - 0.0064265504, - -0.004723897, - -0.01082335, - 0.00878509, - -0.003723763, - 0.01733123, - 0.010461135, - 0.013380833, - -0.0038136397, - -0.018431053, - -0.0010229552, - 0.00978177, - 0.024860049, - -0.018423384, - -0.01891028, - 0.0039146026, - 0.011098649, - -0.009328955, - -0.002772458, - -0.0011127553, - -0.018733954, - 0.012146079, - -0.0072466824, - -0.009605725, - -0.00665079, - 0.008173502, - 0.0026368443, - -0.003917502, - -0.0058525032, - -0.017703952, - 0.0062658773, - -0.020939691, - 0.0021278758, - 0.007908453, - -0.011409653, - -0.0092687635, - -0.004636867, - 0.019842783, - 0.0076550264, - -0.016466364, - 0.004534609, - 0.0021301014, - 0.0103083495, - -0.0040716324, - -0.008304741, - -0.008676265, - -0.0005450137, - -0.019451665, - -0.022115756, - -0.00228445, - 0.027327226, - 0.014739233, - -0.0067364518, - -0.008608089, - -0.0074806917, - -0.021891374, - -0.040118545, - 0.007014732, - 0.0053301617, - 0.0055953544, - -0.011373351, - -0.03537951, - 0.015639776, - -0.014535883, - -0.0031767706, - 0.020978592, - -0.0020599137, - -0.0028302018, - -0.0142751625, - -0.022609854, - 0.006861811, - -0.015275224, - 0.010049177, - -0.0054873317, - 0.005091672, - 0.014556085, - -0.0022810379, - -0.0019676762, - 0.0178269, - -0.010274198, - -0.015757011, - -0.004225745, - 0.004795091, - -0.003861758 - ] + "file": "explore_and_find_wheat_seeds.js" }, { - "quality": 0.9, - "successCount": 1, + "quality": 0.7, + "successCount": 0, "failureCount": 0, "name": "craft_a_wooden_hoe", "description": "Craft a wooden hoe", @@ -5727,265 +305,7 @@ "hoe", "wood" ], - "file": "craft_a_wooden_hoe.js", - "embedding": [ - -0.014629905, - 0.016159082, - 0.0024992218, - -0.05535739, - 0.0047063855, - 0.008978656, - 0.016514901, - 0.018463273, - 0.005864593, - 0.00087105297, - -0.045154896, - -0.0068314117, - 0.015394935, - -0.01715069, - 0.12135589, - -0.037610196, - 0.011596094, - -0.006386034, - -0.001806086, - -0.004604051, - 0.008788831, - -0.0058142818, - -0.003189615, - -0.015742438, - -0.009542635, - 0.0053858594, - 0.0018013238, - 0.016318185, - 0.027874911, - -0.014469377, - -0.020669559, - 0.035720225, - 0.0047391513, - -0.005578213, - 0.0073375967, - 0.04004595, - 0.019946646, - -0.013928304, - -0.0005194239, - 0.0056780064, - -0.0041980064, - 0.016404273, - -0.028332185, - -0.003958251, - 0.007934786, - 0.025300458, - -0.0022743004, - -0.029452026, - 0.011144877, - 0.020595068, - 0.010055744, - 0.01220626, - -0.015533623, - -0.1863208, - -0.014093645, - 0.016347872, - -0.020056738, - -0.0044477703, - 0.010838047, - -0.009165845, - -0.013338984, - 0.013644331, - 0.001060073, - -0.0050069205, - 0.009389318, - 0.0059949807, - 0.004073033, - -0.0025322519, - -0.011607983, - -0.0024743385, - 0.013871656, - -0.004196241, - -0.013860351, - -0.0074346284, - -0.019997869, - -0.0199887, - -0.007620736, - 0.0074554784, - -0.016146999, - 0.054696273, - -0.021354295, - -0.03356184, - -0.016455254, - -0.008604, - -0.015214234, - -0.011841138, - -0.011345569, - 0.0013880895, - -0.02400306, - -0.005564709, - -0.0150994295, - 0.0049751126, - -0.00445553, - 0.03267278, - 0.029414104, - -0.010837227, - -0.011946686, - -0.010704517, - -0.020916576, - 0.013045932, - -0.019357113, - -0.021494856, - 0.0072007123, - 0.014066189, - 0.015166805, - -0.033855684, - 0.0057552624, - 0.018834608, - -0.0064700395, - 0.0029629227, - 0.03827436, - 0.0056095654, - -0.00073889137, - 0.034703586, - 0.015778352, - -0.19863777, - 0.018932138, - -0.0052194707, - -0.0052331705, - 0.003968753, - -0.00605982, - -0.006222939, - -0.0057584695, - 0.022797301, - 0.0048704804, - 0.0036308938, - -0.0049468917, - 0.010519172, - 0.0019039828, - -0.005393841, - -0.0045363572, - -0.0007516865, - -0.010364829, - 0.027726911, - 0.012461862, - 0.026500097, - -0.015994623, - -0.02358971, - -0.007821091, - 0.010517978, - 0.0011292882, - -0.0012241872, - 0.001021618, - -0.0063220547, - -0.020701015, - -0.02816149, - 0.015075515, - -0.0001387535, - -0.020221263, - -0.010912897, - -0.003709817, - -0.020323096, - 0.0021772776, - -0.02083261, - -0.031793874, - -0.030007334, - 0.00026661987, - 0.009431516, - -0.013636246, - 0.029142322, - -0.002300103, - 0.016061453, - 0.013271666, - 0.006898233, - 0.016061287, - -0.020528523, - 0.026240284, - -0.001821416, - 0.00011506488, - 0.0142190065, - -0.00873781, - 0.0050532077, - 0.0058302353, - 0.0011355543, - -0.0012692157, - -0.0015918923, - 0.0030607833, - -0.0062379087, - 0.009745252, - 0.017481258, - 0.018594252, - 0.0033436746, - -0.0023176156, - -0.018458322, - 0.0071297325, - -0.017720835, - -0.014636777, - -0.018085746, - 0.021371368, - -0.0098572485, - 0.008999697, - 0.023471674, - -0.0053181467, - -0.021694524, - -0.008223958, - 0.004597326, - -0.02535269, - -0.013699229, - 0.021282524, - 0.0067584733, - 0.0091449, - 0.003676132, - 0.0013709007, - 0.011020947, - 0.009097937, - 0.018717425, - -0.010591504, - -0.017178522, - 0.006193722, - 0.044751436, - 0.029540323, - -0.037163343, - -0.011605176, - -0.0012295203, - 0.0030885981, - 0.004666789, - -0.0055139447, - 0.007333488, - 0.023446178, - -0.00012701542, - -0.012063668, - 0.00777113, - 0.024660902, - -0.0038025002, - -0.013567889, - 0.0077091246, - -0.033265322, - -0.013683652, - -0.028966457, - 0.014064431, - -0.0035464887, - -0.013292093, - -0.0075676865, - -0.016915018, - -0.00262567, - 0.0042575332, - -0.026113404, - 0.0040590204, - 0.015735976, - -0.0028833463, - 0.015827838, - -0.027854469, - -0.00273799, - 0.0014456051, - 0.02147953, - 0.01038061, - -0.0020822543, - 0.0015194494, - -0.018330848, - -0.026058037, - -0.0066156276, - 0.007005429, - -0.008483754, - 0.003790096, - 0.014661947, - -0.0243125 - ] + "file": "craft_a_wooden_hoe.js" }, { "quality": 0.7, @@ -5998,540 +318,20 @@ "farm", "crops" ], - "file": "walk_to_the_nearest_farmland.js", - "embedding": [ - -0.013860536, - 0.004835028, - 0.007073885, - -0.0491659, - -0.021930011, - -0.0015858917, - 0.020621764, - 0.0019888496, - 0.039493803, - 0.021516234, - -0.0065038446, - 0.016832713, - -0.024165398, - 0.022245375, - 0.11986381, - -0.01703077, - 0.00075052027, - -0.011648252, - 0.019267574, - 0.011342763, - -0.015603637, - 0.0041580265, - 0.013313661, - -0.032158952, - -0.008214135, - 0.0051957, - 0.019000879, - 0.028475745, - 0.03947019, - 0.0012757661, - 0.0065497044, - 0.021620113, - -0.003532289, - 0.021184595, - 0.015281155, - 0.017472018, - 0.030403677, - -0.014317428, - 0.01350106, - -0.009915154, - -0.021235531, - -0.009712379, - 0.002681948, - 0.014959269, - 0.012776861, - 0.0060422006, - 0.0011048507, - -0.025184173, - -0.004985884, - 0.0094444575, - 0.018383013, - 0.0035445034, - -0.007589449, - -0.21129946, - 0.0026099351, - 0.01585082, - 0.0014294778, - 0.007307883, - 0.0013666871, - -0.015215179, - -0.00908385, - 0.03852128, - -0.024051838, - -0.010317201, - 0.009570956, - -0.0027627195, - -0.0022909818, - 0.00059667596, - -0.03347179, - 0.005058052, - 0.01788139, - -0.0026598987, - -0.005430792, - -0.030578738, - -0.009904437, - -0.019271227, - -0.022200044, - 0.020414647, - -0.0064425785, - 0.035561264, - -0.0070883203, - -0.009487349, - -0.02269101, - -0.037085928, - 0.0025283531, - -0.0277666, - 0.016012687, - 0.007401183, - -0.010161143, - -0.0036004838, - -0.018141434, - 0.021721868, - 0.012712918, - 0.013498481, - -0.001549985, - 0.008247928, - -0.016187241, - -0.036716726, - 0.008769657, - 0.008070655, - -0.01140822, - -0.023185516, - -0.0056853946, - 0.0006404784, - 0.0065459595, - -0.028222809, - 0.0058921804, - -0.008983634, - -0.0072079943, - -0.009082366, - 0.029092452, - -0.0053374534, - -0.009673108, - 0.021667609, - 0.010292957, - -0.17244647, - 0.024571057, - 0.0007081021, - -0.009702096, - 0.010179696, - -0.020977158, - -0.00654827, - 0.00817091, - 0.014403851, - -0.000121263554, - 0.004528421, - -0.009861023, - -0.009613069, - 0.009617913, - 0.0032317166, - 0.01970706, - 0.011534127, - -0.009929534, - 0.0209344, - -0.02149074, - 0.014997124, - -0.0028870145, - -0.017822416, - 0.00079628354, - 0.00883671, - -0.0016327196, - 0.0020126551, - -0.0016213278, - -0.010203278, - -0.011627255, - -0.037264794, - -0.023127653, - 0.004532206, - -0.0014250509, - -0.004647327, - -0.007670489, - 0.0019553436, - -0.0033313157, - -0.027064137, - -0.017024407, - -0.045049794, - 0.0028715935, - -0.017092338, - 0.004866794, - 0.020652603, - 0.009022768, - -0.014130154, - 0.0033792278, - -0.00676019, - 0.013739916, - -0.012008869, - -0.0048246533, - -0.019603265, - 0.014805122, - 0.030221751, - -0.03782087, - -0.025015296, - -0.012769124, - -0.010435384, - -0.0047207284, - -0.012237213, - -0.012545623, - 0.02208503, - 0.010186856, - 0.02127095, - 0.011469088, - -0.0096921045, - -0.00046471952, - -0.0108533325, - 0.013034771, - -0.015677348, - -0.015582535, - -0.017629592, - 0.018401941, - -0.015337938, - -0.005634091, - 0.0030261974, - 0.0007856001, - -0.007833279, - -0.0031488067, - -0.01867208, - -0.0023169415, - 0.0020145248, - 0.022481142, - -0.023555916, - 0.019790433, - 0.0023504114, - 0.008367593, - -0.01860093, - 0.01403618, - -0.015048595, - -0.009675681, - 0.010914431, - -0.008721751, - 0.0010696324, - 0.019112095, - -0.028020406, - 0.002580835, - 0.0007268997, - 0.027169954, - -0.01016972, - -0.015334482, - 0.0059493585, - -0.004381523, - -0.0067137214, - 0.0023940161, - -0.02721626, - 0.02869006, - 0.007339864, - 0.0051048137, - -0.012072242, - 0.008694693, - 0.0077777216, - -0.014480489, - 0.011787511, - 0.0075648753, - 0.014399912, - 0.019476157, - -0.029810267, - 0.010415436, - 0.024579737, - -0.0049524233, - 0.032356866, - 0.00920832, - -0.0076752612, - -0.009159109, - -0.04785754, - 0.017971322, - 0.0035373408, - 0.0011477753, - -0.017376464, - 0.0118880775, - 0.003157497, - -0.024340816, - -0.023679653, - 0.010160515, - -0.005556936, - 0.009994497, - -0.018138627, - 0.000012755367, - -0.020812895 - ] + "file": "walk_to_the_nearest_farmland.js" }, { - "quality": 0.7, - "successCount": 0, + "quality": 0.8, + "successCount": 9, "failureCount": 0, "name": "mine_3_oak_logs", "description": "Mine 3 oak logs", - "keywords": [ - "mine", - "oak_log", - "wood" - ], - "file": "mine_3_oak_logs.js", - "embedding": [ - 0.005131506, - -0.0051548057, - 0.015245073, - -0.07322916, - 0.016444543, - -0.005528063, - 0.013040258, - 0.006741368, - 0.006709657, - 0.002164252, - 0.0075749718, - -0.003314564, - -0.015344543, - 0.012464394, - 0.12978213, - -0.020575661, - -0.0031427972, - -0.0026453645, - -0.011940645, - -0.017203398, - -0.020674078, - -0.019847311, - 0.00982395, - -0.0039128466, - -0.018186165, - 0.009730381, - 0.020176712, - 0.03113783, - 0.029476332, - -0.025846187, - -0.007800885, - 0.028641026, - 0.01086996, - -0.0073737586, - -0.004978028, - 0.033560794, - 0.005609148, - -0.009132333, - -0.010776952, - 0.00042511726, - -0.0047392393, - 0.011675265, - -0.0033181924, - -0.02743159, - 0.004378876, - 0.024876034, - -0.0065842154, - -0.011997699, - 0.0046605654, - 0.0038166938, - 0.012613575, - 0.017321922, - -0.013574086, - -0.18441209, - -0.0061685736, - -0.005190188, - -0.0043010856, - -0.0016152912, - 0.026274834, - -0.0024935252, - -0.012010024, - 0.021199876, - -0.015699096, - -0.01239909, - 0.008073763, - 0.0017239144, - 0.013419278, - -0.009477257, - -0.017016321, - -0.016030468, - -0.01885768, - 0.008662191, - -0.004075812, - -0.026192501, - -0.014249343, - -0.005063298, - 0.008016011, - 0.016306207, - -0.013922797, - 0.04909944, - -0.00048861536, - -0.03811055, - -0.022385318, - 0.008761238, - -0.0064039696, - -0.012303037, - -0.026517428, - -0.0020132167, - -0.026000505, - 0.009670772, - -0.012691701, - 0.014018345, - -0.009444521, - 0.025731765, - 0.013386281, - 0.0094491, - -0.00011687638, - -0.014515687, - -0.0031780512, - -0.0058361827, - -0.0054379893, - -0.005069839, - -0.010392767, - -0.016987577, - 0.011846502, - -0.021412931, - 0.009194403, - 0.0071254447, - -0.00505537, - 0.025774004, - -0.0077099665, - 0.0063623483, - 0.013540341, - 0.033402096, - 0.020328522, - -0.20049125, - 0.020024952, - -0.00028375848, - 0.003823142, - 0.011145131, - -0.010100994, - 0.009014058, - 0.0004659428, - 0.020659676, - 0.0034242992, - -0.00056538545, - -0.009908284, - 0.0036977648, - 0.005889959, - -0.0055439505, - 0.00071666844, - 0.0032151372, - 0.00042879663, - 0.017812457, - -0.008875856, - 0.022626976, - 0.0013279422, - -0.0071337665, - -0.021817284, - -0.007229595, - 0.002937048, - -0.0069808927, - -0.0020903593, - 0.0023896564, - -0.01583213, - -0.019870376, - 0.02451168, - 0.005631168, - -0.000012849572, - -0.013852372, - 0.0011379513, - -0.03524004, - 0.0027944879, - 0.0022801952, - -0.016027408, - -0.048669152, - -0.0044465605, - 0.008246575, - -0.022226565, - 0.0051552304, - 0.006191744, - 0.01272972, - 0.016570736, - 0.0037624033, - 0.009337511, - -0.026199814, - 0.012210156, - 0.0194091, - 0.013883566, - 0.010477285, - -0.0055814995, - -0.005864741, - 0.017248457, - -0.020281833, - 0.013055807, - -0.0034429368, - 0.010148242, - 0.005519428, - 0.02103722, - -0.001469078, - 0.007748158, - 0.0008706917, - -0.011269983, - -0.0023885588, - 0.002599965, - -0.019846857, - -0.011281436, - -0.028242916, - 0.02278589, - -0.011062294, - 0.0073098415, - -0.0022923353, - -0.017045325, - -0.019321205, - -0.01442919, - -0.012307629, - -0.00922931, - -0.012215075, - 0.030091237, - 0.00027238604, - 0.0015622963, - -0.001071935, - 0.0105687715, - 0.022496091, - 0.010702434, - 0.0107546635, - -0.004143351, - -0.01374456, - 0.0041548326, - 0.04140109, - 0.022500508, - -0.02783686, - -0.0060905116, - -0.005900197, - 0.0095738, - -0.017513338, - -0.0004105617, - 0.0036048912, - 0.023421176, - 0.016334107, - 0.006151361, - -0.006438607, - 0.041892037, - 0.001835564, - -0.00046155512, - 0.002845034, - -0.014402728, - -0.00072293036, - -0.011557863, - 0.026940629, - -0.006314081, - 0.0031619961, - -0.012825812, - -0.019183697, - -0.010409976, - -0.007720074, - -0.022055961, - 0.03476894, - 0.01775622, - -0.007942316, - 0.00055052905, - -0.027885534, - -0.0058611403, - -0.022570673, - 0.031323515, - -0.015079085, - -0.010356191, - 0.009802826, - -0.00071164133, - -0.025964636, - -0.0060926434, - 0.00019783097, - -0.0069628605, - -0.014399255, - 0.003116989, - -0.019332962 - ] + "keywords": [], + "file": "mine_3_oak_logs.js" }, { - "quality": 0.7, - "successCount": 0, + "quality": 0.9, + "successCount": 11, "failureCount": 0, "name": "craft_a_wooden_pickaxe", "description": "Craft a wooden pickaxe", @@ -6540,265 +340,7 @@ "pickaxe", "wood" ], - "file": "craft_a_wooden_pickaxe.js", - "embedding": [ - -0.014318617, - 0.021080563, - -0.003853364, - -0.056968395, - 0.0040929606, - 0.016758848, - 0.014086653, - 0.011995343, - -0.0040495517, - 0.0025909513, - -0.029997095, - -0.022623416, - 0.0041220593, - -0.0008040115, - 0.11522217, - -0.047511026, - 0.0066440036, - -0.0062191836, - -0.016341927, - -0.01881824, - 0.0021507284, - -0.001448919, - -0.004704279, - -0.021445407, - -0.007610717, - -0.0060899807, - 0.0003584436, - 0.024134044, - 0.026358588, - -0.007998863, - -0.012422549, - 0.025690474, - 0.011527525, - -0.006777541, - -0.003575911, - 0.03499377, - 0.008437869, - -0.009197508, - 0.000625123, - -0.0047386307, - -0.012360428, - 0.008866021, - -0.036512587, - -0.000073964235, - 0.013584913, - 0.013477237, - -0.0050994405, - -0.018893264, - 0.009768483, - 0.016994098, - 0.01663838, - 0.026502835, - -0.013034579, - -0.18830957, - -0.027138446, - 0.0049976013, - -0.012530427, - -0.0007968392, - 0.015585576, - -0.0019312821, - -0.011937167, - 0.022185642, - 0.0033332221, - -0.003455182, - 0.026605172, - 0.0061691464, - 0.0044336617, - -0.005765231, - -0.0026167943, - -0.0066365707, - 0.017218512, - -0.0010666681, - -0.0057730908, - -0.010532149, - -0.020655038, - -0.019575542, - -0.0027559025, - 0.014790932, - -0.017283618, - 0.060765274, - -0.013233039, - -0.03185097, - -0.014766455, - 0.00062507583, - -0.002953312, - -0.0037524085, - -0.009439188, - 0.0034380336, - -0.029434236, - -0.0070573813, - -0.01841563, - 0.01394073, - -0.007944365, - 0.036033664, - 0.026851224, - 0.005018153, - 0.0012044425, - -0.013238056, - -0.019503318, - 0.01303077, - -0.0011099847, - -0.014871315, - -0.0041542975, - -0.0068050013, - 0.01163655, - -0.03487999, - 0.0044729332, - 0.015877236, - -0.018055452, - 0.018865397, - 0.020454796, - 0.0069089686, - 0.0057029375, - 0.04106716, - 0.020980708, - -0.19569421, - 0.0075913253, - -0.0058414573, - 0.009731747, - -0.0039281123, - -0.0028543768, - 0.0044915774, - 0.00445546, - 0.031046255, - 0.00060829695, - 0.011427695, - -0.0017316124, - 0.006479315, - 0.009166111, - -0.008535977, - -0.010319924, - 0.0073213098, - -0.003806259, - 0.02318291, - 0.014693403, - 0.021239843, - -0.01689689, - -0.013922278, - -0.011451256, - -0.00030871446, - 0.0013103071, - -0.003730806, - 0.0022770795, - -0.00305434, - -0.013985605, - -0.022233004, - 0.012427702, - 0.019565925, - -0.015233261, - -0.0059779272, - 0.009978507, - -0.014222774, - 0.0033977102, - -0.022895603, - -0.038002312, - -0.029387731, - -0.0012274439, - 0.014195344, - -0.007889176, - 0.023111599, - -0.00016495497, - 0.01960931, - 0.021227999, - 0.009554233, - 0.0033499356, - -0.019920234, - 0.0031017803, - 0.0006855128, - 0.0075156298, - 0.00876359, - -0.010880337, - -0.0049741706, - 0.004204639, - 0.006942243, - 0.0111713, - 0.001354976, - 0.017446576, - -0.010111898, - 0.023159696, - 0.0113024255, - 0.023377446, - 0.019036124, - 0.0016053551, - -0.010815582, - 0.014321162, - -0.013473813, - -0.0140183065, - -0.01464653, - 0.030135583, - 0.007995126, - -0.009964036, - 0.015796052, - -0.01800443, - -0.01850101, - -0.0076565384, - -0.00058126, - -0.03399069, - -0.008438975, - 0.033803057, - 0.018869832, - 0.011886632, - -0.0097849, - 0.0056265728, - 0.0077404864, - 0.0033452825, - 0.011501479, - 0.0021769018, - -0.015598894, - 0.0029975518, - 0.033291698, - 0.0076618777, - -0.027886132, - -0.007569019, - 0.0013292617, - 0.014095154, - -0.009418564, - 0.0009537788, - 0.0022600468, - 0.02578976, - -0.0013190169, - -0.018539717, - 0.0049176514, - 0.030474544, - -0.000078913064, - -0.014434456, - 0.007819126, - -0.03244604, - -0.012043666, - -0.026876936, - 0.021733953, - -0.01367908, - -0.0015648766, - -0.018318042, - -0.01775652, - -0.012187076, - 0.0024568574, - -0.022209914, - 0.017159512, - 0.007921412, - -0.011506174, - 0.023761174, - -0.01848915, - -0.004083829, - -0.0063203117, - 0.020855272, - 0.0038448942, - -0.0002160605, - 0.012800388, - -0.0044753603, - -0.023511339, - -0.015294558, - 0.012231447, - -0.013674564, - -0.0048225997, - 0.016375529, - -0.020492483 - ] + "file": "craft_a_wooden_pickaxe.js" }, { "quality": 0.7, @@ -6812,265 +354,7 @@ "fuel", "progression" ], - "file": "mine_3_coalore.js", - "embedding": [ - 0.0034906934, - 0.015201778, - 0.04204883, - -0.07663066, - 0.006769177, - 0.021370253, - -0.011548117, - 0.0062422925, - 0.003851055, - 0.010114718, - 0.0073158974, - -0.0024360144, - -0.00044826537, - 0.01362009, - 0.11799556, - -0.0048340796, - -0.00040232608, - -0.012538582, - -0.0061657703, - 0.0016944272, - -0.014743747, - -0.0001357824, - -0.02007795, - -0.020733312, - -0.011616308, - -0.0075010764, - 0.044429384, - 0.04754907, - 0.027493306, - -0.016213035, - 0.00008896299, - 0.022638027, - 0.016195854, - -0.00080597063, - 0.001980485, - 0.023198387, - -0.010176639, - -0.024145665, - 0.0037459102, - 0.0066478592, - -0.024997989, - 0.025668362, - 0.005218221, - -0.004292739, - -0.0013536717, - -0.00043951286, - 0.0059728506, - -0.018116398, - -0.017520364, - 0.017440371, - 0.0090948595, - 0.009949818, - -0.008413719, - -0.20278628, - -0.026658652, - -0.0058896407, - 0.010264212, - -0.00067911827, - 0.017639643, - -0.002336896, - -0.013916616, - 0.028957207, - -0.021195432, - -0.027326101, - 0.0014439857, - 0.004719751, - 0.015085308, - -0.009936395, - -0.01462626, - -0.014110507, - -0.014433326, - 0.0014224931, - -0.006509251, - -0.033870332, - -0.0019054264, - -0.0034057652, - 0.008000792, - -0.0038519471, - -0.021382041, - 0.053178903, - 0.0014715889, - -0.03012897, - -0.0017739402, - 0.018921655, - -0.0012661775, - -0.009832966, - -0.003426928, - -0.0064741713, - -0.006588581, - 0.009214227, - -0.0069454233, - 0.027236173, - -0.0062415577, - 0.01229312, - 0.0057436246, - 0.0155246155, - 0.003949346, - -0.0037467938, - 0.0012105068, - -0.020275557, - 0.01608467, - -0.0046175066, - -0.00619607, - -0.008540851, - -0.00043325, - -0.021820659, - -0.004281454, - 0.007482954, - 0.0049252324, - 0.039831333, - -0.00075864536, - 0.020160655, - 0.018408306, - 0.018260766, - 0.026136713, - -0.17078172, - 0.012471513, - 0.0041266987, - 0.018516533, - 0.02086175, - -0.012653447, - 0.014003602, - 0.023774242, - 0.025956575, - 0.0048035625, - 0.009260433, - 0.00074133987, - -0.016527731, - 0.00006229701, - 0.010106021, - 0.006011791, - 0.0039718933, - 0.01497833, - 0.02405054, - -0.0055491826, - -0.0040553124, - 0.0018291391, - -0.034655303, - -0.015352246, - -0.039590638, - -0.006110027, - -0.010391394, - -0.0009816745, - -0.011758302, - -0.022634946, - -0.018592505, - 0.006522385, - 0.018029595, - -0.0040995465, - -0.028931824, - -0.02070354, - -0.019912153, - 0.009207764, - -0.0010714403, - 0.00079689955, - -0.043482777, - 0.00644644, - 0.01688394, - -0.026714442, - 0.0032026316, - -0.02706995, - 0.0018515618, - 0.022260917, - 0.009535943, - -0.0022529115, - -0.018223112, - 0.011598958, - 0.01978006, - 0.0053277, - 0.02696608, - 0.0022198819, - 0.0043609086, - -0.0060555153, - 0.0028031222, - 0.012149552, - 0.0012913755, - 0.013817089, - 0.0013082593, - 0.022579687, - -0.0042832107, - -0.005270665, - -0.0065138703, - -0.009561693, - 0.006705233, - 0.002354623, - -0.018733142, - 0.013736964, - -0.025252799, - 0.016744584, - -0.003221912, - -0.018298507, - 0.00094729906, - -0.018829735, - -0.0102792075, - -0.0037377207, - -0.019218026, - -0.016820004, - -0.016710166, - 0.010015342, - -0.0006555797, - -0.0057373256, - -0.00060917716, - -0.006434763, - -0.0033086152, - -0.0077866986, - 0.025609143, - 0.00013184229, - -0.024570122, - 0.00017284152, - 0.020922571, - -0.008640394, - -0.016558778, - -0.012085487, - 0.0045823883, - -0.0009598725, - -0.02242719, - -0.0171838, - 0.006588604, - -0.00017844961, - 0.00989589, - 0.0011812532, - 0.009848232, - 0.019853337, - 0.009126219, - -0.0012778762, - -0.009946758, - 0.008155377, - -0.0041233236, - -0.025438895, - 0.026250303, - -0.005535566, - -0.009042227, - -0.0085263485, - -0.0038081547, - -0.013804531, - -0.0048804265, - -0.006993354, - 0.02809386, - 0.016072014, - 0.0020642986, - -0.0045286557, - -0.018688709, - 0.0056775203, - -0.012052873, - 0.033234023, - -0.023916861, - 0.019706368, - -0.0039955815, - 0.021151403, - 0.002339344, - 0.006650187, - -0.007751844, - 0.01841399, - -0.00558985, - 0.0022884698, - -0.024399536 - ] + "file": "mine_3_coalore.js" }, { "quality": 0.7, @@ -7083,265 +367,7 @@ "walk", "north" ], - "file": "explore_50_blocks_to_the.js", - "embedding": [ - -0.027637772, - 0.0015465545, - 0.01782253, - -0.071421094, - 0.00048738948, - -0.0026412164, - 0.008519353, - 0.0053659254, - 0.022804711, - 0.017940741, - -0.0016224438, - -0.001802468, - -0.016936425, - 0.031513173, - 0.11793947, - -0.038359072, - -0.010130906, - -0.0014306171, - 0.015035021, - 0.01080549, - -0.003740766, - -0.028668271, - 0.020009754, - -0.046579808, - -0.003698486, - 0.0058630933, - 0.0016758105, - 0.022798186, - 0.02918512, - -0.021226024, - -0.008596014, - 0.026577892, - 0.017732164, - -0.00016228805, - 0.00092684233, - 0.039718695, - 0.009046551, - -0.014726945, - 0.010505305, - 0.008815208, - -0.023500917, - -0.007181649, - -0.008223521, - 0.0045169536, - -0.01021236, - 0.012131989, - -0.009948951, - -0.003881757, - -0.00017241707, - 0.016854776, - 0.022743132, - 0.019757504, - 0.01262292, - -0.20586112, - 0.008106032, - 0.010914157, - 0.00048951595, - 0.0066319536, - 0.015199991, - -0.023248913, - -0.009454699, - 0.038975935, - -0.03859176, - -0.02663486, - 0.009554309, - -0.0026809978, - 0.027011499, - -0.0006347904, - -0.046344426, - -0.013946428, - -0.009516758, - 0.003301405, - 0.016837478, - -0.025129255, - -0.014361947, - -0.013578905, - 0.0035302504, - 0.012816098, - -0.00621651, - 0.032441273, - -0.012274385, - -0.022117604, - -0.0047195666, - -0.015316191, - -0.00094851106, - -0.013139312, - -0.013470506, - -0.009928873, - -0.021791339, - 0.0064987377, - -0.023664508, - 0.030667784, - 0.008964705, - -0.0023929463, - 0.007335857, - 0.0163563, - -0.001345487, - -0.008463655, - -0.0035526177, - 0.012069699, - -0.020498028, - -0.013773766, - -0.010204619, - -0.030483905, - 0.015732657, - -0.01595422, - 0.0009751087, - -0.0019081446, - -0.020737655, - 0.002158011, - 0.011673282, - -0.017701771, - -0.02951343, - 0.010237227, - 0.009434817, - -0.18443549, - 0.0074124807, - -0.008931177, - 0.009119255, - 0.006933878, - -0.019512765, - -0.01861589, - 0.03624155, - -0.0030101782, - -0.0066228504, - 0.01152662, - 0.011089286, - -0.0035143073, - -0.009311701, - 0.00082967535, - 0.006026216, - 0.006634609, - 0.0037192726, - 0.017386168, - 0.0011746148, - 0.024176802, - -0.014996957, - -0.025304288, - -0.0066082133, - 0.0009040541, - 0.01290958, - 0.004474452, - -0.008755847, - 0.0135515025, - -0.01476039, - -0.0021320512, - -0.013647049, - 0.018357309, - 0.015219134, - -0.012611669, - -0.011621939, - -0.017378028, - 0.010482547, - -0.010450003, - 0.007162917, - -0.057263643, - 0.012745404, - -0.003808116, - -0.023403492, - 0.012649931, - 0.018248389, - -0.02622793, - 0.0057695145, - 0.013753375, - 0.019668438, - -0.00915468, - -0.013609534, - 0.0035847868, - 0.012895651, - 0.018092329, - -0.009943328, - -0.023768323, - -0.006477187, - -0.0055734655, - 0.0006014785, - -0.012833585, - 0.008647914, - 0.023177164, - -0.0004621717, - 0.0036920877, - 0.0047328747, - -0.009461882, - 0.0063229883, - 0.0045136083, - 0.0033901203, - -0.03227427, - -0.010565173, - 0.0046684556, - 0.008801887, - -0.015985955, - 0.0031308217, - 0.015647123, - -0.009597473, - -0.018877553, - -0.021484695, - -0.014537628, - 0.012521326, - -0.0040911897, - 0.02155731, - -0.0071604997, - -0.006949056, - -0.008703125, - 0.009668138, - -0.013998918, - 0.020954551, - -0.0017994816, - -0.005894527, - -0.01848376, - 0.016880095, - 0.020915167, - -0.001690972, - -0.009989652, - 0.024595946, - -0.014244648, - 0.018994212, - -0.022378616, - -0.0028972516, - 0.014787277, - -0.011592843, - -0.019240234, - 0.009090544, - -0.0071708085, - -0.0047608577, - 0.019952256, - -0.009991464, - -0.02403809, - -0.0057875863, - 0.0007272079, - -0.0063826754, - 0.022528006, - 0.0009075607, - 0.017700532, - -0.0024111, - -0.039995108, - -0.000004058625, - -0.00083507854, - -0.0038159066, - 0.0062257573, - 0.016489582, - -0.005933578, - -0.013038762, - -0.025250893, - 0.013137137, - -0.012000224, - 0.015807685, - -0.0058599846, - 0.02003951, - 0.033117052, - -0.016760504, - -0.020719822, - 0.010675782, - -0.016742766, - 0.011002334, - -0.012151092, - -0.015856234, - 0.005397211 - ] + "file": "explore_50_blocks_to_the.js" }, { "quality": 0.7, @@ -7354,265 +380,7 @@ "stone", "cobblestone" ], - "file": "mine_3_cobblestone.js", - "embedding": [ - -0.007606252, - -0.0019644208, - 0.029766371, - -0.067044154, - 0.006232344, - 0.01006985, - -0.0030924717, - -0.0050215493, - -0.007888938, - 0.010624454, - 0.0051671565, - 0.0028422102, - 0.004479267, - 0.0018010007, - 0.11698651, - -0.0131390495, - -0.022589026, - -0.009591301, - -0.009679532, - -0.0066260747, - -0.011857783, - -0.009920511, - -0.0098780645, - -0.007417455, - -0.0067934575, - -0.01220277, - 0.039012905, - 0.05367313, - 0.012237227, - -0.024820244, - 0.003515047, - 0.027484834, - 0.004926558, - -0.012252981, - 0.012217823, - 0.023462795, - -0.001117154, - -0.012432246, - -0.011213483, - 0.011810715, - -0.00021268093, - 0.022277594, - 0.004463689, - -0.005375047, - 0.009659905, - 0.0066962927, - -0.0076001594, - -0.0200996, - -0.009283259, - 0.010539257, - 0.029761057, - 0.024687866, - -0.008304559, - -0.1969949, - -0.01510071, - -0.010706794, - 0.0055894754, - -0.0059412713, - 0.022972053, - -0.002117772, - -0.0008046178, - 0.03946949, - -0.0158208, - -0.037143864, - 0.014298866, - 0.010182896, - 0.020309662, - 0.0012290064, - -0.025383255, - -0.025835577, - -0.017024966, - 0.008724133, - 0.008371411, - -0.022782354, - -0.01713761, - 0.0049174107, - 0.009710865, - -0.010170629, - -0.023689555, - 0.06920729, - 0.004919332, - -0.027585704, - -0.0026261134, - 0.013427872, - 0.01179756, - 0.005278171, - -0.013013481, - 0.012983692, - -0.023011288, - -0.00028391235, - -0.012277231, - 0.022927053, - 0.007827505, - 0.005674471, - -0.0016118952, - 0.000520697, - -0.0024484145, - -0.00015253165, - 0.006068271, - -0.0071656825, - 0.005822695, - 0.0014885934, - -0.011517976, - -0.009773461, - 0.020851258, - -0.026428794, - -0.0020462899, - 0.0056925537, - -0.0051545105, - 0.04493796, - 0.005854063, - 0.0034126064, - 0.010480777, - 0.043084044, - 0.036791697, - -0.17548048, - 0.007323885, - -0.00041915677, - 0.0006993871, - 0.01260842, - -0.01054359, - 0.01601252, - -0.001082025, - 0.036028076, - 0.009377265, - 0.030452173, - -0.008396152, - 0.0003855633, - -0.003678239, - 0.0005754639, - -0.004405881, - 0.02362305, - 0.0020099676, - 0.003980824, - -0.006786891, - -0.0031354814, - 0.0027557411, - -0.018171333, - -0.03744341, - 0.00013482556, - -0.0039191605, - -0.002520539, - -0.012053473, - 0.0023875486, - 0.004443505, - 0.0036201167, - 0.017922064, - 0.016990742, - -0.0097767105, - -0.024862152, - -0.007064754, - -0.03112032, - 0.006241731, - -0.0071574966, - -0.019992251, - -0.044975102, - -0.004349079, - 0.009928799, - -0.008509205, - 0.0045261486, - 0.008172796, - 0.0042945095, - 0.017870825, - 0.01792528, - 0.0029189829, - -0.014624963, - 0.0051011425, - 0.012772132, - 0.01970239, - 0.01578639, - 0.008290481, - -0.00014589516, - -0.010547674, - -0.015218898, - 0.002621195, - -0.0059117507, - 0.01847512, - -0.0033248875, - 0.03436081, - -0.0028435395, - 0.011010813, - 0.011731747, - -0.014715174, - -0.023014966, - 0.0013950557, - -0.021130707, - 0.00036972403, - -0.023946045, - 0.026500026, - -0.0077478006, - -0.0031290827, - 0.004340944, - -0.020753589, - -0.017046746, - -0.001992479, - -0.022674445, - -0.025837682, - -0.019837935, - 0.015116207, - -0.021655776, - 0.00009818139, - 0.0016613848, - 0.016330699, - -0.0065189735, - -0.0045064017, - 0.017021121, - -0.0012752572, - -0.015469628, - 0.008901575, - 0.030758042, - 0.0031289188, - -0.015525917, - 0.00023571623, - 0.0008003749, - 0.006110312, - -0.01859918, - -0.008423254, - 0.008760737, - 0.006577011, - 0.008884738, - -0.013756247, - 0.008366885, - 0.02386144, - 0.007898267, - -0.0031227164, - -0.01740627, - -0.0019478113, - 0.022205256, - -0.015773922, - 0.015340604, - 0.0024863777, - -0.009764217, - -0.026266102, - -0.007840651, - -0.020624183, - -0.0055992147, - -0.006129968, - 0.02067904, - 0.020895787, - -0.021803007, - 0.0071421033, - -0.010300781, - 0.0010961987, - -0.007385858, - 0.024393925, - -0.0042913556, - 0.0114693, - 0.00800468, - 0.0036707562, - -0.025196193, - -0.01357225, - -0.014572449, - -0.00941522, - -0.013782969, - 0.003086561, - -0.017906675 - ] + "file": "mine_3_cobblestone.js" }, { "quality": 0.7, @@ -7625,269 +393,11 @@ "all", "tasks" ], - "file": "stop_all_tasks.js", - "embedding": [ - 0.006423738, - 0.015399955, - 0.019585917, - -0.06731375, - 0.0046966635, - -0.005931602, - 0.0017918458, - 0.005419165, - 0.013375456, - -0.019974455, - -0.00066946953, - -0.0061393175, - 0.0020341123, - -0.0062225345, - 0.123323046, - -0.01588403, - 0.012719015, - 0.00073459535, - -0.0033509145, - -0.0039961888, - -0.010164118, - -0.011103092, - 0.009324899, - 0.002054993, - 0.00063277327, - 0.0009597253, - 0.018059403, - 0.018338919, - 0.029646082, - 0.007581941, - -0.008161025, - 0.009996196, - 0.0002875737, - 0.027221749, - -0.008808014, - 0.021423498, - -0.0013277805, - 0.004277765, - 0.00055213, - 0.013736374, - -0.0073687434, - -0.007921974, - 0.002782938, - 0.0032246904, - -0.0010625215, - 0.0077871294, - 0.009392101, - -0.024438934, - -0.0025760217, - 0.016147476, - 0.01426706, - -0.006923638, - -0.01780081, - -0.22669666, - -0.011487639, - 0.004192976, - 0.0028604702, - -0.0002189476, - 0.0080785025, - -0.0119467955, - -0.02387521, - 0.0024904262, - 0.013586639, - -0.005031433, - -0.0046496266, - 0.006579315, - 0.051411115, - 0.011355134, - -0.029153466, - 0.014880613, - 0.0026422574, - 0.0040187356, - -0.012641269, - -0.01746207, - -0.016829045, - -0.040003896, - 0.013320926, - -0.0054721525, - 0.0041698515, - 0.019805675, - 0.0014168092, - -0.028466197, - 0.0055090454, - -0.018269405, - -0.020177012, - -0.015599654, - -0.009846668, - -0.0077371574, - -0.004659844, - 0.006534013, - -0.011022292, - 0.010151819, - -0.0034579062, - 0.011530511, - 0.025350116, - 0.02352081, - -0.005632753, - 0.0071106707, - -0.015503381, - -0.006517253, - -0.01053925, - -0.011763219, - 0.0020713622, - -0.002956243, - 0.012522889, - -0.013738077, - 0.019478327, - 0.0028786778, - 0.0036346528, - 0.0067252764, - 0.029089974, - -0.005442016, - 0.004354719, - -0.0033003814, - -0.013351596, - -0.18057685, - -0.007104319, - -0.0029398995, - -0.011208765, - -0.009138224, - -0.009704093, - -0.012813801, - -0.01265996, - -0.0076230345, - 0.016058847, - 0.002600344, - 0.009392475, - 0.011816085, - 0.011459722, - -0.006877413, - -0.0067714173, - -0.011624116, - 0.0062242555, - 0.0053189993, - -0.022387037, - 0.017626615, - -0.00791456, - -0.021587463, - -0.01785259, - -0.016385056, - -0.0011113912, - 0.025004279, - 0.018876096, - -0.007411658, - -0.0026317427, - -0.011917281, - 0.0076181567, - 0.00581886, - -0.0014505343, - -0.023006683, - 0.0066244206, - -0.018355038, - 0.0012736737, - -0.009225488, - 0.023619635, - -0.02764777, - 0.0010775198, - -0.0012579558, - 0.005844379, - -0.002252429, - 0.009568455, - -0.006103976, - -0.0062955054, - 0.004484109, - 0.003372785, - -0.002846948, - -0.0019112151, - 0.004930289, - 0.0077132257, - 0.008955023, - 0.0009329022, - -0.024073428, - -0.0042757574, - 0.0010288912, - -0.0105861835, - -0.013861556, - -0.007995903, - -0.006592326, - -0.0037954785, - -0.0070057116, - 0.023790773, - 0.00082256127, - 0.001509419, - 0.014066246, - -0.03425431, - -0.0026682434, - -0.013039082, - -0.0076535125, - -0.010595394, - -0.014320657, - 0.011819758, - 0.017550776, - 0.0031867884, - -0.029180046, - -0.011012598, - -0.016696256, - 0.023970848, - -0.009729886, - 0.009202819, - 0.009884547, - 0.00045775587, - -0.0109650865, - 0.0007733319, - -0.021134496, - 0.008295897, - -0.010645222, - 0.013504332, - -0.0014805413, - -0.008488019, - 0.018443894, - -0.0149141615, - -0.017957143, - 0.009891556, - 0.0095354235, - 0.022029055, - 0.000535884, - -0.01747856, - 0.030466957, - 0.019022422, - 0.0068816156, - 0.01295679, - 0.012152027, - 0.009362021, - 0.0008539943, - -0.004119128, - -0.01375897, - 0.012295482, - -0.0075314655, - 0.021176128, - 0.0030510589, - 0.007550897, - -0.009092235, - -0.01144434, - -0.007359905, - -0.02094557, - 0.013721813, - -0.000114033566, - 0.022246785, - 0.0088559985, - 0.015059006, - -0.005099165, - -0.0016364899, - 0.0055441353, - -0.01298777, - 0.0012880765, - -0.0031748866, - 0.016690725, - 0.008542026, - -0.007922042, - -0.009883133, - -0.005792569, - 0.017274383, - 0.013638516, - 0.0010954136, - -0.00268474, - -0.013584636 - ] + "file": "stop_all_tasks.js" }, { "quality": 0.8, - "successCount": 1, + "successCount": 2, "failureCount": 0, "name": "craft_a_stone_sword", "description": "Craft a stone sword", @@ -7896,265 +406,7 @@ "stone_sword", "tool" ], - "file": "craft_a_stone_sword.js", - "embedding": [ - -0.015731392, - 0.008901211, - 0.020461673, - -0.06612928, - 0.0024220764, - 0.0025286595, - -0.004658226, - 0.02368682, - -0.007682408, - 0.016647262, - -0.028130371, - -0.008124534, - 0.0020613521, - -0.004091113, - 0.10946224, - -0.03783722, - -0.009857614, - -0.009155247, - -0.025134552, - -0.018610856, - -0.010038007, - -0.0024060088, - -0.013974931, - -0.04570744, - -0.0059625236, - 0.0030719037, - 0.0132397, - 0.026894977, - 0.0084953625, - -0.020071225, - -0.00073867035, - 0.019089485, - 0.005751773, - -0.0048176623, - 0.0113218585, - 0.01807248, - 0.0005634285, - -0.01321403, - -0.0048360773, - 0.00090835075, - -0.0071485927, - 0.026507327, - -0.026918912, - 0.0001356233, - 0.021098506, - 0.024326999, - -0.009526465, - -0.021688666, - 0.0041224565, - 0.027236389, - 0.032256447, - 0.032080118, - -0.020052044, - -0.18943784, - -0.009318251, - 0.00042940286, - -0.00012594351, - -0.021718105, - 0.010971615, - 0.0018005975, - -0.0047497996, - 0.029441236, - -0.0012946933, - -0.017311294, - 0.01844383, - 0.010106747, - 0.016926538, - -0.012895857, - -0.035512455, - -0.007179429, - 0.011132266, - 0.008688322, - -0.003502805, - -0.016788919, - -0.02240976, - -0.023213053, - 0.0058111562, - 0.0077492287, - -0.0075212345, - 0.049455307, - -0.01798474, - -0.047792718, - -0.0033078622, - -0.0062506706, - 0.00041255582, - -0.0006243468, - 0.00012969588, - 0.020901335, - -0.016060252, - 0.007405329, - -0.010420454, - 0.007456676, - -0.007952794, - 0.035569232, - 0.027697824, - -0.0005948042, - 0.0008514743, - 0.00018713916, - -0.001688367, - 0.01562139, - -0.017713979, - -0.0068173036, - -0.0023416018, - 0.020619666, - 0.014697829, - -0.050239634, - -0.017756242, - 0.025579827, - -0.018302519, - 0.011738845, - 0.031032689, - 0.0059499084, - -0.010277026, - 0.026266426, - 0.015501658, - -0.18473212, - 0.0045967996, - -0.00027455256, - 0.014094613, - 0.006321096, - -0.0059828353, - 0.00821729, - -0.02306921, - 0.037799504, - 0.0128157595, - 0.022088284, - -0.008233171, - -0.0070587746, - -0.007079553, - -0.001973393, - -0.009306038, - -0.0009180492, - -0.0027695955, - 0.01961857, - 0.009733649, - 0.021197831, - -0.01644414, - 0.0012782499, - -0.008104971, - -0.0048924596, - 0.0014527491, - -0.01284929, - -0.006668756, - 0.0031261956, - -0.0140333865, - -0.011352261, - 0.0016776405, - 0.019580428, - -0.0087142605, - -0.037930742, - -0.0022000263, - -0.016234575, - 0.014131487, - -0.02740275, - -0.02411785, - -0.03981599, - -0.011900591, - 0.004735632, - -0.009371215, - 0.008579712, - 0.008182781, - 0.0073083336, - 0.02023164, - 0.022232192, - -0.013246999, - -0.024741387, - -0.0016428119, - -0.010531734, - -0.0059322575, - 0.029346071, - -0.009653051, - 0.006120166, - -0.014080638, - 0.0048331623, - -0.009109214, - -0.0073612113, - -0.002345924, - 0.0008523778, - -0.0017585478, - 0.011716675, - 0.01842421, - 0.01204961, - 0.00685682, - -0.010517794, - -0.00018884757, - -0.01805307, - -0.0053914106, - -0.027672319, - 0.03591114, - -0.011333663, - 0.010983257, - 0.0065321038, - -0.0055260384, - -0.011276277, - -0.012186348, - -0.013189474, - -0.023808863, - -0.02406923, - 0.029568756, - -0.002682524, - 0.0053028287, - -0.007199784, - 0.019109018, - -0.008909403, - -0.0020070204, - 0.0049229474, - -0.0069980593, - -0.021195643, - 0.004404859, - 0.018162139, - 0.00052192865, - -0.025557451, - 0.00033470136, - 0.0026188425, - 0.010399455, - 0.004740267, - 0.0052501825, - -0.0028575473, - 0.0067401305, - 0.006253363, - -0.0074435608, - 0.007918115, - 0.031373866, - -0.007984171, - -0.010339782, - -0.0021316493, - -0.031995323, - -0.0066003897, - -0.013790283, - 0.009522935, - 0.0036238614, - -0.015865188, - -0.010635897, - 0.0013257447, - -0.0031571493, - 0.000502047, - -0.011922109, - 0.011096195, - 0.012943646, - 0.0011120437, - 0.012354941, - -0.0049421913, - 0.007944104, - 0.0005601943, - 0.023661355, - 0.012045715, - 0.016241174, - 0.013062598, - -0.012926658, - -0.025601974, - -0.004134283, - 0.009555346, - -0.006751729, - 0.0057976586, - 0.013701536, - -0.007564337 - ] + "file": "craft_a_stone_sword.js" }, { "quality": 0.8, @@ -8167,265 +419,7 @@ "birch_log", "wood" ], - "file": "mine_1_birch_log.js", - "embedding": [ - -0.0013793138, - 0.013500596, - 0.0023222212, - -0.08869637, - -0.007886724, - 0.023096425, - 0.016549433, - -0.0127690565, - -0.0057354514, - 0.013829521, - -0.00012988645, - -0.01885954, - 0.010337604, - 0.01398871, - 0.12392597, - -0.021413058, - 0.012527578, - -0.02777283, - -0.0062056878, - -0.021300776, - -0.011741833, - -0.013489776, - 0.023422861, - -0.016896605, - -0.016741933, - -0.005314341, - 0.021058807, - 0.02736793, - 0.028042795, - -0.029229738, - -0.004815102, - 0.02584896, - 0.007328685, - -0.014848795, - -0.0032708389, - 0.03609772, - 0.0042559565, - -0.009601248, - -0.017695013, - 0.0026546805, - -0.007603289, - 0.02273289, - -0.0013449171, - -0.0147102345, - -0.00030656165, - 0.016479822, - -0.002594321, - -0.006944085, - -0.008382176, - 0.013295781, - -0.0061488133, - 0.027385019, - -0.015844459, - -0.19203241, - -0.008348419, - 0.010007467, - -0.013233217, - 0.0008862855, - 0.021377983, - -0.0031125743, - -0.013071625, - 0.012848917, - -0.014220705, - -0.02085014, - -0.008066196, - -0.004807334, - 0.020743538, - -0.0021671373, - -0.008351686, - -0.02879834, - -0.009000611, - 0.010925796, - -0.020920366, - -0.031735748, - -0.01291391, - -0.005870784, - -0.007399986, - 0.0020055664, - -0.016990352, - 0.04770857, - -0.01597468, - -0.019994747, - -0.008068923, - 0.00288192, - -0.00013669011, - 0.0040580197, - -0.030825553, - -0.012798093, - -0.026292669, - 0.007901634, - 0.0004174272, - 0.02910917, - -0.023272602, - 0.0075506326, - 0.0060196556, - 0.011468309, - -0.0069460557, - -0.00041783872, - 0.014923368, - -0.0065928395, - -0.0014858135, - -0.0143769, - 0.003520268, - -0.009891475, - 0.018236361, - -0.027121874, - 0.018036349, - 0.007573263, - -0.005967694, - 0.014169473, - -0.0003219145, - -0.001833271, - 0.01788097, - 0.021668954, - 0.020899357, - -0.18745361, - 0.020136686, - -0.008060468, - 0.0013189774, - 0.00083393476, - -0.006070121, - 0.010265885, - 0.012204578, - 0.011596287, - -0.008214338, - 0.0057795583, - 0.008219646, - 0.0023946196, - 0.023573097, - -0.011626847, - -0.018375888, - 0.008589577, - -0.005591386, - 0.014411307, - -0.0028340847, - 0.006185145, - -0.0055995923, - -0.0006124237, - 0.00395051, - -0.009719565, - 0.010341208, - -0.011819808, - -0.0017806275, - -0.0070020896, - -0.014658164, - 0.00055623415, - 0.035452213, - -0.009068129, - -0.02455556, - -0.0133283315, - -0.004409905, - -0.027766723, - 0.007190244, - -0.007187967, - -0.013335885, - -0.047583222, - 0.006674448, - -0.0020206855, - -0.035492316, - 0.0094019, - 0.00029848263, - 0.0039546047, - 0.012062726, - 0.010408044, - 0.0060196384, - 0.0006306109, - 0.0050799227, - 0.030087337, - 0.016969921, - 0.018960902, - -0.0039542387, - -0.017648071, - 0.02479092, - -0.017561963, - 0.024301969, - 0.008069681, - 0.0072224173, - -0.0064781616, - 0.016207265, - 0.018405704, - 0.012682439, - -0.018378476, - -0.020075047, - -0.00046739395, - 0.006589551, - -0.0034488847, - -0.00065716926, - -0.021474104, - 0.036155324, - -0.003132128, - 0.013633293, - 0.008199218, - -0.0024240743, - -0.030366896, - -0.0039933226, - -0.0066528837, - -0.027493885, - -0.0016646779, - 0.022357762, - 0.0033118892, - -0.008878335, - -0.009631753, - 0.0044183014, - 0.0028651382, - -0.003748383, - 0.011434162, - 0.0011595231, - -0.012992431, - 0.009310178, - 0.041615013, - 0.014311014, - -0.02915195, - -0.011561456, - 0.001867002, - 0.015675768, - -0.010645436, - -0.01788963, - 0.0062904437, - 0.028547226, - 0.0121323345, - -0.0056208563, - -0.02423693, - 0.02801685, - 0.012586369, - 0.0074763703, - -0.006527853, - -0.0017362608, - -0.008212773, - -0.022025213, - 0.0290201, - -0.02117996, - -0.00093582243, - -0.013701428, - -0.031404097, - -0.0071243076, - -0.016612569, - -0.020345058, - 0.018121962, - 0.010246727, - -0.009813027, - -0.0062800446, - -0.03036519, - -0.014460015, - -0.010733547, - 0.0067028496, - 0.0016153797, - -0.0030742073, - 0.026857948, - 0.0072117727, - -0.014506363, - -0.0013034089, - -0.0036424717, - -0.0036099034, - -0.000073712465, - 0.0152402, - -0.018432416 - ] + "file": "mine_1_birch_log.js" }, { "quality": 0.8, @@ -8438,5139 +432,262 @@ "stone", "furnace" ], - "file": "mine_7_stone_blocks.js", - "embedding": [ - 0.00006698517, - -0.00003475976, - 0.019953521, - -0.08131006, - -0.0005169208, - -0.00781688, - 0.009122132, - 0.008020209, - 0.013408101, - 0.023053959, - -0.006214239, - -0.020915292, - 0.0099469265, - 0.007980221, - 0.11843465, - 0.0037509212, - -0.020925405, - -0.018503811, - -0.019575596, - -0.00657157, - -0.027799934, - -0.0028751362, - 0.007435173, - -0.0346319, - -0.018087156, - 0.006736181, - 0.020317849, - 0.040652003, - 0.02463551, - -0.03772435, - -0.0062150764, - 0.011525795, - 0.01312635, - -0.005173475, - 0.020496018, - 0.022322338, - -0.012009699, - -0.0045665195, - 0.0034193154, - 0.009035379, - 0.007603328, - 0.010424722, - -0.0129011255, - 0.0008392291, - -0.025408074, - 0.0034587162, - 0.010323606, - -0.008431979, - -0.003176141, - 0.016792756, - 0.024992755, - 0.015336525, - -0.021772778, - -0.17575477, - -0.014664952, - 0.0084915375, - 0.015076322, - 0.012051089, - 0.031025399, - -0.009585336, - 0.012782592, - 0.05697709, - -0.0051252884, - -0.016369184, - 0.023948522, - 0.016740158, - 0.023617355, - -0.014871845, - -0.011170919, - -0.035310727, - -0.029917004, - -0.0030207813, - 0.00086490205, - -0.015868576, - -0.016381163, - -0.0154991895, - -0.00940111, - -0.001535947, - -0.0054675736, - 0.037917893, - -0.0030340625, - -0.026684737, - -0.0088787805, - -0.0006357071, - 0.001577648, - -0.014877403, - -0.010938666, - 0.0072918395, - -0.014836801, - -0.008634629, - -0.017083291, - 0.025876025, - 0.00525435, - 0.0061448235, - 0.005613348, - 0.0012019122, - -0.004590623, - 0.009758584, - 0.008140786, - 0.005761077, - -0.021324622, - -0.012670651, - -0.005990664, - -0.0011070588, - 0.0068371817, - -0.021523096, - -0.003994439, - 0.0038728288, - -0.010295111, - 0.004782664, - 0.010120144, - 0.018837119, - 0.000025988156, - 0.025688605, - 0.009336145, - -0.166896, - 0.015929094, - 0.016110893, - 0.009133247, - -0.00024664646, - 0.0030303549, - 0.01002042, - 0.0062941327, - 0.020598723, - 0.005849465, - 0.025558025, - 0.015030315, - -0.008385499, - -0.013107055, - 0.0006085056, - -0.01671396, - 0.008769408, - -0.0007952493, - 0.01943895, - -0.00042918633, - 0.01276004, - 0.006757317, - -0.012678796, - -0.02983543, - -0.0055249194, - 0.009043293, - 0.0017963299, - -0.012256488, - 0.004033557, - -0.021300325, - -0.007357006, - 0.022948276, - -0.0022270037, - -0.021956693, - -0.02867392, - -0.0103802, - -0.035280805, - 0.000063190426, - -0.022369765, - -0.0049996683, - -0.06273484, - -0.009378585, - 0.0069472273, - -0.015398262, - 0.0054806285, - 0.0119588645, - 0.0126286335, - 0.008006573, - 0.0224817, - -0.005283364, - -0.012270118, - -0.0011194636, - 0.016799254, - 0.0041456306, - 0.02178119, - 0.012122365, - -0.015175482, - 0.009778575, - -0.012251997, - 0.01902194, - 0.0018194044, - 0.0058185537, - -0.010606977, - 0.020743163, - 0.014661073, - 0.015953453, - 0.0064227753, - -0.00432018, - -0.021381615, - 0.00036921873, - -0.021683378, - -0.008228348, - -0.024610164, - 0.011505492, - -0.0046402407, - -0.012312664, - -0.0062830374, - -0.004970778, - -0.018155271, - -0.01925944, - -0.0325294, - -0.021547275, - -0.006425981, - 0.007079003, - -0.01659138, - -0.0095662335, - 0.0026949998, - -0.0036394498, - 0.004638701, - -0.009098159, - 0.012085669, - -0.01433205, - -0.002463788, - 0.02149498, - 0.036568597, - 0.011363077, - -0.022048738, - 0.0067638606, - 0.007314535, - 0.000051156407, - -0.016842214, - -0.0004469685, - 0.0185465, - 0.032045748, - 0.0013147322, - -0.009172044, - 0.0011502597, - 0.008610078, - 0.0065024537, - -0.007282389, - -0.034139965, - -0.0009057617, - -0.0017235624, - 0.008104159, - 0.025113909, - 0.02460676, - -0.01389704, - 0.0038961356, - -0.026987128, - -0.007911688, - -0.0063290023, - -0.014985135, - 0.028815232, - 0.0056111775, - -0.014857522, - -0.019656193, - -0.013672199, - 0.024306193, - 0.0032212364, - 0.036049962, - -0.009966131, - 0.006251273, - 0.021538364, - -0.01968976, - -0.031747986, - -0.007984238, - -0.0117741, - -0.0064522945, - -0.0037688764, - 0.0046988437, - -0.010185451 - ] + "file": "mine_7_stone_blocks.js" }, { "quality": 0.8, - "successCount": 1, + "successCount": 5, "failureCount": 0, "name": "mine_3_coal_ore_at", - "description": "Mine 3 coal ore at 867, 37, 285", + "description": "Mine 3 coal ore at 907, 76, 404", "keywords": [ + "mine", "coal", - "mining", - "fuel" + "resource" ], - "file": "mine_3_coal_ore_at.js", - "embedding": [ - -0.013765679, - 0.0013356627, - 0.026693033, - -0.08641005, - -0.0022852921, - -0.0020361247, - 0.006963494, - 0.0046094577, - 0.002911113, - 0.008963938, - -0.0043643694, - -0.0077165277, - -0.0026056387, - 0.004812554, - 0.12579177, - -0.018599639, - -0.002820685, - -0.012015205, - -0.019499652, - 0.015694525, - -0.020455565, - -0.008941191, - -0.009478029, - -0.03188989, - -0.0060236645, - 0.0047641597, - 0.031031039, - 0.03155506, - 0.034657884, - -0.012744425, - 0.010089534, - 0.030812403, - 0.0009020748, - 0.0005958947, - 0.0066904034, - 0.021357149, - 0.00053661445, - -0.0062404405, - 0.010122465, - 0.020303668, - -0.011698072, - 0.008517313, - -0.0041528395, - -0.0049871705, - -0.0060414323, - 0.014109483, - 0.006542781, - -0.027285263, - -0.009723092, - 0.018790202, - 0.021265622, - 0.013756493, - -0.024496429, - -0.19927351, - -0.01787235, - -0.006927883, - 0.011689555, - -0.005763139, - 0.022575274, - 0.008575426, - -0.0040466734, - 0.041436322, - -0.01770408, - -0.032492597, - -0.0034190125, - 0.007566056, - 0.0078083635, - 0.0018417056, - -0.019190382, - -0.014461065, - -0.02876756, - 0.009520785, - -0.0017986974, - -0.03499759, - -0.00787515, - -0.0015695033, - 0.0036355173, - -0.0023418856, - -0.017464148, - 0.040251914, - -0.0118000675, - -0.034590542, - -0.0137015525, - 0.008299529, - -0.015934607, - -0.010498405, - -0.017838301, - -0.008349145, - -0.0021348742, - 0.011483596, - -0.010277996, - 0.014584276, - 0.0033517797, - 0.009227359, - 0.020133773, - 0.0061505805, - -0.0012846197, - -0.008622561, - 0.006738797, - -0.0106327105, - -0.0082853, - -0.011102085, - -0.00966579, - 0.008034723, - 0.006408993, - -0.01442392, - 0.0028178098, - 0.02195938, - -0.010512463, - 0.025760442, - 0.009684752, - 0.024292585, - 0.009950389, - 0.017460383, - 0.022772072, - -0.17839946, - 0.014074291, - -0.0052401125, - 0.019603599, - 0.025488295, - -0.022139382, - 0.0003470112, - -0.0040616486, - 0.011433714, - -0.004444677, - 0.015190031, - -0.001041902, - -0.021385526, - -0.004731454, - 0.010584998, - 0.013099351, - 0.005096912, - 0.015528389, - 0.026484737, - -0.0047520576, - 0.01460521, - -0.0059947926, - -0.01214108, - -0.021237537, - -0.018991115, - -0.021635644, - 0.009280025, - -0.0015261278, - 0.006679773, - -0.03076734, - -0.018765762, - 0.009809553, - 0.021568641, - -0.0057407757, - -0.02144422, - -0.019778242, - -0.034192204, - -0.0015509223, - -0.012593734, - 0.012313263, - -0.04662419, - 0.013336462, - 0.008636937, - -0.009126804, - 0.01571978, - -0.011630488, - 0.0034992099, - 0.01612994, - 0.0056768344, - 0.0032483134, - -0.020657241, - 0.00381959, - 0.0017633118, - -0.00036265713, - 0.010764197, - 0.0040746173, - -0.013179718, - 0.014634275, - -0.010297272, - 0.0045425775, - -0.005605789, - 0.023155622, - 0.005540015, - 0.024114741, - 0.0140959285, - 0.0015592341, - -0.008128173, - -0.0013536402, - 0.001967696, - -0.018545307, - -0.019088926, - -0.0017141507, - -0.028127858, - 0.015962616, - -0.0023781648, - -0.0022314487, - 0.00503852, - -0.0038642443, - -0.007982321, - 0.001820705, - -0.020851381, - -0.033114187, - -0.01646653, - 0.004296811, - -0.0077427346, - 0.00019673383, - -0.008355649, - -0.007904279, - -0.0006444557, - -0.01655522, - 0.025729584, - -0.01145443, - -0.015881432, - -0.0032703828, - 0.029843224, - 0.0068270043, - -0.023267824, - -0.006770271, - 0.011875188, - 0.0043778373, - -0.029376324, - -0.004224327, - 0.011455221, - -0.0054127746, - -0.009085299, - -0.0051284144, - 0.006291693, - 0.027605787, - 0.010460428, - 0.0017967495, - -0.006194184, - 0.0045840633, - 0.023032589, - -0.029532854, - 0.018272502, - -0.0037804793, - -0.0013585477, - 0.009939768, - -0.017249072, - -0.017698763, - -0.016537081, - -0.0021957385, - 0.022028446, - 0.012894569, - -0.0131941335, - 0.00071516517, - -0.018717865, - 0.0071017975, - -0.016126316, - 0.035564873, - -0.027561123, - 0.022323659, - 0.001985389, - 0.013571487, - -0.0122763505, - 0.011770051, - -0.008004352, - 0.009787064, - -0.008826093, - -0.012057822, - -0.0045513776 - ] + "file": "mine_3_coal_ore_at.js" + }, + { + "quality": 0.7200000000000001, + "successCount": 6, + "failureCount": 1, + "name": "mine_3_iron_ore_at", + "description": "Mine 3 iron ore at 821,", + "keywords": [], + "file": "mine_3_iron_ore_at.js" }, { "quality": 0.8, "successCount": 1, "failureCount": 0, - "name": "mine3blocksofgrass", - "description": "mine_3_blocks_of_grass", + "name": "mine_3_copper_ore_blocks", + "description": "Mine 3 copper ore blocks", "keywords": [ "mine", - "grass", - "seeds" + "copper", + "builder" ], - "file": "mine3blocksofgrass.js", - "embedding": [ - -0.0152653605, - -0.014330846, - 0.007875608, - -0.04699456, - 0.018967541, - 0.012309512, - -0.002645602, - 0.002193055, - 0.00189791, - 0.018809354, - 0.009051825, - -0.009076121, - -0.0060064234, - 0.03220253, - 0.12695779, - -0.005795882, - -0.01513343, - -0.028185, - -0.009785632, - 0.012051718, - 0.0010310317, - 0.000036791484, - 0.00041000496, - -0.004540311, - -0.033053227, - -0.015958052, - 0.031274505, - 0.040374644, - 0.007547782, - -0.0041639064, - -0.017849205, - 0.029799044, - -0.0014681591, - 0.0005325381, - 0.0059179813, - 0.038928635, - 0.01570976, - -0.02350525, - 0.006542592, - 0.005594216, - -0.010666929, - 0.026594922, - 0.013180037, - 0.012864923, - 0.01256689, - 0.026196534, - -0.012775259, - -0.017887563, - -0.00763707, - 0.003426463, - 0.0039771465, - -0.0015492667, - -0.015944837, - -0.17491838, - -0.01828697, - -0.014436262, - -0.003216658, - 0.0031604688, - 0.007854137, - -0.018836485, - -0.005953813, - 0.027128367, - -0.012564502, - -0.028196469, - 0.009520993, - -0.0066041597, - 0.0052492707, - 0.005568141, - -0.0094639575, - -0.010667518, - -0.020469489, - 0.0025213694, - 0.0057813576, - -0.020679245, - -0.002316923, - 0.01043237, - 0.0075805555, - 0.003126362, - -0.0041207923, - 0.048960958, - -0.00019221778, - -0.012607793, - -0.024843035, - 0.0037312047, - -0.008059177, - -0.0020865223, - -0.026381135, - 0.003693669, - -0.020666784, - -0.0043053934, - -0.019704903, - 0.019803844, - -0.0046926946, - 0.006427587, - 0.000056806646, - 0.00834894, - 0.010593703, - -0.0039341035, - -0.0069762617, - -0.012204584, - -0.01904588, - -0.009561859, - -0.00703815, - -0.018862253, - 0.037062295, - -0.011066059, - 0.011113843, - 0.00044305524, - 0.01125732, - 0.030593975, - 0.018721338, - 0.017428959, - 0.016535176, - 0.00945132, - 0.024845053, - -0.17019026, - 0.018966129, - 0.00087770267, - -0.016300263, - 0.019608999, - 0.00011865993, - -0.004756165, - 0.012805185, - 0.023605326, - 0.01924897, - 0.012414683, - -0.025189443, - -0.0000602274, - 0.00198841, - -0.009133012, - -0.0029656237, - 0.017389512, - -0.0084834825, - 0.008874918, - 0.003928734, - 0.017791633, - 0.014357394, - -0.008017553, - -0.015196546, - -0.009567154, - -0.0049707172, - 0.007041003, - -0.015730403, - -0.023320332, - -0.00027537745, - -0.026811244, - 0.0030104923, - 0.013416057, - -0.0136064235, - -0.010660011, - -0.02198779, - -0.043950506, - 0.009854296, - -0.01820979, - -0.02269902, - -0.05972212, - 0.0062752794, - -0.00054534624, - -0.017941356, - -0.007791939, - -0.0060540237, - 0.00069407513, - 0.0015732126, - 0.0017388775, - 0.0017043514, - -0.0049848384, - 0.0017813414, - 0.03025984, - 0.0062481207, - 0.023405524, - 0.0013855161, - 0.0036647518, - 0.018184207, - -0.0054886304, - -0.010096075, - -0.014211762, - -0.008116967, - 0.02494202, - 0.042473163, - 0.0018958537, - -0.021582266, - -0.010819542, - -0.023999024, - -0.017095288, - 0.015212085, - -0.026833555, - -0.0012668007, - -0.017543446, - 0.01342396, - -0.010596981, - -0.002127164, - 0.023913069, - -0.013625527, - -0.021902418, - 0.011007946, - -0.027851103, - -0.021300614, - -0.017008997, - 0.004937496, - -0.017594336, - -0.0027916187, - 0.0032829118, - 0.0057920893, - -0.004203937, - -0.0063191643, - 0.008431835, - -0.0039717117, - -0.018355237, - 0.013197295, - 0.013171815, - 0.0062299236, - -0.018728197, - -0.019951293, - 0.012602422, - -0.0018468547, - -0.008467636, - 0.0020105147, - -0.0104538435, - -0.0055573527, - 0.010169731, - 0.008181222, - -0.0004406834, - 0.03640426, - 0.013575391, - -0.021716118, - 0.0033862588, - 0.007953121, - -0.011421253, - -0.023296747, - -0.001932166, - -0.0031507986, - 0.0042926557, - -0.031424165, - -0.014493233, - -0.018992156, - 0.0008547893, - -0.0053459257, - 0.037228934, - 0.014324397, - -0.0072673745, - -0.023373682, - -0.0038779152, - 0.0071537783, - 0.00023484856, - 0.0036102952, - 0.004384707, - 0.00009979742, - 0.0036291818, - 0.010590411, - -0.021717256, - 0.001597665, - -0.011552334, - -0.0145182265, - -0.023998018, - 0.00089180906, - -0.03187588 - ] + "file": "mine_3_copper_ore_blocks.js" }, { - "quality": 0.9, - "successCount": 5, + "quality": 0.8, + "successCount": 2, "failureCount": 0, - "name": "craft_20_spruce_planks", - "description": "Craft 20 spruce planks", + "name": "minethenearestoaklog", + "description": "mine_the_nearest_oak_log", "keywords": [ - "craft", - "spruce_planks", - "wood" + "wood", + "mining", + "explorer" ], - "file": "craft_20_spruce_planks.js", - "embedding": [ - -0.013326881, - -0.0030340517, - 0.007772092, - -0.0843073, - 0.008700537, - -0.00077475264, - 0.020497989, - 0.006496435, - -0.00042915778, - 0.007834614, - -0.016977238, - -0.017236412, - -0.004662566, - -0.025050279, - 0.11943537, - -0.030592127, - 0.018492913, - -0.009999533, - -0.006309832, - -0.02979694, - 0.00070160977, - -0.004202523, - 0.00025610303, - -0.0066610216, - -0.023086108, - 0.002131436, - 0.010536305, - 0.009659744, - 0.03576892, - -0.0042374856, - -0.021717299, - 0.022740195, - 0.014668133, - -0.0190715, - -0.0033127556, - 0.051656548, - 0.0025777721, - -0.017416112, - -0.00979257, - -0.011135709, - -0.006126334, - 0.0072468882, - -0.02683366, - -0.013500194, - -0.00017515532, - 0.011772381, - -0.010839265, - -0.017766997, - 0.007556987, - 0.023781652, - 0.03624735, - 0.018336583, - -0.015970515, - -0.18961541, - -0.0020103722, - -0.0047354824, - -0.016618969, - -0.011958874, - 0.015843207, - -0.0099600535, - -0.004344724, - 0.012635075, - -0.019469377, - -0.01690359, - 0.0097228065, - -0.003529131, - 0.002116009, - -0.007407935, - 0.0012874452, - 0.00782875, - 0.0014054695, - 0.0055970233, - -0.011766735, - 0.003549919, - -0.02554712, - -0.018669268, - -0.0044303644, - 0.018473294, - 0.0036436385, - 0.037258863, - -0.007450985, - -0.028808814, - -0.014938299, - -0.007943668, - -0.023471238, - -0.010352433, - -0.017274426, - -0.012844829, - -0.0053112213, - 0.0005438125, - -0.009128463, - 0.0013549703, - -0.01856392, - -0.0023088527, - 0.031958245, - 0.0038661775, - 0.009921019, - -0.0012648643, - -0.017829597, - 0.015471091, - -0.008339558, - -0.003010305, - 0.0020016057, - -0.0067073954, - 0.023295026, - -0.027032398, - 0.00017022752, - 0.019201146, - -0.019407105, - 0.0035507735, - 0.016500594, - -0.0011762925, - 0.015462452, - 0.038527302, - 0.014032408, - -0.18840018, - 0.02546328, - 0.0030646815, - 0.0010897715, - -0.00903535, - -0.015283623, - -0.002914597, - 0.006094835, - 0.011084165, - 0.012887994, - -0.0028395029, - -0.008331492, - 0.0054868064, - 0.006699429, - 0.004270599, - -0.006063349, - 0.006029337, - 0.00044574548, - 0.019887703, - -0.004215886, - -0.0048827957, - -0.008020746, - -0.0033630836, - 0.0036613215, - -0.0024249523, - 0.005397776, - 0.0082721505, - 0.009769081, - -0.0016394414, - -0.018857492, - -0.021383602, - 0.0059737004, - 0.017790413, - -0.007718055, - -0.028381987, - 0.005355763, - -0.003979734, - 0.014290339, - 0.0034338909, - -0.032165382, - -0.034104824, - -0.008297269, - 0.010690263, - -0.028022323, - 0.031035107, - 0.012955012, - 0.022337394, - 0.00712636, - -0.01039701, - 0.021830752, - -0.0022000433, - 0.006046671, - 0.0125237685, - -0.0046001035, - 0.024201617, - -0.010559991, - -0.010759114, - 0.009386452, - 0.00063089596, - 0.007927278, - -0.002663028, - 0.013985991, - -0.005629882, - 0.0011852009, - 0.0039453697, - 0.022786789, - 0.006134166, - -0.0034791187, - -0.00050607487, - 0.024260432, - 0.004392228, - -0.024259945, - -0.018418977, - 0.04278244, - 0.01226433, - 0.021465154, - 0.01729177, - 0.0104736015, - -0.027491137, - -0.0060981107, - -0.012678219, - -0.0072174426, - -0.01976966, - 0.026266515, - -0.005754419, - 0.010031961, - -0.0041622887, - 0.011439918, - 0.0037527601, - 0.015543368, - 0.0037858272, - -0.006318256, - 0.0031772708, - 0.017444758, - 0.020787414, - 0.020849003, - -0.019172719, - -0.017378122, - 0.012217239, - 0.0099160755, - 0.0072885016, - -0.0058463598, - -0.012000432, - 0.026478305, - -0.0074161417, - -0.007613643, - 0.0041630585, - 0.030106004, - 0.0027870496, - -0.0017095559, - 0.003319232, - -0.014730009, - 0.008051956, - -0.026199043, - 0.03128842, - 0.007832936, - 0.01527621, - -0.002104689, - -0.02756476, - 0.002093253, - -0.012959987, - -0.011695111, - 0.026205031, - 0.00477534, - -0.010865512, - 0.0014017167, - -0.014691805, - -0.011326225, - -0.016958004, - 0.015661152, - -0.0002234991, - -0.004263388, - -0.00026833912, - -0.0016195307, - -0.030432891, - 0.016570345, - -0.01661294, - -0.015052145, - -0.002957144, - -0.0065657683, - -0.004182299 - ] + "file": "minethenearestoaklog.js" }, { - "quality": 0.9, - "successCount": 1, + "quality": 0.8, + "successCount": 14, "failureCount": 0, - "name": "mine_6_spruce_logs", - "description": "Mine 6 spruce logs", + "name": "mine_3_spruce_logs", + "description": "Mine 3 spruce logs", "keywords": [ - "mine", "spruce_log", - "wood" + "builder", + "shelter" ], - "file": "mine_6_spruce_logs.js", - "embedding": [ - -0.0025809547, - -0.001359377, - 0.0069304155, - -0.07902963, - 0.0071376683, - -0.007406419, - 0.016560664, - 0.0071249944, - 0.0075063044, - 0.0060668006, - 0.0026579567, - -0.0034922909, - 0.010923144, - 0.00037158382, - 0.11853407, - -0.016145863, - 0.0050427914, - -0.0063971034, - -0.011883498, - -0.026920242, - -0.018748753, - -0.015306896, - 0.011673829, - -0.018231727, - -0.03066446, - 0.011086564, - 0.020718308, - 0.022244575, - 0.039337844, - -0.007826382, - -0.02473869, - 0.025777582, - 0.010654331, - 0.00256231, - -0.012487788, - 0.049305953, - 0.0030845734, - -0.008537368, - -0.0060913423, - -0.0036167726, - 0.009110884, - 0.009937557, - -0.009291117, - -0.021049479, - -0.0019313357, - 0.013276752, - -0.004648414, - -0.014010555, - -0.0034827914, - 0.01750947, - 0.005061167, - 0.009824158, - -0.012762129, - -0.18460125, - -0.0034105985, - 0.01519403, - -0.014811132, - 0.009029681, - 0.017401546, - -0.009752323, - 0.0018270116, - 0.015866982, - -0.005128189, - -0.00251614, - 0.004865528, - -0.0011458097, - 0.022882342, - -0.008851199, - -0.0009332618, - -0.008818404, - -0.016044335, - 0.0021178725, - -0.01114507, - -0.028477332, - -0.023337962, - -0.008905407, - -0.00403691, - 0.020616248, - -0.011060299, - 0.03461386, - -0.010285048, - -0.031595934, - -0.026346479, - -0.0023592012, - -0.012637475, - 0.007142738, - -0.030913575, - -0.0064209327, - -0.025213884, - 0.009353529, - -0.02141385, - 0.021150334, - -0.02586063, - 0.0053774514, - 0.020322729, - 0.005972054, - -0.004105688, - -0.004958934, - 0.005770768, - -0.0029213908, - -0.017916733, - -0.0024912767, - -0.008294086, - -0.020656133, - 0.021938268, - -0.01929402, - 0.015982823, - 0.001954831, - -0.010354983, - -0.002792236, - 0.004495498, - 0.000054142965, - 0.006033551, - 0.045044005, - 0.009044954, - -0.19244027, - 0.018978773, - 0.010837105, - 0.0030982331, - 0.012938146, - 0.008678081, - -0.00790496, - 0.013348222, - 0.0013261102, - 0.0041682217, - 0.010329374, - 0.008182796, - -0.0035914453, - 0.0057663363, - -0.00485568, - -0.0027741296, - 0.02097131, - 0.0073508513, - 0.012842046, - -0.004295401, - 0.01158448, - -0.00019886455, - -0.008527881, - -0.0098094465, - -0.004831838, - -0.0016335137, - -0.015592306, - 0.0038601088, - 0.00089114887, - -0.016880525, - -0.009371742, - 0.032310244, - 0.004584203, - -0.010553572, - -0.020743929, - -0.016190298, - -0.0279125, - 0.006496505, - -0.0054618916, - -0.009053255, - -0.041247647, - -0.002062675, - 0.0036692326, - -0.034334812, - 0.014331473, - 0.0033389712, - 0.016971968, - 0.019166814, - 0.015496164, - 0.0075334576, - -0.008317228, - 0.0006788451, - 0.027281214, - 0.022520198, - 0.014613289, - -0.014102015, - -0.015779713, - 0.029586893, - -0.02206781, - 0.016915532, - -0.0053551625, - 0.0054850634, - 0.0031683121, - 0.009212677, - 0.0061695655, - 0.010167745, - 0.0010172107, - -0.010587342, - 0.003730294, - 0.00010889663, - -0.011429347, - -0.014664356, - -0.032488935, - 0.02987948, - -0.0015938121, - 0.008169499, - -0.007582623, - -0.01136198, - -0.022947827, - -0.014025257, - -0.015738266, - -0.013691301, - -0.007544514, - 0.023374623, - -0.011601007, - -0.0010593811, - -0.004046822, - 0.0042007174, - 0.021536304, - 0.0060372725, - 0.0011736403, - 0.012673084, - 0.0051329136, - 0.0024128861, - 0.03294926, - 0.015366074, - -0.027521944, - -0.019380027, - 0.009139227, - 0.00788655, - -0.0055894805, - 0.002611672, - -0.0020953822, - 0.032711953, - 0.010385856, - 0.0013778345, - -0.02349629, - 0.026349515, - 0.0018522145, - -0.00035614189, - 0.0006191861, - -0.00074432965, - 0.0054437527, - -0.017440038, - 0.028694019, - -0.013135672, - 0.009214344, - -0.0065663108, - -0.019880807, - -0.010798025, - -0.014776724, - -0.026533416, - 0.03695462, - 0.011608603, - -0.011254728, - -0.0002144868, - -0.025025811, - -0.0045811427, - -0.029041836, - 0.017078374, - -0.011604432, - -0.01186763, - 0.016803011, - 0.008091988, - -0.021669827, - 0.012883662, - -0.0062498497, - -0.0015738454, - -0.0076336977, - -0.0012524395, - -0.011220814 - ] + "file": "mine_3_spruce_logs.js" }, { "quality": 0.8, "successCount": 1, "failureCount": 0, - "name": "mine_5_short_grass_to", - "description": "Mine 5 short grass to find wheat seeds", - "keywords": [ - "mine", - "short_grass", - "wheat_seeds" - ], - "file": "mine_5_short_grass_to.js", - "embedding": [ - -0.020238888, - 0.013877247, - 0.013036328, - -0.05884184, - 0.009350503, - -0.005275406, - -0.0014020613, - -0.0032036328, - 0.009463139, - 0.014994168, - -0.014236472, - -0.0063822386, - 0.001662124, - 0.021347286, - 0.14268254, - -0.01588641, - -0.01759823, - -0.0124300765, - -0.01084902, - 0.0052544037, - -0.010255585, - -0.0046755057, - 0.013431684, - -0.035870913, - -0.021913944, - -0.018096067, - 0.021526575, - 0.02809107, - 0.018279713, - -0.023348082, - -0.025712023, - 0.024553236, - 0.006742693, - 0.011844469, - 0.005334095, - 0.040838134, - 0.01880133, - -0.012406104, - -0.0025797356, - 0.013365953, - -0.004241021, - 0.015822545, - -0.017708113, - 0.0069890353, - 0.0121408515, - 0.010609932, - 0.0001449151, - -0.0051729027, - -0.009007175, - 0.019070359, - 0.011025876, - 0.00210894, - -0.017904406, - -0.17604385, - -0.010648394, - -0.0035750766, - 0.0150892, - -0.017616171, - 0.014655685, - -0.0039594127, - -0.004357297, - 0.045871317, - -0.025303274, - -0.013252203, - 0.014311021, - -0.0066935476, - 0.016658453, - -0.002711321, - -0.034862857, - -0.0074873413, - -0.0093003735, - -0.0069326824, - 0.016427582, - -0.03734593, - -0.02164667, - -0.009442885, - 0.0025777605, - 0.0034995857, - -0.007831423, - 0.02849263, - 0.0050731413, - -0.04114349, - -0.0085136, - 0.009366145, - -0.007841823, - -0.0070971446, - -0.032387838, - -0.003537049, - -0.010693431, - 0.009752221, - -0.029419873, - 0.029739898, - -0.008061508, - 0.02997503, - 0.018163454, - 0.02540912, - -0.0031520273, - -0.012807267, - -0.0055288863, - 0.006350712, - -0.036549386, - -0.037914913, - -0.030433947, - -0.0043138494, - 0.021115711, - -0.015142263, - 0.0156241795, - 0.0019288168, - -0.008323743, - 0.0147207985, - 0.012976654, - -0.0015338032, - -0.016684053, - 0.032339845, - 0.010564889, - -0.17971249, - 0.029142726, - 0.010845584, - 0.0041815336, - 0.008314877, - -0.024165485, - 0.0046618995, - 0.010908784, - 0.022755656, - 0.021772468, - 0.0034606762, - -0.0053354544, - -0.01758662, - -0.0013597646, - -0.022284405, - -0.00061851257, - 0.009450137, - -0.010150763, - 0.016502459, - -0.018496206, - 0.014565679, - -0.02053122, - -0.01745771, - -0.017248275, - -0.008632306, - -0.00010116629, - 0.009907351, - 0.0007325029, - 0.0011610003, - -0.017476052, - -0.02703817, - 0.0067091547, - 0.010178471, - 0.013183147, - 0.0015565088, - -0.01548804, - -0.010676628, - 0.012225905, - -0.02594509, - -0.0028407194, - -0.06904665, - 0.0012744396, - -0.002044765, - -0.018736457, - -0.002767085, - 0.009275029, - 0.0019872917, - 0.018913643, - 0.020056792, - 0.0043639257, - -0.004697235, - -0.0017254377, - 0.011937094, - -0.00078356056, - 0.009231728, - -0.005041995, - -0.01056016, - 0.008515231, - -0.0020250068, - 0.012716144, - -0.0038079433, - 0.0012135562, - 0.0058077495, - 0.012354024, - 0.0058974074, - 0.012244606, - 0.0037215063, - -0.008362574, - -0.013265238, - 0.018419974, - -0.01571015, - 0.0028538473, - -0.012252139, - 0.022250902, - -0.0041405107, - -0.0022353374, - -0.00065538066, - -0.012587878, - -0.024857894, - -0.013270049, - -0.020845741, - -0.022192895, - -0.00991525, - 0.02039318, - -0.0028046616, - -0.007909149, - 0.0011188515, - 0.010210881, - -0.0014510277, - -0.0008728472, - 0.0018498006, - -0.008407954, - 0.0131849395, - 0.0068745622, - 0.022698347, - 0.026081793, - -0.007977211, - -0.000682894, - 0.015411737, - -0.00030141635, - 0.0069780126, - -0.0075022024, - 0.004094978, - 0.0031200903, - -0.00032796618, - -0.0145514, - 0.00020112797, - 0.0138102705, - 0.0011241653, - -0.0063894563, - -0.008034988, - -0.004651502, - -0.02436015, - -0.0019626536, - 0.035482537, - 0.011227501, - -0.011159327, - -0.020406011, - -0.03882383, - 0.002536315, - -0.0016273295, - -0.004460014, - 0.035617374, - 0.01230083, - -0.0018393452, - -0.016526332, - -0.034767073, - -0.0024533109, - -0.013232126, - 0.021880431, - -0.0037172511, - -0.007610999, - 0.009353853, - -0.008233824, - -0.010804951, - 0.0020869863, - -0.0039355196, - -0.016422028, - 0.0025262195, - 0.008408211, - -0.025679436 - ] + "name": "mine_2_moss_blocks", + "description": "Mine 2 moss blocks", + "keywords": [], + "file": "mine_2_moss_blocks.js" }, { "quality": 0.9, - "successCount": 3, + "successCount": 10, "failureCount": 0, - "name": "craft_1_crafting_table", - "description": "Craft 1 crafting table", - "keywords": [ - "craft", - "crafting", - "table" - ], - "file": "craft_1_crafting_table.js", - "embedding": [ - -0.013768034, - 0.010399985, - 0.004625982, - -0.069005854, - -0.011340811, - 0.012160846, - 0.00636676, - -0.005181246, - -0.016083244, - 0.015586321, - -0.004489382, - -0.017434578, - 0.001137991, - -0.028368955, - 0.11929004, - -0.037816375, - 0.0109502515, - -0.013389374, - -0.015518362, - -0.022447448, - 0.009663908, - 0.0000072343346, - 0.011692081, - -0.009164369, - -0.025968187, - -0.00001408955, - 0.01550934, - 0.017780544, - 0.03270734, - -0.01678122, - -0.02627496, - 0.0151741775, - 0.0128670195, - -0.018689817, - 0.0012731905, - 0.062460776, - 0.0035773069, - -0.015988138, - -0.021824058, - 0.0032083148, - -0.0193018, - 0.016788514, - -0.03740216, - -0.0013334688, - -0.002961942, - 0.016087875, - -0.0013662723, - -0.014285018, - -0.012354519, - 0.02258782, - 0.016657652, - 0.02110833, - -0.0052743107, - -0.18454163, - -0.002126146, - 0.009405286, - -0.022083258, - -0.015030225, - -0.00029258645, - -0.008452026, - -0.0153373, - 0.01658412, - -0.016967203, - -0.01997938, - 0.0019461004, - -0.0051067225, - 0.004726548, - -0.0044467035, - 0.0060293605, - 0.0004118741, - 0.003654222, - 0.00392384, - -0.014086036, - 0.0043914197, - -0.029488195, - -0.024137026, - -0.0008090193, - 0.011520855, - 0.009522278, - 0.043476664, - -0.010297878, - -0.023908343, - -0.019364154, - -0.01031762, - -0.0100188935, - -0.0008744322, - -0.017581955, - 0.0048603374, - -0.012397071, - -0.009675298, - -0.022594724, - 0.023576617, - -0.021781811, - 0.0016703731, - 0.023614822, - -0.010236466, - 0.010789805, - 0.003981015, - -0.016846657, - -0.00083079515, - -0.010778043, - -0.0062505114, - 0.00812846, - 0.0016881543, - 0.036218572, - -0.018577525, - 0.006744479, - 0.029148946, - -0.011905904, - 0.016687326, - 0.025250202, - 0.0072292667, - 0.013319365, - 0.021532834, - 0.030399323, - -0.19090037, - 0.018569566, - -0.0019544596, - -0.01425879, - 0.0033481221, - -0.0011529364, - -0.0006073228, - 0.008855917, - 0.009118045, - 0.0021313517, - 0.0045528, - -0.014862653, - 0.011066475, - 0.008611249, - 0.0036969746, - -0.02966216, - 0.00069003337, - 0.0037509552, - 0.033606254, - 0.0026548838, - 0.014542321, - -0.007847808, - -0.0030287039, - 0.019077145, - 0.0008109914, - 0.0070073176, - 0.011725312, - 0.006835858, - -0.0073151533, - -0.033727597, - -0.021658981, - 0.0002512038, - 0.024224669, - -0.031934623, - -0.017026406, - -0.009037888, - -0.0061327294, - 0.007353313, - 0.0031236117, - -0.028760443, - -0.037124276, - -0.011467706, - 0.009692873, - -0.0022175764, - 0.014296582, - 0.004818817, - 0.015570129, - 0.004197983, - 0.009435304, - 0.012981276, - -0.0093943095, - 0.0061991643, - 0.01481351, - 0.008981017, - 0.027163167, - -0.012194303, - -0.015223242, - 0.013464783, - 0.007470711, - 0.025177287, - 0.004182439, - 0.019983893, - 0.0027807155, - 0.007901841, - 0.0070901266, - 0.024651878, - 0.000034920482, - 0.0016545712, - -0.0014207172, - 0.016468609, - 0.009390458, - -0.001688228, - -0.022490898, - 0.03477671, - 0.0041038557, - 0.027560392, - 0.015583586, - -0.005419533, - -0.024742857, - -0.0030262752, - -0.002254943, - -0.030309908, - -0.024631392, - 0.03726978, - -0.0026379905, - 0.008205228, - -0.0010214665, - -0.0033280891, - 0.007860432, - -0.016606966, - -0.009848934, - 0.0052078124, - -0.008364046, - -0.0057793995, - 0.03556984, - 0.020044252, - -0.02080544, - -0.014189105, - 0.019971548, - 0.0140581, - 0.0018614774, - -0.007650435, - -0.007797341, - 0.020957015, - -0.010208205, - -0.017134685, - 0.018617278, - 0.021406908, - 0.0097349575, - -0.011965102, - -0.0003179804, - -0.03320967, - 0.0019908838, - -0.021334006, - 0.020285113, - 0.0015410065, - -0.0024851982, - -0.0109684905, - -0.02061473, - 0.0021040898, - -0.021506509, - -0.010802657, - 0.008592657, - 0.0024788282, - -0.0027405424, - 0.0006298393, - -0.016594665, - -0.010226737, - 0.0061049014, - 0.032914292, - 0.021509213, - 0.0041644634, - 0.001980111, - 0.0032850744, - -0.014357523, - 0.00374745, - -0.0064192684, - -0.016880454, - 0.011369518, - 0.011490583, - -0.007194128 - ] + "name": "mine_3_oak_logs_at", + "description": "Mine 3 oak logs at 798, 7", + "keywords": [], + "file": "mine_3_oak_logs_at.js" }, { - "quality": 0.8, - "successCount": 1, + "quality": 0.9, + "successCount": 12, "failureCount": 0, - "name": "mine_10_short_grass_to", - "description": "Mine 10 short grass to collect wheat seeds", - "keywords": [ - "mine", - "short_grass", - "seeds" - ], - "file": "mine_10_short_grass_to.js", - "embedding": [ - -0.019888135, - 0.009439164, - 0.010959619, - -0.060375858, - 0.0051200762, - -0.0018896007, - 0.0052182213, - -0.004744459, - 0.0040997756, - 0.017666858, - -0.005911235, - -0.0075395424, - 0.003275662, - 0.022931555, - 0.14144495, - -0.009192021, - -0.0077097123, - -0.017703222, - -0.015014994, - -0.0008360443, - -0.013814866, - 0.0020239132, - 0.014272722, - -0.037903413, - -0.024713011, - -0.0164888, - 0.021072391, - 0.02803485, - 0.016173411, - -0.025656147, - -0.020146724, - 0.024209276, - -0.005560068, - 0.00878907, - 0.0054405974, - 0.02775273, - 0.01584358, - -0.008317324, - -0.0104212025, - 0.009012226, - -0.0029929338, - 0.014280263, - -0.020617006, - 0.0116798, - 0.009574333, - 0.010772297, - -0.005599335, - -0.012214731, - -0.0002919604, - 0.020572677, - 0.0136176795, - -0.004642372, - -0.012891096, - -0.18321271, - -0.010433937, - -0.00041014404, - 0.015216232, - -0.013473955, - 0.019784277, - -0.014290896, - 0.00028268786, - 0.04043537, - -0.019350177, - -0.024631495, - 0.00556194, - -0.00727413, - 0.018773254, - 0.0043302807, - -0.027821708, - -0.008109442, - -0.00639138, - -0.010077009, - 0.019581107, - -0.025525039, - -0.016949674, - -0.011424448, - 0.0022295206, - 0.0064012944, - -0.006581894, - 0.034265954, - 0.003752108, - -0.039090157, - -0.0046790573, - 0.00801932, - -0.0123569295, - -0.0003933144, - -0.033575308, - -0.0021440468, - -0.0012057982, - 0.018016668, - -0.02459498, - 0.026929718, - -0.015816156, - 0.027596934, - 0.015603683, - 0.03109614, - 0.0067018014, - -0.01239636, - -0.0033633546, - 0.0036859927, - -0.041306518, - -0.026936606, - -0.032038998, - 0.0012569383, - 0.019073684, - -0.017787024, - 0.0045684385, - -0.0080621885, - -0.0072465227, - 0.01709887, - 0.018474838, - -0.0034777045, - -0.0075195, - 0.0258829, - 0.009203511, - -0.17481896, - 0.029556738, - 0.01239048, - 0.007844443, - 0.011609224, - -0.019450916, - 0.0066443738, - -0.000325384, - 0.026653737, - 0.01954455, - 0.0032065376, - -0.013373626, - -0.009500975, - 0.002442054, - -0.020926783, - -0.0016181873, - -0.001521572, - -0.009311466, - 0.023928601, - -0.013302799, - 0.008765436, - -0.019802107, - -0.016324028, - -0.02599745, - -0.0072254003, - 0.0006759704, - 0.00559458, - -0.006466609, - -0.0039999792, - -0.020947613, - -0.035275757, - -0.000034232355, - 0.011842063, - 0.014541041, - 0.0010598919, - -0.004164158, - -0.003730817, - 0.010099392, - -0.023554893, - 0.0010697071, - -0.07158094, - 0.0037197524, - -0.0043859, - -0.016236609, - 0.0030580622, - 0.007509316, - -0.00432988, - 0.017778032, - 0.021175977, - 0.0022708625, - -0.006567792, - -0.01307597, - 0.011389264, - 0.004585828, - 0.018604603, - -0.0019354573, - -0.018994495, - 0.009743199, - 0.0026021358, - 0.012460471, - 0.001587221, - 0.003279792, - 0.01041264, - 0.0007999174, - 0.0059278263, - 0.0094791595, - 0.0025274379, - -0.00895498, - -0.023181917, - 0.010370963, - -0.025046397, - -0.0024292823, - -0.013776194, - 0.01895923, - -0.0097408425, - -0.0038338227, - 0.00027444176, - -0.012428052, - -0.025745928, - -0.012306689, - -0.024724156, - -0.018633641, - -0.015228666, - 0.019718774, - -0.004109621, - -0.0062928773, - 0.002072792, - 0.008999196, - -0.003258306, - -0.0056331963, - 0.0060086492, - -0.010907198, - 0.016433736, - 0.01196319, - 0.029581957, - 0.022038402, - -0.023752728, - 0.0014746939, - 0.024572242, - 0.004133579, - 0.0029683835, - -0.0058353418, - 0.008765188, - 0.011634883, - -0.000084232925, - -0.009224683, - 0.00033907944, - 0.017673519, - -0.011002217, - -0.0065488424, - -0.0104763545, - -0.003638889, - -0.02490098, - -0.008224847, - 0.033964187, - 0.0047690063, - -0.00424425, - -0.017478162, - -0.041823883, - -0.0007550503, - -0.0063006096, - -0.0036116373, - 0.03554151, - 0.012638973, - -0.007939893, - -0.016112521, - -0.028097445, - -0.00001090682, - -0.0033529482, - 0.019249996, - -0.000582048, - -0.0004992043, - 0.005861219, - -0.007477705, - -0.0070198877, - -0.0001456127, - -0.010713288, - -0.007146621, - 0.004951583, - -0.00091583474, - -0.018337652 - ] + "name": "craft_1_crafting_table_using", + "description": "Craft 1 crafting table using 4 spruce planks", + "keywords": [], + "file": "craft_1_crafting_table_using.js" }, { "quality": 0.9, - "successCount": 1, + "successCount": 2, "failureCount": 0, - "name": "craft_16_spruce_planks", - "description": "Craft 16 spruce planks", - "keywords": [ - "craft", - "spruce_planks" - ], - "file": "craft_16_spruce_planks.js", - "embedding": [ - -0.019732855, - -0.0012482365, - 0.012010346, - -0.079186164, - 0.0053718775, - -0.010143627, - 0.017165575, - 0.0041439356, - -0.003593696, - -0.0037744266, - 0.002646511, - -0.015778786, - -0.0043346854, - -0.03339301, - 0.11637002, - -0.031896215, - 0.013625393, - -0.004361547, - -0.008481266, - -0.016551228, - 0.005818332, - -0.008633119, - 0.00077793625, - -0.02111983, - -0.025182107, - 0.0039876834, - 0.013740022, - 0.014076039, - 0.035008308, - -0.009902338, - -0.016228085, - 0.029621663, - 0.02144421, - -0.013060046, - -0.0057946364, - 0.05476432, - -0.00056644634, - -0.020027157, - -0.011972554, - -0.009167148, - 0.0027918688, - 0.01647069, - -0.024211608, - -0.010032408, - 0.0015696755, - 0.01611517, - -0.019738298, - -0.013934115, - 0.014029612, - 0.020655548, - 0.030783411, - 0.0139884055, - -0.018459674, - -0.18902563, - -0.0084242495, - 0.000473096, - -0.023569372, - -0.015446199, - 0.02257809, - -0.023183076, - -0.009550893, - 0.016187044, - -0.026092125, - -0.018272119, - -0.000052475578, - -0.00054631114, - 0.0126496, - -0.0040686736, - 0.0036604127, - 0.008487834, - -0.0067757145, - 0.007040607, - -0.013081844, - 0.012538987, - -0.025108641, - -0.020878466, - -0.009037786, - 0.017360775, - -0.000884286, - 0.043542754, - -0.008619431, - -0.02696299, - -0.025646003, - -0.0021058586, - -0.01729068, - -0.007221336, - -0.013885564, - -0.00038148652, - -0.0038731343, - 0.006554176, - -0.0046899254, - 0.0046862816, - -0.016635396, - 0.00816778, - 0.03090908, - 0.0019352176, - 0.015496563, - 0.0042670094, - -0.023846203, - 0.0053583225, - -0.020500252, - 0.0023158526, - 0.005909363, - -0.009645102, - 0.027241735, - -0.027713165, - 0.007307007, - 0.024003454, - -0.016168024, - 0.010648546, - 0.019073924, - 0.0007231586, - 0.0234776, - 0.032038506, - 0.024231432, - -0.19356777, - 0.015102763, - 0.010473296, - -0.005267483, - -0.002468616, - -0.0033926945, - -0.0026853078, - 0.011061188, - -0.00069325557, - 0.015549751, - 0.006657177, - -0.0029021732, - -0.0024942171, - 0.00992531, - 0.004836007, - -0.0140184425, - 0.0048204768, - 0.004825243, - 0.023516547, - -0.010154463, - -0.0041454104, - -0.010643308, - -0.0072651044, - -0.0028793681, - 0.010458457, - 0.022399813, - 0.01121166, - 0.008968734, - -0.0039571943, - -0.025555328, - -0.012163788, - 0.00085684867, - 0.025983693, - -0.011449129, - -0.023342969, - -0.00029426801, - -0.0130295325, - 0.018429307, - 0.008125177, - -0.04155769, - -0.039881762, - -0.0055644284, - 0.0070483494, - -0.013585197, - 0.035666957, - 0.002721617, - 0.021049997, - 0.014260173, - -0.009332968, - 0.021581763, - -0.011279545, - -0.00016718093, - 0.010784473, - -0.004252087, - 0.029686257, - -0.010844683, - -0.013029079, - 0.0048833448, - 0.006023245, - 0.013028515, - 0.00018700842, - 0.020795424, - 0.0071760966, - 0.009925039, - -0.0023494386, - 0.017873801, - -0.0028783607, - 0.005927708, - 0.006862518, - 0.023836335, - 0.0022923015, - -0.014045082, - -0.029779613, - 0.03666008, - 0.011426249, - 0.018155104, - 0.0027238184, - -0.00020096045, - -0.023895647, - -0.005184462, - -0.027194008, - -0.0043534474, - -0.018248327, - 0.030445661, - -0.013105597, - 0.008250385, - -0.002500642, - -0.00005173228, - -0.0046212794, - 0.01202167, - 0.009773914, - -0.00081474625, - 0.010993876, - 0.018791774, - 0.026430491, - 0.014860991, - -0.019133892, - -0.017253274, - 0.019319305, - 0.014488418, - 0.00023355204, - -0.0041773687, - -0.016040105, - 0.019418, - -0.015222883, - -0.013164532, - 0.004823073, - 0.041419808, - 0.009123434, - -0.005334232, - 0.006420114, - -0.019897599, - 0.015838984, - -0.02343852, - 0.025032114, - -0.0019523521, - 0.013355447, - 0.008178135, - -0.023257852, - 0.000019599956, - -0.032764766, - -0.02421602, - 0.022434797, - 0.0027072162, - -0.013594676, - 0.0050848746, - -0.0050446955, - -0.017125143, - -0.014869788, - 0.020223934, - 0.0024723427, - -0.008092059, - -0.0028017235, - 0.002637921, - -0.022044066, - 0.015489754, - -0.017335355, - -0.016401358, - -0.004143685, - -0.008492685, - -0.0054531544 - ] + "name": "craft_12_spruce_planks_at", + "description": "Craft 12 spruce planks at the crafting table at 807,", + "keywords": [], + "file": "craft_12_spruce_planks_at.js" }, { "quality": 0.8, "successCount": 1, "failureCount": 0, - "name": "mine_5_clay_blocks", - "description": "Mine 5 clay blocks", + "name": "mine_12_cobblestone_from_the", + "description": "Mine 12 cobblestone from the nearby stone blocks", "keywords": [ "mine", - "clay" + "cobblestone", + "stone" ], - "file": "mine_5_clay_blocks.js", - "embedding": [ - -0.013378763, - 0.016051214, - 0.014879361, - -0.08017857, - 0.013642514, - -0.00065071386, - 0.0051255263, - 0.00827093, - 0.015943676, - 0.0046809474, - -0.023739794, - 0.0028855477, - 0.0036853626, - 0.02337213, - 0.1395633, - -0.008571325, - -0.028182363, - -0.0061891614, - -0.006882676, - -0.002359512, - -0.0073998696, - -0.0163427, - 0.021875458, - -0.012061425, - -0.018050322, - 0.00008976366, - 0.017566381, - 0.042847253, - 0.03376635, - -0.014090896, - -0.020764068, - 0.007437506, - 0.023216024, - 0.004136937, - 0.015226615, - 0.03272872, - 0.0010524915, - -0.004623074, - 0.001456705, - 0.015511926, - -0.016164972, - 0.005905564, - -0.013223034, - 0.015649876, - 0.012310773, - 0.0030807413, - 0.001835459, - -0.0066359174, - -0.0026323593, - -0.004778626, - 0.01709475, - 0.017248362, - -0.010897806, - -0.18195842, - -0.0126678655, - -0.010064927, - 0.0042071864, - -0.01032456, - 0.029240625, - 0.0025159044, - -0.014448581, - 0.04526377, - -0.020339193, - -0.00959411, - 0.008467167, - 0.0065213917, - 0.009153273, - 0.002364333, - -0.01837041, - -0.012732809, - -0.026604544, - 0.009135306, - 0.0048509487, - -0.030474767, - -0.013622723, - -0.022743665, - -0.007801937, - 0.026158933, - -0.0020786403, - 0.03685432, - -0.0018449696, - -0.03139319, - -0.004189562, - -0.0057338174, - -0.00047626134, - -0.0034287123, - -0.01654034, - 0.008999343, - -0.016287992, - -0.008118542, - -0.02359536, - 0.013127347, - -0.0053109443, - 0.030748911, - 0.0034339102, - 0.015240155, - -0.018845223, - -0.011965657, - -0.0068158847, - 0.011060907, - -0.011823463, - -0.008175412, - -0.02176771, - -0.020108279, - 0.023697993, - -0.02831833, - 0.012412802, - -0.005679369, - 0.0013231522, - 0.030126428, - 0.01410255, - 0.0031154957, - -0.0044207536, - 0.04504592, - 0.018889781, - -0.19114827, - 0.016676845, - 0.016077781, - 0.012060331, - -0.0069735367, - -0.0037591902, - -0.010742359, - 0.015152319, - 0.017762687, - 0.021211077, - 0.009590903, - -0.004266253, - -0.017093267, - -0.016784145, - -0.010155068, - -0.0041643213, - -0.0033451002, - -0.0035611456, - 0.021570038, - -0.010104127, - 0.015072771, - -0.014674285, - -0.019811, - -0.016649375, - -0.010723774, - 0.00529163, - 0.010874912, - 0.011135004, - 0.008403099, - -0.02289154, - -0.0053643715, - 0.017589273, - 0.00639583, - -0.014609187, - -0.017061817, - -0.019097337, - -0.028261922, - 0.01037326, - -0.009694329, - -0.0015768064, - -0.048852682, - -0.003000506, - -0.007468025, - -0.016591407, - 0.0082163345, - 0.0141974725, - 0.014584279, - 0.01936779, - 0.027752383, - -0.012142014, - -0.014977327, - -0.009027314, - 0.025070079, - 0.009435774, - 0.0015936346, - 0.013205282, - -0.0135797905, - -0.0016752871, - -0.011143409, - 0.0221069, - -0.01023363, - 0.015760032, - 0.0027227344, - 0.010661038, - 0.007291531, - 0.02875792, - 0.0014570538, - -0.0012283907, - -0.01662783, - -0.0030341712, - -0.027408332, - -0.012849326, - -0.034797445, - 0.006745673, - -0.005765908, - -0.00049137784, - -0.002062254, - -0.016064575, - -0.033097018, - -0.024510333, - -0.019329662, - -0.0177989, - -0.023631256, - 0.034234695, - 0.008485257, - -0.00042662738, - 0.0018258776, - -0.005281217, - 0.00013526645, - 0.014451036, - 0.019802708, - -0.021793533, - 0.008681209, - 0.021230107, - 0.028605023, - 0.014091535, - -0.011861787, - 0.00746156, - 0.012006654, - -0.002073177, - -0.015714863, - -0.000027440205, - 0.008002468, - 0.016505891, - -0.003840831, - -0.009517175, - -0.007401942, - 0.025015397, - -0.0020591202, - -0.0062967883, - -0.0128178345, - -0.015263448, - -0.0014065055, - 0.023367224, - 0.032367658, - 0.0084415665, - -0.011407736, - -0.02904479, - -0.024342969, - -0.015520511, - -0.005454405, - -0.01992071, - 0.024301331, - 0.019657424, - 0.0037503976, - -0.009316959, - -0.016677603, - 0.016171135, - -0.02000232, - 0.036051285, - -0.013883507, - 0.006784615, - 0.025784269, - -0.018309183, - -0.019868916, - 0.006612214, - -0.009953523, - -0.021792108, - -0.0047036028, - 0.008428775, - -0.009371783 - ] + "file": "mine_12_cobblestone_from_the.js" }, { "quality": 0.8, "successCount": 1, "failureCount": 0, - "name": "mine_5_kelp_plants", - "description": "Mine 5 kelp plants", - "keywords": [ - "mine", - "kelp", - "water" - ], - "file": "mine_5_kelp_plants.js", - "embedding": [ - -0.0016762917, - 0.0006861243, - 0.0074491785, - -0.0982347, - 0.009174288, - 0.0022015679, - 0.0147491805, - 0.010324855, - 0.03104727, - -0.0011214395, - -0.020313486, - -0.004922819, - 0.018873869, - 0.0017183294, - 0.14765319, - -0.008151117, - -0.014657193, - 0.011882003, - -0.011850244, - 0.013503188, - -0.020843491, - -0.020238621, - 0.020265127, - -0.027433997, - -0.01877257, - -0.0080345385, - 0.032976717, - 0.042057097, - 0.02513958, - -0.017374955, - -0.01329123, - 0.017525198, - 0.007148494, - 0.0063096033, - 0.013148961, - 0.033908296, - 0.0027456412, - -0.022234485, - 0.0004808462, - 0.017076101, - -0.004491481, - 0.014226073, - -0.01077089, - 0.0002767634, - 0.0014995747, - 0.0058316705, - 0.0027356953, - -0.011270939, - -0.0046793623, - 0.014557893, - 0.017749324, - -0.0006118981, - -0.02031032, - -0.18365991, - -0.009301551, - 0.008927774, - -0.0036486452, - -0.033616964, - 0.015862545, - -0.0014549526, - -0.0074354997, - 0.0464614, - -0.04000322, - -0.020713817, - 0.015788423, - -0.0035184624, - -0.009883668, - 0.01282941, - -0.021136109, - -0.013106723, - -0.023690142, - 0.0024231146, - -0.010316298, - -0.029936433, - -0.028988969, - -0.005700775, - -0.013214998, - 0.019163173, - 0.012264702, - 0.039151598, - -0.0023597593, - -0.016786985, - -0.0329232, - 0.0015698372, - -0.012256312, - -0.0077944216, - -0.032101948, - -0.016237667, - -0.014102467, - 0.0114939185, - -0.02915736, - 0.010476735, - -0.0025331615, - 0.025678204, - -0.00831127, - 0.013398972, - -0.009465071, - -0.01628856, - -0.014744091, - 0.0033375719, - -0.015464893, - 0.00550042, - -0.011886527, - -0.009167114, - 0.0072628497, - -0.011960779, - 0.0002466874, - -0.012846574, - -0.00023254969, - 0.014421918, - -0.004578088, - -0.0018876086, - -0.0012957779, - 0.028060189, - 0.015029416, - -0.17998946, - 0.017562373, - 0.009546893, - 0.016663158, - -0.004142645, - -0.013622595, - -0.005617284, - 0.010078403, - 0.020663543, - 0.0067560854, - 0.019711426, - -0.00055002136, - -0.028851878, - -0.0019110515, - -0.015180814, - 0.027522769, - 0.028575366, - 0.015339819, - 0.011345707, - -0.009756559, - 0.01336782, - -0.0078466525, - -0.0043791593, - -0.022355666, - -0.0045001036, - 0.0014082775, - 0.0073998454, - 0.02314512, - -0.0006066476, - -0.009879048, - -0.024954194, - 0.00999428, - 0.020894857, - -0.014893212, - -0.0091932705, - -0.0161363, - -0.007299025, - 0.0077864365, - -0.00897474, - 0.018161047, - -0.04718269, - -0.012616405, - -0.004325433, - -0.02877395, - 0.009534767, - 0.023855519, - -0.0003267256, - 0.0059142285, - 0.0076323682, - 0.00041733868, - -0.022128524, - -0.0023449245, - 0.035757214, - 0.0028846913, - 0.010705075, - -0.004954291, - -0.014266564, - 0.007533471, - -0.020908304, - -0.0046453257, - -0.0010385519, - -0.0053543295, - -0.01153553, - -0.00068326696, - 0.013271932, - 0.015902378, - 0.0019661335, - -0.0056315493, - -0.011753968, - 0.010297122, - -0.036432352, - 0.022841718, - -0.027961714, - 0.013069848, - -0.020426987, - -0.006113191, - 0.007648365, - 0.0044468236, - -0.024644887, - -0.0101250885, - -0.028553102, - -0.0059255823, - -0.0049061226, - 0.007493616, - -0.02025446, - 0.00081414153, - 0.014628473, - -0.004884866, - 0.001144158, - 0.007938841, - 0.0048254705, - -0.00956921, - 0.0026734737, - 0.014892224, - 0.015980802, - 0.003519954, - -0.026140492, - 0.017394338, - 0.001472197, - -0.0020685347, - -0.021088667, - -0.007833815, - 0.0012645391, - 0.015274071, - -0.0061820163, - -0.0023688278, - -0.01791155, - -0.0016718878, - -0.0017949095, - 0.010498878, - -0.016448548, - 0.000043322998, - -0.008917963, - -0.0065091695, - 0.022513123, - -0.010303974, - -0.0035390705, - -0.018227126, - -0.03008731, - -0.013672222, - -0.0004730369, - -0.021661459, - 0.048462216, - 0.006055874, - 0.008932361, - -0.015965303, - -0.0017461202, - 0.008096312, - -0.03178193, - 0.023562213, - 0.004273751, - -0.005907081, - 0.0036340402, - -0.008195392, - -0.019311912, - 0.0034397407, - -0.012238453, - -0.019957606, - -0.008074128, - -0.0023134495, - -0.008986496 - ] + "name": "walk_to_the_chest_at", + "description": "Walk to the chest at 855, 64,", + "keywords": [], + "file": "walk_to_the_chest_at.js" }, { "quality": 0.8, - "successCount": 1, + "successCount": 2, "failureCount": 0, - "name": "mine_5_dirt_blocks", - "description": "Mine 5 dirt blocks", - "keywords": [ - "mine", - "dirt", - "farm" - ], - "file": "mine_5_dirt_blocks.js", - "embedding": [ - -0.008552478, - 0.010837567, - 0.019329477, - -0.06840614, - 0.01177585, - -0.0039740633, - 0.011571234, - -0.0022338603, - 0.010812236, - 0.014725293, - -0.027503982, - 0.0071962634, - -0.0029690508, - 0.0180404, - 0.12963647, - -0.008674976, - -0.02801136, - 0.0027610618, - -0.021233482, - -0.005728308, - -0.014410337, - -0.017225204, - 0.020632321, - -0.012806251, - -0.0037226863, - 0.0014646179, - 0.034922246, - 0.034183335, - 0.029773902, - -0.008256157, - -0.018474462, - 0.00730141, - 0.019771487, - 0.01261551, - -0.00025979706, - 0.03630365, - 0.0007833867, - 0.0032805982, - 0.006671982, - 0.013137305, - -0.012572747, - 0.006802747, - -0.020703644, - 0.0073685716, - 0.004847989, - 0.0023728106, - 0.0059393654, - -0.015859563, - -0.005646163, - 0.01059977, - 0.010908748, - 0.009363469, - -0.014502663, - -0.17142513, - -0.016778927, - 0.0049282047, - -0.002334991, - -0.013780712, - 0.0218035, - -0.00835677, - -0.00897603, - 0.04902238, - -0.024643535, - -0.019319814, - 0.009056496, - -0.005160745, - 0.0060103936, - -0.004082471, - -0.017907364, - -0.010255106, - -0.006998788, - -0.00043276924, - -0.0016251951, - -0.036041137, - -0.01751596, - -0.016022857, - -0.01658477, - 0.003313012, - -0.008908142, - 0.04204453, - 0.005385409, - -0.023128344, - -0.004842306, - -0.0017943518, - 0.0004254188, - -0.020199317, - -0.024323825, - 0.002392952, - -0.02211631, - -0.00044819014, - -0.029330652, - 0.012489083, - 0.0036619795, - 0.014313573, - 0.0031146852, - 0.02259704, - -0.009408039, - -0.020819973, - 0.005690116, - -0.0016905705, - -0.013950035, - -0.010602692, - -0.027571013, - -0.0048062005, - 0.024225844, - -0.024708407, - 0.016874438, - -0.00793015, - -0.008865827, - 0.0132591855, - 0.0049296045, - -0.00495919, - -0.009480598, - 0.035683386, - 0.02741215, - -0.18640718, - 0.02008613, - 0.01157109, - 0.010732279, - 0.0005989603, - -0.013710037, - 0.0058219996, - 0.013128442, - 0.023851288, - 0.021453032, - 0.009504071, - -0.0010194689, - -0.009383172, - -0.00087126734, - -0.020000188, - -0.0073929722, - 0.007928618, - -0.022041906, - 0.02038352, - 0.0030483522, - 0.020389257, - -0.0006789824, - -0.012638819, - -0.018731933, - 0.0009716934, - -0.009300807, - 0.0035664425, - -0.007092676, - 0.008941813, - -0.006906048, - -0.015946822, - 0.0007947246, - 0.0016853341, - -0.026453039, - -0.022641746, - -0.018268373, - -0.022484772, - 0.014813727, - -0.009411426, - 0.0064807837, - -0.04927801, - -0.0051885457, - -0.014152326, - -0.0054716542, - 0.006189937, - 0.0068459213, - 0.0047261, - 0.014770052, - 0.013193616, - 0.00031148235, - -0.0085622715, - 0.013388044, - 0.019251272, - 0.0057428833, - 0.0130516095, - -0.008636804, - -0.018675454, - -0.005703861, - -0.024384212, - 0.027698364, - -0.008786141, - 0.0012289516, - 0.0114816995, - 0.02866002, - 0.020293765, - 0.02175754, - 0.014733022, - -0.0025818385, - -0.023245819, - -0.00059860287, - -0.011679483, - -0.011028865, - -0.036729474, - -0.0012230953, - -0.009806473, - 0.001322395, - 0.0046644057, - -0.018382475, - -0.043075286, - -0.0045446265, - -0.03750072, - -0.003969253, - -0.0040722815, - 0.017224299, - -0.0018649742, - -0.0033950347, - 0.011226787, - -0.0018031338, - 0.002250711, - 0.0018952843, - 0.011022158, - -0.027816767, - 0.010651418, - 0.01671011, - 0.02541481, - 0.020331487, - -0.01401209, - 0.009667006, - 0.0073204977, - 0.0001312051, - -0.008276925, - -0.017412908, - 0.0060076686, - 0.025886422, - 0.009372107, - -0.0100360345, - -0.018300934, - 0.021790545, - 0.006541816, - -0.005620526, - -0.009255105, - -0.009645191, - -0.00839075, - 0.009148809, - 0.024354663, - 0.015039685, - -0.005728941, - -0.020413635, - -0.033957135, - -0.016295264, - -0.0013908813, - -0.014468149, - 0.030600652, - 0.007542501, - -0.008104184, - -0.0107699, - -0.020673258, - 0.009158659, - -0.018207416, - 0.025526693, - -0.0063568717, - 0.0033078273, - 0.006472459, - -0.01140497, - -0.026011264, - -0.0013048917, - -0.0016527885, - -0.016116826, - -0.017583502, - 0.0065707094, - -0.01551375 - ] + "name": "mine1oaklog", + "description": "mine_1_oak_log", + "keywords": [], + "file": "mine1oaklog.js" }, { - "quality": 0.8, + "quality": 0.9, "successCount": 2, "failureCount": 0, - "name": "mine_3_iron_ore_at", - "description": "Mine 3 iron ore at 1734, 46, 427", - "keywords": [ - "mine", - "iron_ore" - ], - "file": "mine_3_iron_ore_at.js", - "embedding": [ - -0.010440801, - -0.0025520376, - 0.02002366, - -0.0857283, - 0.0019052129, - -0.017510947, - -0.0011270201, - -0.004422805, - -0.00070548314, - 0.003222412, - -0.010486838, - 0.0031013761, - 0.0011689508, - 0.011275919, - 0.13370661, - -0.021673633, - -0.012228964, - 0.00022394257, - -0.022663137, - -0.0036844274, - -0.027757347, - -0.019118732, - 0.00081256754, - -0.035522744, - -0.005877264, - -0.0031749548, - 0.023642614, - 0.029092235, - 0.03106075, - -0.015566631, - 0.005614872, - 0.025307255, - 0.0024475197, - 0.001306378, - 0.0031888317, - 0.03056107, - 0.0133179575, - -0.011639832, - -0.011527897, - 0.01773556, - -0.010182199, - 0.011654279, - -0.011324567, - -0.013863239, - -0.014537223, - 0.0158473, - 0.015024963, - -0.02031179, - -0.0033141188, - 0.0022575052, - 0.00971446, - 0.01266831, - -0.01844434, - -0.1813272, - -0.007917659, - -0.0012673985, - 0.013582156, - -0.022590969, - 0.018875282, - 0.0014248828, - -0.016972573, - 0.053777248, - -0.013888004, - -0.03205599, - 0.005664878, - 0.0066302456, - 0.019841405, - 0.0050932444, - -0.010756662, - -0.01812466, - -0.018221471, - -0.0073813996, - -0.001547857, - -0.03255444, - -0.0010979661, - -0.0037989386, - 0.008063427, - 0.004843864, - -0.00028233384, - 0.034621414, - -0.034344174, - -0.02801842, - -0.007230378, - 0.011149042, - -0.009741515, - -0.0020132053, - -0.0266616, - 0.012037372, - 0.0041063027, - 0.011999166, - -0.03471682, - 0.030290665, - 0.0056480872, - 0.020489777, - 0.01435743, - 0.0091876555, - 0.014407085, - -0.00039465906, - -0.0050676325, - -0.018094316, - -0.0201223, - -0.015311045, - -0.017879955, - -0.0052739726, - 0.011948391, - -0.03531072, - 0.010246584, - 0.011689883, - -0.006665025, - 0.007250713, - 0.011360818, - 0.0007314377, - 0.0029632014, - 0.025810752, - 0.0272302, - -0.17054343, - 0.008955759, - -0.0032398363, - 0.011794741, - 0.005987987, - -0.024553971, - 0.0018771088, - 0.0017195385, - 0.023197735, - 0.004865066, - 0.010748166, - -0.014856584, - -0.018309277, - -0.003829803, - 0.000012537737, - 0.0052097645, - 0.0063908356, - 0.013277607, - 0.034689795, - -0.013430681, - 0.023294961, - -0.0061046034, - -0.0023001393, - -0.019036321, - -0.0077450364, - -0.009787363, - 0.0126025025, - 0.0056016836, - 0.0076471106, - -0.026032243, - -0.012965869, - 0.007300091, - 0.010655718, - -0.0031599794, - -0.012132305, - -0.015669746, - -0.02943772, - 0.012589681, - -0.0031227295, - -0.008822202, - -0.04329702, - 0.0016172775, - 0.006003354, - 0.0013404911, - 0.018258736, - -0.0064652385, - -0.005652896, - 0.02545167, - 0.013694939, - -0.00046542042, - -0.016164768, - 0.008593689, - 0.008369577, - 0.008450132, - 0.011688925, - 0.009992254, - -0.024112888, - -0.010327974, - -0.0035734444, - 0.009598263, - -0.014646228, - 0.026165122, - 0.0017810756, - 0.00966472, - 0.024882585, - 0.012763927, - -0.0030421664, - -0.01115806, - -0.0014253291, - -0.009979936, - -0.01050647, - 0.007383861, - -0.024853885, - 0.02015766, - -0.004722776, - -0.0029920614, - -0.004735895, - -0.030166423, - -0.030265605, - -0.0104092425, - -0.019001033, - -0.032756045, - -0.01843802, - 0.038458344, - -0.01762732, - -0.006383002, - -0.0112170195, - -0.006409575, - 0.005039303, - -0.004792516, - 0.0102100475, - -0.011887988, - -0.0031125213, - -0.0060946094, - 0.031697635, - -0.0036780639, - -0.021494653, - -0.0011838265, - 0.013868218, - -0.0072583873, - -0.024498466, - 0.0058566523, - 0.0034549106, - 0.010401378, - -0.020614075, - 0.0074786204, - -0.00094389525, - 0.031111883, - 0.010255117, - -0.009207385, - -0.004927122, - 0.0045582335, - 0.019337123, - -0.015611606, - 0.027626049, - 0.00034085772, - -0.0076177036, - -0.0031393596, - -0.017908186, - -0.01993235, - -0.012729006, - -0.0056124777, - 0.019906145, - 0.010285606, - -0.001976089, - -0.008514845, - -0.009617285, - 0.004875905, - -0.013805523, - 0.037729587, - -0.02984699, - 0.013575773, - 0.011084842, - -0.007857952, - -0.007322971, - -0.013493016, - -0.0019646063, - -0.011591836, - -0.011184303, - -0.0060989116, - -0.012645497 - ] + "name": "craft_1_crafting_table_and", + "description": "Craft 1 crafting table and 1", + "keywords": [], + "file": "craft_1_crafting_table_and.js" }, { "quality": 0.9, "successCount": 2, "failureCount": 0, - "name": "craft_1_furnace", - "description": "Craft 1 furnace", + "name": "craft_4_oak_planks_from", + "description": "Craft 4 oak planks from 1 oak log", + "keywords": [], + "file": "craft_4_oak_planks_from.js" + }, + { + "quality": 0.9, + "successCount": 4, + "failureCount": 0, + "name": "craft_4_sticks_using_2", + "description": "Craft 4 sticks using 2 spruce planks", + "keywords": [], + "file": "craft_4_sticks_using_2.js" + }, + { + "quality": 0.8, + "successCount": 2, + "failureCount": 0, + "name": "mine_3_stone_blocks", + "description": "Mine 3 stone blocks", "keywords": [ - "craft", - "furnace" + "stone", + "mine", + "cobblestone" ], - "file": "craft_1_furnace.js", - "embedding": [ - -0.0025208306, - 0.007100302, - 0.02145758, - -0.081568986, - 0.00026990334, - 0.014575351, - -0.010202329, - 0.00090188015, - -0.0014306782, - 0.010806984, - -0.012601959, - -0.010289142, - 0.011154386, - -0.013164198, - 0.13324012, - -0.026585562, - -0.0044519943, - -0.01100138, - -0.0070631388, - -0.016299285, - -0.0070998985, - -0.0038770437, - 0.010145559, - -0.0063288743, - -0.022058576, - 0.005769116, - 0.012565267, - 0.035863634, - 0.0066887694, - -0.02338372, - -0.015869495, - 0.013377934, - 0.011594688, - -0.0047059087, - 0.00043734926, - 0.042720433, - 0.0033743873, - -0.024485702, - -0.008951777, - -0.0011563986, - -0.0024942658, - 0.013351034, - -0.027907405, - 0.0014865807, - 0.0006932145, - 0.012778904, - 0.0054492354, - -0.007970387, - -0.0073966263, - 0.023264153, - 0.012055072, - 0.01663701, - -0.013996718, - -0.20549421, - -0.021564808, - 0.002374521, - 0.0033671076, - -0.009332086, - 0.009220321, - 0.004928015, - -0.015248218, - 0.035127565, - -0.013131471, - -0.012957142, - 0.0035555437, - 0.013413267, - 0.014048731, - -0.010826314, - -0.004236324, - -0.025583897, - -0.0076165493, - -0.0038660164, - -0.0014189924, - -0.0066428594, - -0.006814571, - -0.016770754, - -0.002918558, - 0.008532942, - 0.0030743396, - 0.04916992, - -0.025991471, - -0.029621901, - -0.026708703, - 0.0042343982, - -0.0033501072, - -0.016115759, - -0.02437391, - -0.006078484, - -0.007531796, - -0.0032843777, - -0.020237457, - 0.021963118, - -0.0062030083, - 0.02541696, - 0.0048709926, - -0.0072250008, - 0.008215991, - -0.0028721022, - -0.020764377, - 0.012641059, - -0.017374488, - -0.009014989, - 0.009622376, - 0.016591128, - 0.011317475, - -0.03518837, - -0.002774053, - 0.0207054, - -0.018282838, - 0.013398719, - 0.011352644, - 0.022925695, - 0.015267075, - 0.021030314, - 0.034508634, - -0.18448934, - 0.007205186, - 0.009510242, - -0.00045984113, - -0.019655766, - -0.0041627623, - 0.005530203, - 0.0025168231, - 0.016771818, - 0.0098422645, - 0.020854343, - -0.007365507, - -0.010047206, - 0.011954278, - -0.0031570483, - -0.027194493, - -0.015102907, - -0.0026603583, - 0.049471524, - -0.0019158267, - 0.016582463, - -0.0077363956, - -0.0007549247, - -0.011264439, - -0.0076418254, - -0.005456952, - -0.003715415, - -0.0077097304, - -0.013096999, - -0.031742033, - -0.0032050803, - 0.021209722, - 0.015527272, - -0.026981477, - -0.010691748, - 0.007344216, - -0.01837648, - 0.013268245, - 0.000044478118, - -0.015202183, - -0.026299838, - 0.005332404, - 0.005822809, - 0.006940666, - 0.004747826, - -0.009195601, - 0.017567052, - 0.0011552055, - 0.01680624, - 0.005068277, - -0.006857826, - 0.0028000963, - 0.0075048725, - 0.0024217083, - 0.018977666, - 0.0145439515, - -0.011783525, - 0.012716528, - 0.010831979, - 0.013715283, - 0.016253985, - 0.01533178, - -0.0035927442, - 0.00024893525, - 0.013804469, - 0.017536953, - -0.0012439718, - 0.009882515, - -0.0073211035, - 0.008761407, - 0.0025287971, - -0.0012674568, - -0.018546227, - 0.01625162, - -0.0030045956, - -0.02095387, - 0.014626284, - 0.0067883506, - -0.010637346, - -0.017629158, - 0.0018650811, - -0.013933211, - -0.015854463, - 0.022147248, - 0.014506442, - 0.007672165, - 0.0047792536, - -0.012270095, - 0.0024042618, - -0.0148543175, - -0.0010837845, - -0.00882358, - 0.006781289, - 0.0014569333, - 0.038719837, - -0.0031101264, - -0.023993222, - 0.008953705, - 0.018215211, - -0.008624651, - -0.01610682, - -0.012783186, - 0.013780874, - 0.022655819, - -0.013213629, - -0.0026951982, - 0.021116143, - 0.009987983, - 0.002296942, - -0.005758972, - -0.0059465594, - -0.015877603, - -0.0020782803, - -0.010822659, - 0.023812488, - 0.015419595, - -0.017819455, - 0.00032589366, - -0.014173285, - -0.0049748137, - -0.0031569693, - -0.0032786161, - 0.0155396685, - 0.018338861, - 0.0014765172, - -0.008892106, - -0.027125718, - 0.023748243, - 0.0042592813, - 0.042720772, - -0.0008118896, - 0.00820532, - -0.0024833656, - -0.00046389436, - -0.021059347, - -0.0070544574, - -0.00014323705, - -0.005012076, - 0.014434371, - 0.006616415, - -0.004388796 - ] + "file": "mine_3_stone_blocks.js" }, { "quality": 0.8, "successCount": 1, "failureCount": 0, - "name": "place_1_dirt_block", - "description": "Place 1 dirt block", - "keywords": [ - "place", - "dirt", - "block" - ], - "file": "place_1_dirt_block.js", - "embedding": [ - -0.016017258, - 0.005093691, - 0.026214298, - -0.075435266, - 0.00014921764, - 0.0056521622, - -0.00091210863, - -0.013738155, - 0.0033192998, - -0.00068881013, - 0.0017365707, - 0.0019988623, - -0.002059478, - 0.013923224, - 0.12509982, - -0.019473707, - -0.022464074, - 0.0074563636, - -0.029842015, - -0.024361024, - 0.010346849, - -0.024945676, - 0.013561414, - 0.0015855088, - -0.0054463204, - 0.009730315, - 0.026298817, - 0.030804055, - 0.028264746, - -0.024320807, - -0.017925814, - 0.008614464, - 0.013920839, - -0.009319148, - -0.00032980603, - 0.021709496, - 0.006793095, - -0.007144079, - 0.00042712622, - 0.008015345, - -0.02019519, - 0.010533639, - -0.013967947, - -0.0024879414, - 0.006439238, - 0.01108503, - -0.0018091298, - -0.026751814, - -0.0061320397, - 0.010147073, - 0.005331013, - -0.0006178114, - -0.006461464, - -0.18695195, - -0.006646071, - 0.0066493605, - 0.014968751, - 0.0027412195, - 0.006628584, - -0.011563797, - -0.008724545, - 0.026461415, - -0.024355883, - -0.013034396, - -0.0029192585, - -0.00924693, - 0.0027906676, - 0.0038113531, - -0.015477297, - 0.0014288585, - -0.0064635836, - 0.015282028, - 0.009806397, - -0.011179427, - -0.023584815, - -0.0026168525, - 0.0067788693, - 0.007141586, - -0.005674515, - 0.036141865, - -0.022851987, - -0.025745956, - -0.011730755, - -0.0034711976, - 0.005884866, - -0.013057022, - -0.02470223, - 0.014708236, - -0.024353664, - -0.0093248375, - -0.028875288, - 0.014438111, - 0.011198552, - 0.022975694, - 0.014209246, - 0.028622504, - 0.006839073, - -0.010516038, - -0.0042351577, - -0.0048435777, - -0.025874406, - -0.0005060658, - -0.022506686, - 0.005613487, - 0.016158866, - -0.034951307, - 0.009199977, - 0.007533873, - -0.030946672, - 0.008397623, - 0.021589058, - 0.008648251, - -0.0019594391, - 0.0047029466, - 0.027727153, - -0.20358773, - 0.020747399, - 0.013164517, - 0.0016776848, - -0.0095348125, - -0.02214526, - 0.0043913904, - -0.0039429306, - 0.017203491, - 0.019730082, - 0.007138333, - -0.007898127, - 0.0053278226, - 0.0053967373, - -0.004599952, - -0.0075751594, - -0.003308201, - -0.019331949, - 0.030124463, - 0.01701226, - 0.024022758, - -0.019091774, - -0.0016821583, - -0.011678883, - 0.0063853674, - -0.0077743432, - 0.012844202, - -0.0022605953, - -0.0010869642, - -0.023165973, - 0.014784552, - 0.0026440152, - 0.00967066, - -0.027305387, - -0.016509356, - -0.012551923, - -0.022281937, - 0.0035898187, - -0.009059005, - -0.0069125174, - -0.036609735, - 0.013499239, - -0.014686944, - 0.013874987, - 0.009937132, - 0.0074111745, - 0.014002562, - 0.018713305, - 0.00806988, - -0.0036829407, - -0.008325397, - 0.006320776, - 0.0023809143, - -0.0054400414, - 0.023569774, - 0.004032478, - -0.023765642, - -0.006817055, - 0.0073549007, - 0.033402234, - -0.014149223, - -0.008280875, - 0.0028811109, - 0.009027342, - 0.0141416695, - 0.0037276396, - 0.006550095, - 0.012741073, - -0.0065939217, - -0.008770757, - -0.018669983, - -0.019228509, - -0.025292974, - 0.024130775, - -0.0003123845, - 0.0029166488, - 0.005450579, - -0.00712424, - -0.021892887, - 0.008721487, - -0.017637707, - -0.011838177, - -0.011888323, - 0.028699456, - -0.0029309555, - 0.0073273974, - -0.014660514, - 0.0011028724, - -0.021864494, - -0.003995088, - 0.010961044, - -0.013054617, - -0.007865885, - 0.0043744063, - 0.03268201, - 0.014472039, - -0.034083996, - 0.0039161006, - 0.017894689, - 0.010744463, - -0.010603256, - -0.025631044, - 0.0046792384, - 0.024187325, - -0.00037421018, - -0.0040566176, - 0.0038711426, - 0.029389534, - 0.007824527, - -0.023698283, - -0.003626236, - -0.03232702, - -0.0055428394, - -0.0010986478, - 0.02511833, - -0.00037622082, - -0.022228923, - -0.0211344, - -0.0293798, - -0.019793935, - -0.012691858, - -0.028425904, - 0.013873837, - 0.016868284, - -0.00008504688, - 0.007412513, - -0.022609714, - 0.004280266, - -0.009213309, - 0.026308909, - 0.00036148183, - 0.00858115, - 0.011150173, - -0.015233229, - -0.027171524, - -0.011195739, - -0.0060588107, - -0.019615611, - -0.016320875, - -0.0056612855, - 0.009644742 - ] + "name": "pick_up_the_10_nearby", + "description": "Pick up the 10 nearby items on the ground", + "keywords": [], + "file": "pick_up_the_10_nearby.js" }, { "quality": 0.8, - "successCount": 2, + "successCount": 1, "failureCount": 0, - "name": "place_1_furnace", - "description": "Place 1 furnace", + "name": "collect_the_wooden_pickaxe_and", + "description": "Collect the wooden pickaxe and 2 oak doors from the chest at 855, 64, 259", "keywords": [ - "furnace", - "place", - "iron" + "chest", + "pickaxe", + "door" ], - "file": "place_1_furnace.js", - "embedding": [ - -0.0019469026, - 0.0117400745, - 0.035329737, - -0.082484074, - -0.002939832, - 0.00931904, - -0.012280075, - 0.0033138986, - 0.0023754921, - 0.0048486767, - -0.013876259, - -0.0029604833, - 0.012713506, - 0.00543314, - 0.12566194, - -0.018442666, - 0.0014445476, - -0.0024898718, - -0.009748994, - -0.03157432, - -0.012344147, - -0.01019968, - -0.0035420228, - -0.016038833, - -0.0106582455, - 0.006423657, - 0.026434744, - 0.028998261, - 0.031080727, - -0.03947266, - -0.010597577, - 0.008714951, - 0.004961918, - -0.005348603, - -0.00692362, - 0.032555286, - 0.005495458, - -0.026466992, - 0.0030237725, - 0.0059205927, - -0.008239585, - 0.0126683535, - -0.016781775, - -0.005929034, - -0.008504033, - 0.008210737, - 0.010542571, - -0.017833801, - -0.009473239, - 0.014435226, - 0.008617231, - 0.0151955355, - -0.020712227, - -0.18184586, - -0.015874948, - 0.00097318605, - 0.024949953, - 0.0053717853, - 0.010347868, - 0.0033875273, - -0.013875555, - 0.033306777, - -0.013831192, - -0.017822487, - -0.0063198144, - 0.00885063, - 0.025373079, - -0.011477206, - -0.0012323427, - -0.0155272335, - 0.00034612665, - -0.004327273, - -0.003304293, - -0.01345767, - 0.0039369604, - -0.005917009, - 0.009006642, - -0.0031027745, - -0.0021861363, - 0.0424425, - -0.03411788, - -0.021693578, - -0.027416611, - 0.010462248, - 0.0021789516, - -0.023972677, - -0.03835603, - 0.0028137937, - -0.023855463, - -0.004277928, - -0.021410411, - 0.02274626, - 0.00450304, - 0.023418372, - 0.012244982, - 0.0068187853, - 0.01628691, - -0.008459059, - -0.009846243, - -0.007644961, - -0.01840889, - -0.001542405, - 0.0029879895, - 0.019875592, - -0.00063793425, - -0.04365955, - 0.005718224, - 0.030296983, - -0.017369257, - -0.0006240766, - 0.006350123, - 0.0243903, - 0.013722622, - 0.0077866954, - 0.03384619, - -0.18291688, - 0.0018310604, - 0.018915998, - -0.00067776046, - -0.022030955, - -0.015862398, - -0.0025009392, - 0.005979715, - 0.009222729, - 0.014133595, - 0.008813452, - -0.00044463566, - -0.0023373184, - 0.007980969, - -0.0012301945, - -0.027675973, - -0.009039655, - -0.013189233, - 0.046971586, - 0.0069821803, - 0.010850116, - -0.01465224, - 0.015587637, - -0.004181156, - -0.012926565, - -0.0058215437, - 0.0008837366, - -0.004809772, - -0.012000596, - -0.0342525, - 0.024078572, - 0.0051423735, - 0.014846591, - -0.034070957, - -0.008341004, - -0.0015153477, - -0.016702667, - 0.010118928, - -0.01051708, - -0.008352632, - -0.0308074, - 0.0056193057, - -0.0034286138, - 0.004566579, - 0.014153069, - -0.0050597116, - 0.014427252, - 0.0021659248, - 0.0053093326, - 0.004305379, - -0.011542611, - 0.006750027, - 0.005711976, - -0.0047505666, - 0.02523549, - 0.015833063, - -0.02164199, - 0.0045071435, - 0.012515301, - 0.015280463, - 0.00753913, - 0.012973443, - -0.013900852, - 0.0059306817, - 0.026686551, - 0.009223291, - -0.005250908, - 0.019345177, - -0.0044047814, - 0.0060871327, - -0.013253941, - -0.0018646638, - -0.022355728, - 0.015484811, - -0.010600659, - -0.011637086, - 0.016250942, - 0.010320307, - -0.0065827784, - -0.006870601, - -0.0064162435, - -0.021068277, - -0.008322231, - 0.010458843, - -0.005952233, - 0.0034407743, - -0.015300494, - -0.0025074193, - -0.0062788283, - -0.016601272, - 0.002170249, - -0.013948281, - -0.003868964, - 0.0058727795, - 0.02761051, - -0.00067685806, - -0.02152756, - 0.010622865, - 0.03203106, - -0.00090925343, - -0.01705756, - -0.014379159, - 0.0045702313, - 0.010829618, - -0.0036573624, - 0.0037336564, - 0.009998834, - 0.01650902, - 0.011670803, - -0.010343945, - -0.018547853, - -0.0066719474, - 0.0035112388, - -0.01822778, - 0.027229205, - 0.015937785, - -0.027413458, - 0.013602966, - -0.024502277, - -0.01754839, - 0.00004568868, - -0.013488762, - 0.0215237, - 0.006023295, - 0.0064398367, - -0.00670587, - -0.019781824, - 0.009874758, - -0.009257059, - 0.038718708, - -0.002794335, - -0.00017078024, - 0.0057217954, - 0.0041175005, - -0.013134127, - -0.007440682, - -0.005451238, - -0.007700977, - -0.004598621, - -0.0040638745, - 0.011100812 - ] + "file": "collect_the_wooden_pickaxe_and.js" }, { "quality": 0.9, "successCount": 2, "failureCount": 0, - "name": "craft_1_stone_pickaxe", - "description": "Craft 1 stone pickaxe", + "name": "mine_3_stone_blocks_to", + "description": "Mine 3 stone blocks to collect cobblestone", "keywords": [ - "craft", - "stone_pickaxe", - "cobblestone", - "stick" + "mine", + "stone", + "cobblestone" ], - "file": "craft_1_stone_pickaxe.js", - "embedding": [ - -0.019004501, - 0.0181837, - 0.0063691237, - -0.05968848, - -0.004056257, - 0.024818627, - -0.0053021912, - 0.0036103849, - -0.014214827, - 0.010784491, - -0.019232662, - -0.011746745, - 0.005913794, - -0.0085818255, - 0.10110115, - -0.043373358, - 0.0022886298, - -0.019881751, - -0.023499334, - -0.008106236, - 0.0029285613, - -0.006959101, - 0.0033144758, - -0.021878269, - -0.0043580704, - -0.022743888, - 0.0053380504, - 0.019806636, - 0.015974907, - -0.010616235, - -0.0012387799, - 0.020569533, - 0.0044328356, - -0.01865361, - -0.0036252204, - 0.02205048, - 0.0007281127, - -0.013279014, - -0.0048877927, - 0.00032122358, - -0.007012914, - 0.016431388, - -0.022660289, - 0.003399813, - 0.012729569, - 0.014835188, - -0.0040168636, - -0.016698122, - 0.0012718198, - 0.025318509, - 0.029905612, - 0.027538626, - -0.0029761537, - -0.19897582, - -0.020232264, - -0.00009798944, - -0.0026015956, - -0.018594746, - 0.020852145, - -0.0056277295, - -0.010157872, - 0.028474705, - -0.004406195, - -0.009246658, - 0.015150418, - 0.007795914, - 0.0150095895, - 0.004141193, - -0.0035834967, - -0.015545763, - 0.0022016068, - 0.016352521, - 0.0011613083, - -0.009691057, - -0.020820856, - -0.022954691, - 0.0043664053, - 0.008576479, - -0.027906964, - 0.07445858, - -0.01345375, - -0.033394624, - -0.012188235, - 0.0006567392, - 0.0044044964, - -0.0018053581, - -0.008441734, - 0.014348443, - -0.023937482, - -0.00798215, - -0.015345239, - 0.018967327, - -0.008386825, - 0.022186125, - 0.019093623, - 0.0056482423, - -0.0043133614, - 0.00990153, - -0.013567528, - 0.0018681571, - 0.00089216785, - -0.009116783, - -0.0077335495, - 0.005689326, - 0.021893121, - -0.033826254, - -0.0072201686, - 0.009010104, - -0.02369975, - 0.02043015, - 0.01381387, - -0.0012439919, - 0.0082751755, - 0.039947055, - 0.028584145, - -0.1910453, - 0.0026824095, - -0.00083142455, - 0.008821368, - -0.008201108, - 0.0009616428, - 0.011507321, - -0.0066274423, - 0.037157506, - 0.004579566, - 0.016956571, - -0.009274529, - -0.0083606765, - 0.006139531, - -0.0007313882, - -0.029113011, - -0.0012103718, - -0.0038367773, - 0.010252621, - 0.014400053, - 0.020169966, - -0.0136216665, - -0.0017584648, - -0.0151122715, - 0.0028766838, - 0.003479045, - -0.00395318, - -0.0012913683, - -0.0055299248, - -0.013494213, - -0.009233052, - 0.00010077767, - 0.025638321, - -0.022845436, - -0.026355168, - 0.001985869, - -0.0006369582, - 0.00381504, - -0.020581447, - -0.034805812, - -0.03372862, - 0.0013281632, - 0.015584218, - 0.00502476, - 0.020985352, - 0.002347784, - 0.0132493405, - 0.018578576, - 0.020840615, - -0.0009824969, - -0.017225211, - 0.0008317012, - 0.000881638, - -0.0006203964, - 0.012909276, - -0.003097202, - -0.009168219, - 0.0036962612, - 0.00714447, - 0.0050577237, - -0.0021287934, - 0.015832296, - -0.006060407, - 0.020048948, - 0.011652021, - 0.015685126, - 0.032408815, - 0.01375, - -0.005087111, - 0.0013566909, - -0.0076334546, - -0.010239895, - -0.014261323, - 0.029622799, - 0.009419487, - -0.013226725, - 0.0020888874, - -0.025248095, - -0.016025139, - -0.0037293532, - -0.0051516024, - -0.035993047, - -0.007132675, - 0.038723662, - 0.0033767913, - 0.01461183, - -0.007957542, - 0.013703472, - 0.004013275, - -0.00068380643, - 0.008631591, - 0.0067258473, - -0.012883458, - 0.0058038686, - 0.030758848, - -0.0008637002, - -0.020418648, - 0.008236606, - 0.008604668, - 0.014196921, - -0.0027054888, - -0.007959469, - 0.011069509, - 0.022055335, - 0.0053360765, - -0.030060535, - 0.016084025, - 0.024830543, - -0.0031552457, - -0.02522462, - 0.002632193, - -0.033834383, - -0.00009781511, - -0.025020298, - 0.018193433, - -0.005899742, - -0.011245851, - -0.002877395, - -0.003912338, - -0.004497402, - 0.00468322, - -0.022704622, - 0.020134566, - 0.016238218, - -0.0130728055, - 0.017643431, - -0.0113283945, - -0.009845921, - 0.00039091808, - 0.031333245, - 0.011191892, - 0.0038037603, - 0.012856338, - -0.0062496555, - -0.018023506, - -0.022125708, - 0.008273994, - -0.011696248, - -0.0023257432, - 0.015046997, - -0.01619155 - ] + "file": "mine_3_stone_blocks_to.js" }, { - "quality": 0.8, + "quality": 0.9, "successCount": 1, "failureCount": 0, - "name": "mine_1_spruce_log", - "description": "Mine 1 spruce log", + "name": "craft_4_spruce_planks_from", + "description": "Craft 4 spruce planks from 1 spruce log", "keywords": [ - "mine", - "spruce_log", - "wood" + "spruce_planks", + "craft" ], - "file": "mine_1_spruce_log.js", - "embedding": [ - -0.00084763096, - 0.010573037, - 0.0022059595, - -0.08355614, - 0.0036836155, - 0.01680831, - 0.011725145, - -0.013280437, - 0.00037600452, - 0.017631866, - 0.008020602, - -0.012993261, - 0.00566679, - 0.005206396, - 0.12108951, - -0.03600952, - 0.0147165, - -0.014482263, - -0.0083278725, - -0.02519871, - -0.014314216, - -0.013074719, - 0.012866322, - -0.010735086, - -0.023850748, - -0.0010566256, - 0.015302071, - 0.028042898, - 0.032078825, - -0.019905712, - -0.013637151, - 0.020620331, - 0.012379402, - -0.013470362, - -0.0035392975, - 0.04563991, - 0.002652343, - -0.012662368, - -0.019941803, - 0.0023080152, - -0.006789464, - 0.023719197, - 0.0011233393, - -0.012751257, - -0.00058391056, - 0.024522332, - -0.009232187, - -0.01774464, - 0.0014305315, - 0.02134023, - 0.0111897215, - 0.013496815, - -0.010837557, - -0.20069061, - -0.0033032754, - 0.017563257, - -0.011091638, - 0.00035563257, - 0.01877536, - -0.0064442074, - -0.011052926, - 0.01617813, - -0.004584112, - -0.0070487102, - -0.004041001, - -0.0036763258, - 0.022312127, - 0.00024440666, - 0.0007248511, - -0.016140824, - -0.008466592, - 0.006662754, - -0.021945043, - -0.027901663, - -0.020324878, - -0.007611393, - 0.0028436077, - 0.0080090435, - -0.016525317, - 0.044429205, - -0.010223204, - -0.019257177, - -0.016701763, - 0.011446273, - 0.00052330724, - 0.009886898, - -0.036636937, - -0.012533743, - -0.023986896, - 0.003841434, - -0.011041183, - 0.026862783, - -0.026538217, - 0.0001662103, - 0.015739804, - -0.0010114072, - 0.00073224376, - 0.0010864447, - 0.008840941, - -0.014126497, - 0.0016112354, - -0.0132142855, - -0.0015924004, - -0.0021193882, - 0.031547036, - -0.01682884, - 0.007690652, - 0.0045768935, - -0.0081063565, - 0.0071366434, - 0.00444298, - -0.0035270478, - 0.016226217, - 0.033786297, - 0.016841952, - -0.19019048, - 0.020850018, - -0.0022606307, - 0.003927114, - 0.0041390033, - 0.0017744334, - -0.0009575115, - 0.009054796, - 0.0072982353, - -0.006547161, - 0.0057016513, - 0.008645868, - 0.0060998667, - 0.012463975, - -0.0123644965, - -0.018812774, - 0.010914849, - 0.00335443, - 0.01837539, - 0.004713462, - 0.00763516, - 0.0014131515, - 0.005265811, - 0.0006574445, - -0.0017273073, - 0.008056754, - -0.020355716, - -0.0019111728, - -0.00042600627, - -0.0135644255, - -0.0064866156, - 0.032682497, - -0.002290997, - -0.03072922, - -0.0150048155, - -0.012163235, - -0.029506218, - 0.010322618, - 0.0057224305, - -0.021374047, - -0.039482284, - -0.0055105668, - -0.009920932, - -0.024398552, - 0.007883322, - -0.0016524424, - 0.01291574, - 0.010927035, - 0.011931709, - 0.0018853078, - -0.012424782, - 0.0017044374, - 0.028541159, - 0.020170236, - 0.020690672, - -0.01093837, - -0.015598952, - 0.02730181, - -0.016262429, - 0.026179364, - 0.0023021435, - 0.0066536264, - -0.0018523677, - 0.010315826, - 0.024978017, - 0.012417789, - 0.0019560729, - -0.015380498, - 0.00481204, - 0.0016441705, - 0.00688091, - -0.0030150472, - -0.021935599, - 0.033635832, - 0.005759642, - 0.014983965, - 0.0016990431, - -0.007888227, - -0.022299152, - -0.0024808242, - -0.003058601, - -0.027152965, - -0.004951664, - 0.02582865, - -0.010893875, - -0.005265036, - -0.008696192, - 0.0064973086, - 0.010143801, - 0.0015160018, - 0.009332401, - 0.001572836, - -0.008731161, - 0.0071192505, - 0.046025135, - 0.015471994, - -0.03138164, - -0.01828988, - 0.011762008, - 0.017134428, - -0.0049162903, - -0.0076848683, - -0.0021325646, - 0.03458888, - 0.006630955, - -0.000028294551, - -0.013725669, - 0.02762294, - 0.0030100907, - -0.0023727175, - -0.0042223996, - -0.008615967, - -0.00059562473, - -0.016136872, - 0.030762713, - -0.027595675, - 0.004220614, - -0.0023445527, - -0.02265686, - -0.008635499, - -0.020446038, - -0.019672697, - 0.026103292, - 0.0066561447, - -0.015241601, - -0.010653819, - -0.029568996, - -0.004375129, - -0.024890613, - 0.013928872, - -0.0006352927, - -0.014281241, - 0.013350377, - 0.012462738, - -0.013881335, - 0.0068673724, - -0.004059944, - -0.0024523963, - 0.003838625, - 0.0098005915, - -0.015084251 - ] + "file": "craft_4_spruce_planks_from.js" }, { "quality": 0.8, "successCount": 1, "failureCount": 0, - "name": "mine_1_iron_ore_at", - "description": "Mine 1 iron ore at 1744, 34, 437", + "name": "mine_3_stone_blocks_with", + "description": "Mine 3 stone blocks with the wooden pickaxe", "keywords": [ "mine", - "iron_ore" + "stone", + "cobblestone" ], - "file": "mine_1_iron_ore_at.js", - "embedding": [ - -0.01372225, - 0.004059183, - 0.011304235, - -0.090499416, - -0.009252671, - -0.008155594, - 0.008033465, - -0.016532429, - -0.00007280703, - 0.011307838, - -0.013743513, - 0.008514529, - 0.006594507, - 0.019745903, - 0.13205417, - -0.02822198, - -0.013548611, - -0.008113772, - -0.028066687, - -0.012798342, - -0.028902179, - -0.025207728, - 0.008219903, - -0.03468048, - -0.000017565833, - -0.0022558586, - 0.019429555, - 0.040691383, - 0.042694278, - -0.026810713, - 0.0015518172, - 0.01677169, - -0.0004390176, - 0.0016048232, - 0.0048782523, - 0.036900915, - 0.0038449096, - -0.010138496, - -0.004062495, - 0.014171018, - -0.006054622, - 0.019761806, - -0.014942225, - -0.010258272, - -0.008927549, - 0.014581562, - 0.013403972, - -0.026489673, - 0.0014912544, - 0.009680806, - -0.0033253569, - 0.016669283, - -0.020761399, - -0.18704295, - -0.009300921, - 0.0021407795, - 0.016939225, - -0.025603028, - 0.02091199, - -0.0007673626, - -0.0092590675, - 0.050924666, - -0.005681188, - -0.029904637, - -0.008315171, - 0.0058893324, - 0.019511284, - -0.0034372623, - -0.004523533, - -0.016007936, - -0.020507002, - -0.001847478, - -0.0051634763, - -0.03765051, - -0.015942987, - -0.00031786627, - 0.0012363988, - 0.006524026, - -0.0064653475, - 0.03926034, - -0.03287275, - -0.023411212, - -0.0142705785, - 0.007941983, - -0.01428378, - 0.005940991, - -0.034200262, - 0.0018546514, - -0.00884821, - 0.0042215465, - -0.027547916, - 0.03805265, - 0.0018489907, - 0.03012172, - 0.010206362, - 0.014031614, - 0.012249617, - 0.0073840017, - 0.0010438743, - -0.03479139, - -0.03173633, - -0.012714397, - -0.014520185, - 0.008361998, - 0.016242534, - -0.024239287, - 0.008517623, - 0.010857794, - -0.011741813, - 0.0121832015, - 0.013988749, - -0.0047996407, - -0.007355356, - 0.016827356, - 0.01924584, - -0.17712125, - 0.012638077, - 0.0008558521, - 0.011819417, - 0.0050738286, - -0.013933054, - 0.004296257, - -0.0025218672, - 0.02404153, - 0.0043023825, - 0.0028200808, - -0.0068605985, - -0.0122056035, - 0.00849049, - -0.0029045877, - -0.005692725, - -0.0005106751, - 0.0011535329, - 0.031659182, - 0.005849639, - 0.024927618, - -0.0051066484, - 0.009472806, - -0.01268184, - -0.015200339, - -0.0038252748, - 0.016243156, - 0.004285144, - 0.011803892, - -0.027766638, - -0.006157635, - 0.011559499, - 0.014273446, - -0.0122215785, - -0.014911816, - -0.008921796, - -0.023252588, - 0.010245662, - -0.005769848, - -0.009771616, - -0.04264963, - 0.0073753507, - -0.0004007711, - 0.002337651, - 0.014008994, - 0.002707129, - 0.001531955, - 0.02329605, - 0.012307277, - -0.008603474, - -0.019278208, - -0.007450681, - 0.0009069849, - 0.00047302342, - 0.014518092, - 0.009675808, - -0.02989773, - 0.004753443, - -0.005796775, - 0.018954765, - -0.012222686, - 0.016645353, - -0.00025891815, - 0.0061879638, - 0.042691804, - 0.015450592, - -0.005276656, - -0.0043260185, - -0.0021301338, - -0.0072565004, - -0.010796746, - -0.0017105069, - -0.023953311, - 0.028129688, - -0.0035230904, - 0.002895801, - 0.00368768, - -0.01899391, - -0.025040338, - -0.012438299, - -0.013606569, - -0.043012656, - -0.020042656, - 0.037062306, - -0.014429359, - -0.011265924, - -0.014766858, - -0.01286219, - 0.0016137036, - -0.009834507, - 0.005853022, - -0.020527054, - 0.0022215813, - 0.01111572, - 0.03872472, - -0.0017351507, - -0.023004843, - -0.00037035154, - 0.020074865, - -0.006934742, - -0.025676314, - -0.006317234, - 0.0076795635, - 0.02234753, - -0.009832538, - 0.0031058057, - -0.007184451, - 0.031748254, - 0.0016532546, - -0.012320284, - -0.00042467125, - -0.0032515996, - 0.0060025393, - -0.013748409, - 0.028906122, - -0.006046367, - -0.015708923, - 0.0067231497, - -0.025375843, - -0.014917246, - -0.014857979, - -0.0033626128, - 0.019054722, - 0.008348239, - -0.0046702027, - -0.007131649, - -0.013513282, - 0.006493397, - -0.010028422, - 0.028523864, - -0.0263518, - 0.010039897, - 0.012278186, - -0.0039009666, - -0.0075456556, - -0.014759817, - -0.0005927608, - -0.017278284, - 0.00525559, - 0.0045794747, - -0.0023711508 - ] + "file": "mine_3_stone_blocks_with.js" + }, + { + "quality": 0.9, + "successCount": 4, + "failureCount": 0, + "name": "craft_1_stone_pickaxe_using", + "description": "Craft 1 stone pickaxe using 3 cobblestone", + "keywords": [], + "file": "craft_1_stone_pickaxe_using.js" + }, + { + "quality": 0.9, + "successCount": 1, + "failureCount": 0, + "name": "craft_1_furnace_using_8", + "description": "Craft 1 furnace using 8 cobblestone", + "keywords": [], + "file": "craft_1_furnace_using_8.js" + }, + { + "quality": 0.9, + "successCount": 1, + "failureCount": 0, + "name": "craft_4_sticks_using_spruce", + "description": "Craft 4 sticks using spruce planks", + "keywords": [], + "file": "craft_4_sticks_using_spruce.js" }, { "quality": 0.9, @@ -13583,805 +700,246 @@ "stone_pickaxe", "tool" ], - "file": "craft_a_stone_pickaxe.js", - "embedding": [ - -0.021896688, - 0.016565531, - 0.008840588, - -0.06036416, - 0.0036941604, - 0.012630163, - -0.00019223326, - 0.01592146, - -0.0019177143, - 0.010881934, - -0.03362295, - -0.008983632, - 0.0015035354, - -0.0027526668, - 0.108096994, - -0.046401788, - -0.006523308, - -0.009975734, - -0.01443375, - -0.01643935, - -0.0065339175, - -0.007139921, - -0.00850045, - -0.033383083, - -0.0066795084, - -0.011744265, - 0.008706275, - 0.025066568, - 0.020900015, - -0.014158068, - -0.0021525528, - 0.017500333, - 0.011945773, - -0.00786909, - -0.0016453103, - 0.021563763, - 0.0009407863, - -0.010266507, - 0.004550442, - 0.0068626734, - -0.0064811064, - 0.016897926, - -0.027895194, - 0.0054633124, - 0.017933331, - 0.008500399, - -0.0041833003, - -0.023076696, - 0.008163116, - 0.018401138, - 0.036098946, - 0.027403884, - -0.016475206, - -0.19793725, - -0.016741024, - 0.0018753964, - -0.0044405204, - -0.012658733, - 0.020264672, - -0.0034097661, - -0.011757583, - 0.027481956, - 0.003912758, - -0.009448034, - 0.03149312, - 0.0054564048, - 0.015058287, - 0.0012470037, - -0.01683953, - -0.01385752, - 0.0070684697, - 0.0026489329, - 0.0014490641, - -0.008669795, - -0.015404075, - -0.026564425, - -0.0034822235, - 0.015996214, - -0.023762973, - 0.060780663, - -0.012007471, - -0.035199445, - -0.008980979, - -0.0042553595, - 0.0008773234, - -0.0024023713, - -0.0000018049897, - 0.016764037, - -0.02761745, - -0.00650539, - -0.0099829035, - 0.012170023, - -0.0020925344, - 0.031501874, - 0.030560633, - 0.00025036663, - -0.009830447, - 0.0029058522, - -0.017740259, - 0.007594814, - -0.0013063413, - -0.0075655254, - -0.011145341, - 0.012309573, - 0.016880384, - -0.03419426, - -0.004769189, - 0.017434834, - -0.018127726, - 0.021379301, - 0.022494627, - 0.009823664, - -0.0020408954, - 0.041494172, - 0.029219931, - -0.1904981, - -0.0023198593, - 0.0031468328, - 0.012810333, - 0.00051893125, - 0.0008588162, - 0.009917993, - -0.002698817, - 0.028391747, - 0.012200354, - 0.019186653, - -0.010598023, - -0.0031262587, - -0.005593714, - -0.0012092425, - -0.025856236, - 0.0010625087, - 0.0006910225, - 0.011826762, - 0.0129906805, - 0.018350443, - -0.01687098, - -0.0006390623, - -0.018327052, - 0.0007066725, - 0.0035656337, - -0.0016039629, - 0.003926891, - -0.0032172622, - -0.013492789, - -0.015025886, - 0.0029136739, - 0.016475873, - -0.011002369, - -0.026151953, - 0.00067743845, - -0.015936514, - 0.00349155, - -0.02520142, - -0.035920393, - -0.038885422, - -0.01126345, - 0.008195528, - 0.000664154, - 0.025880836, - -0.004244307, - 0.0115515385, - 0.01506558, - 0.02405102, - -0.0063943886, - -0.022545483, - 0.0048122676, - -0.009159511, - -0.00076600525, - 0.018085862, - -0.008703135, - -0.009551779, - -0.0042560194, - 0.0052999756, - 0.0026858663, - -0.0021844967, - 0.01414031, - -0.008987263, - 0.0147508895, - 0.011447982, - 0.020047775, - 0.027626336, - 0.014935033, - -0.008026423, - 0.005202415, - -0.019953065, - -0.008944453, - -0.019675903, - 0.025641691, - -0.0023861523, - -0.010996822, - 0.0069176294, - -0.015114879, - -0.016230188, - -0.009958904, - -0.007661782, - -0.03618924, - -0.0018481571, - 0.029964615, - 0.013109486, - 0.006719712, - -0.019078128, - 0.013797461, - 0.0016886657, - -0.0018231681, - 0.010172853, - -0.0010682688, - -0.020837037, - 0.008613132, - 0.024464635, - 0.006340982, - -0.028866084, - 0.002782464, - -0.0013422203, - 0.011708825, - 0.0013376944, - 0.0004529895, - 0.0017721545, - 0.016499478, - -0.0008456062, - -0.024234733, - 0.017520973, - 0.026543463, - -0.0007498872, - -0.016215602, - -0.001254037, - -0.035864864, - -0.0003259053, - -0.01961339, - 0.014140755, - -0.000033729815, - -0.012588837, - -0.013017633, - -0.006853149, - -0.013672676, - 0.0078063295, - -0.022227686, - 0.01702339, - 0.015147448, - -0.016524334, - 0.012939888, - -0.0032268127, - 0.0018977303, - -0.0019036856, - 0.0216812, - -0.0025865664, - 0.013216558, - 0.014835126, - -0.0029073078, - -0.025718184, - -0.014595626, - 0.01083359, - -0.015530452, - 0.0014128686, - 0.009952866, - -0.014940649 - ] + "file": "craft_a_stone_pickaxe.js" }, { - "quality": 0.8, - "successCount": 1, + "quality": 0.9, + "successCount": 2, "failureCount": 0, - "name": "go_back_to_the_surface", - "description": "go back to the surface", + "name": "mine_1_oak_log_at", + "description": "Mine 1 oak log at 798, 70, 227", "keywords": [ - "back", - "the", - "surface" + "mine", + "oak_log", + "wood" ], - "file": "go_back_to_the_surface.js", - "embedding": [ - -0.034260973, - 0.0049958555, - 0.0109438095, - -0.09539272, - -0.014430383, - -0.0037798572, - -0.0033713744, - -0.006628979, - 0.008195972, - 0.007917352, - 0.0022581446, - -0.008272861, - 0.0043288367, - -0.010336631, - 0.17660058, - -0.022514654, - 0.004497497, - 0.0018558383, - -0.01820139, - -0.010504974, - 0.005906092, - 0.0058933394, - -0.021529503, - -0.024766993, - 0.0076513872, - -0.009247827, - 0.018568702, - 0.025925443, - 0.052261982, - -0.010680913, - 0.0058660423, - 0.015845038, - 0.0027719655, - -0.0068292306, - -0.0057168184, - 0.016803252, - 0.00078058965, - -0.0035229865, - 0.0049397107, - 0.001891153, - -0.010503766, - 0.0017342191, - 0.005112446, - -0.00090994616, - 0.015413255, - -0.005721782, - -0.0010083362, - -0.003665293, - 0.011963811, - 0.029041143, - 0.0114102615, - -0.009983397, - -0.0011439678, - -0.22080651, - -0.010565059, - 0.0026434986, - 0.01165449, - -0.0026081246, - -0.017283743, - -0.012826576, - -0.022401724, - 0.010943564, - -0.015682977, - -0.023657382, - -0.0058311047, - 0.0023969628, - 0.025445666, - 0.003057906, - -0.018234419, - 0.012329372, - 0.007104605, - 0.010169757, - -0.013782413, - 0.0011343136, - -0.0005074189, - -0.018628059, - 0.0024873526, - 0.017718226, - 0.0148183545, - 0.024863744, - -0.013818857, - -0.025243228, - 0.0085010305, - -0.01590797, - 0.012391195, - -0.0005630964, - -0.0017198616, - 0.0023579805, - -0.005585224, - 0.02285389, - -0.01187123, - 0.0040853936, - 0.025840964, - 0.015396927, - -0.0059823794, - 0.011989501, - -0.021995401, - 0.0065526864, - -0.012368256, - -0.032608222, - -0.02711317, - -0.01469501, - -0.014779357, - -0.0013235155, - 0.031470932, - 0.0128290905, - -0.0031773923, - -0.00287703, - -0.0039988677, - 0.014107655, - 0.0075182403, - 0.0058454364, - -0.0073467204, - -0.004741229, - 0.022236725, - -0.1708971, - 0.0191833, - -0.017329715, - 0.022016931, - 0.02486661, - -0.0129347555, - -0.012201392, - 0.008177756, - 0.024399681, - -0.020268656, - 0.0061750486, - 0.014368483, - -0.019196343, - -0.014802689, - 0.008932383, - -0.0049538636, - 0.013421968, - 0.0059451098, - 0.0050721145, - 0.012275119, - 0.015872976, - -0.013769333, - -0.011381176, - -0.025159927, - -0.023299344, - -0.0076212273, - 0.0013827261, - 0.01385153, - 0.006980321, - -0.0033125859, - -0.0011721137, - -0.013345492, - 0.0051810495, - 0.006312578, - -0.023695283, - -0.018290907, - -0.010568042, - 0.0009944715, - -0.023535028, - 0.0021791444, - -0.033339772, - -0.0070338547, - 0.0071397102, - -0.004624041, - 0.016650936, - -0.0014922351, - -0.016810346, - 0.014455689, - -0.012969103, - 0.020518955, - -0.0150106605, - 0.009641271, - 0.0067121387, - 0.015262616, - 0.031563643, - -0.008999475, - -0.007730702, - -0.01741119, - -0.0050510555, - -0.0072770263, - 0.007924123, - 0.0071577304, - 0.00007807428, - 0.0044118576, - 0.0174908, - 0.012752262, - 0.014014046, - 0.0041754786, - -0.026281597, - 0.001457801, - -0.027983613, - -0.023201317, - -0.0003109182, - 0.0018527065, - -0.0075451545, - -0.0059345104, - 0.01663328, - -0.014007785, - -0.020154852, - -0.0075953556, - -0.02435244, - 0.006294268, - 0.011700487, - 0.01768926, - 0.0030904466, - 0.013534127, - 0.020686459, - 0.00835111, - -0.016815493, - -0.012890838, - -0.008724654, - 0.009048979, - 0.009414283, - 0.021728978, - 0.00040665976, - -0.01772584, - -0.03506308, - 0.01394671, - -0.020351052, - 0.024681117, - -0.006457325, - -0.0039456333, - -0.0149052935, - 0.00010733104, - -0.020240879, - 0.0059483964, - 0.0016233481, - -0.00014167897, - 0.03188672, - -0.001509066, - -0.025423909, - 0.0075211427, - -0.018498132, - 0.00036288236, - -0.004286662, - -0.004140474, - 0.0011336475, - 0.006731382, - -0.021062125, - -0.02396284, - 0.0038906366, - 0.0058532073, - 0.002556355, - 0.011611096, - 0.022191668, - 0.00601759, - -0.012627994, - 0.017967133, - -0.010015153, - -0.0025158164, - -0.0032819307, - -0.024306078, - 0.022370633, - -0.024087895, - -0.009664423, - 0.014559889, - -0.012765409, - -0.0020056413, - -0.00032657298, - 0.024697606, - -0.007223404 - ] + "file": "mine_1_oak_log_at.js" }, { - "quality": 0.8, + "quality": 0.9, "successCount": 1, "failureCount": 0, - "name": "mine_10_dirt_blocks", - "description": "Mine 10 dirt blocks", + "name": "place_the_crafting_table_and", + "description": "Place the crafting table and craft 4 sticks", "keywords": [ - "mine", - "dirt" + "crafting", + "sticks", + "tools" ], - "file": "mine_10_dirt_blocks.js" + "file": "place_the_crafting_table_and.js" }, { "quality": 0.9, "successCount": 1, "failureCount": 0, - "name": "craft_1_crafting_table_using", - "description": "Craft 1 crafting table using spruce logs", - "keywords": [ - "craft", - "crafting_table", - "spruce_log" - ], - "file": "craft_1_crafting_table_using.js" + "name": "craft_24_oak_planks_from", + "description": "Craft 24 oak planks from 6 oak logs", + "keywords": [], + "file": "craft_24_oak_planks_from.js" }, { "quality": 0.8, "successCount": 1, "failureCount": 0, - "name": "mine_5_shortgrass_to_get", - "description": "Mine 5 short_grass to get wheat seeds", - "keywords": [ - "mine", - "short_grass", - "seeds" - ], - "file": "mine_5_shortgrass_to_get.js" + "name": "pick_up_the_items_located", + "description": "Pick up the items located 4 meters away", + "keywords": [], + "file": "pick_up_the_items_located.js" }, { "quality": 0.8, - "successCount": 1, + "successCount": 3, "failureCount": 0, - "name": "mine_20_oak_logs", - "description": "Mine 20 oak logs", + "name": "explore_the_ocean_to_find", + "description": "Explore the ocean to find an island", "keywords": [ - "mine", - "oak", - "logs" + "explore", + "the", + "ocean", + "find", + "island" ], - "file": "mine_20_oak_logs.js" + "file": "explore_the_ocean_to_find.js" }, { - "quality": 0.8, + "quality": 0.9, "successCount": 1, "failureCount": 0, - "name": "build_a_5x5_house_using", - "description": "Build a 5x5 house using oak planks and an oak door", + "name": "craft_1_crafting_table", + "description": "Craft 1 crafting table", "keywords": [ - "build", - "5x5", - "house", - "using", - "oak", - "planks", - "and", - "oak", - "door" + "craft", + "crafting", + "table" ], - "file": "build_a_5x5_house_using.js" + "file": "craft_1_crafting_table.js" }, { "quality": 0.9, "successCount": 1, "failureCount": 0, - "name": "craft_40_oak_planks", - "description": "Craft 40 oak planks", + "name": "craft_12_oak_planks", + "description": "Craft 12 oak planks", "keywords": [ "craft", "oak", "planks" ], - "file": "craft_40_oak_planks.js" + "file": "craft_12_oak_planks.js" }, { - "quality": 0.8, + "quality": 0.9, "successCount": 1, "failureCount": 0, - "name": "idleandlookaround", - "description": "idle_and_look_around", + "name": "craft_1_oak_boat", + "description": "Craft 1 oak boat", "keywords": [ - "idle", - "guard", - "watch" + "craft", + "oak", + "boat" ], - "file": "idleandlookaround.js" + "file": "craft_1_oak_boat.js" }, { "quality": 0.8, "successCount": 1, "failureCount": 0, - "name": "patrolsquare", - "description": "patrol_square", + "name": "explore_to_find_an_island", + "description": "Explore to find an island", "keywords": [ - "patrol", - "guard", - "security" + "explore", + "find", + "island" ], - "file": "patrolsquare.js" + "file": "explore_to_find_an_island.js" }, { "quality": 0.9, "successCount": 1, "failureCount": 0, - "name": "craft_4_oak_planks", - "description": "Craft 4 oak planks", + "name": "mine_2_oak_logs", + "description": "Mine 2 oak logs", "keywords": [ - "craft", - "oak_planks", - "wood" + "mine", + "oak", + "logs" ], - "file": "craft_4_oak_planks.js" + "file": "mine_2_oak_logs.js" }, { - "quality": 0.8, + "quality": 0.9, "successCount": 1, "failureCount": 0, - "name": "findawatersource", - "description": "find_a_water_source", + "name": "craft_8_oak_planks", + "description": "Craft 8 oak planks", "keywords": [ - "find", - "water", - "source" + "craft", + "oak", + "planks" ], - "file": "findawatersource.js" + "file": "craft_8_oak_planks.js" }, { "quality": 0.9, "successCount": 1, "failureCount": 0, - "name": "mine_1_brownmushroom", - "description": "Mine 1 brown_mushroom", + "name": "place_crafting_table_and_craft", + "description": "Place crafting table and craft 1 stone pickaxe using 3 cobblestone and 2 sticks", "keywords": [ - "mine", - "brown_mushroom" + "craft", + "stone_pickaxe", + "upgrade" ], - "file": "mine_1_brownmushroom.js" + "file": "place_crafting_table_and_craft.js" }, { - "quality": 0.8, - "successCount": 1, - "failureCount": 0, - "name": "mine_1_dirt_block", - "description": "Mine 1 dirt block", + "name": "explore_100_blocks_to_the", + "description": "Explore 100 blocks to the west", "keywords": [ - "mine", - "dirt", - "block" + "explore", + "walk", + "west" ], - "file": "mine_1_dirt_block.js" - }, - { + "file": "explore_100_blocks_to_the.js", "quality": 0.8, - "successCount": 1, - "failureCount": 0, - "name": "build_me_a_dirt_house", - "description": "build me a dirt house", - "keywords": [ - "build", - "dirt", - "house" - ], - "file": "build_me_a_dirt_house.js" + "successCount": 4, + "failureCount": 0 }, { "quality": 0.8, "successCount": 1, "failureCount": 0, - "name": "challenge_stranger_barley_to_combat", - "description": "Challenge stranger Barley to combat", + "name": "explore_100_blocks_to_find", + "description": "Explore 100 blocks to find a village", "keywords": [ - "challenge", - "guard", - "combat", - "player_interaction" + "explore", + "village", + "structure" ], - "file": "challenge_stranger_barley_to_combat.js" + "file": "explore_100_blocks_to_find.js" }, { "quality": 0.8, "successCount": 1, "failureCount": 0, - "name": "build_a_5block_tall_dirt", - "description": "Build a 5-block tall dirt tower at current position to create a guard watchtower", - "keywords": [ - "building", - "defense", - "territory_control", - "guard_duty" - ], - "file": "build_a_5block_tall_dirt.js" + "name": "open_the_chest_at_920", + "description": "Open the chest at 920,", + "keywords": [], + "file": "open_the_chest_at_920.js" }, { - "quality": 0.9, + "quality": 0.8, "successCount": 1, "failureCount": 0, - "name": "craft_12_oak_planks_from", - "description": "Craft 12 oak planks from 3 oak logs at crafting table (824,63,444)", + "name": "explore_and_find_a_cave", + "description": "Explore and find a cave entrance", "keywords": [ - "crafting", - "oak_planks", - "guard_tower", - "building_materials" + "explore", + "cave", + "structure" ], - "file": "craft_12_oak_planks_from.js" + "file": "explore_and_find_a_cave.js" }, { "quality": 0.8, - "successCount": 1, + "successCount": 3, "failureCount": 0, - "name": "patrol_square_pattern_around_current", - "description": "Patrol square pattern around current position for 2 minutes", + "name": "mine_3_stripped_birch_logs", + "description": "Mine 3 stripped birch logs", "keywords": [ - "patrol", - "guard", - "defense", - "territory" + "mine", + "birch", + "wood" ], - "file": "patrol_square_pattern_around_current.js" + "file": "mine_3_stripped_birch_logs.js" }, { "quality": 0.8, "successCount": 1, "failureCount": 0, - "name": "build_a_3block_overhang_using", - "description": "Build a 3-block overhang using stone slabs on the north side of your dirt watchtower at 705,63,554", - "keywords": [ - "defense", - "watchtower", - "overhang", - "guard" - ], - "file": "build_a_3block_overhang_using.js" + "name": "open_the_chest_at_908", + "description": "Open the chest at 908, 84", + "keywords": [], + "file": "open_the_chest_at_908.js" }, { "quality": 0.8, "successCount": 1, "failureCount": 0, - "name": "build_a_3x3_platform_using", - "description": "Build a 3x3 platform using stone slabs at watchtower top (705,65,553) for better sightlines", - "keywords": [ - "build", - "platform", - "defense", - "watchtower" - ], - "file": "build_a_3x3_platform_using.js" + "name": "pick_up_the_item_6", + "description": "Pick up the item 6 meters away", + "keywords": [], + "file": "pick_up_the_item_6.js" }, { - "name": "approach_crisisact_to_within_melee", - "description": "Approach CrisisAct to within melee range", + "name": "pick_up_all_7_nearby", + "description": "Pick up all 7 nearby dropped items", "keywords": [ - "approach", - "crisisact", - "within", - "melee", - "range" + "pickup", + "items", + "resources" ], - "file": "approach_crisisact_to_within_melee.js", + "file": "pick_up_all_7_nearby.js", "quality": 0.8, "successCount": 1, "failureCount": 0 diff --git a/skills/locate_the_nearest_body_of.js b/skills/locate_the_nearest_body_of.js new file mode 100644 index 0000000..61e2732 --- /dev/null +++ b/skills/locate_the_nearest_body_of.js @@ -0,0 +1,23 @@ +async function locateNearestWater(bot) { + const waterBlock = bot.findBlock({ + matching: b => b.name === 'water', + maxDistance: 32 + }); + if (waterBlock) { + await moveTo(waterBlock.position.x, waterBlock.position.y, waterBlock.position.z, 2, 30); + } else { + await exploreUntil('forward', 60, () => { + return bot.findBlock({ + matching: b => b.name === 'water', + maxDistance: 32 + }); + }); + const foundWater = bot.findBlock({ + matching: b => b.name === 'water', + maxDistance: 32 + }); + if (foundWater) { + await moveTo(foundWater.position.x, foundWater.position.y, foundWater.position.z, 2, 30); + } + } +} \ No newline at end of file diff --git a/skills/mine1oaklog.js b/skills/mine1oaklog.js index 142b0fa..6fb4fe7 100644 --- a/skills/mine1oaklog.js +++ b/skills/mine1oaklog.js @@ -1,16 +1,24 @@ async function mineOneOakLog(bot) { - const oakLog = bot.findBlock({ - matching: b => b.name === 'oak_log', + const targetName = 'oak_log'; + const targetCount = 1; + const getCount = () => { + const item = bot.inventory.items().find(i => i.name === targetName); + return item ? item.count : 0; + }; + const initialCount = getCount(); + const findTarget = () => bot.findBlock({ + matching: b => b.name === targetName, maxDistance: 32 }); - if (!oakLog) { - const Vec3 = require('vec3'); - await exploreUntil(new Vec3(1, 0, 0), 60, () => { - return bot.findBlock({ - matching: b => b.name === 'oak_log', - maxDistance: 32 - }); - }); + let target = findTarget(); + if (!target) { + await exploreUntil('north', 60, () => findTarget()); + } + await mineBlock(targetName, targetCount); + + // Check if we actually got the log. If not, try again to handle potential item pick-up delays or failures. + const currentCount = getCount(); + if (currentCount <= initialCount) { + await mineBlock(targetName, targetCount); } - await mineBlock("oak_log", 1); } \ No newline at end of file diff --git a/skills/mine_10_stone_bricks.js b/skills/mine_10_stone_bricks.js new file mode 100644 index 0000000..a9e98f1 --- /dev/null +++ b/skills/mine_10_stone_bricks.js @@ -0,0 +1,21 @@ +async function mineTenStoneBricks(bot) { + const targetBlock = 'stone_bricks'; + const count = 10; + const findBricks = () => bot.findBlock({ + matching: b => b.name === targetBlock, + maxDistance: 32 + }); + let bricks = findBricks(); + if (!bricks) { + // Try exploring in a different direction (south) for a longer duration + await exploreUntil(bot, 'south', 120, () => { + return findBricks(); + }); + } + + // Double check if we found any before attempting to mine + bricks = findBricks(); + if (bricks) { + await mineBlock(targetBlock, count); + } +} \ No newline at end of file diff --git a/skills/mine_12_cobblestone_from_the.js b/skills/mine_12_cobblestone_from_the.js new file mode 100644 index 0000000..b422d9a --- /dev/null +++ b/skills/mine_12_cobblestone_from_the.js @@ -0,0 +1,17 @@ +async function mineTwelveCobblestone(bot) { + const targetBlock = 'stone'; + const count = 12; + const stone = bot.findBlock({ + matching: b => b.name === targetBlock, + maxDistance: 32 + }); + if (!stone) { + await exploreUntil('north', 60, () => { + return bot.findBlock({ + matching: b => b.name === targetBlock, + maxDistance: 32 + }); + }); + } + await mineBlock(targetBlock, count); +} \ No newline at end of file diff --git a/skills/mine_1_bone_block.js b/skills/mine_1_bone_block.js new file mode 100644 index 0000000..fb0b0c8 --- /dev/null +++ b/skills/mine_1_bone_block.js @@ -0,0 +1,19 @@ +async function mineBoneBlock(bot) { + const targetBlock = 'bone_block'; + const count = 1; + let boneBlock = bot.findBlock({ + matching: b => b.name === targetBlock, + maxDistance: 32 + }); + if (!boneBlock) { + boneBlock = await exploreUntil('north', 120, () => { + return bot.findBlock({ + matching: b => b.name === targetBlock, + maxDistance: 32 + }); + }); + } + if (boneBlock) { + await mineBlock(targetBlock, count); + } +} \ No newline at end of file diff --git a/skills/mine_1_bone_block_nearby.js b/skills/mine_1_bone_block_nearby.js new file mode 100644 index 0000000..86617db --- /dev/null +++ b/skills/mine_1_bone_block_nearby.js @@ -0,0 +1,16 @@ +async function mineOneBoneBlock(bot) { + const targetBlock = bot.findBlock({ + matching: block => block.name === 'bone_block', + maxDistance: 32 + }); + if (!targetBlock) { + await exploreUntil('horizontal', 60, () => { + const found = bot.findBlock({ + matching: block => block.name === 'bone_block', + maxDistance: 32 + }); + return found; + }); + } + await mineBlock('bone_block', 1); +} \ No newline at end of file diff --git a/skills/mine_1_emerald_ore_using.js b/skills/mine_1_emerald_ore_using.js new file mode 100644 index 0000000..fdd4e69 --- /dev/null +++ b/skills/mine_1_emerald_ore_using.js @@ -0,0 +1,44 @@ +async function mineEmeraldWithIronPickaxe(bot) { + let ironPick = bot.inventory.items().find(i => i.name === 'iron_pickaxe'); + if (!ironPick) { + let ingots = bot.inventory.items().find(i => i.name === 'iron_ingot'); + let ingotCount = ingots ? ingots.count : 0; + if (ingotCount < 3) { + let rawIronItem = bot.inventory.items().find(i => i.name === 'raw_iron'); + let rawCount = rawIronItem ? rawIronItem.count : 0; + if (rawCount < 3 - ingotCount) { + let ironBlock = bot.findBlock({ + matching: b => ['iron_ore', 'deepslate_iron_ore'].includes(b.name), + maxDistance: 32 + }); + if (!ironBlock) { + ironBlock = await exploreUntil('south', 60, () => bot.findBlock({ + matching: b => ['iron_ore', 'deepslate_iron_ore'].includes(b.name), + maxDistance: 32 + })); + } + if (ironBlock) { + await mineBlock(ironBlock.name, 3 - ingotCount - rawCount); + } + } + rawIronItem = bot.inventory.items().find(i => i.name === 'raw_iron'); + if (rawIronItem) { + await smeltItem('raw_iron', 'coal', 3 - ingotCount); + } + } + await craftItem('iron_pickaxe', 1); + } + let emeraldBlock = bot.findBlock({ + matching: b => ['emerald_ore', 'deepslate_emerald_ore'].includes(b.name), + maxDistance: 32 + }); + if (!emeraldBlock) { + emeraldBlock = await exploreUntil('north', 120, () => bot.findBlock({ + matching: b => ['emerald_ore', 'deepslate_emerald_ore'].includes(b.name), + maxDistance: 32 + })); + } + if (emeraldBlock) { + await mineBlock(emeraldBlock.name, 1); + } +} \ No newline at end of file diff --git a/skills/mine_1_iron_ore_at.js b/skills/mine_1_iron_ore_at.js index cda74d8..bb20f0f 100644 --- a/skills/mine_1_iron_ore_at.js +++ b/skills/mine_1_iron_ore_at.js @@ -1,4 +1,17 @@ -async function mineOneIronOreAt(bot) { - await moveTo(1744, 34, 437, 3, 30); - await mineBlock('iron_ore', 1); +async function mineOneIronOreAtTarget(bot) { + const targetX = 886; + const targetY = 70; + const targetZ = 209; + await moveTo(targetX, targetY, targetZ, 3, 60); + const findIron = () => bot.findBlock({ + matching: block => ['iron_ore', 'deepslate_iron_ore'].includes(block.name), + maxDistance: 32 + }); + let targetBlock = findIron(); + if (!targetBlock) { + targetBlock = await exploreUntil('north', 30, () => findIron()); + } + if (targetBlock) { + await mineBlock(targetBlock.name, 1); + } } \ No newline at end of file diff --git a/skills/mine_1_lapis_lazuli_ore.js b/skills/mine_1_lapis_lazuli_ore.js new file mode 100644 index 0000000..2a5c6ad --- /dev/null +++ b/skills/mine_1_lapis_lazuli_ore.js @@ -0,0 +1,14 @@ +async function mine1LapisLazuliOre(bot) { + const findLapis = () => bot.findBlock({ + matching: b => ['lapis_ore', 'deepslate_lapis_ore'].includes(b.name), + maxDistance: 32 + }); + let lapis = findLapis(); + if (!lapis) { + await exploreUntil(bot, 'south', 60, () => findLapis()); + lapis = findLapis(); + } + if (lapis) { + await mineBlock(lapis.name, 1); + } +} \ No newline at end of file diff --git a/skills/mine_1_lapis_ore.js b/skills/mine_1_lapis_ore.js new file mode 100644 index 0000000..38c029d --- /dev/null +++ b/skills/mine_1_lapis_ore.js @@ -0,0 +1,16 @@ +async function mineOneLapisOre(bot) { + const findLapis = () => bot.findBlock({ + matching: block => ['lapis_ore', 'deepslate_lapis_ore'].includes(block.name), + maxDistance: 32 + }); + let lapisBlock = findLapis(); + if (!lapisBlock) { + await exploreUntil(bot, 'horizontal', 120, () => { + return findLapis(); + }); + lapisBlock = findLapis(); + } + if (lapisBlock) { + await mineBlock(lapisBlock.name, 1); + } +} \ No newline at end of file diff --git a/skills/mine_1_lapis_ore_block.js b/skills/mine_1_lapis_ore_block.js new file mode 100644 index 0000000..3a38baf --- /dev/null +++ b/skills/mine_1_lapis_ore_block.js @@ -0,0 +1,22 @@ +async function mineOneLapisOre(bot) { + let lapisBlock = bot.findBlock({ + matching: block => block.name === 'lapis_ore' || block.name === 'deepslate_lapis_ore', + maxDistance: 32 + }); + if (!lapisBlock) { + await exploreUntil(bot, 'south', 60, () => { + const found = bot.findBlock({ + matching: block => block.name === 'lapis_ore' || block.name === 'deepslate_lapis_ore', + maxDistance: 32 + }); + return found; + }); + lapisBlock = bot.findBlock({ + matching: block => block.name === 'lapis_ore' || block.name === 'deepslate_lapis_ore', + maxDistance: 32 + }); + } + if (lapisBlock) { + await mineBlock(lapisBlock.name, 1); + } +} \ No newline at end of file diff --git a/skills/mine_1_lapis_ore_nearby.js b/skills/mine_1_lapis_ore_nearby.js new file mode 100644 index 0000000..c4fca5c --- /dev/null +++ b/skills/mine_1_lapis_ore_nearby.js @@ -0,0 +1,17 @@ +async function mineOneLapisOre(bot) { + const findLapis = () => bot.findBlock({ + matching: block => ['lapis_ore', 'deepslate_lapis_ore'].includes(block.name), + maxDistance: 32 + }); + let lapisBlock = findLapis(); + if (!lapisBlock) { + await exploreUntil(bot, 'horizontal', 60, () => { + const block = findLapis(); + return block ? block : null; + }); + lapisBlock = findLapis(); + } + if (lapisBlock) { + await mineBlock(lapisBlock.name, 1); + } +} \ No newline at end of file diff --git a/skills/mine_1_lapisore_block.js b/skills/mine_1_lapisore_block.js new file mode 100644 index 0000000..97109a4 --- /dev/null +++ b/skills/mine_1_lapisore_block.js @@ -0,0 +1,19 @@ +async function mineOneLapisOre(bot) { + const targetBlock = 'lapis_ore'; + const count = 1; + let lapis = bot.findBlock({ + matching: b => b.name === targetBlock, + maxDistance: 32 + }); + if (!lapis) { + lapis = await exploreUntil('north', 60, () => { + return bot.findBlock({ + matching: b => b.name === targetBlock, + maxDistance: 32 + }); + }); + } + if (lapis) { + await mineBlock(targetBlock, count); + } +} \ No newline at end of file diff --git a/skills/mine_1_oak_log.js b/skills/mine_1_oak_log.js index 2d8fd72..711c598 100644 --- a/skills/mine_1_oak_log.js +++ b/skills/mine_1_oak_log.js @@ -1,19 +1,15 @@ async function mineOneOakLog(bot) { - try { - const oakLog = bot.findBlock({ - matching: b => b.name === 'oak_log', + const targetLog = bot.findBlock({ + matching: block => block.name === 'oak_log', + maxDistance: 32 + }); + if (targetLog) { + await moveTo(targetLog.position.x, targetLog.position.y, targetLog.position.z, 2); + } else { + await exploreUntil('north', 60, () => bot.findBlock({ + matching: block => block.name === 'oak_log', maxDistance: 32 - }); - if (!oakLog) { - await exploreUntil("north", 60, () => { - return bot.findBlock({ - matching: b => b.name === 'oak_log', - maxDistance: 32 - }); - }); - } - await mineBlock("oak_log", 1); - } catch (err) { - console.error('Error mining oak log:', err); + })); } + await mineBlock('oak_log', 1); } \ No newline at end of file diff --git a/skills/mine_1_oak_log_at.js b/skills/mine_1_oak_log_at.js new file mode 100644 index 0000000..a68fa6b --- /dev/null +++ b/skills/mine_1_oak_log_at.js @@ -0,0 +1,7 @@ +async function mineOakLogAtTaskLocation(bot) { + const targetX = 847; + const targetY = 68; + const targetZ = 199; + await moveTo(targetX, targetY, targetZ, 3, 60); + await mineBlock('oak_log', 1); +} \ No newline at end of file diff --git a/skills/mine_1_spruce_log.js b/skills/mine_1_spruce_log.js index 385f695..557d9f4 100644 --- a/skills/mine_1_spruce_log.js +++ b/skills/mine_1_spruce_log.js @@ -1,21 +1,13 @@ async function mineOneSpruceLog(bot) { - const targetBlock = 'spruce_log'; - const count = 1; - const spruceLog = bot.findBlock({ - matching: b => b.name === targetBlock, + const targetName = 'spruce_log'; + const targetCount = 1; + const findSpruce = () => bot.findBlock({ + matching: b => b.name === targetName, maxDistance: 32 }); + let spruceLog = findSpruce(); if (!spruceLog) { - await exploreUntil({ - x: 1, - y: 0, - z: 0 - }, 60, () => { - return bot.findBlock({ - matching: b => b.name === targetBlock, - maxDistance: 32 - }); - }); + await exploreUntil(bot, 'north', 60, () => findSpruce()); } - await mineBlock(targetBlock, count); + await mineBlock(targetName, targetCount); } \ No newline at end of file diff --git a/skills/mine_2_moss_blocks.js b/skills/mine_2_moss_blocks.js new file mode 100644 index 0000000..9ba740a --- /dev/null +++ b/skills/mine_2_moss_blocks.js @@ -0,0 +1,17 @@ +async function mineTwoMossBlocks(bot) { + const targetBlock = 'moss_block'; + const count = 2; + const moss = bot.findBlock({ + matching: b => b.name === targetBlock, + maxDistance: 32 + }); + if (!moss) { + await exploreUntil('north', 60, () => { + return bot.findBlock({ + matching: b => b.name === targetBlock, + maxDistance: 32 + }); + }); + } + await mineBlock(targetBlock, count); +} \ No newline at end of file diff --git a/skills/mine_2_oak_logs.js b/skills/mine_2_oak_logs.js new file mode 100644 index 0000000..47f565d --- /dev/null +++ b/skills/mine_2_oak_logs.js @@ -0,0 +1,20 @@ +async function mineTwoOakLogs(bot) { + const logName = 'oak_log'; + const targetCount = 2; + const currentLogs = bot.inventory.items().filter(i => i.name === logName).reduce((acc, i) => acc + i.count, 0); + const needed = targetCount - currentLogs; + if (needed <= 0) return; + const block = bot.findBlock({ + matching: b => b.name === logName, + maxDistance: 32 + }); + if (!block) { + await exploreUntil('north', 60, () => { + return bot.findBlock({ + matching: b => b.name === logName, + maxDistance: 32 + }); + }); + } + await mineBlock(logName, needed); +} \ No newline at end of file diff --git a/skills/mine_2_stone_blocks_with.js b/skills/mine_2_stone_blocks_with.js new file mode 100644 index 0000000..7d6359d --- /dev/null +++ b/skills/mine_2_stone_blocks_with.js @@ -0,0 +1,21 @@ +async function mineTwoStoneWithPickaxe(bot) { + const pickaxe = bot.inventory.items().find(i => i.name === 'wooden_pickaxe'); + if (pickaxe) { + await bot.equip(pickaxe, 'hand'); + } + const targetBlock = 'stone'; + const count = 2; + const stone = bot.findBlock({ + matching: b => b.name === targetBlock, + maxDistance: 32 + }); + if (!stone) { + await exploreUntil('north', 60, () => { + return bot.findBlock({ + matching: b => b.name === targetBlock, + maxDistance: 32 + }); + }); + } + await mineBlock(targetBlock, count); +} \ No newline at end of file diff --git a/skills/mine_3_birch_logs.js b/skills/mine_3_birch_logs.js new file mode 100644 index 0000000..c47088b --- /dev/null +++ b/skills/mine_3_birch_logs.js @@ -0,0 +1,17 @@ +async function mineThreeBirchLogs(bot) { + const targetBlock = 'birch_log'; + const count = 3; + const birchLog = bot.findBlock({ + matching: b => b.name === targetBlock, + maxDistance: 32 + }); + if (!birchLog) { + await exploreUntil('north', 60, () => { + return bot.findBlock({ + matching: b => b.name === targetBlock, + maxDistance: 32 + }); + }); + } + await mineBlock(targetBlock, count); +} \ No newline at end of file diff --git a/skills/mine_3_blocks_of_grass.js b/skills/mine_3_blocks_of_grass.js index d89ab44..94a424b 100644 --- a/skills/mine_3_blocks_of_grass.js +++ b/skills/mine_3_blocks_of_grass.js @@ -1,9 +1,16 @@ -async function mine3BlocksOfGrass(bot) { - try { - await mineBlock("grass_block", 3); - bot.chat("Mined 3 grass blocks."); - } catch (err) { - console.error('Error mining grass_block:', err); - bot.chat("Failed to mine grass blocks."); +async function mineThreeBlocksOfGrass(bot) { + const blockName = 'grass_block'; + let target = bot.findBlock({ + matching: b => b.name === blockName, + maxDistance: 32 + }); + if (!target) { + await exploreUntil(bot, 'north', 60, () => { + return bot.findBlock({ + matching: b => b.name === blockName, + maxDistance: 32 + }); + }); } -} + await mineBlock(blockName, 3); +} \ No newline at end of file diff --git a/skills/mine_3_blocks_of_iron.js b/skills/mine_3_blocks_of_iron.js new file mode 100644 index 0000000..3575558 --- /dev/null +++ b/skills/mine_3_blocks_of_iron.js @@ -0,0 +1,27 @@ +async function mineThreeIronOreBlocks(bot) { + // Ensure we have a stone pickaxe to successfully collect iron ore drops + const hasStonePickaxe = bot.inventory.items().find(i => i.name === 'stone_pickaxe'); + if (!hasStonePickaxe) { + // craftItem handles finding or placing a crafting table if needed + await craftItem('stone_pickaxe', 1); + } + + // Define a helper to locate iron ore or deepslate iron ore + const findIron = () => bot.findBlock({ + matching: block => ['iron_ore', 'deepslate_iron_ore'].includes(block.name), + maxDistance: 32 + }); + + // Check for nearby iron ore + let targetBlock = findIron(); + + // If no iron ore is found nearby, explore the area + if (!targetBlock) { + targetBlock = await exploreUntil('south', 60, () => findIron()); + } + + // If iron ore is located, mine 3 blocks + if (targetBlock) { + await mineBlock(targetBlock.name, 3); + } +} \ No newline at end of file diff --git a/skills/mine_3_coal_ore_at.js b/skills/mine_3_coal_ore_at.js index 74df1ca..4136bcc 100644 --- a/skills/mine_3_coal_ore_at.js +++ b/skills/mine_3_coal_ore_at.js @@ -1,17 +1,4 @@ -async function mineThreeCoalOre(bot) { - const targetBlock = 'coal_ore'; - const count = 3; - const coalOre = bot.findBlock({ - matching: b => b.name === targetBlock, - maxDistance: 32 - }); - if (!coalOre) { - await exploreUntil('south', 60, () => { - return bot.findBlock({ - matching: b => b.name === targetBlock, - maxDistance: 32 - }); - }); - } - await mineBlock(targetBlock, count); +async function mine3CoalOreAt(bot) { + await moveTo(935, 58, 339, 3, 60); + await mineBlock('coal_ore', 3); } \ No newline at end of file diff --git a/skills/mine_3_coal_ore_blocks.js b/skills/mine_3_coal_ore_blocks.js new file mode 100644 index 0000000..49fac4f --- /dev/null +++ b/skills/mine_3_coal_ore_blocks.js @@ -0,0 +1,7 @@ +async function mineThreeCoalOreAtTarget(bot) { + const targetX = 807; + const targetY = 51; + const targetZ = 226; + await moveTo(targetX, targetY, targetZ, 3, 60); + await mineBlock('coal_ore', 3); +} \ No newline at end of file diff --git a/skills/mine_3_cobblestone_blocks.js b/skills/mine_3_cobblestone_blocks.js new file mode 100644 index 0000000..92684f4 --- /dev/null +++ b/skills/mine_3_cobblestone_blocks.js @@ -0,0 +1,3 @@ +async function mineThreeCobblestoneBlocks(bot) { + await mineThreeCobblestone(bot); +} \ No newline at end of file diff --git a/skills/mine_3_cobblestone_from_the.js b/skills/mine_3_cobblestone_from_the.js new file mode 100644 index 0000000..5acde0e --- /dev/null +++ b/skills/mine_3_cobblestone_from_the.js @@ -0,0 +1,7 @@ +async function mine3CobblestoneFromStone(bot) { + const pickaxe = bot.inventory.items().find(i => i.name === 'wooden_pickaxe'); + if (pickaxe) { + await bot.equip(pickaxe, 'hand'); + } + await mineThreeCobblestone(bot); +} \ No newline at end of file diff --git a/skills/mine_3_copper_ore_blocks.js b/skills/mine_3_copper_ore_blocks.js new file mode 100644 index 0000000..65c1947 --- /dev/null +++ b/skills/mine_3_copper_ore_blocks.js @@ -0,0 +1,14 @@ +async function mineThreeCopperOreBlocks(bot) { + const findCopper = () => bot.findBlock({ + matching: block => ['copper_ore', 'deepslate_copper_ore'].includes(block.name), + maxDistance: 32 + }); + let copperBlock = findCopper(); + if (!copperBlock) { + await exploreUntil('horizontal', 60, () => findCopper()); + copperBlock = findCopper(); + } + if (copperBlock) { + await mineBlock(copperBlock.name, 3); + } +} \ No newline at end of file diff --git a/skills/mine_3_iron_ore.js b/skills/mine_3_iron_ore.js new file mode 100644 index 0000000..09d0e9e --- /dev/null +++ b/skills/mine_3_iron_ore.js @@ -0,0 +1,31 @@ +async function mineThreeIronOre(bot) { + const ironOreNames = ['iron_ore', 'deepslate_iron_ore']; + const inventoryItems = bot.inventory.items(); + const hasGoodPickaxe = inventoryItems.some(item => ['stone_pickaxe', 'iron_pickaxe', 'diamond_pickaxe', 'netherite_pickaxe'].includes(item.name)); + if (!hasGoodPickaxe) { + const sticks = inventoryItems.find(i => i.name === 'stick'); + if (!sticks || sticks.count < 2) { + await craftItem('stick', 1); + } + await craftItem('stone_pickaxe', 1); + } + let targetOre = bot.findBlock({ + matching: block => ironOreNames.includes(block.name), + maxDistance: 32 + }); + if (!targetOre) { + await exploreUntil('south', 60, () => { + return bot.findBlock({ + matching: block => ironOreNames.includes(block.name), + maxDistance: 32 + }); + }); + targetOre = bot.findBlock({ + matching: block => ironOreNames.includes(block.name), + maxDistance: 32 + }); + } + if (targetOre) { + await mineBlock(targetOre.name, 3); + } +} \ No newline at end of file diff --git a/skills/mine_3_iron_ore_at.js b/skills/mine_3_iron_ore_at.js index 9ac89af..228cd75 100644 --- a/skills/mine_3_iron_ore_at.js +++ b/skills/mine_3_iron_ore_at.js @@ -1,17 +1,4 @@ -async function mineThreeIronOre(bot) { - const targetBlock = 'iron_ore'; - const targetCount = 3; - const ironOre = bot.findBlock({ - matching: b => b.name === targetBlock, - maxDistance: 32 - }); - if (!ironOre) { - await exploreUntil('north', 60, () => { - return bot.findBlock({ - matching: b => b.name === targetBlock, - maxDistance: 32 - }); - }); - } - await mineBlock(targetBlock, targetCount); +async function mine3IronOreAt(bot) { + await moveTo(918, 64, 385, 3, 60); + await mineBlock('iron_ore', 3); } \ No newline at end of file diff --git a/skills/mine_3_iron_ore_blocks.js b/skills/mine_3_iron_ore_blocks.js new file mode 100644 index 0000000..30fda9e --- /dev/null +++ b/skills/mine_3_iron_ore_blocks.js @@ -0,0 +1,27 @@ +async function mineThreeIronOreBlocks(bot) { + const ironOreNames = ['iron_ore', 'deepslate_iron_ore']; + const items = bot.inventory.items(); + const hasGoodPickaxe = items.some(item => ['stone_pickaxe', 'iron_pickaxe', 'diamond_pickaxe', 'netherite_pickaxe'].includes(item.name)); + if (!hasGoodPickaxe) { + const cobble = items.find(i => i.name === 'cobblestone'); + const sticks = items.find(i => i.name === 'stick'); + if (!cobble || cobble.count < 3) { + await mineBlock('stone', 3); + } + if (!sticks || sticks.count < 2) { + await craftItem('stick', 1); + } + await craftItem('stone_pickaxe', 1); + } + const findOre = () => bot.findBlock({ + matching: block => ironOreNames.includes(block.name), + maxDistance: 32 + }); + let oreBlock = findOre(); + if (!oreBlock) { + oreBlock = await exploreUntil('south', 60, () => findOre()); + } + if (oreBlock) { + await mineBlock(oreBlock.name, 3); + } +} \ No newline at end of file diff --git a/skills/mine_3_oak_logs.js b/skills/mine_3_oak_logs.js index 81a91e4..22a93ed 100644 --- a/skills/mine_3_oak_logs.js +++ b/skills/mine_3_oak_logs.js @@ -1,4 +1,20 @@ async function mineThreeOakLogs(bot) { - const currentCount = bot.inventory.items().find(i => i.name === 'oak_log')?.count || 0; - await mineBlock('oak_log', currentCount + 3); + const logName = 'oak_log'; + const targetCount = 3; + const currentLogs = bot.inventory.items().filter(i => i.name === logName).reduce((acc, i) => acc + i.count, 0); + const needed = targetCount - currentLogs; + if (needed <= 0) return; + const block = bot.findBlock({ + matching: b => b.name === logName, + maxDistance: 32 + }); + if (!block) { + await exploreUntil(bot.entity.yaw, 60, () => { + return bot.findBlock({ + matching: b => b.name === logName, + maxDistance: 32 + }); + }); + } + await mineBlock(logName, needed); } \ No newline at end of file diff --git a/skills/mine_3_oak_logs_at.js b/skills/mine_3_oak_logs_at.js new file mode 100644 index 0000000..178b091 --- /dev/null +++ b/skills/mine_3_oak_logs_at.js @@ -0,0 +1,7 @@ +async function mineThreeOakLogsAtLocation(bot) { + const currentLogs = bot.inventory.items().filter(i => i.name === 'oak_log').reduce((acc, i) => acc + i.count, 0); + const needed = 3 - currentLogs; + if (needed <= 0) return; + await moveTo(931, 68, 338, 3, 60); + await mineBlock('oak_log', needed); +} \ No newline at end of file diff --git a/skills/mine_3_spruce_logs.js b/skills/mine_3_spruce_logs.js index d8a52b5..1e4ae60 100644 --- a/skills/mine_3_spruce_logs.js +++ b/skills/mine_3_spruce_logs.js @@ -1,22 +1,12 @@ async function mineThreeSpruceLogs(bot) { - const targetBlock = 'spruce_log'; + const targetName = 'spruce_log'; const targetCount = 3; - const item = bot.inventory.items().find(i => i.name === targetBlock); - const currentCount = item ? item.count : 0; - if (currentCount >= targetCount) { - return; - } - let spruceLog = bot.findBlock({ - matching: b => b.name === targetBlock, + const findSpruce = () => bot.findBlock({ + matching: b => b.name === targetName, maxDistance: 32 }); - if (!spruceLog) { - await exploreUntil('north', 60, () => { - return bot.findBlock({ - matching: b => b.name === targetBlock, - maxDistance: 32 - }); - }); + if (!findSpruce()) { + await exploreUntil('north', 60, () => findSpruce()); } - await mineBlock(targetBlock, targetCount - currentCount); + await mineBlock(targetName, targetCount); } \ No newline at end of file diff --git a/skills/mine_3_stone_blocks.js b/skills/mine_3_stone_blocks.js new file mode 100644 index 0000000..bea77a5 --- /dev/null +++ b/skills/mine_3_stone_blocks.js @@ -0,0 +1,17 @@ +async function mineThreeStoneBlocks(bot) { + const targetBlock = 'stone'; + const count = 3; + const stone = bot.findBlock({ + matching: b => b.name === targetBlock, + maxDistance: 32 + }); + if (!stone) { + await exploreUntil('north', 60, () => { + return bot.findBlock({ + matching: b => b.name === targetBlock, + maxDistance: 32 + }); + }); + } + await mineBlock(targetBlock, count); +} \ No newline at end of file diff --git a/skills/mine_3_stone_blocks_to.js b/skills/mine_3_stone_blocks_to.js new file mode 100644 index 0000000..0165e26 --- /dev/null +++ b/skills/mine_3_stone_blocks_to.js @@ -0,0 +1,15 @@ +async function mineThreeStoneBlocks(bot) { + const stone = bot.findBlock({ + matching: b => b.name === 'stone', + maxDistance: 32 + }); + if (!stone) { + await exploreUntil('north', 60, () => { + return bot.findBlock({ + matching: b => b.name === 'stone', + maxDistance: 32 + }); + }); + } + await mineBlock('stone', 3); +} \ No newline at end of file diff --git a/skills/mine_3_stone_blocks_with.js b/skills/mine_3_stone_blocks_with.js new file mode 100644 index 0000000..49ca591 --- /dev/null +++ b/skills/mine_3_stone_blocks_with.js @@ -0,0 +1,19 @@ +async function mineThreeStoneBlocks(bot) { + const woodenPickaxe = bot.inventory.items().find(i => i.name === 'wooden_pickaxe'); + if (woodenPickaxe) { + await bot.equip(woodenPickaxe, 'hand'); + } + const stone = bot.findBlock({ + matching: b => b.name === 'stone', + maxDistance: 32 + }); + if (!stone) { + await exploreUntil('north', 60, () => { + return bot.findBlock({ + matching: b => b.name === 'stone', + maxDistance: 32 + }); + }); + } + await mineBlock('stone', 3); +} \ No newline at end of file diff --git a/skills/mine_3_stripped_birch_logs.js b/skills/mine_3_stripped_birch_logs.js new file mode 100644 index 0000000..1874bd1 --- /dev/null +++ b/skills/mine_3_stripped_birch_logs.js @@ -0,0 +1,20 @@ +async function mineThreeStrippedBirchLogs(bot) { + const blockName = 'stripped_birch_log'; + const targetCount = 3; + const currentCount = bot.inventory.items().filter(i => i.name === blockName).reduce((acc, i) => acc + i.count, 0); + const needed = targetCount - currentCount; + if (needed <= 0) return; + const block = bot.findBlock({ + matching: b => b.name === blockName, + maxDistance: 32 + }); + if (!block) { + await exploreUntil('north', 60, () => { + return !!bot.findBlock({ + matching: b => b.name === blockName, + maxDistance: 32 + }); + }); + } + await mineBlock(blockName, needed); +} \ No newline at end of file diff --git a/skills/mine_4_clay_blocks_nearby.js b/skills/mine_4_clay_blocks_nearby.js new file mode 100644 index 0000000..181b792 --- /dev/null +++ b/skills/mine_4_clay_blocks_nearby.js @@ -0,0 +1,15 @@ +async function mineFourClayBlocks(bot) { + const clayBlock = bot.findBlock({ + matching: block => block.name === 'clay', + maxDistance: 32 + }); + if (!clayBlock) { + await exploreUntil(bot, 'horizontal', 60, () => { + return bot.findBlock({ + matching: block => block.name === 'clay', + maxDistance: 32 + }); + }); + } + await mineBlock(bot, 'clay', 4); +} \ No newline at end of file diff --git a/skills/mine_4_moss_blocks.js b/skills/mine_4_moss_blocks.js new file mode 100644 index 0000000..1098b17 --- /dev/null +++ b/skills/mine_4_moss_blocks.js @@ -0,0 +1,17 @@ +async function mineFourMossBlocks(bot) { + const targetBlock = 'moss_block'; + const count = 4; + const moss = bot.findBlock({ + matching: b => b.name === targetBlock, + maxDistance: 32 + }); + if (!moss) { + await exploreUntil('north', 60, () => { + return bot.findBlock({ + matching: b => b.name === targetBlock, + maxDistance: 32 + }); + }); + } + await mineBlock(targetBlock, count); +} \ No newline at end of file diff --git a/skills/mine_5_blue_terracotta_blocks.js b/skills/mine_5_blue_terracotta_blocks.js new file mode 100644 index 0000000..9859ec5 --- /dev/null +++ b/skills/mine_5_blue_terracotta_blocks.js @@ -0,0 +1,16 @@ +async function mineFiveBlueTerracottaBlocks(bot) { + const findBlueTerracotta = () => bot.findBlock({ + matching: block => block.name === 'blue_terracotta', + maxDistance: 32 + }); + let terracottaBlock = findBlueTerracotta(); + if (!terracottaBlock) { + await exploreUntil(bot, 'horizontal', 60, () => { + return findBlueTerracotta(); + }); + terracottaBlock = findBlueTerracotta(); + } + if (terracottaBlock) { + await mineBlock('blue_terracotta', 5); + } +} \ No newline at end of file diff --git a/skills/mine_5_copper_ore_blocks.js b/skills/mine_5_copper_ore_blocks.js new file mode 100644 index 0000000..28b56c0 --- /dev/null +++ b/skills/mine_5_copper_ore_blocks.js @@ -0,0 +1,38 @@ +async function mineFiveCopperOreBlocks(bot) { + const hasStonePickaxe = bot.inventory.items().find(i => i.name === 'stone_pickaxe'); + if (!hasStonePickaxe) { + const craftingTable = bot.findBlock({ + matching: b => b.name === 'crafting_table', + maxDistance: 32 + }); + if (!craftingTable) { + const tableInInventory = bot.inventory.items().find(i => i.name === 'crafting_table'); + if (tableInInventory) { + const pos = bot.entity.position.offset(1, -1, 1); + await placeItem('crafting_table', pos.x, pos.y + 1, pos.z); + } else { + await mineBlock('oak_log', 1); + await craftItem('oak_planks', 1); + await craftItem('crafting_table', 1); + const pos = bot.entity.position.offset(1, -1, 1); + await placeItem('crafting_table', pos.x, pos.y + 1, pos.z); + } + } + await craftItem('stone_pickaxe', 1); + } + const findCopper = () => bot.findBlock({ + matching: block => ['copper_ore', 'deepslate_copper_ore'].includes(block.name), + maxDistance: 32 + }); + let copperBlock = findCopper(); + if (!copperBlock) { + await exploreUntil(bot, 'horizontal', 60, () => { + const block = findCopper(); + return block ? block : null; + }); + copperBlock = findCopper(); + } + if (copperBlock) { + await mineBlock(copperBlock.name, 5); + } +} \ No newline at end of file diff --git a/skills/mine_5_spruce_logs.js b/skills/mine_5_spruce_logs.js new file mode 100644 index 0000000..03a342e --- /dev/null +++ b/skills/mine_5_spruce_logs.js @@ -0,0 +1,17 @@ +async function mineFiveSpruceLogs(bot) { + const targetName = 'spruce_log'; + const targetCount = 5; + const spruceLog = bot.findBlock({ + matching: b => b.name === targetName, + maxDistance: 32 + }); + if (!spruceLog) { + await exploreUntil('north', 60, () => { + return bot.findBlock({ + matching: b => b.name === targetName, + maxDistance: 32 + }); + }); + } + await mineBlock(targetName, targetCount); +} \ No newline at end of file diff --git a/skills/mine_6_oak_logs.js b/skills/mine_6_oak_logs.js new file mode 100644 index 0000000..ec3a44f --- /dev/null +++ b/skills/mine_6_oak_logs.js @@ -0,0 +1,17 @@ +async function mineSixOakLogs(bot) { + for (let i = 0; i < 6; i++) { + let oakLog = bot.findBlock({ + matching: block => block.name === 'oak_log', + maxDistance: 32 + }); + if (!oakLog) { + await exploreUntil('north', 60, () => { + return bot.findBlock({ + matching: block => block.name === 'oak_log', + maxDistance: 32 + }); + }); + } + await mineBlock('oak_log', 1); + } +} \ No newline at end of file diff --git a/skills/mine_8_stone_blocks.js b/skills/mine_8_stone_blocks.js new file mode 100644 index 0000000..8063911 --- /dev/null +++ b/skills/mine_8_stone_blocks.js @@ -0,0 +1,17 @@ +async function mineEightStoneBlocks(bot) { + const targetBlock = 'stone'; + const count = 8; + const stone = bot.findBlock({ + matching: b => b.name === targetBlock, + maxDistance: 32 + }); + if (!stone) { + await exploreUntil('north', 60, () => { + return bot.findBlock({ + matching: b => b.name === targetBlock, + maxDistance: 32 + }); + }); + } + await mineBlock(targetBlock, count); +} \ No newline at end of file diff --git a/skills/mine_the_coalore_at_906.js b/skills/mine_the_coalore_at_906.js new file mode 100644 index 0000000..f8f0560 --- /dev/null +++ b/skills/mine_the_coalore_at_906.js @@ -0,0 +1,4 @@ +async function mineCoalOreAtCoordinate(bot) { + await moveTo(906, 56, 394, 3); + await mineBlock('coal_ore', 1); +} \ No newline at end of file diff --git a/skills/mine_the_iron_ore_at.js b/skills/mine_the_iron_ore_at.js new file mode 100644 index 0000000..9977339 --- /dev/null +++ b/skills/mine_the_iron_ore_at.js @@ -0,0 +1,25 @@ +async function mineIronOreAtTarget(bot) { + const targetX = 810; + const targetY = 42; + const targetZ = 237; + + // Move to the specified iron ore location + await moveTo(targetX, targetY, targetZ, 3, 60); + + // Helper function to find iron ore or deepslate iron ore nearby + const findIron = () => bot.findBlock({ + matching: block => ['iron_ore', 'deepslate_iron_ore'].includes(block.name), + maxDistance: 32 + }); + let targetBlock = findIron(); + + // If the block is not immediately visible, explore the area to find it + if (!targetBlock) { + targetBlock = await exploreUntil('north', 30, () => findIron()); + } + + // If we found iron ore, mine it + if (targetBlock) { + await mineBlock(targetBlock.name, 1); + } +} \ No newline at end of file diff --git a/skills/mine_the_ironore_at_908.js b/skills/mine_the_ironore_at_908.js new file mode 100644 index 0000000..9a717cf --- /dev/null +++ b/skills/mine_the_ironore_at_908.js @@ -0,0 +1,4 @@ +async function mineIronOreAtTarget(bot) { + await moveTo(908, 52, 389, 3, 60); + await mineBlock('iron_ore', 1); +} \ No newline at end of file diff --git a/skills/mine_the_ironore_at_969.js b/skills/mine_the_ironore_at_969.js new file mode 100644 index 0000000..f433516 --- /dev/null +++ b/skills/mine_the_ironore_at_969.js @@ -0,0 +1,4 @@ +async function mineIronOreAtTarget(bot) { + await moveTo(969, 59, 373, 2, 60); + await mineBlock('iron_ore', 1); +} \ No newline at end of file diff --git a/skills/mine_the_lapisore_nearby.js b/skills/mine_the_lapisore_nearby.js new file mode 100644 index 0000000..ac039a8 --- /dev/null +++ b/skills/mine_the_lapisore_nearby.js @@ -0,0 +1,21 @@ +async function mineLapisOreNearby(bot) { + let lapis = bot.findBlock({ + matching: b => b.name === 'lapis_ore' || b.name === 'deepslate_lapis_ore', + maxDistance: 32 + }); + if (!lapis) { + await exploreUntil('north', 60, () => { + return bot.findBlock({ + matching: b => b.name === 'lapis_ore' || b.name === 'deepslate_lapis_ore', + maxDistance: 32 + }); + }); + lapis = bot.findBlock({ + matching: b => b.name === 'lapis_ore' || b.name === 'deepslate_lapis_ore', + maxDistance: 32 + }); + } + if (lapis) { + await mineBlock(lapis.name, 1); + } +} \ No newline at end of file diff --git a/skills/minethenearestoaklog.js b/skills/minethenearestoaklog.js new file mode 100644 index 0000000..e1e9f0b --- /dev/null +++ b/skills/minethenearestoaklog.js @@ -0,0 +1,16 @@ +async function mineTheNearestOakLog(bot) { + const logName = 'oak_log'; + const nearestLog = bot.findBlock({ + matching: block => block.name === logName, + maxDistance: 32 + }); + if (!nearestLog) { + await exploreUntil(bot, 'north', 60, () => { + return bot.findBlock({ + matching: block => block.name === logName, + maxDistance: 32 + }); + }); + } + await mineBlock(logName, 1); +} \ No newline at end of file diff --git a/skills/open_the_chest_at_908.js b/skills/open_the_chest_at_908.js new file mode 100644 index 0000000..cd8bb60 --- /dev/null +++ b/skills/open_the_chest_at_908.js @@ -0,0 +1,4 @@ +async function openChestAt90884396(bot) { + await moveTo(908, 84, 396, 2, 60); + await inspectContainer('chest'); +} \ No newline at end of file diff --git a/skills/open_the_chest_at_920.js b/skills/open_the_chest_at_920.js new file mode 100644 index 0000000..3e08f7b --- /dev/null +++ b/skills/open_the_chest_at_920.js @@ -0,0 +1,4 @@ +async function openChestAt920(bot) { + await moveTo(920, 86, 403, 2, 60); + await inspectContainer('chest'); +} \ No newline at end of file diff --git a/skills/pick_up_all_7_nearby.js b/skills/pick_up_all_7_nearby.js new file mode 100644 index 0000000..8b37105 --- /dev/null +++ b/skills/pick_up_all_7_nearby.js @@ -0,0 +1,13 @@ +async function pickUpSevenNearbyItems(bot) { + for (let i = 0; i < 7; i++) { + const itemEntity = bot.nearestEntity(entity => entity.name === 'item'); + if (!itemEntity) break; + const { + x, + y, + z + } = itemEntity.position; + await moveTo(x, y, z, 1, 10); + await bot.waitForTicks(10); + } +} \ No newline at end of file diff --git a/skills/pick_up_all_dropped_items.js b/skills/pick_up_all_dropped_items.js new file mode 100644 index 0000000..39cc9a0 --- /dev/null +++ b/skills/pick_up_all_dropped_items.js @@ -0,0 +1,19 @@ +async function pickUpAllItemsWithinFifteenMeters(bot) { + while (true) { + const itemEntity = bot.nearestEntity(entity => { + const isItem = entity.name === 'item' || entity.type === 'item' || entity.objectType === 'Item'; + if (!isItem) return false; + return bot.entity.position.distanceTo(entity.position) <= 15; + }); + if (!itemEntity) { + break; + } + const { + x, + y, + z + } = itemEntity.position; + await moveTo(x, y, z, 1, 10); + await bot.waitForTicks(5); + } +} \ No newline at end of file diff --git a/skills/pick_up_the_10_nearby.js b/skills/pick_up_the_10_nearby.js new file mode 100644 index 0000000..9a119eb --- /dev/null +++ b/skills/pick_up_the_10_nearby.js @@ -0,0 +1,15 @@ +async function pickUpTenNearbyItems(bot) { + for (let i = 0; i < 10; i++) { + const itemEntity = bot.nearestEntity(entity => entity.name === 'item'); + if (!itemEntity) { + break; + } + const { + x, + y, + z + } = itemEntity.position; + await moveTo(x, y, z, 1, 10); + await bot.waitForTicks(5); + } +} \ No newline at end of file diff --git a/skills/pick_up_the_dropped_items.js b/skills/pick_up_the_dropped_items.js new file mode 100644 index 0000000..5d330e7 --- /dev/null +++ b/skills/pick_up_the_dropped_items.js @@ -0,0 +1,19 @@ +async function pickUpDroppedItemsWithinFifteenMeters(bot) { + while (true) { + const itemEntity = bot.nearestEntity(entity => { + const isItem = entity.name === 'item' || entity.type === 'item' || entity.objectType === 'Item'; + if (!isItem) return false; + return bot.entity.position.distanceTo(entity.position) <= 15; + }); + if (!itemEntity) { + break; + } + const { + x, + y, + z + } = itemEntity.position; + await moveTo(x, y, z, 1, 10); + await bot.waitForTicks(10); + } +} \ No newline at end of file diff --git a/skills/pick_up_the_item_6.js b/skills/pick_up_the_item_6.js new file mode 100644 index 0000000..b3828cd --- /dev/null +++ b/skills/pick_up_the_item_6.js @@ -0,0 +1,12 @@ +async function pickUpNearbyItem(bot) { + const item = bot.nearestEntity(e => e.name === 'item' || e.type === 'item'); + if (item) { + const { + x, + y, + z + } = item.position; + await moveTo(x, y, z, 0, 15); + await bot.waitForTicks(20); + } +} \ No newline at end of file diff --git a/skills/pick_up_the_items_located.js b/skills/pick_up_the_items_located.js new file mode 100644 index 0000000..ca5f531 --- /dev/null +++ b/skills/pick_up_the_items_located.js @@ -0,0 +1,15 @@ +async function pickUpNearbyItems(bot) { + const maxAttempts = 10; + for (let i = 0; i < maxAttempts; i++) { + const itemEntity = bot.nearestEntity(entity => entity.name === 'item'); + if (!itemEntity) break; + if (bot.entity.position.distanceTo(itemEntity.position) > 16) break; + const { + x, + y, + z + } = itemEntity.position; + await moveTo(x, y, z, 1, 10); + await bot.waitForTicks(10); + } +} \ No newline at end of file diff --git a/skills/pick_up_the_items_on.js b/skills/pick_up_the_items_on.js new file mode 100644 index 0000000..e476413 --- /dev/null +++ b/skills/pick_up_the_items_on.js @@ -0,0 +1,3 @@ +async function pickUpNearbyItems(bot) { + await pickUpTenNearbyItems(bot); +} \ No newline at end of file diff --git a/skills/place_1_furnace_at_909.js b/skills/place_1_furnace_at_909.js new file mode 100644 index 0000000..5068bab --- /dev/null +++ b/skills/place_1_furnace_at_909.js @@ -0,0 +1,12 @@ +async function placeFurnaceAtPosition(bot) { + const furnaceItem = bot.inventory.items().find(i => i.name === 'furnace'); + if (!furnaceItem) { + const cobblestoneCount = bot.inventory.items().filter(i => i.name === 'cobblestone').reduce((acc, i) => acc + i.count, 0); + if (cobblestoneCount < 8) { + await mineBlock('stone', 8 - cobblestoneCount); + } + await craftItem('furnace', 1); + } + await moveTo(910, 52, 390, 2, 10); + await placeItem('furnace', 909, 52, 390); +} \ No newline at end of file diff --git a/skills/place_crafting_table_and_craft.js b/skills/place_crafting_table_and_craft.js new file mode 100644 index 0000000..4a47422 --- /dev/null +++ b/skills/place_crafting_table_and_craft.js @@ -0,0 +1,36 @@ +async function placeTableAndCraftStonePickaxe(bot) { + let craftingTableItem = bot.inventory.items().find(i => i.name === 'crafting_table'); + if (!craftingTableItem) { + const oakPlanks = bot.inventory.items().find(i => i.name === 'oak_planks'); + const birchPlanks = bot.inventory.items().find(i => i.name === 'birch_planks'); + const sprucePlanks = bot.inventory.items().find(i => i.name === 'spruce_planks'); + if (!(oakPlanks && oakPlanks.count >= 4) && !(birchPlanks && birchPlanks.count >= 4) && !(sprucePlanks && sprucePlanks.count >= 4)) { + await mineBlock('oak_log', 1); + await craftItem('oak_planks', 1); + } + await craftItem('crafting_table', 1); + craftingTableItem = bot.inventory.items().find(i => i.name === 'crafting_table'); + } + let tableBlock = bot.findBlock({ + matching: b => b.name === 'crafting_table', + maxDistance: 32 + }); + if (!tableBlock) { + const referenceBlock = bot.findBlock({ + matching: b => ['grass_block', 'dirt', 'stone', 'cobblestone', 'andesite'].includes(b.name), + maxDistance: 32 + }); + if (referenceBlock) { + const pos = referenceBlock.position.offset(0, 1, 0); + await placeItem('crafting_table', pos.x, pos.y, pos.z); + tableBlock = bot.findBlock({ + matching: b => b.name === 'crafting_table', + maxDistance: 32 + }); + } + } + if (tableBlock) { + await moveTo(tableBlock.position.x, tableBlock.position.y, tableBlock.position.z, 3, 10); + await craftItem('stone_pickaxe', 1); + } +} \ No newline at end of file diff --git a/skills/place_the_crafting_table_and.js b/skills/place_the_crafting_table_and.js new file mode 100644 index 0000000..1886f62 --- /dev/null +++ b/skills/place_the_crafting_table_and.js @@ -0,0 +1,46 @@ +async function placeTableAndCraftSticks(bot) { + // 1. Ensure we have a crafting table in inventory + let craftingTable = bot.inventory.items().find(i => i.name === 'crafting_table'); + if (!craftingTable) { + // Check if we have enough planks (need 4 for crafting table) + let sprucePlanks = bot.inventory.items().find(i => i.name === 'spruce_planks'); + if (!sprucePlanks || sprucePlanks.count < 4) { + // If not enough spruce planks, use oak logs to get planks + let oakLogs = bot.inventory.items().find(i => i.name === 'oak_log'); + if (oakLogs && oakLogs.count > 0) { + await craftItem('oak_planks', 1); + } else { + // If no logs, mine one + await mineBlock('oak_log', 1); + await craftItem('oak_planks', 1); + } + } + await craftItem('crafting_table', 1); + craftingTable = bot.inventory.items().find(i => i.name === 'crafting_table'); + } + + // 2. Place the crafting table + if (craftingTable) { + const referenceBlock = bot.findBlock({ + matching: b => ['grass_block', 'dirt', 'stone', 'sand'].includes(b.name), + maxDistance: 32 + }); + if (referenceBlock) { + const targetPos = referenceBlock.position.offset(0, 1, 0); + // Move within range to place the item + await moveTo(targetPos.x, targetPos.y, targetPos.z, 3); + await placeItem('crafting_table', targetPos.x, targetPos.y, targetPos.z); + } + } + + // 3. Craft 4 sticks (1 recipe set) + // Check if we have enough planks (need 2 for 4 sticks) + let planks = bot.inventory.items().find(i => i.name.endsWith('_planks')); + if (!planks || planks.count < 2) { + let oakLogs = bot.inventory.items().find(i => i.name === 'oak_log'); + if (oakLogs && oakLogs.count > 0) { + await craftItem('oak_planks', 1); + } + } + await craftItem('stick', 1); +} \ No newline at end of file diff --git a/skills/scout_for_a_nearby_island.js b/skills/scout_for_a_nearby_island.js new file mode 100644 index 0000000..3ff1bf1 --- /dev/null +++ b/skills/scout_for_a_nearby_island.js @@ -0,0 +1,41 @@ +async function scoutForNearbyIsland(bot) { + const startPosition = bot.entity.position.clone(); + const direction = { + x: 0, + y: 0, + z: -1 + }; + + // Step 1: Find water to start crossing + let waterBlock = bot.findBlock({ + matching: b => b.name === 'water', + maxDistance: 32 + }); + if (!waterBlock) { + waterBlock = await exploreUntil(direction, 60, () => { + return bot.findBlock({ + matching: b => b.name === 'water', + maxDistance: 32 + }); + }); + } + if (waterBlock) { + await moveTo(waterBlock.position.x, waterBlock.position.y, waterBlock.position.z, 2); + } + + // Step 2: Explore across the water to find a new landmass (potential island) + // We look for land blocks that are at least 40 blocks away from our starting point + const islandBlock = await exploreUntil(direction, 300, () => { + const land = bot.findBlock({ + matching: b => ['grass_block', 'sand', 'dirt', 'stone', 'gravel', 'clay', 'podzol', 'mycelium'].includes(b.name), + maxDistance: 32 + }); + if (land && land.position.distanceTo(startPosition) > 40) { + return land; + } + return null; + }); + if (islandBlock) { + await moveTo(islandBlock.position.x, islandBlock.position.y, islandBlock.position.z, 2); + } +} \ No newline at end of file diff --git a/skills/sharewisdomwithnearbyplayer.js b/skills/sharewisdomwithnearbyplayer.js new file mode 100644 index 0000000..da7ae74 --- /dev/null +++ b/skills/sharewisdomwithnearbyplayer.js @@ -0,0 +1,10 @@ +async function approachAndShareWisdom(bot) { + try { + await walkToNearestPlayer(bot); + await lookAtNearestPlayer(bot); + await shareWisdom(bot); + } catch (err) { + console.error('Error sharing wisdom:', err); + bot.chat('I encountered an error while trying to share wisdom.'); + } +} \ No newline at end of file diff --git a/skills/stop_mining_logs.js b/skills/stop_mining_logs.js new file mode 100644 index 0000000..171b73f --- /dev/null +++ b/skills/stop_mining_logs.js @@ -0,0 +1,3 @@ +async function stopMiningLogsTask(bot) { + await stopMiningLogs(bot); +} \ No newline at end of file diff --git a/skills/swim_across_the_water_to.js b/skills/swim_across_the_water_to.js new file mode 100644 index 0000000..42d684b --- /dev/null +++ b/skills/swim_across_the_water_to.js @@ -0,0 +1,29 @@ +async function swimToFindIsland(bot) { + const startPos = bot.entity.position.clone(); + + // Based on world memory, water is to the East (832, 60, 421) from (803, 60, 422). + const direction = { + x: 1, + y: 0, + z: 0 + }; + + // Explore in the chosen direction until we find land that is far from the start + const targetLand = await exploreUntil(direction, 120000, () => { + const landBlock = bot.findBlock({ + matching: b => ['grass_block', 'sand', 'dirt', 'stone', 'gravel', 'snow_block', 'podzol', 'coarse_dirt'].includes(b.name), + maxDistance: 32 + }); + if (landBlock) { + // Check if this land is at least 50 blocks away from where we started to ensure it's a separate landmass/island + const distance = landBlock.position.distanceTo(startPos); + if (distance > 50) { + return landBlock; + } + } + return null; + }); + if (targetLand) { + await moveTo(targetLand.position.x, targetLand.position.y + 1, targetLand.position.z, 2, 60); + } +} \ No newline at end of file diff --git a/skills/swim_or_walk_across_the.js b/skills/swim_or_walk_across_the.js new file mode 100644 index 0000000..7fab80f --- /dev/null +++ b/skills/swim_or_walk_across_the.js @@ -0,0 +1,30 @@ +async function findSeparateLandmass(bot) { + const startPos = bot.entity.position.clone(); + + // Direction across the water based on known water location (852, 61, 424) + // Current position is (854, 61, 425). Direction is roughly West (-1, 0, 0). + const direction = { + x: -1, + y: 0, + z: 0 + }; + + // Explore in the chosen direction until we find land that is far from the start + const targetLand = await exploreUntil(direction, 120000, () => { + const landBlock = bot.findBlock({ + matching: b => ['grass_block', 'sand', 'dirt', 'stone', 'gravel', 'snow_block'].includes(b.name), + maxDistance: 32 + }); + if (landBlock) { + // Check if this land is at least 40 blocks away from where we started + const distance = landBlock.position.distanceTo(startPos); + if (distance > 40) { + return landBlock; + } + } + return null; + }); + if (targetLand) { + await moveTo(targetLand.position.x, targetLand.position.y, targetLand.position.z, 1, 60); + } +} \ No newline at end of file diff --git a/skills/travel_to_the_discovered_island.js b/skills/travel_to_the_discovered_island.js new file mode 100644 index 0000000..19a3d2b --- /dev/null +++ b/skills/travel_to_the_discovered_island.js @@ -0,0 +1,46 @@ +async function travelToDiscoveredIsland(bot) { + // The task is to travel to a discovered island. + // Since specific coordinates aren't provided in the prompt, we use the logic of exploring the ocean to find land. + const searchDirection = { + x: 1, + y: 0, + z: 0 + }; + + // Step 1: Find water and move to it to start the crossing + let waterBlock = bot.findBlock({ + matching: b => b.name === 'water', + maxDistance: 32 + }); + if (!waterBlock) { + waterBlock = await exploreUntil(searchDirection, 30, () => { + return bot.findBlock({ + matching: b => b.name === 'water', + maxDistance: 32 + }); + }); + } + if (waterBlock) { + await moveTo(waterBlock.position.x, waterBlock.position.y, waterBlock.position.z, 2); + } + + // Step 2: Explore across the water to find a new landmass (the island) + // We look for land blocks that are at a distance from our starting point + const startPos = bot.entity.position.clone(); + const islandBlock = await exploreUntil(searchDirection, 120, () => { + const land = bot.findBlock({ + matching: b => ['grass_block', 'sand', 'dirt', 'stone', 'gravel', 'oak_log', 'spruce_log'].includes(b.name), + maxDistance: 32 + }); + // Check if the land found is at least 20 blocks away from where we started the water crossing + if (land && land.position.distanceTo(startPos) > 20) { + return land; + } + return null; + }); + + // Step 3: Move to the discovered island + if (islandBlock) { + await moveTo(islandBlock.position.x, islandBlock.position.y, islandBlock.position.z, 2); + } +} \ No newline at end of file diff --git a/skills/walk_to_and_open_the.js b/skills/walk_to_and_open_the.js new file mode 100644 index 0000000..cd46fb6 --- /dev/null +++ b/skills/walk_to_and_open_the.js @@ -0,0 +1,7 @@ +async function walkToAndOpenSpecificChest(bot) { + const x = 907; + const y = 81; + const z = 396; + await moveTo(x, y, z, 2, 60); + await inspectContainer('chest'); +} \ No newline at end of file diff --git a/skills/walk_to_packet1.js b/skills/walk_to_packet1.js new file mode 100644 index 0000000..e60e323 --- /dev/null +++ b/skills/walk_to_packet1.js @@ -0,0 +1,15 @@ +async function walkToPacket1(bot) { + const targetPlayer = bot.players['Packet1']; + if (targetPlayer && targetPlayer.entity) { + const pos = targetPlayer.entity.position; + await moveTo(pos.x, pos.y, pos.z, 2, 30); + } else { + const target = await exploreUntil('forward', 60, () => { + const p = bot.players['Packet1']; + return p && p.entity ? p.entity : null; + }); + if (target) { + await moveTo(target.position.x, target.position.y, target.position.z, 2, 30); + } + } +} \ No newline at end of file diff --git a/skills/walk_to_packet2.js b/skills/walk_to_packet2.js new file mode 100644 index 0000000..7d0b274 --- /dev/null +++ b/skills/walk_to_packet2.js @@ -0,0 +1,20 @@ +async function walkToPacket2(bot) { + const player = bot.players['Packet2']; + if (player && player.entity) { + const pos = player.entity.position; + await moveTo(pos.x, pos.y, pos.z, 2, 30); + } else { + const targetEntity = await exploreUntil({ + x: 1, + y: 0, + z: 0 + }, 60, () => { + const p = bot.players['Packet2']; + return p && p.entity ? p.entity : null; + }); + if (targetEntity) { + const pos = targetEntity.position; + await moveTo(pos.x, pos.y, pos.z, 2, 30); + } + } +} \ No newline at end of file diff --git a/skills/walk_to_the_chest_at.js b/skills/walk_to_the_chest_at.js new file mode 100644 index 0000000..c9d6096 --- /dev/null +++ b/skills/walk_to_the_chest_at.js @@ -0,0 +1,6 @@ +async function walkToSpecificChest(bot) { + const targetX = 855; + const targetY = 64; + const targetZ = 259; + await moveTo(targetX, targetY, targetZ, 1, 60); +} \ No newline at end of file diff --git a/skills/walk_to_the_crafting_table.js b/skills/walk_to_the_crafting_table.js new file mode 100644 index 0000000..3c0444f --- /dev/null +++ b/skills/walk_to_the_crafting_table.js @@ -0,0 +1,32 @@ +async function walkToCraftingTableAtLocation(bot) { + const tablePos = { + x: 857, + y: 65, + z: 254 + }; + + // The task category is 'craft' and the previous error was 'crafting_table was not crafted'. + // This implies the bot needs to craft a crafting table as part of the task. + const hasTable = bot.inventory.items().find(i => i.name === 'crafting_table'); + if (!hasTable) { + // Check if we have materials for a crafting table (4 planks). + // The bot has 1 spruce log and 2 oak planks. + const spruceLog = bot.inventory.items().find(i => i.name === 'spruce_log'); + if (spruceLog) { + // 1 log = 4 planks, which is enough for a crafting table. + await craftItem('spruce_planks', 1); + } else { + // If no spruce log, ensure we have enough oak planks or logs. + const oakPlanks = bot.inventory.items().find(i => i.name === 'oak_planks'); + if (!oakPlanks || oakPlanks.count < 4) { + await mineBlock('oak_log', 1); + await craftItem('oak_planks', 1); + } + } + // Craft the crafting table. + await craftItem('crafting_table', 1); + } + + // Now that the craft requirement is met, proceed to the target location. + await moveTo(tablePos.x, tablePos.y, tablePos.z, 1, 60); +} \ No newline at end of file diff --git a/skills/walk_to_the_nearest_player.js b/skills/walk_to_the_nearest_player.js index 5ffd484..364cfef 100644 --- a/skills/walk_to_the_nearest_player.js +++ b/skills/walk_to_the_nearest_player.js @@ -1,28 +1,18 @@ -async function goToPlayer(bot) { - try { - const players = Object.values(bot.players).filter(p => p.entity); - if (players.length === 0) { - bot.chat('No players nearby'); - return; - } - let nearest = players[0]; - let nearestDist = Infinity; - const myPos = bot.entity.position; - for (const p of players) { - if (!p.entity) continue; - const dx = p.entity.position.x - myPos.x; - const dz = p.entity.position.z - myPos.z; - const dist = Math.sqrt(dx * dx + dz * dz); - if (dist < nearestDist) { - nearest = p; - nearestDist = dist; - } - } - const tp = nearest.entity.position; - bot.chat(`Going to player at ${tp.x}, ${tp.y}, ${tp.z}`); - await moveTo(tp.x, tp.y, tp.z, 3, 15); - bot.chat('Reached the player'); - } catch (err) { - bot.chat(`Error: ${err}`); +async function walkToTheNearestPlayer(bot) { + const getNearestPlayerEntity = () => { + return bot.nearestEntity(entity => entity.type === 'player' && entity.username !== bot.username); + }; + let targetPlayer = getNearestPlayerEntity(); + if (!targetPlayer) { + // If no player is nearby, explore in a direction to find one + targetPlayer = await exploreUntil({ + x: 1, + y: 0, + z: 0 + }, 60, () => getNearestPlayerEntity()); + } + if (targetPlayer) { + const pos = targetPlayer.position; + await moveTo(pos.x, pos.y, pos.z, 2, 30); } } \ No newline at end of file diff --git a/skills/withdraw_the_wooden_pickaxe_and.js b/skills/withdraw_the_wooden_pickaxe_and.js new file mode 100644 index 0000000..e927d05 --- /dev/null +++ b/skills/withdraw_the_wooden_pickaxe_and.js @@ -0,0 +1,5 @@ +async function withdrawItemsFromChest(bot) { + await walkToSpecificChest(bot); + await withdrawItem('chest', 'wooden_pickaxe', 1); + await withdrawItem('chest', 'apple', 1); +} \ No newline at end of file diff --git a/src/actions/attack.ts b/src/actions/attack.ts index 8659b8d..f607ed3 100644 --- a/src/actions/attack.ts +++ b/src/actions/attack.ts @@ -25,6 +25,7 @@ export async function attack(bot: Bot, entityName: string, maxDuration = 30000): return new Promise((resolve, reject) => { let hits = 0; + let finished = false; const startTime = Date.now(); let droppedItem: any = null; @@ -50,6 +51,8 @@ export async function attack(bot: Bot, entityName: string, maxDuration = 30000): }; const finish = async (success: boolean, message: string) => { + if (finished) return; + finished = true; if (success && droppedItem) { try { await (bot as any).collectBlock.collect(droppedItem, { ignoreNoPath: true }); @@ -71,7 +74,7 @@ export async function attack(bot: Bot, entityName: string, maxDuration = 30000): const timeoutId = setTimeout(() => { cleanup(); - reject(new Error(`Failed to kill ${entityName} within ${Math.round(maxDuration / 1000)}s`)); + void finish(false, `Failed to kill ${entityName} within ${Math.round(maxDuration / 1000)}s`); }, maxDuration); const attackInterval = setInterval(() => { diff --git a/src/actions/container.ts b/src/actions/container.ts index 754b4f1..0ad52a4 100644 --- a/src/actions/container.ts +++ b/src/actions/container.ts @@ -33,21 +33,22 @@ export async function inspectContainer(bot: Bot, blockName: string, position?: V const moved = await moveNear(bot, containerBlock.position.x, containerBlock.position.y, containerBlock.position.z, 3); if (!moved) return { success: false, message: `Could not reach nearby ${blockName}` }; + const container = await (bot as any).openContainer(containerBlock); try { - const container = await (bot as any).openContainer(containerBlock); const items = (container.containerItems?.() || []) .filter((item: any) => item) .reduce((acc: Record, item: any) => { acc[item.name] = (acc[item.name] || 0) + item.count; return acc; }, {}); - container.close(); const summary = Object.keys(items).length > 0 ? Object.entries(items).map(([name, count]) => `${name}x${count}`).join(', ') : 'empty'; return { success: true, message: `Inspected ${blockName}: ${summary}`, data: { items } }; } catch (err: any) { return { success: false, message: `Inspect failed for ${blockName}: ${err.message}` }; + } finally { + container.close(); } } @@ -62,13 +63,14 @@ export async function withdrawFromContainer(bot: Bot, blockName: string, itemNam const moved = await moveNear(bot, containerBlock.position.x, containerBlock.position.y, containerBlock.position.z, 3); if (!moved) return { success: false, message: `Could not reach nearby ${blockName}` }; + const container = await (bot as any).openContainer(containerBlock); try { - const container = await (bot as any).openContainer(containerBlock); await container.withdraw(item.id, null, count); - container.close(); return { success: true, message: `Withdrew ${count} ${itemName} from ${blockName}` }; } catch (err: any) { return { success: false, message: `Withdraw failed from ${blockName}: ${err.message}` }; + } finally { + container.close(); } } @@ -83,12 +85,13 @@ export async function depositToContainer(bot: Bot, blockName: string, itemName: const moved = await moveNear(bot, containerBlock.position.x, containerBlock.position.y, containerBlock.position.z, 3); if (!moved) return { success: false, message: `Could not reach nearby ${blockName}` }; + const container = await (bot as any).openContainer(containerBlock); try { - const container = await (bot as any).openContainer(containerBlock); await container.deposit(item.id, null, count); - container.close(); return { success: true, message: `Deposited ${count} ${itemName} into ${blockName}` }; } catch (err: any) { return { success: false, message: `Deposit failed into ${blockName}: ${err.message}` }; + } finally { + container.close(); } } diff --git a/src/actions/giveItem.ts b/src/actions/giveItem.ts index f5502be..1eca4e0 100644 --- a/src/actions/giveItem.ts +++ b/src/actions/giveItem.ts @@ -1,7 +1,7 @@ import { Bot } from 'mineflayer'; import { ActionResult } from './types'; -export async function giveItem(bot: Bot, playerName: string, itemName: string, count = 1): Promise { +export async function giveItem(bot: Bot, playerName: string, itemName: string, count = 1, onGift?: (playerName: string) => void): Promise { const mcData = require('minecraft-data')(bot.version); const itemInfo = mcData.itemsByName[itemName]; if (!itemInfo) { @@ -31,6 +31,7 @@ export async function giveItem(bot: Bot, playerName: string, itemName: string, c try { await bot.toss(itemInfo.id, null, count); + if (onGift) onGift(playerName); return { success: true, message: `Tossed ${count} ${itemName} toward ${playerName}` }; } catch (err: any) { return { success: false, message: `Give failed: ${err.message}` }; diff --git a/src/actions/smelt.ts b/src/actions/smelt.ts index a6a6eee..d870a8b 100644 --- a/src/actions/smelt.ts +++ b/src/actions/smelt.ts @@ -42,8 +42,8 @@ export async function smelt(bot: Bot, itemName: string, fuelName: string, count return { success: false, message: `Found furnace for ${itemName} but could not reach it` }; } + const furnace = await (bot as any).openFurnace(furnaceBlock); try { - const furnace = await (bot as any).openFurnace(furnaceBlock); let smelted = 0; for (let i = 0; i < count; i++) { @@ -62,8 +62,6 @@ export async function smelt(bot: Bot, itemName: string, fuelName: string, count smelted++; } - furnace.close(); - if (smelted === 0) { return { success: false, @@ -81,5 +79,7 @@ export async function smelt(bot: Bot, itemName: string, fuelName: string, count success: false, message: `Smelting ${itemName} failed: ${err.message}. Inventory: ${inventorySummary(bot)}`, }; + } finally { + furnace.close(); } } diff --git a/src/actions/walkTo.ts b/src/actions/walkTo.ts index 4b05fcd..b298aff 100644 --- a/src/actions/walkTo.ts +++ b/src/actions/walkTo.ts @@ -4,22 +4,30 @@ import { ActionResult } from './types'; export async function walkTo(bot: Bot, x: number, y: number, z: number, range = 2): Promise { return new Promise((resolve) => { + let finished = false; + + const done = (result: ActionResult) => { + if (finished) return; + finished = true; + clearTimeout(timeout); + bot.removeListener('goal_reached', onReached); + bot.removeListener('path_update', onPathUpdate); + resolve(result); + }; + const timeout = setTimeout(() => { bot.pathfinder.stop(); - resolve({ success: false, message: 'Pathfinding timeout' }); + done({ success: false, message: 'Pathfinding timeout' }); }, 30000); const onReached = () => { - clearTimeout(timeout); - bot.removeListener('path_update', onPathUpdate); - resolve({ success: true, message: `Reached ${Math.round(x)}, ${Math.round(y)}, ${Math.round(z)}` }); + done({ success: true, message: `Reached ${Math.round(x)}, ${Math.round(y)}, ${Math.round(z)}` }); }; const onPathUpdate = (r: any) => { if (r.status === 'noPath') { - clearTimeout(timeout); - bot.removeListener('goal_reached', onReached); - resolve({ success: false, message: 'No path found' }); + bot.pathfinder.stop(); + done({ success: false, message: 'No path found' }); } }; diff --git a/src/ai/GeminiClient.ts b/src/ai/GeminiClient.ts index f297064..3056099 100644 --- a/src/ai/GeminiClient.ts +++ b/src/ai/GeminiClient.ts @@ -7,12 +7,16 @@ export class GeminiClient implements LLMClient { private temperature: number; private defaultMaxTokens: number; private baseUrl = 'https://generativelanguage.googleapis.com/v1beta/models/'; + private supportsThinking: boolean; constructor(opts: { apiKey: string; model: string; temperature: number; maxTokens: number }) { this.apiKey = opts.apiKey; this.model = opts.model; this.temperature = opts.temperature; this.defaultMaxTokens = opts.maxTokens; + // Only models with "thinking" or "3.1" in name support thinkingConfig + this.supportsThinking = /thinking|3\.1/i.test(this.model); + logger.info({ model: this.model, supportsThinking: this.supportsThinking }, 'Gemini client initialized'); } async chat(systemPrompt: string, contents: any[], maxTokens?: number): Promise { @@ -24,9 +28,7 @@ export class GeminiClient implements LLMClient { generationConfig: { temperature: this.temperature, maxOutputTokens: maxTokens || this.defaultMaxTokens, - thinkingConfig: { - thinkingBudget: 128, // Minimal thinking for fast chat responses - }, + ...(this.supportsThinking && { thinkingConfig: { thinkingBudget: 128 } }), }, }; @@ -71,9 +73,7 @@ export class GeminiClient implements LLMClient { generationConfig: { temperature: this.temperature, maxOutputTokens: maxTokens || this.defaultMaxTokens, - thinkingConfig: { - thinkingBudget: 2048, - }, + ...(this.supportsThinking && { thinkingConfig: { thinkingBudget: 2048 } }), }, }; diff --git a/src/ai/prompts/personality.ts b/src/ai/prompts/personality.ts index 4308e9e..50b73a1 100644 --- a/src/ai/prompts/personality.ts +++ b/src/ai/prompts/personality.ts @@ -1,10 +1,48 @@ import { getPersonality } from '../../personality/PersonalityType'; +import { BotEmotionalState } from '../../social/SocialMemory'; -export function buildSystemPrompt(botName: string, personalityKey: string, affinity: number, codegenMode = false, internalState?: string): string { +export interface SocialContext { + nearbyBots?: { name: string; personality: string; activity: string }[]; + memoryContext?: string; + emotionalState?: BotEmotionalState; + relationshipSummary?: string; +} + +export function buildSystemPrompt( + botName: string, + personalityKey: string, + affinity: number, + codegenMode = false, + internalState?: string, + socialContext?: SocialContext, +): string { const personality = getPersonality(personalityKey); const stateContext = internalState ? `\n── CURRENT ACTIVITY ──\n\n${internalState}\n` : ''; + // Build optional social sections + let socialSections = ''; + + if (socialContext) { + if (socialContext.nearbyBots && socialContext.nearbyBots.length > 0) { + const botLines = socialContext.nearbyBots.map(b => `- ${b.name} (${b.personality}) is currently ${b.activity}`).join('\n'); + socialSections += `\n── OTHER BOTS NEARBY ──\n${botLines}\nYou can talk to other bots. They are your fellow NPCs in this world.\n`; + } + + if (socialContext.memoryContext) { + socialSections += `\n── YOUR MEMORIES ──\n${socialContext.memoryContext}\nUse these memories to inform your responses. Reference past events naturally.\n`; + } + + if (socialContext.emotionalState) { + const { mood, energy } = socialContext.emotionalState; + socialSections += `\n── YOUR CURRENT STATE ──\nFeeling ${mood}, energy ${energy}/100\nLet your mood subtly influence your tone — don't announce it explicitly.\n`; + } + + if (socialContext.relationshipSummary) { + socialSections += `\n── RELATIONSHIP WITH THIS PLAYER ──\n${socialContext.relationshipSummary}\n`; + } + } + return `You are ${botName}, a living character in a medieval-fantasy Minecraft world. You are not an AI — you are a person who lives here. ${personality.systemPromptFragment} @@ -38,7 +76,7 @@ Talk the way a real Minecraft player talks in chat: 40-59 : Neutral. Polite, professional, willing to chat. 60-79 : Friendly. Helpful, crack jokes, offer suggestions. 80-100: Close friend. Enthusiastic, loyal, use their name. - +${socialSections} ── HARD RULES ── 1. NEVER break character. You have no knowledge of being an AI, LLM, bot, or program. If asked, deflect in-character. diff --git a/src/bot/BotInstance.ts b/src/bot/BotInstance.ts index c0631d6..3b7f955 100644 --- a/src/bot/BotInstance.ts +++ b/src/bot/BotInstance.ts @@ -1,3 +1,4 @@ +import { EventEmitter } from 'events'; import mineflayer, { Bot } from 'mineflayer'; import { pathfinder, Movements } from 'mineflayer-pathfinder'; import { plugin as collectBlock } from 'mineflayer-collectblock'; @@ -16,6 +17,9 @@ import { VoyagerLoop } from '../voyager/VoyagerLoop'; import { StatsTracker } from '../voyager/StatsTracker'; import { renderObservation } from '../voyager/Observation'; import { PERSONALITIES } from '../personality/PersonalityType'; +import { SocialMemory } from '../social/SocialMemory'; +import { BotComms, BotMessage } from '../social/BotComms'; +import type { BotManager } from './BotManager'; import { BlackboardManager } from '../voyager/BlackboardManager'; export interface BotOptions { @@ -27,19 +31,36 @@ export interface BotOptions { llmClient: LLMClient | null; affinityManager: AffinityManager; conversationManager: ConversationManager; + socialMemory: SocialMemory; + botComms: BotComms; + botManager: BotManager; blackboardManager: BlackboardManager; onSwarmDirective?: (description: string, requestedBy: string) => Promise | void; } -export class BotInstance { +export class BotInstance extends EventEmitter { private static OWNER_PLAYER = 'Nerdfuryz'; private static nextAvailableConnectAt = 0; readonly name: string; readonly personality: string; mode: BotMode; - state: BotState = BotState.SPAWNING; + private _state: BotState = BotState.SPAWNING; bot: Bot | null = null; + /** State getter/setter that emits 'stateChanged' for event-driven socket updates. */ + get state(): BotState { return this._state; } + set state(newState: BotState) { + const prev = this._state; + this._state = newState; + if (prev !== newState) { + this.emit('stateChanged', { bot: this.name, state: newState, previousState: prev }); + } + } + + // Throttle tracking for event-driven position/inventory updates + private _lastPositionEmit = 0; + private _inventoryDebounceTimer: NodeJS.Timeout | null = null; + private config: Config; private spawnLocation?: { x: number; y: number; z: number }; private headTrackingInterval: NodeJS.Timeout | null = null; @@ -54,20 +75,27 @@ export class BotInstance { private blackboardManager: BlackboardManager; private onSwarmDirective?: (description: string, requestedBy: string) => Promise | void; private chatCooldowns: Map = new Map(); + private socialMemory: SocialMemory; + private botComms: BotComms; + private botManager: BotManager; private voyagerLoop: VoyagerLoop | null = null; + private reflectionInterval: ReturnType | null = null; + private rawMessageHandler: ((jsonMsg: any) => void) | null = null; private instinctInterval: NodeJS.Timeout | null = null; private instinctResumeTimeout: NodeJS.Timeout | null = null; private instinctActive = false; private instinctReason: 'attack' | 'hazard' | null = null; - private voyagerPausedByInstinct = false; + private instinctPauseKey: string | null = null; private lastAttackedAt = 0; private lastHealth = 20; private lastAttackerName: string | null = null; private statsTracker = new StatsTracker('./data'); private static CHAT_COOLDOWN_MS = 3000; private lastPathResetLog: { reason: string; at: number; suppressed: number } | null = null; + private lastInteractionAt: number = Date.now(); constructor(options: BotOptions) { + super(); this.name = options.name; this.personality = options.personality; this.mode = options.mode; @@ -76,6 +104,9 @@ export class BotInstance { this.llmClient = options.llmClient; this.affinityManager = options.affinityManager; this.conversationManager = options.conversationManager; + this.socialMemory = options.socialMemory; + this.botComms = options.botComms; + this.botManager = options.botManager; this.blackboardManager = options.blackboardManager; this.onSwarmDirective = options.onSwarmDirective; } @@ -161,6 +192,51 @@ export class BotInstance { this.startWandering(); // Voyager owns movement in codegen mode } this.startChatListener(); + // Debug: log all raw messages to diagnose chat issues + if (!this.bot) return; // Bot disconnected during auth/class selection + if (this.rawMessageHandler) { + this.bot.removeListener('message', this.rawMessageHandler); + } + this.rawMessageHandler = (jsonMsg: any) => { + const text = jsonMsg.toString(); + if (text && !text.includes('Chunk size') && text.trim().length > 0) { + logger.debug({ bot: this.name, rawMessage: text }, 'Raw message received'); + } + }; + this.bot.on('message', this.rawMessageHandler); + + // Listen for inter-bot messages (unregister first to avoid duplicates on reconnect) + this.botComms.unregisterListener(this.name); + this.botComms.registerListener(this.name, (msg: BotMessage) => { + // Don't process messages from self + if (msg.from.toLowerCase() === this.name.toLowerCase()) return; + + logger.info({ bot: this.name, from: msg.from, content: msg.content }, 'Received bot message'); + this.socialMemory.addMemory(this.name, 'social', + `${msg.from} sent me a message: "${msg.content.substring(0, 80)}"`, + [msg.from], 5 + ); + + // Detect task-like requests and queue them via VoyagerLoop + const taskRequest = this.extractBotTaskRequest(msg.content); + if (taskRequest && this.voyagerLoop) { + logger.info( + { bot: this.name, from: msg.from, task: taskRequest }, + 'Queuing bot-to-bot task request' + ); + this.voyagerLoop.queuePlayerTask(taskRequest, msg.from); + } + }); + + // Periodic reflection every 10 minutes + this.reflectionInterval = setInterval(() => { + const recent = this.socialMemory.getRecentMemories(this.name, 10); + if (recent.length >= 5) { + this.socialMemory.reflect(this.name, recent); + } + }, 600000); + + this.scheduleAmbientChat(); this.startVoyagerIfCodegen(); }); }); @@ -211,6 +287,54 @@ export class BotInstance { this.triggerAttackInstinct(source || this.findLikelyThreat(), 'entity-hurt'); }); + // --- Event-driven socket updates (position, health, inventory) --- + + // Position: throttle to max once per second + this.bot.on('move', () => { + const now = Date.now(); + if (now - this._lastPositionEmit < 1000) return; + const pos = this.bot?.entity?.position; + if (!pos) return; + this._lastPositionEmit = now; + this.emit('positionChanged', { + bot: this.name, + x: Math.round(pos.x), + y: Math.round(pos.y), + z: Math.round(pos.z), + }); + }); + + // Health: emit on change (mineflayer fires 'health' on actual change) + this.bot.on('health', () => { + this.emit('healthChanged', { + bot: this.name, + health: this.bot?.health ?? 0, + food: this.bot?.food ?? 0, + }); + }); + + // Inventory: debounce to once per 2 seconds since changes can be rapid + const emitInventory = () => { + if (!this.bot) return; + try { + const items = this.bot.inventory.items(); + this.emit('inventoryChanged', { + bot: this.name, + items: items.map((i: any) => ({ name: i.name, count: i.count, slot: i.slot })), + }); + } catch { /* bot may be disconnected */ } + }; + const debouncedInventory = () => { + if (this._inventoryDebounceTimer) clearTimeout(this._inventoryDebounceTimer); + this._inventoryDebounceTimer = setTimeout(emitInventory, 2000); + }; + this.bot.on('playerCollect' as any, debouncedInventory); + this.bot.once('spawn', () => { + (this.bot?.inventory as any)?.on?.('updateSlot', debouncedInventory); + }); + + // --- End event-driven socket updates --- + this.bot.on('goal_reached', () => { const pos = this.bot?.entity?.position; logger.info({ @@ -259,7 +383,7 @@ export class BotInstance { logger.warn({ bot: this.name, reason }, 'Bot was kicked'); this.state = BotState.DISCONNECTED; this.stopAmbientBehaviors(); - this.scheduleReconnect(); + // 'end' will also fire after kick — let 'end' handle reconnect }); this.bot.on('end', (reason) => { @@ -273,11 +397,24 @@ export class BotInstance { private static BOT_PASSWORD = 'dyobot2026'; + // Map bot personality to server class (hotbar slot) + // Classes: Warrior=0, Mage=1, Archer=2, Tank=3 + private static CLASS_MAP: Record = { + guard: { slot: 3, name: 'Tank' }, + blacksmith: { slot: 0, name: 'Warrior' }, + explorer: { slot: 2, name: 'Archer' }, + elder: { slot: 1, name: 'Mage' }, + merchant: { slot: 2, name: 'Archer' }, + farmer: { slot: 0, name: 'Warrior' }, + builder: { slot: 3, name: 'Tank' }, + }; + private handleAuth(onReady: () => void): void { if (!this.bot) return; const bot = this.bot; let authDone = false; + let classSelected = false; const finish = () => { if (authDone) return; @@ -287,12 +424,38 @@ export class BotInstance { onReady(); }; + const selectClass = () => { + if (classSelected) return; + classSelected = true; + const mapping = BotInstance.CLASS_MAP[this.personality] || { slot: 0, name: 'Warrior' }; + logger.info({ bot: this.name, class: mapping.name, slot: mapping.slot }, 'Selecting class'); + try { + bot.setQuickBarSlot(mapping.slot); + setTimeout(() => { + try { + bot.activateItem(); + logger.info({ bot: this.name, class: mapping.name }, 'Class selected via activateItem'); + } catch (e) { + logger.debug({ bot: this.name, err: String(e) }, 'activateItem failed, trying swingArm'); + try { bot.swingArm('right'); } catch {} + } + }, 500); + } catch (e) { + logger.warn({ bot: this.name, err: String(e) }, 'Class selection failed'); + } + }; + const onMessage = (jsonMsg: any) => { if (authDone) return; const msg = jsonMsg.toString(); + if (msg.trim()) { + logger.info({ bot: this.name, authMsg: msg.substring(0, 200) }, 'Auth phase message'); + } + // Auth login/register flow if (msg.includes('Registered successfully') || msg.includes('Logged in successfully') || msg.includes('already logged in')) { - finish(); + // Don't finish yet — class selection may follow + logger.info({ bot: this.name }, 'Login successful, waiting for class selection'); } else if (msg.includes('already registered') || msg.includes('Please log in')) { logger.info({ bot: this.name }, 'Already registered, logging in'); bot.chat(`/login ${BotInstance.BOT_PASSWORD}`); @@ -300,34 +463,58 @@ export class BotInstance { logger.info({ bot: this.name }, 'Registering with DyoAuth'); bot.chat(`/register ${BotInstance.BOT_PASSWORD} ${BotInstance.BOT_PASSWORD}`); } + + // Class selection flow + if (msg.includes('Choose your class') || msg.includes('choose your class')) { + logger.info({ bot: this.name }, 'Class selection prompt detected'); + setTimeout(() => selectClass(), 1000); + } else if (msg.includes('Please select a class')) { + logger.info({ bot: this.name }, 'Class reminder, retrying selection'); + classSelected = false; // Allow retry + setTimeout(() => selectClass(), 500); + } else if (msg.includes('You are now a') || msg.includes('Class selected') || msg.includes('you have selected')) { + logger.info({ bot: this.name }, 'Class confirmed, auth complete'); + finish(); + } }; bot.on('message', onMessage); - // Proactively try login after 1s, then register after 3s (in case message events were missed) + // Proactively try login after 2s (in case message event was missed) setTimeout(() => { - if (!authDone && bot) { - logger.info({ bot: this.name }, 'Proactively trying /login'); - bot.chat(`/login ${BotInstance.BOT_PASSWORD}`); + if (!authDone && bot && typeof bot.chat === 'function') { + try { + logger.info({ bot: this.name }, 'Proactively trying /login'); + bot.chat(`/login ${BotInstance.BOT_PASSWORD}`); + } catch (e) { + logger.debug({ bot: this.name, err: String(e) }, 'Proactive login failed'); + } } - }, 1000); + }, 2000); + // Try /register at 4s if still not authed setTimeout(() => { - if (!authDone && bot) { - logger.info({ bot: this.name }, 'Proactively trying /register'); - bot.chat(`/register ${BotInstance.BOT_PASSWORD} ${BotInstance.BOT_PASSWORD}`); + if (!authDone && bot && typeof bot.chat === 'function') { + try { + logger.info({ bot: this.name }, 'Proactively trying /register'); + bot.chat(`/register ${BotInstance.BOT_PASSWORD} ${BotInstance.BOT_PASSWORD}`); + } catch (e) { + logger.debug({ bot: this.name, err: String(e) }, 'Proactive register failed'); + } } - }, 3000); + }, 4000); - // Timeout fallback + // Timeout fallback — finish even if class wasn't confirmed setTimeout(() => { if (!authDone) { logger.warn({ bot: this.name }, 'Auth timeout, proceeding anyway'); finish(); } - }, 15000); + }, 20000); } + private reconnectTimer: ReturnType | null = null; + // Maps personality to the class hotbar slot (DyoClasses puts icons in slots 2-5) private static PERSONALITY_CLASS_MAP: Record = { guard: { slot: 2, className: 'Warrior' }, @@ -403,12 +590,19 @@ export class BotInstance { }, 10000); } + /** Reset reconnect state so the bot can be reconnected by the watchdog. */ + resetReconnect(): void { + this.reconnectAttempts = 0; + this.destroyed = false; + } + + isDestroyed(): boolean { + return this.destroyed; + } + private scheduleReconnect(): void { if (this.destroyed) return; - if (this.pendingConnectTimeout) { - logger.debug({ bot: this.name }, 'Reconnect already queued, skipping duplicate schedule'); - return; - } + if (this.reconnectTimer) return; // Already scheduled if (this.reconnectAttempts >= this.config.bots.maxReconnectAttempts) { logger.error({ bot: this.name }, 'Max reconnect attempts reached'); return; @@ -416,13 +610,13 @@ export class BotInstance { const delay = Math.min( this.config.bots.reconnectDelaySec * Math.pow(2, this.reconnectAttempts) * 1000, - 30000 + 60000 ); this.reconnectAttempts++; logger.info({ bot: this.name, delay, attempt: this.reconnectAttempts }, 'Scheduling reconnect'); - this.pendingConnectTimeout = setTimeout(() => { - this.pendingConnectTimeout = null; + this.reconnectTimer = setTimeout(() => { + this.reconnectTimer = null; void this.connect(); }, delay); } @@ -439,7 +633,7 @@ export class BotInstance { if (this.headTrackingInterval) return; this.headTrackingInterval = setInterval(() => { - if (!this.bot || this.state === BotState.DISCONNECTED) return; + if (!this.bot || this.state === BotState.DISCONNECTED || !this.bot.players || !this.bot.entity) return; if (this.mode === BotMode.CODEGEN && this.voyagerLoop?.getCurrentTask()) return; if (this.state === BotState.EXECUTING_TASK) return; @@ -510,11 +704,22 @@ export class BotInstance { // Ignore own messages and empty messages if (!this.bot || username === this.bot.username || !message.trim()) return; + logger.info({ bot: this.name, from: username, message }, 'Chat received'); + + // Check if the chatting player is actually another bot + const otherBot = this.botManager.getBot(username); + if (otherBot) { + this.botComms.sendMessage(username, this.name, message, 'chat'); + } + // Check if player is within conversation radius const player = this.bot.players[username]; - if (!player?.entity) return; - if (username.toLowerCase() !== BotInstance.OWNER_PLAYER.toLowerCase()) return; + if (!player?.entity) { + logger.debug({ bot: this.name, from: username, hasPlayer: !!player, hasEntity: !!player?.entity }, 'Chat ignored: no player entity'); + return; + } + // Handle swarm directives from owner const swarmMatch = message.match(/^swarm:\s*(.+)$/i); if (swarmMatch && this.onSwarmDirective) { const directive = swarmMatch[1].trim(); @@ -525,7 +730,10 @@ export class BotInstance { } const dist = player.entity.position.distanceTo(this.bot.entity.position); - if (dist > this.config.behavior.conversationRadius) return; + if (dist > this.config.behavior.conversationRadius) { + logger.debug({ bot: this.name, from: username, dist, radius: this.config.behavior.conversationRadius }, 'Chat ignored: out of range'); + return; + } // Rate limit per player const now = Date.now(); @@ -567,10 +775,10 @@ export class BotInstance { case 'follow': this.bot.chat(`Alright ${playerName}, I'll follow you.`); this.state = BotState.FOLLOWING; - if (this.voyagerLoop) this.voyagerLoop.pause(); + if (this.voyagerLoop) this.voyagerLoop.pause('command:follow'); followPlayer(this.bot, playerName, 600000).finally(() => { if (this.state === BotState.FOLLOWING) this.state = BotState.IDLE; - if (this.voyagerLoop) this.voyagerLoop.resume(); + if (this.voyagerLoop) this.voyagerLoop.resume('command:follow'); }); break; @@ -601,18 +809,18 @@ export class BotInstance { } this.bot.chat(`Starting build from ${match}. This may take a while...`); this.state = BotState.EXECUTING_TASK; - if (this.voyagerLoop) this.voyagerLoop.pause(); + if (this.voyagerLoop) this.voyagerLoop.pause('command:build-schematic'); const origin = this.bot.entity.position.floored(); buildSchematic(this.bot, match, { x: origin.x, y: origin.y, z: origin.z }, (placed, total) => { this.bot?.chat(`Building... ${placed}/${total} blocks`); }).then((result) => { if (this.bot) this.bot.chat(result.message ?? 'Build complete.'); this.state = BotState.IDLE; - if (this.voyagerLoop) this.voyagerLoop.resume(); + if (this.voyagerLoop) this.voyagerLoop.resume('command:build-schematic'); }).catch((err) => { this.bot?.chat(`Build failed: ${err.message}`); this.state = BotState.IDLE; - if (this.voyagerLoop) this.voyagerLoop.resume(); + if (this.voyagerLoop) this.voyagerLoop.resume('command:build-schematic'); }); break; } @@ -636,7 +844,17 @@ export class BotInstance { const affinity = this.affinityManager.get(this.name, playerName); const isCodegen = this.mode === BotMode.CODEGEN; const internalState = this.voyagerLoop?.getInternalState(); - const systemPrompt = buildSystemPrompt(this.name, this.personality, affinity, isCodegen, internalState); + + // Build social context for enhanced prompts + const memoryContext = this.socialMemory.buildMemoryContext(this.name, [playerName]); + const emotionalState = this.socialMemory.getEmotionalState(this.name); + const relationshipSummary = this.affinityManager.getRelationshipSummary(this.name, playerName); + const nearbyBots = this.botManager.getNearbyBotInfo(this.name); + + const systemPrompt = buildSystemPrompt( + this.name, this.personality, affinity, isCodegen, internalState, + { nearbyBots, memoryContext, emotionalState, relationshipSummary } + ); // Build conversation history (current message appended by buildContentsArray) const contents = this.conversationManager.buildContentsArray(this.name, playerName, message); @@ -676,13 +894,36 @@ export class BotInstance { 'Chat response sent' ); + // Record memory of this interaction + this.socialMemory.addMemory(this.name, 'social', + `${playerName} said: "${message.substring(0, 50)}". I responded about ${flatText.substring(0, 50)}`, + [playerName], + sentiment === 'POSITIVE' ? 6 : sentiment === 'NEGATIVE' ? 7 : 4 + ); + this.socialMemory.updateEmotionalState(this.name, + sentiment === 'POSITIVE' ? 'positive_chat' : sentiment === 'NEGATIVE' ? 'negative_chat' : 'social_interaction' + ); + this.lastInteractionAt = Date.now(); + // Queue task in Voyager loop if extracted - if (goalDescription && this.voyagerLoop) { - logger.info({ bot: this.name, player: playerName, goal: goalDescription }, 'Long-term goal extracted from chat'); - this.voyagerLoop.queueLongTermGoal(goalDescription, playerName); - } else if (taskDescription && this.voyagerLoop) { - logger.info({ bot: this.name, player: playerName, task: taskDescription }, 'Task extracted from chat'); - this.voyagerLoop.queuePlayerTask(taskDescription, playerName); + if ((goalDescription || taskDescription) && this.voyagerLoop) { + // Force-resume if paused so the player's request actually executes + if (this.voyagerLoop.isPaused()) { + this.voyagerLoop.forceResume('player-chat-request'); + logger.info({ bot: this.name, player: playerName }, 'Force-resumed voyager loop for player request'); + } + if (!this.voyagerLoop.isRunning()) { + this.voyagerLoop.start(); + logger.info({ bot: this.name, player: playerName }, 'Auto-started voyager loop for player request'); + } + + if (goalDescription) { + logger.info({ bot: this.name, player: playerName, goal: goalDescription }, 'Long-term goal extracted from chat'); + this.voyagerLoop.queueLongTermGoal(goalDescription, playerName); + } else if (taskDescription) { + logger.info({ bot: this.name, player: playerName, task: taskDescription }, 'Task extracted from chat'); + this.voyagerLoop.queuePlayerTask(taskDescription, playerName); + } } } catch (err: any) { logger.error({ bot: this.name, player: playerName, err: err.message }, 'Chat response failed'); @@ -693,14 +934,44 @@ export class BotInstance { private scheduleAmbientChat(): void { if (!this.llmClient) return; - // Ambient chat is rare — 10 to 20 minutes between attempts - const minMs = 600_000; // 10 minutes - const maxMs = 1_200_000; // 20 minutes + // Mood-based ambient chat intervals + const emotionalState = this.socialMemory.getEmotionalState(this.name); + let minMs: number; + let maxMs: number; + switch (emotionalState.mood) { + case 'lonely': + minMs = 300_000; // 5 minutes + maxMs = 600_000; // 10 minutes + break; + case 'annoyed': + minMs = 1_200_000; // 20 minutes + maxMs = 2_400_000; // 40 minutes + break; + case 'happy': + case 'excited': + minMs = 480_000; // 8 minutes + maxMs = 900_000; // 15 minutes + break; + case 'scared': + minMs = 900_000; // 15 minutes + maxMs = 1_800_000; // 30 minutes + break; + default: // neutral + minMs = 600_000; // 10 minutes + maxMs = 1_200_000; // 20 minutes + break; + } const delay = minMs + Math.random() * (maxMs - minMs); this.ambientChatTimeout = setTimeout(async () => { if (!this.bot || this.destroyed) return; + // Check for idle_long: if no interactions for 10+ minutes, trigger lonely mood + const idleMs = Date.now() - this.lastInteractionAt; + if (idleMs > 600_000) { // 10 minutes + this.socialMemory.updateEmotionalState(this.name, 'idle_long'); + } + // 30% chance to actually say something even when timer fires if (Math.random() > 0.3) { this.scheduleAmbientChat(); @@ -708,6 +979,10 @@ export class BotInstance { } // Find nearest player within conversation radius + if (!this.bot.players || !this.bot.entity) { + this.scheduleAmbientChat(); + return; + } const players = Object.values(this.bot.players).filter( (p) => p.entity && p.username !== this.bot!.username ); @@ -765,6 +1040,19 @@ export class BotInstance { this.llmClient ); this.voyagerLoop.setBlackboardManager(this.blackboardManager); + + // Wire social memory into task lifecycle + this.voyagerLoop.onTaskSuccess = (taskDescription: string) => { + this.socialMemory.addMemory(this.name, 'event', `Successfully completed: ${taskDescription}`, [], 5); + this.socialMemory.updateEmotionalState(this.name, 'task_success'); + this.lastInteractionAt = Date.now(); + }; + this.voyagerLoop.onTaskFailure = (taskDescription: string) => { + this.socialMemory.addMemory(this.name, 'event', `Failed task: ${taskDescription}`, [], 4); + this.socialMemory.updateEmotionalState(this.name, 'task_failure'); + this.lastInteractionAt = Date.now(); + }; + this.voyagerLoop.start(); } @@ -778,6 +1066,11 @@ export class BotInstance { this.lastAttackedAt = Date.now(); this.lastAttackerName = source?.username || source?.name || null; + // Track affinity hit when a real player (not another bot) attacks this bot + if (this.lastAttackerName && source?.type === 'player' && !this.botManager.getBot(this.lastAttackerName)) { + this.affinityManager.onHit(this.name, this.lastAttackerName); + } + if (!this.instinctActive) { this.activateInstinct('attack', trigger); logger.warn({ bot: this.name, trigger, attacker: this.lastAttackerName, health: this.bot.health }, 'Attack instinct triggered'); @@ -823,9 +1116,10 @@ export class BotInstance { this.bot.pathfinder.stop(); } this.clearMovementControls(); - if (this.voyagerLoop?.isRunning() && !this.voyagerLoop.isPaused()) { - this.voyagerLoop.pause(`instinct:${trigger}`); - this.voyagerPausedByInstinct = true; + if (this.voyagerLoop?.isRunning()) { + const key = `instinct:${trigger}`; + this.instinctPauseKey = key; + this.voyagerLoop.pause(key); } } @@ -877,10 +1171,10 @@ export class BotInstance { if (this.state === BotState.INSTINCT) { this.state = BotState.IDLE; } - if (!this.destroyed && this.state !== BotState.DISCONNECTED && this.voyagerPausedByInstinct && this.voyagerLoop) { - this.voyagerLoop.resume(`instinct-ended:${reason}`); + if (!this.destroyed && this.state !== BotState.DISCONNECTED && this.instinctPauseKey && this.voyagerLoop) { + this.voyagerLoop.resume(this.instinctPauseKey); } - this.voyagerPausedByInstinct = false; + this.instinctPauseKey = null; if (this.mode !== BotMode.CODEGEN) { this.startWandering(); } @@ -896,7 +1190,10 @@ export class BotInstance { if (dist > 12) return false; if (entity.type === 'hostile') return true; if (hostileNames.has(entity.name)) return true; - if (entity.type === 'player' && entity.username !== this.bot!.username) return true; + if (entity.type === 'player' && entity.username !== this.bot!.username) { + // Only treat players as threats if they are hostile (low affinity) + return this.affinityManager.isHostile(this.name, entity.username); + } return false; }); } @@ -1024,10 +1321,19 @@ export class BotInstance { clearTimeout(this.ambientChatTimeout); this.ambientChatTimeout = null; } + if (this.reflectionInterval) { + clearInterval(this.reflectionInterval); + this.reflectionInterval = null; + } if (this.voyagerLoop) { this.voyagerLoop.stop(); this.voyagerLoop = null; } + this.chatCooldowns.clear(); + if (this._inventoryDebounceTimer) { + clearTimeout(this._inventoryDebounceTimer); + this._inventoryDebounceTimer = null; + } } private sendLongChat(text: string): void { @@ -1074,11 +1380,27 @@ export class BotInstance { this.destroyed = true; this.stopAmbientBehaviors(); + if (this.pendingConnectTimeout) { + clearTimeout(this.pendingConnectTimeout); + this.pendingConnectTimeout = null; + } + if (this.reconnectTimer) { + clearTimeout(this.reconnectTimer); + this.reconnectTimer = null; + } + + this.botComms.unregisterListener(this.name); + if (this.bot) { + if (this.rawMessageHandler) { + this.bot.removeListener('message', this.rawMessageHandler); + this.rawMessageHandler = null; + } this.bot.quit(); this.bot = null; } + this.removeAllListeners(); this.state = BotState.DISCONNECTED; logger.info({ bot: this.name }, 'Bot disconnected and destroyed'); } @@ -1190,6 +1512,8 @@ export class BotInstance { isPaused: this.voyagerLoop.isPaused(), currentTask: this.voyagerLoop.getCurrentTask(), queuedTasks: this.voyagerLoop.getQueuedTasks().length, + queuedTaskCount: this.voyagerLoop.getQueueLength(), + queuedTaskPreviews: this.voyagerLoop.getQueuedTasksDetailed().slice(0, 5), lastExecution: this.voyagerLoop.getLastExecutionMetrics(), } : null, @@ -1208,4 +1532,26 @@ export class BotInstance { getVoyagerLoop(): VoyagerLoop | null { return this.voyagerLoop; } + + /** + * Detect task-like requests in bot-to-bot messages using keyword matching. + * Returns the extracted task string if the message looks like a request, or null otherwise. + */ + private extractBotTaskRequest(content: string): string | null { + const lower = content.toLowerCase(); + + // Only match direct requests — require "please", "can you", "could you", "go", "I need you to" + // or imperative form directed at this bot. This avoids matching status chatter like + // "I need to gather more resources" which is self-directed, not a request. + const directRequestPattern = /\b(please|can you|could you|go|i need you to|would you|will you)\b/; + if (!directRequestPattern.test(lower)) return null; + + // Check that the message also contains an action verb + noun + const taskPattern = /\b(mine|craft|gather|bring|come|help|build|collect|get|find|chop|dig|smelt|cook|farm|harvest|fetch|deliver|make|place|break)\b.*?\b(\w{3,})\b/; + const match = lower.match(taskPattern); + if (!match) return null; + + // Use the original content (preserving case) as the task description + return content.trim(); + } } diff --git a/src/bot/BotManager.ts b/src/bot/BotManager.ts index 9c1b68b..46fc7be 100644 --- a/src/bot/BotManager.ts +++ b/src/bot/BotManager.ts @@ -7,6 +7,8 @@ import { logger } from '../util/logger'; import { LLMClient } from '../ai/LLMClient'; import { AffinityManager } from '../personality/AffinityManager'; import { ConversationManager } from '../personality/ConversationManager'; +import { SocialMemory } from '../social/SocialMemory'; +import { BotComms } from '../social/BotComms'; import { BlackboardManager } from '../voyager/BlackboardManager'; interface SavedBot { @@ -23,7 +25,10 @@ export class BotManager { private llmClient: LLMClient | null; private affinityManager: AffinityManager; private conversationManager: ConversationManager; + private socialMemory: SocialMemory; + private botComms: BotComms; private blackboardManager: BlackboardManager; + private watchdogInterval: NodeJS.Timeout | null = null; constructor(config: Config, llmClient: LLMClient | null) { this.config = config; @@ -31,6 +36,8 @@ export class BotManager { this.llmClient = llmClient; this.affinityManager = new AffinityManager(config.affinity, path.join(process.cwd(), 'data')); this.conversationManager = new ConversationManager(); + this.socialMemory = new SocialMemory(); + this.botComms = new BotComms(); this.blackboardManager = new BlackboardManager(path.join(process.cwd(), 'data')); } @@ -55,6 +62,20 @@ export class BotManager { const effectiveMode = mode || this.config.bots.defaultMode; const botMode = effectiveMode === 'codegen' ? BotMode.CODEGEN : BotMode.PRIMITIVE; + // Whitelist and OP the new bot using an already-connected bot + const connectedBot = this.getAllBots().find((b) => b.bot); + if (connectedBot?.bot) { + try { + connectedBot.bot.chat(`/whitelist add ${name}`); + await new Promise((r) => setTimeout(r, 500)); + connectedBot.bot.chat(`/op ${name}`); + logger.info({ bot: name, via: connectedBot.name }, 'Whitelisted and OP\'d new bot'); + await new Promise((r) => setTimeout(r, 1000)); + } catch (e) { + logger.warn({ bot: name }, 'Failed to whitelist/OP bot — may need manual setup'); + } + } + const instance = new BotInstance({ name, personality, @@ -64,6 +85,9 @@ export class BotManager { llmClient: this.llmClient, affinityManager: this.affinityManager, conversationManager: this.conversationManager, + socialMemory: this.socialMemory, + botComms: this.botComms, + botManager: this, blackboardManager: this.blackboardManager, onSwarmDirective: (description, requestedBy) => this.handleSwarmDirective(description, requestedBy), }); @@ -100,6 +124,16 @@ export class BotManager { return count; } + /** Flush all pending debounced writes across every data manager. */ + shutdownPersistence(): void { + this.affinityManager.shutdown(); + this.socialMemory.shutdown(); + this.blackboardManager.shutdown(); + for (const bot of this.bots.values()) { + bot.getVoyagerLoop()?.shutdownPersistence(); + } + } + getBot(name: string): BotInstance | undefined { return this.bots.get(name.toLowerCase()); } @@ -124,10 +158,37 @@ export class BotManager { return this.conversationManager; } + getSocialMemory(): SocialMemory { + return this.socialMemory; + } + + getBotComms(): BotComms { + return this.botComms; + } + + getNearbyBotInfo(botName: string, radius: number = 64): { name: string; personality: string; activity: string }[] { + const bot = this.getBot(botName); + if (!bot?.bot?.entity) return []; + const botPos = bot.bot.entity.position; + + return this.getAllBots() + .filter(b => b.name !== botName && b.bot?.entity) + .filter(b => b.bot!.entity.position.distanceTo(botPos) <= radius) + .map(b => ({ + name: b.name, + personality: b.personality, + activity: b.getVoyagerLoop()?.getCurrentTask() || 'idle', + })); + } + getBlackboardManager(): BlackboardManager { return this.blackboardManager; } + getLLMClient(): LLMClient | null { + return this.llmClient; + } + async handleSwarmDirective(description: string, requestedBy: string): Promise { const bots = this.getAllBots().filter((bot) => bot.getVoyagerLoop()); for (const bot of bots) { @@ -149,6 +210,34 @@ export class BotManager { return true; } + /** Start a watchdog that reconnects disconnected bots every 60 seconds. */ + startWatchdog(): void { + if (this.watchdogInterval) return; + this.watchdogInterval = setInterval(() => this.watchdogTick(), 60_000); + logger.info('Bot watchdog started (60s interval)'); + } + + stopWatchdog(): void { + if (this.watchdogInterval) { + clearInterval(this.watchdogInterval); + this.watchdogInterval = null; + } + } + + private watchdogTick(): void { + for (const bot of this.bots.values()) { + if (bot.state !== 'DISCONNECTED') continue; + if (bot.isDestroyed()) { + logger.info({ bot: bot.name }, 'Watchdog: reviving destroyed bot'); + bot.resetReconnect(); + } else { + logger.info({ bot: bot.name }, 'Watchdog: reconnecting disconnected bot'); + bot.resetReconnect(); + } + void bot.connect(); + } + } + private saveBots(): void { const data: SavedBot[] = this.getAllBots().map((bot) => ({ name: bot.name, diff --git a/src/bot/BotState.ts b/src/bot/BotState.ts index 048a343..7caa348 100644 --- a/src/bot/BotState.ts +++ b/src/bot/BotState.ts @@ -8,6 +8,7 @@ export enum BotState { HOSTILE = 'HOSTILE', INSTINCT = 'INSTINCT', EXECUTING_TASK = 'EXECUTING_TASK', + BUILDING = 'BUILDING', DISCONNECTED = 'DISCONNECTED', } diff --git a/src/build/BuildCoordinator.ts b/src/build/BuildCoordinator.ts new file mode 100644 index 0000000..b70f4a4 --- /dev/null +++ b/src/build/BuildCoordinator.ts @@ -0,0 +1,704 @@ +import fs from 'fs'; +import path from 'path'; +import crypto from 'crypto'; +import { Vec3 } from 'vec3'; +import { BotManager } from '../bot/BotManager'; +import { BotState } from '../bot/BotState'; +import { Server as SocketIOServer } from 'socket.io'; +import { EventLog } from '../server/EventLog'; +import { logger } from '../util/logger'; + +// ── Interfaces ────────────────────────────────────────────── + +export interface SchematicInfo { + filename: string; + size: { x: number; y: number; z: number }; + blockCount: number; +} + +export interface BuildJob { + id: string; + schematicFile: string; + origin: { x: number; y: number; z: number }; + status: 'pending' | 'running' | 'paused' | 'completed' | 'cancelled' | 'failed'; + createdAt: number; + totalBlocks: number; + placedBlocks: number; + assignments: BotAssignment[]; + /** Bot names to remove when the build finishes (created specifically for this build) */ + cleanupBotNames?: string[]; +} + +export interface BotAssignment { + botName: string; + yMin: number; + yMax: number; + status: 'waiting' | 'building' | 'completed' | 'failed'; + blocksTotal: number; + blocksPlaced: number; + currentY: number; +} + +interface BlockEntry { + // Store as raw numbers instead of Vec3 to save memory + wx: number; wy: number; wz: number; // world position + name: string; + stateStr: string; // pre-computed block state string + localY: number; +} + +// ── Build Coordinator ─────────────────────────────────────── + +export class BuildCoordinator { + private botManager: BotManager; + private io: SocketIOServer; + private eventLog: EventLog; + private jobs: Map = new Map(); + private cancelledJobs: Set = new Set(); + private pausedJobs: Set = new Set(); + private schematicsDir: string; + + constructor(botManager: BotManager, io: SocketIOServer, eventLog: EventLog) { + this.botManager = botManager; + this.io = io; + this.eventLog = eventLog; + this.schematicsDir = path.join(process.cwd(), 'schematics'); + } + + // ── Schematic listing ─────────────────────────────────── + + private getBotVersion(): string { + const bots = this.botManager.getAllBots(); + const connected = bots.find((b) => b.bot); + return connected?.bot?.version ?? '1.21.11'; + } + + async listSchematics(): Promise { + if (!fs.existsSync(this.schematicsDir)) return []; + + const files = fs.readdirSync(this.schematicsDir).filter( + (f) => f.endsWith('.schem') || f.endsWith('.schematic'), + ); + + const results: SchematicInfo[] = []; + for (const filename of files) { + try { + // Skip files larger than 1MB to avoid OOM on huge schematics + const filePath = path.join(this.schematicsDir, filename); + const stat = fs.statSync(filePath); + if (stat.size > 10_000_000) { + results.push({ filename, size: { x: 0, y: 0, z: 0 }, blockCount: 0 }); + logger.info({ filename, sizeBytes: stat.size }, 'Skipping large schematic metadata load'); + continue; + } + const info = await this.getSchematicInfoSafe(filename); + if (info) results.push(info); + } catch (err: any) { + logger.warn({ filename, err: err.message }, 'Failed to read schematic metadata'); + results.push({ filename, size: { x: 0, y: 0, z: 0 }, blockCount: 0 }); + } + } + return results; + } + + /** Safe metadata loader — gets dimensions without holding the full schematic in memory */ + private async getSchematicInfoSafe(filename: string): Promise { + const { Schematic } = require('prismarine-schematic'); + const fullPath = path.join(this.schematicsDir, filename); + if (!fs.existsSync(fullPath)) return null; + + // Heuristic: compressed .schem files typically have ~100:1 to ~200:1 ratio. + // Files over 50KB compressed likely decompress to millions of voxels — skip parsing entirely. + const fileSize = fs.statSync(fullPath).size; + if (fileSize > 50_000) { + logger.info({ filename, fileSize }, 'Large schematic — estimating dimensions from file size'); + // Rough estimate: compressed size * 150 gives approx total voxels, cube-root for dimensions + const estVoxels = fileSize * 150; + const estDim = Math.round(Math.cbrt(estVoxels)); + return { filename, size: { x: estDim, y: Math.round(estDim * 0.6), z: estDim }, blockCount: Math.round(estVoxels * 0.15) }; + } + + let schematic: any; + try { + const buffer = fs.readFileSync(fullPath); + schematic = await Schematic.read(buffer, this.getBotVersion()); + } catch (err: any) { + logger.warn({ filename, err: err.message }, 'Failed to parse schematic'); + return { filename, size: { x: 0, y: 0, z: 0 }, blockCount: 0 }; + } + + const size = schematic.size; + const volume = size.x * size.y * size.z; + + // For large schematics, estimate block count and skip iteration + if (volume > 200_000) { + schematic = null; // release memory + return { filename, size: { x: size.x, y: size.y, z: size.z }, blockCount: Math.round(volume * 0.15) }; + } + + // For smaller schematics, count actual blocks + const start = schematic.start(); + const end = schematic.end(); + let blockCount = 0; + const tempPos = new Vec3(0, 0, 0); + for (let y = start.y; y <= end.y; y++) { + for (let z = start.z; z <= end.z; z++) { + for (let x = start.x; x <= end.x; x++) { + tempPos.x = x; tempPos.y = y; tempPos.z = z; + const block = schematic.getBlock(tempPos); + if (block && block.name !== 'air' && block.name !== 'cave_air' && block.name !== 'void_air') { + blockCount++; + } + } + } + } + schematic = null; // release memory + + return { filename, size: { x: size.x, y: size.y, z: size.z }, blockCount }; + } + + async getSchematicInfoAsync(filename: string): Promise { + return this.getSchematicInfoSafe(filename); + } + + // ── Build job management ──────────────────────────────── + + async startBuild( + schematicFile: string, + origin: { x: number; y: number; z: number }, + botNames: string[], + options?: { cleanupBotNames?: string[]; fillFoundation?: boolean; snapToGround?: boolean }, + ): Promise { + const { Schematic } = require('prismarine-schematic'); + const fullPath = path.join(this.schematicsDir, schematicFile); + + if (!fs.existsSync(fullPath)) { + throw new Error(`Schematic file not found: ${schematicFile}`); + } + + // Validate bots exist and are connected + for (const name of botNames) { + const instance = this.botManager.getBot(name); + if (!instance) throw new Error(`Bot not found: ${name}`); + if (!instance.bot) throw new Error(`Bot not connected: ${name}`); + } + + // Check file size before loading to prevent OOM + const fileStat = fs.statSync(fullPath); + if (fileStat.size > 10_000_000) { + throw new Error(`Schematic file too large (${(fileStat.size / 1_000_000).toFixed(1)}MB). Max file size: 10MB.`); + } + + // Load schematic + const buffer = fs.readFileSync(fullPath); + const schematic = await Schematic.read(buffer, this.getBotVersion()); + + // Check volume before iterating — reject if too large + const schSize = schematic.size; + const volume = schSize.x * schSize.y * schSize.z; + if (volume > 2_000_000) { + throw new Error(`Schematic volume too large (${schSize.x}x${schSize.y}x${schSize.z} = ${volume.toLocaleString()} voxels). Max 2M voxels.`); + } + + const ox = origin.x, oy = origin.y, oz = origin.z; + const start = schematic.start(); + const end = schematic.end(); + const sx = start.x, sy = start.y, sz = start.z; + + // Collect all non-air blocks as lightweight objects (no Vec3 to save memory) + const blocks: BlockEntry[] = []; + const tempPos = new Vec3(0, 0, 0); // reuse single Vec3 + for (let y = start.y; y <= end.y; y++) { + for (let z = start.z; z <= end.z; z++) { + for (let x = start.x; x <= end.x; x++) { + tempPos.x = x; tempPos.y = y; tempPos.z = z; + const block = schematic.getBlock(tempPos); + if (block && block.name !== 'air' && block.name !== 'cave_air' && block.name !== 'void_air') { + const props = block.getProperties ? block.getProperties() : {}; + const stateStr = Object.entries(props).map(([k, v]) => `${k}=${v}`).join(','); + blocks.push({ + wx: ox + x - sx, wy: oy + y - sy, wz: oz + z - sz, + name: block.name, + stateStr, + localY: y - sy, + }); + } + } + } + } + // Free schematic from memory + // @ts-ignore + buffer.fill(0); + + if (blocks.length === 0) { + throw new Error('Schematic contains no blocks'); + } + + // ── Snap-to-ground: adjust origin Y to average terrain height ── + const fillFoundation = options?.fillFoundation !== false; // default true + const snapToGround = options?.snapToGround === true; // default false + + // Find a connected bot to query world blocks + const probeInstance = botNames + .map((n) => this.botManager.getBot(n)) + .find((inst) => inst?.bot); + const probeBot = probeInstance?.bot; + + if (snapToGround && probeBot) { + // Build a grid of sample points across the footprint + const footprintXZ = new Map(); + for (const b of blocks) { + const key = `${b.wx},${b.wz}`; + if (!footprintXZ.has(key)) footprintXZ.set(key, { wx: b.wx, wz: b.wz }); + } + + // Sample up to 50 evenly-spaced columns + const allColumns = [...footprintXZ.values()]; + const sampleStep = Math.max(1, Math.floor(allColumns.length / 50)); + const samples: number[] = []; + for (let i = 0; i < allColumns.length; i += sampleStep) { + const col = allColumns[i]; + // Scan downward from origin Y + 10 to find the first solid block + for (let y = oy + 10; y >= oy - 30; y--) { + const wb = probeBot.blockAt(new Vec3(col.wx, y, col.wz)); + if (wb && wb.name !== 'air' && wb.name !== 'cave_air' && wb.name !== 'void_air') { + samples.push(y + 1); // ground surface is top of this block + break; + } + } + } + + if (samples.length > 0) { + // Use median ground level + samples.sort((a, b) => a - b); + const medianGround = samples[Math.floor(samples.length / 2)]; + const diff = medianGround - oy; + if (Math.abs(diff) <= 5 && diff !== 0) { + logger.info( + { oldY: oy, newY: medianGround, diff, samples: samples.length }, + 'Snap-to-ground: adjusting origin Y to median terrain height', + ); + // Shift all block world-Y positions by the difference + for (const b of blocks) { + b.wy += diff; + } + origin.y = medianGround; + } else if (Math.abs(diff) > 5) { + logger.info( + { originY: oy, medianGround, diff }, + 'Snap-to-ground: terrain difference too large, skipping adjustment', + ); + } + } + } + + // ── Foundation filling: add support blocks under schematic footprint ── + if (fillFoundation && probeBot) { + const MAX_FILL_DEPTH = 20; + + // For each (wx, wz) column, find the lowest schematic block Y + const columnMinY = new Map(); + for (const b of blocks) { + const key = `${b.wx},${b.wz}`; + const prev = columnMinY.get(key); + if (prev === undefined || b.wy < prev) { + columnMinY.set(key, b.wy); + } + } + + const foundationBlocks: BlockEntry[] = []; + let skippedLiquid = 0; + let skippedOutOfRange = 0; + + for (const [key, minBlockY] of columnMinY) { + const [wxStr, wzStr] = key.split(','); + const wx = parseInt(wxStr, 10); + const wz = parseInt(wzStr, 10); + + // Scan downward from minBlockY - 1 to find ground + let filledCount = 0; + for (let y = minBlockY - 1; y >= minBlockY - MAX_FILL_DEPTH; y--) { + const wb = probeBot.blockAt(new Vec3(wx, y, wz)); + if (!wb) { + // Bot too far to query — skip this column + skippedOutOfRange++; + break; + } + + if (wb.name === 'air' || wb.name === 'cave_air' || wb.name === 'void_air') { + // Air gap — fill with stone + foundationBlocks.push({ + wx, wy: y, wz, + name: 'stone', + stateStr: '', + localY: -1 - filledCount, // negative localY so they sort before schematic blocks + }); + filledCount++; + } else if (wb.name === 'water' || wb.name === 'lava' + || wb.name === 'flowing_water' || wb.name === 'flowing_lava') { + logger.warn( + { wx, y, wz, liquid: wb.name }, + 'Foundation fill: liquid detected under schematic, skipping column', + ); + skippedLiquid++; + break; + } else { + // Hit solid ground — done with this column + break; + } + } + } + + if (foundationBlocks.length > 0) { + logger.info( + { foundationBlocks: foundationBlocks.length, columns: columnMinY.size, skippedLiquid, skippedOutOfRange }, + 'Foundation fill: adding support blocks under schematic', + ); + // Sort foundation blocks bottom-up so they build from ground level + foundationBlocks.sort((a, b) => a.wy - b.wy); + // Prepend foundation blocks before schematic blocks + blocks.unshift(...foundationBlocks); + } else { + logger.info('Foundation fill: no gaps detected, no fill needed'); + } + } else if (fillFoundation && !probeBot) { + logger.warn('Foundation fill requested but no connected bot available to probe terrain'); + } + + // Safety limit on actual block count to prevent OOM + const MAX_BLOCKS = 500000; + if (blocks.length > MAX_BLOCKS) { + throw new Error(`Schematic has ${blocks.length.toLocaleString()} blocks. Max supported: ${MAX_BLOCKS.toLocaleString()}.`); + } + + // Determine Y range + const minLocalY = 0; + const maxLocalY = end.y - start.y; + + // Partition by block count so each bot gets roughly equal work + // Group blocks by Y layer, then assign layers to bots greedily + const blocksPerY = new Map(); + for (const b of blocks) { + blocksPerY.set(b.localY, (blocksPerY.get(b.localY) || 0) + 1); + } + const yLevels = [...blocksPerY.keys()].sort((a, b) => a - b); + const targetPerBot = Math.ceil(blocks.length / botNames.length); + + // Assign contiguous Y ranges to each bot, splitting when block count target is reached + const botRanges: { yMin: number; yMax: number; count: number }[] = []; + let currentCount = 0; + let rangeStart = yLevels[0] ?? 0; + + for (let i = 0; i < yLevels.length; i++) { + currentCount += blocksPerY.get(yLevels[i])!; + const isLast = i === yLevels.length - 1; + const reachedTarget = currentCount >= targetPerBot && botRanges.length < botNames.length - 1; + + if (reachedTarget || isLast) { + botRanges.push({ yMin: rangeStart, yMax: yLevels[i], count: currentCount }); + currentCount = 0; + if (i < yLevels.length - 1) rangeStart = yLevels[i + 1]; + } + } + + // If fewer ranges than bots (very flat schematic), only use needed bots + const assignments: BotAssignment[] = botNames.slice(0, botRanges.length).map((botName, idx) => { + const range = botRanges[idx]; + const botBlocks = blocks.filter((b) => b.localY >= range.yMin && b.localY <= range.yMax); + return { + botName, + yMin: range.yMin, + yMax: range.yMax, + status: 'waiting' as const, + blocksTotal: botBlocks.length, + blocksPlaced: 0, + currentY: range.yMin, + } as BotAssignment; + }); + + logger.info( + { assignments: assignments.map(a => ({ bot: a.botName, yMin: a.yMin, yMax: a.yMax, blocks: a.blocksTotal })) }, + 'Build work partitioned by block count', + ); + + const jobId = crypto.randomUUID(); + const job: BuildJob = { + id: jobId, + schematicFile, + origin, + status: 'running', + createdAt: Date.now(), + totalBlocks: blocks.length, + placedBlocks: 0, + assignments, + cleanupBotNames: options?.cleanupBotNames, + }; + + this.jobs.set(jobId, job); + + // Emit started event + this.io.emit('build:started', { job }); + this.eventLog.push({ + type: 'build:started', + botName: botNames.join(', '), + description: `Build started: ${schematicFile} with ${botNames.length} bot(s)`, + metadata: { jobId, schematicFile, origin, botNames }, + }); + + logger.info( + { jobId, schematicFile, origin, bots: botNames, totalBlocks: blocks.length }, + 'Multi-bot build started', + ); + + // Start execution in background (non-blocking) + this.executeBuild(jobId, blocks, assignments).catch((err) => { + logger.error({ jobId, err }, 'Build execution failed'); + job.status = 'failed'; + this.io.emit('build:completed', { job, error: err.message }); + }); + + return job; + } + + cancelBuild(jobId: string): boolean { + const job = this.jobs.get(jobId); + if (!job || job.status === 'completed' || job.status === 'cancelled') return false; + + this.cancelledJobs.add(jobId); + job.status = 'cancelled'; + + // Reset bot states + for (const assignment of job.assignments) { + if (assignment.status === 'building' || assignment.status === 'waiting') { + assignment.status = 'failed'; + const instance = this.botManager.getBot(assignment.botName); + if (instance) instance.state = BotState.IDLE; + } + } + + this.io.emit('build:cancelled', { jobId }); + this.eventLog.push({ + type: 'build:cancelled', + botName: job.assignments.map((a) => a.botName).join(', '), + description: `Build cancelled: ${job.schematicFile}`, + metadata: { jobId }, + }); + + logger.info({ jobId }, 'Build cancelled'); + return true; + } + + pauseBuild(jobId: string): boolean { + const job = this.jobs.get(jobId); + if (!job || job.status !== 'running') return false; + + this.pausedJobs.add(jobId); + job.status = 'paused'; + + this.io.emit('build:bot-status', { jobId, status: 'paused' }); + logger.info({ jobId }, 'Build paused'); + return true; + } + + resumeBuild(jobId: string): boolean { + const job = this.jobs.get(jobId); + if (!job || job.status !== 'paused') return false; + + this.pausedJobs.delete(jobId); + job.status = 'running'; + + this.io.emit('build:bot-status', { jobId, status: 'running' }); + logger.info({ jobId }, 'Build resumed'); + return true; + } + + getBuildJob(jobId: string): BuildJob | undefined { + return this.jobs.get(jobId); + } + + getAllBuildJobs(): BuildJob[] { + return [...this.jobs.values()]; + } + + // ── Execution engine ──────────────────────────────────── + + private async executeBuild( + jobId: string, + blocks: BlockEntry[], + assignments: BotAssignment[], + ): Promise { + const job = this.jobs.get(jobId)!; + + // Execute all bot assignments in parallel — each bot works on its Y range + const promises = assignments.map(async (assignment, i) => { + // Check for cancellation + if (this.cancelledJobs.has(jobId)) return; + + // Get the mineflayer bot instance + const instance = this.botManager.getBot(assignment.botName); + if (!instance || !instance.bot) { + assignment.status = 'failed'; + logger.error({ jobId, bot: assignment.botName }, 'Bot not available for building'); + return; + } + + // Pause voyager loop so the bot doesn't wander off + const voyager = instance.getVoyagerLoop(); + if (voyager) voyager.pause('building'); + + // Stop any current pathfinding/movement + try { + instance.bot.pathfinder.stop(); + instance.bot.clearControlStates(); + } catch {} + + // Set creative mode so bot can't die during build, then teleport to site + try { + const opBot = this.botManager.getAllBots().find((b) => b.bot && b.name !== assignment.botName); + const cmds = [ + `/gamemode creative ${assignment.botName}`, + `/tp ${assignment.botName} ${job.origin.x} ${job.origin.y + 50} ${job.origin.z}`, + ]; + for (const cmd of cmds) { + if (opBot?.bot) opBot.bot.chat(cmd); + instance.bot.chat(cmd); + await this.sleep(500); + } + await this.sleep(1000); + logger.info({ bot: assignment.botName }, 'Set creative mode and teleported to build site'); + } catch (e) { + logger.warn({ bot: assignment.botName }, 'Failed to prepare bot for building'); + } + + // Set bot state to BUILDING + instance.state = BotState.BUILDING; + assignment.status = 'building'; + + this.io.emit('build:bot-status', { + jobId, + botName: assignment.botName, + status: 'building', + yMin: assignment.yMin, + yMax: assignment.yMax, + }); + + // Get blocks for this assignment's Y range + const botBlocks = blocks.filter( + (b) => b.localY >= assignment.yMin && b.localY <= assignment.yMax, + ); + + // Stagger start by 2s per bot to avoid overwhelming server + if (i > 0) await new Promise((r) => setTimeout(r, i * 2000)); + + try { + await this.executeBotAssignment(jobId, job, assignment, instance.bot, botBlocks); + assignment.status = 'completed'; + } catch (err: any) { + assignment.status = 'failed'; + logger.error({ jobId, bot: assignment.botName, err: err.message }, 'Bot assignment failed'); + } + + // Switch back to survival and reset bot state + try { + instance.bot.chat(`/gamemode survival ${assignment.botName}`); + } catch {} + instance.state = BotState.IDLE; + if (voyager) voyager.resume(); + + this.io.emit('build:bot-status', { + jobId, + botName: assignment.botName, + status: assignment.status, + blocksPlaced: assignment.blocksPlaced, + }); + }); + + await Promise.all(promises); + + // Final status + if (!this.cancelledJobs.has(jobId)) { + const allCompleted = assignments.every((a) => a.status === 'completed'); + job.status = allCompleted ? 'completed' : 'failed'; + + this.io.emit('build:completed', { job }); + this.eventLog.push({ + type: 'build:completed', + botName: assignments.map((a) => a.botName).join(', '), + description: `Build ${job.status}: ${job.schematicFile} (${job.placedBlocks}/${job.totalBlocks} blocks)`, + metadata: { jobId, status: job.status }, + }); + + logger.info( + { jobId, status: job.status, placed: job.placedBlocks, total: job.totalBlocks }, + 'Build finished', + ); + + // Remove bots that were created specifically for this build + if (job.cleanupBotNames && job.cleanupBotNames.length > 0) { + for (const botName of job.cleanupBotNames) { + try { + const removed = await this.botManager.removeBot(botName); + if (removed) { + logger.info({ botName, jobId }, 'Cleaned up build bot'); + this.io.emit('bot:disconnect', { bot: botName }); + } + } catch (err) { + logger.warn({ botName, jobId, err }, 'Failed to cleanup build bot'); + } + } + } + } + + // Cleanup cancellation tracking + this.cancelledJobs.delete(jobId); + } + + private async executeBotAssignment( + jobId: string, + job: BuildJob, + assignment: BotAssignment, + bot: any, + blocks: BlockEntry[], + ): Promise { + for (const block of blocks) { + // Check cancellation + if (this.cancelledJobs.has(jobId)) return; + + // Check pause — spin-wait with sleep + while (this.pausedJobs.has(jobId)) { + if (this.cancelledJobs.has(jobId)) return; + await this.sleep(500); + } + + // Place block using /setblock command + const blockSpec = block.stateStr ? `${block.name}[${block.stateStr}]` : block.name; + bot.chat( + `/setblock ${block.wx} ${block.wy} ${block.wz} minecraft:${blockSpec} replace`, + ); + + // 250ms delay between blocks to avoid server spam kick + await this.sleep(250); + + assignment.blocksPlaced++; + assignment.currentY = block.localY; + job.placedBlocks++; + + // Emit progress every 20 blocks + if (job.placedBlocks % 20 === 0) { + this.io.emit('build:progress', { + jobId, + placedBlocks: job.placedBlocks, + totalBlocks: job.totalBlocks, + percentage: Math.round((job.placedBlocks / job.totalBlocks) * 100), + botName: assignment.botName, + botBlocksPlaced: assignment.blocksPlaced, + botBlocksTotal: assignment.blocksTotal, + }); + } + } + } + + private sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); + } +} diff --git a/src/control/CommandCenter.ts b/src/control/CommandCenter.ts new file mode 100644 index 0000000..e813d04 --- /dev/null +++ b/src/control/CommandCenter.ts @@ -0,0 +1,888 @@ +import { Server as SocketIOServer } from 'socket.io'; +import { + CommandRecord, + CommandType, + CommandScope, + CommandPriority, + CommandSource, + CommandStatus, + CommandError, + COMMAND_EVENTS, +} from './CommandTypes'; +import { BotManager } from '../bot/BotManager'; +import { BotInstance } from '../bot/BotInstance'; +import { MarkerStore } from './MarkerStore'; +import { logger } from '../util/logger'; +import * as fs from 'fs'; +import * as path from 'path'; + +export interface CommandMetrics { + totalCreated: number; + totalSucceeded: number; + totalFailed: number; + totalCancelled: number; + totalTimedOut: number; + averageDurationMs: number; + byType: Record; + byBot: Record; +} + +export interface CreateCommandParams { + type: CommandType; + scope?: CommandScope; + priority?: CommandPriority; + source?: CommandSource; + targets: string[]; + params?: Record; + /** Alias for params — either field is accepted */ + payload?: Record; +} + +interface CommandFilters { + bot?: string; + status?: CommandStatus; + limit?: number; +} + +const DATA_PATH = path.join(process.cwd(), 'data', 'commands.json'); +const MAX_PERSISTED = 500; + +/** Command types that involve pathfinder-based movement */ +const MOVEMENT_COMMAND_TYPES: ReadonlySet = new Set([ + 'walk_to_coords', + 'move_to_marker', + 'follow_player', + 'patrol_route', + 'guard_zone', +]); + +/** How long a command may stay in 'started' before being auto-failed (ms) */ +const COMMAND_TIMEOUT_MS = 60_000; + +/** Interval at which we check for timed-out commands (ms) */ +const TIMEOUT_CHECK_INTERVAL_MS = 10_000; + +export class CommandCenter { + private commands: Map = new Map(); + private botManager: BotManager; + private io: SocketIOServer; + private markerStore: MarkerStore | null; + private timeoutTimer: ReturnType | null = null; + + constructor(botManager: BotManager, io: SocketIOServer, markerStore?: MarkerStore) { + this.botManager = botManager; + this.io = io; + this.markerStore = markerStore ?? null; + this.loadFromDisk(); + this.startTimeoutChecker(); + } + + // ── Cleanup ───────────────────────────────────────────── + + /** Stop the timeout checker interval (call on shutdown) */ + destroy(): void { + if (this.timeoutTimer) { + clearInterval(this.timeoutTimer); + this.timeoutTimer = null; + } + } + + // ── ID generation ────────────────────────────────────────── + + private generateId(): string { + return `cmd_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`; + } + + // ── Public API ───────────────────────────────────────────── + + createCommand(input: CreateCommandParams): CommandRecord { + // Accept either `params` or `payload` for backward compat + const resolvedParams = input.params ?? input.payload ?? {}; + + const command: CommandRecord = { + id: this.generateId(), + type: input.type, + scope: input.scope ?? 'single', + priority: input.priority ?? 'normal', + source: input.source ?? 'api', + status: 'queued', + targets: input.targets, + params: resolvedParams, + createdAt: new Date().toISOString(), + }; + + this.commands.set(command.id, command); + this.emitStatus(command, COMMAND_EVENTS.QUEUED); + this.persist(); + + this.logLifecycle(command, 'Command created'); + + return command; + } + + async dispatchCommand(command: CommandRecord): Promise { + // Fan-out for multi-target scopes + if ( + (command.scope === 'squad' || command.scope === 'selection' || command.scope === 'all') && + command.targets.length > 1 + ) { + return this.dispatchFanOut(command); + } + + const botName = command.targets[0]; + if (!botName) { + command.error = { code: 'NO_TARGET', message: 'No target bot specified' }; + this.updateStatus(command, 'failed'); + return command; + } + + // ── Task 4: Validate bot exists and is connected ── + const bot = this.botManager.getBot(botName); + if (!bot) { + command.error = { code: 'BOT_NOT_FOUND', message: `Bot "${botName}" not found`, botName }; + this.updateStatus(command, 'failed'); + return command; + } + + if (!bot.bot) { + command.error = { code: 'BOT_OFFLINE', message: `Bot "${botName}" is not connected`, botName }; + this.updateStatus(command, 'failed'); + return command; + } + + // ── Task 3: Concurrent command protection ── + // If the bot already has an active (started) command, cancel it first + await this.cancelActiveCommandForBot(botName, 'superseded'); + + this.updateStatus(command, 'started'); + + try { + const result = await this.executeHandler(command.type, bot, command.params); + command.result = result; + this.updateStatus(command, 'succeeded'); + } catch (err: any) { + const cmdError: CommandError = + err && err.code + ? err + : { code: 'HANDLER_ERROR', message: String(err?.message ?? err) }; + command.error = cmdError; + this.updateStatus(command, 'failed'); + } + + return command; + } + + getCommands(filters?: CommandFilters): CommandRecord[] { + let results = [...this.commands.values()]; + + // Newest first + results.sort((a, b) => b.createdAt.localeCompare(a.createdAt)); + + if (filters?.bot) { + results = results.filter((c) => c.targets.includes(filters.bot!)); + } + if (filters?.status) { + results = results.filter((c) => c.status === filters.status); + } + const limit = filters?.limit ?? 100; + return results.slice(0, limit); + } + + getCommand(id: string): CommandRecord | undefined { + return this.commands.get(id); + } + + getMetrics(): CommandMetrics { + const all = [...this.commands.values()]; + + const totalCreated = all.length; + let totalSucceeded = 0; + let totalFailed = 0; + let totalCancelled = 0; + let totalTimedOut = 0; + let totalDurationMs = 0; + let durationCount = 0; + + const byType: Record = {}; + const byBot: Record = {}; + + for (const cmd of all) { + // Per-status counters + if (cmd.status === 'succeeded') totalSucceeded++; + else if (cmd.status === 'failed') { + totalFailed++; + if (cmd.error?.code === 'TIMEOUT') totalTimedOut++; + } + else if (cmd.status === 'cancelled') totalCancelled++; + + // Duration + const duration = this.computeDurationMs(cmd); + if (duration !== undefined) { + totalDurationMs += duration; + durationCount++; + } + + // By type + if (!byType[cmd.type]) { + byType[cmd.type] = { count: 0, succeeded: 0, failed: 0, totalDurationMs: 0, durationCount: 0 }; + } + const t = byType[cmd.type]; + t.count++; + if (cmd.status === 'succeeded') t.succeeded++; + else if (cmd.status === 'failed') t.failed++; + if (duration !== undefined) { + t.totalDurationMs += duration; + t.durationCount++; + } + + // By bot (first target) + const botName = cmd.targets?.[0]; + if (botName) { + if (!byBot[botName]) { + byBot[botName] = { count: 0, succeeded: 0, failed: 0 }; + } + const b = byBot[botName]; + b.count++; + if (cmd.status === 'succeeded') b.succeeded++; + else if (cmd.status === 'failed') b.failed++; + } + } + + // Build final byType with avgDurationMs + const byTypeFinal: CommandMetrics['byType'] = {}; + for (const [type, data] of Object.entries(byType)) { + byTypeFinal[type] = { + count: data.count, + succeeded: data.succeeded, + failed: data.failed, + avgDurationMs: data.durationCount > 0 ? Math.round(data.totalDurationMs / data.durationCount) : 0, + }; + } + + return { + totalCreated, + totalSucceeded, + totalFailed, + totalCancelled, + totalTimedOut, + averageDurationMs: durationCount > 0 ? Math.round(totalDurationMs / durationCount) : 0, + byType: byTypeFinal, + byBot, + }; + } + + private computeDurationMs(cmd: CommandRecord): number | undefined { + if (cmd.startedAt && cmd.completedAt) { + return new Date(cmd.completedAt).getTime() - new Date(cmd.startedAt).getTime(); + } + return undefined; + } + + /** Return count of commands in a given status */ + getCountByStatus(status: CommandStatus): number { + let count = 0; + for (const cmd of this.commands.values()) { + if (cmd.status === status) count++; + } + return count; + } + + /** Return count of commands that failed within the last N milliseconds */ + getRecentFailedCount(withinMs: number): number { + const cutoff = new Date(Date.now() - withinMs).toISOString(); + let count = 0; + for (const cmd of this.commands.values()) { + if (cmd.status === 'failed' && cmd.completedAt && cmd.completedAt >= cutoff) { + count++; + } + } + return count; + } + + cancelCommand(id: string, reason?: string): CommandRecord | undefined { + const command = this.commands.get(id); + if (!command) return undefined; + + if (command.status === 'queued' || command.status === 'started') { + // ── Task 1: Stop pathfinder for movement commands that are in-flight ── + if (command.status === 'started' && MOVEMENT_COMMAND_TYPES.has(command.type)) { + this.stopPathfinderForTargets(command.targets); + } + + if (reason) { + command.error = { code: 'CANCELLED', message: reason }; + } + this.updateStatus(command, 'cancelled'); + return command; + } + + // Already terminal — return as-is + return command; + } + + // ── Task 2: Timeout handling ─────────────────────────────── + + /** Check all started commands for timeout. Called on a 10-second interval. */ + checkTimeouts(): void { + const now = Date.now(); + + for (const command of this.commands.values()) { + if (command.status !== 'started') continue; + if (!command.startedAt) continue; + + const startedMs = new Date(command.startedAt).getTime(); + const elapsed = now - startedMs; + + if (elapsed > COMMAND_TIMEOUT_MS) { + // Stop pathfinder if it was a movement command + if (MOVEMENT_COMMAND_TYPES.has(command.type)) { + this.stopPathfinderForTargets(command.targets); + } + + command.error = { code: 'TIMEOUT', message: 'Command timed out' }; + this.updateStatus(command, 'failed'); + + logger.warn( + { commandId: command.id, botName: command.targets[0], type: command.type, durationMs: elapsed }, + 'Command timed out', + ); + } + } + } + + // ── Task 3: Cancel any active command for a specific bot ── + + private async cancelActiveCommandForBot(botName: string, reason: string): Promise { + for (const command of this.commands.values()) { + if (command.status === 'started' && command.targets.includes(botName)) { + this.cancelCommand(command.id, reason); + logger.info( + { commandId: command.id, botName, type: command.type, reason }, + 'Active command superseded by new command', + ); + } + } + } + + // ── Pathfinder stop helper ─────────────────────────────── + + private stopPathfinderForTargets(targets: string[]): void { + for (const botName of targets) { + try { + const bot = this.botManager.getBot(botName); + if (bot?.bot) { + bot.bot.pathfinder.stop(); + logger.debug({ botName }, 'Pathfinder stopped for cancelled/timed-out command'); + } + } catch (err: any) { + logger.warn({ err, botName }, 'Failed to stop pathfinder'); + } + } + } + + // ── Fan-out ──────────────────────────────────────────────── + + private async dispatchFanOut(parent: CommandRecord): Promise { + this.updateStatus(parent, 'started'); + + const childIds: string[] = []; + let allSucceeded = true; + const errors: CommandError[] = []; + + for (const target of parent.targets) { + const child = this.createCommand({ + type: parent.type, + scope: 'single', + priority: parent.priority, + source: parent.source, + targets: [target], + params: parent.params, + }); + child.parentCommandId = parent.id; + childIds.push(child.id); + + await this.dispatchCommand(child); + + if (child.status !== 'succeeded') { + allSucceeded = false; + if (child.error) errors.push(child.error); + } + } + + parent.childCommandIds = childIds; + + if (allSucceeded) { + parent.result = { childCount: childIds.length, allSucceeded: true }; + this.updateStatus(parent, 'succeeded'); + } else { + parent.error = { + code: 'PARTIAL_FAILURE', + message: `${errors.length}/${parent.targets.length} targets failed`, + }; + parent.result = { childCount: childIds.length, failedCount: errors.length, errors }; + this.updateStatus(parent, 'failed'); + } + + return parent; + } + + // ── Command handlers ─────────────────────────────────────── + + private async executeHandler( + type: CommandType, + bot: BotInstance, + params: Record, + ): Promise> { + switch (type) { + case 'pause_voyager': + return this.handlePauseVoyager(bot); + case 'resume_voyager': + return this.handleResumeVoyager(bot); + case 'stop_movement': + return this.handleStopMovement(bot); + case 'follow_player': + return this.handleFollowPlayer(bot, params); + case 'walk_to_coords': + return this.handleWalkToCoords(bot, params); + case 'move_to_marker': + return this.handleMoveToMarker(bot, params); + case 'return_to_base': + return this.handleReturnToBase(bot); + case 'regroup': + return this.handleRegroup(bot, params); + case 'guard_zone': + return this.handleGuardZone(bot, params); + case 'patrol_route': + return this.handlePatrolRoute(bot, params); + case 'deposit_inventory': + return this.handleDepositInventory(bot); + case 'equip_best': + return this.handleEquipBest(bot); + case 'unstuck': + return this.handleUnstuck(bot); + default: + throw { code: 'UNKNOWN_COMMAND', message: `Unknown command type: ${type}` }; + } + } + + private handlePauseVoyager(bot: BotInstance): Record { + const voyager = bot.getVoyagerLoop(); + if (!voyager) { + throw { code: 'NO_VOYAGER', message: `${bot.name} is not running a voyager loop` } as CommandError; + } + voyager.pause('dashboard'); + logger.info({ botName: bot.name }, 'Voyager paused via command'); + return { paused: true }; + } + + private handleResumeVoyager(bot: BotInstance): Record { + const voyager = bot.getVoyagerLoop(); + if (!voyager) { + throw { code: 'NO_VOYAGER', message: `${bot.name} is not running a voyager loop` } as CommandError; + } + voyager.forceResume('dashboard'); + logger.info({ botName: bot.name }, 'Voyager force-resumed via command'); + return { resumed: true }; + } + + private handleStopMovement(bot: BotInstance): Record { + if (!bot.bot) { + throw { code: 'BOT_OFFLINE', message: `${bot.name} is not connected` } as CommandError; + } + bot.bot.pathfinder.stop(); + logger.info({ botName: bot.name }, 'Movement stopped via command'); + return { stopped: true }; + } + + private handleFollowPlayer(bot: BotInstance, params: Record): Record { + if (!bot.bot) { + throw { code: 'BOT_OFFLINE', message: `${bot.name} is not connected` } as CommandError; + } + const playerName = params.playerName; + if (!playerName) { + throw { code: 'MISSING_PARAM', message: 'playerName is required' } as CommandError; + } + + const target = bot.bot.players[playerName]?.entity; + if (!target) { + throw { code: 'PLAYER_NOT_FOUND', message: `Player "${playerName}" not found nearby` } as CommandError; + } + + const { goals } = require('mineflayer-pathfinder'); + bot.bot.pathfinder.setGoal(new goals.GoalFollow(target, 3), true); + + logger.info({ botName: bot.name, playerName }, 'Following player via command'); + return { following: playerName }; + } + + private handleWalkToCoords(bot: BotInstance, params: Record): Record { + if (!bot.bot) { + throw { code: 'BOT_OFFLINE', message: `${bot.name} is not connected` } as CommandError; + } + + const { x, y, z } = params; + if (x == null || y == null || z == null) { + throw { code: 'MISSING_PARAM', message: 'x, y, z coordinates are required' } as CommandError; + } + + const { goals } = require('mineflayer-pathfinder'); + bot.bot.pathfinder.setGoal(new goals.GoalNear(x, y, z, 2)); + + logger.info({ botName: bot.name, x, y, z }, 'Walking to coordinates via command'); + return { walkingTo: { x, y, z } }; + } + + private handleMoveToMarker(bot: BotInstance, params: Record): Record { + if (!bot.bot) { + throw { code: 'BOT_OFFLINE', message: `${bot.name} is not connected` } as CommandError; + } + if (!this.markerStore) { + throw { code: 'NO_MARKER_STORE', message: 'MarkerStore is not available' } as CommandError; + } + + const markerId = params.markerId; + if (!markerId) { + throw { code: 'MISSING_PARAM', message: 'markerId is required' } as CommandError; + } + + const marker = this.markerStore.getMarker(markerId); + if (!marker) { + throw { code: 'MARKER_NOT_FOUND', message: `Marker "${markerId}" not found` } as CommandError; + } + + const { x, y, z } = marker.position; + const { goals } = require('mineflayer-pathfinder'); + bot.bot.pathfinder.setGoal(new goals.GoalNear(x, y, z, 2)); + + logger.info({ botName: bot.name, markerId, markerName: marker.name, x, y, z }, 'Moving to marker via command'); + return { movingToMarker: marker.name, position: { x, y, z } }; + } + + private handleReturnToBase(bot: BotInstance): Record { + if (!bot.bot) { + throw { code: 'BOT_OFFLINE', message: `${bot.name} is not connected` } as CommandError; + } + if (!this.markerStore) { + throw { code: 'NO_MARKER_STORE', message: 'MarkerStore is not available — cannot locate base' } as CommandError; + } + + const pos = bot.bot.entity.position; + const baseMarker = this.markerStore.findNearestMarker({ x: pos.x, y: pos.y, z: pos.z }, 'base'); + if (!baseMarker) { + throw { code: 'NO_BASE', message: 'No base marker found' } as CommandError; + } + + const { x, y, z } = baseMarker.position; + const { goals } = require('mineflayer-pathfinder'); + bot.bot.pathfinder.setGoal(new goals.GoalNear(x, y, z, 2)); + + logger.info({ botName: bot.name, baseName: baseMarker.name, x, y, z }, 'Returning to base via command'); + return { returningToBase: baseMarker.name, position: { x, y, z } }; + } + + private handleRegroup(bot: BotInstance, params: Record): Record { + if (!bot.bot) { + throw { code: 'BOT_OFFLINE', message: `${bot.name} is not connected` } as CommandError; + } + + // If a rally marker is specified, use it; otherwise try to find the nearest base + const rallyMarkerId = params.markerId ?? params.rallyMarkerId; + let targetX: number; + let targetY: number; + let targetZ: number; + let targetName: string; + + if (rallyMarkerId && this.markerStore) { + const marker = this.markerStore.getMarker(rallyMarkerId); + if (!marker) { + throw { code: 'MARKER_NOT_FOUND', message: `Rally marker "${rallyMarkerId}" not found` } as CommandError; + } + targetX = marker.position.x; + targetY = marker.position.y; + targetZ = marker.position.z; + targetName = marker.name; + } else if (params.x != null && params.y != null && params.z != null) { + targetX = params.x; + targetY = params.y; + targetZ = params.z; + targetName = `coords(${params.x}, ${params.y}, ${params.z})`; + } else if (this.markerStore) { + const pos = bot.bot.entity.position; + const baseMarker = this.markerStore.findNearestMarker({ x: pos.x, y: pos.y, z: pos.z }, 'base'); + if (!baseMarker) { + throw { code: 'NO_RALLY_POINT', message: 'No rally point or base marker found for regroup' } as CommandError; + } + targetX = baseMarker.position.x; + targetY = baseMarker.position.y; + targetZ = baseMarker.position.z; + targetName = baseMarker.name; + } else { + throw { code: 'NO_RALLY_POINT', message: 'No rally point specified and MarkerStore is not available' } as CommandError; + } + + const { goals } = require('mineflayer-pathfinder'); + bot.bot.pathfinder.setGoal(new goals.GoalNear(targetX, targetY, targetZ, 3)); + + logger.info({ botName: bot.name, targetName, x: targetX, y: targetY, z: targetZ }, 'Regrouping via command'); + return { regroupingAt: targetName, position: { x: targetX, y: targetY, z: targetZ } }; + } + + private handleGuardZone(bot: BotInstance, params: Record): Record { + if (!bot.bot) { + throw { code: 'BOT_OFFLINE', message: `${bot.name} is not connected` } as CommandError; + } + if (!this.markerStore) { + throw { code: 'NO_MARKER_STORE', message: 'MarkerStore is not available' } as CommandError; + } + + const zoneId = params.zoneId; + if (!zoneId) { + throw { code: 'MISSING_PARAM', message: 'zoneId is required' } as CommandError; + } + + const zone = this.markerStore.getZone(zoneId); + if (!zone) { + throw { code: 'ZONE_NOT_FOUND', message: `Zone "${zoneId}" not found` } as CommandError; + } + + let centerX: number; + let centerZ: number; + + if (zone.shape === 'circle' && zone.circle) { + centerX = zone.circle.x; + centerZ = zone.circle.z; + } else if (zone.shape === 'rectangle' && zone.rectangle) { + const r = zone.rectangle; + centerX = (r.minX + r.maxX) / 2; + centerZ = (r.minZ + r.maxZ) / 2; + } else { + throw { code: 'INVALID_ZONE', message: `Zone "${zoneId}" has no valid shape data` } as CommandError; + } + + // Use the bot's current Y since zones are 2D + const y = bot.bot.entity.position.y; + + const { goals } = require('mineflayer-pathfinder'); + bot.bot.pathfinder.setGoal(new goals.GoalNear(centerX, y, centerZ, 2)); + + logger.info({ botName: bot.name, zoneId, zoneName: zone.name, centerX, centerZ }, 'Guarding zone via command'); + return { guardingZone: zone.name, center: { x: centerX, z: centerZ } }; + } + + private handlePatrolRoute(bot: BotInstance, params: Record): Record { + if (!bot.bot) { + throw { code: 'BOT_OFFLINE', message: `${bot.name} is not connected` } as CommandError; + } + if (!this.markerStore) { + throw { code: 'NO_MARKER_STORE', message: 'MarkerStore is not available' } as CommandError; + } + + const routeId = params.routeId; + if (!routeId) { + throw { code: 'MISSING_PARAM', message: 'routeId is required' } as CommandError; + } + + const route = this.markerStore.getRoute(routeId); + if (!route) { + throw { code: 'ROUTE_NOT_FOUND', message: `Route "${routeId}" not found` } as CommandError; + } + + if (route.waypointIds.length === 0) { + throw { code: 'EMPTY_ROUTE', message: `Route "${route.name}" has no waypoints` } as CommandError; + } + + // Resolve the first waypoint marker + const firstWaypointId = route.waypointIds[0]; + const firstMarker = this.markerStore.getMarker(firstWaypointId); + if (!firstMarker) { + throw { + code: 'WAYPOINT_NOT_FOUND', + message: `First waypoint marker "${firstWaypointId}" not found for route "${route.name}"`, + } as CommandError; + } + + const { x, y, z } = firstMarker.position; + const { goals } = require('mineflayer-pathfinder'); + bot.bot.pathfinder.setGoal(new goals.GoalNear(x, y, z, 2)); + + logger.info( + { botName: bot.name, routeId, routeName: route.name, firstWaypoint: firstMarker.name, waypointCount: route.waypointIds.length }, + 'Patrol started — moving to first waypoint', + ); + return { + patrolling: route.name, + currentWaypoint: firstMarker.name, + waypointCount: route.waypointIds.length, + note: 'Moving to first waypoint; full patrol loop not yet implemented', + }; + } + + private handleDepositInventory(bot: BotInstance): Record { + if (!bot.bot) { + throw { code: 'BOT_OFFLINE', message: `${bot.name} is not connected` } as CommandError; + } + + // Deposit is complex (needs async chest interaction). Return a clear message for now. + logger.info({ botName: bot.name }, 'Deposit inventory requested (not yet fully implemented)'); + return { + deposited: false, + note: 'deposit_inventory is not yet fully implemented — chest interaction requires async flow', + }; + } + + private handleEquipBest(bot: BotInstance): Record { + if (!bot.bot) { + throw { code: 'BOT_OFFLINE', message: `${bot.name} is not connected` } as CommandError; + } + + const inventory = bot.bot.inventory.items(); + // Prefer swords, then pickaxes, then axes as general-purpose best equipment + const weaponPriority = ['netherite_sword', 'diamond_sword', 'iron_sword', 'stone_sword', 'wooden_sword', + 'netherite_pickaxe', 'diamond_pickaxe', 'iron_pickaxe', 'stone_pickaxe', 'wooden_pickaxe', + 'netherite_axe', 'diamond_axe', 'iron_axe', 'stone_axe', 'wooden_axe']; + + let bestItem: any = null; + let bestRank = weaponPriority.length; + + for (const item of inventory) { + const rank = weaponPriority.indexOf(item.name); + if (rank !== -1 && rank < bestRank) { + bestRank = rank; + bestItem = item; + } + } + + if (!bestItem) { + throw { code: 'NO_EQUIPMENT', message: 'No sword, pickaxe, or axe found in inventory' } as CommandError; + } + + // bot.bot.equip returns a promise but we fire-and-forget since the caller awaits executeHandler + bot.bot.equip(bestItem, 'hand').catch((err: any) => { + logger.warn({ err, botName: bot.name }, 'Failed to equip item'); + }); + + logger.info({ botName: bot.name, item: bestItem.name }, 'Equipping best item via command'); + return { equipping: bestItem.name, slot: bestItem.slot }; + } + + private async handleUnstuck(bot: BotInstance): Promise> { + if (!bot.bot) { + throw { code: 'BOT_OFFLINE', message: `${bot.name} is not connected` } as CommandError; + } + + // Stop current movement + bot.bot.pathfinder.stop(); + + // Jump to help dislodge from stuck positions + bot.bot.setControlState('jump', true); + await new Promise((resolve) => setTimeout(resolve, 500)); + bot.bot.setControlState('jump', false); + + // Small random walk to get unstuck + const pos = bot.bot.entity.position; + const dx = (Math.random() - 0.5) * 8; + const dz = (Math.random() - 0.5) * 8; + + const { goals } = require('mineflayer-pathfinder'); + bot.bot.pathfinder.setGoal(new goals.GoalNear(pos.x + dx, pos.y, pos.z + dz, 1)); + + logger.info({ botName: bot.name, dx, dz }, 'Unstuck attempt via command (jump + random walk)'); + return { unstuck: true, jumped: true, movedTo: { x: pos.x + dx, z: pos.z + dz } }; + } + + // ── Status lifecycle ─────────────────────────────────────── + + /** Task 6: Structured logging for every lifecycle transition */ + private logLifecycle(command: CommandRecord, message: string): void { + const durationMs = command.startedAt && command.completedAt + ? new Date(command.completedAt).getTime() - new Date(command.startedAt).getTime() + : command.startedAt + ? Date.now() - new Date(command.startedAt).getTime() + : undefined; + + logger.info( + { + commandId: command.id, + botName: command.targets[0], + type: command.type, + status: command.status, + ...(durationMs !== undefined ? { durationMs } : {}), + }, + message, + ); + } + + private updateStatus(command: CommandRecord, status: CommandStatus): void { + command.status = status; + + if (status === 'started') { + command.startedAt = new Date().toISOString(); + } + if (status === 'succeeded' || status === 'failed' || status === 'cancelled') { + command.completedAt = new Date().toISOString(); + } + + const eventKey = `command:${status}` as string; + this.emitStatus(command, eventKey); + this.persist(); + + // Task 6: Structured log on every status transition + this.logLifecycle(command, `Command ${status}`); + } + + private emitStatus(command: CommandRecord, event: string): void { + this.io.emit(event, { + id: command.id, + type: command.type, + status: command.status, + targets: command.targets, + error: command.error, + result: command.result, + }); + } + + // ── Timeout checker ──────────────────────────────────────── + + private startTimeoutChecker(): void { + this.timeoutTimer = setInterval(() => { + this.checkTimeouts(); + }, TIMEOUT_CHECK_INTERVAL_MS); + } + + // ── Persistence ──────────────────────────────────────────── + + private persist(): void { + try { + // Keep only the most recent commands + const all = [...this.commands.values()] + .sort((a, b) => b.createdAt.localeCompare(a.createdAt)) + .slice(0, MAX_PERSISTED); + + const dir = path.dirname(DATA_PATH); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + + fs.writeFileSync(DATA_PATH, JSON.stringify({ commands: all }, null, 2)); + } catch (err) { + logger.error({ err }, 'Failed to persist commands'); + } + } + + private loadFromDisk(): void { + try { + if (!fs.existsSync(DATA_PATH)) return; + + const raw = fs.readFileSync(DATA_PATH, 'utf-8'); + const data = JSON.parse(raw) as { commands: CommandRecord[] }; + + for (const cmd of data.commands) { + this.commands.set(cmd.id, cmd); + } + + logger.info({ count: data.commands.length }, 'Loaded persisted commands'); + } catch (err) { + logger.error({ err }, 'Failed to load persisted commands'); + } + } +} diff --git a/src/control/CommandTypes.ts b/src/control/CommandTypes.ts new file mode 100644 index 0000000..b0b3423 --- /dev/null +++ b/src/control/CommandTypes.ts @@ -0,0 +1,89 @@ +// ═══════════════════════════════════════ +// COMMAND TYPES FOR CONTROL PLATFORM +// ═══════════════════════════════════════ + +export type CommandType = + | 'pause_voyager' + | 'resume_voyager' + | 'stop_movement' + | 'follow_player' + | 'walk_to_coords' + | 'move_to_marker' + | 'return_to_base' + | 'regroup' + | 'guard_zone' + | 'patrol_route' + | 'deposit_inventory' + | 'equip_best' + | 'unstuck'; + +export type CommandScope = 'single' | 'squad' | 'selection' | 'all'; + +export type CommandPriority = 'low' | 'normal' | 'high' | 'critical'; + +export type CommandSource = 'dashboard' | 'api' | 'hotkey' | 'automated' | 'commander'; + +export type CommandStatus = + | 'queued' + | 'started' + | 'succeeded' + | 'failed' + | 'cancelled'; + +export interface CommandError { + code: string; + message: string; + botName?: string; +} + +export interface CommandRecord { + id: string; + type: CommandType; + scope: CommandScope; + priority: CommandPriority; + source: CommandSource; + status: CommandStatus; + targets: string[]; // bot names + params: Record; // type-specific parameters + createdAt: string; // ISO timestamp + startedAt?: string; + completedAt?: string; + error?: CommandError; + result?: Record; + childCommandIds?: string[]; // for fan-out commands + parentCommandId?: string; +} + +export const COMMAND_EVENTS = { + QUEUED: 'command:queued', + STARTED: 'command:started', + SUCCEEDED: 'command:succeeded', + FAILED: 'command:failed', + CANCELLED: 'command:cancelled', +} as const; + +// Commander plan types +export interface CommanderPlanCommand { + type: CommandType; + targets: string[]; + payload: Record; +} + +export interface CommanderPlanMission { + type: string; + title: string; + description?: string; + assigneeIds: string[]; +} + +export interface CommanderPlan { + id: string; + input: string; + intent: string; + confidence: number; + warnings: string[]; + requiresConfirmation: boolean; + commands: CommanderPlanCommand[]; + missions: CommanderPlanMission[]; + createdAt: string; +} diff --git a/src/control/CommanderService.ts b/src/control/CommanderService.ts new file mode 100644 index 0000000..43d8470 --- /dev/null +++ b/src/control/CommanderService.ts @@ -0,0 +1,405 @@ +import { LLMClient } from '../ai/LLMClient'; +import { BotManager } from '../bot/BotManager'; +import { CommandCenter } from './CommandCenter'; +import { MissionManager } from './MissionManager'; +import { MarkerStore } from './MarkerStore'; +import { + CommanderPlan, + CommanderPlanCommand, + CommanderPlanMission, + CommandRecord, + CommandType, +} from './CommandTypes'; +import { MissionRecord, MissionType } from './MissionTypes'; +import { logger } from '../util/logger'; + +// All valid command types for validation +const VALID_COMMAND_TYPES: CommandType[] = [ + 'pause_voyager', + 'resume_voyager', + 'stop_movement', + 'follow_player', + 'walk_to_coords', + 'move_to_marker', + 'return_to_base', + 'regroup', + 'guard_zone', + 'patrol_route', + 'deposit_inventory', + 'equip_best', + 'unstuck', +]; + +const VALID_MISSION_TYPES: MissionType[] = [ + 'queue_task', + 'gather_items', + 'craft_items', + 'smelt_batch', + 'build_schematic', + 'supply_chain', + 'patrol_zone', + 'escort_player', + 'resupply_builder', +]; + +export interface CommanderServiceDeps { + llmClient: LLMClient | null; + botManager: BotManager; + commandCenter: CommandCenter; + missionManager: MissionManager; + markerStore: MarkerStore; +} + +export interface CommanderExecuteResult { + commands: CommandRecord[]; + missions: MissionRecord[]; +} + +export interface CommanderHistoryEntry { + planId: string; + input: string; + plan: CommanderPlan; + result?: CommanderExecuteResult; + status: 'parsed' | 'executed' | 'partial_failure'; + createdAt: string; + executedAt?: string; +} + +export class CommanderService { + private llmClient: LLMClient | null; + private botManager: BotManager; + private commandCenter: CommandCenter; + private missionManager: MissionManager; + private markerStore: MarkerStore; + private plans: Map = new Map(); + private history: CommanderHistoryEntry[] = []; + + constructor(deps: CommanderServiceDeps) { + this.llmClient = deps.llmClient; + this.botManager = deps.botManager; + this.commandCenter = deps.commandCenter; + this.missionManager = deps.missionManager; + this.markerStore = deps.markerStore; + } + + // ── Plan ID generation ────────────────────────────────── + + private generateId(): string { + return `plan_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`; + } + + getHistory(limit = 20): CommanderHistoryEntry[] { + return this.history.slice(0, limit); + } + + private upsertHistory(entry: CommanderHistoryEntry): void { + this.history = [entry, ...this.history.filter((item) => item.planId !== entry.planId)].slice(0, 100); + } + + // ── Parse NL input into a structured plan ─────────────── + + async parse(input: string): Promise { + const planId = this.generateId(); + const now = new Date().toISOString(); + + // If no LLM client, return a low-confidence stub + if (!this.llmClient) { + const plan: CommanderPlan = { + id: planId, + input, + intent: '', + confidence: 0, + warnings: ['No LLM configured — natural language parsing is unavailable'], + requiresConfirmation: true, + commands: [], + missions: [], + createdAt: now, + }; + this.plans.set(planId, plan); + this.upsertHistory({ planId, input, plan, status: 'parsed', createdAt: now }); + logger.warn({ planId }, 'Commander parse: no LLM client configured'); + return plan; + } + + // Build context for the LLM + const botNames = this.botManager.getAllBots().map((b) => b.name); + const botStates = this.botManager.getAllBots().map((b) => { + const status = b.getStatus(); + return `${b.name} (${b.personality}, ${status.mode}, pos: ${status.position ? `${status.position.x},${status.position.y},${status.position.z}` : 'unknown'})`; + }); + const markers = this.markerStore.getMarkers(); + const markerNames = markers.map((m) => `${m.name} (${m.kind} at ${m.position.x},${m.position.y},${m.position.z})`); + const zones = this.markerStore.getZones(); + const zoneNames = zones.map((z) => `${z.name} (${z.mode}, ${z.shape})`); + + const systemPrompt = `You are a Minecraft bot command parser. Given the user's natural language request and the current context, output a JSON plan. + +Available bots: ${botNames.length > 0 ? botNames.join(', ') : '(none spawned)'} +Bot states: ${botStates.length > 0 ? botStates.join('; ') : '(none)'} +Available command types: ${VALID_COMMAND_TYPES.join(', ')} +Available mission types: ${VALID_MISSION_TYPES.join(', ')} +Available markers: ${markerNames.length > 0 ? markerNames.join('; ') : '(none)'} +Available zones: ${zoneNames.length > 0 ? zoneNames.join('; ') : '(none)'} + +Output ONLY valid JSON (no markdown, no explanation) in this exact format: +{ + "intent": "description of what the user wants", + "confidence": 0.0-1.0, + "warnings": ["any issues or ambiguities"], + "commands": [{ "type": "command_type", "targets": ["BotName"], "payload": {} }], + "missions": [{ "type": "mission_type", "title": "...", "description": "...", "assigneeIds": ["BotName"] }] +} + +Rules: +- Only use bot names that exist in the available bots list +- Only use command types from the available command types list +- Only use mission types from the available mission types list +- If the request is ambiguous, set confidence lower and add warnings +- If no bots are available, set confidence to 0 and add a warning +- For movement commands (walk_to_coords), include x, y, z in the payload +- For follow_player, include playerName in the payload +- Use "queue_task" mission type for general tasks that should be queued to a bot's voyager loop +- Commands are immediate actions; missions are longer-running objectives`; + + try { + const response = await this.llmClient.generate(systemPrompt, input, 1024); + const parsed = this.extractJson(response.text); + + if (!parsed) { + const plan: CommanderPlan = { + id: planId, + input, + intent: 'Failed to parse LLM response', + confidence: 0, + warnings: ['LLM response was not valid JSON'], + requiresConfirmation: true, + commands: [], + missions: [], + createdAt: now, + }; + this.plans.set(planId, plan); + this.upsertHistory({ planId, input, plan, status: 'parsed', createdAt: now }); + logger.warn({ planId, rawResponse: response.text.slice(0, 500) }, 'Commander parse: invalid JSON from LLM'); + return plan; + } + + // Validate and build the plan + const { commands, missions, warnings } = this.validateParsed(parsed, botNames); + + const confidence = Math.max(0, Math.min(1, Number(parsed.confidence) || 0)); + const allWarnings = [ + ...(Array.isArray(parsed.warnings) ? parsed.warnings : []), + ...warnings, + ]; + + const plan: CommanderPlan = { + id: planId, + input, + intent: String(parsed.intent || ''), + confidence, + warnings: allWarnings, + requiresConfirmation: confidence < 0.8 || allWarnings.length > 0, + commands, + missions, + createdAt: now, + }; + + this.plans.set(planId, plan); + this.upsertHistory({ planId, input, plan, status: 'parsed', createdAt: now }); + logger.info( + { planId, intent: plan.intent, confidence, commandCount: commands.length, missionCount: missions.length }, + 'Commander plan parsed', + ); + return plan; + } catch (err: any) { + const plan: CommanderPlan = { + id: planId, + input, + intent: 'LLM call failed', + confidence: 0, + warnings: [`LLM error: ${err?.message ?? String(err)}`], + requiresConfirmation: true, + commands: [], + missions: [], + createdAt: now, + }; + this.plans.set(planId, plan); + this.upsertHistory({ planId, input, plan, status: 'parsed', createdAt: now }); + logger.error({ err, planId }, 'Commander parse failed'); + return plan; + } + } + + // ── Execute a previously parsed plan ──────────────────── + + async execute(planId: string): Promise { + const plan = this.plans.get(planId); + if (!plan) return null; + + const commands: CommandRecord[] = []; + const missions: MissionRecord[] = []; + + // Execute commands + for (const cmd of plan.commands) { + try { + const command = this.commandCenter.createCommand({ + type: cmd.type, + targets: cmd.targets, + params: cmd.payload, + source: 'commander', + priority: 'normal', + }); + await this.commandCenter.dispatchCommand(command); + commands.push(command); + } catch (err: any) { + logger.error({ err, planId, commandType: cmd.type }, 'Command dispatch failed'); + } + } + + // Execute missions + for (const msn of plan.missions) { + try { + const missionType = msn.type as MissionType; + const mission = this.missionManager.createMission({ + type: missionType, + title: msn.title, + description: msn.description, + assigneeType: 'bot', + assigneeIds: msn.assigneeIds, + source: 'commander', + priority: 'normal', + }); + missions.push(mission); + } catch (err: any) { + logger.error({ err, planId, missionType: msn.type }, 'Mission creation failed'); + } + } + + logger.info( + { planId, commandsCreated: commands.length, missionsCreated: missions.length }, + 'Commander plan executed', + ); + + const result = { commands, missions }; + const status = commands.some((command) => command.status === 'failed') ? 'partial_failure' : 'executed'; + this.upsertHistory({ + planId, + input: plan.input, + plan, + result, + status, + createdAt: plan.createdAt, + executedAt: new Date().toISOString(), + }); + return result; + } + + // ── Get a stored plan ─────────────────────────────────── + + getPlan(planId: string): CommanderPlan | undefined { + return this.plans.get(planId); + } + + // ── Helpers ───────────────────────────────────────────── + + private extractJson(text: string): any | null { + // Try direct parse first + try { + return JSON.parse(text); + } catch { + // Try to find JSON block in the response + const jsonMatch = text.match(/\{[\s\S]*\}/); + if (jsonMatch) { + try { + return JSON.parse(jsonMatch[0]); + } catch { + return null; + } + } + return null; + } + } + + private validateParsed( + parsed: any, + botNames: string[], + ): { commands: CommanderPlanCommand[]; missions: CommanderPlanMission[]; warnings: string[] } { + const commands: CommanderPlanCommand[] = []; + const missions: CommanderPlanMission[] = []; + const warnings: string[] = []; + const botNamesLower = botNames.map((n) => n.toLowerCase()); + + // Validate commands + if (Array.isArray(parsed.commands)) { + for (const raw of parsed.commands) { + if (!raw || typeof raw !== 'object') continue; + + const type = raw.type as CommandType; + if (!VALID_COMMAND_TYPES.includes(type)) { + warnings.push(`Unknown command type: ${raw.type}`); + continue; + } + + const targets = Array.isArray(raw.targets) ? raw.targets : []; + const validTargets: string[] = []; + for (const t of targets) { + const matchIdx = botNamesLower.indexOf(String(t).toLowerCase()); + if (matchIdx >= 0) { + validTargets.push(botNames[matchIdx]); // use canonical name + } else { + warnings.push(`Bot "${t}" not found, skipping from command target`); + } + } + + if (validTargets.length === 0) { + warnings.push(`Command "${type}" has no valid targets, skipping`); + continue; + } + + commands.push({ + type, + targets: validTargets, + payload: typeof raw.payload === 'object' && raw.payload ? raw.payload : {}, + }); + } + } + + // Validate missions + if (Array.isArray(parsed.missions)) { + for (const raw of parsed.missions) { + if (!raw || typeof raw !== 'object') continue; + + const type = raw.type; + if (!VALID_MISSION_TYPES.includes(type as MissionType)) { + warnings.push(`Unknown mission type: ${raw.type}`); + continue; + } + + const assigneeIds = Array.isArray(raw.assigneeIds) ? raw.assigneeIds : []; + const validAssignees: string[] = []; + for (const a of assigneeIds) { + const matchIdx = botNamesLower.indexOf(String(a).toLowerCase()); + if (matchIdx >= 0) { + validAssignees.push(botNames[matchIdx]); + } else { + warnings.push(`Bot "${a}" not found, skipping from mission assignees`); + } + } + + if (validAssignees.length === 0) { + warnings.push(`Mission "${raw.title || type}" has no valid assignees, skipping`); + continue; + } + + missions.push({ + type, + title: String(raw.title || `${type} mission`), + description: raw.description ? String(raw.description) : undefined, + assigneeIds: validAssignees, + }); + } + } + + return { commands, missions, warnings }; + } +} diff --git a/src/control/FleetTypes.ts b/src/control/FleetTypes.ts new file mode 100644 index 0000000..37aac09 --- /dev/null +++ b/src/control/FleetTypes.ts @@ -0,0 +1,67 @@ +export interface SquadRecord { + id: string; + name: string; + botNames: string[]; + defaultRole?: string; + homeMarkerId?: string; + activeMissionId?: string; + createdAt: number; + updatedAt: number; +} + +export type RoleType = 'guard' | 'builder' | 'hauler' | 'farmer' | 'miner' | 'scout' | 'merchant' | 'free-agent'; + +export type AutonomyLevel = 'manual' | 'assisted' | 'autonomous'; + +export type InterruptPolicy = 'always' | 'confirm-if-busy' | 'never-while-critical'; + +export type RoleApprovalStatus = 'pending' | 'approved' | 'rejected' | 'expired'; + +export interface RoleApprovalRequestRecord { + id: string; + assignmentId: string; + assignmentUpdatedAt: number; + botName: string; + role: RoleType; + status: RoleApprovalStatus; + createdAt: number; + expiresAt: number; + decidedAt?: number; + decidedBy?: string; + decisionNote?: string; + missionDraft: { + type: string; + title: string; + description: string; + assigneeType: 'bot'; + assigneeIds: string[]; + priority: 'normal'; + source: 'role'; + }; +} + +export interface RoleAssignmentRecord { + id: string; + botName: string; + role: RoleType; + autonomyLevel: AutonomyLevel; + homeMarkerId?: string; + allowedZoneIds: string[]; + preferredMissionTypes: string[]; + loadoutPolicy?: Record; + interruptPolicy?: InterruptPolicy; + updatedAt: number; +} + +// Socket event names +export const FLEET_EVENTS = { + SQUAD_UPDATED: 'squad:updated', + ROLE_UPDATED: 'role:updated', +} as const; + +export const WORLD_EVENTS = { + MARKER_CREATED: 'marker:created', + MARKER_UPDATED: 'marker:updated', + ZONE_UPDATED: 'zone:updated', + ROUTE_UPDATED: 'route:updated', +} as const; diff --git a/src/control/MarkerStore.ts b/src/control/MarkerStore.ts new file mode 100644 index 0000000..23a0ba5 --- /dev/null +++ b/src/control/MarkerStore.ts @@ -0,0 +1,303 @@ +import { Server as SocketIOServer } from 'socket.io'; +import { MarkerRecord, ZoneRecord, RouteRecord } from './WorldTypes'; +import { WORLD_EVENTS } from './FleetTypes'; +import { logger } from '../util/logger'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as crypto from 'crypto'; + +const DATA_DIR = path.join(process.cwd(), 'data'); +const DEBOUNCE_MS = 1_000; + +function ensureDataDir(): void { + try { + if (!fs.existsSync(DATA_DIR)) { + fs.mkdirSync(DATA_DIR, { recursive: true }); + } + } catch (err) { + logger.error({ err }, 'Failed to create data directory'); + } +} + +function loadJson(filePath: string, fallback: T): T { + try { + if (fs.existsSync(filePath)) { + return JSON.parse(fs.readFileSync(filePath, 'utf-8')); + } + } catch (err) { + logger.warn({ err, filePath }, 'Failed to load JSON file, using fallback'); + } + return fallback; +} + +function saveJson(filePath: string, data: unknown): void { + try { + ensureDataDir(); + fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8'); + } catch (err) { + logger.error({ err, filePath }, 'Failed to save JSON file'); + } +} + +function genId(prefix: string): string { + return `${prefix}_${crypto.randomBytes(6).toString('hex')}`; +} + +export class MarkerStore { + private markers: Map = new Map(); + private zones: Map = new Map(); + private routes: Map = new Map(); + + private markersPath = path.join(DATA_DIR, 'markers.json'); + private zonesPath = path.join(DATA_DIR, 'zones.json'); + private routesPath = path.join(DATA_DIR, 'routes.json'); + + private markerSaveTimer: ReturnType | null = null; + private zoneSaveTimer: ReturnType | null = null; + private routeSaveTimer: ReturnType | null = null; + + constructor(private io: SocketIOServer) { + this.load(); + logger.info( + { markers: this.markers.size, zones: this.zones.size, routes: this.routes.size }, + 'MarkerStore loaded', + ); + } + + // ── Persistence ────────────────────────────────────────── + + private load(): void { + const markers = loadJson(this.markersPath, []); + for (const m of markers) this.markers.set(m.id, m); + + const zones = loadJson(this.zonesPath, []); + for (const z of zones) this.zones.set(z.id, z); + + const routes = loadJson(this.routesPath, []); + for (const r of routes) this.routes.set(r.id, r); + } + + private saveMarkers(): void { + if (this.markerSaveTimer) return; + this.markerSaveTimer = setTimeout(() => { + this.markerSaveTimer = null; + saveJson(this.markersPath, Array.from(this.markers.values())); + }, DEBOUNCE_MS); + } + + private saveMarkersImmediate(): void { + if (this.markerSaveTimer) { clearTimeout(this.markerSaveTimer); this.markerSaveTimer = null; } + saveJson(this.markersPath, Array.from(this.markers.values())); + } + + private saveZones(): void { + if (this.zoneSaveTimer) return; + this.zoneSaveTimer = setTimeout(() => { + this.zoneSaveTimer = null; + saveJson(this.zonesPath, Array.from(this.zones.values())); + }, DEBOUNCE_MS); + } + + private saveZonesImmediate(): void { + if (this.zoneSaveTimer) { clearTimeout(this.zoneSaveTimer); this.zoneSaveTimer = null; } + saveJson(this.zonesPath, Array.from(this.zones.values())); + } + + private saveRoutes(): void { + if (this.routeSaveTimer) return; + this.routeSaveTimer = setTimeout(() => { + this.routeSaveTimer = null; + saveJson(this.routesPath, Array.from(this.routes.values())); + }, DEBOUNCE_MS); + } + + private saveRoutesImmediate(): void { + if (this.routeSaveTimer) { clearTimeout(this.routeSaveTimer); this.routeSaveTimer = null; } + saveJson(this.routesPath, Array.from(this.routes.values())); + } + + /** Flush all pending saves and clear timers */ + shutdown(): void { + this.saveMarkersImmediate(); + this.saveZonesImmediate(); + this.saveRoutesImmediate(); + } + + // ── Markers ────────────────────────────────────────────── + + createMarker(data: { + name: string; + kind: MarkerRecord['kind']; + position: MarkerRecord['position']; + tags?: string[]; + notes?: string; + }): MarkerRecord { + const now = Date.now(); + const marker: MarkerRecord = { + id: genId('mkr'), + name: data.name, + kind: data.kind, + position: data.position, + tags: data.tags ?? [], + notes: data.notes, + createdAt: now, + updatedAt: now, + }; + this.markers.set(marker.id, marker); + this.saveMarkers(); + this.io.emit(WORLD_EVENTS.MARKER_CREATED, marker); + logger.info({ markerId: marker.id, name: marker.name, kind: marker.kind }, 'Marker created'); + return marker; + } + + getMarkers(): MarkerRecord[] { + return Array.from(this.markers.values()); + } + + getMarker(id: string): MarkerRecord | undefined { + return this.markers.get(id); + } + + updateMarker(id: string, data: Partial>): MarkerRecord | undefined { + const existing = this.markers.get(id); + if (!existing) return undefined; + const updated: MarkerRecord = { ...existing, ...data, id: existing.id, createdAt: existing.createdAt, updatedAt: Date.now() }; + this.markers.set(id, updated); + this.saveMarkers(); + this.io.emit(WORLD_EVENTS.MARKER_UPDATED, updated); + logger.info({ markerId: id, name: updated.name, kind: updated.kind }, 'Marker updated'); + return updated; + } + + deleteMarker(id: string): boolean { + const marker = this.markers.get(id); + const existed = this.markers.delete(id); + if (existed) { + this.saveMarkers(); + this.io.emit(WORLD_EVENTS.MARKER_UPDATED, { id, deleted: true }); + logger.info({ markerId: id, name: marker?.name, kind: marker?.kind }, 'Marker deleted'); + } + return existed; + } + + // ── Zones ──────────────────────────────────────────────── + + createZone(data: Omit): ZoneRecord { + const zone: ZoneRecord = { id: genId('zne'), ...data }; + this.zones.set(zone.id, zone); + this.saveZones(); + this.io.emit(WORLD_EVENTS.ZONE_UPDATED, zone); + logger.info({ zoneId: zone.id, name: zone.name }, 'Zone created'); + return zone; + } + + getZones(): ZoneRecord[] { + return Array.from(this.zones.values()); + } + + getZone(id: string): ZoneRecord | undefined { + return this.zones.get(id); + } + + updateZone(id: string, data: Partial>): ZoneRecord | undefined { + const existing = this.zones.get(id); + if (!existing) return undefined; + const updated: ZoneRecord = { ...existing, ...data, id: existing.id }; + this.zones.set(id, updated); + this.saveZones(); + this.io.emit(WORLD_EVENTS.ZONE_UPDATED, updated); + logger.info({ zoneId: id }, 'Zone updated'); + return updated; + } + + deleteZone(id: string): boolean { + const existed = this.zones.delete(id); + if (existed) { + this.saveZones(); + this.io.emit(WORLD_EVENTS.ZONE_UPDATED, { id, deleted: true }); + logger.info({ zoneId: id }, 'Zone deleted'); + } + return existed; + } + + // ── Routes ─────────────────────────────────────────────── + + createRoute(data: Omit): RouteRecord { + const route: RouteRecord = { id: genId('rte'), ...data }; + this.routes.set(route.id, route); + this.saveRoutes(); + this.io.emit(WORLD_EVENTS.ROUTE_UPDATED, route); + logger.info({ routeId: route.id, name: route.name }, 'Route created'); + return route; + } + + getRoutes(): RouteRecord[] { + return Array.from(this.routes.values()); + } + + getRoute(id: string): RouteRecord | undefined { + return this.routes.get(id); + } + + updateRoute(id: string, data: Partial>): RouteRecord | undefined { + const existing = this.routes.get(id); + if (!existing) return undefined; + const updated: RouteRecord = { ...existing, ...data, id: existing.id }; + this.routes.set(id, updated); + this.saveRoutes(); + this.io.emit(WORLD_EVENTS.ROUTE_UPDATED, updated); + logger.info({ routeId: id }, 'Route updated'); + return updated; + } + + deleteRoute(id: string): boolean { + const existed = this.routes.delete(id); + if (existed) { + this.saveRoutes(); + this.io.emit(WORLD_EVENTS.ROUTE_UPDATED, { id, deleted: true }); + logger.info({ routeId: id }, 'Route deleted'); + } + return existed; + } + + // ── Spatial Helpers ────────────────────────────────────── + + findNearestMarker( + position: { x: number; y: number; z: number }, + kind?: MarkerRecord['kind'], + ): MarkerRecord | undefined { + let nearest: MarkerRecord | undefined; + let bestDist = Infinity; + + for (const marker of this.markers.values()) { + if (kind && marker.kind !== kind) continue; + const dx = marker.position.x - position.x; + const dy = marker.position.y - position.y; + const dz = marker.position.z - position.z; + const dist = dx * dx + dy * dy + dz * dz; + if (dist < bestDist) { + bestDist = dist; + nearest = marker; + } + } + return nearest; + } + + isInsideZone(x: number, z: number, zoneId: string): boolean { + const zone = this.zones.get(zoneId); + if (!zone) return false; + + if (zone.shape === 'circle' && zone.circle) { + const dx = x - zone.circle.x; + const dz = z - zone.circle.z; + return dx * dx + dz * dz <= zone.circle.radius * zone.circle.radius; + } + + if (zone.shape === 'rectangle' && zone.rectangle) { + const r = zone.rectangle; + return x >= r.minX && x <= r.maxX && z >= r.minZ && z <= r.maxZ; + } + + return false; + } +} diff --git a/src/control/MissionManager.ts b/src/control/MissionManager.ts new file mode 100644 index 0000000..cece073 --- /dev/null +++ b/src/control/MissionManager.ts @@ -0,0 +1,739 @@ +import fs from 'fs'; +import path from 'path'; +import { Server as SocketIOServer } from 'socket.io'; +import { BotManager } from '../bot/BotManager'; +import { BuildCoordinator } from '../build/BuildCoordinator'; +import { ChainCoordinator } from '../supplychain/ChainCoordinator'; +import { CommandCenter } from './CommandCenter'; +import { SquadManager } from './SquadManager'; +import { logger } from '../util/logger'; +import { + MissionRecord, + MissionStatus, + MissionPriority, + MissionSource, + MissionType, + MISSION_EVENTS, +} from './MissionTypes'; + +const DATA_DIR = './data'; +const MISSIONS_FILE = path.join(DATA_DIR, 'missions.json'); +const STALE_THRESHOLD_MS = 30 * 60 * 1000; // 30 minutes + +export interface MissionMetrics { + totalCreated: number; + totalCompleted: number; + totalFailed: number; + totalCancelled: number; + byType: Record; + byBot: Record; +} + +export interface CreateMissionParams { + type: MissionType; + title: string; + description?: string; + assigneeType: 'bot' | 'squad'; + assigneeIds: string[]; + priority?: MissionPriority; + source?: MissionSource; + steps?: MissionRecord['steps']; + linkedCommandIds?: string[]; +} + +export interface MissionFilters { + bot?: string; + squad?: string; + status?: MissionStatus; + limit?: number; +} + +export class MissionManager { + private missions: Map = new Map(); + private botMissionQueues: Map = new Map(); // botName → ordered mission IDs + private botManager: BotManager; + private io: SocketIOServer; + private buildCoordinator?: BuildCoordinator; + private chainCoordinator?: ChainCoordinator; + private commandCenter?: CommandCenter; + private squadManager?: SquadManager; + /** Tracks which task description was queued for a running mission (missionId → description) */ + private missionTaskDescriptions: Map = new Map(); + + constructor(botManager: BotManager, io: SocketIOServer) { + this.botManager = botManager; + this.io = io; + this.load(); + } + + // ── Coordinator adapters ──────────────────────────── + + setBuildCoordinator(bc: BuildCoordinator): void { + this.buildCoordinator = bc; + } + + setChainCoordinator(cc: ChainCoordinator): void { + this.chainCoordinator = cc; + } + + setCommandCenter(cc: CommandCenter): void { + this.commandCenter = cc; + } + + setSquadManager(sm: SquadManager): void { + this.squadManager = sm; + } + + // ── ID generation ────────────────────────────────── + + private generateId(): string { + return `msn_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`; + } + + /** + * Resolve assignee IDs to bot names. + * For assigneeType 'bot', IDs are already bot names. + * For assigneeType 'squad', each ID is a squad ID that must be resolved via SquadManager. + */ + private resolveAssigneeBotNames(mission: MissionRecord): string[] { + if (mission.assigneeType === 'bot') { + return mission.assigneeIds; + } + + // assigneeType === 'squad' + if (!this.squadManager) { + logger.warn( + { missionId: mission.id }, + 'Cannot resolve squad assignees: SquadManager not set' + ); + return []; + } + + const botNames: string[] = []; + for (const squadId of mission.assigneeIds) { + const squad = this.squadManager.getSquad(squadId); + if (squad) { + botNames.push(...squad.botNames); + } else { + logger.warn( + { missionId: mission.id, squadId }, + 'Squad not found when resolving mission assignees' + ); + } + } + return [...new Set(botNames)]; // deduplicate + } + + private missionTargetsBot(mission: MissionRecord, botName: string): boolean { + const lower = botName.toLowerCase(); + return this.resolveAssigneeBotNames(mission).some((name) => name.toLowerCase() === lower); + } + + // ── CRUD ─────────────────────────────────────────── + + createMission(params: CreateMissionParams): MissionRecord { + const now = Date.now(); + const mission: MissionRecord = { + id: this.generateId(), + type: params.type, + title: params.title, + description: params.description, + assigneeType: params.assigneeType, + assigneeIds: params.assigneeIds, + status: 'queued', + priority: params.priority ?? 'normal', + steps: params.steps ?? [], + createdAt: now, + updatedAt: now, + source: params.source ?? 'dashboard', + linkedCommandIds: params.linkedCommandIds, + }; + + this.missions.set(mission.id, mission); + + // Add to each assignee bot's queue (resolve squad IDs to bot names if needed) + const botNames = this.resolveAssigneeBotNames(mission); + for (const botName of botNames) { + this.addToBotQueue(botName, mission.id); + } + + this.save(); + this.io.emit(MISSION_EVENTS.CREATED, mission); + logger.info({ missionId: mission.id, title: mission.title, assignees: mission.assigneeIds }, 'Mission created'); + return mission; + } + + getMissions(filters?: MissionFilters): MissionRecord[] { + let results = Array.from(this.missions.values()); + + if (filters?.bot) { + results = results.filter((m) => this.missionTargetsBot(m, filters.bot!)); + } + if (filters?.squad) { + results = results.filter( + (m) => m.assigneeType === 'squad' && m.assigneeIds.includes(filters.squad!) + ); + } + if (filters?.status) { + results = results.filter((m) => m.status === filters.status); + } + + // Sort by priority (urgent first) then creation time + const priorityOrder: Record = { urgent: 0, high: 1, normal: 2, low: 3 }; + results.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority] || a.createdAt - b.createdAt); + + if (filters?.limit && filters.limit > 0) { + results = results.slice(0, filters.limit); + } + + return results; + } + + getMission(id: string): MissionRecord | undefined { + return this.missions.get(id); + } + + getMetrics(): MissionMetrics { + const all = [...this.missions.values()]; + + let totalCompleted = 0; + let totalFailed = 0; + let totalCancelled = 0; + + const byType: Record = {}; + const byBot: Record = {}; + + for (const m of all) { + if (m.status === 'completed') totalCompleted++; + else if (m.status === 'failed') totalFailed++; + else if (m.status === 'cancelled') totalCancelled++; + + // By type + if (!byType[m.type]) { + byType[m.type] = { count: 0, completed: 0, failed: 0 }; + } + const t = byType[m.type]; + t.count++; + if (m.status === 'completed') t.completed++; + else if (m.status === 'failed') t.failed++; + + // By bot + for (const botName of (m.assigneeIds || [])) { + if (!byBot[botName]) { + byBot[botName] = { count: 0, completed: 0, failed: 0 }; + } + const b = byBot[botName]; + b.count++; + if (m.status === 'completed') b.completed++; + else if (m.status === 'failed') b.failed++; + } + } + + return { + totalCreated: all.length, + totalCompleted, + totalFailed, + totalCancelled, + byType, + byBot, + }; + } + + /** Count missions currently in 'running' status */ + getRunningCount(): number { + let count = 0; + for (const m of this.missions.values()) { + if (m.status === 'running') count++; + } + return count; + } + + /** Count missions flagged as stale */ + getStaleCount(): number { + const now = Date.now(); + let count = 0; + for (const m of this.missions.values()) { + if (m.status === 'running' && m.startedAt && now - m.startedAt > STALE_THRESHOLD_MS) { + count++; + } + } + return count; + } + + // ── Status transitions ───────────────────────────── + + updateMissionStatus( + id: string, + newStatus: MissionStatus, + metadata?: { reason?: string; error?: string } + ): MissionRecord | undefined { + const mission = this.missions.get(id); + if (!mission) return undefined; + + const oldStatus = mission.status; + mission.status = newStatus; + mission.updatedAt = Date.now(); + + if (newStatus === 'running' && !mission.startedAt) { + mission.startedAt = Date.now(); + } + if (newStatus === 'completed' || newStatus === 'failed' || newStatus === 'cancelled') { + mission.completedAt = Date.now(); + // Clean up task tracking when mission reaches terminal state + this.missionTaskDescriptions.delete(id); + } + if (metadata?.reason) { + mission.blockedReason = metadata.reason; + } + + this.save(); + + // Emit specific event based on target status + const eventMap: Record = { + completed: MISSION_EVENTS.COMPLETED, + failed: MISSION_EVENTS.FAILED, + cancelled: MISSION_EVENTS.CANCELLED, + }; + const specificEvent = eventMap[newStatus]; + if (specificEvent) { + this.io.emit(specificEvent, mission); + } + this.io.emit(MISSION_EVENTS.UPDATED, mission); + + logger.info( + { missionId: id, oldStatus, newStatus, reason: metadata?.reason }, + 'Mission status updated' + ); + return mission; + } + + pauseMission(id: string): MissionRecord | undefined { + const mission = this.missions.get(id); + if (!mission || (mission.status !== 'running' && mission.status !== 'queued')) { + return undefined; + } + return this.updateMissionStatus(id, 'paused', { reason: 'User paused' }); + } + + resumeMission(id: string): MissionRecord | undefined { + const mission = this.missions.get(id); + if (!mission || mission.status !== 'paused') { + return undefined; + } + return this.updateMissionStatus(id, 'queued'); + } + + cancelMission(id: string): MissionRecord | undefined { + const mission = this.missions.get(id); + if (!mission || mission.status === 'completed' || mission.status === 'cancelled') { + return undefined; + } + + // Remove from bot queues (resolve squad IDs to bot names) + const botNames = this.resolveAssigneeBotNames(mission); + for (const botName of botNames) { + this.removeFromBotQueue(botName, id); + } + + return this.updateMissionStatus(id, 'cancelled', { reason: 'User cancelled' }); + } + + retryMission(id: string): MissionRecord | undefined { + const mission = this.missions.get(id); + if (!mission || (mission.status !== 'failed' && mission.status !== 'cancelled')) { + return undefined; + } + + // Reset steps + for (const step of mission.steps) { + if (step.status === 'failed' || step.status === 'cancelled') { + step.status = 'pending'; + step.error = undefined; + } + } + + // Re-add to bot queues (resolve squad IDs to bot names if needed) + const botNames = this.resolveAssigneeBotNames(mission); + for (const botName of botNames) { + this.addToBotQueue(botName, id); + } + + mission.completedAt = undefined; + mission.blockedReason = undefined; + return this.updateMissionStatus(id, 'queued'); + } + + // ── Dependency check ─────────────────────────────── + + /** + * Check whether a mission's prerequisites are met. + * If the mission has linkedCommandIds, all linked commands must have succeeded. + */ + canStart(mission: MissionRecord): boolean { + if (!mission.linkedCommandIds || mission.linkedCommandIds.length === 0) { + return true; + } + + if (!this.commandCenter) { + // Without a CommandCenter we cannot verify dependencies; assume not ready + logger.warn( + { missionId: mission.id }, + 'Cannot check mission dependencies: CommandCenter not set' + ); + return false; + } + + for (const cmdId of mission.linkedCommandIds) { + const cmd = this.commandCenter.getCommand(cmdId); + if (!cmd || cmd.status !== 'succeeded') { + return false; + } + } + + return true; + } + + // ── Mission execution ──────────────────────────────── + + async startMission(id: string): Promise { + const mission = this.missions.get(id); + if (!mission) return undefined; + if (mission.status !== 'queued' && mission.status !== 'paused') { + logger.warn({ missionId: id, status: mission.status }, 'Cannot start mission: invalid status'); + return undefined; + } + + // Check dependencies before starting + if (!this.canStart(mission)) { + const pendingCmds = (mission.linkedCommandIds ?? []).filter((cmdId) => { + const cmd = this.commandCenter?.getCommand(cmdId); + return !cmd || cmd.status !== 'succeeded'; + }); + logger.info( + { missionId: id, pendingCommands: pendingCmds }, + 'Mission blocked: linked commands not yet succeeded' + ); + // Update blockedReason but keep status as queued + mission.blockedReason = `Waiting on linked commands: ${pendingCmds.join(', ')}`; + mission.updatedAt = Date.now(); + this.save(); + this.io.emit(MISSION_EVENTS.UPDATED, mission); + return mission; + } + + // Clear any previous blocked reason once dependencies are met + if (mission.blockedReason) { + mission.blockedReason = undefined; + } + + switch (mission.type) { + case 'queue_task': + return this.executeQueueTaskMission(mission); + case 'build_schematic': + return this.executeBuildMission(mission); + case 'supply_chain': + return this.executeChainMission(mission); + default: + // For other mission types, just transition to running + return this.updateMissionStatus(id, 'running'); + } + } + + // ── VoyagerLoop bridge for queue_task missions ────── + + private executeQueueTaskMission(mission: MissionRecord): MissionRecord | undefined { + const taskDescription = mission.description || mission.title; + const botNames = this.resolveAssigneeBotNames(mission); + + if (botNames.length === 0) { + logger.warn({ missionId: mission.id }, 'No bots resolved for mission, failing'); + return this.updateMissionStatus(mission.id, 'failed', { reason: 'No bots available' }); + } + + for (const botName of botNames) { + const bot = this.botManager.getBot(botName); + if (!bot) { + logger.warn( + { missionId: mission.id, botName }, + 'Cannot queue task: bot not found' + ); + continue; + } + + const voyager = bot.getVoyagerLoop(); + if (!voyager) { + logger.warn( + { missionId: mission.id, botName }, + 'Cannot queue task: bot has no VoyagerLoop' + ); + continue; + } + + voyager.queuePlayerTask(taskDescription, 'mission'); + logger.info( + { missionId: mission.id, botName, task: taskDescription }, + 'Queued task to VoyagerLoop via mission' + ); + } + + // Track the task description so checkMissionProgress can match it + this.missionTaskDescriptions.set(mission.id, taskDescription); + + return this.updateMissionStatus(mission.id, 'running'); + } + + // ── Mission progress checking ────────────────────── + + /** + * Check progress of all running missions. + * For 'queue_task' missions, inspects the VoyagerLoop to detect task completion/failure. + * Also flags stale missions that have been running for over 30 minutes. + * + * Call this periodically (e.g. every 30s–60s). + */ + checkMissionProgress(): void { + const now = Date.now(); + + for (const mission of this.missions.values()) { + if (mission.status !== 'running') continue; + + // ── Stale mission detection ── + if (mission.startedAt && now - mission.startedAt > STALE_THRESHOLD_MS) { + if (mission.blockedReason !== 'Stale - running for over 30 minutes') { + mission.blockedReason = 'Stale - running for over 30 minutes'; + mission.updatedAt = now; + this.save(); + this.io.emit(MISSION_EVENTS.UPDATED, mission); + logger.warn( + { missionId: mission.id, botName: mission.assigneeIds[0], reason: 'Running for over 30 minutes', runningSinceMs: now - mission.startedAt }, + 'Mission stale' + ); + } + } + + // ── queue_task completion tracking ── + if (mission.type === 'queue_task') { + this.checkQueueTaskProgress(mission); + } + } + } + + private checkQueueTaskProgress(mission: MissionRecord): void { + const trackedDescription = this.missionTaskDescriptions.get(mission.id); + if (!trackedDescription) return; + + const botNames = this.resolveAssigneeBotNames(mission); + let completedCount = 0; + let failedCount = 0; + let failedBotName: string | undefined; + const totalAssignees = botNames.length; + + // Check ALL assigned bots' VoyagerLoops for completion + for (const botName of botNames) { + const bot = this.botManager.getBot(botName); + if (!bot) continue; + + const voyager = bot.getVoyagerLoop(); + if (!voyager) continue; + + const completedTasks = voyager.getCompletedTasks(); + const failedTasks = voyager.getFailedTasks(); + + if (completedTasks.includes(trackedDescription)) { + completedCount++; + } else if (failedTasks.includes(trackedDescription)) { + failedCount++; + if (!failedBotName) failedBotName = botName; + } + // If neither completed nor failed, the bot is still working on it + } + + // Fail the mission if any bot failed + if (failedCount > 0) { + logger.info( + { missionId: mission.id, failedCount, totalAssignees, task: trackedDescription }, + 'Mission task failed: one or more bots failed' + ); + this.updateMissionStatus(mission.id, 'failed', { + error: `Task failed on ${failedCount}/${totalAssignees} bot(s): ${trackedDescription}`, + }); + return; + } + + // Complete only when ALL assignees have finished + if (completedCount >= totalAssignees && totalAssignees > 0) { + logger.info( + { missionId: mission.id, completedCount, totalAssignees, task: trackedDescription }, + 'Mission task completed by all assignees' + ); + this.updateMissionStatus(mission.id, 'completed'); + return; + } + + // Otherwise, still in progress — let stale detector handle truly stuck missions + } + + private async executeBuildMission(mission: MissionRecord): Promise { + if (!this.buildCoordinator) { + logger.error({ missionId: mission.id }, 'Cannot execute build mission: BuildCoordinator not set'); + return this.updateMissionStatus(mission.id, 'failed', { error: 'BuildCoordinator not available' }); + } + + // Extract build parameters from the first step's payload or from assigneeIds + const payload = mission.steps[0]?.payload ?? {}; + const schematicFile = (payload.schematicFile as string) ?? ''; + const origin = (payload.origin as { x: number; y: number; z: number }) ?? { x: 0, y: 0, z: 0 }; + const botNames = (payload.botNames as string[]) ?? mission.assigneeIds; + + if (!schematicFile) { + logger.error({ missionId: mission.id }, 'Build mission missing schematicFile in step payload'); + return this.updateMissionStatus(mission.id, 'failed', { error: 'Missing schematicFile in step payload' }); + } + + try { + const job = await this.buildCoordinator.startBuild(schematicFile, origin, botNames); + // Link the build job ID to the mission + if (mission.steps[0]) { + mission.steps[0].payload.buildJobId = job.id; + mission.steps[0].status = 'running'; + } + logger.info({ missionId: mission.id, buildJobId: job.id }, 'Build mission started'); + return this.updateMissionStatus(mission.id, 'running'); + } catch (err: any) { + logger.error({ err, missionId: mission.id }, 'Failed to start build mission'); + return this.updateMissionStatus(mission.id, 'failed', { error: err.message }); + } + } + + private executeChainMission(mission: MissionRecord): MissionRecord | undefined { + if (!this.chainCoordinator) { + logger.error({ missionId: mission.id }, 'Cannot execute chain mission: ChainCoordinator not set'); + return this.updateMissionStatus(mission.id, 'failed', { error: 'ChainCoordinator not available' }); + } + + // Extract chain ID from the first step's payload + const payload = mission.steps[0]?.payload ?? {}; + const chainId = (payload.chainId as string) ?? ''; + + if (!chainId) { + logger.error({ missionId: mission.id }, 'Chain mission missing chainId in step payload'); + return this.updateMissionStatus(mission.id, 'failed', { error: 'Missing chainId in step payload' }); + } + + const started = this.chainCoordinator.startChain(chainId); + if (!started) { + logger.error({ missionId: mission.id, chainId }, 'Failed to start supply chain'); + return this.updateMissionStatus(mission.id, 'failed', { error: `Failed to start chain ${chainId}` }); + } + + if (mission.steps[0]) { + mission.steps[0].payload.chainId = chainId; + mission.steps[0].status = 'running'; + } + logger.info({ missionId: mission.id, chainId }, 'Supply chain mission started'); + return this.updateMissionStatus(mission.id, 'running'); + } + + // ── Bot mission queue management ─────────────────── + + getBotMissionQueue(botName: string): MissionRecord[] { + const queueIds = this.botMissionQueues.get(botName) ?? []; + return queueIds + .map((id) => this.missions.get(id)) + .filter((m): m is MissionRecord => !!m && m.status !== 'completed' && m.status !== 'cancelled' && m.status !== 'failed'); + } + + updateBotMissionQueue( + botName: string, + action: 'remove' | 'reorder' | 'clear', + missionId?: string, + position?: { from: number; to: number } + ): boolean { + const queue = this.botMissionQueues.get(botName); + if (!queue) return false; + + switch (action) { + case 'remove': { + if (!missionId) return false; + const idx = queue.indexOf(missionId); + if (idx === -1) return false; + queue.splice(idx, 1); + break; + } + case 'reorder': { + if (!position || position.from < 0 || position.from >= queue.length || position.to < 0 || position.to >= queue.length) { + return false; + } + const [item] = queue.splice(position.from, 1); + queue.splice(position.to, 0, item); + break; + } + case 'clear': { + queue.length = 0; + break; + } + default: + return false; + } + + this.save(); + return true; + } + + private addToBotQueue(botName: string, missionId: string): void { + if (!this.botMissionQueues.has(botName)) { + this.botMissionQueues.set(botName, []); + } + const queue = this.botMissionQueues.get(botName)!; + if (!queue.includes(missionId)) { + queue.push(missionId); + } + } + + private removeFromBotQueue(botName: string, missionId: string): void { + const queue = this.botMissionQueues.get(botName); + if (!queue) return; + const idx = queue.indexOf(missionId); + if (idx !== -1) queue.splice(idx, 1); + } + + // ── Persistence ──────────────────────────────────── + + private load(): void { + try { + if (fs.existsSync(MISSIONS_FILE)) { + const raw = fs.readFileSync(MISSIONS_FILE, 'utf-8'); + const data = JSON.parse(raw) as { + missions: MissionRecord[]; + botQueues: Record; + }; + + for (const m of data.missions ?? []) { + this.missions.set(m.id, m); + } + for (const [botName, ids] of Object.entries(data.botQueues ?? {})) { + this.botMissionQueues.set(botName, ids); + } + logger.info({ count: this.missions.size }, 'Loaded missions from disk'); + } + } catch (err: any) { + logger.warn({ err }, 'Failed to load missions file, starting fresh'); + } + } + + private save(): void { + try { + if (!fs.existsSync(DATA_DIR)) { + fs.mkdirSync(DATA_DIR, { recursive: true }); + } + + const data = { + missions: Array.from(this.missions.values()), + botQueues: Object.fromEntries(this.botMissionQueues), + }; + fs.writeFileSync(MISSIONS_FILE, JSON.stringify(data, null, 2), 'utf-8'); + } catch (err: any) { + logger.error({ err }, 'Failed to save missions file'); + } + } +} diff --git a/src/control/MissionTypes.ts b/src/control/MissionTypes.ts new file mode 100644 index 0000000..1e3381d --- /dev/null +++ b/src/control/MissionTypes.ts @@ -0,0 +1,52 @@ +export type MissionType = + | 'queue_task' + | 'gather_items' + | 'craft_items' + | 'smelt_batch' + | 'build_schematic' + | 'supply_chain' + | 'patrol_zone' + | 'escort_player' + | 'resupply_builder'; + +export type MissionStatus = 'draft' | 'queued' | 'running' | 'paused' | 'completed' | 'failed' | 'cancelled'; + +export type MissionPriority = 'low' | 'normal' | 'high' | 'urgent'; + +export type MissionSource = 'dashboard' | 'map' | 'role' | 'routine' | 'commander'; + +export interface MissionStep { + id: string; + type: string; + status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'; + payload: Record; + error?: string; +} + +export interface MissionRecord { + id: string; + type: MissionType; + title: string; + description?: string; + assigneeType: 'bot' | 'squad'; + assigneeIds: string[]; + status: MissionStatus; + priority: MissionPriority; + steps: MissionStep[]; + createdAt: number; + updatedAt: number; + startedAt?: number; + completedAt?: number; + blockedReason?: string; + linkedCommandIds?: string[]; + source: MissionSource; +} + +// Socket event names +export const MISSION_EVENTS = { + CREATED: 'mission:created', + UPDATED: 'mission:updated', + COMPLETED: 'mission:completed', + FAILED: 'mission:failed', + CANCELLED: 'mission:cancelled', +} as const; diff --git a/src/control/RoleManager.ts b/src/control/RoleManager.ts new file mode 100644 index 0000000..914b945 --- /dev/null +++ b/src/control/RoleManager.ts @@ -0,0 +1,482 @@ +import { Server as SocketIOServer } from 'socket.io'; +import { RoleApprovalRequestRecord, RoleAssignmentRecord, RoleType, AutonomyLevel, FLEET_EVENTS } from './FleetTypes'; +import type { MissionManager } from './MissionManager'; +import type { MissionType } from './MissionTypes'; +import { logger } from '../util/logger'; +import * as fs from 'fs'; +import * as path from 'path'; + +const VALID_ROLES: RoleType[] = ['guard', 'builder', 'hauler', 'farmer', 'miner', 'scout', 'merchant', 'free-agent']; +const VALID_AUTONOMY: AutonomyLevel[] = ['manual', 'assisted', 'autonomous']; +const DEBOUNCE_MS = 1_000; + +export interface OverrideRecord { + reason: string; + commandId: string; + at: number; +} + +const OVERRIDE_EXPIRY_MS = 5 * 60 * 1000; // 5 minutes +const APPROVAL_EXPIRY_MS = 5 * 60 * 1000; // 5 minutes + +export class RoleManager { + private assignments: RoleAssignmentRecord[] = []; + private overrides: Map = new Map(); + private approvalRequests: RoleApprovalRequestRecord[] = []; + private readonly filePath: string; + private readonly io: SocketIOServer; + private saveTimer: ReturnType | null = null; + private overrideCheckInterval: ReturnType | null = null; + private automationInterval: ReturnType | null = null; + private missionManager: MissionManager | null = null; + private lastGeneratedAt: Map = new Map(); + + constructor(io: SocketIOServer) { + this.io = io; + this.filePath = path.join(process.cwd(), 'data', 'roles.json'); + this.load(); + this.overrideCheckInterval = setInterval(() => this.checkOverrideTimeouts(), 30_000); + this.automationInterval = setInterval(() => this.evaluateAutomation(), 60_000); + } + + setMissionManager(missionManager: MissionManager): void { + this.missionManager = missionManager; + this.evaluateAutomation(); + } + + // ── Manual Override Tracking ────────────────────────────── + + setOverride(botName: string, reason: string, commandId: string): void { + this.overrides.set(botName, { reason, commandId, at: Date.now() }); + this.emit(); + this.save(); + logger.info({ botName, reason, commandId }, 'RoleManager: override set'); + } + + clearOverride(botName: string): void { + if (this.overrides.delete(botName)) { + this.emit(); + this.save(); + logger.info({ botName }, 'RoleManager: override cleared'); + this.evaluateAutomation(botName); + } + } + + getOverride(botName: string): OverrideRecord | null { + return this.overrides.get(botName) || null; + } + + isOverridden(botName: string): boolean { + return this.overrides.has(botName); + } + + getOverrides(): Record { + return Object.fromEntries(this.overrides); + } + + /** Clear overrides older than 5 minutes. Call this periodically. */ + checkOverrideTimeouts(): void { + const now = Date.now(); + let changed = false; + for (const [botName, record] of this.overrides) { + if (now - record.at > OVERRIDE_EXPIRY_MS) { + this.overrides.delete(botName); + logger.info({ botName, ageMs: now - record.at }, 'RoleManager: override expired'); + changed = true; + this.evaluateAutomation(botName); + } + } + for (const request of this.approvalRequests) { + if (request.status === 'pending' && now > request.expiresAt) { + request.status = 'expired'; + request.decidedAt = now; + changed = true; + } + } + if (changed) { + this.emit(); + this.save(); + } + } + + // ── Persistence ────────────────────────────────────────── + + private load(): void { + try { + if (!fs.existsSync(this.filePath)) return; + + const raw = fs.readFileSync(this.filePath, 'utf-8'); + const parsed = JSON.parse(raw); + + if (Array.isArray(parsed)) { + this.assignments = parsed; + this.approvalRequests = []; + } else if (parsed && typeof parsed === 'object') { + this.assignments = Array.isArray(parsed.assignments) ? parsed.assignments : []; + this.approvalRequests = Array.isArray(parsed.approvalRequests) ? parsed.approvalRequests : []; + if (parsed.overrides && typeof parsed.overrides === 'object') { + this.overrides = new Map(Object.entries(parsed.overrides as Record)); + } + } else { + logger.warn('roles.json is corrupt, starting empty'); + this.assignments = []; + this.approvalRequests = []; + return; + } + logger.info({ count: this.assignments.length }, 'RoleManager: loaded assignments'); + } catch (err) { + logger.warn({ err }, 'RoleManager: failed to load roles.json, starting empty'); + this.assignments = []; + } + } + + /** Schedule a debounced save */ + private save(): void { + if (this.saveTimer) return; + this.saveTimer = setTimeout(() => { + this.saveTimer = null; + this.saveImmediate(); + }, DEBOUNCE_MS); + } + + /** Write to disk immediately */ + private saveImmediate(): void { + if (this.saveTimer) { clearTimeout(this.saveTimer); this.saveTimer = null; } + try { + const dir = path.dirname(this.filePath); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + fs.writeFileSync(this.filePath, JSON.stringify({ + assignments: this.assignments, + approvalRequests: this.approvalRequests, + overrides: this.getOverrides(), + }, null, 2)); + } catch (err) { + logger.error({ err }, 'RoleManager: failed to save roles.json'); + } + } + + /** Flush pending saves and clear timers */ + shutdown(): void { + if (this.overrideCheckInterval) { clearInterval(this.overrideCheckInterval); this.overrideCheckInterval = null; } + if (this.automationInterval) { clearInterval(this.automationInterval); this.automationInterval = null; } + this.saveImmediate(); + } + + private getDefaultMissionType(assignment: RoleAssignmentRecord): MissionType | null { + const preferred = assignment.preferredMissionTypes[0] as MissionType | undefined; + if (preferred) return preferred; + + switch (assignment.role) { + case 'guard': + return 'patrol_zone'; + case 'builder': + case 'hauler': + case 'farmer': + case 'miner': + case 'scout': + case 'merchant': + return 'queue_task'; + case 'free-agent': + default: + return null; + } + } + + private getDefaultMissionTitle(assignment: RoleAssignmentRecord): string { + switch (assignment.role) { + case 'guard': + return assignment.homeMarkerId ? `Guard ${assignment.homeMarkerId}` : `Guard assigned area`; + case 'builder': + return 'Maintain builder readiness'; + case 'hauler': + return 'Maintain hauling readiness'; + case 'farmer': + return 'Maintain farming readiness'; + case 'miner': + return 'Maintain mining readiness'; + case 'scout': + return 'Maintain scouting readiness'; + case 'merchant': + return 'Maintain merchant readiness'; + default: + return `Role automation for ${assignment.role}`; + } + } + + private buildMissionDraft(assignment: RoleAssignmentRecord): RoleApprovalRequestRecord['missionDraft'] | null { + const missionType = this.getDefaultMissionType(assignment); + if (!missionType) return null; + + const loadoutHint = assignment.loadoutPolicy ? ' Loadout policy configured.' : ''; + const zoneHint = assignment.allowedZoneIds.length > 0 ? ` Allowed zones: ${assignment.allowedZoneIds.join(', ')}.` : ''; + const description = assignment.homeMarkerId + ? `Auto-generated ${assignment.role} mission near ${assignment.homeMarkerId}` + : `Auto-generated ${assignment.role} mission`; + + return { + type: missionType, + title: this.getDefaultMissionTitle(assignment), + description: `${description}.${zoneHint}${loadoutHint}`.trim(), + assigneeType: 'bot', + assigneeIds: [assignment.botName], + priority: 'normal', + source: 'role', + }; + } + + private createApprovalRequest(assignment: RoleAssignmentRecord, missionDraft: RoleApprovalRequestRecord['missionDraft']): void { + const existing = this.approvalRequests.find((request) => request.botName === assignment.botName && request.status === 'pending'); + if (existing) return; + + const now = Date.now(); + this.approvalRequests.unshift({ + id: `approval_${now}_${Math.random().toString(36).slice(2, 8)}`, + assignmentId: assignment.id, + assignmentUpdatedAt: assignment.updatedAt, + botName: assignment.botName, + role: assignment.role, + status: 'pending', + createdAt: now, + expiresAt: now + APPROVAL_EXPIRY_MS, + missionDraft, + }); + this.approvalRequests = this.approvalRequests.slice(0, 100); + this.emit(); + this.save(); + } + + private canReplaceActiveMission(assignment: RoleAssignmentRecord, activeMission: { priority: string; source: string; status: string } | undefined): boolean { + if (!activeMission) return true; + if (activeMission.source === 'role') return false; + + switch (assignment.interruptPolicy) { + case 'always': + return true; + case 'never-while-critical': + return !['running', 'paused'].includes(activeMission.status) && activeMission.priority !== 'urgent'; + case 'confirm-if-busy': + default: + return false; + } + } + + private handleInterruptibleMission(assignment: RoleAssignmentRecord, activeMission: { id: string; priority: string; source: string; status: string } | undefined): boolean { + if (!activeMission) return true; + if (activeMission.source === 'role') return false; + if (assignment.interruptPolicy !== 'always') return false; + if (!this.missionManager) return false; + + if (['queued', 'paused', 'draft'].includes(activeMission.status)) { + this.missionManager.cancelMission(activeMission.id); + logger.info({ botName: assignment.botName, interruptedMissionId: activeMission.id }, 'RoleManager: interrupted queued mission for role automation'); + return true; + } + + return false; + } + + evaluateAutomation(botName?: string): void { + if (!this.missionManager) return; + + const candidates = botName + ? this.assignments.filter((assignment) => assignment.botName === botName) + : this.assignments; + + const now = Date.now(); + + for (const assignment of candidates) { + if (assignment.autonomyLevel === 'manual') continue; + if (assignment.role === 'free-agent') continue; + if (this.isOverridden(assignment.botName)) continue; + + const activeMissions = this.missionManager.getMissions({ bot: assignment.botName }) + .filter((mission) => ['draft', 'queued', 'running', 'paused'].includes(mission.status)); + const activeRoleMission = activeMissions.find((mission) => mission.source === 'role'); + if (activeRoleMission) continue; + + const activeNonRoleMission = activeMissions.find((mission) => mission.source !== 'role'); + if (activeNonRoleMission && !this.canReplaceActiveMission(assignment, activeNonRoleMission)) { + const interrupted = this.handleInterruptibleMission(assignment, activeNonRoleMission); + if (!interrupted) continue; + } + + const lastGeneratedAt = this.lastGeneratedAt.get(assignment.botName) ?? 0; + if (now - lastGeneratedAt < 60_000) continue; + + const missionDraft = this.buildMissionDraft(assignment); + if (!missionDraft) continue; + + if (assignment.autonomyLevel === 'assisted') { + this.createApprovalRequest(assignment, missionDraft); + this.lastGeneratedAt.set(assignment.botName, now); + logger.info({ botName: assignment.botName, role: assignment.role }, 'RoleManager: created assisted approval request'); + continue; + } + + this.missionManager.createMission({ + ...missionDraft, + type: missionDraft.type as MissionType, + }); + this.lastGeneratedAt.set(assignment.botName, now); + logger.info({ botName: assignment.botName, role: assignment.role, missionType: missionDraft.type }, 'RoleManager: auto-generated role mission'); + } + } + + private generateId(): string { + return `role_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`; + } + + private emit(): void { + this.io.emit(FLEET_EVENTS.ROLE_UPDATED, { + assignments: this.assignments, + overrides: this.getOverrides(), + approvalRequests: this.approvalRequests, + }); + } + + getApprovalRequests(): RoleApprovalRequestRecord[] { + return this.approvalRequests; + } + + approveApprovalRequest(id: string, decidedBy?: string, decisionNote?: string): { approvalRequest: RoleApprovalRequestRecord; missionId: string } | null { + if (!this.missionManager) return null; + const request = this.approvalRequests.find((entry) => entry.id === id); + if (!request || request.status !== 'pending') return null; + const assignment = this.getAssignment(request.assignmentId); + if (!assignment || assignment.updatedAt !== request.assignmentUpdatedAt || assignment.autonomyLevel !== 'assisted') { + request.status = 'expired'; + request.decidedAt = Date.now(); + this.emit(); + this.save(); + return null; + } + const missionRecord = this.missionManager.createMission({ + ...request.missionDraft, + type: request.missionDraft.type as MissionType, + }); + request.status = 'approved'; + request.decidedAt = Date.now(); + request.decidedBy = decidedBy; + request.decisionNote = decisionNote; + this.emit(); + this.save(); + return { approvalRequest: request, missionId: missionRecord.id }; + } + + rejectApprovalRequest(id: string, decidedBy?: string, decisionNote?: string): RoleApprovalRequestRecord | null { + const request = this.approvalRequests.find((entry) => entry.id === id); + if (!request || request.status !== 'pending') return null; + request.status = 'rejected'; + request.decidedAt = Date.now(); + request.decidedBy = decidedBy; + request.decisionNote = decisionNote; + this.emit(); + this.save(); + return request; + } + + // ── CRUD ───────────────────────────────────────────────── + + createAssignment(data: { + botName: string; + role: RoleType; + autonomyLevel: AutonomyLevel; + homeMarkerId?: string; + allowedZoneIds?: string[]; + preferredMissionTypes?: string[]; + interruptPolicy?: RoleAssignmentRecord['interruptPolicy']; + loadoutPolicy?: RoleAssignmentRecord['loadoutPolicy']; + }): RoleAssignmentRecord { + // Validate role + if (!VALID_ROLES.includes(data.role)) { + throw new Error(`Invalid role: ${data.role}`); + } + // Validate autonomy level + if (!VALID_AUTONOMY.includes(data.autonomyLevel)) { + throw new Error(`Invalid autonomy level: ${data.autonomyLevel}`); + } + + // Replace existing assignment for the same bot + const existing = this.assignments.findIndex((a) => a.botName === data.botName); + if (existing !== -1) { + logger.warn( + { assignmentId: this.assignments[existing].id, botName: data.botName, role: this.assignments[existing].role, action: 'replace' }, + 'RoleManager: replacing existing role assignment for bot', + ); + this.assignments.splice(existing, 1); + } + + const record: RoleAssignmentRecord = { + id: this.generateId(), + botName: data.botName, + role: data.role, + autonomyLevel: data.autonomyLevel, + homeMarkerId: data.homeMarkerId, + allowedZoneIds: data.allowedZoneIds ?? [], + preferredMissionTypes: data.preferredMissionTypes ?? [], + interruptPolicy: data.interruptPolicy, + loadoutPolicy: data.loadoutPolicy, + updatedAt: Date.now(), + }; + + this.assignments.push(record); + this.save(); + this.emit(); + this.evaluateAutomation(record.botName); + logger.info({ assignmentId: record.id, botName: record.botName, role: record.role, action: 'create' }, 'RoleManager: assignment created'); + return record; + } + + getAssignments(): RoleAssignmentRecord[] { + return this.assignments; + } + + getAssignment(id: string): RoleAssignmentRecord | null { + return this.assignments.find((a) => a.id === id) ?? null; + } + + getAssignmentForBot(botName: string): RoleAssignmentRecord | null { + return this.assignments.find((a) => a.botName === botName) ?? null; + } + + updateAssignment(id: string, data: Partial): RoleAssignmentRecord | null { + const idx = this.assignments.findIndex((a) => a.id === id); + if (idx === -1) return null; + + // Validate role if provided + if (data.role && !VALID_ROLES.includes(data.role)) { + throw new Error(`Invalid role: ${data.role}`); + } + // Validate autonomy level if provided + if (data.autonomyLevel && !VALID_AUTONOMY.includes(data.autonomyLevel)) { + throw new Error(`Invalid autonomy level: ${data.autonomyLevel}`); + } + + // Don't allow changing id + const { id: _ignoreId, ...updateFields } = data; + this.assignments[idx] = { ...this.assignments[idx], ...updateFields, updatedAt: Date.now() }; + + this.save(); + this.emit(); + this.evaluateAutomation(this.assignments[idx].botName); + logger.info( + { assignmentId: id, botName: this.assignments[idx].botName, role: this.assignments[idx].role, action: 'update' }, + 'RoleManager: assignment updated', + ); + return this.assignments[idx]; + } + + deleteAssignment(id: string): boolean { + const idx = this.assignments.findIndex((a) => a.id === id); + if (idx === -1) return false; + + const removed = this.assignments.splice(idx, 1)[0]; + this.save(); + this.emit(); + this.lastGeneratedAt.delete(removed.botName); + logger.info({ assignmentId: id, botName: removed.botName, role: removed.role, action: 'delete' }, 'RoleManager: assignment deleted'); + return true; + } +} diff --git a/src/control/SquadManager.ts b/src/control/SquadManager.ts new file mode 100644 index 0000000..4e27b7a --- /dev/null +++ b/src/control/SquadManager.ts @@ -0,0 +1,177 @@ +import { Server as SocketIOServer } from 'socket.io'; +import { SquadRecord, FLEET_EVENTS } from './FleetTypes'; +import { logger } from '../util/logger'; +import * as fs from 'fs'; +import * as path from 'path'; + +const DATA_DIR = path.join(process.cwd(), 'data'); +const SQUADS_FILE = path.join(DATA_DIR, 'squads.json'); +const DEBOUNCE_MS = 1_000; + +function generateId(): string { + return `sqd_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`; +} + +export class SquadManager { + private squads: Map = new Map(); + private io: SocketIOServer; + private saveTimer: ReturnType | null = null; + + constructor(io: SocketIOServer) { + this.io = io; + this.load(); + } + + // ── Persistence ────────────────────────────────────── + + private load(): void { + try { + if (!fs.existsSync(SQUADS_FILE)) return; + + const raw = fs.readFileSync(SQUADS_FILE, 'utf-8'); + const records = JSON.parse(raw); + + if (!Array.isArray(records)) { + logger.warn('squads.json is corrupt (not an array), starting fresh'); + return; + } + + for (const rec of records) { + this.squads.set(rec.id, rec); + } + logger.info({ count: records.length }, 'Loaded squads from disk'); + } catch (err) { + logger.warn({ err }, 'Failed to load squads.json, starting fresh'); + this.squads.clear(); + } + } + + /** Schedule a debounced save */ + private save(): void { + if (this.saveTimer) return; + this.saveTimer = setTimeout(() => { + this.saveTimer = null; + this.saveImmediate(); + }, DEBOUNCE_MS); + } + + /** Write to disk immediately */ + private saveImmediate(): void { + if (this.saveTimer) { clearTimeout(this.saveTimer); this.saveTimer = null; } + try { + if (!fs.existsSync(DATA_DIR)) { + fs.mkdirSync(DATA_DIR, { recursive: true }); + } + const records = Array.from(this.squads.values()); + fs.writeFileSync(SQUADS_FILE, JSON.stringify(records, null, 2), 'utf-8'); + } catch (err) { + logger.error({ err }, 'Failed to save squads.json'); + } + } + + /** Flush pending saves and clear timers */ + shutdown(): void { + this.saveImmediate(); + } + + private emitUpdate(): void { + this.io.emit(FLEET_EVENTS.SQUAD_UPDATED, this.getSquads()); + } + + // ── CRUD ───────────────────────────────────────────── + + createSquad(data: { + name: string; + botNames: string[]; + defaultRole?: string; + homeMarkerId?: string; + }): SquadRecord { + const now = Date.now(); + const squad: SquadRecord = { + id: generateId(), + name: data.name, + botNames: data.botNames ?? [], + defaultRole: data.defaultRole, + homeMarkerId: data.homeMarkerId, + createdAt: now, + updatedAt: now, + }; + this.squads.set(squad.id, squad); + this.save(); + this.emitUpdate(); + logger.info({ squadId: squad.id, name: squad.name, action: 'create' }, 'Squad created'); + return squad; + } + + getSquads(): SquadRecord[] { + return Array.from(this.squads.values()); + } + + getSquad(id: string): SquadRecord | null { + return this.squads.get(id) ?? null; + } + + updateSquad(id: string, data: Partial): SquadRecord | null { + const existing = this.squads.get(id); + if (!existing) return null; + + // Prevent overwriting immutable fields + const { id: _id, createdAt: _ca, ...safeData } = data; + + const updated: SquadRecord = { + ...existing, + ...safeData, + updatedAt: Date.now(), + }; + this.squads.set(id, updated); + this.save(); + this.emitUpdate(); + logger.info({ squadId: id, name: updated.name, action: 'update' }, 'Squad updated'); + return updated; + } + + deleteSquad(id: string): boolean { + const squad = this.squads.get(id); + const existed = this.squads.delete(id); + if (existed) { + this.save(); + this.emitUpdate(); + logger.info({ squadId: id, name: squad?.name, action: 'delete' }, 'Squad deleted'); + } + return existed; + } + + // ── Membership helpers ─────────────────────────────── + + addBotToSquad(squadId: string, botName: string): boolean { + const squad = this.squads.get(squadId); + if (!squad) return false; + if (squad.botNames.includes(botName)) return true; // already a member + + squad.botNames.push(botName); + squad.updatedAt = Date.now(); + this.save(); + this.emitUpdate(); + logger.info({ squadId, name: squad.name, action: 'add_bot' }, 'Bot added to squad'); + return true; + } + + removeBotFromSquad(squadId: string, botName: string): boolean { + const squad = this.squads.get(squadId); + if (!squad) return false; + + const idx = squad.botNames.indexOf(botName); + if (idx === -1) return false; + + squad.botNames.splice(idx, 1); + squad.updatedAt = Date.now(); + this.save(); + this.emitUpdate(); + logger.info({ squadId, name: squad.name, action: 'remove_bot' }, 'Bot removed from squad'); + return true; + } + + getSquadsForBot(botName: string): SquadRecord[] { + return this.getSquads().filter((s) => s.botNames.includes(botName)); + } +} diff --git a/src/control/WorldTypes.ts b/src/control/WorldTypes.ts new file mode 100644 index 0000000..4e04932 --- /dev/null +++ b/src/control/WorldTypes.ts @@ -0,0 +1,28 @@ +export interface MarkerRecord { + id: string; + name: string; + kind: 'base' | 'storage' | 'build-site' | 'mine' | 'village' | 'custom'; + position: { x: number; y: number; z: number }; + tags: string[]; + notes?: string; + createdAt: number; + updatedAt: number; +} + +export interface ZoneRecord { + id: string; + name: string; + mode: 'guard' | 'avoid' | 'farm' | 'build' | 'gather' | 'custom'; + shape: 'circle' | 'rectangle'; + circle?: { x: number; z: number; radius: number }; + rectangle?: { minX: number; minZ: number; maxX: number; maxZ: number }; + markerIds?: string[]; + rules?: Record; +} + +export interface RouteRecord { + id: string; + name: string; + waypointIds: string[]; + loop: boolean; +} diff --git a/src/control/index.ts b/src/control/index.ts new file mode 100644 index 0000000..39a117f --- /dev/null +++ b/src/control/index.ts @@ -0,0 +1,5 @@ +export * from './CommandTypes'; +export * from './MissionTypes'; +export * from './WorldTypes'; +export * from './FleetTypes'; +export * from './CommanderService'; diff --git a/src/index.ts b/src/index.ts index e999f1c..95070d9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -63,7 +63,7 @@ async function main() { await botManager.loadSavedBots(); // Start HTTP API server with Socket.IO - const { httpServer, io, eventLog } = createAPIServer(botManager); + const { httpServer, io, eventLog, markerStore } = createAPIServer(botManager); // Set up real-time Socket.IO event broadcasting setupSocketEvents(botManager, io, eventLog); @@ -136,6 +136,9 @@ async function main() { startMemoryDiagnostics(); + // Start watchdog to auto-reconnect disconnected bots every 60s + botManager.startWatchdog(); + httpServer.listen(config.api.port, config.api.host, () => { logger.info({ port: config.api.port, host: config.api.host }, 'DyoBot API server running (HTTP + WebSocket)'); }); @@ -147,6 +150,10 @@ async function main() { clearInterval(memoryInterval); memoryInterval = null; } + botManager.stopWatchdog(); + // Flush all debounced file writes before tearing down bots + botManager.shutdownPersistence(); + markerStore.shutdown(); io.close(); await botManager.removeAllBots(); process.exit(0); diff --git a/src/personality/AffinityManager.ts b/src/personality/AffinityManager.ts index 5cd23e0..4f0fbbc 100644 --- a/src/personality/AffinityManager.ts +++ b/src/personality/AffinityManager.ts @@ -6,10 +6,23 @@ interface AffinityStore { [botName: string]: { [playerName: string]: number }; } +interface RelationshipEvent { + type: string; + timestamp: number; + detail?: string; +} + +interface PersistedData { + scores: AffinityStore; + events: { [key: string]: RelationshipEvent[] }; +} + export class AffinityManager { private store: AffinityStore = {}; + private events: Map = new Map(); private config: Config['affinity']; private savePath: string; + private _saveTimer: ReturnType | null = null; constructor(config: Config['affinity'], dataDir: string) { this.config = config; @@ -31,21 +44,25 @@ export class AffinityManager { onPositiveChat(botName: string, playerName: string): void { this.set(botName, playerName, this.get(botName, playerName) + this.config.chatBonus); + this.recordEvent(botName, playerName, 'chat', 'positive conversation'); this.save(); } onNegativeSentiment(botName: string, playerName: string): void { this.set(botName, playerName, this.get(botName, playerName) - this.config.negativeSentimentPenalty); + this.recordEvent(botName, playerName, 'chat', 'negative sentiment'); this.save(); } onHit(botName: string, playerName: string): void { this.set(botName, playerName, this.get(botName, playerName) - this.config.hitPenalty); + this.recordEvent(botName, playerName, 'hit', 'was hit by player'); this.save(); } onGift(botName: string, playerName: string): void { this.set(botName, playerName, this.get(botName, playerName) + this.config.giftBonus); + this.recordEvent(botName, playerName, 'gift', 'received a gift'); this.save(); } @@ -60,7 +77,6 @@ export class AffinityManager { /** Get the entire affinity store (all bots, all players) */ getAll(): AffinityStore { - // Return a deep-ish copy to prevent mutation const copy: AffinityStore = {}; for (const [bot, players] of Object.entries(this.store)) { copy[bot] = { ...players }; @@ -69,20 +85,115 @@ export class AffinityManager { } clearBot(botName: string): void { - delete this.store[botName.toLowerCase()]; + const key = botName.toLowerCase(); + delete this.store[key]; + // Clear events for this bot + for (const eventKey of this.events.keys()) { + if (eventKey.startsWith(key + ':')) { + this.events.delete(eventKey); + } + } this.save(); } + recordEvent(botName: string, playerName: string, type: string, detail?: string): void { + const key = `${botName.toLowerCase()}:${playerName.toLowerCase()}`; + if (!this.events.has(key)) this.events.set(key, []); + const events = this.events.get(key)!; + events.push({ type, timestamp: Date.now(), detail }); + // Keep last 20 per relationship + if (events.length > 20) { + events.splice(0, events.length - 20); + } + } + + getRelationshipSummary(botName: string, playerName: string): string { + const key = `${botName.toLowerCase()}:${playerName.toLowerCase()}`; + const events = this.events.get(key) ?? []; + const affinity = this.get(botName, playerName); + + const counts: Record = {}; + for (const e of events) { + counts[e.type] = (counts[e.type] || 0) + 1; + } + + const parts: string[] = []; + if (counts['chat']) parts.push(`chatted ${counts['chat']} time${counts['chat'] > 1 ? 's' : ''}`); + if (counts['hit']) parts.push(`been hit ${counts['hit']} time${counts['hit'] > 1 ? 's' : ''}`); + if (counts['gift']) parts.push(`received ${counts['gift']} gift${counts['gift'] > 1 ? 's' : ''}`); + if (counts['cooperation']) parts.push(`cooperated ${counts['cooperation']} time${counts['cooperation'] > 1 ? 's' : ''}`); + if (counts['help_request']) parts.push(`${counts['help_request']} help request${counts['help_request'] > 1 ? 's' : ''}`); + + let tier: string; + if (affinity >= 80) tier = 'Close friend'; + else if (affinity >= 60) tier = 'Friendly'; + else if (affinity >= 40) tier = 'Neutral'; + else if (affinity >= 20) tier = 'Wary'; + else tier = 'Hostile'; + + const interaction = parts.length > 0 ? `You've ${parts.join(', ')} with them. ` : 'No recorded interactions. '; + return `${interaction}Affinity: ${affinity} (${tier}).`; + } + + getTopRelationships(botName: string, limit = 5): { player: string; affinity: number; summary: string }[] { + const allForBot = this.getAllForBot(botName); + return Object.entries(allForBot) + .sort((a, b) => b[1] - a[1]) + .slice(0, limit) + .map(([player, affinity]) => ({ + player, + affinity, + summary: this.getRelationshipSummary(botName, player), + })); + } + private save(): void { + if (this._saveTimer) clearTimeout(this._saveTimer); + this._saveTimer = setTimeout(() => { + this._saveTimer = null; + this.writeAtomic(); + }, 2000); + } + + private writeAtomic(): void { const dir = path.dirname(this.savePath); if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); - fs.writeFileSync(this.savePath, JSON.stringify(this.store, null, 2)); + const data: PersistedData = { + scores: this.store, + events: Object.fromEntries(this.events), + }; + const tmpPath = this.savePath + '.tmp'; + try { + fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2)); + fs.renameSync(tmpPath, this.savePath); + } catch { + try { fs.writeFileSync(this.savePath, JSON.stringify(data, null, 2)); } catch { /* best effort */ } + try { fs.unlinkSync(tmpPath); } catch { /* ignore */ } + } + } + + shutdown(): void { + if (this._saveTimer) { + clearTimeout(this._saveTimer); + this._saveTimer = null; + this.writeAtomic(); + } } private load(): void { if (fs.existsSync(this.savePath)) { try { - this.store = JSON.parse(fs.readFileSync(this.savePath, 'utf-8')); + const raw = JSON.parse(fs.readFileSync(this.savePath, 'utf-8')); + // Support both old format (flat AffinityStore) and new format (PersistedData) + if (raw.scores) { + this.store = raw.scores; + if (raw.events) { + this.events = new Map(Object.entries(raw.events)); + } + } else { + // Old format: the entire file is the AffinityStore + this.store = raw; + } } catch { /* start fresh */ } } } diff --git a/src/server/api.ts b/src/server/api.ts index 25af635..f516276 100644 --- a/src/server/api.ts +++ b/src/server/api.ts @@ -1,11 +1,20 @@ import express, { Request, Response } from 'express'; import cors from 'cors'; import http from 'http'; -import path from 'path'; +import { Vec3 } from 'vec3'; import { Server as SocketIOServer } from 'socket.io'; import { BotManager } from '../bot/BotManager'; import { BotInstance } from '../bot/BotInstance'; import { EventLog, BotEvent } from './EventLog'; +import { CommandCenter } from '../control/CommandCenter'; +import { CommandType } from '../control/CommandTypes'; +import { MissionManager } from '../control/MissionManager'; +import { MarkerStore } from '../control/MarkerStore'; +import { SquadManager } from '../control/SquadManager'; +import { RoleManager } from '../control/RoleManager'; +import { CommanderService } from '../control/CommanderService'; +import { BuildCoordinator } from '../build/BuildCoordinator'; +import { ChainCoordinator } from '../supplychain/ChainCoordinator'; import { logger } from '../util/logger'; export interface APIServerResult { @@ -13,6 +22,14 @@ export interface APIServerResult { httpServer: http.Server; io: SocketIOServer; eventLog: EventLog; + commandCenter: CommandCenter; + missionManager: MissionManager; + buildCoordinator: BuildCoordinator; + chainCoordinator: ChainCoordinator; + markerStore: MarkerStore; + squadManager: SquadManager; + roleManager: RoleManager; + commanderService: CommanderService; } export function createAPIServer(botManager: BotManager): APIServerResult { @@ -31,12 +48,6 @@ export function createAPIServer(botManager: BotManager): APIServerResult { app.use(express.json()); - const dashboardDir = path.join(process.cwd(), 'dashboard'); - app.use('/dashboard', express.static(dashboardDir)); - app.get('/', (_req: Request, res: Response) => { - res.redirect('/dashboard/'); - }); - // Event log (in-memory circular buffer) const eventLog = new EventLog(500); @@ -58,6 +69,30 @@ export function createAPIServer(botManager: BotManager): APIServerResult { }); }); + // ── Control platform singletons (created once, wired to routes below) ── + const markerStore = new MarkerStore(io); + const squadManager = new SquadManager(io); + const roleManager = new RoleManager(io); + const commandCenter = new CommandCenter(botManager, io, markerStore); + const missionManager = new MissionManager(botManager, io); + const buildCoordinator = new BuildCoordinator(botManager, io, eventLog); + const chainCoordinator = new ChainCoordinator(botManager, io, eventLog); + missionManager.setBuildCoordinator(buildCoordinator); + missionManager.setChainCoordinator(chainCoordinator); + missionManager.setSquadManager(squadManager); + roleManager.setMissionManager(missionManager); + + const commanderService = new CommanderService({ + llmClient: botManager.getLLMClient?.() ?? null, + botManager, commandCenter, missionManager, markerStore, + }); + + // Wire role manager to command center for auto-override + if ('setRoleManager' in commandCenter) (commandCenter as any).setRoleManager(roleManager); + + // Check override timeouts periodically + setInterval(() => roleManager.checkOverrideTimeouts?.(), 60000); + // ═══════════════════════════════════════ // EXISTING ENDPOINTS (unchanged logic) // ═══════════════════════════════════════ @@ -67,6 +102,21 @@ export function createAPIServer(botManager: BotManager): APIServerResult { res.json({ status: 'ok', botCount: botManager.getAllBots().length, + controlPlatform: { + commandCount: commandCenter.getCommands().length, + missionCount: missionManager.getMissions().length, + markerCount: markerStore.getMarkers().length, + squadCount: squadManager.getSquads().length, + roleCount: roleManager.getAssignments().length, + }, + }); + }); + + // Metrics endpoint + app.get('/api/metrics', (_req: Request, res: Response) => { + res.json({ + commands: typeof commandCenter.getMetrics === 'function' ? commandCenter.getMetrics() : {}, + missions: typeof missionManager.getMetrics === 'function' ? missionManager.getMetrics() : {}, }); }); @@ -333,6 +383,53 @@ export function createAPIServer(botManager: BotManager): APIServerResult { res.json({ events }); }); + // Online players with positions + app.get('/api/players', (_req: Request, res: Response) => { + const bots = botManager.getAllBots(); + const connectedBot = bots.find((b) => b.bot); + if (!connectedBot?.bot) { + res.json({ players: [] }); + return; + } + const players = Object.values(connectedBot.bot.players) + .filter((p: any) => p.username) + .map((p: any) => ({ + name: p.username, + position: p.entity + ? { x: Math.floor(p.entity.position.x), y: Math.floor(p.entity.position.y), z: Math.floor(p.entity.position.z) } + : null, + isOnline: true, + })); + res.json({ players }); + }); + + // Social Memory + app.get('/api/bots/:name/memories', (req: Request, res: Response) => { + const name = req.params.name as string; + const memories = botManager.getSocialMemory().getRecentMemories(name, 20); + const reflections = botManager.getSocialMemory().getReflections(name, 5); + const emotional = botManager.getSocialMemory().getEmotionalState(name); + res.json({ memories, reflections, emotionalState: emotional }); + }); + + // Bot Communications + app.get('/api/bots/:name/messages', (req: Request, res: Response) => { + const name = req.params.name as string; + const messages = botManager.getBotComms().getRecentMessages(name, 20); + res.json({ messages }); + }); + + // Send a message between bots (from dashboard) + app.post('/api/bots/:name/bot-message', (req: Request, res: Response) => { + const { to, content } = req.body; + if (!to || !content) { + res.status(400).json({ error: 'to and content required' }); + return; + } + const msg = botManager.getBotComms().sendMessage(req.params.name as string, to, content, 'chat'); + res.json({ success: true, message: msg }); + }); + // Send chat message to a bot (from dashboard) app.post('/api/bots/:name/chat', (req: Request, res: Response) => { const { playerName, message } = req.body; @@ -402,5 +499,858 @@ export function createAPIServer(botManager: BotManager): APIServerResult { res.json({ success: true }); }); - return { app, httpServer, io, eventLog }; + // ═══════════════════════════════════════ + // CONTROL PLATFORM - COMMAND ENDPOINTS + // ═══════════════════════════════════════ + + // Create and dispatch a command + app.post('/api/commands', async (req: Request, res: Response) => { + const { type, scope, priority, source, targets, params, payload } = req.body; + + if (!type || !targets || !Array.isArray(targets) || targets.length === 0) { + res.status(400).json({ error: 'type and targets[] are required' }); + return; + } + + try { + const command = commandCenter.createCommand({ + type: type as CommandType, + scope: scope === 'bot' ? 'single' : (scope ?? 'single'), + priority: priority === 'urgent' ? 'critical' : (priority ?? 'normal'), + source: source ?? 'dashboard', + targets, + params: params ?? payload ?? {}, + }); + const result = await commandCenter.dispatchCommand(command); + const statusCode = result.status === 'succeeded' ? 200 : result.status === 'failed' ? 422 : 200; + res.status(statusCode).json({ command: result }); + } catch (err: any) { + res.status(500).json({ error: err?.message ?? 'Internal error' }); + } + }); + + // List commands with optional filters + app.get('/api/commands', (req: Request, res: Response) => { + const bot = req.query.bot ? String(req.query.bot) : undefined; + const status = req.query.status ? String(req.query.status) as any : undefined; + const limit = req.query.limit ? parseInt(String(req.query.limit)) : undefined; + const commands = commandCenter.getCommands({ bot, status, limit }); + res.json({ commands }); + }); + + // Get single command + app.get('/api/commands/:id', (req: Request, res: Response) => { + const command = commandCenter.getCommand(req.params.id as string); + if (!command) { + res.status(404).json({ error: 'Command not found' }); + return; + } + res.json({ command }); + }); + + // Cancel a command + app.post('/api/commands/:id/cancel', (req: Request, res: Response) => { + const command = commandCenter.cancelCommand(req.params.id as string); + if (!command) { + res.status(404).json({ error: 'Command not found' }); + return; + } + res.json({ command }); + }); + + // ═══════════════════════════════════════ + // BOT ACTION SHORTCUTS (via CommandCenter) + // ═══════════════════════════════════════ + + // Pause voyager + app.post('/api/bots/:name/pause', async (req: Request, res: Response) => { + const name = req.params.name as string; + const command = commandCenter.createCommand({ + type: 'pause_voyager', targets: [name], source: 'dashboard', + }); + await commandCenter.dispatchCommand(command); + if (command.status === 'failed') { + res.status(422).json({ success: false, error: command.error?.message }); + return; + } + res.json({ success: true }); + }); + + // Resume voyager + app.post('/api/bots/:name/resume', async (req: Request, res: Response) => { + const name = req.params.name as string; + const command = commandCenter.createCommand({ + type: 'resume_voyager', targets: [name], source: 'dashboard', + }); + await commandCenter.dispatchCommand(command); + if (command.status === 'failed') { + res.status(422).json({ success: false, error: command.error?.message }); + return; + } + res.json({ success: true }); + }); + + // Stop movement + app.post('/api/bots/:name/stop', async (req: Request, res: Response) => { + const name = req.params.name as string; + const command = commandCenter.createCommand({ + type: 'stop_movement', targets: [name], source: 'dashboard', + }); + await commandCenter.dispatchCommand(command); + if (command.status === 'failed') { + res.status(422).json({ success: false, error: command.error?.message }); + return; + } + res.json({ success: true }); + }); + + // Follow player + app.post('/api/bots/:name/follow', async (req: Request, res: Response) => { + const name = req.params.name as string; + const { playerName } = req.body; + if (!playerName) { + res.status(400).json({ error: 'playerName is required' }); + return; + } + const command = commandCenter.createCommand({ + type: 'follow_player', targets: [name], source: 'dashboard', + params: { playerName }, + }); + await commandCenter.dispatchCommand(command); + if (command.status === 'failed') { + res.status(422).json({ success: false, error: command.error?.message }); + return; + } + res.json({ success: true }); + }); + + // Walk to coordinates + app.post('/api/bots/:name/walkto', async (req: Request, res: Response) => { + const name = req.params.name as string; + const { x, y, z } = req.body; + if (x == null || y == null || z == null) { + res.status(400).json({ error: 'x, y, z are required' }); + return; + } + const command = commandCenter.createCommand({ + type: 'walk_to_coords', targets: [name], source: 'dashboard', + params: { x, y, z }, + }); + await commandCenter.dispatchCommand(command); + if (command.status === 'failed') { + res.status(422).json({ success: false, error: command.error?.message }); + return; + } + res.json({ success: true }); + }); + + // ═══════════════════════════════════════ + // CONTROL PLATFORM - MISSION ENDPOINTS + // ═══════════════════════════════════════ + + // Start mission (transitions queued → running and triggers executor) + app.post('/api/missions/:id/start', async (req: Request, res: Response) => { + try { + const mission = await missionManager.startMission(req.params.id as string); + if (!mission) { + res.status(404).json({ error: 'Mission not found or cannot be started' }); + return; + } + res.json({ mission }); + } catch (err: any) { + res.status(500).json({ error: err.message }); + } + }); + + // Create mission + app.post('/api/missions', (req: Request, res: Response) => { + const { type, title, description, assigneeType, assigneeIds, priority, source, steps, linkedCommandIds } = req.body; + if (!type || !title || !assigneeType || !assigneeIds?.length) { + res.status(400).json({ error: 'type, title, assigneeType, and assigneeIds are required' }); + return; + } + const mission = missionManager.createMission({ + type, title, description, assigneeType, assigneeIds, priority, source, steps, linkedCommandIds, + }); + res.status(201).json({ mission }); + }); + + // List missions + app.get('/api/missions', (req: Request, res: Response) => { + const filters = { + bot: req.query.bot ? String(req.query.bot) : undefined, + squad: req.query.squad ? String(req.query.squad) : undefined, + status: req.query.status ? String(req.query.status) as any : undefined, + limit: req.query.limit ? parseInt(String(req.query.limit)) : undefined, + }; + const missions = missionManager.getMissions(filters); + res.json({ missions }); + }); + + // Get single mission + app.get('/api/missions/:id', (req: Request, res: Response) => { + const mission = missionManager.getMission(req.params.id as string); + if (!mission) { + res.status(404).json({ error: 'Mission not found' }); + return; + } + res.json({ mission }); + }); + + // Pause mission + app.post('/api/missions/:id/pause', (req: Request, res: Response) => { + const mission = missionManager.pauseMission(req.params.id as string); + if (!mission) { + res.status(404).json({ error: 'Mission not found or cannot be paused' }); + return; + } + res.json({ mission }); + }); + + // Resume mission + app.post('/api/missions/:id/resume', (req: Request, res: Response) => { + const mission = missionManager.resumeMission(req.params.id as string); + if (!mission) { + res.status(404).json({ error: 'Mission not found or cannot be resumed' }); + return; + } + res.json({ mission }); + }); + + // Cancel mission + app.post('/api/missions/:id/cancel', (req: Request, res: Response) => { + const mission = missionManager.cancelMission(req.params.id as string); + if (!mission) { + res.status(404).json({ error: 'Mission not found or cannot be cancelled' }); + return; + } + res.json({ mission }); + }); + + // Retry mission + app.post('/api/missions/:id/retry', (req: Request, res: Response) => { + const mission = missionManager.retryMission(req.params.id as string); + if (!mission) { + res.status(404).json({ error: 'Mission not found or cannot be retried' }); + return; + } + res.json({ mission }); + }); + + // Get bot's combined mission queue (MissionManager + VoyagerLoop) + app.get('/api/bots/:name/mission-queue', (req: Request, res: Response) => { + const name = req.params.name as string; + const bot = botManager.getBot(name); + if (!bot) { + res.status(404).json({ error: 'Bot not found' }); + return; + } + const missions = missionManager.getBotMissionQueue(name); + const voyager = bot.getVoyagerLoop(); + const voyagerTasks = voyager ? voyager.getQueuedTasksDetailed() : []; + res.json({ missions, voyagerTasks }); + }); + + // Reorder/remove from bot mission queue + app.patch('/api/bots/:name/mission-queue', (req: Request, res: Response) => { + const name = req.params.name as string; + const bot = botManager.getBot(name); + if (!bot) { + res.status(404).json({ error: 'Bot not found' }); + return; + } + const voyager = bot.getVoyagerLoop(); + if (!voyager) { + res.status(400).json({ error: 'Bot is not in codegen mode' }); + return; + } + const { action, missionId, index, fromIndex, toIndex } = req.body; + let success = false; + if (action === 'remove' || action === 'reorder' || action === 'clear') { + success = missionManager.updateBotMissionQueue( + name, + action, + missionId, + typeof fromIndex === 'number' && typeof toIndex === 'number' + ? { from: fromIndex, to: toIndex } + : undefined, + ); + + if (success) { + res.json({ success: true }); + return; + } + } + + switch (action) { + case 'remove': + success = typeof index === 'number' ? voyager.removeQueuedTask(index) : false; + break; + case 'reorder': + success = typeof fromIndex === 'number' && typeof toIndex === 'number' + ? voyager.reorderQueue(fromIndex, toIndex) + : false; + break; + case 'clear': + voyager.clearQueue(); + success = true; + break; + default: + res.status(400).json({ error: 'action must be remove, reorder, or clear' }); + return; + } + res.json({ success }); + }); + + // ═══════════════════════════════════════ + // CONTROL PLATFORM - WORLD PLANNING + // ═══════════════════════════════════════ + + app.get('/api/markers', (_req: Request, res: Response) => { + res.json({ markers: markerStore.getMarkers() }); + }); + app.post('/api/markers', (req: Request, res: Response) => { + const { name, kind, position } = req.body; + if (!name || !kind || !position) { res.status(400).json({ error: 'name, kind, and position are required' }); return; } + const marker = markerStore.createMarker({ name, kind, position, tags: req.body.tags, notes: req.body.notes }); + res.status(201).json({ marker }); + }); + app.patch('/api/markers/:id', (req: Request, res: Response) => { + const updated = markerStore.updateMarker(req.params.id as string, req.body); + if (!updated) { res.status(404).json({ error: 'Marker not found' }); return; } + res.json({ marker: updated }); + }); + app.delete('/api/markers/:id', (req: Request, res: Response) => { + const deleted = markerStore.deleteMarker(req.params.id as string); + if (!deleted) { res.status(404).json({ error: 'Marker not found' }); return; } + res.json({ success: true }); + }); + + app.get('/api/zones', (_req: Request, res: Response) => { + res.json({ zones: markerStore.getZones() }); + }); + app.post('/api/zones', (req: Request, res: Response) => { + const { name, mode, shape } = req.body; + if (!name || !mode || !shape) { res.status(400).json({ error: 'name, mode, and shape are required' }); return; } + const zone = markerStore.createZone(req.body); + res.status(201).json({ zone }); + }); + app.patch('/api/zones/:id', (req: Request, res: Response) => { + const updated = markerStore.updateZone(req.params.id as string, req.body); + if (!updated) { res.status(404).json({ error: 'Zone not found' }); return; } + res.json({ zone: updated }); + }); + app.delete('/api/zones/:id', (req: Request, res: Response) => { + const deleted = markerStore.deleteZone(req.params.id as string); + if (!deleted) { res.status(404).json({ error: 'Zone not found' }); return; } + res.json({ success: true }); + }); + + app.get('/api/routes', (_req: Request, res: Response) => { + res.json({ routes: markerStore.getRoutes() }); + }); + app.post('/api/routes', (req: Request, res: Response) => { + const { name, waypointIds, loop } = req.body; + if (!name || !Array.isArray(waypointIds)) { res.status(400).json({ error: 'name and waypointIds are required' }); return; } + const route = markerStore.createRoute({ name, waypointIds, loop: loop ?? false }); + res.status(201).json({ route }); + }); + app.patch('/api/routes/:id', (req: Request, res: Response) => { + const updated = markerStore.updateRoute(req.params.id as string, req.body); + if (!updated) { res.status(404).json({ error: 'Route not found' }); return; } + res.json({ route: updated }); + }); + app.delete('/api/routes/:id', (req: Request, res: Response) => { + const deleted = markerStore.deleteRoute(req.params.id as string); + if (!deleted) { res.status(404).json({ error: 'Route not found' }); return; } + res.json({ success: true }); + }); + + // ═══════════════════════════════════════ + // CONTROL PLATFORM - SQUAD ENDPOINTS + // ═══════════════════════════════════════ + + app.get('/api/squads', (_req: Request, res: Response) => { + res.json({ squads: squadManager.getSquads() }); + }); + app.post('/api/squads', (req: Request, res: Response) => { + const { name, botNames, defaultRole, homeMarkerId } = req.body; + if (!name) { res.status(400).json({ error: 'name is required' }); return; } + const squad = squadManager.createSquad({ name, botNames: botNames ?? [], defaultRole, homeMarkerId }); + res.status(201).json({ squad }); + }); + app.get('/api/squads/:id', (req: Request, res: Response) => { + const squad = squadManager.getSquad(req.params.id as string); + if (!squad) { res.status(404).json({ error: 'Squad not found' }); return; } + res.json({ squad }); + }); + app.patch('/api/squads/:id', (req: Request, res: Response) => { + const squad = squadManager.updateSquad(req.params.id as string, req.body); + if (!squad) { res.status(404).json({ error: 'Squad not found' }); return; } + res.json({ squad }); + }); + app.delete('/api/squads/:id', (req: Request, res: Response) => { + const deleted = squadManager.deleteSquad(req.params.id as string); + if (!deleted) { res.status(404).json({ error: 'Squad not found' }); return; } + res.json({ success: true }); + }); + app.post('/api/squads/:id/members', (req: Request, res: Response) => { + const { botName } = req.body; + if (!botName) { res.status(400).json({ error: 'botName is required' }); return; } + const added = squadManager.addBotToSquad(req.params.id as string, botName); + if (!added) { res.status(404).json({ error: 'Squad not found' }); return; } + res.json({ success: true }); + }); + app.delete('/api/squads/:id/members/:botName', (req: Request, res: Response) => { + const removed = squadManager.removeBotFromSquad(req.params.id as string, req.params.botName as string); + if (!removed) { res.status(404).json({ error: 'Squad not found or bot not a member' }); return; } + res.json({ success: true }); + }); + app.post('/api/squads/:id/commands', async (req: Request, res: Response) => { + const squad = squadManager.getSquad(req.params.id as string); + if (!squad) { res.status(404).json({ error: 'Squad not found' }); return; } + + const { type, payload, priority, source } = req.body; + if (!type) { res.status(400).json({ error: 'type is required' }); return; } + if (!Array.isArray(squad.botNames) || squad.botNames.length === 0) { + res.status(400).json({ error: 'Squad has no members' }); + return; + } + + try { + const command = commandCenter.createCommand({ + type, + scope: 'squad', + priority: priority === 'urgent' ? 'critical' : (priority ?? 'normal'), + source: source ?? 'dashboard', + targets: squad.botNames, + params: payload ?? {}, + }); + const result = await commandCenter.dispatchCommand(command); + res.json({ command: result, squad }); + } catch (err: any) { + logger.error({ err, squadId: squad.id, type }, 'Squad command dispatch failed'); + res.status(500).json({ error: err.message ?? 'Failed to dispatch squad command' }); + } + }); + app.post('/api/squads/:id/missions', (req: Request, res: Response) => { + const squad = squadManager.getSquad(req.params.id as string); + if (!squad) { res.status(404).json({ error: 'Squad not found' }); return; } + + const { type, title, description, priority, steps, linkedCommandIds, source } = req.body; + if (!type || !title) { + res.status(400).json({ error: 'type and title are required' }); + return; + } + + try { + const mission = missionManager.createMission({ + type, + title, + description, + assigneeType: 'squad', + assigneeIds: [squad.id], + priority: priority ?? 'normal', + source: source ?? 'dashboard', + steps, + linkedCommandIds, + }); + squadManager.updateSquad(squad.id, { activeMissionId: mission.id }); + res.status(201).json({ mission, squadId: squad.id }); + } catch (err: any) { + logger.error({ err, squadId: squad.id, type }, 'Squad mission creation failed'); + res.status(500).json({ error: err.message ?? 'Failed to create squad mission' }); + } + }); + + // ═══════════════════════════════════════ + // CONTROL PLATFORM - ROLE ENDPOINTS + // ═══════════════════════════════════════ + + app.get('/api/roles', (_req: Request, res: Response) => { + res.json({ assignments: roleManager.getAssignments(), overrides: roleManager.getOverrides?.() ?? [], approvalRequests: roleManager.getApprovalRequests?.() ?? [] }); + }); + app.post('/api/roles/assignments', (req: Request, res: Response) => { + const { botName, role, autonomyLevel, homeMarkerId, allowedZoneIds, preferredMissionTypes, interruptPolicy, loadoutPolicy } = req.body; + if (!botName || !role || !autonomyLevel) { res.status(400).json({ error: 'botName, role, and autonomyLevel are required' }); return; } + if (!botManager.getBot(botName)) { res.status(404).json({ error: `Bot "${botName}" not found` }); return; } + try { + const assignment = roleManager.createAssignment({ botName, role, autonomyLevel, homeMarkerId, allowedZoneIds, preferredMissionTypes, interruptPolicy, loadoutPolicy }); + res.status(201).json({ assignment }); + } catch (err: any) { res.status(400).json({ error: err.message }); } + }); + app.get('/api/roles/assignments/:id', (req: Request, res: Response) => { + const assignment = roleManager.getAssignment(req.params.id as string); + if (!assignment) { res.status(404).json({ error: 'Assignment not found' }); return; } + res.json({ assignment }); + }); + app.patch('/api/roles/assignments/:id', (req: Request, res: Response) => { + try { + const updated = roleManager.updateAssignment(req.params.id as string, req.body); + if (!updated) { res.status(404).json({ error: 'Assignment not found' }); return; } + res.json({ assignment: updated }); + } catch (err: any) { res.status(400).json({ error: err.message }); } + }); + app.delete('/api/roles/assignments/:id', (req: Request, res: Response) => { + const deleted = roleManager.deleteAssignment(req.params.id as string); + if (!deleted) { res.status(404).json({ error: 'Assignment not found' }); return; } + res.json({ success: true }); + }); + + // Override endpoints + app.get('/api/bots/:name/override', (req: Request, res: Response) => { + const override = roleManager.getOverride?.(req.params.name as string); + res.json({ override: override || null }); + }); + app.delete('/api/bots/:name/override', (req: Request, res: Response) => { + roleManager.clearOverride?.(req.params.name as string); + res.json({ success: true }); + }); + app.post('/api/roles/approvals/:id/approve', (req: Request, res: Response) => { + const result = roleManager.approveApprovalRequest?.(req.params.id as string, req.body?.decidedBy, req.body?.decisionNote); + if (!result) { res.status(404).json({ error: 'Approval request not found or no longer valid' }); return; } + res.json(result); + }); + app.post('/api/roles/approvals/:id/reject', (req: Request, res: Response) => { + const result = roleManager.rejectApprovalRequest?.(req.params.id as string, req.body?.decidedBy, req.body?.decisionNote); + if (!result) { res.status(404).json({ error: 'Approval request not found or no longer valid' }); return; } + res.json({ approvalRequest: result }); + }); + + // ═══════════════════════════════════════ + // BUILD & SCHEMATIC ENDPOINTS + // ═══════════════════════════════════════ + + app.get('/api/schematics', async (_req: Request, res: Response) => { + try { + const schematics = await buildCoordinator.listSchematics(); + res.json({ schematics }); + } catch (err: any) { + logger.error({ err }, 'Failed to list schematics'); + res.status(500).json({ error: 'Failed to list schematics' }); + } + }); + + app.get('/api/schematics/:filename', async (req: Request, res: Response) => { + try { + const filename = req.params.filename as string; + if (/[\/\\]|\.\./.test(filename)) { + res.status(400).json({ error: 'Invalid filename: path traversal characters not allowed' }); return; + } + const info = await buildCoordinator.getSchematicInfoAsync(filename); + if (!info) { res.status(404).json({ error: 'Schematic not found' }); return; } + res.json({ schematic: info }); + } catch (err: any) { + logger.error({ err, filename: req.params.filename }, 'Failed to get schematic info'); + res.status(500).json({ error: 'Failed to get schematic info' }); + } + }); + + app.post('/api/builds', async (req: Request, res: Response) => { + const { schematicFile, origin, botNames, cleanupBotNames, fillFoundation, snapToGround } = req.body; + if (!schematicFile || !origin || !botNames || !Array.isArray(botNames) || botNames.length === 0) { + res.status(400).json({ error: 'schematicFile, origin {x,y,z}, and botNames[] are required' }); return; + } + if (/[\/\\]|\.\./.test(schematicFile)) { + res.status(400).json({ error: 'Invalid schematicFile: path traversal characters not allowed' }); return; + } + if (typeof origin.x !== 'number' || typeof origin.y !== 'number' || typeof origin.z !== 'number') { + res.status(400).json({ error: 'origin must have numeric x, y, z fields' }); return; + } + try { + const build = await buildCoordinator.startBuild(schematicFile, origin, botNames, { + cleanupBotNames: Array.isArray(cleanupBotNames) ? cleanupBotNames : undefined, + fillFoundation: typeof fillFoundation === 'boolean' ? fillFoundation : undefined, + snapToGround: typeof snapToGround === 'boolean' ? snapToGround : undefined, + }); + res.status(201).json({ success: true, build }); + } catch (err: any) { + logger.error({ err }, 'Failed to start build'); + res.status(400).json({ error: err.message }); + } + }); + + app.get('/api/builds', (_req: Request, res: Response) => { + const jobs = buildCoordinator.getAllBuildJobs(); + res.json({ builds: jobs }); + }); + + app.get('/api/builds/:id', (req: Request, res: Response) => { + const job = buildCoordinator.getBuildJob(req.params.id as string); + if (!job) { res.status(404).json({ error: 'Build job not found' }); return; } + res.json({ build: job }); + }); + + app.post('/api/builds/:id/cancel', (req: Request, res: Response) => { + const success = buildCoordinator.cancelBuild(req.params.id as string); + if (!success) { res.status(404).json({ error: 'Build not found or already finished' }); return; } + res.json({ success: true }); + }); + + app.post('/api/builds/:id/pause', (req: Request, res: Response) => { + const success = buildCoordinator.pauseBuild(req.params.id as string); + if (!success) { res.status(404).json({ error: 'Build not found or not running' }); return; } + res.json({ success: true }); + }); + + app.post('/api/builds/:id/resume', (req: Request, res: Response) => { + const success = buildCoordinator.resumeBuild(req.params.id as string); + if (!success) { res.status(404).json({ error: 'Build not found or not paused' }); return; } + res.json({ success: true }); + }); + + // ═══════════════════════════════════════ + // SUPPLY CHAIN ENDPOINTS + // ═══════════════════════════════════════ + + app.get('/api/chain-templates', (_req: Request, res: Response) => { + const templates = chainCoordinator.getTemplates(); + res.json({ templates }); + }); + + app.get('/api/chains', (_req: Request, res: Response) => { + const chains = chainCoordinator.getAllChains(); + res.json({ chains }); + }); + + app.get('/api/chains/:id', (req: Request, res: Response) => { + const chain = chainCoordinator.getChain(req.params.id as string); + if (!chain) { res.status(404).json({ error: 'Supply chain not found' }); return; } + res.json({ chain }); + }); + + app.post('/api/chains', (req: Request, res: Response) => { + const { name, description, templateId, stages, loop, botAssignments, chestLocations } = req.body; + if (!name) { res.status(400).json({ error: 'name is required' }); return; } + try { + const chain = chainCoordinator.createChain({ name, description, templateId, stages, loop, botAssignments, chestLocations }); + res.status(201).json({ chain }); + } catch (err: any) { + logger.error({ err }, 'Failed to create supply chain'); + res.status(400).json({ error: err.message }); + } + }); + + app.delete('/api/chains/:id', (req: Request, res: Response) => { + const success = chainCoordinator.deleteChain(req.params.id as string); + if (!success) { res.status(404).json({ error: 'Supply chain not found' }); return; } + res.json({ success: true }); + }); + + app.post('/api/chains/:id/start', (req: Request, res: Response) => { + const success = chainCoordinator.startChain(req.params.id as string); + if (!success) { res.status(404).json({ error: 'Supply chain not found or already running' }); return; } + res.json({ success: true }); + }); + + app.post('/api/chains/:id/pause', (req: Request, res: Response) => { + const success = chainCoordinator.pauseChain(req.params.id as string); + if (!success) { res.status(404).json({ error: 'Supply chain not found or not running' }); return; } + res.json({ success: true }); + }); + + app.post('/api/chains/:id/cancel', (req: Request, res: Response) => { + const success = chainCoordinator.cancelChain(req.params.id as string); + if (!success) { res.status(404).json({ error: 'Supply chain not found' }); return; } + res.json({ success: true }); + }); + + // ═══════════════════════════════════════ + // CONTROL PLATFORM - COMMANDER ENDPOINTS + // ═══════════════════════════════════════ + + app.post('/api/commander/parse', async (req: Request, res: Response) => { + const { input } = req.body; + if (!input || typeof input !== 'string' || !input.trim()) { + res.status(400).json({ error: 'input is required' }); return; + } + try { + const plan = await commanderService.parse(input.trim()); + const event = eventLog.push({ + type: 'commander:parse', + botName: 'system', + description: `Commander parsed input: ${input.trim().slice(0, 80)}`, + metadata: { planId: plan.id, confidence: plan.confidence, warnings: plan.warnings.length }, + }); + io.emit('activity', event); + res.json({ plan }); + } catch (err: any) { + logger.error({ err }, 'Commander parse failed'); + res.status(500).json({ error: err.message }); + } + }); + + app.get('/api/commander/history', (req: Request, res: Response) => { + const limit = Number(req.query.limit ?? 20); + res.json({ entries: commanderService.getHistory(Number.isFinite(limit) ? limit : 20) }); + }); + + app.post('/api/commander/execute', async (req: Request, res: Response) => { + const { planId } = req.body; + if (!planId) { res.status(400).json({ error: 'planId is required' }); return; } + const plan = commanderService.getPlan(planId); + if (!plan) { res.status(404).json({ error: 'Plan not found' }); return; } + try { + const result = await commanderService.execute(planId); + if (result) { + const event = eventLog.push({ + type: 'commander:execute', + botName: 'system', + description: `Commander executed plan ${planId}`, + metadata: { planId, commands: result.commands.length, missions: result.missions.length }, + }); + io.emit('activity', event); + } + res.json(result); + } catch (err: any) { + logger.error({ err }, 'Commander execute failed'); + res.status(500).json({ error: err.message }); + } + }); + + // ═══════════════════════════════════════ + // TERRAIN SCANNING ENDPOINTS + // ═══════════════════════════════════════ + + /** Scan downward from y=200 to y=-64 to find the first non-air surface block. */ + function findNearestConnectedBot(x: number, z: number): any | null { + const connected = botManager.getAllBots().filter((b) => b.bot?.entity); + if (connected.length === 0) return null; + let best = connected[0]; + let bestDist = Infinity; + for (const b of connected) { + const pos = b.bot!.entity.position; + const dx = pos.x - x; + const dz = pos.z - z; + const dist = dx * dx + dz * dz; + if (dist < bestDist) { + bestDist = dist; + best = b; + } + } + return best.bot; + } + + function findSurfaceBlock(bot: any, x: number, z: number): { y: number; name: string } | null { + for (let y = 200; y >= -64; y--) { + const block = bot.blockAt(new Vec3(x, y, z)); + if (block && block.name !== 'air' && block.name !== 'cave_air' && block.name !== 'void_air') { + return { y, name: block.name }; + } + } + return null; + } + + // GET /api/terrain?cx=100&cz=200&radius=64&step=2 + app.get('/api/terrain', (_req: Request, res: Response) => { + const cx = parseInt(String(_req.query.cx ?? '0'), 10); + const cz = parseInt(String(_req.query.cz ?? '0'), 10); + const bot = findNearestConnectedBot(cx, cz); + if (!bot) { + res.status(503).json({ error: 'No bot connected' }); + return; + } + let radius = parseInt(String(_req.query.radius ?? '64'), 10); + let step = parseInt(String(_req.query.step ?? '2'), 10); + + // Clamp for performance + if (radius > 128) radius = 128; + if (step < 1) step = 1; + + const size = Math.floor(2 * radius / step) + 1; + const blocks: string[] = []; + + for (let z = cz - radius; z <= cz + radius; z += step) { + for (let x = cx - radius; x <= cx + radius; x += step) { + const surface = findSurfaceBlock(bot, x, z); + blocks.push(surface ? surface.name : 'unknown'); + } + } + + res.json({ cx, cz, radius, step, size, blocks }); + }); + + // GET /api/terrain/height?x=100&z=200 + app.get('/api/terrain/height', (_req: Request, res: Response) => { + const x = parseInt(String(_req.query.x ?? '0'), 10); + const z = parseInt(String(_req.query.z ?? '0'), 10); + const bot = findNearestConnectedBot(x, z); + if (!bot) { + res.status(503).json({ error: 'No bot connected' }); + return; + } + + const surface = findSurfaceBlock(bot, x, z); + if (surface) { + res.json({ x, z, y: surface.y, block: surface.name }); + } else { + res.json({ x, z, y: null, block: 'unknown' }); + } + }); + + // POST /api/terrain/height — batch mode + app.post('/api/terrain/height', (req: Request, res: Response) => { + const positions: Array<{ x: number; z: number }> = req.body?.positions; + if (!Array.isArray(positions)) { + res.status(400).json({ error: 'positions array is required' }); + return; + } + + const heights = positions.map((p) => { + const x = Number(p.x) || 0; + const z = Number(p.z) || 0; + const bot = findNearestConnectedBot(x, z); + if (!bot) return { x, z, y: null as number | null, block: 'unknown' }; + const surface = findSurfaceBlock(bot, x, z); + return surface + ? { x, z, y: surface.y, block: surface.name } + : { x, z, y: null as number | null, block: 'unknown' }; + }); + + res.json({ heights }); + }); + + // POST /api/terrain/heightmap — rectangular area heightmap + app.post('/api/terrain/heightmap', (req: Request, res: Response) => { + const { minX, maxX, minZ, maxZ } = req.body ?? {}; + if (minX == null || maxX == null || minZ == null || maxZ == null) { + res.status(400).json({ error: 'minX, maxX, minZ, maxZ are required' }); + return; + } + + let step = parseInt(String(req.body.step ?? '1'), 10); + if (step < 1) step = 1; + + const heights: number[][] = []; + const blocks: string[][] = []; + + const centerX = Math.round((minX + maxX) / 2); + const centerZ = Math.round((minZ + maxZ) / 2); + const bot = findNearestConnectedBot(centerX, centerZ); + if (!bot) { + res.status(503).json({ error: 'No bot connected' }); + return; + } + + for (let z = minZ; z <= maxZ; z += step) { + const heightRow: number[] = []; + const blockRow: string[] = []; + for (let x = minX; x <= maxX; x += step) { + const surface = findSurfaceBlock(bot, x, z); + if (surface) { + heightRow.push(surface.y); + blockRow.push(surface.name); + } else { + heightRow.push(-64); + blockRow.push('unknown'); + } + } + heights.push(heightRow); + blocks.push(blockRow); + } + + res.json({ heights, blocks }); + }); + + return { app, httpServer, io, eventLog, commandCenter, missionManager, buildCoordinator, chainCoordinator, markerStore, squadManager, roleManager, commanderService }; } diff --git a/src/server/socketEvents.ts b/src/server/socketEvents.ts index 0a5b490..c05ada7 100644 --- a/src/server/socketEvents.ts +++ b/src/server/socketEvents.ts @@ -6,20 +6,97 @@ import { logger } from '../util/logger'; /** * Sets up real-time event broadcasting from bot instances to connected dashboard clients. - * Polls bot state and emits changes via Socket.IO. + * + * Primary: event-driven updates emitted directly from BotInstance via EventEmitter. + * Fallback: polling loop every 10 seconds catches any missed changes. */ export function setupSocketEvents( botManager: BotManager, io: SocketIOServer, eventLog: EventLog ): void { - // Track previous state to detect changes + // Track previous state to detect changes (used by both event-driven and fallback polling) const prevPositions = new Map(); const prevHealth = new Map(); const prevStates = new Map(); const prevInventory = new Map(); - // Poll bot state every 2 seconds and emit changes + // Track which bots we've attached event listeners to + const subscribedBots = new Set(); + + /** + * Subscribe to event-driven updates from a BotInstance. + * Forwards BotInstance EventEmitter events to Socket.IO. + */ + function subscribeToBotEvents(bot: BotInstance): void { + if (subscribedBots.has(bot.name)) return; + subscribedBots.add(bot.name); + + bot.on('positionChanged', (data: { bot: string; x: number; y: number; z: number }) => { + const posKey = `${data.x},${data.y},${data.z}`; + if (prevPositions.get(data.bot) !== posKey) { + prevPositions.set(data.bot, posKey); + io.emit('bot:position', data); + } + }); + + bot.on('healthChanged', (data: { bot: string; health: number; food: number }) => { + const healthKey = `${data.health}:${data.food}`; + if (prevHealth.get(data.bot) !== healthKey) { + prevHealth.set(data.bot, healthKey); + io.emit('bot:health', data); + } + }); + + bot.on('stateChanged', (data: { bot: string; state: string; previousState: string }) => { + if (prevStates.get(data.bot) !== data.state) { + prevStates.set(data.bot, data.state); + io.emit('bot:state', data); + + eventLog.push({ + type: 'bot:state', + botName: data.bot, + description: `${data.bot} state: ${data.previousState ?? '?'} → ${data.state}`, + metadata: { from: data.previousState, to: data.state }, + }); + } + }); + + bot.on('inventoryChanged', (data: { bot: string; items: Array<{ name: string; count: number; slot: number }> }) => { + const invKey = data.items.map((i) => `${i.name}:${i.count}`).sort().join(','); + if (prevInventory.get(data.bot) !== invKey) { + prevInventory.set(data.bot, invKey); + io.emit('bot:inventory', data); + } + }); + + // Player join/leave events + if (bot.bot) { + bot.bot.on('playerJoined', (player: any) => { + if (player.username) io.emit('player:join', { name: player.username }); + }); + bot.bot.on('playerLeft', (player: any) => { + if (player.username) io.emit('player:leave', { name: player.username }); + }); + } + + logger.debug({ bot: bot.name }, 'Subscribed to event-driven socket updates'); + } + + // Subscribe to all currently existing bots and check for new ones periodically + function subscribeAllBots(): void { + for (const bot of botManager.getAllBots()) { + subscribeToBotEvents(bot); + } + } + + // Initial subscription + subscribeAllBots(); + + // Check for new bots every 5 seconds and subscribe them + setInterval(subscribeAllBots, 5000); + + // Fallback polling every 10 seconds — safety net in case events are missed setInterval(() => { const bots = botManager.getAllBots(); for (const bot of bots) { @@ -87,8 +164,22 @@ export function setupSocketEvents( }); } } catch { /* ignore */ } + + // Player positions (only for players with entities in range) + try { + for (const p of Object.values(bot.bot.players) as any[]) { + if (p.username && p.entity) { + io.emit('player:position', { + name: p.username, + x: Math.round(p.entity.position.x), + y: Math.round(p.entity.position.y), + z: Math.round(p.entity.position.z), + }); + } + } + } catch { /* ignore */ } } - }, 2000); + }, 10000); // World time broadcast every 30 seconds setInterval(() => { @@ -119,9 +210,10 @@ export function setupSocketEvents( prevHealth.delete(name); prevStates.delete(name); prevInventory.delete(name); + subscribedBots.delete(name); } } }, 60000); - logger.info('Socket.IO event broadcasting initialized'); + logger.info('Socket.IO event broadcasting initialized (event-driven + 10s fallback polling)'); } diff --git a/src/social/BotComms.ts b/src/social/BotComms.ts new file mode 100644 index 0000000..5dc46bc --- /dev/null +++ b/src/social/BotComms.ts @@ -0,0 +1,82 @@ +import crypto from 'crypto'; +import { logger } from '../util/logger'; + +export interface BotMessage { + id: string; + from: string; + to: string; + content: string; + type: 'chat' | 'request' | 'inform' | 'greeting'; + timestamp: number; + read: boolean; +} + +export class BotComms { + private queues: Map = new Map(); + private listeners: Map void> = new Map(); + + sendMessage(from: string, to: string, content: string, type: BotMessage['type'] = 'chat'): BotMessage { + const msg: BotMessage = { + id: crypto.randomUUID(), + from: from.toLowerCase(), + to: to.toLowerCase(), + content, + type, + timestamp: Date.now(), + read: false, + }; + + const toKey = to.toLowerCase(); + if (!this.queues.has(toKey)) this.queues.set(toKey, []); + this.queues.get(toKey)!.push(msg); + + // Also store in sender's queue for history + const fromKey = from.toLowerCase(); + if (!this.queues.has(fromKey)) this.queues.set(fromKey, []); + this.queues.get(fromKey)!.push({ ...msg, read: true }); + + const listener = this.listeners.get(toKey); + if (listener) { + try { + listener(msg); + } catch (err) { + logger.error({ err, to }, 'BotComms listener error'); + } + } + + logger.debug({ from, to, type }, 'Bot message sent'); + return msg; + } + + getUnread(botName: string): BotMessage[] { + const key = botName.toLowerCase(); + const queue = this.queues.get(key) ?? []; + const unread = queue.filter(m => !m.read && m.to === key); + for (const m of unread) { + m.read = true; + } + return unread; + } + + registerListener(botName: string, callback: (msg: BotMessage) => void): void { + this.listeners.set(botName.toLowerCase(), callback); + } + + unregisterListener(botName: string): void { + this.listeners.delete(botName.toLowerCase()); + } + + getRecentMessages(botName: string, limit = 10): BotMessage[] { + const key = botName.toLowerCase(); + const queue = this.queues.get(key) ?? []; + return queue + .sort((a, b) => b.timestamp - a.timestamp) + .slice(0, limit); + } + + clearBot(botName: string): void { + const key = botName.toLowerCase(); + this.queues.delete(key); + this.listeners.delete(key); + } +} diff --git a/src/social/SocialMemory.ts b/src/social/SocialMemory.ts new file mode 100644 index 0000000..dde04b7 --- /dev/null +++ b/src/social/SocialMemory.ts @@ -0,0 +1,327 @@ +import fs from 'fs'; +import path from 'path'; +import crypto from 'crypto'; +import { logger } from '../util/logger'; + +export interface Memory { + id: string; + botName: string; + type: 'social' | 'event' | 'reflection' | 'observation'; + content: string; + subjects: string[]; + importance: number; + timestamp: number; + decay: number; +} + +export interface Reflection { + id: string; + botName: string; + content: string; + basedOn: string[]; + timestamp: number; +} + +export interface BotEmotionalState { + mood: 'happy' | 'neutral' | 'annoyed' | 'scared' | 'excited' | 'lonely'; + energy: number; + sociability: number; + lastUpdated: number; +} + +interface SocialMemoryStore { + memories: Memory[]; + reflections: Reflection[]; + emotionalStates: { [botName: string]: BotEmotionalState }; +} + +const DECAY_INTERVAL_MS = 5 * 60 * 1000; +const DECAY_AMOUNT = 0.02; +const DECAY_THRESHOLD = 0.1; + +export class SocialMemory { + private store: SocialMemoryStore = { memories: [], reflections: [], emotionalStates: {} }; + private savePath: string; + private decayTimer: ReturnType; + private _saveTimer: ReturnType | null = null; + + constructor() { + this.savePath = path.join('data', 'social_memory.json'); + this.load(); + this.decayTimer = setInterval(() => this.decayMemories(), DECAY_INTERVAL_MS); + } + + addMemory(botName: string, type: Memory['type'], content: string, subjects: string[], importance: number): Memory { + const memory: Memory = { + id: crypto.randomUUID(), + botName: botName.toLowerCase(), + type, + content, + subjects: subjects.map(s => s.toLowerCase()), + importance: Math.max(1, Math.min(10, importance)), + timestamp: Date.now(), + decay: 1.0, + }; + this.store.memories.push(memory); + this.save(); + logger.debug({ botName, type, importance }, 'Social memory added'); + return memory; + } + + getRecentMemories(botName: string, limit = 10): Memory[] { + const key = botName.toLowerCase(); + return this.store.memories + .filter(m => m.botName === key) + .sort((a, b) => b.timestamp - a.timestamp) + .slice(0, limit); + } + + getMemoriesAbout(botName: string, subject: string, limit = 5): Memory[] { + const key = botName.toLowerCase(); + const subj = subject.toLowerCase(); + return this.store.memories + .filter(m => m.botName === key && m.subjects.includes(subj)) + .sort((a, b) => (b.importance * b.decay) - (a.importance * a.decay)) + .slice(0, limit); + } + + getRelevantMemories(botName: string, context: string, limit = 5): Memory[] { + const key = botName.toLowerCase(); + const words = context.toLowerCase().split(/\s+/).filter(w => w.length > 2); + if (words.length === 0) return []; + + const scored = this.store.memories + .filter(m => m.botName === key) + .map(m => { + const contentLower = m.content.toLowerCase(); + const matches = words.filter(w => contentLower.includes(w)).length; + const relevance = matches / words.length; + return { memory: m, score: relevance * m.importance * m.decay }; + }) + .filter(s => s.score > 0) + .sort((a, b) => b.score - a.score); + + return scored.slice(0, limit).map(s => s.memory); + } + + getReflections(botName: string, limit = 3): Reflection[] { + const key = botName.toLowerCase(); + return this.store.reflections + .filter(r => r.botName === key) + .sort((a, b) => b.timestamp - a.timestamp) + .slice(0, limit); + } + + reflect(botName: string, recentMemories: Memory[]): Reflection { + const key = botName.toLowerCase(); + + // Find common subjects + const subjectCounts: Record = {}; + for (const m of recentMemories) { + for (const s of m.subjects) { + subjectCounts[s] = (subjectCounts[s] || 0) + 1; + } + } + const topSubjects = Object.entries(subjectCounts) + .sort((a, b) => b[1] - a[1]) + .slice(0, 3) + .map(([name]) => name); + + // Identify patterns + const socialMemories = recentMemories.filter(m => m.type === 'social'); + const eventMemories = recentMemories.filter(m => m.type === 'event'); + const avgImportance = recentMemories.reduce((sum, m) => sum + m.importance, 0) / (recentMemories.length || 1); + const positiveWords = ['help', 'friend', 'gave', 'thank', 'nice', 'great', 'success', 'crafted', 'built']; + const negativeWords = ['hit', 'attack', 'fail', 'died', 'lost', 'broke', 'angry', 'stole']; + + let positiveCount = 0; + let negativeCount = 0; + for (const m of recentMemories) { + const lower = m.content.toLowerCase(); + if (positiveWords.some(w => lower.includes(w))) positiveCount++; + if (negativeWords.some(w => lower.includes(w))) negativeCount++; + } + + // Generate summary + let content: string; + if (topSubjects.length > 0 && socialMemories.length > eventMemories.length) { + const sentiment = positiveCount > negativeCount ? 'friendly' : negativeCount > positiveCount ? 'hostile' : 'neutral'; + if (sentiment === 'friendly') { + content = `${topSubjects[0]} has been very friendly lately, chatting often and ${avgImportance > 5 ? 'asking for help' : 'being supportive'}`; + } else if (sentiment === 'hostile') { + content = `${topSubjects[0]} has been causing trouble, I should be careful around them`; + } else { + content = `${topSubjects[0]} and I have had some interactions but nothing stands out`; + } + } else if (eventMemories.length > 0) { + const mainActivity = eventMemories[0].content.toLowerCase(); + if (negativeCount > positiveCount) { + content = `I've been busy but keep running into setbacks${topSubjects.length > 0 ? ` involving ${topSubjects[0]}` : ''}`; + } else { + content = `Things have been going well${topSubjects.length > 0 ? `, especially with ${topSubjects[0]}` : ''}`; + } + } else if (topSubjects.length >= 2) { + content = `${topSubjects[0]} and ${topSubjects[1]} haven't interacted much, we should coordinate more`; + } else { + content = `Not much has happened recently, things are quiet`; + } + + const reflection: Reflection = { + id: crypto.randomUUID(), + botName: key, + content, + basedOn: recentMemories.map(m => m.id), + timestamp: Date.now(), + }; + this.store.reflections.push(reflection); + this.save(); + logger.debug({ botName, content }, 'Reflection created'); + return reflection; + } + + getEmotionalState(botName: string): BotEmotionalState { + const key = botName.toLowerCase(); + return this.store.emotionalStates[key] ?? { + mood: 'neutral', + energy: 50, + sociability: 50, + lastUpdated: Date.now(), + }; + } + + updateEmotionalState(botName: string, event: string): void { + const key = botName.toLowerCase(); + const state = this.getEmotionalState(botName); + + switch (event) { + case 'positive_chat': + state.mood = state.mood === 'excited' ? 'excited' : 'happy'; + state.sociability = Math.min(100, state.sociability + 5); + break; + case 'negative_chat': + state.mood = state.mood === 'scared' ? 'scared' : 'annoyed'; + state.sociability = Math.max(0, state.sociability - 5); + break; + case 'death': + state.mood = 'scared'; + state.energy = Math.max(0, state.energy - 20); + break; + case 'task_success': + state.mood = state.mood === 'happy' ? 'excited' : 'happy'; + state.energy = Math.min(100, state.energy + 5); + break; + case 'task_failure': + state.mood = state.mood === 'scared' ? 'scared' : 'annoyed'; + state.energy = Math.max(0, state.energy - 5); + break; + case 'idle_long': + state.mood = 'lonely'; + state.sociability = Math.min(100, state.sociability + 10); + break; + case 'social_interaction': + state.mood = state.mood === 'excited' ? 'excited' : 'happy'; + state.energy = Math.min(100, state.energy + 3); + break; + } + + state.lastUpdated = Date.now(); + this.store.emotionalStates[key] = state; + this.save(); + } + + decayMemories(): void { + const before = this.store.memories.length; + for (const m of this.store.memories) { + m.decay -= DECAY_AMOUNT; + } + this.store.memories = this.store.memories.filter(m => m.decay >= DECAY_THRESHOLD); + const removed = before - this.store.memories.length; + if (removed > 0) { + logger.debug({ removed }, 'Decayed and removed memories'); + } + this.save(); + } + + buildMemoryContext(botName: string, nearbyPlayers: string[], limit = 8): string { + const recent = this.getRecentMemories(botName, limit); + const reflections = this.getReflections(botName, 3); + const emotional = this.getEmotionalState(botName); + const lines: string[] = []; + + if (recent.length > 0) { + lines.push('Recent memories:'); + const now = Date.now(); + for (const m of recent) { + const ago = formatTimeAgo(now - m.timestamp); + lines.push(`- [${ago}] ${m.content} (importance: ${m.importance})`); + } + } + + if (reflections.length > 0) { + lines.push(''); + lines.push('Reflections:'); + for (const r of reflections) { + lines.push(`- ${r.content}`); + } + } + + lines.push(''); + lines.push(`Current mood: ${emotional.mood} | Energy: ${emotional.energy} | Sociability: ${emotional.sociability}`); + + return lines.join('\n'); + } + + save(): void { + if (this._saveTimer) clearTimeout(this._saveTimer); + this._saveTimer = setTimeout(() => { + this._saveTimer = null; + this.writeAtomic(); + }, 2000); + } + + private writeAtomic(): void { + const dir = path.dirname(this.savePath); + if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); + const tmpPath = this.savePath + '.tmp'; + try { + fs.writeFileSync(tmpPath, JSON.stringify(this.store, null, 2)); + fs.renameSync(tmpPath, this.savePath); + } catch { + try { fs.writeFileSync(this.savePath, JSON.stringify(this.store, null, 2)); } catch { /* best effort */ } + try { fs.unlinkSync(tmpPath); } catch { /* ignore */ } + } + } + + shutdown(): void { + if (this._saveTimer) { + clearTimeout(this._saveTimer); + this._saveTimer = null; + this.writeAtomic(); + } + clearInterval(this.decayTimer); + } + + load(): void { + if (fs.existsSync(this.savePath)) { + try { + this.store = JSON.parse(fs.readFileSync(this.savePath, 'utf-8')); + if (!this.store.memories) this.store.memories = []; + if (!this.store.reflections) this.store.reflections = []; + if (!this.store.emotionalStates) this.store.emotionalStates = {}; + } catch { + logger.warn('Failed to load social_memory.json, starting fresh'); + } + } + } +} + +function formatTimeAgo(ms: number): string { + const seconds = Math.floor(ms / 1000); + if (seconds < 60) return `${seconds}s ago`; + const minutes = Math.floor(seconds / 60); + if (minutes < 60) return `${minutes} min ago`; + const hours = Math.floor(minutes / 60); + if (hours < 24) return `${hours}h ago`; + const days = Math.floor(hours / 24); + return `${days}d ago`; +} diff --git a/src/supplychain/ChainCoordinator.ts b/src/supplychain/ChainCoordinator.ts new file mode 100644 index 0000000..321f141 --- /dev/null +++ b/src/supplychain/ChainCoordinator.ts @@ -0,0 +1,562 @@ +import { BotManager } from '../bot/BotManager'; +import { Server as SocketIOServer } from 'socket.io'; +import { EventLog } from '../server/EventLog'; +import { logger } from '../util/logger'; +import fs from 'fs'; +import path from 'path'; +import crypto from 'crypto'; + +// ── Interfaces ────────────────────────────────────────────── + +export interface ChestLocation { + x: number; + y: number; + z: number; + label: string; +} + +export type StageStatus = 'pending' | 'queued' | 'running' | 'completed' | 'failed'; +export type ChainStatus = 'idle' | 'running' | 'paused' | 'completed' | 'failed'; + +export interface ChainStage { + id: string; + botName: string; + task: string; + inputChest?: ChestLocation; + outputChest?: ChestLocation; + inputItems?: { item: string; count: number }[]; + outputItems?: { item: string; count: number }[]; + status: StageStatus; + startedAt?: number; + completedAt?: number; + retries: number; + error?: string; +} + +export interface SupplyChain { + id: string; + name: string; + description?: string; + stages: ChainStage[]; + status: ChainStatus; + currentStageIndex: number; + loop: boolean; + createdAt: number; + updatedAt: number; +} + +export interface ChainTemplate { + id: string; + name: string; + description: string; + stages: { + task: string; + inputItems?: { item: string; count: number }[]; + outputItems?: { item: string; count: number }[]; + }[]; +} + +// ── Built-in templates ────────────────────────────────────── + +const TEMPLATES: ChainTemplate[] = [ + { + id: 'iron-ingots', + name: 'Iron Ingot Production', + description: 'Mine iron ore, smelt into ingots', + stages: [ + { task: 'Mine {count} iron_ore', outputItems: [{ item: 'raw_iron', count: 8 }] }, + { + task: 'Smelt {count} raw_iron using coal as fuel', + inputItems: [{ item: 'raw_iron', count: 8 }], + outputItems: [{ item: 'iron_ingot', count: 8 }], + }, + ], + }, + { + id: 'stone-tools', + name: 'Stone Tool Crafting', + description: 'Mine cobblestone and craft stone tools', + stages: [ + { task: 'Mine 12 cobblestone and 4 oak_log', outputItems: [{ item: 'cobblestone', count: 12 }] }, + { + task: 'Craft 2 stone_pickaxe and 1 stone_axe', + inputItems: [{ item: 'cobblestone', count: 12 }], + outputItems: [{ item: 'stone_pickaxe', count: 2 }], + }, + ], + }, + { + id: 'bread-production', + name: 'Bread Production', + description: 'Harvest wheat, craft into bread', + stages: [ + { task: 'Harvest 9 wheat', outputItems: [{ item: 'wheat', count: 9 }] }, + { + task: 'Craft 3 bread from 9 wheat', + inputItems: [{ item: 'wheat', count: 9 }], + outputItems: [{ item: 'bread', count: 3 }], + }, + ], + }, +]; + +// ── Chain Coordinator ─────────────────────────────────────── + +export class ChainCoordinator { + private botManager: BotManager; + private io: SocketIOServer; + private eventLog: EventLog; + private chains: Map = new Map(); + private dataPath: string; + private pollingInterval: ReturnType | null = null; + private taskDescriptionMap: Map = new Map(); // stageId -> task description sent to bot + + constructor(botManager: BotManager, io: SocketIOServer, eventLog: EventLog) { + this.botManager = botManager; + this.io = io; + this.eventLog = eventLog; + this.dataPath = path.join(process.cwd(), 'data', 'supply_chains.json'); + this.load(); + this.startPolling(); + } + + // ── Persistence ───────────────────────────────────────── + + private load(): void { + try { + if (fs.existsSync(this.dataPath)) { + const raw = fs.readFileSync(this.dataPath, 'utf-8'); + const arr: SupplyChain[] = JSON.parse(raw); + for (const chain of arr) { + this.chains.set(chain.id, chain); + } + logger.info({ count: arr.length }, 'Loaded supply chains from disk'); + } + } catch (err: any) { + logger.warn({ err: err.message }, 'Failed to load supply chains, starting fresh'); + } + } + + private save(): void { + try { + const arr = [...this.chains.values()]; + const dir = path.dirname(this.dataPath); + if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); + fs.writeFileSync(this.dataPath, JSON.stringify(arr, null, 2), 'utf-8'); + } catch (err: any) { + logger.error({ err: err.message }, 'Failed to save supply chains'); + } + } + + // ── Templates ─────────────────────────────────────────── + + getTemplates(): ChainTemplate[] { + return TEMPLATES; + } + + // ── CRUD ──────────────────────────────────────────────── + + getAllChains(): SupplyChain[] { + return [...this.chains.values()]; + } + + getChain(id: string): SupplyChain | undefined { + return this.chains.get(id); + } + + createChain(opts: { + name: string; + description?: string; + templateId?: string; + stages?: ChainStage[]; + loop?: boolean; + botAssignments?: Record; + chestLocations?: Record; + }): SupplyChain { + const chainId = crypto.randomUUID(); + let stages: ChainStage[]; + + if (opts.templateId) { + const template = TEMPLATES.find((t) => t.id === opts.templateId); + if (!template) { + throw new Error(`Template not found: ${opts.templateId}`); + } + + stages = template.stages.map((tmplStage, idx) => { + const botName = opts.botAssignments?.[idx] ?? ''; + const chests = opts.chestLocations?.[idx]; + + return { + id: crypto.randomUUID(), + botName, + task: tmplStage.task, + inputChest: chests?.input, + outputChest: chests?.output, + inputItems: tmplStage.inputItems, + outputItems: tmplStage.outputItems, + status: 'pending' as StageStatus, + retries: 0, + }; + }); + } else if (opts.stages) { + stages = opts.stages.map((s) => ({ + ...s, + id: s.id || crypto.randomUUID(), + status: 'pending' as StageStatus, + retries: s.retries ?? 0, + })); + } else { + throw new Error('Either templateId or stages must be provided'); + } + + // Validate bot names + for (const stage of stages) { + if (stage.botName) { + const bot = this.botManager.getBot(stage.botName); + if (!bot) { + throw new Error(`Bot not found: ${stage.botName}`); + } + } + } + + const chain: SupplyChain = { + id: chainId, + name: opts.name, + description: opts.description, + stages, + status: 'idle', + currentStageIndex: 0, + loop: opts.loop ?? false, + createdAt: Date.now(), + updatedAt: Date.now(), + }; + + this.chains.set(chainId, chain); + this.save(); + + logger.info({ chainId, name: chain.name, stageCount: stages.length }, 'Supply chain created'); + return chain; + } + + deleteChain(id: string): boolean { + const chain = this.chains.get(id); + if (!chain) return false; + + this.chains.delete(id); + this.save(); + + logger.info({ chainId: id, name: chain.name }, 'Supply chain deleted'); + return true; + } + + // ── Execution control ───────────────────────────────── + + startChain(id: string): boolean { + const chain = this.chains.get(id); + if (!chain || chain.status === 'running') return false; + + // Reset all stages + for (const stage of chain.stages) { + stage.status = 'pending'; + stage.startedAt = undefined; + stage.completedAt = undefined; + stage.error = undefined; + stage.retries = 0; + } + + chain.status = 'running'; + chain.currentStageIndex = 0; + chain.updatedAt = Date.now(); + this.save(); + + this.io.emit('chain:started', { chainId: chain.id, name: chain.name }); + this.eventLog.push({ + type: 'chain:started', + botName: chain.stages.map((s) => s.botName).filter(Boolean).join(', '), + description: `Supply chain started: ${chain.name}`, + metadata: { chainId: chain.id }, + }); + + logger.info({ chainId: chain.id, name: chain.name }, 'Supply chain started'); + + this.advanceStage(chain); + return true; + } + + pauseChain(id: string): boolean { + const chain = this.chains.get(id); + if (!chain || chain.status !== 'running') return false; + + chain.status = 'paused'; + chain.updatedAt = Date.now(); + this.save(); + + this.io.emit('chain:paused', { chainId: chain.id }); + this.eventLog.push({ + type: 'chain:paused', + botName: chain.stages[chain.currentStageIndex]?.botName ?? '', + description: `Supply chain paused: ${chain.name}`, + metadata: { chainId: chain.id }, + }); + + logger.info({ chainId: chain.id, name: chain.name }, 'Supply chain paused'); + return true; + } + + cancelChain(id: string): boolean { + const chain = this.chains.get(id); + if (!chain) return false; + + // Reset all stages to pending + for (const stage of chain.stages) { + stage.status = 'pending'; + stage.startedAt = undefined; + stage.completedAt = undefined; + stage.error = undefined; + } + + chain.status = 'idle'; + chain.currentStageIndex = 0; + chain.updatedAt = Date.now(); + this.save(); + + this.io.emit('chain:cancelled', { chainId: chain.id }); + this.eventLog.push({ + type: 'chain:cancelled', + botName: chain.stages.map((s) => s.botName).filter(Boolean).join(', '), + description: `Supply chain cancelled: ${chain.name}`, + metadata: { chainId: chain.id }, + }); + + logger.info({ chainId: chain.id, name: chain.name }, 'Supply chain cancelled'); + return true; + } + + // ── Stage advancement ───────────────────────────────── + + private advanceStage(chain: SupplyChain): void { + if (chain.status !== 'running') return; + + const stageIndex = chain.currentStageIndex; + if (stageIndex >= chain.stages.length) { + // All stages complete + chain.status = 'completed'; + chain.updatedAt = Date.now(); + this.save(); + + this.io.emit('chain:completed', { chainId: chain.id, name: chain.name }); + this.eventLog.push({ + type: 'chain:completed', + botName: chain.stages.map((s) => s.botName).filter(Boolean).join(', '), + description: `Supply chain completed: ${chain.name}`, + metadata: { chainId: chain.id }, + }); + + logger.info({ chainId: chain.id, name: chain.name }, 'Supply chain completed'); + return; + } + + const stage = chain.stages[stageIndex]; + if (!stage.botName) { + stage.status = 'failed'; + stage.error = 'No bot assigned to stage'; + chain.status = 'failed'; + chain.updatedAt = Date.now(); + this.save(); + + this.io.emit('chain:stage-update', { chainId: chain.id, stageIndex, stage }); + this.io.emit('chain:failed', { chainId: chain.id, name: chain.name, error: stage.error }); + return; + } + + const bot = this.botManager.getBot(stage.botName); + if (!bot) { + stage.status = 'failed'; + stage.error = `Bot not found: ${stage.botName}`; + chain.status = 'failed'; + chain.updatedAt = Date.now(); + this.save(); + + this.io.emit('chain:stage-update', { chainId: chain.id, stageIndex, stage }); + this.io.emit('chain:failed', { chainId: chain.id, name: chain.name, error: stage.error }); + return; + } + + const voyager = bot.getVoyagerLoop(); + if (!voyager) { + stage.status = 'failed'; + stage.error = `Bot ${stage.botName} is not in codegen mode`; + chain.status = 'failed'; + chain.updatedAt = Date.now(); + this.save(); + + this.io.emit('chain:stage-update', { chainId: chain.id, stageIndex, stage }); + this.io.emit('chain:failed', { chainId: chain.id, name: chain.name, error: stage.error }); + return; + } + + const taskDescription = this.buildTaskDescription(stage); + this.taskDescriptionMap.set(stage.id, taskDescription); + + voyager.queuePlayerTask(taskDescription, 'supply-chain'); + + stage.status = 'running'; + stage.startedAt = Date.now(); + chain.updatedAt = Date.now(); + this.save(); + + this.io.emit('chain:stage-update', { chainId: chain.id, stageIndex, stage }); + + logger.info( + { chainId: chain.id, stageIndex, botName: stage.botName, task: taskDescription }, + 'Supply chain stage queued', + ); + } + + private buildTaskDescription(stage: ChainStage): string { + let description = stage.task; + + if (stage.inputChest) { + const c = stage.inputChest; + description += `. First go to coordinates (${c.x}, ${c.y}, ${c.z}) and collect items from the chest`; + if (stage.inputItems && stage.inputItems.length > 0) { + const itemList = stage.inputItems.map((i) => `${i.count} ${i.item}`).join(', '); + description += ` (need: ${itemList})`; + } + description += '.'; + } + + if (stage.outputChest) { + const c = stage.outputChest; + description += ` Then go to coordinates (${c.x}, ${c.y}, ${c.z}) and deposit`; + if (stage.outputItems && stage.outputItems.length > 0) { + const itemList = stage.outputItems.map((i) => `${i.count} ${i.item}`).join(', '); + description += ` ${itemList}`; + } else { + description += ' the results'; + } + description += ' into the chest.'; + } + + return description; + } + + // ── Polling ─────────────────────────────────────────── + + private startPolling(): void { + if (this.pollingInterval) return; + this.pollingInterval = setInterval(() => { + this.checkChainProgress(); + }, 5000); + } + + private checkChainProgress(): void { + for (const chain of this.chains.values()) { + if (chain.status !== 'running') continue; + + const stageIndex = chain.currentStageIndex; + if (stageIndex >= chain.stages.length) continue; + + const stage = chain.stages[stageIndex]; + if (stage.status !== 'running') continue; + + const bot = this.botManager.getBot(stage.botName); + if (!bot) continue; + + const voyager = bot.getVoyagerLoop(); + if (!voyager) continue; + + const taskDesc = this.taskDescriptionMap.get(stage.id) ?? stage.task; + const currentTask = voyager.getCurrentTask(); + const completedTasks = voyager.getCompletedTasks(); + const failedTasks = voyager.getFailedTasks(); + + // Check if the task has completed (bot no longer working on it and it appears in completed list) + const isCompleted = completedTasks.some((t) => t.includes(taskDesc) || taskDesc.includes(t)); + const isFailed = failedTasks.some((t) => t.includes(taskDesc) || taskDesc.includes(t)); + const taskFinished = currentTask === null || (!currentTask.includes(taskDesc) && !taskDesc.includes(currentTask ?? '')); + + if (isCompleted) { + stage.status = 'completed'; + stage.completedAt = Date.now(); + chain.currentStageIndex++; + chain.updatedAt = Date.now(); + this.save(); + + this.io.emit('chain:stage-update', { chainId: chain.id, stageIndex, stage }); + + logger.info( + { chainId: chain.id, stageIndex, botName: stage.botName }, + 'Supply chain stage completed', + ); + + // Check if chain should loop + if (chain.currentStageIndex >= chain.stages.length && chain.loop) { + logger.info({ chainId: chain.id, name: chain.name }, 'Supply chain looping'); + for (const s of chain.stages) { + s.status = 'pending'; + s.startedAt = undefined; + s.completedAt = undefined; + s.error = undefined; + s.retries = 0; + } + chain.currentStageIndex = 0; + chain.updatedAt = Date.now(); + this.save(); + } + + this.advanceStage(chain); + } else if (isFailed || (taskFinished && stage.startedAt && Date.now() - stage.startedAt > 10000)) { + // Task failed or bot moved on without completing — retry or fail + stage.retries++; + + if (stage.retries < 3) { + logger.warn( + { chainId: chain.id, stageIndex, botName: stage.botName, retries: stage.retries }, + 'Supply chain stage failed, retrying', + ); + + stage.status = 'queued'; + stage.error = undefined; + chain.updatedAt = Date.now(); + this.save(); + + this.io.emit('chain:stage-update', { chainId: chain.id, stageIndex, stage }); + + // Re-queue the task + const retryDesc = this.taskDescriptionMap.get(stage.id) ?? this.buildTaskDescription(stage); + this.taskDescriptionMap.set(stage.id, retryDesc); + voyager.queuePlayerTask(retryDesc, 'supply-chain'); + + stage.status = 'running'; + stage.startedAt = Date.now(); + this.save(); + } else { + stage.status = 'failed'; + stage.error = 'Max retries exceeded'; + chain.status = 'failed'; + chain.updatedAt = Date.now(); + this.save(); + + this.io.emit('chain:stage-update', { chainId: chain.id, stageIndex, stage }); + this.io.emit('chain:failed', { + chainId: chain.id, + name: chain.name, + error: `Stage ${stageIndex} failed after 3 retries`, + }); + this.eventLog.push({ + type: 'chain:failed', + botName: stage.botName, + description: `Supply chain failed: ${chain.name} (stage ${stageIndex} exceeded retries)`, + metadata: { chainId: chain.id, stageIndex }, + }); + + logger.error( + { chainId: chain.id, stageIndex, botName: stage.botName }, + 'Supply chain stage failed permanently', + ); + } + } + } + } +} diff --git a/src/voyager/ActionAgent.ts b/src/voyager/ActionAgent.ts index b7478b5..567e444 100644 --- a/src/voyager/ActionAgent.ts +++ b/src/voyager/ActionAgent.ts @@ -17,78 +17,34 @@ export interface GeneratedCode { execCode: string; } -const ACTION_SYSTEM_PROMPT = `You are a Minecraft bot code generator. You write JavaScript code as a single named async function to complete the task. - -## Useful programs already available in scope -Reuse these as much as possible. They are the preferred way to act in the world. - -\`\`\` -async function mineBlock(name, count) // collect blocks/items -async function craftItem(name, count) // craft items -async function smeltItem(itemName, fuelName, count) // smelt items in a furnace -async function placeItem(name, x, y, z) // place blocks -async function killMob(name, maxMs) // fight mobs -async function moveTo(x, y, z, range, timeoutSec) // pathfind to a target -async function exploreUntil(direction, maxTime, callback) // explore until callback returns a target -async function withdrawItem(containerName, itemName, count) // withdraw from chest/barrel/etc -async function depositItem(containerName, itemName, count) // deposit into chest/barrel/etc -async function inspectContainer(containerName) // inspect container contents -\`\`\` - -## Bot state / observation APIs -These are mainly for observing state or selecting a target. - -- bot.entity.position, bot.health, bot.food -- bot.inventory.items() — returns array of {name, count}. To find an item: bot.inventory.items().find(i => i.name === 'oak_log') -- bot.findBlock({matching: b => b.name === 'name', maxDistance: 32}) -- bot.lookAt(pos), bot.look(yaw, pitch) -- bot.nearestEntity(filter), bot.players -- bot.waitForTicks(ticks) - -## APIs that do NOT exist — never use these -- bot.inventory.findInventoryItem() — DOES NOT EXIST. Use bot.inventory.items().find(i => i.name === 'x') instead. -- bot.inventory.findItem() — DOES NOT EXIST. -- bot.tossStack() — DOES NOT EXIST. Use bot.toss(itemId, null, count) instead. -- bot.equip() with string argument — use the item object from bot.inventory.items(). - -## Hard rules -1. Output a SINGLE async function: async function functionName(bot) { ... } -2. The function name must be meaningful camelCase. -3. You may call any previously saved skill functions shown in context - they accept (bot) as parameter. -4. Use await for all async operations. -5. Do NOT wrap the entire function body in try/catch. Let errors propagate so they can be detected. Only use try/catch around specific risky operations if needed. -6. Do NOT call bot.chat(). The bot should work silently. -7. Keep code concise and reusable. Do not assume the inventory already contains required items. -8. Do NOT use bot.on() or bot.once() event listeners. -9. Do NOT write infinite loops or recursive functions. -10. maxDistance must always be 32 for bot.findBlock(). -11. Output ONLY the function code. No explanation. No markdown fences. - -## Primitive usage requirements -- For mining or collecting tasks, use mineBlock(...). Do NOT use bot.dig(...) directly. -- For crafting tasks, use craftItem(...). Do NOT use bot.craft(...) or bot.recipesFor(...) directly. -- For smelting tasks, use smeltItem(...). Do NOT use bot.openFurnace(...) directly. -- For placement tasks, use placeItem(...). Do NOT use bot.placeBlock(...) directly. -- For combat tasks, use killMob(...). Do NOT use bot.attack(...) directly. -- For chest or container tasks, use withdrawItem(...) and depositItem(...) instead of scripting container UI manually. -- Use inspectContainer(...) when you need to check what is inside a nearby chest/container. -- For movement tasks, use moveTo(...). -- If the target is not nearby or cannot be found immediately, use exploreUntil(...) before giving up. -- Do NOT use bot.pathfinder.setGoal(...) directly unless there is no primitive that can solve the task. - -## Behavioral guidance -- First identify the target. -- If the target is nearby, use the appropriate primitive. -- If the target is not nearby, explore outward with exploreUntil(...) and then use the primitive. -- Do not stop after only locating a target when the task implies going to it, collecting it, or interacting with it. - -## Previously saved skills -You may call previously saved skill functions shown in context. They accept (bot) as parameter. - -## Composition priority -- Prefer composing 2-3 previously saved skills plus primitives over writing long fresh logic from scratch. -- If a retrieved skill already solves a prerequisite (for example gathering wood, crafting a table, moving to a player, or finding a target), call that skill instead of rewriting it. -- For compound tasks, write a short orchestrator function that calls existing skills/primitives in order.`; +const ACTION_SYSTEM_PROMPT = `You are a Minecraft bot code generator. Output a single async function to complete the task. + +## Available primitives (always prefer these over raw bot APIs) +mineBlock(name, count) — craftItem(name, count) — smeltItem(itemName, fuelName, count) +placeItem(name, x, y, z) — killMob(name, maxMs) — moveTo(x, y, z, range, timeoutSec) +exploreUntil(direction, maxTime, callback) — withdrawItem(containerName, itemName, count) +depositItem(containerName, itemName, count) — inspectContainer(containerName) + +## Bot observation APIs +bot.entity.position, bot.health, bot.food +bot.inventory.items() → [{name, count}]; find: bot.inventory.items().find(i => i.name === 'oak_log') +bot.findBlock({matching: b => b.name === 'name', maxDistance: 32}) +bot.lookAt(pos), bot.look(yaw, pitch), bot.nearestEntity(filter), bot.players, bot.waitForTicks(ticks) + +## Non-existent APIs — never use +bot.inventory.findInventoryItem(), bot.inventory.findItem() → use bot.inventory.items().find() +bot.tossStack() → use bot.toss(itemId, null, count) +bot.equip() with string → pass the item object from bot.inventory.items() + +## Rules +1. Output ONLY a single \`async function functionName(bot) { ... }\` in camelCase. No explanation, no markdown fences. +2. Use await for all async calls. Do not assume inventory already has required items. +3. Always use primitives instead of raw bot APIs: mineBlock not bot.dig, craftItem not bot.craft/bot.recipesFor, smeltItem not bot.openFurnace, placeItem not bot.placeBlock, killMob not bot.attack, withdrawItem/depositItem not manual container UI, moveTo not bot.pathfinder.setGoal (unless no primitive fits). +4. If the target is not nearby, use exploreUntil(...) first, then act on it — do not stop after merely locating the target. +5. Do NOT: wrap the whole body in try/catch (let errors propagate), call bot.chat(), use bot.on()/bot.once(), write infinite loops or recursion. +6. maxDistance must be 32 for bot.findBlock(). +7. You may call previously saved skill functions shown in context — they accept (bot). Prefer composing existing skills + primitives over writing long fresh logic. +8. For compound tasks, write a short orchestrator that calls existing skills/primitives in order.`; export class ActionAgent { private static readonly MAX_PREVIOUS_CODE_CHARS = 800; diff --git a/src/voyager/BlackboardManager.ts b/src/voyager/BlackboardManager.ts index 957a117..4ad80d4 100644 --- a/src/voyager/BlackboardManager.ts +++ b/src/voyager/BlackboardManager.ts @@ -56,6 +56,7 @@ interface BlackboardState { export class BlackboardManager { private filePath: string; private state: BlackboardState; + private _saveTimer: ReturnType | null = null; constructor(dataDir: string) { this.filePath = path.join(dataDir, 'blackboard.json'); @@ -247,6 +248,30 @@ export class BlackboardManager { } private persist(): void { - fs.writeFileSync(this.filePath, JSON.stringify(this.state, null, 2)); + if (this._saveTimer) clearTimeout(this._saveTimer); + this._saveTimer = setTimeout(() => { + this._saveTimer = null; + this.writeAtomic(); + }, 2000); + } + + private writeAtomic(): void { + const tmpPath = this.filePath + '.tmp'; + try { + fs.writeFileSync(tmpPath, JSON.stringify(this.state, null, 2)); + fs.renameSync(tmpPath, this.filePath); + } catch { + // Fallback: direct write (rename can fail on Windows if target is locked) + try { fs.writeFileSync(this.filePath, JSON.stringify(this.state, null, 2)); } catch { /* best effort */ } + try { fs.unlinkSync(tmpPath); } catch { /* ignore */ } + } + } + + shutdown(): void { + if (this._saveTimer) { + clearTimeout(this._saveTimer); + this._saveTimer = null; + this.writeAtomic(); + } } } diff --git a/src/voyager/BlockerMemory.ts b/src/voyager/BlockerMemory.ts index 123ce3d..da8e83c 100644 --- a/src/voyager/BlockerMemory.ts +++ b/src/voyager/BlockerMemory.ts @@ -14,6 +14,7 @@ export interface BlockerRecord { export class BlockerMemory { private records: BlockerRecord[] = []; private filePath: string; + private _saveTimer: ReturnType | null = null; constructor(dataDir: string) { this.filePath = path.join(dataDir, 'blockers.json'); @@ -86,6 +87,29 @@ export class BlockerMemory { } private persist(): void { - fs.writeFileSync(this.filePath, JSON.stringify(this.records, null, 2)); + if (this._saveTimer) clearTimeout(this._saveTimer); + this._saveTimer = setTimeout(() => { + this._saveTimer = null; + this.writeAtomic(); + }, 2000); + } + + private writeAtomic(): void { + const tmpPath = this.filePath + '.tmp'; + try { + fs.writeFileSync(tmpPath, JSON.stringify(this.records, null, 2)); + fs.renameSync(tmpPath, this.filePath); + } catch { + try { fs.writeFileSync(this.filePath, JSON.stringify(this.records, null, 2)); } catch { /* best effort */ } + try { fs.unlinkSync(tmpPath); } catch { /* ignore */ } + } + } + + shutdown(): void { + if (this._saveTimer) { + clearTimeout(this._saveTimer); + this._saveTimer = null; + this.writeAtomic(); + } } } diff --git a/src/voyager/CriticAgent.ts b/src/voyager/CriticAgent.ts index fc1b59d..019e3df 100644 --- a/src/voyager/CriticAgent.ts +++ b/src/voyager/CriticAgent.ts @@ -36,87 +36,39 @@ Ensure the response can be parsed by JSON.parse(), e.g.: no trailing commas, no Do NOT wrap in markdown fences. Here are some examples: -INPUT: -Inventory (2/36): {"oak_log":2, "spruce_log":2} - -Task: Mine 3 wood logs - -RESPONSE: -{"reasoning": "The bot needs to mine 3 wood logs. It has 2 oak logs and 2 spruce logs, which add up to 4 wood logs.", "success": true, "critique": ""} - -INPUT: -Inventory (3/36): {"crafting_table": 1, "spruce_planks": 6, "stick": 4} - -Task: Craft a wooden pickaxe - -RESPONSE: -{"reasoning": "The bot has enough materials to craft a wooden pickaxe, but it did not craft it.", "success": false, "critique": "Craft a wooden pickaxe with a crafting table using 3 spruce planks and 2 sticks."} - INPUT: Inventory (2/36): {"raw_iron": 5, "stone_pickaxe": 1} - Task: Mine 5 iron_ore - RESPONSE: -{"reasoning": "Mining iron_ore in Minecraft yields raw_iron. The bot has 5 raw_iron in inventory.", "success": true, "critique": ""} +{"reasoning": "Mining iron_ore yields raw_iron. The bot has 5 raw_iron in inventory.", "success": true, "critique": ""} INPUT: -Biome: plains - -Nearby blocks: stone, dirt, grass_block, grass, farmland, wheat - -Inventory (26/36): ... - -Task: Plant 1 wheat seed - -RESPONSE: -{"reasoning": "For planting tasks, inventory information is useless. In nearby blocks, there is farmland and wheat, which means the bot successfully planted the wheat seed.", "success": true, "critique": ""} - -INPUT: -Inventory (11/36): {"rotten_flesh": 1, "stone_sword": 1} - -Task: Kill 1 zombie - -RESPONSE: -{"reasoning": "The bot has rotten flesh in inventory, which means it successfully killed a zombie.", "success": true, "critique": ""} - -INPUT: -Hunger: 20.0/20.0 - -Inventory (11/36): ... - -Task: Eat 1 cooked beef - +Inventory (3/36): {"crafting_table": 1, "spruce_planks": 6, "stick": 4} +Task: Craft a wooden pickaxe RESPONSE: -{"reasoning": "For eating tasks, if hunger is 20.0 then the bot successfully ate the food.", "success": true, "critique": ""} +{"reasoning": "The bot has materials but did not craft the pickaxe.", "success": false, "critique": "Craft a wooden pickaxe with a crafting table using 3 spruce planks and 2 sticks."} INPUT: Position before: 100, 65, 200 Position after: 145, 68, 220 Distance moved: 52.3 - Task: Explore 50 blocks to the north - -RESPONSE: -{"reasoning": "The bot moved 52.3 blocks from its starting position, which exceeds the required 50 blocks.", "success": true, "critique": ""} - -INPUT: -Inventory delta: oak_log:+3, oak_planks:+4, stick:+8, crafting_table:+1 - -Task: Mine 3 oak logs - RESPONSE: -{"reasoning": "The inventory delta shows oak_log:+3, meaning the bot gained exactly 3 oak logs during this task.", "success": true, "critique": ""} +{"reasoning": "The bot moved 52.3 blocks, exceeding the required 50.", "success": true, "critique": ""} INPUT: Inventory delta: none Position before: 100, 65, 200 Position after: 100, 65, 200 - Task: Mine 3 cobblestone +RESPONSE: +{"reasoning": "No inventory change and no movement occurred. The bot did not mine anything.", "success": false, "critique": "Use mineBlock('cobblestone', 3) to mine cobblestone. If no cobblestone is nearby, first use exploreUntil to find stone blocks, then mine them."} +INPUT: +Inventory delta: oak_log:+3, oak_planks:+4, stick:+8, crafting_table:+1 +Task: Mine 3 oak logs RESPONSE: -{"reasoning": "No inventory change and no movement occurred. The bot did not mine anything.", "success": false, "critique": "Use mineBlock('cobblestone', 3) to mine cobblestone. If no cobblestone is nearby, first use exploreUntil to find stone blocks, then mine them."}`; +{"reasoning": "The inventory delta shows oak_log:+3. The bot exceeded requirements by also crafting planks and sticks.", "success": true, "critique": ""}`; export class CriticAgent { private llmClient: LLMClient | null; diff --git a/src/voyager/CurriculumAgent.ts b/src/voyager/CurriculumAgent.ts index 26267ec..16db6c4 100644 --- a/src/voyager/CurriculumAgent.ts +++ b/src/voyager/CurriculumAgent.ts @@ -42,6 +42,85 @@ IMPORTANT: Keep reasoning under 20 words. Do NOT write long explanations. The en // Large progression-aware fallback pool used when LLM is unavailable. // Tasks are ordered by progression stage; proposeStaticTask picks the first feasible & uncompleted one. +// Personality-specific task pools — proposed first to give bots flavour. +const PERSONALITY_TASKS: Record = { + farmer: [ + { description: 'Explore and find wheat seeds', keywords: ['explore', 'seeds', 'wheat'] }, + { description: 'Walk to the nearest farmland', keywords: ['walk', 'farm', 'crops'] }, + { description: 'Plant 1 wheat seed', keywords: ['plant', 'wheat', 'farm'] }, + { description: 'Harvest 3 wheat', keywords: ['harvest', 'wheat', 'farm'] }, + { description: 'Craft 1 bread', keywords: ['craft', 'bread', 'food'] }, + { description: 'Craft a wooden hoe', keywords: ['craft', 'wooden_hoe', 'tool'] }, + { description: 'Breed 2 cows', keywords: ['breed', 'cow', 'animal'] }, + { description: 'Breed 2 chickens', keywords: ['breed', 'chicken', 'animal'] }, + { description: 'Collect 3 eggs', keywords: ['collect', 'egg', 'animal'] }, + { description: 'Plant 3 carrot seeds', keywords: ['plant', 'carrot', 'farm'] }, + { description: 'Harvest 3 potatoes', keywords: ['harvest', 'potato', 'farm'] }, + ], + merchant: [ + { description: 'Explore 100 blocks to find a village', keywords: ['explore', 'village', 'trade'] }, + { description: 'Mine 3 gold ore', keywords: ['mine', 'gold_ore', 'resource'] }, + { description: 'Collect 5 emeralds', keywords: ['collect', 'emerald', 'trade'] }, + { description: 'Mine 5 lapis lazuli ore', keywords: ['mine', 'lapis_ore', 'resource'] }, + { description: 'Explore 50 blocks to the east', keywords: ['explore', 'walk', 'east'] }, + { description: 'Mine 3 diamonds', keywords: ['mine', 'diamond', 'resource'] }, + { description: 'Collect 5 redstone', keywords: ['collect', 'redstone', 'resource'] }, + ], + builder: [ + { description: 'Mine 16 oak logs', keywords: ['mine', 'oak_log', 'wood'] }, + { description: 'Craft 32 oak planks', keywords: ['craft', 'oak_planks', 'wood'] }, + { description: 'Mine 16 cobblestone', keywords: ['mine', 'cobblestone', 'stone'] }, + { description: 'Craft 1 crafting table', keywords: ['craft', 'crafting_table', 'wood'] }, + { description: 'Craft 4 oak stairs', keywords: ['craft', 'oak_stairs', 'build'] }, + { description: 'Mine 8 sand', keywords: ['mine', 'sand', 'resource'] }, + { description: 'Craft 8 glass from sand', keywords: ['smelt', 'glass', 'build'] }, + { description: 'Mine 16 dirt', keywords: ['mine', 'dirt', 'resource'] }, + ], + guard: [ + { description: 'Craft a wooden sword', keywords: ['craft', 'wooden_sword', 'tool'] }, + { description: 'Craft a stone sword', keywords: ['craft', 'stone_sword', 'tool'] }, + { description: 'Kill 1 zombie', keywords: ['kill', 'zombie', 'combat'] }, + { description: 'Kill 1 skeleton', keywords: ['kill', 'skeleton', 'combat'] }, + { description: 'Kill 1 spider', keywords: ['kill', 'spider', 'combat'] }, + { description: 'Craft an iron sword', keywords: ['craft', 'iron_sword', 'tool'] }, + { description: 'Craft a shield', keywords: ['craft', 'shield', 'armor'] }, + { description: 'Craft iron armor', keywords: ['craft', 'iron_armor', 'armor'] }, + { description: 'Explore 50 blocks to patrol the area', keywords: ['explore', 'patrol', 'guard'] }, + ], + explorer: [ + { description: 'Explore 100 blocks to the north', keywords: ['explore', 'walk', 'north'] }, + { description: 'Explore 100 blocks to the east', keywords: ['explore', 'walk', 'east'] }, + { description: 'Explore 100 blocks to the south', keywords: ['explore', 'walk', 'south'] }, + { description: 'Explore 100 blocks to the west', keywords: ['explore', 'walk', 'west'] }, + { description: 'Explore and find a cave entrance', keywords: ['explore', 'cave', 'structure'] }, + { description: 'Explore 100 blocks to find a village', keywords: ['explore', 'village', 'structure'] }, + { description: 'Craft a boat', keywords: ['craft', 'boat', 'transport'] }, + { description: 'Craft a compass', keywords: ['craft', 'compass', 'navigation'] }, + ], + blacksmith: [ + { description: 'Mine 5 coal ore', keywords: ['mine', 'coal_ore', 'resource'] }, + { description: 'Mine 5 iron ore', keywords: ['mine', 'iron_ore', 'resource'] }, + { description: 'Craft 1 furnace', keywords: ['craft', 'furnace', 'smelt'] }, + { description: 'Smelt 3 raw iron', keywords: ['smelt', 'raw_iron', 'iron'] }, + { description: 'Craft an iron pickaxe', keywords: ['craft', 'iron_pickaxe', 'tool'] }, + { description: 'Craft an iron sword', keywords: ['craft', 'iron_sword', 'tool'] }, + { description: 'Craft an iron axe', keywords: ['craft', 'iron_axe', 'tool'] }, + { description: 'Mine 3 gold ore', keywords: ['mine', 'gold_ore', 'resource'] }, + { description: 'Smelt 3 raw gold', keywords: ['smelt', 'raw_gold', 'gold'] }, + { description: 'Mine 3 diamonds', keywords: ['mine', 'diamond', 'resource'] }, + ], + elder: [ + { description: 'Mine 3 oak logs', keywords: ['mine', 'oak_log', 'wood'] }, + { description: 'Craft 1 crafting table', keywords: ['craft', 'crafting_table', 'wood'] }, + { description: 'Craft a wooden pickaxe', keywords: ['craft', 'wooden_pickaxe', 'tool'] }, + { description: 'Mine 3 coal ore', keywords: ['mine', 'coal_ore', 'resource'] }, + { description: 'Craft 1 furnace', keywords: ['craft', 'furnace', 'smelt'] }, + { description: 'Craft a bookshelf', keywords: ['craft', 'bookshelf', 'knowledge'] }, + { description: 'Craft a map', keywords: ['craft', 'map', 'knowledge'] }, + { description: 'Craft an enchanting table', keywords: ['craft', 'enchanting_table', 'knowledge'] }, + ], +}; + const FALLBACK_TASKS: Task[] = [ // Stage 0: basics { description: 'Mine 1 oak log', keywords: ['mine', 'oak_log', 'wood'] }, @@ -282,7 +361,7 @@ export class CurriculumAgent { return null; } - private proposeStaticTask(_personality: string): Task { + private proposeStaticTask(personality: string): Task { const progression = this.lastBotForProgression ? getProgressionState(this.lastBotForProgression, this.completedTasks) : { hasWood: false, hasCraftingTable: false, hasWoodenPickaxe: false, hasWoodenHoe: false, hasCobblestone: false, canMineStoneTier: false, canFarm: false }; @@ -290,7 +369,24 @@ export class CurriculumAgent { // Only block tasks that failed very recently (last 5 failures), not permanently const recentFailures = new Set(this.failedTasks.slice(-5)); - // Find the first uncompleted, feasible task in progression order + // 65% chance to try personality-specific tasks first + if (Math.random() < 0.65) { + const personalityPool = PERSONALITY_TASKS[personality] || []; + const personalityUncompleted = personalityPool.filter( + (t) => t.description !== this.lastTask + && !this.completedTasks.includes(t.description) + && !recentFailures.has(t.description) + && taskMatchesProgression(t, progression) + ); + if (personalityUncompleted.length > 0) { + const pick = personalityUncompleted[Math.floor(Math.random() * Math.min(3, personalityUncompleted.length))]; + this.lastTask = pick.description; + logger.info({ personality, task: pick.description }, 'Curriculum: picked personality-weighted task'); + return pick; + } + } + + // Fall back to the generic progression pool const uncompleted = FALLBACK_TASKS.filter( (t) => t.description !== this.lastTask && !this.completedTasks.includes(t.description) @@ -668,4 +764,9 @@ Decompose into ordered subtasks:`; getWorldMemory(): WorldMemory { return this.worldMemory; } + + shutdown(): void { + this.blockerMemory.shutdown(); + this.worldMemory.shutdown(); + } } diff --git a/src/voyager/StatsTracker.ts b/src/voyager/StatsTracker.ts index 78287af..ad776fc 100644 --- a/src/voyager/StatsTracker.ts +++ b/src/voyager/StatsTracker.ts @@ -19,6 +19,7 @@ export interface BotStats { export class StatsTracker { private filePath: string; private stats: Record = {}; + private _saveTimer: ReturnType | null = null; constructor(dataDir: string) { this.filePath = path.join(dataDir, 'stats.json'); @@ -112,6 +113,29 @@ export class StatsTracker { } private persist(): void { - fs.writeFileSync(this.filePath, JSON.stringify(this.stats, null, 2)); + if (this._saveTimer) clearTimeout(this._saveTimer); + this._saveTimer = setTimeout(() => { + this._saveTimer = null; + this.writeAtomic(); + }, 2000); + } + + private writeAtomic(): void { + const tmpPath = this.filePath + '.tmp'; + try { + fs.writeFileSync(tmpPath, JSON.stringify(this.stats, null, 2)); + fs.renameSync(tmpPath, this.filePath); + } catch { + try { fs.writeFileSync(this.filePath, JSON.stringify(this.stats, null, 2)); } catch { /* best effort */ } + try { fs.unlinkSync(tmpPath); } catch { /* ignore */ } + } + } + + shutdown(): void { + if (this._saveTimer) { + clearTimeout(this._saveTimer); + this._saveTimer = null; + this.writeAtomic(); + } } } diff --git a/src/voyager/VoyagerLoop.ts b/src/voyager/VoyagerLoop.ts index fc7fdb1..6ff3be7 100644 --- a/src/voyager/VoyagerLoop.ts +++ b/src/voyager/VoyagerLoop.ts @@ -30,7 +30,7 @@ export class VoyagerLoop { private criticAgent: CriticAgent; private statsTracker: StatsTracker; private running = false; - private paused = false; + private pauseReasons: Set = new Set(); private loopTimeout: NodeJS.Timeout | null = null; private lastExecutionMetrics: { attempt: number; @@ -51,6 +51,12 @@ export class VoyagerLoop { private currentTask: string | null = null; private lastCompletedTask: string | null = null; private lastFailedTask: string | null = null; + private currentTaskSource: 'player-request' | 'long-term-goal' | 'blackboard' | 'autonomous' = 'autonomous'; + private interruptedByPlayer = false; + + // Optional callbacks for task lifecycle events + onTaskSuccess?: (taskDescription: string) => void; + onTaskFailure?: (taskDescription: string) => void; constructor( bot: Bot, @@ -93,7 +99,7 @@ export class VoyagerLoop { stop(): void { this.running = false; - this.paused = false; + this.pauseReasons.clear(); if (this.loopTimeout) { clearTimeout(this.loopTimeout); this.loopTimeout = null; @@ -101,30 +107,58 @@ export class VoyagerLoop { logger.info({ bot: this.botName }, 'Voyager loop stopped'); } + /** Flush all pending debounced writes in owned data managers. */ + shutdownPersistence(): void { + this.statsTracker.shutdown(); + this.curriculumAgent.shutdown(); + } + isRunning(): boolean { return this.running; } pause(reason = 'paused'): void { - if (!this.running || this.paused) return; - this.paused = true; - if (this.loopTimeout) { - clearTimeout(this.loopTimeout); - this.loopTimeout = null; + if (!this.running) return; + const wasEmpty = this.pauseReasons.size === 0; + this.pauseReasons.add(reason); + if (wasEmpty) { + if (this.loopTimeout) { + clearTimeout(this.loopTimeout); + this.loopTimeout = null; + } + this.codeExecutor.requestInterrupt(reason); + logger.warn({ bot: this.botName, reason, reasons: Array.from(this.pauseReasons), task: this.currentTask }, 'Voyager loop paused'); + } else { + logger.warn({ bot: this.botName, reason, reasons: Array.from(this.pauseReasons) }, 'Voyager loop pause reason added'); } - this.codeExecutor.requestInterrupt(reason); - logger.warn({ bot: this.botName, reason, task: this.currentTask }, 'Voyager loop paused'); } resume(reason = 'resumed'): void { - if (!this.running || !this.paused) return; - this.paused = false; - logger.info({ bot: this.botName, reason }, 'Voyager loop resumed'); + if (!this.running) return; + this.pauseReasons.delete(reason); + if (this.pauseReasons.size === 0) { + logger.info({ bot: this.botName, reason }, 'Voyager loop resumed (all pause reasons cleared)'); + this.scheduleNext(); + } else { + logger.info({ bot: this.botName, reason, remaining: Array.from(this.pauseReasons) }, 'Voyager loop pause reason removed, still paused'); + } + } + + /** Force-clear all pause reasons and restart the loop (e.g. player chat override). */ + forceResume(reason = 'force-resumed'): void { + if (!this.running) return; + const had = Array.from(this.pauseReasons); + this.pauseReasons.clear(); + logger.info({ bot: this.botName, reason, cleared: had }, 'Voyager loop force-resumed'); this.scheduleNext(); } isPaused(): boolean { - return this.paused; + return this.pauseReasons.size > 0; + } + + getPauseReasons(): string[] { + return Array.from(this.pauseReasons); } /** Get the current task description, or null if idle */ @@ -146,6 +180,47 @@ export class VoyagerLoop { return this.playerTaskQueue.map((task) => task.description); } + /** Get queue length */ + getQueueLength(): number { + return this.playerTaskQueue.length; + } + + /** Get queued tasks with IDs */ + getQueuedTasksDetailed(): { id: string; description: string; queuedAt: number }[] { + return this.playerTaskQueue.map((task, index) => ({ + id: (task as any)._id || `qt_${index}`, + description: task.description, + queuedAt: (task as any)._queuedAt || Date.now(), + })); + } + + /** Remove a task from queue by index */ + removeQueuedTask(index: number): boolean { + if (index < 0 || index >= this.playerTaskQueue.length) return false; + this.playerTaskQueue.splice(index, 1); + return true; + } + + /** Insert a task at the front of the queue (do next) */ + insertTaskAtFront(description: string, requestedBy: string): void { + const keywords = description.toLowerCase().replace(/[^a-z0-9\s]/g, '').split(/\s+/).filter(w => w.length > 2); + this.playerTaskQueue.unshift({ description, keywords }); + } + + /** Reorder queue: move item at fromIndex to toIndex */ + reorderQueue(fromIndex: number, toIndex: number): boolean { + if (fromIndex < 0 || fromIndex >= this.playerTaskQueue.length) return false; + if (toIndex < 0 || toIndex >= this.playerTaskQueue.length) return false; + const [item] = this.playerTaskQueue.splice(fromIndex, 1); + this.playerTaskQueue.splice(toIndex, 0, item); + return true; + } + + /** Clear all queued tasks */ + clearQueue(): void { + this.playerTaskQueue = []; + } + getLongTermGoal() { if (!this.activeLongTermGoal) return null; return { @@ -225,29 +300,45 @@ export class VoyagerLoop { } queuePlayerTask(description: string, requestedBy: string): void { - // Decompose asynchronously — subtasks get queued when ready - this.decomposeAndQueue(description, requestedBy).catch((err) => { - logger.warn({ err: err.message, task: description }, 'Decompose failed, queuing raw task'); - const keywords = description - .toLowerCase() - .replace(/[^a-z0-9\s]/g, '') - .split(/\s+/) - .filter((w) => w.length > 2); - this.playerTaskQueue.push({ description, keywords }); - }); + const interruptIfAutonomous = () => { + if (this.currentTaskSource === 'autonomous' || this.currentTaskSource === 'blackboard') { + this.interruptedByPlayer = true; + this.codeExecutor.requestInterrupt(`player task from ${requestedBy}: ${description}`); + logger.info({ bot: this.botName, task: description, requestedBy }, 'Interrupting autonomous task for player request'); + } + }; + + // Decompose asynchronously — subtasks get queued when ready, then interrupt if needed + this.decomposeAndQueue(description, requestedBy) + .then(() => interruptIfAutonomous()) + .catch((err) => { + logger.warn({ err: err.message, task: description }, 'Decompose failed, queuing raw task'); + const keywords = description + .toLowerCase() + .replace(/[^a-z0-9\s]/g, '') + .split(/\s+/) + .filter((w) => w.length > 2); + this.playerTaskQueue.push({ description, keywords }); + interruptIfAutonomous(); + }); } private async decomposeAndQueue(description: string, requestedBy: string): Promise { - const subtasks = await this.curriculumAgent.decomposeTask(this.bot, description); - for (const task of subtasks) { - this.playerTaskQueue.push(task); - } + // Don't decompose player chat requests — queue them directly and let the + // codegen execution handle prerequisites dynamically. Decomposition was + // turning "scout for an island" into "mine logs → craft planks → craft boat" + // which defeats the player's intent. + const keywords = description + .toLowerCase() + .replace(/[^a-z0-9\s]/g, '') + .split(/\s+/) + .filter((w) => w.length > 2); + this.playerTaskQueue.push({ description, keywords }); logger.info({ bot: this.botName, - goal: description, + task: description, requestedBy, - subtasks: subtasks.map((t) => t.description), - }, subtasks.length > 1 ? 'Player goal decomposed and queued' : 'Player task queued'); + }, 'Player task queued'); this.blackboardManager?.postMessage(this.botName, 'info', `Queued local task: ${description}`); } @@ -290,10 +381,10 @@ export class VoyagerLoop { } private scheduleNext(): void { - if (!this.running || this.paused) return; + if (!this.running || this.isPaused()) return; this.loopTimeout = setTimeout(async () => { - if (!this.running || this.paused) return; + if (!this.running || this.isPaused()) return; try { await this.runOneCycle(); @@ -306,7 +397,7 @@ export class VoyagerLoop { } private async runOneCycle(): Promise { - if (this.paused) return; + if (this.isPaused()) return; if (this.activeLongTermGoal?.spec.kind === 'build_structure') { await this.runBuildGoalCycle(); return; @@ -328,20 +419,23 @@ export class VoyagerLoop { const progression = getProgressionState(this.bot, (this.curriculumAgent as any).completedTasks || []); const plan = buildTaskPlan(task, progression); this.currentTask = task.description; + this.currentTaskSource = goalTask ? 'long-term-goal' : playerTask ? 'player-request' : blackboardTask ? 'blackboard' : 'autonomous'; + this.interruptedByPlayer = false; logger.info({ bot: this.botName, task: task.description, - source: goalTask ? 'long-term-goal' : playerTask ? 'player-request' : blackboardTask ? 'blackboard' : 'autonomous', + source: this.currentTaskSource, plan: plan.steps.map((step) => step.description), }, 'Voyager task proposed'); for (const step of plan.steps) { const ok = await this.executeTaskStep(step); if (!ok) { - // If the failure was caused by an instinct pause (damage), don't treat it as - // a real failure — just bail out so the goal resumes when instinct ends. - if (this.paused) { + // If the failure was caused by an instinct pause (damage) or a player task + // interrupting an autonomous task, don't treat it as a real failure. + if (this.isPaused() || this.interruptedByPlayer) { + this.interruptedByPlayer = false; this.currentTask = null; return; } @@ -458,7 +552,7 @@ export class VoyagerLoop { const gatherOk = await this.executeTaskStep({ description: gatherTask.description, keywords: gatherTask.keywords, spec: gatherTask.spec }); if (!gatherOk) { // If paused by instinct (damage), don't mark the build goal as blocked - if (this.paused) { + if (this.isPaused()) { this.currentTask = null; return; } @@ -704,6 +798,7 @@ export class VoyagerLoop { this.curriculumAgent.updateProgress(task, true); this.curriculumAgent.getBlockerMemory().clearTask(task); this.lastCompletedTask = task.description; + if (this.onTaskSuccess) this.onTaskSuccess(task.description); this.blackboardManager?.postMessage(this.botName, 'completion', `Finished ${task.description}.`); return true; } @@ -742,6 +837,7 @@ export class VoyagerLoop { events: [], }, lastError || 'task failed'); this.lastFailedTask = task.description; + if (this.onTaskFailure) this.onTaskFailure(task.description); this.blackboardManager?.postMessage(this.botName, 'blocker', `${task.description} failed: ${lastError || 'unknown error'}`); logger.warn({ bot: this.botName, task: task.description, lastError }, 'Task failed after max retries'); return false; diff --git a/src/voyager/WorldMemory.ts b/src/voyager/WorldMemory.ts index 404fa6e..ef3c09f 100644 --- a/src/voyager/WorldMemory.ts +++ b/src/voyager/WorldMemory.ts @@ -16,6 +16,7 @@ export class WorldMemory { private static MAX_RECORDS = 200; private filePath: string; private records: WorldMemoryRecord[] = []; + private _saveTimer: ReturnType | null = null; constructor(dataDir: string) { this.filePath = path.join(dataDir, 'world_memory.json'); @@ -110,7 +111,30 @@ export class WorldMemory { } private persist(): void { - fs.writeFileSync(this.filePath, JSON.stringify(this.records, null, 2)); + if (this._saveTimer) clearTimeout(this._saveTimer); + this._saveTimer = setTimeout(() => { + this._saveTimer = null; + this.writeAtomic(); + }, 2000); + } + + private writeAtomic(): void { + const tmpPath = this.filePath + '.tmp'; + try { + fs.writeFileSync(tmpPath, JSON.stringify(this.records, null, 2)); + fs.renameSync(tmpPath, this.filePath); + } catch { + try { fs.writeFileSync(this.filePath, JSON.stringify(this.records, null, 2)); } catch { /* best effort */ } + try { fs.unlinkSync(tmpPath); } catch { /* ignore */ } + } + } + + shutdown(): void { + if (this._saveTimer) { + clearTimeout(this._saveTimer); + this._saveTimer = null; + this.writeAtomic(); + } } } diff --git a/test/control/CommandCenter.test.ts b/test/control/CommandCenter.test.ts new file mode 100644 index 0000000..21e2db4 --- /dev/null +++ b/test/control/CommandCenter.test.ts @@ -0,0 +1,395 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; + +// Mock fs before importing the module +vi.mock('fs', () => ({ + existsSync: vi.fn().mockReturnValue(false), + readFileSync: vi.fn().mockReturnValue('{"commands":[]}'), + writeFileSync: vi.fn(), + mkdirSync: vi.fn(), +})); + +import { CommandCenter } from '../../src/control/CommandCenter'; +import { CommandRecord } from '../../src/control/CommandTypes'; + +function createMockIO() { + return { emit: vi.fn() } as any; +} + +function createMockBotManager() { + const mockVoyager = { + pause: vi.fn(), + resume: vi.fn(), + isRunning: vi.fn().mockReturnValue(true), + isPaused: vi.fn().mockReturnValue(false), + }; + const mockBot = { + pathfinder: { stop: vi.fn(), setGoal: vi.fn() }, + players: {}, + entity: { position: { x: 0, y: 64, z: 0 } }, + inventory: { items: vi.fn().mockReturnValue([]) }, + }; + const mockInstance = { + bot: mockBot, + getVoyagerLoop: vi.fn().mockReturnValue(mockVoyager), + name: 'TestBot', + }; + return { + getBot: vi.fn().mockReturnValue(mockInstance), + getAllBots: vi.fn().mockReturnValue([mockInstance]), + _mockInstance: mockInstance, + _mockVoyager: mockVoyager, + _mockBot: mockBot, + } as any; +} + +describe('CommandCenter', () => { + let cc: CommandCenter; + let io: ReturnType; + let bm: ReturnType; + + beforeEach(() => { + vi.clearAllMocks(); + vi.useFakeTimers(); + io = createMockIO(); + bm = createMockBotManager(); + cc = new CommandCenter(bm, io); + }); + + afterEach(() => { + cc.destroy(); + vi.useRealTimers(); + }); + + it('creates a command with valid fields', () => { + const cmd = cc.createCommand({ + type: 'pause_voyager', + targets: ['TestBot'], + }); + + expect(cmd).toBeDefined(); + expect(cmd.id).toMatch(/^cmd_/); + expect(cmd.targets).toContain('TestBot'); + expect(cmd.type).toBe('pause_voyager'); + expect(cmd.status).toBe('queued'); + expect(cmd.createdAt).toBeTypeOf('string'); + }); + + it('dispatches pause_voyager command', async () => { + const cmd = cc.createCommand({ + type: 'pause_voyager', + targets: ['TestBot'], + }); + const result = await cc.dispatchCommand(cmd); + + expect(result.status).toBe('succeeded'); + expect(bm._mockVoyager.pause).toHaveBeenCalledOnce(); + }); + + it('dispatches stop_movement command', async () => { + const cmd = cc.createCommand({ + type: 'stop_movement', + targets: ['TestBot'], + }); + const result = await cc.dispatchCommand(cmd); + + expect(result.status).toBe('succeeded'); + expect(bm._mockBot.pathfinder.stop).toHaveBeenCalledOnce(); + }); + + it('emits socket events on state changes', async () => { + const cmd = cc.createCommand({ + type: 'pause_voyager', + targets: ['TestBot'], + }); + await cc.dispatchCommand(cmd); + + const events = io.emit.mock.calls.map((c: any[]) => c[0]); + expect(events).toContain('command:queued'); + expect(events).toContain('command:started'); + expect(events).toContain('command:succeeded'); + }); + + it('supports command cancellation', () => { + const cmd = cc.createCommand({ + type: 'pause_voyager', + targets: ['TestBot'], + }); + expect(cmd.status).toBe('queued'); + + const cancelled = cc.cancelCommand(cmd.id); + expect(cancelled).toBeDefined(); + expect(cancelled!.status).toBe('cancelled'); + }); + + it('queries commands by bot name', () => { + cc.createCommand({ type: 'pause_voyager', targets: ['Alpha'] }); + cc.createCommand({ type: 'stop_movement', targets: ['Alpha'] }); + cc.createCommand({ type: 'pause_voyager', targets: ['Bravo'] }); + + const alphaCommands = cc.getCommands({ bot: 'Alpha' }); + expect(alphaCommands).toHaveLength(2); + expect(alphaCommands.every((c) => c.targets.includes('Alpha'))).toBe(true); + + const bravoCommands = cc.getCommands({ bot: 'Bravo' }); + expect(bravoCommands).toHaveLength(1); + + const allCommands = cc.getCommands(); + expect(allCommands).toHaveLength(3); + }); + + it('fans out squad-scoped commands across squad members', async () => { + const secondVoyager = { + pause: vi.fn(), + resume: vi.fn(), + isRunning: vi.fn().mockReturnValue(true), + isPaused: vi.fn().mockReturnValue(false), + }; + const secondBot = { + pathfinder: { stop: vi.fn(), setGoal: vi.fn() }, + players: {}, + entity: { position: { x: 10, y: 64, z: 10 } }, + inventory: { items: vi.fn().mockReturnValue([]) }, + }; + const secondInstance = { + bot: secondBot, + getVoyagerLoop: vi.fn().mockReturnValue(secondVoyager), + name: 'Bravo', + }; + + bm.getBot.mockImplementation((name: string) => { + if (name === 'Bravo') return secondInstance; + return bm._mockInstance; + }); + + const parent = cc.createCommand({ + type: 'pause_voyager', + scope: 'squad', + targets: ['TestBot', 'Bravo'], + }); + + const result = await cc.dispatchCommand(parent); + + expect(result.status).toBe('succeeded'); + expect(result.childCommandIds).toHaveLength(2); + expect(bm._mockVoyager.pause).toHaveBeenCalledOnce(); + expect(secondVoyager.pause).toHaveBeenCalledOnce(); + }); + + // ── Task 1: Cancellation stops pathfinder for movement commands ── + + it('stops pathfinder when cancelling a started movement command', async () => { + // Create and dispatch a walk_to_coords command + const cmd = cc.createCommand({ + type: 'walk_to_coords', + targets: ['TestBot'], + params: { x: 100, y: 64, z: 200 }, + }); + await cc.dispatchCommand(cmd); + + // It should have succeeded (mock pathfinder.setGoal works), so let's + // test cancelling a command that's in 'started' status by creating one + // and manually setting its status + const cmd2 = cc.createCommand({ + type: 'follow_player', + targets: ['TestBot'], + params: { playerName: 'Steve' }, + }); + // Force the command into started status for testing cancellation + (cmd2 as any).status = 'started'; + (cmd2 as any).startedAt = new Date().toISOString(); + + bm._mockBot.pathfinder.stop.mockClear(); + cc.cancelCommand(cmd2.id); + + expect(cmd2.status).toBe('cancelled'); + expect(bm._mockBot.pathfinder.stop).toHaveBeenCalled(); + }); + + it('does NOT stop pathfinder when cancelling a non-movement command', () => { + const cmd = cc.createCommand({ + type: 'pause_voyager', + targets: ['TestBot'], + }); + // Force into started + (cmd as any).status = 'started'; + (cmd as any).startedAt = new Date().toISOString(); + + bm._mockBot.pathfinder.stop.mockClear(); + cc.cancelCommand(cmd.id); + + expect(cmd.status).toBe('cancelled'); + expect(bm._mockBot.pathfinder.stop).not.toHaveBeenCalled(); + }); + + it('includes reason in cancellation error', () => { + const cmd = cc.createCommand({ + type: 'walk_to_coords', + targets: ['TestBot'], + params: { x: 0, y: 64, z: 0 }, + }); + cc.cancelCommand(cmd.id, 'superseded'); + + expect(cmd.status).toBe('cancelled'); + expect(cmd.error?.code).toBe('CANCELLED'); + expect(cmd.error?.message).toBe('superseded'); + }); + + // ── Task 2: Timeout handling ── + + it('times out commands that stay in started too long', () => { + const cmd = cc.createCommand({ + type: 'walk_to_coords', + targets: ['TestBot'], + params: { x: 0, y: 64, z: 0 }, + }); + // Force into started with a past startedAt + (cmd as any).status = 'started'; + (cmd as any).startedAt = new Date(Date.now() - 70_000).toISOString(); + + cc.checkTimeouts(); + + expect(cmd.status).toBe('failed'); + expect(cmd.error?.code).toBe('TIMEOUT'); + expect(cmd.error?.message).toBe('Command timed out'); + }); + + it('does not time out commands under the threshold', () => { + const cmd = cc.createCommand({ + type: 'walk_to_coords', + targets: ['TestBot'], + params: { x: 0, y: 64, z: 0 }, + }); + (cmd as any).status = 'started'; + (cmd as any).startedAt = new Date(Date.now() - 30_000).toISOString(); + + cc.checkTimeouts(); + + expect(cmd.status).toBe('started'); + }); + + it('timeout checker runs on interval', () => { + const cmd = cc.createCommand({ + type: 'walk_to_coords', + targets: ['TestBot'], + params: { x: 0, y: 64, z: 0 }, + }); + (cmd as any).status = 'started'; + (cmd as any).startedAt = new Date(Date.now() - 70_000).toISOString(); + + // Advance by 10 seconds to trigger interval + vi.advanceTimersByTime(10_000); + + expect(cmd.status).toBe('failed'); + expect(cmd.error?.code).toBe('TIMEOUT'); + }); + + // ── Task 3: Concurrent command protection ── + + it('cancels active command when a new command is dispatched for same bot', async () => { + // First command: manually force into started state + const cmd1 = cc.createCommand({ + type: 'walk_to_coords', + targets: ['TestBot'], + params: { x: 10, y: 64, z: 10 }, + }); + (cmd1 as any).status = 'started'; + (cmd1 as any).startedAt = new Date().toISOString(); + + // Second command dispatched for same bot + const cmd2 = cc.createCommand({ + type: 'stop_movement', + targets: ['TestBot'], + }); + await cc.dispatchCommand(cmd2); + + // First command should have been cancelled with 'superseded' reason + expect(cmd1.status).toBe('cancelled'); + expect(cmd1.error?.message).toBe('superseded'); + + // Second command should have succeeded + expect(cmd2.status).toBe('succeeded'); + }); + + // ── Task 4: Bot validation ── + + it('fails with BOT_NOT_FOUND if bot does not exist', async () => { + bm.getBot.mockReturnValue(null); + + const cmd = cc.createCommand({ + type: 'pause_voyager', + targets: ['NonExistentBot'], + }); + const result = await cc.dispatchCommand(cmd); + + expect(result.status).toBe('failed'); + expect(result.error?.code).toBe('BOT_NOT_FOUND'); + }); + + it('fails with BOT_OFFLINE if bot is not connected', async () => { + bm._mockInstance.bot = null; + + const cmd = cc.createCommand({ + type: 'stop_movement', + targets: ['TestBot'], + }); + const result = await cc.dispatchCommand(cmd); + + expect(result.status).toBe('failed'); + expect(result.error?.code).toBe('BOT_OFFLINE'); + }); + + it('fails with NO_TARGET if no target specified', async () => { + const cmd = cc.createCommand({ + type: 'pause_voyager', + targets: [], + }); + const result = await cc.dispatchCommand(cmd); + + expect(result.status).toBe('failed'); + expect(result.error?.code).toBe('NO_TARGET'); + }); + + // ── Task 6: Structured logging ── + + it('logs lifecycle transitions with structured fields', async () => { + // We just verify the command gets timestamps set correctly on transitions + const cmd = cc.createCommand({ + type: 'pause_voyager', + targets: ['TestBot'], + }); + await cc.dispatchCommand(cmd); + + expect(cmd.startedAt).toBeTypeOf('string'); + expect(cmd.completedAt).toBeTypeOf('string'); + + // Verify durations are calculable + const started = new Date(cmd.startedAt!).getTime(); + const completed = new Date(cmd.completedAt!).getTime(); + expect(completed).toBeGreaterThanOrEqual(started); + }); + + // ── Cleanup: removes old commands ── + + it.todo('cleanup removes commands older than 24 hours — cleanup() not implemented; persist() auto-trims to 500'); + + it.todo('cleanup caps at 500 commands — cleanup() not implemented; persist() auto-trims to 500'); + + // ── Shutdown: cancels active commands ── + + it.todo('shutdown cancels all active commands — shutdown() not implemented; use destroy() to stop timers'); + + // ── Persistence ── + + it('persist writes synchronously on each mutation', async () => { + const fs = await import('fs'); + const writeFileSync = vi.mocked(fs.writeFileSync); + writeFileSync.mockClear(); + + cc.createCommand({ type: 'pause_voyager', targets: ['TestBot'] }); + cc.createCommand({ type: 'pause_voyager', targets: ['TestBot'] }); + cc.createCommand({ type: 'pause_voyager', targets: ['TestBot'] }); + + // persist() is called synchronously on each createCommand, not debounced + expect(writeFileSync.mock.calls.length).toBeGreaterThanOrEqual(3); + }); +}); diff --git a/test/control/CommanderService.test.ts b/test/control/CommanderService.test.ts new file mode 100644 index 0000000..7ca7e42 --- /dev/null +++ b/test/control/CommanderService.test.ts @@ -0,0 +1,271 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +// Mock fs before importing the modules +vi.mock('fs', () => ({ + default: { + existsSync: vi.fn().mockReturnValue(false), + readFileSync: vi.fn().mockReturnValue('[]'), + writeFileSync: vi.fn(), + mkdirSync: vi.fn(), + }, + existsSync: vi.fn().mockReturnValue(false), + readFileSync: vi.fn().mockReturnValue('[]'), + writeFileSync: vi.fn(), + mkdirSync: vi.fn(), +})); + +import { CommanderService, CommanderServiceDeps } from '../../src/control/CommanderService'; +import { CommandRecord } from '../../src/control/CommandTypes'; +import { MissionRecord } from '../../src/control/MissionTypes'; + +function createMockIO() { + return { emit: vi.fn() } as any; +} + +function createMockBotManager(bots: { name: string; personality: string }[] = []) { + const mockInstances = bots.map((b) => ({ + name: b.name, + personality: b.personality, + getStatus: vi.fn().mockReturnValue({ + mode: 'codegen', + position: { x: 0, y: 64, z: 0 }, + }), + })); + return { + getBot: vi.fn((name: string) => mockInstances.find((b) => b.name === name) ?? null), + getAllBots: vi.fn().mockReturnValue(mockInstances), + } as any; +} + +function createMockCommandCenter() { + const commands: CommandRecord[] = []; + return { + createCommand: vi.fn((params: any) => { + const cmd: CommandRecord = { + id: `cmd_${Date.now()}_test`, + type: params.type, + scope: 'single', + priority: params.priority ?? 'normal', + source: params.source ?? 'api', + status: 'queued', + targets: params.targets, + params: params.params ?? {}, + createdAt: new Date().toISOString(), + }; + commands.push(cmd); + return cmd; + }), + dispatchCommand: vi.fn(async (cmd: CommandRecord) => { + cmd.status = 'succeeded'; + return cmd; + }), + getCommands: vi.fn(() => commands), + } as any; +} + +function createMockMissionManager() { + const missions: MissionRecord[] = []; + return { + createMission: vi.fn((params: any) => { + const msn: MissionRecord = { + id: `msn_${Date.now()}_test`, + type: params.type, + title: params.title, + description: params.description, + assigneeType: params.assigneeType, + assigneeIds: params.assigneeIds, + status: 'queued', + priority: params.priority ?? 'normal', + steps: [], + createdAt: Date.now(), + updatedAt: Date.now(), + source: params.source ?? 'commander', + }; + missions.push(msn); + return msn; + }), + getMissions: vi.fn(() => missions), + } as any; +} + +function createMockMarkerStore() { + return { + getMarkers: vi.fn().mockReturnValue([]), + getZones: vi.fn().mockReturnValue([]), + getRoutes: vi.fn().mockReturnValue([]), + } as any; +} + +describe('CommanderService', () => { + let service: CommanderService; + let deps: CommanderServiceDeps; + let commandCenter: ReturnType; + let missionManager: ReturnType; + + beforeEach(() => { + vi.clearAllMocks(); + commandCenter = createMockCommandCenter(); + missionManager = createMockMissionManager(); + + deps = { + llmClient: null, + botManager: createMockBotManager(), + commandCenter, + missionManager, + markerStore: createMockMarkerStore(), + }; + service = new CommanderService(deps); + }); + + // ── Parse with no LLM ──────────────────────────────── + + it('returns low confidence plan when no LLM is configured', async () => { + const plan = await service.parse('send all bots to the mine'); + + expect(plan).toBeDefined(); + expect(plan.id).toMatch(/^plan_/); + expect(plan.input).toBe('send all bots to the mine'); + expect(plan.confidence).toBe(0); + expect(plan.requiresConfirmation).toBe(true); + expect(plan.warnings).toContain('No LLM configured — natural language parsing is unavailable'); + expect(plan.commands).toEqual([]); + expect(plan.missions).toEqual([]); + expect(plan.createdAt).toBeTypeOf('string'); + }); + + // ── Parse returns valid plan structure ──────────────── + + it('stores the plan and retrieves it by ID', async () => { + const plan = await service.parse('hello'); + + const retrieved = service.getPlan(plan.id); + expect(retrieved).toBeDefined(); + expect(retrieved!.id).toBe(plan.id); + expect(retrieved!.input).toBe('hello'); + }); + + it('returns valid plan structure fields', async () => { + const plan = await service.parse('test input'); + + // All required plan fields must be present + expect(plan).toHaveProperty('id'); + expect(plan).toHaveProperty('input'); + expect(plan).toHaveProperty('intent'); + expect(plan).toHaveProperty('confidence'); + expect(plan).toHaveProperty('warnings'); + expect(plan).toHaveProperty('requiresConfirmation'); + expect(plan).toHaveProperty('commands'); + expect(plan).toHaveProperty('missions'); + expect(plan).toHaveProperty('createdAt'); + + expect(Array.isArray(plan.commands)).toBe(true); + expect(Array.isArray(plan.missions)).toBe(true); + expect(Array.isArray(plan.warnings)).toBe(true); + }); + + // ── Parse with LLM (mocked) ────────────────────────── + + it('parses LLM response into a structured plan', async () => { + const mockLLM = { + generate: vi.fn().mockResolvedValue({ + text: JSON.stringify({ + intent: 'Pause the bot', + confidence: 0.95, + warnings: [], + commands: [{ type: 'pause_voyager', targets: ['TestBot'], payload: {} }], + missions: [], + }), + }), + }; + + const bm = createMockBotManager([{ name: 'TestBot', personality: 'farmer' }]); + const svc = new CommanderService({ + ...deps, + llmClient: mockLLM as any, + botManager: bm, + }); + + const plan = await svc.parse('pause TestBot'); + + expect(plan.confidence).toBe(0.95); + expect(plan.intent).toBe('Pause the bot'); + expect(plan.commands).toHaveLength(1); + expect(plan.commands[0].type).toBe('pause_voyager'); + expect(plan.commands[0].targets).toEqual(['TestBot']); + expect(plan.requiresConfirmation).toBe(false); // high confidence, no warnings + }); + + it('handles LLM returning invalid JSON gracefully', async () => { + const mockLLM = { + generate: vi.fn().mockResolvedValue({ text: 'not valid json at all' }), + }; + + const svc = new CommanderService({ + ...deps, + llmClient: mockLLM as any, + }); + + const plan = await svc.parse('do something'); + + expect(plan.confidence).toBe(0); + expect(plan.warnings).toContain('LLM response was not valid JSON'); + expect(plan.commands).toEqual([]); + expect(plan.missions).toEqual([]); + }); + + it('handles LLM error gracefully', async () => { + const mockLLM = { + generate: vi.fn().mockRejectedValue(new Error('API quota exceeded')), + }; + + const svc = new CommanderService({ + ...deps, + llmClient: mockLLM as any, + }); + + const plan = await svc.parse('test'); + + expect(plan.confidence).toBe(0); + expect(plan.warnings.some((w) => w.includes('API quota exceeded'))).toBe(true); + }); + + // ── Execute dispatches commands and creates missions ── + + it('executes a plan by dispatching commands and creating missions', async () => { + // First parse to get a plan (no LLM, so we set up the plan manually) + const plan = await service.parse('pause TestBot'); + + // Manually inject commands/missions into the stored plan for execution test + const storedPlan = service.getPlan(plan.id)!; + storedPlan.commands = [ + { type: 'pause_voyager', targets: ['TestBot'], payload: {} }, + ]; + storedPlan.missions = [ + { type: 'gather_items', title: 'Gather wood', assigneeIds: ['TestBot'] }, + ]; + + const result = await service.execute(plan.id); + + expect(result).toBeDefined(); + expect(commandCenter.createCommand).toHaveBeenCalledOnce(); + expect(commandCenter.dispatchCommand).toHaveBeenCalledOnce(); + expect(missionManager.createMission).toHaveBeenCalledOnce(); + + expect(result!.commands).toHaveLength(1); + expect(result!.missions).toHaveLength(1); + expect(result!.commands[0].type).toBe('pause_voyager'); + expect(result!.missions[0].type).toBe('gather_items'); + }); + + // ── Unknown plan ID returns null ────────────────────── + + it('returns null when executing a nonexistent plan', async () => { + const result = await service.execute('plan_nonexistent'); + expect(result).toBeNull(); + }); + + it('returns null from getPlan for unknown ID', () => { + const plan = service.getPlan('plan_does_not_exist'); + expect(plan).toBeUndefined(); + }); +}); diff --git a/test/control/MarkerStore.test.ts b/test/control/MarkerStore.test.ts new file mode 100644 index 0000000..d55245a --- /dev/null +++ b/test/control/MarkerStore.test.ts @@ -0,0 +1,101 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +// Mock fs before importing the module +vi.mock('fs', () => ({ + existsSync: vi.fn().mockReturnValue(false), + readFileSync: vi.fn().mockReturnValue('[]'), + writeFileSync: vi.fn(), + mkdirSync: vi.fn(), +})); + +import { MarkerStore } from '../../src/control/MarkerStore'; + +function createMockIO() { + return { emit: vi.fn() } as any; +} + +describe('MarkerStore', () => { + let store: MarkerStore; + let io: ReturnType; + + beforeEach(() => { + vi.clearAllMocks(); + io = createMockIO(); + store = new MarkerStore(io); + }); + + it('creates a marker with valid fields', () => { + const marker = store.createMarker({ + name: 'Home Base', + kind: 'base', + position: { x: 100, y: 64, z: -200 }, + }); + + expect(marker).toBeDefined(); + expect(marker.id).toMatch(/^mkr_/); + expect(marker.name).toBe('Home Base'); + expect(marker.position.x).toBe(100); + expect(marker.position.y).toBe(64); + expect(marker.position.z).toBe(-200); + expect(marker.kind).toBe('base'); + expect(marker.createdAt).toBeTypeOf('number'); + expect(marker.updatedAt).toBeTypeOf('number'); + }); + + it('updates a marker', () => { + const marker = store.createMarker({ + name: 'Old Name', + kind: 'custom', + position: { x: 0, y: 0, z: 0 }, + }); + const updated = store.updateMarker(marker.id, { name: 'New Name', position: { x: 50, y: 0, z: 0 } }); + + expect(updated).toBeDefined(); + expect(updated!.name).toBe('New Name'); + expect(updated!.position.x).toBe(50); + expect(updated!.position.y).toBe(0); // unchanged + expect(updated!.updatedAt).toBeGreaterThanOrEqual(marker.createdAt); + + // Verify persistence via getMarker + const fetched = store.getMarker(marker.id); + expect(fetched!.name).toBe('New Name'); + }); + + it('deletes a marker', () => { + const marker = store.createMarker({ + name: 'Temp', + kind: 'custom', + position: { x: 10, y: 20, z: 30 }, + }); + expect(store.getMarkers()).toHaveLength(1); + + const deleted = store.deleteMarker(marker.id); + expect(deleted).toBe(true); + expect(store.getMarker(marker.id)).toBeUndefined(); + expect(store.getMarkers()).toHaveLength(0); + + // Deleting nonexistent returns false + expect(store.deleteMarker('nonexistent')).toBe(false); + }); + + it('creates and retrieves zones', () => { + const zone = store.createZone({ + name: 'Mining Area', + mode: 'mine' as any, + shape: 'rectangle', + rectangle: { minX: 0, minZ: 0, maxX: 100, maxZ: 100 }, + }); + + expect(zone).toBeDefined(); + expect(zone.id).toMatch(/^zne_/); + expect(zone.name).toBe('Mining Area'); + expect(zone.shape).toBe('rectangle'); + + const fetched = store.getZone(zone.id); + expect(fetched).toEqual(zone); + + const all = store.getZones(); + expect(all).toHaveLength(1); + expect(all[0].id).toBe(zone.id); + }); +}); diff --git a/test/control/MissionManager.test.ts b/test/control/MissionManager.test.ts new file mode 100644 index 0000000..47a1fd7 --- /dev/null +++ b/test/control/MissionManager.test.ts @@ -0,0 +1,467 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; + +// Mock fs before importing the module +vi.mock('fs', () => ({ + default: { + existsSync: vi.fn().mockReturnValue(false), + readFileSync: vi.fn().mockReturnValue('{"missions":[],"botQueues":{}}'), + writeFileSync: vi.fn(), + mkdirSync: vi.fn(), + }, + existsSync: vi.fn().mockReturnValue(false), + readFileSync: vi.fn().mockReturnValue('{"missions":[],"botQueues":{}}'), + writeFileSync: vi.fn(), + mkdirSync: vi.fn(), +})); + +import { MissionManager } from '../../src/control/MissionManager'; +import { SquadManager } from '../../src/control/SquadManager'; + +function createMockIO() { + return { emit: vi.fn() } as any; +} + +function createMockBotManager() { + return { + getBot: vi.fn().mockReturnValue(null), + getAllBots: vi.fn().mockReturnValue([]), + } as any; +} + +describe('MissionManager', () => { + let mm: MissionManager; + let io: ReturnType; + let bm: ReturnType; + let squadManager: SquadManager; + + beforeEach(() => { + vi.clearAllMocks(); + vi.useFakeTimers(); + io = createMockIO(); + bm = createMockBotManager(); + mm = new MissionManager(bm, io); + squadManager = new SquadManager(io as any); + mm.setSquadManager(squadManager); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it('creates a mission with valid fields', () => { + const mission = mm.createMission({ + type: 'gather_items', + title: 'Gather wood', + description: 'Collect 64 oak logs', + assigneeType: 'bot', + assigneeIds: ['TestBot'], + }); + + expect(mission).toBeDefined(); + expect(mission.id).toMatch(/^msn_/); + expect(mission.assigneeIds).toContain('TestBot'); + expect(mission.title).toBe('Gather wood'); + expect(mission.description).toBe('Collect 64 oak logs'); + expect(mission.status).toBe('queued'); + expect(mission.createdAt).toBeTypeOf('number'); + expect(mission.updatedAt).toBeTypeOf('number'); + }); + + it('transitions mission through status lifecycle', () => { + const mission = mm.createMission({ + type: 'gather_items', + title: 'Build house', + assigneeType: 'bot', + assigneeIds: ['TestBot'], + }); + + expect(mission.status).toBe('queued'); + + mm.updateMissionStatus(mission.id, 'running'); + expect(mm.getMission(mission.id)!.status).toBe('running'); + + mm.updateMissionStatus(mission.id, 'paused'); + expect(mm.getMission(mission.id)!.status).toBe('paused'); + + mm.updateMissionStatus(mission.id, 'running'); + expect(mm.getMission(mission.id)!.status).toBe('running'); + + mm.updateMissionStatus(mission.id, 'completed'); + expect(mm.getMission(mission.id)!.status).toBe('completed'); + }); + + it('emits socket events on status changes', () => { + const mission = mm.createMission({ + type: 'gather_items', + title: 'Mine diamonds', + assigneeType: 'bot', + assigneeIds: ['TestBot'], + }); + mm.updateMissionStatus(mission.id, 'running'); + mm.updateMissionStatus(mission.id, 'completed'); + + const events = io.emit.mock.calls.map((c: any[]) => c[0]); + expect(events).toContain('mission:created'); + expect(events).toContain('mission:updated'); + expect(events).toContain('mission:completed'); + }); + + it('filters missions by bot name', () => { + mm.createMission({ type: 'gather_items', title: 'Task A', assigneeType: 'bot', assigneeIds: ['Alpha'] }); + mm.createMission({ type: 'gather_items', title: 'Task B', assigneeType: 'bot', assigneeIds: ['Alpha'] }); + mm.createMission({ type: 'gather_items', title: 'Task C', assigneeType: 'bot', assigneeIds: ['Bravo'] }); + + const alphaMissions = mm.getMissions({ bot: 'Alpha' }); + expect(alphaMissions).toHaveLength(2); + expect(alphaMissions.every((m) => m.assigneeIds.includes('Alpha'))).toBe(true); + + const bravoMissions = mm.getMissions({ bot: 'Bravo' }); + expect(bravoMissions).toHaveLength(1); + + const allMissions = mm.getMissions(); + expect(allMissions).toHaveLength(3); + }); + + it('includes squad missions when filtering by bot name', () => { + const squad = squadManager.createSquad({ name: 'Builders', botNames: ['Alpha', 'Bravo'] }); + + const squadMission = mm.createMission({ + type: 'build_schematic', + title: 'Build watchtower', + assigneeType: 'squad', + assigneeIds: [squad.id], + }); + + const alphaMissions = mm.getMissions({ bot: 'Alpha' }); + expect(alphaMissions.map((mission) => mission.id)).toContain(squadMission.id); + + const bravoMissions = mm.getMissions({ bot: 'Bravo' }); + expect(bravoMissions.map((mission) => mission.id)).toContain(squadMission.id); + + const charlieMissions = mm.getMissions({ bot: 'Charlie' }); + expect(charlieMissions).toHaveLength(0); + }); + + it('supports mission cancellation', () => { + const mission = mm.createMission({ + type: 'gather_items', + title: 'Explore cave', + assigneeType: 'bot', + assigneeIds: ['TestBot'], + }); + mm.updateMissionStatus(mission.id, 'running'); + + const cancelled = mm.cancelMission(mission.id); + expect(cancelled).toBeDefined(); + expect(cancelled!.status).toBe('cancelled'); + }); + + // ── VoyagerLoop bridge (queue_task) ────────────────── + + it('queues task to VoyagerLoop when starting a queue_task mission', async () => { + const mockQueuePlayerTask = vi.fn(); + const mockVoyager = { + queuePlayerTask: mockQueuePlayerTask, + getCompletedTasks: vi.fn().mockReturnValue([]), + getFailedTasks: vi.fn().mockReturnValue([]), + }; + const mockBot = { + getVoyagerLoop: vi.fn().mockReturnValue(mockVoyager), + }; + bm.getBot.mockReturnValue(mockBot); + + const mission = mm.createMission({ + type: 'queue_task', + title: 'Mine 10 diamonds', + description: 'Mine 10 diamonds near spawn', + assigneeType: 'bot', + assigneeIds: ['Miner'], + }); + + const started = await mm.startMission(mission.id); + expect(started).toBeDefined(); + expect(started!.status).toBe('running'); + expect(mockQueuePlayerTask).toHaveBeenCalledWith('Mine 10 diamonds near spawn', 'mission'); + }); + + it('uses title as fallback when description is undefined for queue_task', async () => { + const mockQueuePlayerTask = vi.fn(); + const mockVoyager = { + queuePlayerTask: mockQueuePlayerTask, + getCompletedTasks: vi.fn().mockReturnValue([]), + getFailedTasks: vi.fn().mockReturnValue([]), + }; + bm.getBot.mockReturnValue({ getVoyagerLoop: vi.fn().mockReturnValue(mockVoyager) }); + + const mission = mm.createMission({ + type: 'queue_task', + title: 'Gather wood', + assigneeType: 'bot', + assigneeIds: ['Worker'], + }); + + await mm.startMission(mission.id); + expect(mockQueuePlayerTask).toHaveBeenCalledWith('Gather wood', 'mission'); + }); + + // ── Mission completion tracking ────────────────────── + + it('marks mission completed when VoyagerLoop completes the task', async () => { + const mockVoyager = { + queuePlayerTask: vi.fn(), + getCompletedTasks: vi.fn().mockReturnValue(['Mine 10 diamonds']), + getFailedTasks: vi.fn().mockReturnValue([]), + }; + bm.getBot.mockReturnValue({ getVoyagerLoop: vi.fn().mockReturnValue(mockVoyager) }); + + const mission = mm.createMission({ + type: 'queue_task', + title: 'Mine 10 diamonds', + assigneeType: 'bot', + assigneeIds: ['Miner'], + }); + + await mm.startMission(mission.id); + expect(mm.getMission(mission.id)!.status).toBe('running'); + + mm.checkMissionProgress(); + expect(mm.getMission(mission.id)!.status).toBe('completed'); + }); + + it('marks mission failed when VoyagerLoop fails the task', async () => { + const mockVoyager = { + queuePlayerTask: vi.fn(), + getCompletedTasks: vi.fn().mockReturnValue([]), + getFailedTasks: vi.fn().mockReturnValue(['Mine 10 diamonds']), + }; + bm.getBot.mockReturnValue({ getVoyagerLoop: vi.fn().mockReturnValue(mockVoyager) }); + + const mission = mm.createMission({ + type: 'queue_task', + title: 'Mine 10 diamonds', + assigneeType: 'bot', + assigneeIds: ['Miner'], + }); + + await mm.startMission(mission.id); + mm.checkMissionProgress(); + expect(mm.getMission(mission.id)!.status).toBe('failed'); + + const events = io.emit.mock.calls.map((c: any[]) => c[0]); + expect(events).toContain('mission:failed'); + }); + + // ── Stale mission detection ────────────────────────── + + it('flags stale missions running for over 30 minutes', () => { + const mission = mm.createMission({ + type: 'gather_items', + title: 'Long task', + assigneeType: 'bot', + assigneeIds: ['TestBot'], + }); + + mm.updateMissionStatus(mission.id, 'running'); + // Manually backdate startedAt to simulate a stale mission + const record = mm.getMission(mission.id)!; + record.startedAt = Date.now() - 31 * 60 * 1000; + + mm.checkMissionProgress(); + expect(record.blockedReason).toBe('Stale - running for over 30 minutes'); + // Status should remain 'running' + expect(record.status).toBe('running'); + }); + + it('does not flag missions under 30 minutes as stale', () => { + const mission = mm.createMission({ + type: 'gather_items', + title: 'Quick task', + assigneeType: 'bot', + assigneeIds: ['TestBot'], + }); + + mm.updateMissionStatus(mission.id, 'running'); + + mm.checkMissionProgress(); + expect(mm.getMission(mission.id)!.blockedReason).toBeUndefined(); + }); + + // ── Dependency check (canStart) ────────────────────── + + it('canStart returns true when no linkedCommandIds', () => { + const mission = mm.createMission({ + type: 'gather_items', + title: 'Free mission', + assigneeType: 'bot', + assigneeIds: ['TestBot'], + }); + + expect(mm.canStart(mission)).toBe(true); + }); + + it('canStart returns false when CommandCenter not set and linkedCommandIds present', () => { + const mission = mm.createMission({ + type: 'gather_items', + title: 'Dependent mission', + assigneeType: 'bot', + assigneeIds: ['TestBot'], + linkedCommandIds: ['cmd_123'], + }); + + expect(mm.canStart(mission)).toBe(false); + }); + + it('canStart returns true when all linked commands have succeeded', () => { + const mockCC = { + getCommand: vi.fn().mockReturnValue({ status: 'succeeded' }), + } as any; + mm.setCommandCenter(mockCC); + + const mission = mm.createMission({ + type: 'gather_items', + title: 'Dependent mission', + assigneeType: 'bot', + assigneeIds: ['TestBot'], + linkedCommandIds: ['cmd_1', 'cmd_2'], + }); + + expect(mm.canStart(mission)).toBe(true); + expect(mockCC.getCommand).toHaveBeenCalledTimes(2); + }); + + it('canStart returns false when a linked command has not succeeded', () => { + const mockCC = { + getCommand: vi.fn() + .mockReturnValueOnce({ status: 'succeeded' }) + .mockReturnValueOnce({ status: 'queued' }), + } as any; + mm.setCommandCenter(mockCC); + + const mission = mm.createMission({ + type: 'gather_items', + title: 'Blocked mission', + assigneeType: 'bot', + assigneeIds: ['TestBot'], + linkedCommandIds: ['cmd_ok', 'cmd_pending'], + }); + + expect(mm.canStart(mission)).toBe(false); + }); + + it('startMission blocks when dependencies not met', async () => { + const mockCC = { + getCommand: vi.fn().mockReturnValue({ status: 'queued' }), + } as any; + mm.setCommandCenter(mockCC); + + const mission = mm.createMission({ + type: 'gather_items', + title: 'Blocked mission', + assigneeType: 'bot', + assigneeIds: ['TestBot'], + linkedCommandIds: ['cmd_pending'], + }); + + const result = await mm.startMission(mission.id); + expect(result).toBeDefined(); + expect(result!.status).toBe('queued'); + expect(result!.blockedReason).toContain('cmd_pending'); + }); + + // ── Per-bot queue ordering ────────────────────────── + + it('getBotMissionQueue preserves explicit queue order', () => { + mm.createMission({ + type: 'gather_items', + title: 'Low priority task', + assigneeType: 'bot', + assigneeIds: ['TestBot'], + priority: 'low', + }); + mm.createMission({ + type: 'gather_items', + title: 'Urgent task', + assigneeType: 'bot', + assigneeIds: ['TestBot'], + priority: 'urgent', + }); + mm.createMission({ + type: 'gather_items', + title: 'Normal task', + assigneeType: 'bot', + assigneeIds: ['TestBot'], + priority: 'normal', + }); + + const queue = mm.getBotMissionQueue('TestBot'); + expect(queue).toHaveLength(3); + expect(queue[0].title).toBe('Low priority task'); + expect(queue[1].title).toBe('Urgent task'); + expect(queue[2].title).toBe('Normal task'); + }); + + // ── Socket event completeness ──────────────────────── + + it('emits mission:failed event on failure', () => { + const mission = mm.createMission({ + type: 'gather_items', + title: 'Fail task', + assigneeType: 'bot', + assigneeIds: ['TestBot'], + }); + mm.updateMissionStatus(mission.id, 'running'); + mm.updateMissionStatus(mission.id, 'failed', { error: 'timeout' }); + + const events = io.emit.mock.calls.map((c: any[]) => c[0]); + expect(events).toContain('mission:failed'); + expect(events).toContain('mission:updated'); + }); + + it('emits mission:cancelled event on cancel', () => { + const mission = mm.createMission({ + type: 'gather_items', + title: 'Cancel task', + assigneeType: 'bot', + assigneeIds: ['TestBot'], + }); + mm.cancelMission(mission.id); + + const events = io.emit.mock.calls.map((c: any[]) => c[0]); + expect(events).toContain('mission:cancelled'); + expect(events).toContain('mission:updated'); + }); + + // ── Cleanup ────────────────────────────────────── + + it.todo('cleanup removes old completed/failed missions — cleanup() not yet implemented on MissionManager'); + + it.todo('cleanup caps at 200 missions — cleanup() not yet implemented on MissionManager'); + + // ── Shutdown ────────────────────────────────────── + + it.todo('shutdown cancels all running and queued missions — shutdown() not yet implemented on MissionManager'); + + // ── Persistence ────────────────────────────────── + + it('save writes synchronously on each mutation', async () => { + const fs = await import('fs'); + const writeFileSync = vi.mocked(fs.default.writeFileSync); + writeFileSync.mockClear(); + + mm.createMission({ + type: 'gather_items', + title: 'Task 1', + assigneeType: 'bot', + assigneeIds: ['TestBot'], + }); + mm.createMission({ + type: 'gather_items', + title: 'Task 2', + assigneeType: 'bot', + assigneeIds: ['TestBot'], + }); + + // save() is called synchronously on each createMission, not debounced + expect(writeFileSync.mock.calls.length).toBeGreaterThanOrEqual(2); + }); +}); diff --git a/test/control/RoleManager.test.ts b/test/control/RoleManager.test.ts new file mode 100644 index 0000000..8c0e686 --- /dev/null +++ b/test/control/RoleManager.test.ts @@ -0,0 +1,277 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +// Mock fs before importing the module +vi.mock('fs', () => ({ + existsSync: vi.fn().mockReturnValue(false), + readFileSync: vi.fn().mockReturnValue('[]'), + writeFileSync: vi.fn(), + mkdirSync: vi.fn(), +})); + +import { RoleManager } from '../../src/control/RoleManager'; + +function createMockIO() { + return { emit: vi.fn() } as any; +} + +describe('RoleManager', () => { + let rm: RoleManager; + let io: ReturnType; + let mockMissionManager: { createMission: ReturnType; getMissions: ReturnType; cancelMission: ReturnType }; + + beforeEach(() => { + vi.clearAllMocks(); + io = createMockIO(); + rm = new RoleManager(io); + mockMissionManager = { + createMission: vi.fn().mockReturnValue({ id: 'mission-1' }), + getMissions: vi.fn().mockReturnValue([]), + cancelMission: vi.fn(), + }; + rm.setMissionManager(mockMissionManager as any); + }); + + // ── Create assignment ───────────────────────────────── + + it('creates a role assignment with valid fields', () => { + const assignment = rm.createAssignment({ + botName: 'Miner1', + role: 'miner', + autonomyLevel: 'autonomous', + }); + + expect(assignment).toBeDefined(); + expect(assignment.id).toMatch(/^role_/); + expect(assignment.botName).toBe('Miner1'); + expect(assignment.role).toBe('miner'); + expect(assignment.autonomyLevel).toBe('autonomous'); + expect(assignment.allowedZoneIds).toEqual([]); + expect(assignment.preferredMissionTypes).toEqual([]); + }); + + it('throws on invalid role', () => { + expect(() => + rm.createAssignment({ + botName: 'Bot1', + role: 'invalid' as any, + autonomyLevel: 'manual', + }), + ).toThrow('Invalid role: invalid'); + }); + + it('throws on invalid autonomy level', () => { + expect(() => + rm.createAssignment({ + botName: 'Bot1', + role: 'guard', + autonomyLevel: 'turbo' as any, + }), + ).toThrow('Invalid autonomy level: turbo'); + }); + + // ── One-role-per-bot replacement ────────────────────── + + it('replaces existing assignment when assigning a new role to the same bot', () => { + const first = rm.createAssignment({ + botName: 'Worker', + role: 'farmer', + autonomyLevel: 'assisted', + }); + + const second = rm.createAssignment({ + botName: 'Worker', + role: 'builder', + autonomyLevel: 'autonomous', + }); + + // Only one assignment for the bot + const all = rm.getAssignments(); + const workerAssignments = all.filter((a) => a.botName === 'Worker'); + expect(workerAssignments).toHaveLength(1); + expect(workerAssignments[0].role).toBe('builder'); + expect(workerAssignments[0].id).toBe(second.id); + + // Old assignment should not be retrievable + expect(rm.getAssignment(first.id)).toBeNull(); + }); + + // ── Update assignment ───────────────────────────────── + + it('updates an assignment', () => { + const assignment = rm.createAssignment({ + botName: 'Guard1', + role: 'guard', + autonomyLevel: 'manual', + }); + + const updated = rm.updateAssignment(assignment.id, { + autonomyLevel: 'assisted', + allowedZoneIds: ['zone1', 'zone2'], + }); + + expect(updated).toBeDefined(); + expect(updated!.autonomyLevel).toBe('assisted'); + expect(updated!.allowedZoneIds).toEqual(['zone1', 'zone2']); + // Unchanged fields preserved + expect(updated!.role).toBe('guard'); + expect(updated!.botName).toBe('Guard1'); + }); + + it('returns null when updating a nonexistent assignment', () => { + const result = rm.updateAssignment('nonexistent', { role: 'miner' }); + expect(result).toBeNull(); + }); + + it('throws when updating with invalid role', () => { + const assignment = rm.createAssignment({ + botName: 'Bot1', + role: 'scout', + autonomyLevel: 'manual', + }); + expect(() => rm.updateAssignment(assignment.id, { role: 'pirate' as any })).toThrow( + 'Invalid role: pirate', + ); + }); + + // ── Delete assignment ───────────────────────────────── + + it('deletes an assignment', () => { + const assignment = rm.createAssignment({ + botName: 'Temp', + role: 'free-agent', + autonomyLevel: 'autonomous', + }); + + expect(rm.deleteAssignment(assignment.id)).toBe(true); + expect(rm.getAssignment(assignment.id)).toBeNull(); + expect(rm.getAssignments()).toHaveLength(0); + }); + + it('returns false when deleting a nonexistent assignment', () => { + expect(rm.deleteAssignment('nonexistent')).toBe(false); + }); + + // ── getAssignmentForBot ─────────────────────────────── + + it('retrieves assignment for a specific bot', () => { + rm.createAssignment({ botName: 'Alpha', role: 'miner', autonomyLevel: 'autonomous' }); + rm.createAssignment({ botName: 'Bravo', role: 'guard', autonomyLevel: 'manual' }); + + const alphaRole = rm.getAssignmentForBot('Alpha'); + expect(alphaRole).toBeDefined(); + expect(alphaRole!.role).toBe('miner'); + + const unknownRole = rm.getAssignmentForBot('Charlie'); + expect(unknownRole).toBeNull(); + }); + + // ── Override tracking ───────────────────────────────── + + it('sets and retrieves an override', () => { + rm.setOverride('Bot1', 'emergency recall', 'cmd_123'); + + expect(rm.isOverridden('Bot1')).toBe(true); + const override = rm.getOverride('Bot1'); + expect(override).toBeDefined(); + expect(override!.reason).toBe('emergency recall'); + expect(override!.commandId).toBe('cmd_123'); + expect(override!.at).toBeTypeOf('number'); + }); + + it('clears an override', () => { + rm.setOverride('Bot1', 'test', 'cmd_1'); + expect(rm.isOverridden('Bot1')).toBe(true); + + rm.clearOverride('Bot1'); + expect(rm.isOverridden('Bot1')).toBe(false); + expect(rm.getOverride('Bot1')).toBeNull(); + }); + + it('clearing a nonexistent override is a no-op', () => { + // Should not throw or emit + rm.clearOverride('NoSuchBot'); + expect(io.emit).not.toHaveBeenCalled(); + }); + + it('getOverrides returns all active overrides', () => { + rm.setOverride('A', 'reason1', 'cmd_1'); + rm.setOverride('B', 'reason2', 'cmd_2'); + + const overrides = rm.getOverrides(); + expect(Object.keys(overrides)).toHaveLength(2); + expect(overrides['A'].reason).toBe('reason1'); + expect(overrides['B'].reason).toBe('reason2'); + }); + + // ── Override expiry ─────────────────────────────────── + + it('expires overrides older than 5 minutes', () => { + rm.setOverride('Bot1', 'old override', 'cmd_old'); + + // Manually backdate the override + const override = rm.getOverride('Bot1')!; + override.at = Date.now() - 6 * 60 * 1000; // 6 minutes ago + + rm.checkOverrideTimeouts(); + + expect(rm.isOverridden('Bot1')).toBe(false); + }); + + it('does not expire overrides under 5 minutes', () => { + rm.setOverride('Bot1', 'recent override', 'cmd_new'); + + rm.checkOverrideTimeouts(); + + expect(rm.isOverridden('Bot1')).toBe(true); + }); + + it('creates approval requests for assisted automation instead of missions', () => { + rm.createAssignment({ + botName: 'Builder1', + role: 'builder', + autonomyLevel: 'assisted', + }); + + const approvals = rm.getApprovalRequests(); + expect(approvals).toHaveLength(1); + expect(approvals[0].status).toBe('pending'); + expect(mockMissionManager.createMission).not.toHaveBeenCalled(); + }); + + it('approves approval requests into missions', () => { + const assignment = rm.createAssignment({ + botName: 'Miner1', + role: 'miner', + autonomyLevel: 'assisted', + }); + const approval = rm.getApprovalRequests()[0]; + + const result = rm.approveApprovalRequest(approval.id, 'tester'); + expect(result).toBeDefined(); + expect(mockMissionManager.createMission).toHaveBeenCalledOnce(); + expect(rm.getApprovalRequests()[0].status).toBe('approved'); + expect(rm.getApprovalRequests()[0].assignmentId).toBe(assignment.id); + }); + + // ── Socket events ───────────────────────────────────── + + it('emits role:updated on assignment creation', () => { + rm.createAssignment({ + botName: 'Bot1', + role: 'miner', + autonomyLevel: 'autonomous', + }); + + const events = io.emit.mock.calls.map((c: any[]) => c[0]); + expect(events).toContain('role:updated'); + }); + + it('emits role:updated on override set and clear', () => { + rm.setOverride('Bot1', 'test', 'cmd_1'); + rm.clearOverride('Bot1'); + + const events = io.emit.mock.calls.map((c: any[]) => c[0]); + const roleUpdatedCount = events.filter((e) => e === 'role:updated').length; + expect(roleUpdatedCount).toBe(2); + }); +}); diff --git a/test/control/SquadManager.test.ts b/test/control/SquadManager.test.ts new file mode 100644 index 0000000..f323f13 --- /dev/null +++ b/test/control/SquadManager.test.ts @@ -0,0 +1,68 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +// Mock fs before importing the module +vi.mock('fs', () => ({ + existsSync: vi.fn().mockReturnValue(false), + readFileSync: vi.fn().mockReturnValue('[]'), + writeFileSync: vi.fn(), + mkdirSync: vi.fn(), +})); + +import { SquadManager } from '../../src/control/SquadManager'; + +function createMockIO() { + return { emit: vi.fn() } as any; +} + +describe('SquadManager', () => { + let sm: SquadManager; + let io: ReturnType; + + beforeEach(() => { + vi.clearAllMocks(); + io = createMockIO(); + sm = new SquadManager(io); + }); + + it('creates a squad', () => { + const squad = sm.createSquad({ name: 'Alpha Team', botNames: [] }); + + expect(squad).toBeDefined(); + expect(squad.id).toMatch(/^sqd_/); + expect(squad.name).toBe('Alpha Team'); + expect(squad.botNames).toEqual([]); + expect(squad.createdAt).toBeTypeOf('number'); + }); + + it('adds and removes bot members', () => { + const squad = sm.createSquad({ name: 'Miners', botNames: [] }); + + sm.addBotToSquad(squad.id, 'BotA'); + sm.addBotToSquad(squad.id, 'BotB'); + expect(sm.getSquad(squad.id)!.botNames).toEqual(['BotA', 'BotB']); + + // Adding duplicate is a no-op + sm.addBotToSquad(squad.id, 'BotA'); + expect(sm.getSquad(squad.id)!.botNames).toEqual(['BotA', 'BotB']); + + sm.removeBotFromSquad(squad.id, 'BotA'); + expect(sm.getSquad(squad.id)!.botNames).toEqual(['BotB']); + }); + + it('finds squads for a bot', () => { + const s1 = sm.createSquad({ name: 'Team 1', botNames: ['BotA', 'BotB'] }); + const s2 = sm.createSquad({ name: 'Team 2', botNames: ['BotB', 'BotC'] }); + const s3 = sm.createSquad({ name: 'Team 3', botNames: ['BotC'] }); + + const botBSquads = sm.getSquadsForBot('BotB'); + expect(botBSquads).toHaveLength(2); + expect(botBSquads.map((s) => s.name).sort()).toEqual(['Team 1', 'Team 2']); + + const botASquads = sm.getSquadsForBot('BotA'); + expect(botASquads).toHaveLength(1); + expect(botASquads[0].name).toBe('Team 1'); + + const botDSquads = sm.getSquadsForBot('BotD'); + expect(botDSquads).toHaveLength(0); + }); +}); diff --git a/test/control/integration.test.ts b/test/control/integration.test.ts new file mode 100644 index 0000000..0c5ad18 --- /dev/null +++ b/test/control/integration.test.ts @@ -0,0 +1,225 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; + +// Mock fs before importing modules +vi.mock('fs', () => ({ + default: { + existsSync: vi.fn().mockReturnValue(false), + readFileSync: vi.fn().mockReturnValue('[]'), + writeFileSync: vi.fn(), + mkdirSync: vi.fn(), + }, + existsSync: vi.fn().mockReturnValue(false), + readFileSync: vi.fn().mockReturnValue('[]'), + writeFileSync: vi.fn(), + mkdirSync: vi.fn(), +})); + +import { CommandCenter } from '../../src/control/CommandCenter'; +import { MissionManager } from '../../src/control/MissionManager'; +import { MarkerStore } from '../../src/control/MarkerStore'; +import { SquadManager } from '../../src/control/SquadManager'; + +function createMockIO() { + return { emit: vi.fn() } as any; +} + +function createMockBotManager() { + const mockVoyager = { + pause: vi.fn(), + resume: vi.fn(), + isRunning: vi.fn().mockReturnValue(true), + isPaused: vi.fn().mockReturnValue(false), + }; + const mockBot = { + pathfinder: { stop: vi.fn(), setGoal: vi.fn() }, + players: {}, + entity: { position: { x: 0, y: 64, z: 0 } }, + inventory: { items: vi.fn().mockReturnValue([]) }, + }; + const mockInstance = { + bot: mockBot, + getVoyagerLoop: vi.fn().mockReturnValue(mockVoyager), + name: 'TestBot', + }; + return { + getBot: vi.fn().mockReturnValue(mockInstance), + getAllBots: vi.fn().mockReturnValue([mockInstance]), + _mockInstance: mockInstance, + } as any; +} + +describe('Control Platform Integration', () => { + let io: ReturnType; + let bm: ReturnType; + let cc: CommandCenter; + let mm: MissionManager; + let ms: MarkerStore; + let sm: SquadManager; + + beforeEach(() => { + vi.clearAllMocks(); + vi.useFakeTimers(); + io = createMockIO(); + bm = createMockBotManager(); + cc = new CommandCenter(bm, io); + mm = new MissionManager(bm, io); + ms = new MarkerStore(io); + sm = new SquadManager(io); + }); + + afterEach(() => { + cc.destroy(); + vi.useRealTimers(); + }); + + // ── Command → getCommands ───────────────────────────── + + it('created command appears in getCommands', () => { + const cmd = cc.createCommand({ + type: 'pause_voyager', + targets: ['TestBot'], + }); + + const commands = cc.getCommands(); + expect(commands).toHaveLength(1); + expect(commands[0].id).toBe(cmd.id); + expect(commands[0].type).toBe('pause_voyager'); + expect(commands[0].targets).toContain('TestBot'); + }); + + // ── Mission → getMissions ───────────────────────────── + + it('created mission appears in getMissions', () => { + const mission = mm.createMission({ + type: 'gather_items', + title: 'Collect wood', + assigneeType: 'bot', + assigneeIds: ['TestBot'], + }); + + const missions = mm.getMissions(); + expect(missions).toHaveLength(1); + expect(missions[0].id).toBe(mission.id); + expect(missions[0].title).toBe('Collect wood'); + expect(missions[0].assigneeIds).toContain('TestBot'); + }); + + // ── Marker → getMarkers ─────────────────────────────── + + it('created marker appears in getMarkers', () => { + const marker = ms.createMarker({ + name: 'Iron Mine', + kind: 'mine', + position: { x: 100, y: 20, z: -50 }, + tags: ['resource'], + }); + + const markers = ms.getMarkers(); + expect(markers).toHaveLength(1); + expect(markers[0].id).toBe(marker.id); + expect(markers[0].name).toBe('Iron Mine'); + expect(markers[0].kind).toBe('mine'); + }); + + // ── Squad + members → getSquadsForBot ───────────────── + + it('created squad with added members appears in getSquadsForBot', () => { + const squad = sm.createSquad({ + name: 'Mining Team', + botNames: [], + }); + + sm.addBotToSquad(squad.id, 'TestBot'); + sm.addBotToSquad(squad.id, 'Helper'); + + const testBotSquads = sm.getSquadsForBot('TestBot'); + expect(testBotSquads).toHaveLength(1); + expect(testBotSquads[0].name).toBe('Mining Team'); + expect(testBotSquads[0].botNames).toContain('TestBot'); + expect(testBotSquads[0].botNames).toContain('Helper'); + + const helperSquads = sm.getSquadsForBot('Helper'); + expect(helperSquads).toHaveLength(1); + + const unknownSquads = sm.getSquadsForBot('Unknown'); + expect(unknownSquads).toHaveLength(0); + }); + + // ── Cross-service: Command + Mission linked ─────────── + + it('mission linked to command respects dependency', async () => { + mm.setCommandCenter(cc); + + // Create a command + const cmd = cc.createCommand({ + type: 'pause_voyager', + targets: ['TestBot'], + }); + + // Create a mission linked to that command + const mission = mm.createMission({ + type: 'gather_items', + title: 'Wait for pause', + assigneeType: 'bot', + assigneeIds: ['TestBot'], + linkedCommandIds: [cmd.id], + }); + + // Before command succeeds, mission cannot start + expect(mm.canStart(mission)).toBe(false); + + // Dispatch the command (it will succeed) + await cc.dispatchCommand(cmd); + expect(cmd.status).toBe('succeeded'); + + // Now mission can start + expect(mm.canStart(mission)).toBe(true); + }); + + // ── Marker spatial lookup ───────────────────────────── + + it('findNearestMarker returns the closest marker', () => { + ms.createMarker({ + name: 'Far Base', + kind: 'base', + position: { x: 1000, y: 64, z: 1000 }, + }); + ms.createMarker({ + name: 'Near Base', + kind: 'base', + position: { x: 10, y: 64, z: 10 }, + }); + + const nearest = ms.findNearestMarker({ x: 0, y: 64, z: 0 }, 'base'); + expect(nearest).toBeDefined(); + expect(nearest!.name).toBe('Near Base'); + }); + + // ── Zone containment ────────────────────────────────── + + it('isInsideZone checks rectangle containment', () => { + const zone = ms.createZone({ + name: 'Build Area', + mode: 'build', + shape: 'rectangle', + rectangle: { minX: 0, minZ: 0, maxX: 100, maxZ: 100 }, + }); + + expect(ms.isInsideZone(50, 50, zone.id)).toBe(true); + expect(ms.isInsideZone(150, 50, zone.id)).toBe(false); + expect(ms.isInsideZone(-1, 50, zone.id)).toBe(false); + }); + + it('isInsideZone checks circle containment', () => { + const zone = ms.createZone({ + name: 'Guard Post', + mode: 'guard', + shape: 'circle', + circle: { x: 50, z: 50, radius: 25 }, + }); + + expect(ms.isInsideZone(50, 50, zone.id)).toBe(true); // center + expect(ms.isInsideZone(60, 50, zone.id)).toBe(true); // within radius + expect(ms.isInsideZone(100, 100, zone.id)).toBe(false); // outside + }); +}); diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..c5f36b4 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + include: ['test/**/*.test.ts'], + }, +}); diff --git a/web/__tests__/components/CommanderPanel.test.tsx b/web/__tests__/components/CommanderPanel.test.tsx new file mode 100644 index 0000000..66b298b --- /dev/null +++ b/web/__tests__/components/CommanderPanel.test.tsx @@ -0,0 +1,53 @@ +import { render, screen, fireEvent } from '@testing-library/react'; +import { describe, it, expect, vi } from 'vitest'; +import { CommanderPanel } from '@/components/CommanderPanel'; +import type { CommanderPlan } from '@/lib/api'; + +const basePlan: CommanderPlan = { + id: 'plan-1', + input: 'Send Ada to base', + parsedIntent: 'Move Ada to base', + confidence: 0.6, + requiresConfirmation: true, + warnings: ['Marker may be ambiguous'], + commands: [ + { + id: 'cmd-1', + type: 'move_to_marker', + scope: 'bot', + targets: ['Ada'], + payload: { markerId: 'base' }, + priority: 'normal', + source: 'commander', + status: 'queued', + createdAt: Date.now(), + }, + ], + missions: [], +}; + +describe('CommanderPanel', () => { + it('shows clarification guidance and summary counts', () => { + render( + , + ); + + expect(screen.getByText('Clarification Recommended')).toBeInTheDocument(); + expect(screen.getByText('Commands')).toBeInTheDocument(); + expect(screen.getByText('Warnings')).toBeInTheDocument(); + expect(screen.getByText('Move Ada to base')).toBeInTheDocument(); + }); + + it('requires double confirmation before execute callback', () => { + const onExecute = vi.fn(); + render( + , + ); + + fireEvent.click(screen.getByRole('button', { name: 'Confirm Execute' })); + expect(onExecute).not.toHaveBeenCalled(); + + fireEvent.click(screen.getByRole('button', { name: 'Execute' })); + expect(onExecute).toHaveBeenCalledTimes(1); + }); +}); diff --git a/web/__tests__/components/MapContextMenu.test.tsx b/web/__tests__/components/MapContextMenu.test.tsx new file mode 100644 index 0000000..ff93891 --- /dev/null +++ b/web/__tests__/components/MapContextMenu.test.tsx @@ -0,0 +1,30 @@ +import { render, screen, fireEvent } from '@testing-library/react'; +import { describe, it, expect, vi } from 'vitest'; +import MapContextMenu from '@/components/map/MapContextMenu'; + +describe('MapContextMenu', () => { + it('offers zone creation for terrain targets', () => { + const onCreateZone = vi.fn(); + + render( + , + ); + + fireEvent.click(screen.getByRole('button', { name: 'Create Zone' })); + expect(onCreateZone).toHaveBeenCalledWith(12, 34); + }); +}); diff --git a/web/__tests__/lib/control-platform.test.ts b/web/__tests__/lib/control-platform.test.ts new file mode 100644 index 0000000..d64a5c4 --- /dev/null +++ b/web/__tests__/lib/control-platform.test.ts @@ -0,0 +1,120 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { normalizeCommandRecord, normalizeMissionRecord } from '@/lib/api'; +import { useControlStore, useMissionStore } from '@/lib/store'; + +describe('control platform frontend helpers', () => { + beforeEach(() => { + useControlStore.setState({ selectedBotIds: new Set(), commandHistory: [] }); + useMissionStore.setState({ missions: [] }); + }); + + it('normalizes backend command records for the frontend', () => { + const command = normalizeCommandRecord({ + id: 'cmd-1', + type: 'walk_to_coords', + scope: 'single', + targets: ['Ada'], + params: { x: 1, y: 64, z: 2 }, + priority: 'critical', + source: 'dashboard', + status: 'queued', + createdAt: '2026-03-25T12:00:00.000Z', + }); + + expect(command.scope).toBe('bot'); + expect(command.priority).toBe('urgent'); + expect(command.payload).toEqual({ x: 1, y: 64, z: 2 }); + expect(command.createdAt).toBeTypeOf('number'); + }); + + it('upserts command history in descending time order', () => { + const store = useControlStore.getState(); + + store.upsertCommand({ + id: 'cmd-1', + type: 'pause_voyager', + scope: 'bot', + targets: ['Ada'], + payload: {}, + priority: 'normal', + source: 'dashboard', + status: 'queued', + createdAt: 10, + }); + + store.upsertCommand({ + id: 'cmd-2', + type: 'stop_movement', + scope: 'bot', + targets: ['Bee'], + payload: {}, + priority: 'normal', + source: 'dashboard', + status: 'started', + createdAt: 20, + }); + + expect(useControlStore.getState().commandHistory.map((command) => command.id)).toEqual(['cmd-2', 'cmd-1']); + }); + + it('normalizes mission timestamps and stores mission updates', () => { + const mission = normalizeMissionRecord({ + id: 'mission-1', + type: 'queue_task', + title: 'Guard base', + assigneeType: 'bot', + assigneeIds: ['Ada'], + status: 'queued', + priority: 'normal', + steps: [], + createdAt: 100, + updatedAt: '2026-03-25T12:00:00.000Z' as unknown as number, + source: 'role', + }); + + useMissionStore.getState().upsertMission(mission); + + expect(useMissionStore.getState().missions[0].updatedAt).toBeTypeOf('number'); + expect(useMissionStore.getState().missions[0].title).toBe('Guard base'); + }); + + it('returns running missions for a specific bot', () => { + useMissionStore.getState().setMissions([ + { + id: 'mission-1', + type: 'queue_task', + title: 'Mine stone', + assigneeType: 'bot', + assigneeIds: ['Ada'], + status: 'running', + priority: 'normal', + steps: [], + createdAt: 1, + updatedAt: 2, + source: 'dashboard', + }, + { + id: 'mission-2', + type: 'queue_task', + title: 'Guard base', + assigneeType: 'bot', + assigneeIds: ['Bee'], + status: 'queued', + priority: 'normal', + steps: [], + createdAt: 3, + updatedAt: 4, + source: 'role', + }, + ]); + + expect(useMissionStore.getState().getRunningForBot('Ada').map((mission) => mission.id)).toEqual(['mission-1']); + expect(useMissionStore.getState().getRunningForBot('Bee')).toEqual([]); + }); + + it('keeps selection state unique when selecting all bots', () => { + useControlStore.getState().selectAll(['Ada', 'Bee', 'Ada']); + + expect(Array.from(useControlStore.getState().selectedBotIds)).toEqual(['Ada', 'Bee']); + }); +}); diff --git a/web/__tests__/setup.ts b/web/__tests__/setup.ts new file mode 100644 index 0000000..bb02c60 --- /dev/null +++ b/web/__tests__/setup.ts @@ -0,0 +1 @@ +import '@testing-library/jest-dom/vitest'; diff --git a/web/package-lock.json b/web/package-lock.json index 4e4cc10..1fe291a 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -17,15 +17,26 @@ }, "devDependencies": { "@tailwindcss/postcss": "^4", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "16.2.1", + "jsdom": "^29.0.1", "tailwindcss": "^4", - "typescript": "^5" + "typescript": "^5", + "vitest": "^4.1.0" } }, + "node_modules/@adobe/css-tools": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", + "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", + "dev": true, + "license": "MIT" + }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -39,6 +50,67 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@asamuzakjp/css-color": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.0.1.tgz", + "integrity": "sha512-2SZFvqMyvboVV1d15lMf7XiI3m7SDqXUuKaTymJYLN6dSGadqp+fVojqJlVoMlbZnlTmu3S0TLwLTJpvBMO1Aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^3.1.1", + "@csstools/css-color-parser": "^4.0.2", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.2.6" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.0.4.tgz", + "integrity": "sha512-jXR6x4AcT3eIrS2fSNAwJpwirOkGcd+E7F7CP3zjdTqz9B/2huHOL8YJZBgekKwLML+u7qB/6P1LXQuMScsx0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.7" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@babel/code-frame": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", @@ -231,6 +303,16 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", @@ -279,6 +361,159 @@ "node": ">=6.9.0" } }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.1.1.tgz", + "integrity": "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.2.tgz", + "integrity": "sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.1.1" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.1.tgz", + "integrity": "sha512-BvqN0AMWNAnLk9G8jnUT77D+mUbY/H2b3uDTvg2isJkHaOufUE2R3AOwxWo7VBQKT1lOdwdvorddo2B/lk64+w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, "node_modules/@emnapi/core": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz", @@ -456,6 +691,24 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@exodus/bytes": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz", + "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1229,72 +1482,20 @@ "node": ">=12.4.0" } }, - "node_modules/@rtsao/scc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", - "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", - "dev": true, - "license": "MIT" - }, - "node_modules/@socket.io/component-emitter": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", - "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", - "license": "MIT" - }, - "node_modules/@swc/helpers": { - "version": "0.5.15", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", - "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.8.0" - } - }, - "node_modules/@tailwindcss/node": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz", - "integrity": "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "enhanced-resolve": "^5.19.0", - "jiti": "^2.6.1", - "lightningcss": "1.32.0", - "magic-string": "^0.30.21", - "source-map-js": "^1.2.1", - "tailwindcss": "4.2.2" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.2.tgz", - "integrity": "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==", + "node_modules/@oxc-project/types": { + "version": "0.120.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.120.0.tgz", + "integrity": "sha512-k1YNu55DuvAip/MGE1FTsIuU3FUCn6v/ujG9V7Nq5Df/kX2CWb13hhwD0lmJGMGqE+bE1MXvv9SZVnMzEXlWcg==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 20" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.2.2", - "@tailwindcss/oxide-darwin-arm64": "4.2.2", - "@tailwindcss/oxide-darwin-x64": "4.2.2", - "@tailwindcss/oxide-freebsd-x64": "4.2.2", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", - "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", - "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", - "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", - "@tailwindcss/oxide-linux-x64-musl": "4.2.2", - "@tailwindcss/oxide-wasm32-wasi": "4.2.2", - "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", - "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" + "funding": { + "url": "https://github.com/sponsors/Boshen" } }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.2.tgz", - "integrity": "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==", + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.10.tgz", + "integrity": "sha512-jOHxwXhxmFKuXztiu1ORieJeTbx5vrTkcOkkkn2d35726+iwhrY1w/+nYY/AGgF12thg33qC3R1LMBF5tHTZHg==", "cpu": [ "arm64" ], @@ -1305,13 +1506,13 @@ "android" ], "engines": { - "node": ">= 20" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.2.tgz", - "integrity": "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==", + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.10.tgz", + "integrity": "sha512-gED05Teg/vtTZbIJBc4VNMAxAFDUPkuO/rAIyyxZjTj1a1/s6z5TII/5yMGZ0uLRCifEtwUQn8OlYzuYc0m70w==", "cpu": [ "arm64" ], @@ -1322,13 +1523,13 @@ "darwin" ], "engines": { - "node": ">= 20" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.2.tgz", - "integrity": "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.10.tgz", + "integrity": "sha512-rI15NcM1mA48lqrIxVkHfAqcyFLcQwyXWThy+BQ5+mkKKPvSO26ir+ZDp36AgYoYVkqvMcdS8zOE6SeBsR9e8A==", "cpu": [ "x64" ], @@ -1339,13 +1540,13 @@ "darwin" ], "engines": { - "node": ">= 20" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.2.tgz", - "integrity": "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==", + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.10.tgz", + "integrity": "sha512-XZRXHdTa+4ME1MuDVp021+doQ+z6Ei4CCFmNc5/sKbqb8YmkiJdj8QKlV3rCI0AJtAeSB5n0WGPuJWNL9p/L2w==", "cpu": [ "x64" ], @@ -1356,13 +1557,13 @@ "freebsd" ], "engines": { - "node": ">= 20" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.2.tgz", - "integrity": "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==", + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.10.tgz", + "integrity": "sha512-R0SQMRluISSLzFE20sPWYHVmJdDQnRyc/FzSCN72BqQmh2SOZUFG+N3/vBZpR4C6WpEUVYJLrYUXaj43sJsNLA==", "cpu": [ "arm" ], @@ -1373,13 +1574,13 @@ "linux" ], "engines": { - "node": ">= 20" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.2.tgz", - "integrity": "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==", + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.10.tgz", + "integrity": "sha512-Y1reMrV/o+cwpduYhJuOE3OMKx32RMYCidf14y+HssARRmhDuWXJ4yVguDg2R/8SyyGNo+auzz64LnPK9Hq6jg==", "cpu": [ "arm64" ], @@ -1390,13 +1591,13 @@ "linux" ], "engines": { - "node": ">= 20" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.2.tgz", - "integrity": "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==", + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.10.tgz", + "integrity": "sha512-vELN+HNb2IzuzSBUOD4NHmP9yrGwl1DVM29wlQvx1OLSclL0NgVWnVDKl/8tEks79EFek/kebQKnNJkIAA4W2g==", "cpu": [ "arm64" ], @@ -1407,15 +1608,15 @@ "linux" ], "engines": { - "node": ">= 20" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.2.tgz", - "integrity": "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==", + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.10.tgz", + "integrity": "sha512-ZqrufYTgzxbHwpqOjzSsb0UV/aV2TFIY5rP8HdsiPTv/CuAgCRjM6s9cYFwQ4CNH+hf9Y4erHW1GjZuZ7WoI7w==", "cpu": [ - "x64" + "ppc64" ], "dev": true, "license": "MIT", @@ -1424,15 +1625,363 @@ "linux" ], "engines": { - "node": ">= 20" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.2.tgz", - "integrity": "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==", + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.10.tgz", + "integrity": "sha512-gSlmVS1FZJSRicA6IyjoRoKAFK7IIHBs7xJuHRSmjImqk3mPPWbR7RhbnfH2G6bcmMEllCt2vQ/7u9e6bBnByg==", "cpu": [ - "x64" + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.10.tgz", + "integrity": "sha512-eOCKUpluKgfObT2pHjztnaWEIbUabWzk3qPZ5PuacuPmr4+JtQG4k2vGTY0H15edaTnicgU428XW/IH6AimcQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.10.tgz", + "integrity": "sha512-Xdf2jQbfQowJnLcgYfD/m0Uu0Qj5OdxKallD78/IPPfzaiaI4KRAwZzHcKQ4ig1gtg1SuzC7jovNiM2TzQsBXA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.10.tgz", + "integrity": "sha512-o1hYe8hLi1EY6jgPFyxQgQ1wcycX+qz8eEbVmot2hFkgUzPxy9+kF0u0NIQBeDq+Mko47AkaFFaChcvZa9UX9Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.10.tgz", + "integrity": "sha512-Ugv9o7qYJudqQO5Y5y2N2SOo6S4WiqiNOpuQyoPInnhVzCY+wi/GHltcLHypG9DEUYMB0iTB/huJrpadiAcNcA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.1.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", + "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.10.tgz", + "integrity": "sha512-7UODQb4fQUNT/vmgDZBl3XOBAIOutP5R3O/rkxg0aLfEGQ4opbCgU5vOw/scPe4xOqBwL9fw7/RP1vAMZ6QlAQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.10.tgz", + "integrity": "sha512-PYxKHMVHOb5NJuDL53vBUl1VwUjymDcYI6rzpIni0C9+9mTiJedvUxSk7/RPp7OOAm3v+EjgMu9bIy3N6b408w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.10.tgz", + "integrity": "sha512-UkVDEFk1w3mveXeKgaTuYfKWtPbvgck1dT8TUG3bnccrH0XtLTuAyfCoks4Q/M5ZGToSVJTIQYCzy2g/atAOeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz", + "integrity": "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.19.0", + "jiti": "^2.6.1", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.2.2" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.2.tgz", + "integrity": "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.2.2", + "@tailwindcss/oxide-darwin-arm64": "4.2.2", + "@tailwindcss/oxide-darwin-x64": "4.2.2", + "@tailwindcss/oxide-freebsd-x64": "4.2.2", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", + "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", + "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", + "@tailwindcss/oxide-linux-x64-musl": "4.2.2", + "@tailwindcss/oxide-wasm32-wasi": "4.2.2", + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", + "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.2.tgz", + "integrity": "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.2.tgz", + "integrity": "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.2.tgz", + "integrity": "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.2.tgz", + "integrity": "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.2.tgz", + "integrity": "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.2.tgz", + "integrity": "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.2.tgz", + "integrity": "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.2.tgz", + "integrity": "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.2.tgz", + "integrity": "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==", + "cpu": [ + "x64" ], "dev": true, "license": "MIT", @@ -1522,17 +2071,130 @@ "tailwindcss": "4.2.2" } }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/dom/node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", "dev": true, "license": "MIT", - "optional": true, + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", "dependencies": { - "tslib": "^2.4.0" + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" } }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -2148,6 +2810,119 @@ "win32" ] }, + "node_modules/@vitest/expect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.0.tgz", + "integrity": "sha512-EIxG7k4wlWweuCLG9Y5InKFwpMEOyrMb6ZJ1ihYu02LVj/bzUwn2VMU+13PinsjRW75XnITeFrQBMH5+dLvCDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.0", + "@vitest/utils": "4.1.0", + "chai": "^6.2.2", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.0.tgz", + "integrity": "sha512-evxREh+Hork43+Y4IOhTo+h5lGmVRyjqI739Rz4RlUPqwrkFFDF6EMvOOYjTx4E8Tl6gyCLRL8Mu7Ry12a13Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.0", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.0.tgz", + "integrity": "sha512-3RZLZlh88Ib0J7NQTRATfc/3ZPOnSUn2uDBUoGNn5T36+bALixmzphN26OUD3LRXWkJu4H0s5vvUeqBiw+kS0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.0.tgz", + "integrity": "sha512-Duvx2OzQ7d6OjchL+trw+aSrb9idh7pnNfxrklo14p3zmNL4qPCDeIJAK+eBKYjkIwG96Bc6vYuxhqDXQOWpoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.0", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.0.tgz", + "integrity": "sha512-0Vy9euT1kgsnj1CHttwi9i9o+4rRLEaPRSOJ5gyv579GJkNpgJK+B4HSv/rAWixx2wdAFci1X4CEPjiu2bXIMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.0", + "@vitest/utils": "4.1.0", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.0.tgz", + "integrity": "sha512-pz77k+PgNpyMDv2FV6qmk5ZVau6c3R8HC8v342T2xlFxQKTrSeYw9waIJG8KgV9fFwAtTu4ceRzMivPTH6wSxw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.0.tgz", + "integrity": "sha512-XfPXT6a8TZY3dcGY8EdwsBulFCIw+BeeX0RZn2x/BtiY/75YGh8FeWGG8QISN/WhaqSrE2OrlDgtF8q5uhOTmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.0", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", @@ -2188,6 +2963,17 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -2381,6 +3167,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/ast-types-flow": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", @@ -2453,6 +3249,16 @@ "node": ">=6.0.0" } }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -2591,6 +3397,16 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2663,6 +3479,27 @@ "node": ">= 8" } }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -2677,6 +3514,20 @@ "dev": true, "license": "BSD-2-Clause" }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -2748,6 +3599,13 @@ } } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -2791,6 +3649,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -2814,6 +3683,14 @@ "node": ">=0.10.0" } }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -2879,6 +3756,19 @@ "node": ">=10.13.0" } }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-abstract": { "version": "1.24.1", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", @@ -2997,6 +3887,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -3500,6 +4397,16 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -3510,6 +4417,16 @@ "node": ">=0.10.0" } }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3678,6 +4595,21 @@ } } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -3983,6 +4915,19 @@ "hermes-estree": "0.25.1" } }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -4020,6 +4965,16 @@ "node": ">=0.8.19" } }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -4305,6 +5260,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -4512,6 +5474,57 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "29.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.0.1.tgz", + "integrity": "sha512-z6JOK5gRO7aMybVq/y/MlIpKh8JIi68FBKMUtKkK2KH/wMSRlCxQ682d08LB9fYXplyY/UXG8P4XXTScmdjApg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^5.0.1", + "@asamuzakjp/dom-selector": "^7.0.3", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.1", + "@exodus/bytes": "^1.15.0", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.7", + "parse5": "^8.0.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.1", + "undici": "^7.24.5", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.1", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -4926,6 +5939,17 @@ "yallist": "^3.0.2" } }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -4946,6 +5970,13 @@ "node": ">= 0.4" } }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -4970,6 +6001,16 @@ "node": ">=8.6" } }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", @@ -5285,6 +6326,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -5366,6 +6418,19 @@ "node": ">=6" } }, + "node_modules/parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -5393,6 +6458,13 @@ "dev": true, "license": "MIT" }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -5461,6 +6533,44 @@ "node": ">= 0.8.0" } }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -5532,6 +6642,20 @@ "dev": true, "license": "MIT" }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -5576,6 +6700,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", @@ -5628,6 +6762,40 @@ "node": ">=0.10.0" } }, + "node_modules/rolldown": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.10.tgz", + "integrity": "sha512-q7j6vvarRFmKpgJUT8HCAUljkgzEp4LAhPlJUvQhA5LA1SUL36s5QCysMutErzL3EbNOZOkoziSx9iZC4FddKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.120.0", + "@rolldown/pluginutils": "1.0.0-rc.10" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.10", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.10", + "@rolldown/binding-darwin-x64": "1.0.0-rc.10", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.10", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.10", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.10", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.10", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.10", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.10", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.10", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.10", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.10", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.10", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.10", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.10" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -5707,6 +6875,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", @@ -5929,6 +7110,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/socket.io-client": { "version": "4.8.3", "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz", @@ -5973,6 +7161,20 @@ "dev": true, "license": "MIT" }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", + "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", + "dev": true, + "license": "MIT" + }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -6110,6 +7312,19 @@ "node": ">=4" } }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -6172,6 +7387,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, "node_modules/tailwindcss": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz", @@ -6193,6 +7415,23 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", + "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -6241,6 +7480,36 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.0.27", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.27.tgz", + "integrity": "sha512-I4FZcVFcqCRuT0ph6dCDpPuO4Xgzvh+spkcTr1gK7peIvxWauoloVO0vuy1FQnijT63ss6AsHB6+OIM4aXHbPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.27" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.27", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.27.tgz", + "integrity": "sha512-YQ7uPjgWUibIK6DW5lrKujGwUKhLevU4hcGbP5O6TcIUb+oTjJYJVWPS4nZsIHrEEEG6myk/oqAJUEQmpZrHsg==", + "dev": true, + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -6254,6 +7523,32 @@ "node": ">=8.0" } }, + "node_modules/tough-cookie": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", + "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/ts-api-utils": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", @@ -6447,6 +7742,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.5.tgz", + "integrity": "sha512-3IWdCpjgxp15CbJnsi/Y9TCDE7HWVN19j1hmzVhoAkY/+CJx449tVxT5wZc1Gwg8J+P0LWvzlBzxYRnHJ+1i7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -6530,6 +7835,240 @@ "punycode": "^2.1.0" } }, + "node_modules/vite": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.1.tgz", + "integrity": "sha512-wt+Z2qIhfFt85uiyRt5LPU4oVEJBXj8hZNWKeqFG4gRG/0RaRGJ7njQCwzFVjO+v4+Ipmf5CY7VdmZRAYYBPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.10", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.0", + "esbuild": "^0.27.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.0.tgz", + "integrity": "sha512-YbDrMF9jM2Lqc++2530UourxZHmkKLxrs4+mYhEwqWS97WJ7wOYEkcr+QfRgJ3PW9wz3odRijLZjHEaRLTNbqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.0", + "@vitest/mocker": "4.1.0", + "@vitest/pretty-format": "4.1.0", + "@vitest/runner": "4.1.0", + "@vitest/snapshot": "4.1.0", + "@vitest/spy": "4.1.0", + "@vitest/utils": "4.1.0", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.0", + "@vitest/browser-preview": "4.1.0", + "@vitest/browser-webdriverio": "4.1.0", + "@vitest/ui": "4.1.0", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6635,6 +8174,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -6666,6 +8222,23 @@ } } }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, "node_modules/xmlhttprequest-ssl": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", diff --git a/web/package.json b/web/package.json index ae68047..7400373 100644 --- a/web/package.json +++ b/web/package.json @@ -6,7 +6,9 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "eslint" + "lint": "eslint", + "test": "vitest run", + "test:watch": "vitest" }, "dependencies": { "framer-motion": "^12.38.0", @@ -18,12 +20,16 @@ }, "devDependencies": { "@tailwindcss/postcss": "^4", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "16.2.1", + "jsdom": "^29.0.1", "tailwindcss": "^4", - "typescript": "^5" + "typescript": "^5", + "vitest": "^4.1.0" } } diff --git a/web/src/app/activity/page.tsx b/web/src/app/activity/page.tsx index f74beca..904f483 100644 --- a/web/src/app/activity/page.tsx +++ b/web/src/app/activity/page.tsx @@ -1,34 +1,26 @@ 'use client'; -import { useEffect, useState } from 'react'; +import { useMemo, useState } from 'react'; import { motion } from 'framer-motion'; -import { api, type BotEvent } from '@/lib/api'; +import { useBotStore } from '@/lib/store'; import { EVENT_CONFIG } from '@/lib/constants'; import { PageHeader } from '@/components/PageHeader'; const EVENT_TYPES = [ 'all', 'bot:state', 'bot:task', 'bot:chat', 'bot:spawn', - 'bot:disconnect', 'bot:skill_learned', 'bot:death', + 'bot:disconnect', 'bot:skill_learned', 'bot:death', 'commander:parse', 'commander:execute', ]; export default function ActivityPage() { - const [events, setEvents] = useState([]); + const events = useBotStore((s) => s.activityFeed); const [filter, setFilter] = useState(''); const [typeFilter, setTypeFilter] = useState('all'); - const [autoRefresh, setAutoRefresh] = useState(true); + const [livePaused, setLivePaused] = useState(false); + const [pausedEvents, setPausedEvents] = useState([]); - useEffect(() => { - const load = () => { - api.getActivity(200).then((data) => setEvents(data.events)).catch(() => {}); - }; - load(); - if (autoRefresh) { - const interval = setInterval(load, 3000); - return () => clearInterval(interval); - } - }, [autoRefresh]); + const visibleEvents = useMemo(() => (livePaused ? pausedEvents : events), [events, livePaused, pausedEvents]); - const filtered = events.filter((e) => { + const filtered = visibleEvents.filter((e) => { if (typeFilter !== 'all' && e.type !== typeFilter) return false; if (filter && !e.botName.toLowerCase().includes(filter.toLowerCase()) && !e.description.toLowerCase().includes(filter.toLowerCase())) return false; return true; @@ -39,15 +31,20 @@ export default function ActivityPage() {
diff --git a/web/src/app/bots/[name]/page.tsx b/web/src/app/bots/[name]/page.tsx index 70b35ed..236dc2f 100644 --- a/web/src/app/bots/[name]/page.tsx +++ b/web/src/app/bots/[name]/page.tsx @@ -2,16 +2,20 @@ import { useEffect, useState } from 'react'; import { useParams } from 'next/navigation'; +import Image from 'next/image'; import { motion } from 'framer-motion'; import Link from 'next/link'; -import { api, type BotDetailed, type ChatMessage } from '@/lib/api'; +import { api, type BotDetailed, type ChatMessage, type RoleAssignmentRecord } from '@/lib/api'; import { getPersonalityColor, getAffinityTier, STATE_COLORS, STATE_LABELS, PERSONALITY_ICONS } from '@/lib/constants'; import { formatItemName, getItemCategoryColorByName } from '@/lib/items'; +import { ROLE_COLORS, ROLE_ICONS } from '@/components/RoleAssignmentPanel'; import { EquipmentDisplay } from '@/components/EquipmentDisplay'; import { BotActivityPanel } from '@/components/BotActivityPanel'; import { StatsPanel } from '@/components/StatsPanel'; import { WorldContext } from '@/components/WorldContext'; import { BotCommandCenter } from '@/components/BotCommandCenter'; +import { MissionQueuePanel } from '@/components/MissionQueuePanel'; +import { CommandHistoryPanel } from '@/components/CommandHistoryPanel'; export default function BotProfilePage() { const params = useParams(); @@ -21,31 +25,32 @@ export default function BotProfilePage() { const [conversations, setConversations] = useState>({}); const [selectedPlayer, setSelectedPlayer] = useState(null); const [error, setError] = useState(null); - const [taskInput, setTaskInput] = useState(''); - const [sendingTask, setSendingTask] = useState(false); const [chatMsg, setChatMsg] = useState(''); const [chatPlayer, setChatPlayer] = useState(''); const [showCompleted, setShowCompleted] = useState(false); const [showFailed, setShowFailed] = useState(false); + const [roleAssignment, setRoleAssignment] = useState(null); + const [longTermGoal, setLongTermGoal] = useState<{ + rawRequest: string; kind: string; status: string; + buildState: string | null; pendingSubtasks: string[]; + } | null>(null); useEffect(() => { const load = () => { api.getBotDetailed(name).then((data) => { setBot(data.bot); setError(null); }).catch((e) => setError(e.message)); api.getBotRelationships(name).then((data) => setRelationships(data.relationships)).catch(() => {}); api.getBotConversations(name).then((data) => setConversations(data.conversations)).catch(() => {}); + api.getRoleAssignments().then((data) => { + const match = data.assignments.find((a) => a.botName === name); + setRoleAssignment(match || null); + }).catch(() => {}); + api.getBotTasks(name).then((data) => setLongTermGoal(data.longTermGoal ?? null)).catch(() => {}); }; load(); const interval = setInterval(load, 5000); return () => clearInterval(interval); }, [name]); - const handleQueueTask = async () => { - if (!taskInput.trim()) return; - setSendingTask(true); - try { await api.queueTask(name, taskInput.trim()); setTaskInput(''); } catch { /* ignore */ } - setSendingTask(false); - }; - const handleSendChat = async () => { if (!chatMsg.trim()) return; const player = chatPlayer.trim() || 'admin'; @@ -90,7 +95,7 @@ export default function BotProfilePage() { {bot.name} - {/* ═══ HERO SECTION ═══ */} + {/* HERO SECTION */} {bot.name}

{bot.personalityDisplayName}

+ {roleAssignment && (() => { + const roleColor = ROLE_COLORS[roleAssignment.role] || '#6B7280'; + return ( +
+ + {ROLE_ICONS[roleAssignment.role] || ''} + {roleAssignment.role.replace('-', ' ')} + + + {roleAssignment.autonomyLevel} + +
+ ); + })()}
{bot.position && } @@ -133,8 +159,8 @@ export default function BotProfilePage() { {/* Vitals */}
- - + + {bot.experience && (
@@ -158,7 +184,7 @@ export default function BotProfilePage() {
- {/* ═══ BODY: 2-column layout ═══ */} + {/* BODY: 2-column layout */}
{/* LEFT COLUMN (3/5) */}
@@ -171,6 +197,33 @@ export default function BotProfilePage() { accentColor={accentColor} /> + {/* Long-Term Goal */} + {longTermGoal && ( +
+
+ + + + {longTermGoal.buildState && ( + + )} + {longTermGoal.pendingSubtasks.length > 0 && ( +
+

Pending Subtasks

+
+ {longTermGoal.pendingSubtasks.slice(0, 8).map((task, i) => ( + + {task} + + ))} +
+
+ )} +
+
+ )} + {/* Command Center */} - {/* Task Queue */} -
-
- setTaskInput(e.target.value)} - onKeyDown={(e) => e.key === 'Enter' && handleQueueTask()} - placeholder="Queue a task..." - className="flex-1 bg-zinc-800/80 border border-zinc-700/50 rounded-lg px-3 py-1.5 text-xs text-white placeholder-zinc-600" - /> - -
- + {/* Task History */} +
+ {!bot.voyager || (bot.voyager.completedTasks.length === 0 && bot.voyager.failedTasks.length === 0) ? ( +

No task history yet

+ ) : null} {bot.voyager && ( <> {bot.voyager.completedTasks.length > 0 && ( @@ -249,6 +288,48 @@ export default function BotProfilePage() { )}
+ {/* Mission Queue */} + + + {/* Command History */} + + + {/* Diagnostics */} + {bot.voyager && ( +
+
+ + + + + {bot.voyager.failedTasks.length > 0 && ( + + )} +
+
+ )} + {/* Inventory */}
{/* Hotbar */} @@ -305,7 +386,7 @@ export default function BotProfilePage() { className={`w-full text-left rounded-lg p-2 transition-colors ${selectedPlayer === player ? 'bg-zinc-800/60' : 'hover:bg-zinc-800/30'}`} >
- + {player} {tier.label} ({score})
@@ -327,7 +408,7 @@ export default function BotProfilePage() { Back
- + {selectedPlayer}
@@ -354,7 +435,7 @@ export default function BotProfilePage() { Object.entries(conversations).map(([player, msgs]) => ( + )} + {activeBuild.status === 'paused' && ( + + )} + {(activeBuild.status === 'running' || activeBuild.status === 'paused') && ( + + )} +
+
+ + {/* Mission Status */} + {buildMission && ( +
+ Mission + {buildMission.title} + +
+ )} + + {/* Overall Progress */} +
+
+ Overall Progress + + {(activeBuild.placedBlocks ?? 0).toLocaleString()} / {(activeBuild.totalBlocks ?? 0).toLocaleString()} blocks ({overallPct}%) + +
+ +
+ + {/* Per-Bot Assignments */} +
+ {(activeBuild.assignments ?? []).map((assignment) => { + const botPct = assignment.blocksTotal > 0 + ? Math.round((assignment.blocksPlaced / assignment.blocksTotal) * 100) + : 0; + return ( + +
+ {assignment.botName} + +
+
+ Y: {assignment.yMin} - {assignment.yMax} + Current Y: {assignment.currentY} +
+ +
+ {assignment.blocksPlaced} / {assignment.blocksTotal} ({botPct}%) +
+
+ ); + })} +
+ + )} + + + {/* Schematic Selection */} + {!activeBuild && ( + <> +
+

Select Schematic

+ {loading ? ( +
+
+

Loading schematics...

+
+ ) : schematics.length === 0 ? ( +
+
+ + + + + +
+

No schematics available

+

Add .schem or .schematic files to the schematics directory

+
+ ) : ( +
+ {schematics.map((schem, i) => { + const isSelected = selectedSchematic?.filename === schem.filename; + return ( + setSelectedSchematic(isSelected ? null : schem)} + className={`text-left bg-zinc-900/80 border rounded-xl p-4 transition-all duration-150 ${ + isSelected + ? 'border-teal-500/60 ring-1 ring-teal-500/20' + : 'border-zinc-800/60 hover:border-zinc-700/60' + }`} + > +
+

+ {schem.filename.replace(/\.(schem|schematic)$/i, '')} +

+ {isSelected && ( + + + + + + )} +
+
+ {schem.size.x} x {schem.size.y} x {schem.size.z} + | + {schem.blockCount.toLocaleString()} blocks +
+
+ ); + })} +
+ )} +
+ + {/* Build Configuration */} + + {selectedSchematic && ( + +
+

Build Configuration

+ + {/* Origin Coordinates */} +
+ +
+ {(['x', 'y', 'z'] as const).map((axis) => ( +
+ {axis} + setOrigin((prev) => ({ ...prev, [axis]: parseInt(e.target.value) || 0 }))} + className="bg-zinc-800/80 border border-zinc-700/50 rounded-lg px-3 py-2 text-xs text-white w-24 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" + /> +
+ ))} + +
+ {/* Ground height indicator */} + {groundInfo && ( +
{ + const delta = origin.y - groundInfo.y; + if (delta < 0) return 'bg-red-500/10 border-red-500/30 text-red-400'; + if (delta > 10) return 'bg-red-500/10 border-red-500/30 text-red-400'; + if (delta > 2) return 'bg-amber-500/10 border-amber-500/30 text-amber-400'; + return 'bg-emerald-500/10 border-emerald-500/30 text-emerald-400'; + })() + }`}> + + + + + + + Ground at Y={groundInfo.y} ({groundInfo.block}) + {' '}— Building {origin.y - groundInfo.y < 0 + ? `${Math.abs(origin.y - groundInfo.y)} blocks underground` + : `${origin.y - groundInfo.y} block${origin.y - groundInfo.y !== 1 ? 's' : ''} above ground`} +
+ )} + {/* Footprint summary */} + {selectedSchematic && ( +

+ Footprint: {selectedSchematic.size.x} x {selectedSchematic.size.z} blocks,{' '} + {selectedSchematic.size.y} tall + {' '}— from ({origin.x}, {origin.y}, {origin.z}) + {' '}to ({origin.x + selectedSchematic.size.x}, {origin.y + selectedSchematic.size.y}, {origin.z + selectedSchematic.size.z}) +

+ )} + {/* Use player/bot position */} +
+ {playerList.filter((p) => p.isOnline && p.position).map((player) => ( + + ))} + {connectedBots.map((bot) => bot.position && ( + + ))} +
+ {/* Pick on Map mini-map */} + {selectedSchematic && ( + + )} +
+ + {/* Bot Selector -- Tabbed */} +
+ +
+ {(['existing', 'create'] as const).map((mode) => ( + + ))} +
+ + {botMode === 'existing' ? ( + connectedBots.length === 0 ? ( +

No connected bots available

+ ) : ( +
+ {connectedBots.map((bot) => { + const checked = selectedBots.has(bot.name); + return ( + + ); + })} +
+ ) + ) : ( +
+ {/* AI Recommendation */} + {recommendation && ( +
+
+
+ + + + + Recommended: {recommendation.count} bot{recommendation.count !== 1 ? 's' : ''} + +
+ {botCount !== recommendation.count && ( + + )} +
+

{recommendation.reasoning}

+
+ )} + + {/* Name Prefix */} +
+
+ + setNamePrefix(e.target.value.replace(/\s/g, ''))} + placeholder="Builder" + className="w-full bg-zinc-800/80 border border-zinc-700/50 rounded-lg px-3 py-2 text-xs text-white" + /> +
+
+ + +
+
+ + {/* Bot Count Stepper */} +
+ +
+ + {botCount} + + + ~{estimatedMinutes} min estimated + +
+
+ + {/* Name Preview */} +

+ Will create: {effectiveBotNames.join(', ')} +

+
+ )} +
+ + {/* Build Options */} +
+ +
+ + +
+
+ + {/* Layer Preview */} + {layerPreview.length > 0 && ( +
+ +
+ {layerPreview.map((lp, i) => { + const range = lp.yMax - lp.yMin + 1; + const pct = selectedSchematic ? Math.round((range / selectedSchematic.size.y) * 100) : 0; + return ( +
+ {lp.botName} +
+
+
+ + Y {lp.yMin} - {lp.yMax} ({range} layers) + +
+ ); + })} +
+
+ )} + + {/* Error */} + {error && ( +

+ {error} +

+ )} + + {/* Start Button */} + {(() => { + const canStart = botMode === 'existing' + ? selectedBots.size > 0 + : namePrefix.trim().length > 0 && botCount > 0; + const buttonLabel = botMode === 'existing' + ? `Start Build with ${selectedBots.size} Bot${selectedBots.size !== 1 ? 's' : ''}` + : `Create ${botCount} Bot${botCount !== 1 ? 's' : ''} & Start Build`; + return ( + + ); + })()} +
+ + )} + + + )} +
+ ); +} + +function MiniMapSection({ schematic, origin, setOrigin, onOriginPicked }: { schematic: SchematicInfo; origin: { x: number; y: number; z: number }; setOrigin: (o: { x: number; y: number; z: number }) => void; onOriginPicked?: (x: number, z: number) => void }) { + const [showMap, setShowMap] = useState(false); + + return ( +
+ + + {showMap && ( + + { setOrigin(o); onOriginPicked?.(o.x, o.z); }} + height={300} + /> + + )} + +
+ ); +} diff --git a/web/src/app/chains/page.tsx b/web/src/app/chains/page.tsx new file mode 100644 index 0000000..31c7b4c --- /dev/null +++ b/web/src/app/chains/page.tsx @@ -0,0 +1,879 @@ +'use client'; + +import { useEffect, useState, useCallback } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { api, SupplyChain, ChainTemplate, ChainStage, MissionRecord } from '@/lib/api'; +import { useBotStore } from '@/lib/store'; +import { PageHeader } from '@/components/PageHeader'; + +const STAGE_STATUS_COLORS: Record = { + pending: '#6B7280', + queued: '#F59E0B', + running: '#10B981', + completed: '#3B82F6', + failed: '#EF4444', +}; + +const CHAIN_STATUS_COLORS: Record = { + idle: '#6B7280', + running: '#10B981', + paused: '#F59E0B', + completed: '#3B82F6', + failed: '#EF4444', +}; + +const MISSION_STATUS_COLORS: Record = { + draft: '#6B7280', + queued: '#F59E0B', + running: '#1ABC9C', + paused: '#F59E0B', + completed: '#10B981', + failed: '#EF4444', + cancelled: '#6B7280', +}; + +function StatusBadge({ status, colors }: { status: string; colors: Record }) { + const color = colors[status] ?? '#6B7280'; + return ( + + + {status} + + ); +} + +interface StageFormData { + botName: string; + task: string; + inputChest: { x: string; y: string; z: string; label: string }; + outputChest: { x: string; y: string; z: string; label: string }; + inputItems: { item: string; count: string }[]; + outputItems: { item: string; count: string }[]; +} + +function emptyStage(): StageFormData { + return { + botName: '', + task: '', + inputChest: { x: '', y: '', z: '', label: '' }, + outputChest: { x: '', y: '', z: '', label: '' }, + inputItems: [], + outputItems: [], + }; +} + +function ChainCard({ + chain, + mission, + onSelect, + onStart, + onPause, + onCancel, + onDelete, +}: { + chain: SupplyChain; + mission?: MissionRecord | null; + onSelect: () => void; + onStart: () => void; + onPause: () => void; + onCancel: () => void; + onDelete: () => void; +}) { + return ( + +
+
+

{chain.name}

+ {chain.description && ( +

{chain.description}

+ )} +
+ +
+ +
+ {chain.stages.length} stage{chain.stages.length !== 1 ? 's' : ''} + | + Stage {chain.currentStageIndex + 1} of {chain.stages.length} + {chain.loop && ( + <> + | + Loop + + )} +
+ + {/* Mission link */} + {mission && ( +
+ Mission + +
+ )} + + {/* Mini stage indicators */} +
+ {chain.stages.map((stage, i) => { + const color = STAGE_STATUS_COLORS[stage.status] ?? '#6B7280'; + return ( +
+ ); + })} +
+ +
e.stopPropagation()}> + {(chain.status === 'idle' || chain.status === 'paused') && ( + + )} + {chain.status === 'running' && ( + + )} + {(chain.status === 'running' || chain.status === 'paused') && ( + + )} + {(chain.status === 'idle' || chain.status === 'completed' || chain.status === 'failed') && ( + + )} +
+ + ); +} + +function StageCard({ stage, index, isActive }: { stage: ChainStage; index: number; isActive: boolean }) { + return ( + + {isActive && stage.status === 'running' && ( + + )} +
+ Stage {index + 1} + +
+

{stage.botName}

+

{stage.task}

+ + {stage.inputChest && ( +
+ Input: {stage.inputChest.label || `${stage.inputChest.x}, ${stage.inputChest.y}, ${stage.inputChest.z}`} +
+ )} + {stage.outputChest && ( +
+ Output: {stage.outputChest.label || `${stage.outputChest.x}, ${stage.outputChest.y}, ${stage.outputChest.z}`} +
+ )} + + {stage.inputItems && stage.inputItems.length > 0 && ( +
+ {stage.inputItems.map((item, i) => ( + + {item.count}x {item.item} + + ))} +
+ )} + {stage.outputItems && stage.outputItems.length > 0 && ( +
+ {stage.outputItems.map((item, i) => ( + + {item.count}x {item.item} + + ))} +
+ )} + + {stage.error && ( +

{stage.error}

+ )} +
+ ); +} + +function StageArrow() { + return ( +
+
+
+
+ ); +} + +function CreateChainForm({ + templates, + botNames, + onCreated, + onCancel, +}: { + templates: ChainTemplate[]; + botNames: string[]; + onCreated: () => void; + onCancel: () => void; +}) { + const [name, setName] = useState(''); + const [description, setDescription] = useState(''); + const [mode, setMode] = useState<'template' | 'custom'>('template'); + const [selectedTemplate, setSelectedTemplate] = useState(''); + const [stages, setStages] = useState([emptyStage()]); + const [loop, setLoop] = useState(false); + const [creating, setCreating] = useState(false); + const [error, setError] = useState(null); + + const handleTemplateChange = (templateId: string) => { + setSelectedTemplate(templateId); + const tmpl = templates.find((t) => t.id === templateId); + if (tmpl) { + setStages( + tmpl.stages.map((s) => ({ + botName: '', + task: s.task, + inputChest: { x: '', y: '', z: '', label: '' }, + outputChest: { x: '', y: '', z: '', label: '' }, + inputItems: s.inputItems?.map((i) => ({ item: i.item, count: String(i.count) })) ?? [], + outputItems: s.outputItems?.map((i) => ({ item: i.item, count: String(i.count) })) ?? [], + })), + ); + } + }; + + const updateStage = (index: number, patch: Partial) => { + setStages((prev) => prev.map((s, i) => (i === index ? { ...s, ...patch } : s))); + }; + + const addStage = () => setStages((prev) => [...prev, emptyStage()]); + const removeStage = (index: number) => setStages((prev) => prev.filter((_, i) => i !== index)); + + const addInputItem = (stageIdx: number) => { + setStages((prev) => + prev.map((s, i) => + i === stageIdx ? { ...s, inputItems: [...s.inputItems, { item: '', count: '1' }] } : s, + ), + ); + }; + + const addOutputItem = (stageIdx: number) => { + setStages((prev) => + prev.map((s, i) => + i === stageIdx ? { ...s, outputItems: [...s.outputItems, { item: '', count: '1' }] } : s, + ), + ); + }; + + const updateInputItem = (stageIdx: number, itemIdx: number, patch: Partial<{ item: string; count: string }>) => { + setStages((prev) => + prev.map((s, i) => + i === stageIdx + ? { ...s, inputItems: s.inputItems.map((it, j) => (j === itemIdx ? { ...it, ...patch } : it)) } + : s, + ), + ); + }; + + const updateOutputItem = (stageIdx: number, itemIdx: number, patch: Partial<{ item: string; count: string }>) => { + setStages((prev) => + prev.map((s, i) => + i === stageIdx + ? { ...s, outputItems: s.outputItems.map((it, j) => (j === itemIdx ? { ...it, ...patch } : it)) } + : s, + ), + ); + }; + + const handleCreate = async () => { + if (!name.trim()) { + setError('Name is required'); + return; + } + if (stages.some((s) => !s.botName || !s.task.trim())) { + setError('Every stage needs a bot and task'); + return; + } + + setCreating(true); + setError(null); + + try { + const payload = { + name: name.trim(), + description: description.trim() || undefined, + loop, + stages: stages.map((s) => ({ + botName: s.botName, + task: s.task, + inputChest: s.inputChest.x ? { x: Number(s.inputChest.x), y: Number(s.inputChest.y), z: Number(s.inputChest.z), label: s.inputChest.label } : undefined, + outputChest: s.outputChest.x ? { x: Number(s.outputChest.x), y: Number(s.outputChest.y), z: Number(s.outputChest.z), label: s.outputChest.label } : undefined, + inputItems: s.inputItems.filter((i) => i.item).map((i) => ({ item: i.item, count: Number(i.count) || 1 })), + outputItems: s.outputItems.filter((i) => i.item).map((i) => ({ item: i.item, count: Number(i.count) || 1 })), + })), + }; + await api.createChain(payload); + onCreated(); + } catch (err: unknown) { + setError(err instanceof Error ? err.message : 'Failed to create chain'); + } finally { + setCreating(false); + } + }; + + const inputClass = 'w-full bg-zinc-800/60 border border-zinc-700/50 rounded-lg px-3 py-2 text-xs text-white placeholder-zinc-500 focus:outline-none focus:border-amber-500/50'; + const smallInputClass = 'bg-zinc-800/60 border border-zinc-700/50 rounded px-2 py-1 text-[11px] text-white placeholder-zinc-500 focus:outline-none focus:border-amber-500/50'; + + return ( + +
+

Create Supply Chain

+ +
+ +
+
+ + setName(e.target.value)} /> +
+ +
+ + setDescription(e.target.value)} /> +
+ + {/* Mode toggle */} +
+ +
+ + +
+
+ + {/* Template selector */} + {mode === 'template' && ( +
+ + +
+ )} + + {/* Stages */} +
+
+ + {mode === 'custom' && ( + + )} +
+ +
+ {stages.map((stage, idx) => ( +
+
+ Stage {idx + 1} + {stages.length > 1 && ( + + )} +
+ +
+
+ + +
+
+ + updateStage(idx, { task: e.target.value })} + /> +
+
+ + {/* Chest coords */} +
+
+ +
+ updateStage(idx, { inputChest: { ...stage.inputChest, x: e.target.value } })} /> + updateStage(idx, { inputChest: { ...stage.inputChest, y: e.target.value } })} /> + updateStage(idx, { inputChest: { ...stage.inputChest, z: e.target.value } })} /> + updateStage(idx, { inputChest: { ...stage.inputChest, label: e.target.value } })} /> +
+
+
+ +
+ updateStage(idx, { outputChest: { ...stage.outputChest, x: e.target.value } })} /> + updateStage(idx, { outputChest: { ...stage.outputChest, y: e.target.value } })} /> + updateStage(idx, { outputChest: { ...stage.outputChest, z: e.target.value } })} /> + updateStage(idx, { outputChest: { ...stage.outputChest, label: e.target.value } })} /> +
+
+
+ + {/* Items */} +
+
+
+ + +
+ {stage.inputItems.map((item, ii) => ( +
+ updateInputItem(idx, ii, { item: e.target.value })} /> + updateInputItem(idx, ii, { count: e.target.value })} /> +
+ ))} +
+
+
+ + +
+ {stage.outputItems.map((item, oi) => ( +
+ updateOutputItem(idx, oi, { item: e.target.value })} /> + updateOutputItem(idx, oi, { count: e.target.value })} /> +
+ ))} +
+
+
+ ))} +
+
+ + {/* Loop toggle */} + + + {error &&

{error}

} + + +
+
+ ); +} + +function ChainDetail({ + chain, + mission, + onBack, + onStart, + onPause, + onCancel, +}: { + chain: SupplyChain; + mission?: MissionRecord | null; + onBack: () => void; + onStart: () => void; + onPause: () => void; + onCancel: () => void; +}) { + return ( + +
+ +
+

{chain.name}

+ {chain.description &&

{chain.description}

} +
+ +
+ + {/* Mission status */} + {mission && ( +
+ Mission + {mission.title} + +
+ )} + + {/* Action buttons */} +
+ {(chain.status === 'idle' || chain.status === 'paused') && ( + + )} + {chain.status === 'running' && ( + + )} + {(chain.status === 'running' || chain.status === 'paused') && ( + + )} + {chain.loop && ( + + Looping + + )} +
+ + {/* Flow visualization */} +
+
+ {chain.stages.map((stage, i) => ( +
+ + {i < chain.stages.length - 1 && } +
+ ))} + {chain.loop && chain.stages.length > 0 && ( +
+
+ + + + +
+ )} +
+
+ + ); +} + +export default function ChainsPage() { + const [chains, setLocalChains] = useState([]); + const [templates, setTemplates] = useState([]); + const [loading, setLoading] = useState(true); + const [showCreate, setShowCreate] = useState(false); + const [selectedChainId, setSelectedChainId] = useState(null); + const [error, setError] = useState(null); + const [chainMissions, setChainMissions] = useState>({}); + + const botList = useBotStore((s) => s.botList); + const setStoreChains = useBotStore((s) => s.setChains); + + const botNames = botList.map((b) => b.name); + + const fetchChains = useCallback(async () => { + try { + const data = await api.getChains(); + setLocalChains(data.chains); + setStoreChains(data.chains); + } catch { + // API may not exist yet + setLocalChains([]); + } + }, [setStoreChains]); + + const fetchTemplates = useCallback(async () => { + try { + const data = await api.getChainTemplates(); + setTemplates(data.templates); + } catch { + setTemplates([]); + } + }, []); + + useEffect(() => { + let cancelled = false; + (async () => { + await Promise.all([fetchChains(), fetchTemplates()]); + if (!cancelled) { + setLoading(false); + } + })(); + return () => { + cancelled = true; + }; + }, [fetchChains, fetchTemplates]); + + const selectedChain = chains.find((c) => c.id === selectedChainId) ?? null; + + const handleStart = async (id: string) => { + try { + await api.startChain(id); + await fetchChains(); + + // Create a mission for this chain + const chain = chains.find((c) => c.id === id); + if (chain) { + const stageBotNames = [...new Set(chain.stages.map((s) => s.botName))]; + try { + const missionResult = await api.createMission({ + type: 'supply_chain', + title: chain.name, + description: chain.description || `Supply chain with ${chain.stages.length} stage(s)`, + assigneeType: 'bot', + assigneeIds: stageBotNames, + priority: 'normal', + source: 'dashboard', + }); + setChainMissions((prev) => ({ ...prev, [id]: missionResult.mission })); + } catch { + // Mission creation is best-effort + } + } + } catch (err: unknown) { + setError(err instanceof Error ? err.message : 'Failed to start chain'); + } + }; + + const handlePause = async (id: string) => { + try { + await api.pauseChain(id); + await fetchChains(); + } catch (err: unknown) { + setError(err instanceof Error ? err.message : 'Failed to pause chain'); + } + }; + + const handleCancel = async (id: string) => { + try { + await api.cancelChain(id); + await fetchChains(); + // Also cancel linked mission + const mission = chainMissions[id]; + if (mission) { + try { await api.cancelMission(mission.id); } catch {} + setChainMissions((prev) => { + const next = { ...prev }; + delete next[id]; + return next; + }); + } + } catch (err: unknown) { + setError(err instanceof Error ? err.message : 'Failed to cancel chain'); + } + }; + + const handleDelete = async (id: string) => { + try { + await api.deleteChain(id); + await fetchChains(); + } catch (err: unknown) { + setError(err instanceof Error ? err.message : 'Failed to resume chain'); + } + }; + + const handleCreated = () => { + setShowCreate(false); + fetchChains(); + }; + + if (loading) { + return ( +
+ +
+
+
+
+ ); + } + + return ( +
+ + + {error && ( + + {error} + + + )} + + + {selectedChain ? ( + setSelectedChainId(null)} + onStart={() => handleStart(selectedChain.id)} + onPause={() => handlePause(selectedChain.id)} + onCancel={() => handleCancel(selectedChain.id)} + /> + ) : ( + + {/* Header with create button */} +
+

{chains.length} chain{chains.length !== 1 ? 's' : ''}

+ +
+ + + {showCreate && ( +
+ setShowCreate(false)} + /> +
+ )} +
+ + {/* Chain list */} + {chains.length === 0 && !showCreate ? ( +
+ + + + +

No supply chains yet

+

Create one to automate bot production

+
+ ) : ( +
+ + {chains.map((chain) => ( + setSelectedChainId(chain.id)} + onStart={() => handleStart(chain.id)} + onPause={() => handlePause(chain.id)} + onCancel={() => handleCancel(chain.id)} + onDelete={() => handleDelete(chain.id)} + /> + ))} + +
+ )} +
+ )} +
+
+ ); +} diff --git a/web/src/app/chat/page.tsx b/web/src/app/chat/page.tsx index 0c47449..c382b20 100644 --- a/web/src/app/chat/page.tsx +++ b/web/src/app/chat/page.tsx @@ -2,6 +2,7 @@ import { useEffect, useState, useRef } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; +import Image from 'next/image'; import { useBotStore } from '@/lib/store'; import { api, type ChatMessage } from '@/lib/api'; import { getPersonalityColor } from '@/lib/constants'; @@ -53,7 +54,7 @@ export default function ChatPage() { loadAll(); const interval = setInterval(loadAll, 5000); return () => clearInterval(interval); - }, [bots.length]); + }, [bots]); // Auto-scroll to bottom useEffect(() => { @@ -167,9 +168,12 @@ export default function ChatPage() { className="w-8 h-8 rounded-lg flex items-center justify-center" style={{ backgroundColor: `${getPersonalityColor(bots.find((b) => b.name === selectedThread.botName)?.personality ?? '')}20` }} > - diff --git a/web/src/app/commander/page.tsx b/web/src/app/commander/page.tsx new file mode 100644 index 0000000..029b79b --- /dev/null +++ b/web/src/app/commander/page.tsx @@ -0,0 +1,516 @@ +'use client'; + +import { useState, useRef, useCallback, type KeyboardEvent } from 'react'; +import { useEffect } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { api, type CommanderPlan, type CommanderResult } from '@/lib/api'; +import { PageHeader } from '@/components/PageHeader'; +import { CommanderPanel } from '@/components/CommanderPanel'; +import { useControlStore, useMissionStore } from '@/lib/store'; + +const EXAMPLE_PROMPTS = [ + 'Send all guards to the village', + 'Have Ada mine 3 iron ore', + 'Pause every bot except builders', + 'Move the miners to the mine marker', +]; + +const DRAFT_KEY = 'commander-draft'; + +interface HistoryEntry { + input: string; + plan: CommanderPlan; + result?: CommanderResult | null; + timestamp: number; +} + +export default function CommanderPage() { + const recentCommands = useControlStore((s) => s.commandHistory.filter((command) => command.source === 'commander').slice(0, 5)); + const recentMissions = useMissionStore((s) => s.missions.filter((mission) => mission.source === 'commander').slice(0, 5)); + const [input, setInput] = useState(() => { + if (typeof window === 'undefined') return ''; + return window.localStorage.getItem(DRAFT_KEY) ?? ''; + }); + const [parsing, setParsing] = useState(false); + const [plan, setPlan] = useState(null); + const [executing, setExecuting] = useState(false); + const [result, setResult] = useState(null); + const [error, setError] = useState(null); + const [history, setHistory] = useState([]); + const [expandedHistory, setExpandedHistory] = useState(null); + const textareaRef = useRef(null); + + useEffect(() => { + let cancelled = false; + void api.getCommanderHistory({ limit: 20 }).then((data) => { + if (cancelled) return; + setHistory(data.entries.map((entry) => ({ + input: entry.input, + plan: entry.plan, + result: entry.result ?? null, + timestamp: entry.executedAt ? Date.parse(entry.executedAt) : Date.parse(entry.createdAt), + }))); + }).catch(() => {}); + return () => { + cancelled = true; + }; + }, []); + + useEffect(() => { + if (typeof window === 'undefined') return; + window.localStorage.setItem(DRAFT_KEY, input); + }, [input]); + + const handleParse = useCallback(async () => { + if (!input.trim() || parsing) return; + setParsing(true); + setError(null); + setPlan(null); + setResult(null); + try { + const data = await api.parseCommanderInput(input.trim()); + setPlan(data.plan); + } catch (e: unknown) { + setError(e instanceof Error ? e.message : 'Failed to parse command'); + } + setParsing(false); + }, [input, parsing]); + + const handleExecute = useCallback(async () => { + if (!plan || executing) return; + setExecuting(true); + setError(null); + try { + const data = await api.executeCommanderPlan(plan.id); + setResult(data.result); + setHistory((prev) => [ + { input, plan, result: data.result, timestamp: Date.now() }, + ...prev, + ]); + api.getCommanderHistory({ limit: 20 }).then((historyData) => { + setHistory(historyData.entries.map((entry) => ({ + input: entry.input, + plan: entry.plan, + result: entry.result ?? null, + timestamp: entry.executedAt ? Date.parse(entry.executedAt) : Date.parse(entry.createdAt), + }))); + }).catch(() => {}); + setPlan(null); + setInput(''); + } catch (e: unknown) { + setError(e instanceof Error ? e.message : 'Execution failed'); + } + setExecuting(false); + }, [plan, executing, input]); + + const handleCancel = useCallback(() => { + setPlan(null); + setResult(null); + setError(null); + }, []); + + const handleEditRetry = useCallback(() => { + setPlan(null); + setResult(null); + setError(null); + textareaRef.current?.focus(); + }, []); + + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) { + e.preventDefault(); + handleParse(); + } + }; + + const handleExampleClick = (prompt: string) => { + setInput(prompt); + setPlan(null); + setResult(null); + setError(null); + textareaRef.current?.focus(); + }; + + const autoGrow = (el: HTMLTextAreaElement) => { + el.style.height = 'auto'; + el.style.height = `${Math.min(el.scrollHeight, 200)}px`; + }; + + const restoreHistoryEntry = (entry: HistoryEntry) => { + setInput(entry.input); + setPlan(entry.plan); + setResult(entry.result ?? null); + setError(null); + requestAnimationFrame(() => { + if (textareaRef.current) { + textareaRef.current.focus(); + autoGrow(textareaRef.current); + } + }); + }; + + return ( +
+ + + {/* Input area */} +
+
+