Repository navigation
feat(runtime): record scenes in desktop electron apps - #55
Draft
winlp4ever wants to merge 10 commits into
Draft
winlp4ever wants to merge 10 commits into
winlp4ever wants to merge 10 commits into
Conversation
BREAKING CHANGE: App is a union of web and electron apps; code reading app.url narrows on kind first.
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
) * feat(runtime): launch and drive a desktop electron app in a sandbox * fix(runtime): read working folders from /proc on linux when sweeping a sandbox * fix(runtime): bound a desktop app's shutdown and say an app that quits as it's attached * fix(runtime): say a desktop app that quits before showing a window at once
* fix(runtime): wait for the guard's stops before reading a desktop app's window * test(runtime): run the desktop-app suite alone, after every other suite * perf(runtime): kill a desktop app outright and close its connection after * fix(runtime): send a desktop app's window back on commit, with its own time
* feat(runtime)!: confine every desktop-app launch under seatbelt Each launch runs under macOS Seatbelt: it writes only into its own sandbox, never reads the user's folders, runs nothing it drops there, and reaches neither preferences, the keychain, the clipboard nor other agents' sockets. A canary checks the confinement before every launch (one read refused, one allowed); without it, no launch. A project's files/ is copied into each launch's sandbox (links, devices and oversized trees refused); the app's arguments name paths in it. Sandboxes live in ~/.kiframe/sandboxes, each named after the Kiframe that made it, so a crash sweep removes only what no running Kiframe owns. BREAKING CHANGE: a desktop app's args must be paths in the project's files/ (files/<name>); any other argument no longer parses. * fix(runtime): steady a desktop-app launch's checks and copy - each launch writes its own canary, and a canary that went missing proves nothing (refused): launches at once never spoil each other's - the app's time to start begins once its files are copied - a stop during the copy ends the launch before the app starts - a folder in files/ swapped for a link while copied is refused - the app's own temp folder holds its own pages again * fix(runtime): finish a desktop-app launch's copy and canary checks - a folder in files/ is checked again once its entries are copied: one swapped for a link at any point of the copy is refused - each canary lies beside its sandbox, named after it: a crash's is swept - a stop ends the canary check and the copy at once, said as the stop - a canary that was read reads as a confinement that didn't hold - the slow-copy tests are sized to the app's measured start, built once and removed
* feat(runtime): inspect and try a desktop app before it's added - inspectDesktopApp: static checks of a picked .app (where it is, Info.plist via plutil, Electron's framework, its executable, App Sandbox, codesign); its signer kept: a developer's team, or a digest pinning an unsigned build - trialDesktopApp: a confined launch without the network beyond this machine; read as ok, a wrapper's site (named even unreached), a quit - apps allowing one copy (Slack, VS Code, Discord) launch confined: their lock's socket goes to the sandbox's temp (MAC_CHROMIUM_TMPDIR) - launch errors say why structurally; the tests' launch hooks leave the package's launch options - a macOS CI job runs the desktop-app suite * fix(runtime): steady a desktop app's inspection and trial - a pinned build's digest covers the whole bundle (helpers, frameworks) - codesign that fails or times out refuses the app, never read as unsigned or unsandboxed; empty names fall back - a trial runs with the network (an updater can't touch the real app): a wrapper's offline page no longer hides its site - a main window that never loaded its own page is refused at launch, said as such (a dev server not running included); a hung attach is retried - one command helper; the macOS CI job installs ffmpeg * fix(runtime): close a pinned build's links and say a stalled attach - a pinned build's digest covers the whole bundle and refuses a link that leads outside it; unreadable, too large or stopped: refused, said - an attach that never answers is tried again, then said as the launch's own error (runs and trials alike); a trial throws Kiframe's own faults - an error page whose address can't be read is said as an unloaded page - the docs say what a trial reaches (the network, as a run) * refactor(runtime): one launch failure rule and an unforgeable build digest Redesign after three review rounds on the same two seams (reviewed): - every failure ending a launch is an ElectronLaunchError with its why, by one rule (stop, not started, quit first, said, anything else in a fixed phrase, its detail on stderr only when asked); the trial maps only its why - the pinned digest: the path resolved, each entry a JSON line under a version, links resolved inside the bundle step by step (a chain leaving it refused, one leading nowhere kept), sizes checked, sockets and devices refused, a stop honoured per chunk - the unused loopback network mode and a duplicate branch removed * fix(runtime): keep a launch's said reason and hold a trial to the picked build - what a launch already said (a site, a page never loaded) stays said if the app then quits; the attach hands its timeout to the one rule - a pinned build is held by its digest (signatureHolds compares it); a trial runs only the build that was picked - an inspection stops when asked (codesign and plutil killed) - bundle ids with underscores accepted (schema and inspection) - one quoting helper; the digest's link steps no longer block * fix(runtime): say a quit over a generic failure and an unread build as such - only what the app showed (a site, a page never loaded) beats a quit - a build that can't be checked (codesign timed out, unreadable) is said as unread, never as changed; buildHolds says which - the stop honoured in the middle of one large file - one bundle id rule, shared with the schema; one quoting helper, named for the two languages it serves * fix(runtime): keep a frame still loading as a desktop app opens What the main window embeds as it opens is read by a loading frame's src too: its first navigation uncommitted when the launch seals what's the app's own, it was stopped once it loaded (the macOS runner's flake). The single-instance test runs where its fix applies (macOS). * fix(runtime): never stop a desktop app's own devtools opened mid-run A dev build that opens DevTools after Kiframe attached showed them in a window that appears blank first (never filtered as DevTools), and the guard stopped it, failing the step (the macOS runner's failure, then reproduced every time with DevTools opened late). The guard leaves devtools: pages alone, as the window list and emulation already did. * test(runtime): read the app under its devtools, never click it on the runner On the macOS runner a click under DevTools opened mid-run waits for stability until its timeout (a covered window's animation frames throttled); the test's property is DevTools never counted as leaving the app. Clicking with DevTools open is checked on a real app (BACKLOG, PR 5). * fix(runtime): stop a desktop app's off-site frame at launch, blank a failed back - a frame still loading toward what isn't the app's when the launch seals is stopped then (never landing as a stop in a run's first step) - a main window whose back failed is blanked (never left showing the site; the run says to relaunch, as with no history), the failure's detail logged - CI logs desktop-app failures' detail (KIFRAME_ELECTRON_DEBUG)
* feat(desktop): add a desktop app to a project from the apps panel On macOS, "Add desktop app…": main shows the picker and inspects what was picked (the window holds a token, never a path or a site to allow), the card tries it confined, then it's added to project.json and approved. Approvals (desktop-apps.json in app data, written whole, one that doesn't read set aside) are per Mac and per project, and cover the app (its developer, or the exact build of an unsigned one) and what the project opens with it. Each desktop app's status shows in the panel, checked statically (nothing launches). Picking an app the project already names allows it here. Removing it drops this project's approval; a trial ends with its add, the project or a quit; leftovers are swept at the start. * fix(desktop): show what a named desktop app opens, and steady its add - an app a project already names: its card shows what the project opens with it (sites, arguments: the check runs with the sites), and an add after the project changed what it opens is refused; a site allowed in its check is written to the project and approved - one pick and one add at a time; a check of a card given up never shows on another; the add dropped with the window or a new project, a check refused once its project closed - statuses read together; the set-aside note said once; a status that can't be read said * fix(desktop): tie a desktop app's add to its project, checked after each wait - an add lives only while its project is the open one, checked at every step and after every wait (a project changed by any path ends it): an add given up while its app was looked at is never written - a finished add tells the open project's agent only; a site allowed in a check is kept as such (never guessed), one the project refuses is said - one app picker at a time (main); the panel never says an add given up, and picks nothing while one runs - the approvals' set-aside note given once by the store itself * refactor(desktop): own a desktop app's add whole, behind one gate Redesign after three review rounds on its lifetime and guards (reviewed): DesktopAdds runs the whole add (main's dialog included) through a host (the open project, its session, Kif's state, the picker, the approval), with one gate before every change and after every wait: a pick cancelled or a project changed while the user picked or the app was looked at, Kif started meanwhile, an add during a check: refused, nothing written. - the workspace tells listeners as it switches project (every add ends, its trial too); a window reloading drops its add - an add is done once written (an approval failing then: said, never added twice); only the project's rules are reworded (a file changed on disk says to reopen it) * fix(desktop): keep each copy of a desktop app apart and approve the build tried - approvals per copy of an app (its place): a release and a dev build of one bundle id never take each other's approval; a project uses one - the build that was tried is approved (one updated since: "updated") - a status says what needs no look at the app before looking (another project's, opens changed); a pick given up stops looking at its app - an approval is written to disk before it's kept; one check at a time in the panel; one rule for the same app, one "apps changed" in main * fix(desktop): keep each project's tried build and approvals that read back - the build a project tried is kept per project (another's approval of an update never changes its "updated" or "ready") - approvals validated before they're written, names cut and copies capped (the oldest let go): never a file set aside at the next start - an add given up stops its look at the app; the switch wiring tested as main has it; a check per card in the panel; neutral "not allowed in this project yet" wording * fix(desktop): compare a desktop app's version as kept, and free a cancelled add - a version compares as it's kept (a long one never "updated" forever) - an approval refused for its details is said in words; a long signing identifier kept; copies of one path merged - an add's look at the app keeps its own failure unless it was given up - a cancelled add frees the panel at once (per card, as a check) - the copy cap in one place; the unused copy version removed
…61) * feat(desktop): launch a project's desktop app only from its approval The launch main gives the agent (PR 4a, design reviewed): the app's copy approved for this project and what it opens, inspected once by its status. An update by the same developer is tried confined first, quietly (one trial per app and project), and that build approved compare-and-set (the same copy, developer and opens, the project still open). Anything else is refused for the user to settle in the Apps panel, never for the agent to retry. - a take of a desktop app keeps its build and what it opened (appBuild: shown, never in its key, never a reason to record again) - Kif starting ends an "Add desktop app…" check in progress - BACKLOG: #60's CI flakes; what an approval doesn't cover (files/) * fix(desktop): launch only the build that was tried, and only while approved - a launch re-reads the approval and the project after looking at the app: one removed or switched meanwhile is never launched - one quiet trial per build: an update landing during a trial is never launched untried; a stopped launch stops only its own wait - a quiet trial that didn't work says why (a site, a quit, a failure); an approval that can't be written is refused for the user - Kif starting ends checks in progress only (picked cards kept); a launch's stop reaches its look at the app; the take's build shape from its schema; status tests assert the app they return * fix(desktop): one quiet trial per app, ended with the launcher, status asked again - one quiet trial per app and project (launches meanwhile wait); after it a launch asks the app's status again and must find the build just approved (an update, a move, a replacement meanwhile: refused) - the quiet trial ends with the launcher (stopAll: a project switch, a quit), never with one launch's stop; a launch already stopped starts none - the trial approves against the launch's own project; a launch checks the approved build is the one it starts - an add's check ended by Kif starting or a switch is said in words * refactor(desktop): each launch tries its own update, stopped with it Simplified after a third review round on the shared quiet trial: each launch of an updated app tries it itself (stopped by its own stop, or by the launcher's as it was when the launch began: a project switch, a quit refused in words), then approves compare-and-set (two launches that both tried it approve the same build). No trial shared, kept, or joined. - an add's check ended (Kif started, given up) always said in words - a folder in files/ gone mid-copy said as a change; the slow-copy tests budget the app's start on the slower of two * test(runtime): accept an interrupt handled before the first step that met it On a busy CI runner the page loads slower than the late dialog's 300 ms, so the dialog is up as the steps begin and is handled before "wait" instead of "go" (seen twice). Handled once, off camera, before the click it would block, either way: the click works. * test(runtime): close the interrupt test again The previous commit's edit dropped the test's closing brace (lint and format failed in CI). * test: run the desktop-app suite's files one after another Since #59 the electron project holds two files and they ran in parallel: Electron launched while the other file cloned and re-signed it (spawn ETXTBSY on Linux for the first tests, a splash slower than its wait on macOS, both in #61's CI). The suite was meant to run alone: its files now do too.
* feat(studio): ground, check and record scenes in a desktop app The studio works in a desktop app as in a web app (PR 4b, design reviewed), through the host's launcher (from the app's approval, confined): - grounding keeps one live launch of the scene's app (another app's scene closes it first; one that quit is launched again, said); a check and a recording each run in a fresh launch, closed whole after; a take keeps the app's build - the app's pages are said by its guard (its own page or not), never its file path; looking at a ref and an approval's screenshot read the window's size from the page - a refusal the user settles is said for the agent to tell them, never to retry; no handover in a desktop app yet (a scene with one isn't saved) - main gives the agent its launcher, its quiet trials ended as the project switches or the app quits; the prompt says how desktop apps are worked * fix(studio): keep local paths and retries out of a desktop app's refusals - every launch failure is a refusal for the user, its local paths taken out (the app's place, the work area, the project, the home: whole, then any other); a tool error the user settles never invites a retry - the scene's app live before a step's refs are resolved (another app's page: a new snapshot asked); the live page kept between web apps' scenes (a sign-in by hand kept), closed only for a desktop app's - a launch stopped with the tool that asked for it; an app removed from the project closed; a relaunch said on a failing step too - no handover in a desktop app live either, nor through a preset; window sizes read with a bound * fix(studio): open the app asked for, and stop only its own caller - the live page being opened is that app's: a caller asking for another waits, then asks again (closed first if need be); a desktop launch is the studio's (ended as it closes), each caller stopped by its own stop - an app removed while it launched is never kept; a relaunch said only once it worked; a handover refused before anything launches; the handover check reads each step - local paths taken out by one runtime helper (known ones whole, file: URLs, a path up to where it plainly ends); only an approval's refusal is the user's to settle (a launch that timed out may be tried again) * refactor(studio): own each desktop launch, say no paths at the source Round 3 still found normal-use bugs in the shared live launch and the path regex, so both are redesigned: - a desktop launch is owned by the tool call that started it: its stop or the studio's close ends it (trial and spawn), it is never joined (a call while it goes on is refused, one after a stop waits it out), and an app removed or changed while it opened is never kept - launch errors name no local path by construction (OS errors by their code); the launcher passes only those and its own refusals, and replaces whole a message naming a folder it knows - every launch failure is the user's to settle: the agent tells them and never retries * fix(studio): keep a refusal for the user, one copy of a desktop app at a time - a refusal the user settles keeps its flag through the tool wrapper and the agent loop: the model is told to tell them, never to retry - a check or recording closes the live copy of a desktop app first, and a stop during its launch ends the call as a stop - a recording closes its launched app when its take folder can't be made - the live app is closed when the project lists another app under its name; concurrent closes share one close - a step is checked before any app is launched or closed for it - a desktop app's snapshot says its links by the guard (never a file path) - a copy error with no code is said without its message * fix(studio): check a step before any launch, close only the app being checked - a step's shape is checked before any app opens or closes for it (its refs stood in for by a plain locator) - a check or recording closes only its own app's live copy; another desktop app stays live - whether a launch in flight is a desktop one is kept when it starts (an app removed meanwhile is still waited out); one rule for a launch in flight - look says an app that quit and was launched again - a refusal is said in its own words (no "Error:", no doubled prefix); one needsUser helper; the live page's app and identity required * feat(studio): never guess which of several apps to open With several apps and none open, snapshot, look and a handover say to open the scene's app with a step first (a pause step does nothing else) instead of opening the first app. A project with one app still opens it. look's hidden failure reason goes to stderr under KIFRAME_ELECTRON_DEBUG. * ci(desktop): install chromium for the macos electron tests * test(studio): show the desktop fixture's window on linux A hidden window never paints under xvfb, so a look's screenshot timed out on Linux CI. Also backlogs the launch's splash race (seen on macOS CI).
…63) * feat(runtime): let a project's desktop app open terminals of its own A confined app could make no pty (terminal apps failed with posix_spawnp), and could print into every terminal the user had open. Now no app reaches a terminal unless its project says terminals: true, ticked on its add card with a warning and covered by its approval. Such a launch denies the user's ptys open at launch every way (read, write, stat, chmod, ioctl, revoke), allows new ones, and gives its shells a neutral prompt. A terminal the user opens while it runs stays exposed (stated in BACKLOG). * feat(scripts): add a desktop app to real-app runs --desktop name=/path/App.app adds it through the Apps panel (picked, checked confined, allowed), with --desktop-arg, --files and --desktop-terminals.
…64) (#65) * fix(runtime): give a desktop app's shells a temp folder of their own zsh writes here-docs under /tmp/zsh, which the confinement denies: VS Code's shell (read at its start, and its terminal's integration) said "can't create temp file" on camera. TMPPREFIX now points into the sandbox's temp for every desktop app. With KIFRAME_ELECTRON_DEBUG, the app's own output and exit code are written to Kiframe's stderr (how VS Code's silent quit was found). * fix(scripts): give real-app runs a short work folder An app's socket in its sandbox must fit macOS's 104 characters: VS Code quit at once under the run's own output folder. Runs use /tmp/kf-*, removed at exit; the Check outcomes "quit" and "the site" end the wait too. * docs(backlog): record the VS Code pass and its open items Socket path length (stated limit), shell temp files (fixed), grounding in an editor, and two items left for a reviewed design: a desktop take refused when the window is smaller than its viewport, and the guard's way back from a site.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The Electron targets epic (moved into v0 on 2026-10-08; design in OBJECT-MODEL §0.9 "Electron apps", reviewed by an architect agent). It stays a draft until the whole epic lands. Each step is its own reviewed PR into `epic/electron-targets`.
Stated limits for v0: