Compose existing Claude Code tools in isolated JavaScript, preserving tool permissions and returning compact output.

Compose existing session tools in isolated JavaScript and return a compact answer. The mod sends every underlying action through Claude Code's $.tool.call(), including its tool permissions and hooks. It starts no agent session or network service.
Requires Claude Code 2.1.293 or later and Node.js 20 or later on PATH. The checked-in dist/broker.mjs contains the QuickJS runtime, so users do not need npm or a dependency install.
From the AITuning checkout, register the separate local marketplace and install the mod:
claude plugin marketplace add ./mods
claude plugin install code-mode@aituning-mods
Restart Claude Code or run /reload-plugins in an existing session. For a single development session, use claude --plugin-dir ./mods/code-mode. If Node is absent, execution fails visibly. Direct tools remain available.
Claude sees mcp__code-mode__execute with one argument, code. The string is the body of an async JavaScript function:
const read = await tools.call('Read', { file_path: 'data.json' });
const data = JSON.parse(read.result.file.content);
return { count: data.length };
The guest receives tools.call(name, args), tools.list(), and emit(value). Calls return the host's structured result and model-facing text; inspect the actual tool result shape. text can contain line numbers and truncation. Read explicit ranges when the source exceeds the tool's limits. An implicit partial text-file read is reported as an error rather than a complete data source.
tools.list() returns currently available names and descriptions. It does not invent input schemas. Use the existing tool declaration or direct ToolSearch before unfamiliar calls. Deferred tools become usable when they appear in the session's tool list. Unknown tools and recursive code-mode calls are refused.
Await calls and return only the requested fields. Mutations take effect immediately. A failed script does not roll them back, and neither the runtime nor the transport retries them.
Use direct tools for single simple calls, images, interactive tools, subagents, or capabilities this JSON bridge cannot preserve. The global routing rule leaves those routes available.
Use Promise.all for independent built-in Read, Grep, or Glob calls. The host dispatches contiguous pending reads in groups of up to four:
const paths = ['first.json', 'second.json'];
const results = await Promise.all(paths.map(file_path =>
tools.call('Read', { file_path })
));
return results.map(result => JSON.parse(result.result.file.content).id);
Await a result before computing a dependent request. Await the whole read group before using its results in a mutation. Promise.all preserves input order when calls finish out of order. Parallel reads do not provide a consistent snapshot of files that another process changes.
Every other tool is a sequential barrier, including Write, Edit, Bash, and MCP tools. A barrier waits for the preceding group and finishes before the next begins. Read-only descriptions, annotations, and guest flags cannot change this classification. Each concurrent action still passes through the normal $.tool.call() permission and hook path.
The host waits for all started siblings before advancing or cleaning up, including when a transport call fails. Cancellation or expiry prevents another group from starting. Already-running tools retain Claude's cancellation behavior. A denial remains visible even when a sibling succeeds or the guest catches the error. Barriers enforce ordering, not rollback or conditional execution: do not queue a mutation alongside reads it depends on.
The host also checks returned receipt IDs and statuses against its own action records. A mailbox result cannot erase a host-observed denial or turn it into success. Existing reply files are refused before writing, with no action replay.
The result is JSON with ok, value, output, and a calls receipt. Failures include error. A caught denial or tool error remains visible in the receipt and makes ok false. On host failure, receipts identify actions already completed. A cancelled or expired call is not evidence that an in-flight mutation was undone.
| Resource | Limit |
|---|---|
| Script | 16,384 characters |
| Calls | 32 per script; up to four independent built-in reads at once; other tools sequential |
| Tool arguments | 65,536 characters per request |
| Tool result | 2 MiB per reply; larger results fail visibly |
| Guest heap | 32 MiB |
| Guest deadline | 60 seconds, including time waiting for tools |
| Returned guest output | 16,000 characters and 100 emitted items |
The guest has no host filesystem, Node APIs, environment, network, timers, imports, or persistent state. It can reach outside only through session tools. The trusted Node broker exchanges JSON through a private temporary mailbox. The host uses the mod SDK filesystem only for these broker control files. Requested user-file reads still use $.tool.call() and its normal permissions. A reply becomes readable only after its complete JSON write and a separate completion marker.
SDK polling sleeps total at most 250 ms per execution, then a blocking helper handles longer waits. This keeps polling within the hook's own execution budget while retaining the 60-second guest deadline. Large final diagnostic receipts also use the original process transport. Initialization and cleanup remain in the native broker, which validates the temporary root, owner nonce, and expiry. Normal completion cleans the mailbox. An abrupt host or process termination can leave a temporary directory named aituning-codemode-*; never delete one belonging to an active run.
The mod's deadline stops guest computation and scheduling. Claude Code retains control of cancellation for an underlying tool already running. Permission prompts consume the deadline; if one expires, check the action's actual state before deciding whether to retry.
From this folder, install the pinned build dependencies with lifecycle scripts disabled and rebuild the self-contained runtime:
npm ci --ignore-scripts --no-audit --no-fund
npm run build
npm test
claude plugin validate . --strict
claude plugin test .
The source files under scripts/ own runtime behavior; dist/ is a generated distribution artifact. Rebuild it after source changes. The pinned runtime is QuickJS Emscripten 0.32.0; bundled licences are in dist/.
The build preserves the upstream Emscripten loader's URL-probe and memory-growth fallbacks, but logs their caught errors to stderr. It requires exactly two matching upstream handlers and fails if the pinned source changes. Dependencies and the repository's floor guard remain unchanged.
The code-mode guide explains Pi setup, shared routing, and the bounded benchmark summary. This distribution includes offline runtime tests; paid benchmark scripts and private run records are omitted.
The implementation follows Claude's mod authoring restrictions, mods API, and published types. QuickJS integration follows its primary documentation.
hooks/register.js 154 lines1const SELF = 'mcp__code-mode__';
2const RESERVED = ['tool', 'tool_use_id', 'agentId', 'consent', '__proto__', 'constructor', 'prototype'];
3const PARALLEL_READS = new Set(['Read', 'Grep', 'Glob']);
4const READ_CONCURRENCY = 4;
5
6function protocol(result) {
7 if (result.exitCode !== 0) throw new Error('Code-mode helper failed: ' + result.stderr.slice(0, 1000));
8 return JSON.parse(result.stdout);
9}
10
11async function run($, e, next) {
12 if (typeof e.code !== 'string' || !e.code.trim() || e.code.length > 16384) return { result: JSON.stringify({ ok: false, error: 'code must contain 1-16384 characters' }) };
13 const catalog = (await $.tool.list()).filter(t => !t.name.startsWith(SELF)).map(t => ({ name: t.name, description: t.description.slice(0, 400) }));
14 const executable = ['node', $.plugin.root + '/dist/broker.mjs'];
15 const owner = protocol(await $.process.run([...executable, 'init']));
16 const route = [owner.dir, owner.nonce];
17 let job;
18 let settled;
19 let active = true;
20 const completed = new Map();
21 const deadline = (await $.clock.now()) + 60000;
22 try {
23 const running = $.process.run([...executable, 'run', ...route], { stdin: JSON.stringify({ code: e.code, catalog }), timeoutMs: 70000 });
24 // Observe rejection immediately while other broker requests are in flight.
25 settled = running.then(value => { job = { value }; }, error => { job = { error: String(error.message) }; });
26 let sleepMs = 0;
27 const readFrame = async () => {
28 const entries = await $.fs.list(owner.dir);
29 const readJson = async entry => {
30 if (entry.kind !== 'file' || entry.isLink || !Number.isInteger(entry.size) || entry.size < 0 || entry.size > 300000) throw new Error('invalid mailbox frame');
31 const text = await $.fs.read(owner.dir + '/' + entry.name);
32 if (new TextEncoder().encode(text).length > 300000) throw new Error('mailbox frame exceeds limit');
33 return JSON.parse(text);
34 };
35 const done = entries.find(entry => entry.name === 'done.json');
36 // Large diagnostic receipts retain the existing process transport; the
37 // SDK filesystem has a per-file cap that the old transport did not use.
38 if (done && done.size > 300000) return protocol(await $.process.run([...executable, 'next', ...route], { timeoutMs: 3000 }));
39 if (done) return { done: await readJson(done) };
40 const ready = new Set(entries.map(entry => entry.name));
41 const pending = entries.filter(entry => /^request-\d+\.json$/.test(entry.name) && !ready.has(entry.name.replace('request-', 'reply-').replace('.json', '.ready'))).sort((a, b) => Number(a.name.slice(8, -5)) - Number(b.name.slice(8, -5)));
42 if (pending.length > 32) throw new Error('broker call limit exceeded');
43 const requests = [];
44 for (const entry of pending) requests.push(await readJson(entry));
45 return { requests };
46 };
47 const dispatch = async request => {
48 const dispatchNow = await $.clock.now();
49 if (next.signal.aborted || !active || dispatchNow > deadline || job) throw new Error('Code-mode cancelled, expired, or stopped before tool dispatch');
50 if (!Number.isInteger(request.id) || request.id < 1 || request.id > 32) throw new Error('invalid request id');
51 const identity = JSON.stringify(request);
52 if (completed.has(request.id)) {
53 if (completed.get(request.id).identity !== identity) throw new Error('request id reused with different arguments');
54 throw new Error('duplicate request; refusing to repeat an action');
55 }
56 // Reserve before the first action so duplicate in-flight IDs cannot dispatch twice.
57 const record = { identity, tool: request.name, status: 'pending' };
58 completed.set(request.id, record);
59 const args = request.args;
60 let value;
61 if (typeof request.name !== 'string' || request.name.startsWith(SELF) || !catalog.some(t => t.name === request.name)) value = { deny: 'Tool is unavailable or recursive: ' + request.name };
62 else if (!args || typeof args !== 'object' || Array.isArray(args) || Object.keys(args).some(key => RESERVED.includes(key))) value = { deny: 'Tool arguments must be an object without reserved metadata' };
63 else {
64 try { value = await $.tool.call({ ...args, tool: request.name }); }
65 catch (error) { value = { isError: true, text: String(error.message) }; }
66 }
67 const file = value.result && value.result.file;
68 if (request.name === 'Read' && file && Number.isInteger(file.numLines) && Number.isInteger(file.totalLines) && file.numLines < file.totalLines && args.offset === undefined && args.limit === undefined) {
69 value = { ...value, isError: true, text: 'Read returned partial content (' + file.numLines + '/' + file.totalLines + ' lines). Read explicit offset/limit ranges before computing a whole-file answer.' };
70 }
71 if (new TextEncoder().encode(JSON.stringify(value)).length > 2 * 1024 * 1024) value = { isError: true, text: 'Tool output exceeded 2 MiB; request a smaller range or query' };
72 record.status = value.deny ? 'denied' : value.isError ? 'error' : 'ok';
73 // The broker ignores the JSON until the complete write is committed.
74 const reply = owner.dir + '/reply-' + request.id;
75 const existing = await Promise.allSettled(['.json', '.ready'].map(suffix => $.fs.exists(reply + suffix)));
76 for (const check of existing) {
77 if (check.status === 'rejected') throw check.reason;
78 if (check.value) throw new Error('reply already exists; refusing duplicate');
79 }
80 await $.fs.write(reply + '.json', JSON.stringify({ id: request.id, value }));
81 await $.fs.write(reply + '.ready', 'ready');
82 };
83 let answer;
84 while (!answer) {
85 if (next.signal.aborted || (await $.clock.now()) > deadline) throw new Error('Code-mode cancelled or expired; completed effects are not rolled back');
86 let frame = await readFrame();
87 // A process may finish between the directory listing and its completion callback.
88 if (job && !frame.done) frame = await readFrame();
89 if (frame.done) { answer = frame.done; break; }
90 if (job) throw new Error(job.error || 'Code-mode helper exited without a final result');
91 const batch = frame.requests || [];
92 if (!batch.length) {
93 // SDK sleeps count against the hook's own 10s budget. Bound them across
94 // the entire run, then let a blocking helper wait outside that budget.
95 if (sleepMs < 250) { await $.clock.sleep(10); sleepMs += 10; }
96 else {
97 frame = protocol(await $.process.run([...executable, 'next', ...route], { timeoutMs: 3000 }));
98 if (frame.done) { answer = frame.done; break; }
99 batch.push(...(frame.requests || []));
100 }
101 }
102 if (batch.length > 32) throw new Error('broker call limit exceeded');
103 // Only contiguous known reads overlap. Every other action is a full barrier.
104 for (let index = 0; index < batch.length;) {
105 const group = [batch[index++]];
106 if (PARALLEL_READS.has(group[0].name)) {
107 while (index < batch.length && group.length < READ_CONCURRENCY && PARALLEL_READS.has(batch[index].name)) group.push(batch[index++]);
108 }
109 // Settle siblings even on a transport failure before cancellation or cleanup.
110 const outcomes = await Promise.allSettled(group.map(dispatch));
111 const failed = outcomes.find(outcome => outcome.status === 'rejected');
112 if (failed) throw failed.reason;
113 }
114 }
115 await settled;
116 if (job.error || job.value.exitCode !== 0) throw new Error(job.error || 'Code-mode runner failed after execution');
117 // Mailbox data cannot erase an action or denial observed by this host.
118 const receipts = [...completed.entries()].sort((a, b) => a[0] - b[0]);
119 if (!Array.isArray(answer.calls) || answer.calls.length !== receipts.length || answer.calls.some((call, index) => {
120 const [id, record] = receipts[index];
121 return !call || call.id !== id || call.status !== record.status || (call.tool !== undefined && call.tool !== record.tool);
122 }) || (receipts.some(([, record]) => record.status !== 'ok') && answer.ok !== false)) throw new Error('Code-mode broker receipt does not match completed host actions');
123 return { result: JSON.stringify(answer) };
124 } catch (error) {
125 return { result: JSON.stringify({ ok: false, error: String(error.message), calls: [...completed.entries()].sort((a, b) => a[0] - b[0]).map(([id, record]) => ({ id, tool: record.tool, status: record.status })), note: 'Effects are real, are not rolled back, and must not be retried automatically.' }) };
126 } finally {
127 active = false;
128 // A guest deadline stops scheduling. Underlying tools retain host cancellation semantics.
129 if (!job) {
130 try { await $.process.run([...executable, 'cancel', ...route]); }
131 catch (error) { $.ui.log('Code-mode cancellation cleanup failed: ' + String(error.message)); }
132 }
133 if (settled) await settled;
134 if (job) {
135 try { await $.process.run([...executable, 'cleanup', ...route]); }
136 catch (error) { $.ui.log('Code-mode mailbox cleanup failed: ' + String(error.message)); }
137 }
138 }
139}
140
141export function register(on) {
142 on('session.start', async ($, e, next) => {
143 await $.tool.register({
144 name: 'execute', isDeferred: false,
145 description: 'Run JavaScript to compose existing tools and filter large results before they enter context. Use await tools.call("Read", {file_path:"..."}); results have text/result fields. tools.list() lists names/descriptions; use existing tool schemas or ToolSearch for unfamiliar schemas. Return a small JSON value or emit(value). Every underlying call keeps its normal permissions. Use Promise.all for independent Read/Grep/Glob calls (up to 4 at once); await dependencies. All other tools are sequential barriers. Await all calls, and await reads before dependent mutations. No Node, imports, filesystem, network, timers, or persistent state in the guest. Limits: 32 calls, 60s, 32MiB guest memory, 16000 output characters. Effects are real and not rolled back on error. Use direct tools for simple calls or images/interactive capabilities.',
146 inputSchema: { type: 'object', properties: { code: { type: 'string', minLength: 1, maxLength: 16384 } }, required: ['code'], additionalProperties: false },
147 });
148 return next(e);
149 });
150 on('tool.call', { tool: 'mcp__code-mode__execute' }, async ($, e, next) => {
151 return run($, e, next);
152 }).catch(($, e, next) => ({ deny: 'Code-mode failed closed: ' + next.error.message }));
153}
154