Foundation calls CreateProcessW with CREATE_UNICODE_ENVIRONMENT and
nothing else, and offers no way to add to that. The app has no
console of its own, so every console child it started -- an
emulator, qemu-img, the agent CLI -- was given a fresh command
prompt to look at.
Spawn through ChildProcess instead, which asks for CREATE_NO_WINDOW
on Windows and is Process everywhere else.
Drop the Monaco webview. Edit in a GtkSourceView on GTK and in a
native text view on Apple, both speaking to Frida.Compiler's
in-process language server for completions, signature help,
hovers and diagnostics.
Color, complete and indent code natively, matching the GitHub
theme in light and dark.
A comment is a last resort, and these were not one: section banners,
doc headers naming what the declaration below already names, and
notes saying in prose what the next line says in code.
What is left is what the code cannot show -- an ordering that matters,
a platform quirk, why the obvious way fails.
The playground, the notebook cells, the source editor with its
marks, the inspector columns and the canvas are written once for
both Apple platforms, with a file naming what the two frameworks
call differently. The editor is where they really part company.
The engine owns the image and its start-up, so a mission reaches
the whole of Pharo.
Frontends built the session record, set its phase and surfaced it,
briefly showing the detached banner at the idle phase before attach
began. Move construction, phase and surfacing into prepareSpawnSession
and prepareAttachSession so the session appears already attaching.
Fold the local and URL providers into one configurable
OpenAICompatibleProvider and register both, so a remote
endpoint can be set alongside the local one. Accept optional
API keys for compatible providers in the mission sheet, and
collapse duplicate /v1 segments when building request URLs.
Clear the live assistant text on an explicit liveTextCleared
event emitted once the turn is saved, not on messageStop or
finalMessage. Navigating away and back no longer drops the
latest streamed reply.
Detect the plain px and x commands and fetch their bytes via
pxj, rendering them in our interactive hex view rather than as
text. Resolve the target address so the offset column shows the
real addresses, carrying it on the binary result's metadata.
Move to the single cmd that returns an R2CommandResult with an
optional output, and handle the nil that r2 yields for commands
with no string result (e.g. an invalid command from completion).
Bump the SwiftyR2 pin to the revision with that fix.
Make the REPL prompt a clickable mode toggle between JavaScript
and r2; ":", ":r2" and ":js" switch from the keyboard. Carry the
language on REPLCell and REPLResult, route r2 input through the
session disassembler, and render output as a JSInspectValue tree
for JSON commands or ANSI-parsed styled text otherwise.
Yield api_retry events as live notices so model overload is
visible instead of a silent spinner, and fall back to the
streamed error detail when claude exits with empty stderr.
The base URL only applied on Enter, and applyProviderSelection
reset it to the stored value, wiping typed text. Persist edits
after a debounce instead, and only load the stored URL when
switching provider.
Servers like open-webui need a Bearer token, so introduce an
optionalAPIKey capability, declare it on the local
OpenAI-compatible provider, and show the key field without
gating Start on it.
Refresh only the model list when the base URL or API key
changes, instead of rebuilding the whole provider section: the
full rebuild flickered, and hid the API key entry while typing
into it. Guard async model loads with a generation counter so
stale results cannot overwrite newer ones.
Two distinct failure modes were producing misleading tool
output to agents:
1. The joined `text` payload was being inferred as
`GRDB.SQL` instead of `String` via transitive overload
visibility, producing an unbridgeable Swift value that
NSJSONSerialization rejected with an uncaught
NSException during makeResult. The exception unwound
through a Swift async continuation, leaving the
concurrency runtime in a state that subsequently
tripped libswift_Concurrency during AppKit window
key-state propagation, crashing the app.
2. When the session was detached, the cached Disassembler
still returned lines decoded as "invalid" because r2's
IO backend got read failures from a CachingMemoryReader
whose live reader was nil. Agents saw what looked like
real disassembly of garbage and could be misled into
deep debugging of a non-issue.
Pin String at every binding in the disassemble payload to
prevent inference drift; add an isValidJSONObject guard in
makeResult so any future non-JSON value becomes a clean
MCP error with a key-path + Swift type diagnostic instead
of an uncaught ObjC exception; and require an attached
node before invoking the disassembler, using the canonical
"no attached session for id …" error so agents recover via
the established list_sessions → attach_to_process path.
Replace list_custom_instruments + attach_custom_instrument
with list_instruments + attach_instrument over the
InstrumentDescriptor registry. Hookpacks and the tracer
are now discoverable and attachable through the same path.
Re-attaching an instrument that's already on the session
returns the existing instance.
Edit tools (tracer hook + custom instrument) await
recompile and report apply outcome per session. Query
tools (list_session_instruments, list_tracer_hooks,
read_tracer_hook) expose attachment, status, and
per-hook compile errors. submit_console_input and
invoke_widget_action refuse to dispatch when the
session is detached or the instrument is in error.
A wedged custom instrument blocks dispose RPC, which made every
write_custom_instrument_file call hang waiting on the live reload.
The store write already succeeded; only the side-effect bounce was
stuck, and that's exactly when you most need to edit the source.
Detach the reload into a Task so writes return as soon as the
on-disk state is updated.
Add a 'Trust client approvals' toggle on the external MCP
server config. When on, write-mode tool calls from this server
bypass Luma's approval gate (still recorded in history) so
users aren't double-prompted when their MCP client — e.g.
Claude Code — already prompts per tool. Default off; persists
in LumaAppState and applies live to a running server.
Add a GumJS gotchas section covering the polyfilled surface
(Buffer yes, btoa/atob no, ArrayBuffer.wrap is GumJS not Node),
language bridges needing install_package + global_alias, 64-bit
integer wrappers that JSON-stringify, NativeFunction bool as
0/1, and struct args as flat arrays. Pin the custom-instrument
entrypoint to the top of the file per newspaper order.
Console widgets get an appendImage / respond.image API on the
agent. read_widget_state surfaces image entries as attachments
scaled to at most max_dimension pixels (default 1568) on the
longest edge. Anthropic nests image blocks inside tool_result;
OpenAI gets a synthetic follow-up user message. The host scales
via CoreImage on macOS and GdkPixbuf on GTK.
Walks the payload before serialization and replaces any string
over 2 KiB with a head-truncated marker. Guards against a single
5 MiB base64 blob blowing the LLM's context budget.
Action Queue rendering had drifted: stale field name, two
non-existent tool names, and the edit_* tools missing entirely.
Move the metadata to ActionSpec so the view can't drift again.
Custom instrument files can run to thousands of lines;
full-file rewrites waste tokens and risk clobbering
untouched code. read_custom_instrument_file now returns
line_count to make targeting reliable.
New LumaCore.Appearance (light, dark). DisassemblyRequest,
Disassembler and TraceDisassembler key on it directly; the
r2 theme mapping lives in one place. ThemeWatcher returns
Appearance; remaining C-interop callers compare to .dark at
the boundary. CFG views use themeAppearance to avoid clashing
with NSView's inherited appearance property.
Engine.symbolDisplay returns a richer SymbolDisplay (overlay,
source, raw, primary) and persists every live symbolicate
result into module_symbol (sessionID, modulePath, function-entry
offset → name). Offline rendering falls through to that table
first, then to enclosingFunction from the persisted block
ranges, then to module+offset. ProcessNode delegates trace
block annotation back through this path via
onResolveBlockNames so the same names show up on detached
traces.
Replace the auto-derived title with userTitle (nil = auto)
and a parentInsightID link. Display name is userTitle, else
<parent.title>+0x<delta>, else sub_<offset> if at a function
start, else module+offset. Sidebar context menu Rename… and
an LLM rename_insight tool both go through Engine.renameInsight,
which broadcasts via the existing updateInsight op and pushes
the new name into r2 as a flag (plus afn at function starts).
getOrCreateInsight kicks off a background pass that links a
new insight to its enclosing function-start insight, and
adopts siblings inside its region when the new insight is
itself the function start. The same adoption runs when a
fresh ModuleAnalysis newly reveals a function start under an
existing insight.
Persist per-session module analysis (executable ranges + known
functions, keyed by module name + LC_UUID) so on-demand
analysis runs once per binary and rebases on reattach. The
agent gains enumerateModuleRanges and getModuleIdentity; the
store gains a module_analysis table.
Disassembler queries $FB/$FE for the request address; when it
hits a function start, fetch via pDJ bounded to the function's
byte range. Otherwise fall back to pdJ N. Filter pdJ
decoration entries (xrefs, fcn headers, var summaries) that
share the addr of an instruction.
DisassemblyPage carries a scope (.function/.span) so the
SwiftUI and GTK insight views can disable loadMore when the
page is function-bounded. The disassemble AI tool surfaces the
scope too, so the model knows whether it has the whole
function or just an N-instruction span.
Go-to-Function-Start now opens (or selects) the insight rooted
at the function start, which then renders via the bounded
path.
runCommand and decompile now return R2CommandResult so callers
can see r2's log entries. r2_cmd surfaces them to the agent;
findFunctionStart treats any error log as no result.
Four typed tools layered on radare2 + the mission's LLM:
suggest_function_name, suggest_function_signature,
suggest_local_names, find_vulnerabilities. Suggestions are
proposals only; the agent applies them via r2_cmd if it wants.