mirror of
https://github.com/morluto/rea.git
synced 2026-11-03 05:16:16 +00:00
Add a compact rea-codemode MCP server, bounded QuickJS execution and shared SDK/direct adapters. Reuse native target and process owners; retain evidence and lease-scoped snapshots. Extend the real-model trial runner with an isolated experimental MCP registration. Capability and evaluation work remains in progress. Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com> Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
223 lines
7.3 KiB
JavaScript
223 lines
7.3 KiB
JavaScript
// One named contract registry shared by SDK, CLI, direct MCP and Code Mode.
|
|
import { z } from "zod";
|
|
import { KunaFactOwner, FactFailure } from "./provider.mjs";
|
|
|
|
const hex = z.string().regex(/^0x(?:0|[1-9a-f][0-9a-f]{0,15})$/u);
|
|
const snapshot = z.string().startsWith("snap_");
|
|
const page = {
|
|
offset: z.number().int().nonnegative().default(0),
|
|
limit: z.number().int().min(1).max(128).default(20),
|
|
};
|
|
const kinds = z.enum([
|
|
"value",
|
|
"load",
|
|
"store",
|
|
"phi",
|
|
"call",
|
|
"branch",
|
|
"switch",
|
|
"return",
|
|
]);
|
|
const fields = z
|
|
.array(z.enum(["kind", "statement", "definitions", "constants"]))
|
|
.min(1)
|
|
.optional();
|
|
const definition = (description, input, method, example) => ({
|
|
description,
|
|
input: z.strictObject(input),
|
|
method,
|
|
example,
|
|
});
|
|
export const methods = {
|
|
strings: definition(
|
|
"Search retained string inventory by literal text. Paging does not establish artifact completeness.",
|
|
{ query: z.string(), ...page },
|
|
"strings",
|
|
'await rea.strings({query:"incorrect header check"})',
|
|
),
|
|
references: definition(
|
|
"Batch exact-address incoming xrefs, preserving kinds and owning-function identities. Interior string uses require a separate extent query.",
|
|
{ addresses: z.array(hex).min(1).max(16), ...page },
|
|
"references",
|
|
'await rea.references({addresses:["0x2000"]})',
|
|
),
|
|
snapshot: definition(
|
|
"Analyze an explicit function entry once; retain native C and SSA, return its handle and coverage. Reuses the same source within this target lease.",
|
|
{ entry: hex },
|
|
"snapshot",
|
|
'await rea.snapshot({entry:"0x1000"})',
|
|
),
|
|
operations: definition(
|
|
"Select SSA operations by IDs, instruction origins, block IDs, constant literal or kind. Filters intersect; identities and source lines are always retained. A literal match is not object identity.",
|
|
{
|
|
snapshot,
|
|
ids: z.array(z.string()).max(128).optional(),
|
|
addresses: z.array(hex).max(128).optional(),
|
|
block_ids: z.array(z.number().int().nonnegative()).max(128).optional(),
|
|
constant: hex.optional(),
|
|
kind: kinds.optional(),
|
|
fields,
|
|
...page,
|
|
},
|
|
"operations",
|
|
'await rea.operations({snapshot:s,constant:"0x2000",fields:["statement"]})',
|
|
),
|
|
relations: definition(
|
|
"Read explicit definitions/uses for operation IDs, or incoming/outgoing CFG edges for block IDs. Branch polarity refers to the printed predicate. CFG is partial; printed occurrences are not p-code input indexes or phi predecessor bindings.",
|
|
{
|
|
snapshot,
|
|
kind: z.enum(["definitions", "uses", "incoming", "outgoing"]),
|
|
seeds: z
|
|
.array(z.union([z.string(), z.number().int().nonnegative()]))
|
|
.min(1)
|
|
.max(128),
|
|
...page,
|
|
},
|
|
"relations",
|
|
'await rea.relations({snapshot:s,kind:"uses",seeds:[op.id]})',
|
|
),
|
|
slice: definition(
|
|
"Compose a bounded backward definition traversal from explicit operation IDs. Default load/phi cutoffs keep boundaries visible; choose stop_kinds:[] to traverse beyond them. Complete reachability and memory aliases remain unknown.",
|
|
{
|
|
snapshot,
|
|
seeds: z.array(z.string()).min(1).max(64),
|
|
max_operations: z.number().int().min(1).max(256).default(48),
|
|
stop_at: z.array(z.string()).max(128).default([]),
|
|
stop_kinds: z.array(kinds).default(["phi", "load"]),
|
|
},
|
|
"slice",
|
|
"await rea.slice({snapshot:s,seeds:branchIds,max_operations:48})",
|
|
),
|
|
code: definition(
|
|
"Read selected display lines of the native reconstructed C retained with this exact SSA source; offset is zero-based. No additional decompilation.",
|
|
{ snapshot, ...page },
|
|
"code",
|
|
"await rea.code({snapshot:s,offset:100,limit:64})",
|
|
),
|
|
evidence: definition(
|
|
"Read complete retained native source in UTF-16-code-unit pages by its evidence_id; no reanalysis. Available until close_binary.",
|
|
{
|
|
evidence_id: z.string().startsWith("ev_"),
|
|
offset: page.offset,
|
|
limit: z.number().int().min(1).max(16384).default(4096),
|
|
},
|
|
"evidence",
|
|
"await rea.evidence({evidence_id:id,offset:0,limit:4096})",
|
|
),
|
|
};
|
|
|
|
export class AtomicSession {
|
|
#owner;
|
|
#tail = Promise.resolve();
|
|
constructor(factory = KunaFactOwner.open) {
|
|
this.factory = factory;
|
|
}
|
|
#admit(work) {
|
|
const pending = this.#tail.then(work, work);
|
|
this.#tail = pending.catch(() => {});
|
|
return pending;
|
|
}
|
|
get context() {
|
|
return this.#owner?.context ?? null;
|
|
}
|
|
abort() {
|
|
this.#owner?.cancel();
|
|
}
|
|
open(path) {
|
|
return this.#admit(async () => {
|
|
if (this.#owner) await this.#owner.close();
|
|
this.#owner = undefined;
|
|
this.#owner = await this.factory(path);
|
|
return {
|
|
ok: true,
|
|
context: this.context,
|
|
methods: Object.keys(methods),
|
|
evidence_lifetime: "current target lease",
|
|
};
|
|
});
|
|
}
|
|
close() {
|
|
return this.#admit(async () => {
|
|
if (this.#owner) await this.#owner.close();
|
|
this.#owner = undefined;
|
|
return { ok: true, closed: true };
|
|
});
|
|
}
|
|
call(
|
|
name,
|
|
input,
|
|
signal = new AbortController().signal,
|
|
lease = this.context?.lease,
|
|
) {
|
|
return this.#admit(async () => {
|
|
try {
|
|
if (signal.aborted)
|
|
throw new FactFailure(
|
|
"cancelled",
|
|
"Request was cancelled before admission",
|
|
);
|
|
if (!this.#owner)
|
|
throw new FactFailure(
|
|
"no_target",
|
|
"Call open_binary before analysis",
|
|
);
|
|
if (lease !== this.context.lease)
|
|
throw new FactFailure(
|
|
"stale_context",
|
|
"Target lease changed; do not join facts across leases",
|
|
);
|
|
if (!Object.hasOwn(methods, name))
|
|
throw new FactFailure(
|
|
"unknown_method",
|
|
`Unknown API method: ${name}`,
|
|
);
|
|
const contract = methods[name];
|
|
const parsed = contract.input.parse(input);
|
|
const data = await this.#owner[contract.method](parsed, signal);
|
|
return { ok: true, context: this.context, data };
|
|
} catch (error) {
|
|
return {
|
|
ok: false,
|
|
error: {
|
|
code:
|
|
error instanceof z.ZodError
|
|
? "invalid_input"
|
|
: (error.code ?? "analysis_failed"),
|
|
message: error.message,
|
|
details: error.details ?? {},
|
|
},
|
|
};
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
export const reference = (query = "") => {
|
|
const keys = Object.keys(methods).filter(
|
|
(key) =>
|
|
query === "" ||
|
|
key.includes(query) ||
|
|
methods[key].description.toLowerCase().includes(query.toLowerCase()),
|
|
);
|
|
return {
|
|
language:
|
|
"JavaScript async function body; return a JSON value. All rea methods return {ok,context,data} or {ok:false,error}.",
|
|
methods: keys.map((name) => ({
|
|
name: `rea.${name}`,
|
|
description: methods[name].description,
|
|
input_schema: z.toJSONSchema(methods[name].input),
|
|
example: methods[name].example,
|
|
})),
|
|
limits: {
|
|
execution_heap_mib: 64,
|
|
max_host_calls: 64,
|
|
output_utf8_bytes: 32768,
|
|
},
|
|
notes: [
|
|
"No process, filesystem, network, imports or Node globals in the execution environment.",
|
|
"Select facts inside code, return only what the question needs, and keep real source IDs and unknowns.",
|
|
"A fresh JavaScript environment is used per execute_javascript call; analysis snapshots live until close_binary.",
|
|
],
|
|
};
|
|
};
|