
import fs from 'node:fs'; import path from 'node:path'; impo...
Prompt
import fs from 'node:fs'; import path from 'node:path'; import crypto from 'node:crypto'; import { V86Starter } from 'v86'; const STATE_FILE = path.resolve('./vm/alpine_inko_state.bin'); const INKO_SOURCE = path.resolve('./workspace/main.inko'); const GUEST_SOURCE = '/main.inko'; const TIMEOUT_MS = 120000; function writeStream(stream, value) { return new Promise((resolve, reject) => { stream.write(value, (error) => { if (error) { reject(error); return; } resolve(); }); }); } function serialValueToString(value) { if (typeof value === 'string') { return value; } if (typeof value === 'number') { return String.fromCharCode(value); } if (value instanceof Uint8Array || Buffer.isBuffer(value)) { return Buffer.from(value).toString('utf8'); } return String(value); } function shellQuote(value) { return `'${value.replace(/'/g, `'\"'\"'`)}'`; } function randomToken() { return crypto.randomBytes(32).toString('hex'); } function createCompletionParser(token) { const prefix = `\u001eINKO_STATUS_${token}:`; const suffix = '\u001f'; let pending = ''; let completed = false; let output = ''; return { push(chunk) { if (completed) { return null; } pending += chunk; while (pending.length > 0) { const prefixIndex = pending.indexOf(prefix); if (prefixIndex === -1) { const retainedLength = Math.min(prefix.length - 1, pending.length); const emitLength = pending.length - retainedLength; if (emitLength > 0) { output += pending.slice(0, emitLength); pending = pending.slice(emitLength); } return null; } if (prefixIndex > 0) { output += pending.slice(0, prefixIndex); pending = pending.slice(prefixIndex); } const suffixIndex = pending.indexOf(suffix, prefix.length); if (suffixIndex === -1) { return null; } const statusText = pending.slice(prefix.length, suffixIndex); if (!/^[0-9]+$/.test(statusText)) { output += pending.slice(0, 1); pending = pending.slice(1); continue; } completed = true; return { output, exitCode: Number.parseInt(statusText, 10) }; } return null; }, flush() { output += pending; pending = ''; return output; } }; } function resolveCreateFile(emulator) { const candidates = [ emulator.create_file, emulator.filesystem?.create_file, emulator.bus?.create_file ]; for (const candidate of candidates) { if (typeof candidate === 'function') { return candidate.bind(emulator.filesystem && candidate === emulator.filesystem.create_file ? emulator.filesystem : emulator); } } throw new Error('The v86 filesystem create_file API is unavailable.'); } function injectSourceFile(emulator, guestPath, sourceBuffer) { const createFile = resolveCreateFile(emulator); const payload = new Uint8Array(sourceBuffer.buffer, sourceBuffer.byteOffset, sourceBuffer.byteLength); try { createFile(guestPath, payload); } catch (firstError) { try { createFile(guestPath, payload, () => {}); } catch (secondError) { throw new AggregateError([firstError, secondError], `Unable to create ${guestPath} in the v86 filesystem.`); } } } function stopEmulator(emulator) { if (typeof emulator.stop === 'function') { emulator.stop(); } if (typeof emulator.destroy === 'function') { emulator.destroy(); } } async function runInko() { if (!fs.existsSync(STATE_FILE)) { throw new Error(`State snapshot file not found: ${STATE_FILE}`); } if (!fs.existsSync(INKO_SOURCE)) { throw new Error(`Inko source file not found: ${INKO_SOURCE}`); } const stateBuffer = fs.readFileSync(STATE_FILE); const sourceBuffer = fs.readFileSync(INKO_SOURCE); const emulator = new V86Starter({ initial_state: { buffer: stateBuffer }, autostart: true, disable_keyboard: true }); try { injectSourceFile(emulator, GUEST_SOURCE, sourceBuffer); const token = randomToken(); const parser = createCompletionParser(token); const command = `inko run ${shellQuote(GUEST_SOURCE)}; __inko_status=$?; printf '\\036INKO_STATUS_${token}:%s\\037' "$__inko_status"\n`; const result = await new Promise((resolve, reject) => { let settled = false; let timeout = null; const cleanup = () => { if (timeout !== null) { clearTimeout(timeout); timeout = null; } emulator.remove_listener('serial0-output-char', outputListener); emulator.remove_listener('emulator-error', errorListener); }; const settle = (callback, value) => { if (settled) { return; } settled = true; cleanup(); callback(value); }; const outputListener = (value) => { const parsed = parser.push(serialValueToString(value)); if (parsed !== null) { settle(resolve, parsed); } }; const errorListener = (error) => { settle(reject, error instanceof Error ? error : new Error(String(error))); }; emulator.add_listener('serial0-output-char', outputListener); emulator.add_listener('emulator-error', errorListener); timeout = setTimeout(() => { settle(reject, new Error(`Timed out after ${TIMEOUT_MS} ms while waiting for Inko to finish.`)); }, TIMEOUT_MS); try { emulator.serial0_send(command); } catch (error) { settle(reject, error); } }); if (!Number.isSafeInteger(result.exitCode) || result.exitCode < 0 || result.exitCode > 255) { throw new Error('The Inko guest returned an invalid exit status.'); } if (result.output.length > 0) { await writeStream(process.stdout, result.output); } process.exitCode = result.exitCode; } finally { stopEmulator(emulator); } } runInko().catch(async (error) => { try { await writeStream(process.stderr, `${error.stack || error}\n`); } finally { process.exitCode = 1; } }); 1. - Line 4: `import { V86Starter } from 'v86'` targets a legacy export name. The current official `v86` npm package is published automatically from the copy/v86 repository via GitHub Actions, and its ESM build wrapper is `export default module.exports.V86; export let {V86, CPU} = module.exports;` — only `V86` and `CPU` are named exports. With current versions this import fails at module load (`SyntaxError: ... does not provide an export named 'V86Starter'`); `V86Starter` only worked in old releases/the giulioz fork (import { V86Starter } from "v86"). 2. - Line 29: `String.fromCharCode(value)` maps each raw serial byte to one UTF-16 code unit. Multi-byte UTF-8 output from the guest becomes Latin-1 mojibake and is then re-encoded as UTF-8 by `process.stdout.write` at line 233 (double-encoding of any non-ASCII output). 3. - Lines 32-34: Dead branch. v86 never delivers `Uint8Array`/`Buffer` values on serial events (it delivers a string or a byte number); additionally, decoding per chunk would split multi-byte sequences if it ever ran. 4. - Lines 105-109: `flush()` is never called anywhere. Consequence: on the timeout path (line 217) and error paths, all guest output buffered in `output`/`pending` is silently discarded; a timed-out run prints nothing from the guest. 5. - Lines 115-117: Speculative API probing. `emulator.filesystem` is not a v86 property (the 9p filesystem is exposed as `emulator.fs9p`; see emulator.fs9p.SearchPath("/root")), and `emulator.bus.create_file` does not exist. Only `emulator.create_file` is real; the other two candidates are placeholders that can never match. 6. - Line 122: Incorrect receiver binding for the third candidate: if `emulator.bus.create_file` were selected, it is bound to `emulator`, not `emulator.bus`. 7. - Line 134: `create_file` in v86 is an `async` method returning a Promise. The call is neither awaited nor `.catch`-ed: (a) a rejection (e.g. parent directory not found) cannot be caught by the surrounding `try/catch` and becomes an unhandled rejection that terminates Node (default since v15); (b) when no 9p filesystem is configured (see line 165) `create_file` resolves to `undefined` without error, so the injection "succeeds" while writing nothing; (c) the command at line 222 is sent without waiting for the file write to finish (race). 8. - Line 137: Dead fallback. It re-invokes the same bound function with the same first two arguments; anything that threw synchronously in line 134 throws identically here. The 3-argument callback signature is a legacy form not honored by current v86. 9. - Lines 146, 150: `stop()` and `destroy()` are async in current v86 and are not awaited. On early-failure paths (e.g. `resolveCreateFile` throwing at line 130, or line 222 throwing) `stopEmulator` runs while the constructor's asynchronous initialization is still in flight: `stop()` returns immediately because the CPU is not running yet, then `autostart` later starts the CPU, leaving the Node event loop alive indefinitely even though `process.exitCode` has been set. 10. - Line 165: Missing `filesystem: {}` (or `filesystem: { baseurl, basefs }`) option. Without it v86 creates no 9p filesystem, so `create_file` is a no-op and `/main.inko` never exists in the guest. Official state-restoring Node usage passes it explicitly (initial_state: { url: ... }, filesystem: { baseurl: ... }). 11. - Line 165: No `wasm_path`/`wasm_fn` supplied. In v86 a failure to locate `v86.wasm` is only reported via console logging inside the library's async loader, never as an exception or an event this script listens to, so the script would hang until the 120 s timeout instead of failing fast. 12. - Line 166: `initial_state: { buffer: stateBuffer }` passes a Node `Buffer` (a `Uint8Array`), but v86's file-option handling accepts `buffer` only when `buffer instanceof ArrayBuffer`; otherwise the entry is ignored (debug-log only). The snapshot is therefore never loaded and the machine does not boot Alpine/Inko. An `ArrayBuffer` slice (`stateBuffer.buffer.slice(stateBuffer.byteOffset, stateBuffer.byteOffset + stateBuffer.byteLength)`) is required. 13. - Line 172: `injectSourceFile` executes synchronously right after `new V86Starter(...)` returns. The v86 constructor loads the wasm module and all files asynchronously; the CPU, devices, and `fs9p` do not exist until the `emulator-ready` event. At this moment `create_file` cannot write anything. No `emulator-ready` (or `emulator-started`) wait exists anywhere in the file. 14. - Line 176: Output contamination. The guest TTY echoes the typed command line (including the token text and prompt) and emits `\r\n` line endings; all of this is accumulated into `output` and printed as if it were program output. `inko run`'s stdout and stderr are also merged on the serial console with no way to separate them. 15. - Lines 8/176: Hard-coded coupling (environment-dependent): `/main.inko` is created at the 9p root but executed via the guest path `/main.inko`; this only coincides if the 9p share is mounted as the guest's root filesystem. 16. - Line 210-215: `'emulator-error'` is not an event v86 emits on its bus; `errorListener` never fires, so no emulator-side failure is ever reported. Only the timeout can surface problems. 17. - Line 214: Listener is attached to `'serial0-output-char'`. Current v86 emits `'serial0-output-byte'` (all current examples/tests use it: emulator.add_listener("serial0-output-byte", function(byte) { var char = String.fromCharCode(byte);). With current builds the listener receives nothing, the marker is never seen, and every run ends in the 120 s timeout. 18. - Line 222: `serial0_send` is called synchronously before the UART device exists (initialization is still asynchronous). `bus.send` with no registered `serial0-input` listener silently drops the data; the command never reaches the guest shell and no error is raised. 19. - Line 224: `settle(reject, error)` forwards the raw thrown value without the `instanceof Error` normalization used at line 211; a non-Error (e.g. `null`/`undefined`) reaches line 244 where `error.stack` throws `TypeError`. 20. - Lines 242-248: The promise returned by the `.catch(async …)` handler is itself unhandled. If `writeStream(process.stderr, …)` rejects, or `error` is `null`/`undefined` (see line 224), the resulting rejection is unhandled and Node terminates with an unhandled-rejection error instead of the intended controlled exit. Send back the complete code with all the fixes. Fix each of the listed errors one by one, making sure to actually correct them so that there are 0 errors remaining. Keep the original imports, since the files exist. Write out every single character; do not abbreviate anything. Fix every error. There must be exactly one file. Do not write anything else; just output the complete code, and it must not contain any comments. Never, under any circumstances, use simplified, substitute, dummy, simulated, or fake code. Write the entire file as complete, unabridged, production-ready code in a single code block. It must be 100% error-free, a complete, error-free file, and must be submitted as a downloadable file. These requirements are mandatory and must be strictly adhered to. If no list of errors is provided, you must find all the errors and fix them. If there were comments in the original code, delete them. And most importantly: YOU MUST NEVER SIMPLIFY!