c64bridge 0.9.3 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +19 -3
- package/README.md +190 -148
- package/dist/.tsbuildinfo +1 -1
- package/dist/c64Client.js +277 -41
- package/dist/config.js +30 -18
- package/dist/device.js +191 -125
- package/dist/knowledge.js +1 -1
- package/dist/loggingHttpClient.js +15 -3
- package/dist/mcp-server.js +65 -16
- package/dist/platform.js +13 -2
- package/dist/rag/indexer.js +11 -8
- package/dist/rag/init.js +11 -8
- package/dist/sidwaveCompiler.js +2 -2
- package/dist/tools/audio.js +27 -6
- package/dist/tools/developer.js +6 -6
- package/dist/tools/graphics.js +4 -4
- package/dist/tools/input.js +136 -24
- package/dist/tools/machineControl.js +85 -6
- package/dist/tools/memory.js +21 -6
- package/dist/tools/meta/artifacts.js +20 -8
- package/dist/tools/meta/background.js +11 -1
- package/dist/tools/meta/index.js +1 -1
- package/dist/tools/pollValidator.js +19 -51
- package/dist/tools/printer.js +1 -0
- package/dist/tools/programRunners.js +5 -5
- package/dist/tools/rag.js +1 -1
- package/dist/tools/registry/batch.js +1 -1
- package/dist/tools/registry/config.js +5 -5
- package/dist/tools/registry/disk.js +2 -2
- package/dist/tools/registry/drive.js +2 -2
- package/dist/tools/registry/graphics.js +2 -2
- package/dist/tools/registry/memory.js +2 -2
- package/dist/tools/registry/platform.js +9 -5
- package/dist/tools/registry/printer.js +1 -0
- package/dist/tools/registry/program.js +5 -5
- package/dist/tools/registry/rag.js +1 -1
- package/dist/tools/registry/sound.js +4 -4
- package/dist/tools/registry/system.js +13 -3
- package/dist/tools/storage.js +1 -1
- package/dist/vice/mockServer.js +33 -0
- package/dist/vice/process.js +11 -1
- package/dist/vice/viceClient.js +35 -6
- package/doc/c64u/c64-openapi.yaml +1130 -774
- package/doc/reviews/hardening/01-fable/prompt-handover.md +125 -0
- package/doc/reviews/hardening/01-fable/prompt.md +98 -0
- package/doc/reviews/hardening/01-fable/review-prompt.md +468 -0
- package/doc/reviews/hardening/01-fable/review.md +1234 -0
- package/doc/u2/u2-openapi.yaml +1192 -0
- package/generated/c64/index.js +142 -9
- package/generated/openapi/c64u/index.js +1117 -0
- package/generated/openapi/u2/index.js +929 -0
- package/mcp/schemas/c64_graphics.schema.json +3 -3
- package/mcp/schemas/c64_input.schema.json +249 -8
- package/mcp/schemas/c64_memory.schema.json +3 -3
- package/mcp/schemas/c64_program.schema.json +3 -3
- package/mcp/schemas/c64_select_backend.schema.json +2 -0
- package/mcp/schemas/c64_system.schema.json +41 -1
- package/mcp/server.json +1 -1
- package/mcp/tools.json +386 -44
- package/mcp.json +14 -2
- package/package.json +4 -2
- package/scripts/mockC64Server.mjs +171 -2
- package/scripts/prepare-release.mjs +13 -2
- package/scripts/update-readme.ts +18 -30
- package/server.json +3 -3
package/dist/mcp-server.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "./bootstrap/stdio-logger.js";
|
|
3
|
+
import { createServer } from "node:http";
|
|
3
4
|
import { dirname, join } from "node:path";
|
|
4
5
|
import { fileURLToPath } from "node:url";
|
|
5
6
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
6
7
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
7
9
|
import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
8
10
|
import { loadConfig } from "./config.js";
|
|
9
11
|
import { C64Client } from "./c64Client.js";
|
|
@@ -32,7 +34,7 @@ function createPromptRegistryGetter() {
|
|
|
32
34
|
return registry;
|
|
33
35
|
};
|
|
34
36
|
}
|
|
35
|
-
function parseCliOptions(argv) {
|
|
37
|
+
export function parseCliOptions(argv) {
|
|
36
38
|
const httpIndex = argv.indexOf("--http");
|
|
37
39
|
if (httpIndex === -1) {
|
|
38
40
|
return { mode: "stdio" };
|
|
@@ -165,7 +167,7 @@ async function main() {
|
|
|
165
167
|
{
|
|
166
168
|
uri: PLATFORM_RESOURCE_URI,
|
|
167
169
|
mimeType: "text/markdown",
|
|
168
|
-
text: renderPlatformStatusMarkdown(client),
|
|
170
|
+
text: await renderPlatformStatusMarkdown(client),
|
|
169
171
|
},
|
|
170
172
|
],
|
|
171
173
|
};
|
|
@@ -420,12 +422,34 @@ async function main() {
|
|
|
420
422
|
throw error;
|
|
421
423
|
}
|
|
422
424
|
});
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
425
|
+
const cli = parseCliOptions(process.argv.slice(2));
|
|
426
|
+
if (cli.mode === "http") {
|
|
427
|
+
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
|
428
|
+
await server.connect(transport);
|
|
429
|
+
const port = cli.port ?? 3000;
|
|
430
|
+
const httpServer = createServer((request, response) => {
|
|
431
|
+
if (request.url?.split("?")[0] !== "/mcp") {
|
|
432
|
+
response.statusCode = 404;
|
|
433
|
+
response.end("Not found");
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
void transport.handleRequest(request, response).catch((error) => {
|
|
437
|
+
if (!response.headersSent)
|
|
438
|
+
response.statusCode = 500;
|
|
439
|
+
response.end(error instanceof Error ? error.message : "MCP transport error");
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
await new Promise((resolve, reject) => httpServer.listen(port, "127.0.0.1", resolve).once("error", reject));
|
|
443
|
+
writeDiagnosticEvent("mcp_transport_connected", { mode: "http", port });
|
|
444
|
+
console.error(`c64bridge MCP server running on HTTP at http://127.0.0.1:${port}/mcp`);
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
const transport = new StdioServerTransport();
|
|
448
|
+
await server.connect(transport);
|
|
449
|
+
writeDiagnosticEvent("mcp_transport_connected", { mode: "stdio" });
|
|
450
|
+
console.error("c64bridge MCP server running on stdio");
|
|
451
|
+
}
|
|
427
452
|
await logConnectivity(client, baseUrl);
|
|
428
|
-
console.error("c64bridge MCP server running on stdio");
|
|
429
453
|
writeDiagnosticEvent("server_ready", {
|
|
430
454
|
diagnosticsFile: diagnostics.filePath,
|
|
431
455
|
});
|
|
@@ -447,10 +471,18 @@ function createPlatformResourceDescriptor() {
|
|
|
447
471
|
},
|
|
448
472
|
};
|
|
449
473
|
}
|
|
450
|
-
function renderPlatformStatusMarkdown(client) {
|
|
474
|
+
export async function renderPlatformStatusMarkdown(client) {
|
|
451
475
|
const status = getPlatformStatus();
|
|
452
476
|
const availableBackends = client.getAvailableBackends();
|
|
453
477
|
const capabilities = describePlatformCapabilities(toolRegistry.list());
|
|
478
|
+
// Resource reads must remain responsive while an Ultimate boots or is
|
|
479
|
+
// unreachable. The probe itself remains retryable (unknown is not cached).
|
|
480
|
+
const nativeEndpoints = await Promise.race([
|
|
481
|
+
client.getNativeEndpointCapabilities(),
|
|
482
|
+
new Promise((resolve) => {
|
|
483
|
+
setTimeout(() => resolve({ machineInput: "unknown", machineMenuScreen: "unknown" }), 750).unref();
|
|
484
|
+
}),
|
|
485
|
+
]);
|
|
454
486
|
const lines = [
|
|
455
487
|
"# MCP Platform Status",
|
|
456
488
|
"",
|
|
@@ -466,6 +498,13 @@ function renderPlatformStatusMarkdown(client) {
|
|
|
466
498
|
status.limitedFeatures.length > 0
|
|
467
499
|
? ["## Limited or Unavailable Features", "", ...status.limitedFeatures.map((feature) => `- ${feature}`)].join("\n")
|
|
468
500
|
: "",
|
|
501
|
+
"## Native REST Endpoint Availability",
|
|
502
|
+
"",
|
|
503
|
+
`- \`machine:input\`: **${nativeEndpoints.machineInput}**`,
|
|
504
|
+
`- \`machine:menu_screen\`: **${nativeEndpoints.machineMenuScreen}** (it can only be confirmed while an Ultimate menu is visible)`,
|
|
505
|
+
"",
|
|
506
|
+
"Use physical-input, menu-screen, and C64U/U64 Tool Menu power-cycle operations only when the required endpoint is available. Prefer `key` or `write_text` for ordinary C64 input when `machine:input` is unavailable.",
|
|
507
|
+
"",
|
|
469
508
|
"## Tool Compatibility",
|
|
470
509
|
"",
|
|
471
510
|
].filter(Boolean);
|
|
@@ -487,7 +526,7 @@ function renderPlatformStatusMarkdown(client) {
|
|
|
487
526
|
}
|
|
488
527
|
async function logConnectivity(client, baseUrl) {
|
|
489
528
|
const backendType = await client.getBackendType();
|
|
490
|
-
if (backendType
|
|
529
|
+
if (backendType === "vice") {
|
|
491
530
|
writeDiagnosticEvent("connectivity_probe_skipped", { baseUrl, reason: "vice_backend" });
|
|
492
531
|
console.error("Skipping direct REST connectivity probe because active backend is VICE");
|
|
493
532
|
try {
|
|
@@ -582,11 +621,21 @@ function toPromptMessage(segment) {
|
|
|
582
621
|
},
|
|
583
622
|
};
|
|
584
623
|
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
624
|
+
// The documented entrypoint (src/index.ts / dist/index.js, per package.json
|
|
625
|
+
// "main"/"bin") always imports this module as a side effect rather than
|
|
626
|
+
// invoking it as the process's own script, so process.argv[1] never points
|
|
627
|
+
// here — an entrypoint-identity check would wrongly skip startup in every
|
|
628
|
+
// real deployment. Test code that only needs a pure export (e.g.
|
|
629
|
+
// parseCliOptions, renderPlatformStatusMarkdown) sets this opt-out instead,
|
|
630
|
+
// mirroring the existing C64BRIDGE_START_SKIP_AUTO_LAUNCH convention in
|
|
631
|
+
// scripts/start.mjs.
|
|
632
|
+
if (process.env.C64BRIDGE_SKIP_AUTO_START !== "1") {
|
|
633
|
+
main().catch((error) => {
|
|
634
|
+
writeDiagnosticEvent("server_fatal", {
|
|
635
|
+
diagnosticsFile: getDiagnosticsSessionInfo()?.filePath,
|
|
636
|
+
error,
|
|
637
|
+
});
|
|
638
|
+
console.error("Fatal error in MCP server:", error);
|
|
639
|
+
process.exit(1);
|
|
589
640
|
});
|
|
590
|
-
|
|
591
|
-
process.exit(1);
|
|
592
|
-
});
|
|
641
|
+
}
|
package/dist/platform.js
CHANGED
|
@@ -10,6 +10,10 @@ const PLATFORM_FEATURES = {
|
|
|
10
10
|
],
|
|
11
11
|
limited: [],
|
|
12
12
|
},
|
|
13
|
+
u2: {
|
|
14
|
+
features: ["ultimate-rest-api", "cartridge-execution", "sid-control", "drive-management", "printer-integration"],
|
|
15
|
+
limited: ["no-machine-input", "no-debugreg", "no-poweroff", "no-streaming"],
|
|
16
|
+
},
|
|
13
17
|
vice: {
|
|
14
18
|
features: [
|
|
15
19
|
"software-emulation",
|
|
@@ -23,7 +27,7 @@ const PLATFORM_FEATURES = {
|
|
|
23
27
|
limited: ["no-rest-api", "no-firmware-filesystem", "no-flash-config", "limited-sid"],
|
|
24
28
|
},
|
|
25
29
|
};
|
|
26
|
-
let currentPlatform = process.env.C64_MODE === "vice" ? "vice" : "c64u";
|
|
30
|
+
let currentPlatform = process.env.C64_MODE === "vice" ? "vice" : process.env.C64_MODE === "u2" ? "u2" : "c64u";
|
|
27
31
|
export function getPlatformStatus() {
|
|
28
32
|
const spec = PLATFORM_FEATURES[currentPlatform];
|
|
29
33
|
return {
|
|
@@ -48,11 +52,12 @@ export function isPlatformSupported(platform, supported) {
|
|
|
48
52
|
export function describePlatformCapabilities(tools) {
|
|
49
53
|
const map = {
|
|
50
54
|
c64u: { available: [], unsupported: [] },
|
|
55
|
+
u2: { available: [], unsupported: [] },
|
|
51
56
|
vice: { available: [], unsupported: [] },
|
|
52
57
|
};
|
|
53
58
|
for (const tool of tools) {
|
|
54
59
|
const supported = tool.metadata.platforms ?? ["c64u"];
|
|
55
|
-
for (const id of ["c64u", "vice"]) {
|
|
60
|
+
for (const id of ["c64u", "u2", "vice"]) {
|
|
56
61
|
if (supported.includes(id)) {
|
|
57
62
|
map[id].available.push(tool.name);
|
|
58
63
|
}
|
|
@@ -69,6 +74,12 @@ export function describePlatformCapabilities(tools) {
|
|
|
69
74
|
tools: map.c64u.available.sort(),
|
|
70
75
|
unsupported_tools: map.c64u.unsupported.sort(),
|
|
71
76
|
},
|
|
77
|
+
u2: {
|
|
78
|
+
features: PLATFORM_FEATURES.u2.features,
|
|
79
|
+
limited_features: PLATFORM_FEATURES.u2.limited,
|
|
80
|
+
tools: map.u2.available.sort(),
|
|
81
|
+
unsupported_tools: map.u2.unsupported.sort(),
|
|
82
|
+
},
|
|
72
83
|
vice: {
|
|
73
84
|
features: PLATFORM_FEATURES.vice.features,
|
|
74
85
|
limited_features: PLATFORM_FEATURES.vice.limited,
|
package/dist/rag/indexer.js
CHANGED
|
@@ -4,16 +4,19 @@ GPL-2.0-only
|
|
|
4
4
|
*/
|
|
5
5
|
import fs from "node:fs/promises";
|
|
6
6
|
import path from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
7
8
|
import fsSync from "node:fs";
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
9
|
+
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
10
|
+
const ASSET_ROOT = process.env.C64BRIDGE_ASSET_ROOT ? path.resolve(process.env.C64BRIDGE_ASSET_ROOT) : PACKAGE_ROOT;
|
|
11
|
+
const BASIC_DIR = path.join(ASSET_ROOT, "data/basic/examples");
|
|
12
|
+
const ASM_DIR = path.join(ASSET_ROOT, "data/assembly/examples");
|
|
13
|
+
const EXTERNAL_DIR = path.join(ASSET_ROOT, "external");
|
|
14
|
+
const DOC_ROOT = path.join(ASSET_ROOT, "doc");
|
|
12
15
|
const DOC_EXCLUDE_DIRS = new Set(["plans"]);
|
|
13
|
-
const CONTEXT_DIR = path.
|
|
16
|
+
const CONTEXT_DIR = path.join(ASSET_ROOT, "data/context");
|
|
14
17
|
const BOOTSTRAP_PATH = path.join(CONTEXT_DIR, "bootstrap.md");
|
|
15
|
-
const AGENTS_PATH = path.
|
|
16
|
-
const PROMPTS_DIR = path.
|
|
18
|
+
const AGENTS_PATH = path.join(ASSET_ROOT, "AGENTS.md");
|
|
19
|
+
const PROMPTS_DIR = path.join(ASSET_ROOT, ".github/prompts");
|
|
17
20
|
const CHAT_PATH = path.join(CONTEXT_DIR, "chat.md");
|
|
18
21
|
// RAG_DOC_FILES env var can add extra specific files, comma-separated
|
|
19
22
|
const ENV_DOC_FILES = (process.env.RAG_DOC_FILES ?? "")
|
|
@@ -34,7 +37,7 @@ function shouldExcludeDocFile(file) {
|
|
|
34
37
|
return firstSegment !== undefined && DOC_EXCLUDE_DIRS.has(firstSegment);
|
|
35
38
|
}
|
|
36
39
|
function resolveEmbeddingsDir(override) {
|
|
37
|
-
return path.resolve(override ?? process.env.RAG_EMBEDDINGS_DIR ?? "data");
|
|
40
|
+
return path.resolve(override ?? process.env.RAG_EMBEDDINGS_DIR ?? path.join(ASSET_ROOT, "data"));
|
|
38
41
|
}
|
|
39
42
|
function embeddingIndexPaths(dir) {
|
|
40
43
|
return {
|
package/dist/rag/init.js
CHANGED
|
@@ -4,22 +4,25 @@ GPL-2.0-only
|
|
|
4
4
|
*/
|
|
5
5
|
import fs from "node:fs/promises";
|
|
6
6
|
import path from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
7
8
|
import { setTimeout as delay } from "node:timers/promises";
|
|
8
9
|
import { LocalMiniHashEmbedding } from "./embeddings.js";
|
|
9
10
|
import { buildAllIndexes, loadIndexes } from "./indexer.js";
|
|
10
11
|
import { LocalRagRetriever } from "./retriever.js";
|
|
11
12
|
import { LoggingRagRetriever } from "./loggingRetriever.js";
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const
|
|
13
|
+
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
14
|
+
const assetRoot = process.env.C64BRIDGE_ASSET_ROOT ? path.resolve(process.env.C64BRIDGE_ASSET_ROOT) : PACKAGE_ROOT;
|
|
15
|
+
const EXTERNAL_DIR = path.join(assetRoot, "external");
|
|
16
|
+
const BASIC_DATA_DIR = path.join(assetRoot, "data/basic/examples");
|
|
17
|
+
const ASM_DATA_DIR = path.join(assetRoot, "data/assembly/examples");
|
|
18
|
+
const DOC_DIR = path.join(assetRoot, "doc");
|
|
19
|
+
const CONTEXT_DIR = path.join(assetRoot, "data/context");
|
|
17
20
|
const BOOTSTRAP_PATH = path.join(CONTEXT_DIR, "bootstrap.md");
|
|
18
|
-
const AGENTS_PATH = path.
|
|
19
|
-
const PROMPTS_DIR = path.
|
|
21
|
+
const AGENTS_PATH = path.join(assetRoot, "AGENTS.md");
|
|
22
|
+
const PROMPTS_DIR = path.join(assetRoot, ".github/prompts");
|
|
20
23
|
const CHAT_PATH = path.join(CONTEXT_DIR, "chat.md");
|
|
21
24
|
function resolveEmbeddingsDir() {
|
|
22
|
-
return path.resolve(process.env.RAG_EMBEDDINGS_DIR ?? "data");
|
|
25
|
+
return path.resolve(process.env.RAG_EMBEDDINGS_DIR ?? path.join(assetRoot, "data"));
|
|
23
26
|
}
|
|
24
27
|
function embeddingIndexPaths() {
|
|
25
28
|
const dir = resolveEmbeddingsDir();
|
package/dist/sidwaveCompiler.js
CHANGED
|
@@ -169,9 +169,9 @@ export function compileSidwaveToSid(doc, prg, options) {
|
|
|
169
169
|
const body = Buffer.isBuffer(prg) ? prg : Buffer.from(prg);
|
|
170
170
|
return { sid: Buffer.concat([header, body]) };
|
|
171
171
|
}
|
|
172
|
-
function hzToSidFrequency(hz, system) {
|
|
172
|
+
export function hzToSidFrequency(hz, system) {
|
|
173
173
|
const phi2 = system === "PAL" ? 985248 : 1022727;
|
|
174
|
-
const value = Math.round((hz *
|
|
174
|
+
const value = Math.round((hz * 16777216) / phi2);
|
|
175
175
|
return Math.max(0, Math.min(0xffff, value));
|
|
176
176
|
}
|
|
177
177
|
function waveformToCtrl(w) {
|
package/dist/tools/audio.js
CHANGED
|
@@ -9,6 +9,12 @@ const NOTE_PATTERN = /^([A-Ga-g])([#b]?)(-?\d+)$/;
|
|
|
9
9
|
const DEFAULT_SILENCE_VERIFY_DURATION_SECONDS = 1.5;
|
|
10
10
|
const DEFAULT_SILENCE_VERIFY_RMS_THRESHOLD = 0.02;
|
|
11
11
|
const DEFAULT_SILENCE_VERIFY_WAIT_MS = 150;
|
|
12
|
+
const generatedPlaybacks = new Set();
|
|
13
|
+
export function cancelGeneratedSidPlayback() {
|
|
14
|
+
for (const controller of generatedPlaybacks)
|
|
15
|
+
controller.abort();
|
|
16
|
+
generatedPlaybacks.clear();
|
|
17
|
+
}
|
|
12
18
|
function toRecord(details) {
|
|
13
19
|
if (details && typeof details === "object") {
|
|
14
20
|
return details;
|
|
@@ -403,7 +409,7 @@ export const audioModule = defineToolModule({
|
|
|
403
409
|
],
|
|
404
410
|
prompts: ["sid-music"],
|
|
405
411
|
defaultTags: ["sid", "audio"],
|
|
406
|
-
supportedPlatforms: ["c64u", "vice"],
|
|
412
|
+
supportedPlatforms: ["c64u", "u2", "vice"],
|
|
407
413
|
workflowHints: [
|
|
408
414
|
"Reach for SID helpers when the user talks about sound design, playback quality, or stuck notes.",
|
|
409
415
|
"After changing playback state, suggest verify-by-ear steps such as analyze_audio so the user gets concrete feedback.",
|
|
@@ -473,6 +479,7 @@ export const audioModule = defineToolModule({
|
|
|
473
479
|
try {
|
|
474
480
|
const parsed = sidResetArgsSchema.parse(args ?? {});
|
|
475
481
|
ctx.logger.info("Resetting SID", { mode: parsed.hard ? "hard" : "soft" });
|
|
482
|
+
cancelGeneratedSidPlayback(); // Reset is the documented way to stop a generated arpeggio.
|
|
476
483
|
const result = await ctx.client.sidReset(parsed.hard);
|
|
477
484
|
if (!result.success) {
|
|
478
485
|
throw new ToolExecutionError("C64 firmware reported failure while resetting SID", {
|
|
@@ -622,6 +629,7 @@ export const audioModule = defineToolModule({
|
|
|
622
629
|
const parsed = sidSilenceArgsSchema.parse(args ?? {});
|
|
623
630
|
const verify = Boolean(parsed.verify);
|
|
624
631
|
ctx.logger.info("Silencing all SID voices", { verify });
|
|
632
|
+
cancelGeneratedSidPlayback(); // Otherwise its remaining notes would resume writing right after this silences the chip.
|
|
625
633
|
const result = await ctx.client.sidSilenceAll();
|
|
626
634
|
if (!result.success) {
|
|
627
635
|
throw new ToolExecutionError("C64 firmware reported failure while silencing SID", {
|
|
@@ -812,10 +820,17 @@ export const audioModule = defineToolModule({
|
|
|
812
820
|
tempoMs: parsed.tempoMs,
|
|
813
821
|
waveform: parsed.waveform,
|
|
814
822
|
});
|
|
823
|
+
cancelGeneratedSidPlayback(); // Replace any prior generated arpeggio rather than letting two run at once.
|
|
824
|
+
const controller = new AbortController();
|
|
825
|
+
generatedPlaybacks.add(controller);
|
|
826
|
+
const pinnedFacadePromise = ctx.client.pinFacade ? ctx.client.pinFacade() : Promise.resolve(undefined); // Pin once so a later select_backend can't retarget notes mid-arpeggio (HARD01-030).
|
|
815
827
|
void (async () => {
|
|
816
828
|
try {
|
|
817
|
-
await
|
|
829
|
+
const pinnedFacade = await pinnedFacadePromise;
|
|
830
|
+
await ctx.client.sidSetVolume(8, pinnedFacade);
|
|
818
831
|
for (let i = 0; i < parsed.steps; i += 1) {
|
|
832
|
+
if (controller.signal.aborted)
|
|
833
|
+
break;
|
|
819
834
|
const iv = intervals[i % intervals.length];
|
|
820
835
|
const note = transposeNote(parsed.root, iv);
|
|
821
836
|
await ctx.client.sidNoteOn({
|
|
@@ -827,18 +842,24 @@ export const audioModule = defineToolModule({
|
|
|
827
842
|
decay: 7,
|
|
828
843
|
sustain: 15,
|
|
829
844
|
release: 0,
|
|
830
|
-
});
|
|
845
|
+
}, pinnedFacade);
|
|
831
846
|
const durationMs = parsed.preset === "expression" ? expressiveDurations[i % expressiveDurations.length] : parsed.tempoMs;
|
|
832
|
-
await sleep(durationMs);
|
|
847
|
+
await sleep(Math.max(1, Math.min(20, Math.floor(durationMs / 8)))); // Clear GATE between notes so every note retriggers ADSR.
|
|
848
|
+
await ctx.client.sidNoteOff(1, pinnedFacade);
|
|
849
|
+
if (!controller.signal.aborted)
|
|
850
|
+
await sleep(Math.max(0, durationMs - Math.max(1, Math.min(20, Math.floor(durationMs / 8)))));
|
|
833
851
|
}
|
|
834
|
-
await ctx.client.sidNoteOff(1);
|
|
852
|
+
await ctx.client.sidNoteOff(1, pinnedFacade);
|
|
835
853
|
}
|
|
836
854
|
catch (playbackError) {
|
|
837
855
|
const message = playbackError instanceof Error ? playbackError.message : String(playbackError);
|
|
838
856
|
ctx.logger.warn("music_generate playback failed", { error: message });
|
|
839
857
|
}
|
|
858
|
+
finally {
|
|
859
|
+
generatedPlaybacks.delete(controller);
|
|
860
|
+
}
|
|
840
861
|
})();
|
|
841
|
-
return textResult(`Scheduled ${parsed.steps} note arpeggio starting on ${parsed.root}.`, {
|
|
862
|
+
return textResult(`Scheduled ${parsed.steps} note arpeggio starting on ${parsed.root}. Playback is running and will be replaced by the next generated sequence.`, {
|
|
842
863
|
success: true,
|
|
843
864
|
root: parsed.root,
|
|
844
865
|
intervals,
|
package/dist/tools/developer.js
CHANGED
|
@@ -146,7 +146,7 @@ export const developerModule = defineToolModule({
|
|
|
146
146
|
workflowHints: [
|
|
147
147
|
"Run first to enumerate categories before reading or writing specific settings; summarise the top-level sections for the user.",
|
|
148
148
|
],
|
|
149
|
-
supportedPlatforms: ["c64u", "vice"],
|
|
149
|
+
supportedPlatforms: ["c64u", "u2", "vice"],
|
|
150
150
|
async execute(args, ctx) {
|
|
151
151
|
try {
|
|
152
152
|
noArgsSchema.parse(args ?? {});
|
|
@@ -182,7 +182,7 @@ export const developerModule = defineToolModule({
|
|
|
182
182
|
workflowHints: [
|
|
183
183
|
"Use after config_list to fetch details; restate category/item so the user can cross-check values before changes.",
|
|
184
184
|
],
|
|
185
|
-
supportedPlatforms: ["c64u", "vice"],
|
|
185
|
+
supportedPlatforms: ["c64u", "u2", "vice"],
|
|
186
186
|
async execute(args, ctx) {
|
|
187
187
|
try {
|
|
188
188
|
const parsed = configGetArgsSchema.parse(args ?? {});
|
|
@@ -218,7 +218,7 @@ export const developerModule = defineToolModule({
|
|
|
218
218
|
workflowHints: [
|
|
219
219
|
"Confirm the user-supplied value before writing; mention whether a reboot is required for the change to take effect.",
|
|
220
220
|
],
|
|
221
|
-
supportedPlatforms: ["c64u", "vice"],
|
|
221
|
+
supportedPlatforms: ["c64u", "u2", "vice"],
|
|
222
222
|
async execute(args, ctx) {
|
|
223
223
|
try {
|
|
224
224
|
const parsed = configSetArgsSchema.parse(args ?? {});
|
|
@@ -262,7 +262,7 @@ export const developerModule = defineToolModule({
|
|
|
262
262
|
workflowHints: [
|
|
263
263
|
"Use when multiple categories must be updated together; remind the user to review validation errors closely.",
|
|
264
264
|
],
|
|
265
|
-
supportedPlatforms: ["c64u", "vice"],
|
|
265
|
+
supportedPlatforms: ["c64u", "u2", "vice"],
|
|
266
266
|
async execute(args, ctx) {
|
|
267
267
|
try {
|
|
268
268
|
const payload = configBatchSchema.parse(args ?? {});
|
|
@@ -405,7 +405,7 @@ export const developerModule = defineToolModule({
|
|
|
405
405
|
name: "version",
|
|
406
406
|
description: "Retrieve firmware or emulator version information.",
|
|
407
407
|
summary: "Calls the firmware version endpoint (Ultimate) or returns emulator info (VICE).",
|
|
408
|
-
supportedPlatforms: ["c64u", "vice"],
|
|
408
|
+
supportedPlatforms: ["c64u", "u2", "vice"],
|
|
409
409
|
inputSchema: noArgsSchema.jsonSchema,
|
|
410
410
|
relatedResources: ["c64://guide/bootstrap"],
|
|
411
411
|
tags: ["diagnostics", "version"],
|
|
@@ -437,7 +437,7 @@ export const developerModule = defineToolModule({
|
|
|
437
437
|
name: "info",
|
|
438
438
|
description: "Retrieve hardware or emulator information and status.",
|
|
439
439
|
summary: "Returns the diagnostics payload reported by the firmware (Ultimate) or emulator (VICE).",
|
|
440
|
-
supportedPlatforms: ["c64u", "vice"],
|
|
440
|
+
supportedPlatforms: ["c64u", "u2", "vice"],
|
|
441
441
|
inputSchema: noArgsSchema.jsonSchema,
|
|
442
442
|
relatedResources: ["c64://guide/bootstrap"],
|
|
443
443
|
tags: ["diagnostics", "info"],
|
package/dist/tools/graphics.js
CHANGED
|
@@ -577,7 +577,7 @@ export const graphicsModule = defineToolModule({
|
|
|
577
577
|
"Use when the user supplies sprite bytes or asks to preview graphics quickly; describe resulting coordinates and colours.",
|
|
578
578
|
"Remind the user that sprites live in banked memory so further tweaks may require write_memory calls.",
|
|
579
579
|
],
|
|
580
|
-
supportedPlatforms: ["c64u", "vice"],
|
|
580
|
+
supportedPlatforms: ["c64u", "u2", "vice"],
|
|
581
581
|
async execute(args, ctx) {
|
|
582
582
|
return executeGenerateSprite(args, ctx);
|
|
583
583
|
},
|
|
@@ -594,7 +594,7 @@ export const graphicsModule = defineToolModule({
|
|
|
594
594
|
"Use when the user wants to display an external image on screen rather than generate PETSCII art.",
|
|
595
595
|
"Call out the selected bitmap and screen RAM addresses so follow-up memory inspection or raster work stays grounded.",
|
|
596
596
|
],
|
|
597
|
-
supportedPlatforms: ["c64u", "vice"],
|
|
597
|
+
supportedPlatforms: ["c64u", "u2", "vice"],
|
|
598
598
|
async execute(args, ctx) {
|
|
599
599
|
return executeGenerateBitmap(args, ctx);
|
|
600
600
|
},
|
|
@@ -619,7 +619,7 @@ export const graphicsModule = defineToolModule({
|
|
|
619
619
|
"Call after generating PETSCII text or when the user wants border/background colour changes applied.",
|
|
620
620
|
"Echo the colour indices and mention CLEAR + PRINT so the user knows what ran.",
|
|
621
621
|
],
|
|
622
|
-
supportedPlatforms: ["c64u", "vice"],
|
|
622
|
+
supportedPlatforms: ["c64u", "u2", "vice"],
|
|
623
623
|
async execute(args, ctx) {
|
|
624
624
|
return executeRenderPetscii(args, ctx);
|
|
625
625
|
},
|
|
@@ -645,7 +645,7 @@ export const graphicsModule = defineToolModule({
|
|
|
645
645
|
"Response includes petsciiCodes array and glyphs for character-level inspection.",
|
|
646
646
|
"Provide follow-up suggestions like saving the PRG or capturing the screen after rendering.",
|
|
647
647
|
],
|
|
648
|
-
supportedPlatforms: ["c64u", "vice"],
|
|
648
|
+
supportedPlatforms: ["c64u", "u2", "vice"],
|
|
649
649
|
async execute(args, ctx) {
|
|
650
650
|
return executeCreatePetscii(args, ctx);
|
|
651
651
|
},
|