Commit Graph
42 Commits
Author SHA1 Message Date
Ole André Vadla Ravnås f8d25015ae Drop the comments that only restate the code
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.
2026-08-13 13:09:18 +02:00
Ole André Vadla Ravnås 2528c8fed2 Keep a snippet and its result in the notebook
A notebook cell holds a Pharo snippet and what it produced, updated
field by field and synced back when it is re-run. Trace blobs move
to a store of their own, read through one loader.

The method mark's editor keeps its own keys and clicks, so editing
inside an opened body works like editing anywhere else.
2026-08-13 12:46:09 +02:00
Ole André Vadla Ravnås b84bfa0a2b Add r2 as a first-class REPL language
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.
2026-06-26 16:45:21 +02:00
Ole André Vadla Ravnås 9606ec4930 Drop the driver concept; await host-claim ack
The driver seat became fully redundant with the host once session
interaction moved to owner gating: it was always equal to the host and
nothing read it for authorization. Remove it wholesale — the
claim-driver op, driverBySession, localUserIsDriver, Session.driver,
and the REPL "Driving" indicator.

Gate REPL submission on ownership (solo always). Claim hosting through
an acknowledged request and attach only once the server accepts,
instead of optimistically attaching before the claim is even sent.
2026-06-26 15:34:02 +02:00
Ole André Vadla Ravnås 0384896e9d core: Sync persisted module symbols via collab
New update-module-symbols SessionOp carries a batch of
(modulePath, offset, name) entries. persistSymbolicateResults
enqueues one op per symbolicate roundtrip; non-drivers upsert
into module_symbol on receive. Late-joiner snapshots ingest
the whole table.
2026-05-20 21:15:55 +02:00
Ole André Vadla Ravnås 12d5491c22 Sync disasm cache across collab
Identify a process instance by a deterministic identity returned
from the agent (per-OS, with a Process.mainModule + pid fallback)
and stash it on ProcessSession.processInfo. Rekey memory_page
into memory_page_module (module UUID + offset) so cached bytes
survive ASLR, and memory_page_anon (process identity + address)
for JIT / heap regions that only survive within one process
instance. Purge stale anon pages on every fresh attach.

Add updateProcessInfo, updateModuleAnalysis and updateInsight
SessionOps plus a side-channel publish-memory-page op and a
+fetch-memory-page request. The driver write-throughs every
page it fetches; non-drivers pull on miss and persist locally.
Module-analysis rows flow the same way so non-drivers can key
their cache by module UUID. The lab-join snapshot now carries
processInfo and module_analyses too.

Refresh actions on the insight detail view: Reread Bytes drops
the pages overlapping the visible range; Reanalyze Module drops
the module's analysis and pages, re-running r2 on next access.

The macOS detail view now reads the insight live from
insightsBySession via observation; GTK forwards .insightUpdated
into the detail and reschedules a refresh when the resolved
address changes.
2026-05-19 15:16:48 +02:00
Ole André Vadla Ravnås e6c3d8946f core+gtk: Delete and edit individual note messages
Engine: deleteAddressNoteMessage drops the row from the store
and enqueues an address_note_message_remove op. Collab apply
mirrors the existing edit path. AddressNoteChange grows a
messageRemoved case.

GTK: right-click on a user message offers Edit and Delete.
Edit swaps the body label for an inline editor; Save commits
via editUserMessage. Delete confirms first, then removes the
row from the popover and via the engine.
2026-05-18 18:57:50 +02:00
Ole André Vadla Ravnås 1c4177a83d core: Sync address notes via collaboration
AddressNoteOp covers note upsert/remove plus message append/edit.
Outbox table mirrors mission_outbox; inbound ops apply through
Engine and refresh the address-annotation gutter.
2026-05-18 00:25:58 +02:00
Ole André Vadla Ravnås 4b5ceb6806 collab: Patch chat list per change instead of rebuild
Replace the void did-change callback with a
ChatMessageChange enum so the GTK panel routes each
mutation to a targeted ListBox op. With a 100-message
buffer the prior full rebuild on every send cost ~200 row
constructions for the optimistic+reconcile pair.
2026-05-16 21:39:18 +02:00
Ole André Vadla Ravnås ccdb89e65e collab: Send chat optimistically and reconcile
sendChat appends the message immediately, awaits the .add
response, then replaces the entry with the server-stamped
version or rolls it back on failure. Wire id ties the
optimistic and canonical entries together so peers see the
same identity. UI restores the draft when the send fails.
2026-05-16 20:59:29 +02:00
Ole André Vadla Ravnås 6fd038520b core: Surface instrument load and reload failures
Catches around loadInstrumentOnNode, applyInstrumentConfig
and the custom-instrument reloader now set the runtime
status via InstrumentStatus.from(error:kind:) instead of
swallowing the error with print. Status changes are
broadcast both locally (so the host UI refreshes) and to
collaboration peers via a new runtime_status piggy-backed
on the existing add/update-instrument wire payload.
2026-05-16 02:55:45 +02:00
Ole André Vadla Ravnås 110d3ec4c1 core: Parse chat message timestamp from server
ChatMessage.fromJSON ignored the server's timestamp field, so
joining a lab stamped every history entry with the join time.
2026-05-15 13:25:37 +02:00
Ole André Vadla Ravnås d1606523b5 Support multi-file custom instruments
- Store source files as named CustomInstrumentFile rows per def
- Render sidebar with disclosure groups
- Drive editor via Monaco project models with activePath
- Validate Add/Rename/Delete dialogs and submit on Enter
- Fork installed hook packs into editable custom instruments
- Reload instances on def change; clear stale compat reasons live
- Focus editor on selection (native + Monaco)
2026-05-13 08:54:48 +02:00
Ole André Vadla Ravnås 39aa24f773 Use unified error formatting for user messages
WelcomeModel, CollaborationSession, and AuthFailure all hand-rolled
String(describing:) variants to dodge NSError bridging or the enum-case
form. With Frida.Error now formatting itself, these collapse to plain
localizedDescription / String(describing:).
2026-05-11 10:56:53 +02:00
Ole André Vadla Ravnås 26d687cecd Wire widgets through agent, UI, and collab
Engine owns the widget state cache, applies inbound updates from
the agent, persists session-scoped widgets, and broadcasts to
collaborating peers (action invocations route to the host).
Renderers in both UIs read the cache and the unified update
stream, with a Clear button that propagates to peers.

Agent SDK adds ctx.widget(id) with push/upsertItem/removeItem/
clear, plus a typed onAction handler. create() now takes a
restored snapshot seeded from persisted state and the join
snapshot.

Per-def .d.ts narrows the widget map, restored shape, and action
union to the exact ids declared in the editor.
2026-05-10 21:02:42 +02:00
Ole André Vadla Ravnås ef0017255b mission: Drop targets, add discovery tools
The agent finds or creates its own sessions via list_devices,
list_processes, attach_to_process, and spawn_process.
2026-05-09 13:16:45 +02:00
Ole André Vadla Ravnås 5d0748515b mission: Add goal-driven agent loop
LLM agent for user-stated RE goals: propose-and-approve over the
existing RPC, evidence-grounded findings, collab via the op-outbox
pattern. Anthropic / OpenAI / local-OAI providers. r2ai used for
explain_function when the plugin is loaded; falls back to the
mission's own provider otherwise.
2026-05-09 12:54:14 +02:00
Ole André Vadla Ravnås 05380cd568 core: Add spawn gating
Engine drives device.enableSpawnGating() per armed-session intent;
matched spawns re-attach via the existing attach path and misses
resume immediately. Engagement intent is in-memory only so launches
don't auto-engage gating for sessions armed in a previous run.

ProcessSession gains armingState and lastArmPattern, mirrored in
the SQLite schema. RuntimeEvent.Source.spawnGating carries the
captured/released outcome with wire encoding.

Lab protocol adds update-arming as a SessionOp variant; armed-only
sessions announce themselves with pid 0 and phase detached so peers
can see them before any capture.

Engine.onUserNotification surfaces gating-enable failures using the
clean Frida.Error message.
2026-05-08 16:15:25 +02:00
Ole André Vadla Ravnås 91385e11a4 core: Wire ITrace, deltas, and UI state in engine
Engine subscribes to module/thread deltas and trace updates, drives
the unified ITrace lifecycle, paginates set-trace-data uploads with
lazy server-stat resume, and falls back to a paginated +fetch with
optional first-paint prefix and onProgress reporting. Server-driven
trace-data-progressed broadcasts feed back as cache invalidations.

ProjectStore picks up itrace, project_ui_state and session_ui_state
tables; the schema is created with if-not-exists, no migrations.
SessionOp carries upsertTrace, removeTrace, traceDataProgressed,
and the module/thread updates are now ModuleDelta/ThreadDelta.
2026-05-07 16:40:30 +02:00
Ole André Vadla Ravnås c111319d3b core: Add custom instruments 2026-05-05 19:15:50 +02:00
Ole André Vadla Ravnås eaac8b03ff core: Backfill local session children to new lab
When a host enables collaboration on an existing project, replay
each session's REPL cells, instruments, insights, and captures so
peers get a complete snapshot. Fire the sessions fan-out right
after createLab.
2026-05-03 22:33:54 +02:00
Ole André Vadla Ravnås 4a89ed69f2 core: Retry avatar fetch with backoff
The avatar fetch occasionally fails transiently. Retry at
0/0.5/2s before giving up.
2026-05-03 22:33:54 +02:00
Ole André Vadla Ravnås dc9e5b5c2f core: Defer lab create/join until welcome arrives
/labs/... requires localUser, which only arrives via +welcome.
Stash the request and dispatch once welcome lands, instead of
racing the handshake.
2026-05-03 22:31:49 +02:00
Ole André Vadla Ravnås 9d02419562 Sync sessions across collaborative labs
Sessions in a lab are now collaborative artifacts. Any owner can
host (own the local Frida attachment) and any owner can drive
(issue REPL evals and instrument commands); a driver's actions
are relayed to the current host. Members watch read-only and see
an "Only lab owners can host sessions" CTA when they try the new
session button.

Re-host (Run on My Device…) lets an owner take over a running
session by attaching to their own equivalent process; the prior
host's Frida attachment detaches and the new host auto-loads the
session's instruments. Driver auto-reverts to host when the
current driver fully disconnects from the lab.
2026-04-30 23:28:15 +02:00
Ole André Vadla Ravnås 5a874af791 Import FoundationNetworking on Linux 2026-04-27 18:25:58 +02:00
Ole André Vadla Ravnås 5249108b5e Always seed lab pictures from creator avatar
A lab now always has a picture. The client fetches the
signed-in user's GitHub avatar before sending .create
/labs and submits its bytes as the initial picture; if
the avatar fetch fails, lab creation is aborted with an
error rather than producing a pictureless lab.

Drop the remove-picture flow on both sides — the owner
can still upload a replacement, but there's no way to
clear the picture, since the data model now requires
one. With the invariant in place, LabSummary's picture
fields and the welcome-screen lab rows no longer carry
fallback paths to the owner's avatar.
2026-04-27 15:04:54 +02:00
Ole André Vadla Ravnås 8fb8c13dfc Switch client→server messages to a "to" key 2026-04-27 14:59:50 +02:00
Ole André Vadla Ravnås 1b33edcfe3 Polish collab panel and notebook interactions
Several small, related fixes:

- GTK NotebookPane: fix insert-first regression in the .updated
  handler. We were anchoring on next.prevSibling *after*
  removing the existing row, which collapses to nil when the row
  was last and lands the fresh one at the top. Capture prev
  before removal instead.
- GTK NotebookPane: drop the per-row "+ Note" and "Delete"
  buttons from the header. Both actions live in the right-click
  menu already, and the header was getting crowded. Left the
  Edit button in place.
- GTK + Mac CollaborationPanel: remove the "Project
  collaboration" heading — it's redundant with the "Collaboration"
  title at the top of the panel — and drop the "Lab: <id>" row;
  the id is already on the invite link.
- GTK CollaborationPanel: widen the invite hint label so it
  fills the frame instead of wrapping too early, and relocate
  the "Enable notifications" affordance from the participants
  row into labSection so it sits near the project identity
  rather than the member list.
- Both panels: rename "Disable collaboration" to "Disconnect
  from lab". We're just leaving the session — the lab itself,
  and the user's membership, stick around.
- Client: handle +update on /users/<id>/push_subscriptions so
  the "Enable notifications" button disappears live once the
  browser enrollment completes (server now broadcasts on every
  subscription change).
- AuthFailure.fromError: decode the `luma-auth:<base64>` marker
  the server now emits instead of spelunking for a JSON blob
  through quote-escape layers. Survives any amount of
  stringification frida-core does on its way across the wire.
- Engine: temporary reorder-path diagnostic print while we
  chase the "Move Up stays in place" report.
2026-04-20 11:03:32 +02:00
Ole André Vadla Ravnås 60fc7bb706 Add lab pictures and refresh timestamps
Lab pictures are now uploadable by the owner — pick an image in
the CollaborationPanel (NSOpenPanel on Mac, GtkFileDialog shim
on GTK) and it becomes the lab's avatar on every client and on
the browser /l/<id> page. PNG, JPEG, WebP, and GIF are accepted;
the server enforces a 512 KiB cap. When no picture is set we
fall through to the owner's GitHub avatar, so a lab that nobody
bothers customizing still shows a real face. Clients get
pictures inline in the .join snapshot (bytes appended after the
notebook blob) and live updates via +update on
/labs/<id>/picture; CollaborationSession gains setLabPicture /
removeLabPicture RPCs.

Two other fixes in the same pass:

- Relative timestamps now refresh. Mac wraps each label in a
  TimelineView(.periodic(..., by: 60)); GTK runs a single 60 s
  Task that rewrites the string on every registered Label. So
  "1 min ago" actually advances to "2 min ago" without a
  rebuild.
- Closing a document window now stops collaboration. Previously
  the session stayed joined until the whole app quit — Mac via
  .onDisappear on MainWindowView, GTK via the existing
  closeHandler calling engine.collaboration.stop() before
  persisting state.
2026-04-20 10:17:50 +02:00
Ole André Vadla Ravnås 235b978234 Add lab title and render times relatively
Every lab now has a title. On .create the client mints one from
the owner's local timezone — "<Weekday> <morning|afternoon|
evening|night> <verb>" with verb ∈ {reversing, tracing, hacking,
spelunking, poking, sleuthing, dissecting} — so the first thing
everyone sees is something like "Friday evening spelunking"
instead of an opaque id. Owners rename at any time via a pencil
button in the CollaborationPanel (SwiftUI inline field on Mac,
inline Entry on GTK); the server broadcasts +update on
/labs/<id> so every connected client gets the new title live.

Separately, all visible timestamps switch to a coarse "time ago"
form ("just now", "5 min ago", "2 hr ago", "yesterday", "3 days
ago", "last week"). A shared `LumaCore.RelativeTime.string(from:)`
helper keeps Mac and GTK consistent; the absolute timestamp lives
on as a tooltip for anyone who needs it. Event stream / REPL
cells keep their absolute high-precision times since those are
high-volume and the exact value matters.
2026-04-20 09:54:46 +02:00
Ole André Vadla Ravnås 72fb0dec78 Sync notebook via op log with outbox
Replace the four per-mutation notification types (+add/+update/
+remove/+reorder) with a single +op envelope carrying {op_id,
kind, ...}. Pending mutations persist to a `notebook_outbox`
table on the client and replay on every .joined transition, so
an offline user's work propagates as soon as they're back on the
bus. Projects that never enable collaboration keep a zero-row
outbox — we only touch it once `collaboration_state.lab_id` is
set, and bulk-enqueue existing entries on createLab's success.

Model changes, in the same pass:

- NotebookEntry swaps `author: Author?` for `editors: [Author]`
  (creator at index 0, each editor appended as the server applies
  their update op). The boolean `isUserNote` is gone; callers now
  read `entry.kind == .note` directly.
- Entries carry a `position: Double` — server-authoritative on
  add (`max + 1000`), client-computed midpoint on drag. Clients
  sort by `(position, id)` so concurrent reorders converge.
- Schema folds the new columns into v1 (no migration — Luma isn't
  shipped).

UI:

- Mac: header renders a stack of editor avatars (tooltip = name,
  tap opens the GitHub profile). Entry rows are draggable and
  accept drops on top/bottom halves to compute the insertion slot.
- GTK: same avatar stack; reordering lives in the right-click
  menu (Move Up / Move Down) since SwiftGtk's DragSource binding
  isn't verified yet on this branch.
2026-04-20 09:30:07 +02:00
Ole André Vadla Ravnås d732977e33 Batch notebook entries during backfill
Replace the per-entry send loop with notifyEntriesAdded(_:), which
greedily packs entries into chunks capped at 4 MiB per +add (JSON
payload size plus binary bytes) and emits one message per chunk.
notifyEntryAdded delegates to the plural form so the live-add
path shares the same framing.

Matches the batch-friendly protocol and gives createLab one
round-trip for the initial sync instead of N fire-and-forget
notifications.
2026-04-20 02:04:11 +02:00
Ole André Vadla Ravnås 533097db61 Skip self-authored events and suppress if focused
Three related fixes so the GTK desktop notifications only fire
when they're actually useful.

- CollaborationSession grows an isSelf(userID:) helper so the
  "don't notify myself" check reads cleanly at call sites.
  Engine now stamps NotebookEntry.author with the local user on
  addNotebookEntry so the check works for entries the user
  authored before the server has had a chance to echo one back.

- NotebookChange gains a .snapshot([NotebookEntry]) case that
  Engine fires once for the batch delivered on .join. Previously
  every entry in the initial snapshot went out as .added, which
  would have surfaced as a notification per historical entry
  when the user joined a lab. NotebookPane routes it to
  rebuildEntries.

- DesktopNotifier holds the main window and skips delivery
  whenever that window is the active toplevel, so users looking
  at Luma don't get a bubble over the app showing them what
  they just watched happen.

The MainWindow / CollaborationPanel callback chains are also
tidied up to go via self.engine once and use the new isSelf
helper, so the optional soup shrinks. Not yet addressed:
macOS's equivalent blind-spot when the app is minimised but
still connected — APNs won't fire because the user looks
"online" to the server. Covering that wants a mirror of this
with UNUserNotificationCenter, next pass.
2026-04-20 00:42:37 +02:00
Ole André Vadla Ravnås 24029fd93c Track registered push platforms from welcome
CollaborationSession now keeps registeredPushPlatforms as a
Set<String> populated from the server's push.registered list
in the welcome envelope, cleared on stop(), and updated
optimistically when the client sends its own
+add push_subscriptions so the UI reacts without a round trip.

LumaGtk's collaboration panel hides the "Enable notifications"
button whenever that set is non-empty: if the user already has
APNs from the Mac app, the browser handoff would be dormant
(APNs wins), and if they already enrolled Web Push from this
or another browser, re-enrolling is redundant.
2026-04-20 00:42:37 +02:00
Ole André Vadla Ravnås 93ff0a5005 luma-gtk: Mint tokens and hand off to the browser
LumaCore exposes requestPushEnrollmentToken() on
CollaborationSession — a thin request/response wrapper around
.create on /users/<id>/push_enrollment_tokens — and Engine
wraps it with webPushEnrollmentURL() that folds the returned
token plus VAPID public key into
https://luma.frida.re/push-enroll#token=…&vapid=… .

The GTK collaboration panel grows an "Enable notifications"
button next to the Participants heading that mints a ticket
and hands the URL to the system browser via UriLauncher. The
Mac app deliberately skips this — it will register APNs
natively (next pass) rather than farm push out to Safari.
2026-04-20 00:42:37 +02:00
Ole André Vadla Ravnås dd031c0a6a Speak the new resource-path protocol
Rewrite CollaborationSession around the server's new envelope
shape: a single dispatcher that routes (from, type), explicit
request/response correlation via client-chosen ids, and typed
Member objects carrying role + Presence + last_seen_at.

NotebookEntry gains an Author field stamped by the server and
a Kind enum (note | capture); isUserNote is preserved as a
computed alias so the UI doesn't churn. Notebook update hooks
now fire with (UUID, changes) so Engine can merge into the
locally-cached entry without the server having to re-send the
full row.

Protocol key shake-up is snake_case throughout, and UserInfo
now reads "avatar" (matches what the server actually emits —
the previous "avatar_url" lookup was a latent bug). The Mac
and GTK collaboration panels switch from participants to
members.
2026-04-20 00:42:37 +02:00
Ole André Vadla Ravnås 1fe6e4704f core: Clear token on portal auth rejection
The portal's authenticator now surfaces a structured
{domain, code, message} envelope in the thrown error when it
rejects a token. Parse that in AuthFailure, and if domain is
"auth" (persistent problem — bad token, missing scope,
unverified email), fire an onAuthRejected callback on the
CollaborationSession. Engine wires that to gitHubAuth.signOut,
clearing the keychain so the user is prompted to re-auth with
the current scope set. Transient github.* failures leave the
session alone.

This also unblocks future scope widening: older tokens will be
rejected with a recognised code and the client will silently
bounce the user through sign-in again.
2026-04-19 21:27:56 +02:00
Ole André Vadla Ravnås 9fda5eb27e Rename room → lab throughout
Match the server's rename: CollaborationSession uses labID,
createLab / joinLab, ProjectCollaborationState's roomID field
+ its DB column become labID / lab_id, and UI strings / deep-
link query param all track "lab".

Since the app hasn't launched, the DB column rename is landed
directly in the v1 migration rather than via a new migration —
dev databases need to be wiped.
2026-04-18 12:15:37 +02:00
Ole André Vadla Ravnås e53a83d79d Fix chat message sender key mismatch
The server puts the sender under "user" (matching the rest of the
protocol), but ChatMessage.fromJSON was looking for "sender", so
every chat message — including the sender's own echo — failed to
parse and silently dropped.
2026-04-18 01:50:25 +02:00
Ole André Vadla Ravnås ee08a1a537 luma: Incremental UI updates across all panes
Replace snapshot-and-rebuild patterns with granular change
events throughout the app.

REPL cells: Engine emits onREPLCellAdded; REPLPane appends
individual cell widgets instead of clearing and rebuilding.

Notebook entries: Add NotebookChange enum with added/updated/
removed/reordered cases. Engine emits from addNotebookEntry,
updateNotebookEntry, deleteNotebookEntry, and collaboration
callbacks. NotebookPane tracks rows by ID and handles each
event incrementally.

Event stream: EventLog emits onEventsAppended with just the
new events from each flush. EventStreamPane appends matching
rows inline. Full rebuild only on filter/search change or
resume from pause.

Device list: Add DeviceChange enum to frida-swift with
appeared/disappeared cases and a changes() stream alongside
existing snapshots(). TargetPicker inserts and removes
individual device rows.

Collaboration: CollaborationSession emits onParticipantJoined,
onParticipantLeft, onChatMessageReceived. CollaborationPanel
appends participant rows and chat bubbles incrementally.
2026-04-09 15:06:31 +02:00
Ole André Vadla Ravnås 875a19cfc2 Delete Workspace+Collaboration
CollaborationSession is now @Observable. Views read its
state directly via workspace.engine.collaboration. The
notebook entry methods live on Engine. The portal cert
is a LumaCore SwiftPM resource via Bundle.module.

Workspace keeps only the auth-flow glue (showing the
sign-in sheet, signOut clearing UI state) and the deep
link join handler.
2026-04-07 07:47:39 +02:00
Ole André Vadla Ravnås 9959d80719 Move package mgmt and collab into LumaCore
Move the core business logic for both subsystems:

Package management (CompilerWorkspace):
- PackageOperationQueue: serial async operation queue
- ESMBundleParser: Frida ESM bundle format parsing
- CompilerWorkspace: package install/remove/upgrade,
  bundle building via Frida Compiler, workspace lifecycle
- CompilerWorkspacePaths: URL helpers

Collaboration (CollaborationSession):
- Portal bus state machine (connect, create/join room)
- Participant tracking, chat messaging
- Notebook sync protocol (add/update/delete/reorder)
- Uses AsyncEventSource for status change notifications

Both use ProjectStore for persistence instead of SwiftData.
2026-04-06 20:18:33 +02:00