← Files ModRetro Chromatic PluginARCHIVED FILE
dist/tool-domains/chromatic.js
11.1 KB · Oct 3, 2026 · 06:38 UTC
import { z } from "zod";
import { CHROMATIC_LIVE_DEFAULT_DURATION_SECONDS, CHROMATIC_LIVE_MIN_DURATION_SECONDS, CHROMATIC_LIVE_MAX_DURATION_SECONDS } from "../../scripts/chromatic.mjs";
const identifier = z.string().min(1).max(128);
const requestId = identifier.describe("Unique ID for the authorized attempt. If the reply is lost, retrieve this same ID through device operation_status; it never starts another attempt.");
const confirm = z.boolean().describe("Set true only when the user explicitly requests this specific setup, live-play, or write action.");
const deviceToken = identifier.describe("Select this token from the latest device discovery; an accepted detect, play, or flash consumes it.");
const admissionRefusals = new Set([
"CHROMATIC_INVALID_INPUT", "CHROMATIC_CONFIRMATION_REQUIRED", "CHROMATIC_CLOSING", "CHROMATIC_BUSY",
"CHROMATIC_SESSION_CHANGED", "CHROMATIC_SELECTION_EXPIRED", "CHROMATIC_INVALID_ROM_BINDING", "PROJECT_SELECTION_REQUIRED",
]);
export function registerChromaticTools(server, context) {
async function invoke(input, signal) {
let accepted = false;
try {
// Confirmation refusal must not require a selected project or workspace.
const root = input.confirm === true && (input.command === "flash" || input.command === "play")
? await context.authorizedRoot() : undefined;
const value = context.service.execute(input, root, signal);
accepted = true;
const result = context.toolResult(value);
if (value.state === "running") {
result.content = [{ type: "text", text: `${input.command === "play" ? "Host-emulation-to-Chromatic live-demo" : "Chromatic"} operation started (${value.operationId}). Read device operation_status for the original result. Do not repeat the action.` }];
}
else if (value.state === "unresolved") {
result.content = [{ type: "text", text: `Chromatic operation remains unresolved (${value.operationId}). Preserve its original result and reservation; do not retry or assume the device is unchanged.` }];
result.isError = true;
}
else if (value.state === "failed") {
// Use the formatted response so its path/privacy policy applies here too.
const error = result.structuredContent?.error;
result.content = [{ type: "text", text: `Chromatic operation failed (${value.operationId}). ${error?.message ?? "The complete original command result is retained below."} No automatic retry ran.` }];
result.isError = true;
}
if (value.state === "succeeded") {
const report = result.structuredContent?.result;
const notices = [...(report?.diagnostics ?? []), ...(report?.recovery ? [report.recovery] : [])];
if (notices.length)
result.content.unshift({ type: "text", text: notices.map(notice => notice.message).join("\n\n") });
}
return result;
}
catch (error) {
const result = context.errorResult(error);
// Only definite admission refusals describe this invocation as unstarted.
// Lookup, replay-conflict and journal failures cannot establish whether
// the original request already started, so retain its uncertainty.
const code = error instanceof Error && "code" in error ? String(error.code) : "";
let refused = !accepted && input.command !== "status" && input.command !== "operation_status" && admissionRefusals.has(code);
if (refused && input.requestId) {
// Validation may run before request deduplication (for example when
// confirm is missing). Never apply its refusal to a retained original.
try {
context.service.execute({ command: "operation_status", requestId: input.requestId });
refused = false;
}
catch (lookupError) {
refused = lookupError instanceof Error && "code" in lookupError && lookupError.code === "CHROMATIC_OPERATION_UNKNOWN";
if (refused)
try {
const status = context.service.execute({ command: "status" });
refused = !status.retainedReservation && !status.activeOperation;
}
catch {
refused = false;
}
}
}
if (refused) {
result.structuredContent = { ...result.structuredContent, operationStarted: false };
}
return result;
}
}
server.registerTool("device", {
title: "Inspect Chromatic devices and operations",
description: "Read platform/driver requirements without touching USB, list connected Chromatics, or retrieve an original operation. Supply a caller-chosen requestId for discovery so a lost reply can be recovered without scanning again; reusing it returns the original result, never fresh tokens. Choose the intended device; there is no default player. Report discovery diagnostics even when another device is selectable. Duplicate player numbers need distinct on-device settings followed by reconnection; unmatched USB functions need the reported access checks. For a lost or unresolved action, read its original operation_status; do not start a replacement.",
inputSchema: z.object({
command: z.enum(["status", "list_devices", "operation_status"]).describe("status: no other fields; list_devices: optional requestId; operation_status: exactly one of operationId or requestId."),
operationId: identifier.describe("operation_status only: returned operation ID; omit requestId.").optional(),
requestId: identifier.describe("list_devices: optional caller-chosen ID for one discovery. operation_status: original discovery, setup, play, or flash request ID; omit operationId.").optional(),
}).strict(),
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: true },
}, async (input, extra) => invoke(input, extra.signal));
server.registerTool("setup", {
title: "Set up Chromatic device access",
description: "Install drivers or detect a cartridge when the user requests that specific action. Driver setup changes Linux udev access or requests Windows administrator approval; macOS needs no vendor driver. Cartridge detection can reconfigure the FPGA and consumes the selected device token. Retain the requestId and poll device operation_status. Unreadable cartridges or unknown flash models return recovery guidance; do not automatically repeat detection.",
inputSchema: z.object({
command: z.enum(["install_drivers", "detect_cartridge"]).describe("install_drivers requires only sessionId; detect_cartridge requires only deviceToken. Both require requestId and confirm."), requestId, confirm,
sessionId: identifier.optional().describe("Required for install_drivers; use the sessionId from current device status."),
deviceToken: deviceToken.optional().describe("Required for detect_cartridge; choose a token from current discovery. The action consumes it."),
}).strict(),
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
}, async (input, extra) => invoke(input, extra.signal));
server.registerTool("flash", {
title: "Write a selected ROM to Chromatic",
description: "Flash homebrew or the user's own game; refuse third-party commercial games, including known retail rebuilds. A ROM you compiled from game source otherwise qualifies; try the local emulator for unknown supplied ROMs and do not flash while origin remains unclear. Developer Mode activation is managed outside this plugin using ModRetro Updater: https://support.modretro.com/en_us/articles/chromatic-firmware-updater-ryhoYnzCx. Never request or accept activation codes. After external activation, use fresh device selection and normal flash confirmation; do not automatically retry an uncertain write. On the first flash per device, explain that existing cartridge game data will be erased, saves may be lost, and no backup is made; get explicit consent acknowledging this. Do not repeat consent for later writes the user requests on the same device. Requires a fresh selected deviceToken and the path, sizeBytes, and SHA-256 from rom_inspect. Poll device operation_status; use operationId for a copied preview error because its requestId is preview-scoped. device.program_failed alone does not identify the cause: inspect the original public error summary and recovery. Timeout, cancellation, or process closure never authorizes another flash. Vendor success does not establish manual boot, play, or audio.",
inputSchema: z.object({
deviceToken,
romPath: z.string().min(1).max(4096),
expectedSha256: z.string().regex(/^[a-f0-9]{64}$/u),
expectedSizeBytes: z.number().int().min(0x150).max(8 * 1024 * 1024),
requestId, confirm: confirm.describe("Set true only for a user-requested write once the user has explicitly accepted cartridge data loss for this device."),
}).strict(),
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
}, async (input, extra) => invoke({ ...input, command: "flash" }, extra.signal));
server.registerTool("play", {
title: "Stream host emulation to Chromatic",
description: "Stream timed host-emulated video/audio to the selected Chromatic; this does not write or run the cartridge. Requires a user-requested live demo, a fresh selected deviceToken, and the path, sizeBytes, and SHA-256 from rom_inspect. Poll device operation_status until the original process closes; progress alone is not completion. Read returned HID diagnostics without treating them as a driver probe or retry instruction. No remote input, frame capture, stop command, or hardware performance counters are exposed.",
inputSchema: z.object({
deviceToken,
romPath: z.string().min(1).max(4096),
expectedSha256: z.string().regex(/^[a-f0-9]{64}$/u),
expectedSizeBytes: z.number().int().min(0x150).max(8 * 1024 * 1024),
durationSeconds: z.number().min(CHROMATIC_LIVE_MIN_DURATION_SECONDS).max(CHROMATIC_LIVE_MAX_DURATION_SECONDS).optional()
.describe(`Requested emulation seconds; defaults to ${CHROMATIC_LIVE_DEFAULT_DURATION_SECONDS}. Completion requires observing the original process close.`),
saveMode: z.enum(["none", "isolated"]).optional().describe("Default none disables save loading/writing. Isolated reuses battery saves only for this exact ROM SHA-256 under the authorized root."),
requestId, confirm,
}).strict(),
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
}, async (input, extra) => invoke({ ...input, command: "play" }, extra.signal));
}
//# sourceMappingURL=chromatic.js.mapSHA-256: 5a67046714b0d7050982d58578ddb9db0ad1e54ff023d04ae4a3dda897a82852