Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40fffdf839 | ||
|
|
b8984d30f0 | ||
|
|
48741e8cbb | ||
|
|
78fcdfff34 | ||
|
|
643b04b760 | ||
|
|
8fb27f98a2 | ||
|
|
406f67a1a0 | ||
|
|
b54a1a04b3 | ||
|
|
33604e2a51 | ||
|
|
08fe45814e | ||
|
|
fedd4ad673 | ||
|
|
88c0199845 | ||
|
|
22f73a3bad | ||
|
|
17f2706ba4 | ||
|
|
6b09ffb362 | ||
|
|
519199eba4 | ||
|
|
e28071f380 | ||
|
|
445f56881c | ||
|
|
a3d1852f45 |
@@ -12,27 +12,26 @@ These are requirements, not preferences. Changing one needs the owner's agreemen
|
||||
|
||||
A deploy is a release that isn't uploaded: the same notarized disk image, installed on the owner's Macs instead of published. When asked to deploy, do exactly this, from the worktree root:
|
||||
|
||||
1. Run `mac/deploy.sh`, always as the last action of the turn. It runs `mac/package.sh` (the build, both notarizations and the disk image, as for a release, into `mac/.build/release/<version>/`), builds the iPhone app with `mac/phone.sh` and uploads it to TestFlight (`docs/iphone.md`), and installs the image's app as `/Applications/Byre.app` with `mac/install.sh`. The service runs `/Applications/Byre.app/Contents/Helpers/byre`; within 5 seconds it sees that file replaced and exits, and launchd starts the new server and `byre-hands`. Everything is signed with Developer ID, so the macOS privacy grants (Screen Recording, Accessibility, Automation) survive.
|
||||
2. When you resume, confirm it came up: read the tail of `~/Library/Logs/byre.log`, and `GET /api/tailnet` at the loopback address (`byre check` prints it as `loopback`), whose `state` must be `Running`. Report the new build number first (`plutil -extract CFBundleVersion raw /Applications/Byre.app/Contents/Info.plist` prints it; the iPhone build uploaded to TestFlight has the same one), then what you deployed and what you checked.
|
||||
3. Once the service is confirmed healthy, update each client Mac whose app is running, through the client tools and without asking first: upload the disk image (`mac/.build/release/<version>/byre-<version>.dmg`) and `mac/install.sh`, quit the app, run `install.sh <dmg>`, and `open /Applications/Byre.app`. The app reads its server address from its iCloud-synced SwiftData store, so it reconnects to prod with no setup. Report each Mac's build number (`install.sh` prints it, then the commit).
|
||||
4. End the report with release notes for everything on main since the last release tag (`git describe --tags --abbrev=0` names it), written for a byre user in the "Releasing" shape (`## New` and `## Fixed`, each a bullet with a bold short name and what it does or what now works; no `## Install`). Read `git diff <tag>..HEAD`, not just the commit subjects, and leave out changes no user sees.
|
||||
1. Run `mac/deploy.sh`. It runs `mac/package.sh` (the build, both notarizations and the disk image, as for a release, into `mac/.build/release/<version>/`), builds the iPhone app with `mac/phone.sh` and uploads it to TestFlight (`docs/iphone.md`), and copies the disk image and `mac/install.sh` to `~/byre-deploy/byre.dmg` and `~/byre-deploy/install.sh`. That copy stays there for installing by hand on Macs that aren't connected. It installs nothing.
|
||||
2. Sort the client Macs `list_clients` shows (leaving out the iPhone) by whether they run a byre server of their own: one does when `launchctl print gui/$(id -u)/run.fc.byre` exits 0 there (`client_bash`). Update the app on each one that runs no server, one Mac at a time, through the client tools and without asking first: upload `~/byre-deploy/byre.dmg` and `~/byre-deploy/install.sh` to the same paths there (`client_upload`), then run `~/byre-deploy/install.sh ~/byre-deploy/byre.dmg` there through `client_bash`. It installs, prints the build number and the commit, then quits the app if it is running and reopens it on the new build, which ends the `client_bash` call as the client disconnects while the script carries on. Wait until `list_clients` shows that Mac back on the new build and commit, and report them. Every one of these must be done before any server restarts, since a protocol change can leave an old client unable to reach a new server to be updated.
|
||||
3. Only then update each client Mac that runs a server, the same way. Its server sees its executable replaced and restarts on the new build.
|
||||
4. Install on the Mac you run on with `mac/install.sh ~/byre-deploy/byre.dmg`, always as the last action of the turn. The service runs `/Applications/Byre.app/Contents/Helpers/byre`; within 5 seconds it sees that file replaced and exits, and launchd starts the new server and `byre-hands`. Everything is signed with Developer ID, so the macOS privacy grants (Screen Recording, Accessibility, Automation) survive. `install.sh` relaunches its app if it is running, and leaves it closed if not.
|
||||
5. When you resume, confirm it came up: read the tail of `~/Library/Logs/byre.log`, and `GET /api/tailnet` at the loopback address (`byre check` prints it as `loopback`), whose `state` must be `Running`. Report the new build number first (`plutil -extract CFBundleVersion raw /Applications/Byre.app/Contents/Info.plist` prints it; the iPhone build uploaded to TestFlight has the same one), then what you deployed and what you checked.
|
||||
6. End the report with release notes for everything on main since the last release tag (`git describe --tags --abbrev=0` names it), written for a byre user in the "Releasing" shape (`## New` and `## Fixed`, each a bullet with a bold short name and what it does or what now works; no `## Install`). Read `git diff <tag>..HEAD`, not just the commit subjects, and leave out changes no user sees.
|
||||
|
||||
Deploying means running `mac/deploy.sh` on iso. Never stand in for it by running `mac/package.sh` and `mac/install.sh` yourself, not even to build once for several Macs. Every other Mac gets the disk image `mac/deploy.sh` built, after iso is confirmed healthy (step 3).
|
||||
Deploying means building with `mac/deploy.sh` on the Mac you run on. Never stand in for it by running `mac/package.sh` yourself, not even to build once for several Macs. Every Mac, this one included, gets the disk image `mac/deploy.sh` left in `~/byre-deploy/`.
|
||||
|
||||
Besides iso, beast also runs its own byre server:
|
||||
|
||||
- Upload the disk image to beast as `~/byre-deploy/byre.dmg` and `mac/install.sh` as `~/byre-deploy/install.sh` (`client_upload`), then run `~/byre-deploy/run.sh` there detached, through `client_bash`: `perl -MPOSIX -e 'fork and exit; POSIX::setsid(); open STDIN,"</dev/null"; open STDOUT,">>$ENV{HOME}/byre-deploy/run.log"; open STDERR,">&STDOUT"; exec "/bin/zsh","$ENV{HOME}/byre-deploy/run.sh"'`. It quits the app, installs, and reopens it; the beast client drops for about a minute and `list_clients` shows it back on the new commit. Do the same for every other running client Mac, and relaunch iso's app only if it is running.
|
||||
- Check each server with the bearer token in `~/Library/Application Support/byre/loopback-token`: `GET https://byre-<host>.tailcecab.ts.net/api/tailnet` (`state` is `Running`) and `/api/admin/status` (report zombies, protocol errors, rate limits). The loopback address serves `/api/tailnet` but not `/api/admin/status`.
|
||||
Check each server (this Mac's and each client Mac's from step 3) with the bearer token in `~/Library/Application Support/byre/loopback-token`: `GET https://byre-<host>.tailcecab.ts.net/api/tailnet` (`state` is `Running`) and `/api/admin/status` (report zombies, protocol errors, rate limits). The loopback address serves `/api/tailnet` but not `/api/admin/status`.
|
||||
|
||||
What to expect:
|
||||
|
||||
- The notarizations take a minute or two each; `notarytool` prints "In Progress" until "Accepted". A rejection stops the script before anything is installed; `xcrun notarytool log <id> --keychain-profile byre-notary` says why.
|
||||
- You run inside byre, so the restart kills your own turn just after the install. That is expected, not a reason to skip it. You resume with a "byre restarted while you were mid-turn" message; do the checks then, and confirm the service started after your install (the log shows `executable replaced, exiting` just before it).
|
||||
- The restart can cut off the record of the last tool call, so you may resume with no memory of having deployed. Before concluding that another agent restarted byre, compare the service's start time (the last `listening` line in the log) with when you ran `mac/deploy.sh`: a restart a few minutes after it, once the notarizations are done, is most likely your install. If byre restarts without your install, another agent deployed its own branch, and the running build is theirs.
|
||||
- The restart can cut off the record of the last tool call, so you may resume with no memory of having deployed. Before concluding that another agent restarted byre, compare the service's start time (the last `listening` line in the log) with when you ran `mac/install.sh`: a restart within seconds of it is most likely your install. If byre restarts without your install, another agent deployed its own branch, and the running build is theirs.
|
||||
|
||||
## Releasing
|
||||
|
||||
A release is a deploy that is uploaded, and goes to every Mac through the app's updater. Make one only when the owner asks. It needs a `v<version>` tag on a commit of main, pushed by itself (`git push origin v<version>`, never moving or recreating a tag), and a clean checkout at that tag on iso, where `mac/release.sh <notes>` checks the tag, runs `mac/package.sh`, uploads the image, its `.sha256` and `appcast.xml` to `dl.byre.run`, and creates the release on g.fc.run with copies of the image and its `.sha256`, and the Markdown file `<notes>` as its notes. `docs/operations.md`, "Releases", has the details.
|
||||
A release is a deploy that is uploaded, and goes to every Mac through the app's updater. Make one only when the owner asks. It needs a `v<version>` tag on a commit of main, pushed by itself (`git push origin v<version>`, never moving or recreating a tag), and a clean checkout at that tag on the Mac byre runs on, where `mac/release.sh <notes>` checks the tag, runs `mac/package.sh`, uploads the image, its `.sha256` and `appcast.xml` to `dl.byre.run`, and creates the release on g.fc.run with copies of the image and its `.sha256`, and the Markdown file `<notes>` as its notes. `docs/operations.md`, "Releases", has the details.
|
||||
|
||||
Before running it, write the notes yourself from the diffs since the previous tag (`git describe --tags --abbrev=0 v<version>^` names it; read `git diff <previous>..v<version>`, not just the commit subjects). Write the file outside the checkout, so the tree stays clean. It is a changelog for people who use byre, in this shape:
|
||||
|
||||
@@ -52,7 +51,7 @@ When the owner tells the lead "deploy byre", or anything that means the same, th
|
||||
|
||||
- Exactly two panes, side by side, each exactly half the width, at every window size. Never three or more columns, never a sidebar, never a resizable or collapsible split.
|
||||
- No navbar. The panes fill the window, below only the identity banner when it shows.
|
||||
- The left pane has six tabs: Lead (the user's lead agent), Status, Notes (what the lead has noted about the user), Workflows (the saved workflows), Server (the byre service on this Mac) and Settings (the user's and this client's preferences), with the connection dot and the user's name at the end of its tab strip. Nothing else of the user's goes in it. The right pane always shows one task's conversation (its transcript, or a workflow stage task's run), or a project's dashboard when no task is selected; the lead sets the task, and the human can switch it (⌘K). The task's other views (its other tabs, and images and diagrams zoomed from its transcript) open only when the human opens them, in a sheet over the left pane: attached to the center line, in the task's color and named by it, leaving a strip of the left pane visible that closes it. Images and diagrams zoomed from the lead chat open the same way over the right pane. A new task view is a new tab that opens in that sheet, never a new column or region.
|
||||
- The left pane has six tabs: Lead (the user's lead agent), Status, Notes (what the lead has noted about the user), Workflows (the saved workflows), Server (the byre service on this Mac) and Settings (the user's and this client's preferences), with the connection dot and the user's name at the end of its tab strip. Nothing else of the user's goes in it. The right pane always shows one task's conversation (its transcript, or a workflow stage task's run), or a project's dashboard when no task is selected, or the list of every project's active tasks while the task bar's list button has it open; the lead sets the task, and the human can switch it (⌘K). The task's other views (its other tabs, and images and diagrams zoomed from its transcript) open only when the human opens them, in a sheet over the left pane: attached to the center line, in the task's color and named by it, leaving a strip of the left pane visible that closes it. Images and diagrams zoomed from the lead chat open the same way over the right pane. A new task view is a new tab that opens in that sheet, never a new column or region.
|
||||
- Overlays (the task switcher, confirmation dialogs) are allowed; they sit above the panes and don't change the layout.
|
||||
|
||||
## UI look
|
||||
|
||||
@@ -12,7 +12,7 @@ A server and macOS app for running many coding agents (Claude Code, Codex and An
|
||||
- **Merge.** One click: the agent merges the default branch in and resolves conflicts, then byre squashes, pushes and closes the task, with co-author trailers and a git note recording the task. Merge after review, and one merge per project at a time with a queue.
|
||||
- **Subtasks and teams.** Agents can spawn subtasks on their own branches and merge them back, or several agents can share one worktree and talk through a board.
|
||||
- **Workflows.** Named sequences of stages that run in order or in parallel, with each stage running as its own task.
|
||||
- **TODO.md.** A project's `TODO.md` items show on the project's dashboard (⌘P); start or discuss one in a click.
|
||||
- **TODO.md.** A project's `TODO.md` items show on the project's dashboard; start or discuss one in a click.
|
||||
- **Changes, Files and Terminal.** Per-task diff view with line comments sent to the agent, a read-only file browser, and shells in the worktree, including ones that start by running a saved command.
|
||||
- **Previews.** Agents expose dev servers, and the app opens each at a local `*.localhost` address forwarded over the tailnet.
|
||||
- **Screen and app control.** Agents can see and drive the Mac byre runs on (screen capture, accessibility, Orion, Chrome DevTools) and the Macs you have the app open on (shell, files, the same screen tools).
|
||||
|
||||
+33
-4
@@ -337,10 +337,13 @@ paths:
|
||||
parameters:
|
||||
- { name: projectId, in: path, required: true, schema: { type: string } }
|
||||
get:
|
||||
operationId: listTasks
|
||||
operationId: listActiveTasks
|
||||
description: >
|
||||
The project's active tasks. A closed task comes from getTask; task events name the task they change, which
|
||||
clients fetch again with getTask.
|
||||
responses:
|
||||
"200":
|
||||
description: The project's tasks, newest first.
|
||||
description: The project's active tasks, newest first.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
@@ -972,6 +975,32 @@ paths:
|
||||
"404": { $ref: "#/components/responses/Error" }
|
||||
"409": { $ref: "#/components/responses/Error" }
|
||||
|
||||
/api/sessions/{sessionId}/continue:
|
||||
parameters:
|
||||
- { name: sessionId, in: path, required: true, schema: { type: string } }
|
||||
post:
|
||||
operationId: continueSession
|
||||
description: >
|
||||
Creates a new task from a merged or discarded task's agent session: its branch and worktree start from the
|
||||
project's default branch, with an agent session that continues that session's conversation from its end (the
|
||||
vendor's own session fork) and gets the message as its first turn, as sendMessage does. The new task has
|
||||
forked_from and no forked_at. Refused while the session's task is active, for a session that isn't a task's
|
||||
implementer, and for an Antigravity session.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/MessageCreate" }
|
||||
responses:
|
||||
"201":
|
||||
description: The new task.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/Task" }
|
||||
"400": { $ref: "#/components/responses/Error" }
|
||||
"404": { $ref: "#/components/responses/Error" }
|
||||
"409": { $ref: "#/components/responses/Error" }
|
||||
|
||||
/api/tasks/{taskId}/sessions:
|
||||
parameters:
|
||||
- { name: taskId, in: path, required: true, schema: { type: string } }
|
||||
@@ -1367,7 +1396,7 @@ paths:
|
||||
/api/admin/permissions:
|
||||
post:
|
||||
operationId: checkPermissions
|
||||
description: Tests the service's macOS privacy grants by running byre-hands' check, which asks macOS for any grant not yet decided. Waits while macOS shows an Automation prompt, up to 50 seconds.
|
||||
description: Tests the service's macOS privacy grants by running byre-hands' check, which asks macOS for any grant not yet decided. Waits while macOS shows its prompts (Automation, and access to the Desktop, Documents, Downloads and iCloud Drive folders), up to 2 minutes.
|
||||
responses:
|
||||
"200":
|
||||
description: One result per grant.
|
||||
@@ -1539,7 +1568,7 @@ components:
|
||||
merge_commit: { type: string, description: "merged: the squash commit on the default branch; empty when there was nothing to merge." }
|
||||
todo_item: { type: string, description: "The title of the TODO.md item the task was started from; absent for other tasks." }
|
||||
forked_from: { type: string, description: "The task this one was forked from; absent for other tasks." }
|
||||
forked_at: { type: integer, description: "With forked_from: the n of the snapshot it was forked at." }
|
||||
forked_at: { type: integer, description: "With forked_from: the n of the snapshot it was forked at; absent when it continues a closed task from its end." }
|
||||
merge_queued_at: { type: string, format: date-time, description: "When Merge was requested while another task in the project was merging; absent once the merge starts or when none is held." }
|
||||
workflow: { type: string, description: "For a workflow run's task: the name of the workflow it froze at creation (getTaskWorkflow has its copy). A run's task has no agent, worktree or branch (branch, worktree_path and base_ref are empty); its stage tasks are its subtasks, and it closes once they all have, as merged when any of them merged. Absent for other tasks." }
|
||||
workflow_stage: { type: string, description: "For a stage task of a workflow run: the name of the stage it runs; parent_task_id is the run's task. Absent for other tasks." }
|
||||
|
||||
+1
-1
@@ -17,5 +17,5 @@ The simulator's window (Xcode's Device Hub) lists the app's own accessibility el
|
||||
## Platform facts
|
||||
|
||||
- CloudKit on iOS requires the `remote-notification` background mode in `Info.plist`, even though the app sets up no push of its own.
|
||||
- `ProcessInfo.hostName` is the Mac's name in the simulator and meaningless on a phone, so the phone's tailnet node and `Byre-Host` are named `iPhone`.
|
||||
- The phone's name is the one typed in Settings, kept in the phone's own `UserDefaults` (not synced through iCloud, since each phone has its own), and `iPhone` until one is typed. It goes in `Byre-Host` and the tailnet node's name, both fixed when the app starts. `UIDevice.current.name` returns just `iPhone` without Apple's `com.apple.developer.device-information.user-assigned-device-name` entitlement, which Apple refused because byre shows the name to other people and sends it to Tailscale and to the agents' model vendors.
|
||||
- SwiftTerm's iOS `TerminalViewDelegate` requires `requestOpenLink`, which its macOS one provides a default for.
|
||||
|
||||
+2
-1
@@ -26,7 +26,8 @@ Every build is signed for distribution outside the App Store: the team's Develop
|
||||
|
||||
## Startup and streams
|
||||
|
||||
- Startup waits until the app's tailnet node lists the server's node among its peers, not just until it is `Running`: a node starting from libtailscale's netmap cache reports `Running` with no peers before control sends the real netmap, and a dial then waits out the SOCKS proxy's 5 s and is refused, which `URLSession` reports as "bad URL".
|
||||
- Startup waits until the app's tailnet node lists the server's node among its peers, not just until it is `Running`: a node starting from libtailscale's netmap cache reports `Running` with no peers before control sends the real netmap, and a dial made then fails.
|
||||
- Every request reaches the tailnet through byre's own SOCKS5 proxy on a loopback port (`Proxy.swift`), which dials with `tailscale_dial`, not libtailscale's `tailscale_loopback`. iOS reclaims a suspended app's listening sockets, and tsnet's proxy stops for good on the first failed accept, leaving the phone unable to reach any server. byre's proxy binds again on the same port each time the app returns to the foreground.
|
||||
- Run byre on this Mac and the Server tab run `byre install` through the user's login shell, since `byre install` copies its `PATH` into the service and the app's own `PATH` from launchd finds none of `claude`, `codex`, `agy` or `npx`.
|
||||
- SwiftUI's window restoration is off, but the scene keeps the id `main` that earlier builds used: AppKit restores their saved window by that id, and SwiftUI then opens no launch window at all when the id names no scene.
|
||||
|
||||
|
||||
+4
-2
@@ -12,6 +12,8 @@ Add SQL migrations in `internal/store/migrations/` as `NNN-name.sql`. The number
|
||||
|
||||
byre runs nothing in a new worktree. Installing dependencies or copying untracked files such as `.env` is the project's own instruction in its AGENTS.md, carried out by the agent with its own tools.
|
||||
|
||||
Closing a task deletes its branch, worktree and snapshot refs, so a task continued from a closed one (Send in a merged or discarded task, which becomes Continue) starts from the default branch: a merged task's work is there, a discarded task's is gone. Only the agent's conversation carries over, through the vendor's own session files, which outlive the worktree.
|
||||
|
||||
## Running as a service
|
||||
|
||||
The service always runs from the app: `/Applications/Byre.app/Contents/Helpers/byre install`, which the app's Server tab and connection screen run. `byre install` takes `PATH` from the shell it is run in, since the service's `PATH` must find `claude`, `codex`, `agy`, `git`, `curl` and `npx`. When the service is already loaded, `install` hands `launchctl bootout` and `bootstrap` to a detached `sh` and exits: `bootout` kills every process in the service's process groups, which includes `byre install` itself when an agent byre runs is the one running it.
|
||||
@@ -36,7 +38,7 @@ Each preview gets its own port on byre's node rather than a path under byre's or
|
||||
|
||||
## Releases
|
||||
|
||||
A release is one notarized disk image, `byre-<version>.dmg`, holding `Byre.app` with the server, `byre-hands` and `byre-update` in `Contents/Helpers`. Images are served from `https://dl.byre.run/`; each release's notes are on `https://g.fc.run/ai/byre/releases`. `mac/package.sh` builds one, `mac/deploy.sh` installs it locally (see `AGENTS.md`, "Deploying"), and `mac/release.sh <notes>` publishes it from a clean checkout at a pushed `v<version>` tag. The build number (`CFBundleVersion`) of both the Mac and iPhone apps is the Unix time of the commit they were built from (`git log -1 --format=%ct`), so one deploy's apps share it; the apps send it in their `Byre-Build` header and the Status tab's client list shows it. A deployed build's `CFBundleVersion` is at least the last release's, so the updater never offers to go back to it.
|
||||
A release is one notarized disk image, `byre-<version>.dmg`, holding `Byre.app` with the server, `byre-hands` and `byre-update` in `Contents/Helpers`. Images are served from `https://dl.byre.run/`; each release's notes are on `https://g.fc.run/ai/byre/releases`. `mac/package.sh` builds one, `mac/deploy.sh` builds one for installing on the owner's Macs and keeps a copy in `~/byre-deploy/` (see `AGENTS.md`, "Deploying"), and `mac/release.sh <notes>` publishes it from a clean checkout at a pushed `v<version>` tag. The build number (`CFBundleVersion`) of both the Mac and iPhone apps is the Unix time of the commit they were built from (`git log -1 --format=%ct`), so one deploy's apps share it; the apps send it in their `Byre-Build` header and the Status tab's client list shows it. A deployed build's `CFBundleVersion` is at least the last release's, so the updater never offers to go back to it.
|
||||
|
||||
The appcast must never name an image that isn't available yet, and a published g.fc.run release must already have its attachments. If a failed release run leaves a draft on the releases page, delete it there before running again.
|
||||
|
||||
@@ -120,7 +122,7 @@ byre never signs with a key on the machine it runs on: every git command it runs
|
||||
- **Orion.** Orion ignores key events that carry only a Unicode string, so typing posts each ASCII character as its key code. Some apps list `confirm` but ignore it, Orion's address bar among them; send Return instead. `do JavaScript` needs Orion's Develop → "Allow JavaScript from Apple Events".
|
||||
- **Chrome.** Chrome refuses remote debugging on its default profile, so byre's Chrome is its own profile (`~/Library/Application Support/byre/chrome`), a separate Chrome beside any the human runs, and it keeps its logins: the human signs in there once. Chrome comes from `brew install --cask google-chrome`; macOS's "downloaded from the Internet" prompt on its first launch must be answered Open (or `osascript -e 'tell application "System Events" to tell process "CoreServicesUIAgent" to click button "Open" of window 1'`), and until it is, Chrome cannot accept debugging commands.
|
||||
- **Chrome's trust boundary.** Use Chrome's loopback debugging port with byre's separate persistent profile and Chrome's default browser-origin checks; never add `--remote-allow-origins=*`. Other local OS accounts can reach that debugger and control its logged-in sessions. This is an accepted risk on the personal Macs byre runs on, as is access to the app's local preview forwards; do not add account-isolating brokers for these listeners. Chrome must be launched with `--remote-debugging-port=0` so it records its port and browser WebSocket path in the profile’s `DevToolsActivePort` file. After switching from the pipe or fixed-port transport, quit the old byre-profile Chrome once so the next tool call can start it with that discovery file. The saved profile and logins stay in place.
|
||||
- **Permissions.** Agents' processes run as byre's descendants, so macOS privacy controls (TCC) hold byre responsible for them: the Screen Recording, Accessibility and Automation grants in System Settings → Privacy & Security are byre's, made by clicking at the Mac's screen, and each app scripted for the first time asks for its own Automation grant. Without Accessibility an untrusted process just sees no windows and no menu bar. Accessibility can list byre switched off; only its switch being on counts. TCC keys grants on the signing identifier and team, and every build is signed with Developer ID, so grants carry over from build to build. On a client Mac the grants belong to byre's app; `NSAppleEventsUsageDescription` in `mac/Info.plist` is what lets macOS ask for the Automation grant. `byre-hands check` prints the grants of the process that runs it; the Server and Settings tabs run it.
|
||||
- **Permissions.** Agents' processes run as byre's descendants, so macOS privacy controls (TCC) hold byre responsible for them: the Screen Recording, Accessibility and Automation grants in System Settings → Privacy & Security are byre's, made by clicking at the Mac's screen, and each app scripted for the first time asks for its own Automation grant. Without Accessibility an untrusted process just sees no windows and no menu bar. Accessibility can list byre switched off; only its switch being on counts. TCC keys grants on the signing identifier and team, and every build is signed with Developer ID, so grants carry over from build to build. On a client Mac the grants belong to byre's app; `NSAppleEventsUsageDescription` in `mac/Info.plist` is what lets macOS ask for the Automation grant. The Desktop, Documents, Downloads and iCloud Drive folders each ask for their own Files & Folders grant the first time a process byre is responsible for reads them. `byre-hands check` prints the grants of the process that runs it, listing each of those folders so their prompts come up during the check rather than mid-turn; the Server and Settings tabs run it.
|
||||
|
||||
## Terminals
|
||||
|
||||
|
||||
@@ -56,9 +56,26 @@ func checkOrion() -> [String: String] {
|
||||
}
|
||||
}
|
||||
|
||||
func checkFolder(_ name: String, _ path: String) -> [String: String] {
|
||||
let dir = (NSHomeDirectory() as NSString).appendingPathComponent(path)
|
||||
if !FileManager.default.fileExists(atPath: dir) {
|
||||
return permission(name, "unchecked", "\(dir) doesn't exist.", "FilesAndFolders")
|
||||
}
|
||||
do {
|
||||
let entries = try FileManager.default.contentsOfDirectory(atPath: dir)
|
||||
return permission(name, "granted", "Listed \(dir) (entries: \(entries.count)).", "FilesAndFolders")
|
||||
} catch {
|
||||
return permission(name, "missing", "Listing \(dir) failed (\(error.localizedDescription)). Switch \(name) on under byre in Files & Folders.", "FilesAndFolders")
|
||||
}
|
||||
}
|
||||
|
||||
func runCheck() {
|
||||
Task.detached {
|
||||
let rows = [await checkScreenRecording(), checkAccessibility(), checkOrion()]
|
||||
let rows = [
|
||||
await checkScreenRecording(), checkAccessibility(), checkOrion(),
|
||||
checkFolder("Desktop Folder", "Desktop"), checkFolder("Documents Folder", "Documents"),
|
||||
checkFolder("Downloads Folder", "Downloads"), checkFolder("iCloud Drive", "Library/Mobile Documents/com~apple~CloudDocs"),
|
||||
]
|
||||
let out = try! JSONSerialization.data(withJSONObject: rows, options: .withoutEscapingSlashes)
|
||||
FileHandle.standardOutput.write(out + Data("\n".utf8))
|
||||
exit(0)
|
||||
|
||||
+64
-2
@@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -19,6 +20,13 @@ func (m *Manager) Fork(ctx context.Context, src api.Task, project api.Project, n
|
||||
return t, err
|
||||
}
|
||||
|
||||
func forkOrigin(t api.Task, from string) string {
|
||||
if t.ForkedAt == nil {
|
||||
return "continued from " + from
|
||||
}
|
||||
return fmt.Sprintf("forked from %s at snapshot %d", from, *t.ForkedAt)
|
||||
}
|
||||
|
||||
func forkTitle(src api.Task, n int) string {
|
||||
return fmt.Sprintf("%s (fork of #%d)", src.Title, n)
|
||||
}
|
||||
@@ -172,7 +180,7 @@ func (m *Manager) fork(ctx context.Context, src api.Task, project api.Project, n
|
||||
return api.Task{}, "", fmt.Errorf("%w: snapshot %d of task %s has no recorded agent message at or before it to fork the conversation from", ErrConflict, n, src.Id)
|
||||
}
|
||||
|
||||
t, err := m.createTask(ctx, project, api.Task{
|
||||
return m.forkSession(ctx, project, api.Task{
|
||||
Title: title,
|
||||
MergeTitle: src.MergeTitle,
|
||||
CreatedBy: creator.Login,
|
||||
@@ -181,7 +189,61 @@ func (m *Manager) fork(ctx context.Context, src api.Task, project api.Project, n
|
||||
ForkedAt: &n,
|
||||
WorkflowStage: src.WorkflowStage,
|
||||
ParentTaskId: src.ParentTaskId,
|
||||
}, snap.CommitSha, creator.Login)
|
||||
}, snap.CommitSha, creator, srcSess, at)
|
||||
}
|
||||
|
||||
func (m *Manager) Continue(ctx context.Context, srcSess api.AgentSession, src api.Task, project api.Project, creator api.User, text, model, sendID, client string, images ...string) (api.Task, error) {
|
||||
if creator.GitName == "" || creator.GitEmail == "" {
|
||||
return api.Task{}, ErrNoGitIdentity
|
||||
}
|
||||
if src.Status == api.TaskStatusActive {
|
||||
return api.Task{}, fmt.Errorf("%w: task %s is still active", ErrConflict, src.Id)
|
||||
}
|
||||
if srcSess.Role != api.Implementer {
|
||||
return api.Task{}, fmt.Errorf("%w: session %s is a %s, not a task's agent", ErrConflict, srcSess.Id, srcSess.Role)
|
||||
}
|
||||
if srcSess.Vendor == api.Antigravity {
|
||||
return api.Task{}, fmt.Errorf("%w: session %s ran on Antigravity, and agy can't fork a conversation", ErrConflict, srcSess.Id)
|
||||
}
|
||||
|
||||
unlock := m.lockDelivery("continue:" + srcSess.Id)
|
||||
defer unlock()
|
||||
ctx = context.WithoutCancel(ctx)
|
||||
prior, sent, err := m.priorSend(ctx, sendID)
|
||||
if err != nil {
|
||||
return api.Task{}, err
|
||||
}
|
||||
if sent {
|
||||
return m.store.GetTask(ctx, prior.TaskId)
|
||||
}
|
||||
|
||||
events, err := m.store.TaskEvents(ctx, src.Id, 0, -1)
|
||||
if err != nil {
|
||||
return api.Task{}, err
|
||||
}
|
||||
at := forkPoint(events, srcSess.Id, math.MaxInt64)
|
||||
if at == "" {
|
||||
return api.Task{}, fmt.Errorf("%w: session %s has no recorded agent message to continue the conversation from", ErrConflict, srcSess.Id)
|
||||
}
|
||||
|
||||
t, sessionID, err := m.forkSession(ctx, project, api.Task{
|
||||
Title: src.Title + " (continued)",
|
||||
MergeTitle: src.MergeTitle,
|
||||
CreatedBy: creator.Login,
|
||||
ForkedFrom: &src.Id,
|
||||
}, project.DefaultBranch, creator, srcSess, at)
|
||||
if err != nil {
|
||||
return api.Task{}, err
|
||||
}
|
||||
_, _, err = m.Message(ctx, sessionID, creator.Login, text, model, sendID, client, images...)
|
||||
if err != nil {
|
||||
return api.Task{}, fmt.Errorf("task %s created, but its agent did not start: %w", t.Id, err)
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (m *Manager) forkSession(ctx context.Context, project api.Project, task api.Task, base string, creator api.User, srcSess api.AgentSession, at string) (api.Task, string, error) {
|
||||
t, err := m.createTask(ctx, project, task, base, creator.Login)
|
||||
if err != nil {
|
||||
return api.Task{}, "", err
|
||||
}
|
||||
|
||||
@@ -142,6 +142,18 @@ func subtaskGuidance(parent api.Task) string {
|
||||
}
|
||||
|
||||
func forkGuidance(task, src api.Task) string {
|
||||
if task.ForkedAt == nil {
|
||||
outcome := "was discarded, so none of its changes are in this worktree"
|
||||
switch {
|
||||
case src.MergeCommit != nil:
|
||||
outcome = fmt.Sprintf("was merged as commit %s, so its work is already on the default branch", *src.MergeCommit)
|
||||
case src.Status == api.TaskStatusMerged:
|
||||
outcome = "was merged with no changes"
|
||||
}
|
||||
return fmt.Sprintf("\n- This conversation continues byre task %q, which %s. It goes on in a new task of its own: branch %s, worktree %s, started from the default branch. "+
|
||||
"Everything before this point happened in %s, which no longer exists: from now on read and change files only in %s, and use paths under it.",
|
||||
src.Title, outcome, task.Branch, task.WorktreePath, src.WorktreePath, task.WorktreePath)
|
||||
}
|
||||
return fmt.Sprintf("\n- This conversation was forked from byre task %q at its snapshot %d, into a task of its own: branch %s, worktree %s, which held that snapshot's files when the fork was made. "+
|
||||
"Everything before the fork happened in %s, which belongs to the other task: from now on read and change files only in %s, never in the other worktree, and use paths under it.",
|
||||
src.Title, *task.ForkedAt, task.Branch, task.WorktreePath, src.WorktreePath, task.WorktreePath)
|
||||
|
||||
@@ -70,14 +70,14 @@ func (m *Manager) CheckPermissions(ctx context.Context) ([]api.Permission, error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(ctx, 50*time.Second)
|
||||
ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
|
||||
defer cancel()
|
||||
cmd := exec.CommandContext(ctx, bin, "check")
|
||||
stderr := strings.Builder{}
|
||||
cmd.Stderr = &stderr
|
||||
out, err := cmd.Output()
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
return nil, fmt.Errorf("byre-hands check didn't finish within 50s; answer macOS's prompt on the server's Mac and check again")
|
||||
return nil, fmt.Errorf("byre-hands check didn't finish within 2 minutes; answer macOS's prompts on the server's Mac and check again")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("byre-hands check: %w: %s", err, strings.TrimSpace(stderr.String()))
|
||||
|
||||
@@ -588,7 +588,7 @@ func (m *Manager) signalText(ctx context.Context, ev api.Event) (string, error)
|
||||
return "", err
|
||||
}
|
||||
if task.ForkedFrom != nil {
|
||||
return fmt.Sprintf("%s was forked by %s from %s at snapshot %d.", m.taskLabel(ctx, task.Id), ev.Actor, m.taskLabel(ctx, *task.ForkedFrom), *task.ForkedAt), nil
|
||||
return fmt.Sprintf("%s was %s by %s.", m.taskLabel(ctx, task.Id), forkOrigin(task, m.taskLabel(ctx, *task.ForkedFrom)), ev.Actor), nil
|
||||
}
|
||||
if task.WorkflowStage != nil {
|
||||
return fmt.Sprintf("%s was created to run stage %q of %s.", m.taskLabel(ctx, task.Id), *task.WorkflowStage, m.taskLabel(ctx, *task.ParentTaskId)), nil
|
||||
|
||||
@@ -518,15 +518,18 @@ func (m *Manager) leadTools(l *live) []mcp.Tool {
|
||||
}
|
||||
m.mu.Lock()
|
||||
working := m.paneFocus[user.Login].Focused || m.paneFocus[user.Login].Drafting
|
||||
humanMoved := m.humanMoved[user.Login]
|
||||
since := time.Since(humanMoved)
|
||||
move := m.moveHolds[user.Login]
|
||||
start := m.startHolds[user.Login]
|
||||
since := time.Since(move.at)
|
||||
moveHeld := since < moveHold
|
||||
startHeld := !moveHeld && time.Since(m.startedTodo[user.Login]) < startHold
|
||||
startHeld := !moveHeld && time.Since(start.at) < startHold
|
||||
if moveHeld {
|
||||
m.moveRefused[user.Login] = true
|
||||
move.refused = true
|
||||
m.moveHolds[user.Login] = move
|
||||
}
|
||||
if startHeld {
|
||||
m.startRefused[user.Login] = true
|
||||
start.refused = true
|
||||
m.startHolds[user.Login] = start
|
||||
}
|
||||
m.mu.Unlock()
|
||||
if moveHeld {
|
||||
@@ -596,13 +599,12 @@ func (m *Manager) SetFocus(ctx context.Context, login string, f api.Focus, actor
|
||||
m.mu.Lock()
|
||||
m.paneMoved[login] = at
|
||||
if actor == login {
|
||||
m.humanMoved[login] = at
|
||||
delete(m.moveRefused, login)
|
||||
m.moveHolds[login] = showHold{at: at}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
if actor == login {
|
||||
time.AfterFunc(moveHold, func() {
|
||||
err := m.endMoveHold(context.Background(), login, at)
|
||||
err := m.endShowHold(context.Background(), login, at, m.moveHolds, moveHoldEnded)
|
||||
if err != nil {
|
||||
slog.Error("end move hold", "login", login, "err", err)
|
||||
}
|
||||
@@ -702,21 +704,24 @@ const moveHold = 10 * time.Second
|
||||
|
||||
const moveHoldEnded = "The move hold is over: the human moved the right pane 10 seconds ago and hasn't moved it since, and a show of yours was refused in that time, so show works again under its other rules."
|
||||
|
||||
func (m *Manager) endMoveHold(ctx context.Context, login string, at time.Time) error {
|
||||
type showHold struct {
|
||||
at time.Time
|
||||
refused bool
|
||||
}
|
||||
|
||||
func (m *Manager) endShowHold(ctx context.Context, login string, at time.Time, holds map[string]showHold, text string) error {
|
||||
m.mu.Lock()
|
||||
current, ok := m.humanMoved[login]
|
||||
if !ok || !current.Equal(at) {
|
||||
hold := holds[login]
|
||||
if !hold.at.Equal(at) {
|
||||
m.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
delete(m.humanMoved, login)
|
||||
refused := m.moveRefused[login]
|
||||
delete(m.moveRefused, login)
|
||||
delete(holds, login)
|
||||
m.mu.Unlock()
|
||||
if !refused {
|
||||
if !hold.refused {
|
||||
return nil
|
||||
}
|
||||
return m.addLeadInput(ctx, store.LeadInput{Owner: login, Kind: store.LeadSignal, Actor: "byre", Text: moveHoldEnded})
|
||||
return m.addLeadInput(ctx, store.LeadInput{Owner: login, Kind: store.LeadSignal, Actor: "byre", Text: text})
|
||||
}
|
||||
|
||||
const startHold = 10 * time.Second
|
||||
@@ -726,38 +731,20 @@ const startHoldEnded = "The start hold is over: the human started a TODO item 10
|
||||
func (m *Manager) setStartedTodo(login string, started bool) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
delete(m.startRefused, login)
|
||||
if !started {
|
||||
delete(m.startedTodo, login)
|
||||
delete(m.startHolds, login)
|
||||
return
|
||||
}
|
||||
at := time.Now()
|
||||
m.startedTodo[login] = at
|
||||
m.startHolds[login] = showHold{at: at}
|
||||
time.AfterFunc(startHold, func() {
|
||||
err := m.endStartHold(context.Background(), login, at)
|
||||
err := m.endShowHold(context.Background(), login, at, m.startHolds, startHoldEnded)
|
||||
if err != nil {
|
||||
slog.Error("end start hold", "login", login, "err", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (m *Manager) endStartHold(ctx context.Context, login string, at time.Time) error {
|
||||
m.mu.Lock()
|
||||
current, ok := m.startedTodo[login]
|
||||
if !ok || !current.Equal(at) {
|
||||
m.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
delete(m.startedTodo, login)
|
||||
refused := m.startRefused[login]
|
||||
delete(m.startRefused, login)
|
||||
m.mu.Unlock()
|
||||
if !refused {
|
||||
return nil
|
||||
}
|
||||
return m.addLeadInput(ctx, store.LeadInput{Owner: login, Kind: store.LeadSignal, Actor: "byre", Text: startHoldEnded})
|
||||
}
|
||||
|
||||
func (m *Manager) modelOr(model string, l *live) string {
|
||||
if model != "" {
|
||||
return model
|
||||
@@ -907,7 +894,7 @@ func (m *Manager) listTasksText(ctx context.Context) (string, error) {
|
||||
fmt.Fprintf(b, "; from TODO item %q", *t.TodoItem)
|
||||
}
|
||||
if t.ForkedFrom != nil {
|
||||
fmt.Fprintf(b, "; forked from %s at snapshot %d", *t.ForkedFrom, *t.ForkedAt)
|
||||
fmt.Fprintf(b, "; %s", forkOrigin(t, *t.ForkedFrom))
|
||||
}
|
||||
if t.ParentTaskId != nil && t.WorkflowStage == nil {
|
||||
fmt.Fprintf(b, "; subtask of %s", *t.ParentTaskId)
|
||||
|
||||
@@ -58,10 +58,8 @@ type Manager struct {
|
||||
paneFocus map[string]api.PaneFocus
|
||||
paneReported map[string]time.Time
|
||||
paneMoved map[string]time.Time
|
||||
humanMoved map[string]time.Time
|
||||
moveRefused map[string]bool
|
||||
startedTodo map[string]time.Time
|
||||
startRefused map[string]bool
|
||||
moveHolds map[string]showHold
|
||||
startHolds map[string]showHold
|
||||
handoffModel map[string]string
|
||||
mergeTurns map[string]mergeTurn
|
||||
|
||||
@@ -163,10 +161,8 @@ func New(st *store.Store, worktreeRoot, leadRoot, handsRoot, codexHome, antigrav
|
||||
paneFocus: map[string]api.PaneFocus{},
|
||||
paneReported: map[string]time.Time{},
|
||||
paneMoved: map[string]time.Time{},
|
||||
humanMoved: map[string]time.Time{},
|
||||
moveRefused: map[string]bool{},
|
||||
startedTodo: map[string]time.Time{},
|
||||
startRefused: map[string]bool{},
|
||||
moveHolds: map[string]showHold{},
|
||||
startHolds: map[string]showHold{},
|
||||
handoffModel: map[string]string{},
|
||||
mergeTurns: map[string]mergeTurn{},
|
||||
signals: make(chan []api.Event, 1024),
|
||||
|
||||
@@ -34,8 +34,8 @@ func (m *Manager) mergeNote(ctx context.Context, task api.Task, login string) (s
|
||||
if task.TodoItem != nil {
|
||||
fmt.Fprintf(b, "Started from the TODO item %q.\n", *task.TodoItem)
|
||||
}
|
||||
if task.ForkedFrom != nil && task.ForkedAt != nil {
|
||||
fmt.Fprintf(b, "Forked from task %s at snapshot %d.\n", *task.ForkedFrom, *task.ForkedAt)
|
||||
if task.ForkedFrom != nil {
|
||||
fmt.Fprintf(b, "This task was %s.\n", forkOrigin(task, "task "+*task.ForkedFrom))
|
||||
}
|
||||
fmt.Fprintf(b, "Full transcripts: the events of task %s in byre (GET /api/events?task_id=%s).\n", task.Id, task.Id)
|
||||
|
||||
|
||||
+12
-20
@@ -9,7 +9,6 @@ import (
|
||||
"time"
|
||||
|
||||
"g.fc.run/ai/byre/internal/api"
|
||||
"g.fc.run/ai/byre/internal/ids"
|
||||
"g.fc.run/ai/byre/internal/mcp"
|
||||
"g.fc.run/ai/byre/internal/schedule"
|
||||
"g.fc.run/ai/byre/internal/store"
|
||||
@@ -45,29 +44,22 @@ func (m *Manager) SaveSchedule(ctx context.Context, projectID, scheduleID string
|
||||
sc.Id = existing.Id
|
||||
sc.ProjectId = existing.ProjectId
|
||||
sc.Owner = existing.Owner
|
||||
sc, err = m.store.UpdateSchedule(ctx, sc, actor)
|
||||
} else {
|
||||
user, err := m.store.GetUser(ctx, login)
|
||||
if err != nil {
|
||||
return api.Schedule{}, err
|
||||
}
|
||||
m.poke()
|
||||
return sc, nil
|
||||
if user.GitName == "" || user.GitEmail == "" {
|
||||
return api.Schedule{}, ErrNoGitIdentity
|
||||
}
|
||||
project, err := m.store.GetProject(ctx, projectID)
|
||||
if err != nil {
|
||||
return api.Schedule{}, err
|
||||
}
|
||||
sc.ProjectId = project.Id
|
||||
sc.Owner = user.Login
|
||||
}
|
||||
|
||||
user, err := m.store.GetUser(ctx, login)
|
||||
if err != nil {
|
||||
return api.Schedule{}, err
|
||||
}
|
||||
if user.GitName == "" || user.GitEmail == "" {
|
||||
return api.Schedule{}, ErrNoGitIdentity
|
||||
}
|
||||
project, err := m.store.GetProject(ctx, projectID)
|
||||
if err != nil {
|
||||
return api.Schedule{}, err
|
||||
}
|
||||
sc.Id = ids.New(ids.Schedule)
|
||||
sc.ProjectId = project.Id
|
||||
sc.Owner = user.Login
|
||||
sc, err = m.store.CreateSchedule(ctx, sc, actor)
|
||||
sc, err = m.store.SaveSchedule(ctx, sc, actor)
|
||||
if err != nil {
|
||||
return api.Schedule{}, err
|
||||
}
|
||||
|
||||
@@ -119,6 +119,17 @@ func (m *Manager) createTask(ctx context.Context, project api.Project, t api.Tas
|
||||
t.Status = api.TaskStatusActive
|
||||
t.CreatedAt = time.Now().UTC()
|
||||
|
||||
if base == project.DefaultBranch {
|
||||
creator, err := m.store.GetUser(ctx, t.CreatedBy)
|
||||
if err != nil {
|
||||
return api.Task{}, err
|
||||
}
|
||||
err = git.EnsureBranchCommit(ctx, project.RepoPath, base, git.IdentityEnv(creator.GitName, creator.GitEmail))
|
||||
if err != nil {
|
||||
return api.Task{}, err
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
t.BaseRef, err = git.AddWorktree(ctx, project.RepoPath, t.WorktreePath, t.Branch, base)
|
||||
if err != nil {
|
||||
|
||||
+391
-353
@@ -1526,7 +1526,7 @@ type Task struct {
|
||||
// Discarding Whether a Discard was started and the task isn't closed yet: its summary turn is waiting for the running turn, running, or done and the task is being removed.
|
||||
Discarding bool `json:"discarding"`
|
||||
|
||||
// ForkedAt With forked_from: the n of the snapshot it was forked at.
|
||||
// ForkedAt With forked_from: the n of the snapshot it was forked at; absent when it continues a closed task from its end.
|
||||
ForkedAt *int `json:"forked_at,omitempty"`
|
||||
|
||||
// ForkedFrom The task this one was forked from; absent for other tasks.
|
||||
@@ -2014,6 +2014,9 @@ type UpdateScheduleJSONRequestBody = ScheduleSave
|
||||
// CompactSessionJSONRequestBody defines body for CompactSession for application/json ContentType.
|
||||
type CompactSessionJSONRequestBody = CompactCreate
|
||||
|
||||
// ContinueSessionJSONRequestBody defines body for ContinueSession for application/json ContentType.
|
||||
type ContinueSessionJSONRequestBody = MessageCreate
|
||||
|
||||
// SendMessageJSONRequestBody defines body for SendMessage for application/json ContentType.
|
||||
type SendMessageJSONRequestBody = MessageCreate
|
||||
|
||||
@@ -2165,7 +2168,7 @@ type ServerInterface interface {
|
||||
CreateSchedule(w http.ResponseWriter, r *http.Request, projectId string)
|
||||
|
||||
// (GET /api/projects/{projectId}/tasks)
|
||||
ListTasks(w http.ResponseWriter, r *http.Request, projectId string)
|
||||
ListActiveTasks(w http.ResponseWriter, r *http.Request, projectId string)
|
||||
|
||||
// (POST /api/projects/{projectId}/tasks)
|
||||
CreateTask(w http.ResponseWriter, r *http.Request, projectId string)
|
||||
@@ -2191,6 +2194,9 @@ type ServerInterface interface {
|
||||
// (POST /api/sessions/{sessionId}/compact)
|
||||
CompactSession(w http.ResponseWriter, r *http.Request, sessionId string)
|
||||
|
||||
// (POST /api/sessions/{sessionId}/continue)
|
||||
ContinueSession(w http.ResponseWriter, r *http.Request, sessionId string)
|
||||
|
||||
// (POST /api/sessions/{sessionId}/interrupt)
|
||||
InterruptSession(w http.ResponseWriter, r *http.Request, sessionId string)
|
||||
|
||||
@@ -3193,8 +3199,8 @@ func (siw *ServerInterfaceWrapper) CreateSchedule(w http.ResponseWriter, r *http
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// ListTasks operation middleware
|
||||
func (siw *ServerInterfaceWrapper) ListTasks(w http.ResponseWriter, r *http.Request) {
|
||||
// ListActiveTasks operation middleware
|
||||
func (siw *ServerInterfaceWrapper) ListActiveTasks(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var err error
|
||||
_ = err
|
||||
@@ -3209,7 +3215,7 @@ func (siw *ServerInterfaceWrapper) ListTasks(w http.ResponseWriter, r *http.Requ
|
||||
}
|
||||
|
||||
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.ListTasks(w, r, projectId)
|
||||
siw.Handler.ListActiveTasks(w, r, projectId)
|
||||
}))
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
@@ -3436,6 +3442,32 @@ func (siw *ServerInterfaceWrapper) CompactSession(w http.ResponseWriter, r *http
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// ContinueSession operation middleware
|
||||
func (siw *ServerInterfaceWrapper) ContinueSession(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var err error
|
||||
_ = err
|
||||
|
||||
// ------------- Path parameter "sessionId" -------------
|
||||
var sessionId string
|
||||
|
||||
err = runtime.BindStyledParameterWithOptions("simple", "sessionId", r.PathValue("sessionId"), &sessionId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: "", ValueIsUnescaped: true})
|
||||
if err != nil {
|
||||
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "sessionId", Err: err})
|
||||
return
|
||||
}
|
||||
|
||||
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.ContinueSession(w, r, sessionId)
|
||||
}))
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// InterruptSession operation middleware
|
||||
func (siw *ServerInterfaceWrapper) InterruptSession(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -4724,7 +4756,7 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H
|
||||
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/projects/{projectId}", wrapper.GetProject)
|
||||
m.HandleFunc(http.MethodPut+" "+options.BaseURL+"/api/projects/{projectId}/review-workflow", wrapper.SetReviewWorkflow)
|
||||
m.HandleFunc(http.MethodPut+" "+options.BaseURL+"/api/projects/{projectId}/audit-workflow", wrapper.SetAuditWorkflow)
|
||||
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/projects/{projectId}/tasks", wrapper.ListTasks)
|
||||
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/projects/{projectId}/tasks", wrapper.ListActiveTasks)
|
||||
m.HandleFunc(http.MethodPost+" "+options.BaseURL+"/api/projects/{projectId}/tasks", wrapper.CreateTask)
|
||||
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/projects/{projectId}/schedules", wrapper.ListSchedules)
|
||||
m.HandleFunc(http.MethodPost+" "+options.BaseURL+"/api/projects/{projectId}/schedules", wrapper.CreateSchedule)
|
||||
@@ -4758,6 +4790,7 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H
|
||||
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/tasks/{taskId}/reviews", wrapper.ListReviews)
|
||||
m.HandleFunc(http.MethodPut+" "+options.BaseURL+"/api/reviews/{reviewId}/findings/{index}", wrapper.SetFindingState)
|
||||
m.HandleFunc(http.MethodPost+" "+options.BaseURL+"/api/tasks/{taskId}/snapshots/{n}/fork", wrapper.ForkSnapshot)
|
||||
m.HandleFunc(http.MethodPost+" "+options.BaseURL+"/api/sessions/{sessionId}/continue", wrapper.ContinueSession)
|
||||
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/tasks/{taskId}/sessions", wrapper.ListSessions)
|
||||
m.HandleFunc(http.MethodPost+" "+options.BaseURL+"/api/tasks/{taskId}/sessions", wrapper.CreateSession)
|
||||
m.HandleFunc(http.MethodDelete+" "+options.BaseURL+"/api/tasks/{taskId}/queue/{inputId}", wrapper.WithdrawMessage)
|
||||
@@ -4796,354 +4829,359 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H
|
||||
// const string: with thousands of chunks the chained `+` fold is several
|
||||
// times slower for the Go compiler than parsing a slice literal.
|
||||
var swaggerSpec = []string{
|
||||
"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",
|
||||
"iEyh7ecrYTUicRMmooABvgNm8qtRUn/48P8PAA==",
|
||||
}
|
||||
|
||||
// decodeSpec returns the embedded OpenAPI spec as raw JSON bytes,
|
||||
|
||||
@@ -19,8 +19,6 @@ var clientMethods = []string{
|
||||
"thread/start",
|
||||
"thread/resume",
|
||||
"thread/fork",
|
||||
"thread/read",
|
||||
"thread/compact/start",
|
||||
"turn/start",
|
||||
"turn/steer",
|
||||
"turn/interrupt",
|
||||
@@ -269,22 +267,11 @@ func (g *gen) generate(version string) []byte {
|
||||
}
|
||||
h.WriteString(")\n\n")
|
||||
|
||||
writeTable := func(name string, ms []method, field func(method) string) {
|
||||
fmt.Fprintf(&h, "var %s = map[string]func() any{\n", name)
|
||||
for _, m := range ms {
|
||||
t := field(m)
|
||||
if t == "" {
|
||||
fmt.Fprintf(&h, "Method%s: nil,\n", pascal(m.name))
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(&h, "Method%s: func() any { return &%s{} },\n", pascal(m.name), t)
|
||||
}
|
||||
h.WriteString("}\n\n")
|
||||
h.WriteString("var notificationMethods = map[string]struct{}{\n")
|
||||
for _, m := range notifications {
|
||||
fmt.Fprintf(&h, "Method%s: {},\n", pascal(m.name))
|
||||
}
|
||||
writeTable("clientResults", client, func(m method) string { return m.response })
|
||||
writeTable("notificationParams", notifications, func(m method) string { return m.params })
|
||||
writeTable("serverRequestParams", requests, func(m method) string { return m.params })
|
||||
writeTable("serverRequestResponses", requests, func(m method) string { return m.response })
|
||||
h.WriteString("}\n\n")
|
||||
|
||||
h.Write(g.consts.Bytes())
|
||||
h.Write(g.out.Bytes())
|
||||
|
||||
+101
-160
@@ -11,8 +11,6 @@ const (
|
||||
MethodThreadStart = "thread/start"
|
||||
MethodThreadResume = "thread/resume"
|
||||
MethodThreadFork = "thread/fork"
|
||||
MethodThreadCompactStart = "thread/compact/start"
|
||||
MethodThreadRead = "thread/read"
|
||||
MethodTurnStart = "turn/start"
|
||||
MethodTurnSteer = "turn/steer"
|
||||
MethodTurnInterrupt = "turn/interrupt"
|
||||
@@ -115,133 +113,92 @@ const (
|
||||
MethodExecCommandApproval = "execCommandApproval"
|
||||
)
|
||||
|
||||
var clientResults = map[string]func() any{
|
||||
MethodInitialize: func() any { return &InitializeResponse{} },
|
||||
MethodThreadStart: func() any { return &ThreadStartResponse{} },
|
||||
MethodThreadResume: func() any { return &ThreadResumeResponse{} },
|
||||
MethodThreadFork: func() any { return &ThreadForkResponse{} },
|
||||
MethodThreadCompactStart: func() any { return &ThreadCompactStartResponse{} },
|
||||
MethodThreadRead: func() any { return &ThreadReadResponse{} },
|
||||
MethodTurnStart: func() any { return &TurnStartResponse{} },
|
||||
MethodTurnSteer: func() any { return &TurnSteerResponse{} },
|
||||
MethodTurnInterrupt: func() any { return &TurnInterruptResponse{} },
|
||||
MethodAccountRateLimitsRead: func() any { return &GetAccountRateLimitsResponse{} },
|
||||
}
|
||||
|
||||
var notificationParams = map[string]func() any{
|
||||
MethodError: func() any { return &ErrorNotification{} },
|
||||
MethodThreadStarted: func() any { return &ThreadStartedNotification{} },
|
||||
MethodThreadStatusChanged: func() any { return &ThreadStatusChangedNotification{} },
|
||||
MethodThreadReadStateChanged: func() any { return &ThreadReadStateChangedNotification{} },
|
||||
MethodThreadArchived: func() any { return &ThreadArchivedNotification{} },
|
||||
MethodThreadDeleted: func() any { return &ThreadDeletedNotification{} },
|
||||
MethodThreadUnarchived: func() any { return &ThreadUnarchivedNotification{} },
|
||||
MethodThreadClosed: func() any { return &ThreadClosedNotification{} },
|
||||
MethodThreadReverted: func() any { return &ThreadRevertedNotification{} },
|
||||
MethodSkillsChanged: func() any { return &SkillsChangedNotification{} },
|
||||
MethodThreadNameUpdated: func() any { return &ThreadNameUpdatedNotification{} },
|
||||
MethodThreadAttachmentUpdated: func() any { return &ThreadAttachmentUpdatedNotification{} },
|
||||
MethodThreadGoalUpdated: func() any { return &ThreadGoalUpdatedNotification{} },
|
||||
MethodThreadPredictionUpdated: func() any { return &ThreadPredictionUpdatedNotification{} },
|
||||
MethodThreadGoalCleared: func() any { return &ThreadGoalClearedNotification{} },
|
||||
MethodThreadQueueChanged: func() any { return &ThreadQueueChangedNotification{} },
|
||||
MethodProjectChanged: func() any { return &ProjectChangedNotification{} },
|
||||
MethodThreadProjectUpdated: func() any { return &ThreadProjectUpdatedNotification{} },
|
||||
MethodThreadEnvironmentConnected: func() any { return &EnvironmentConnectionNotification{} },
|
||||
MethodThreadEnvironmentDisconnected: func() any { return &EnvironmentConnectionNotification{} },
|
||||
MethodThreadSettingsUpdated: func() any { return &ThreadSettingsUpdatedNotification{} },
|
||||
MethodThreadTokenUsageUpdated: func() any { return &ThreadTokenUsageUpdatedNotification{} },
|
||||
MethodTurnStarted: func() any { return &TurnStartedNotification{} },
|
||||
MethodHookStarted: func() any { return &HookStartedNotification{} },
|
||||
MethodTurnCompleted: func() any { return &TurnCompletedNotification{} },
|
||||
MethodHookCompleted: func() any { return &HookCompletedNotification{} },
|
||||
MethodTurnDiffUpdated: func() any { return &TurnDiffUpdatedNotification{} },
|
||||
MethodTurnPlanUpdated: func() any { return &TurnPlanUpdatedNotification{} },
|
||||
MethodItemStarted: func() any { return &ItemStartedNotification{} },
|
||||
MethodItemAutoApprovalReviewStarted: func() any { return &ItemGuardianApprovalReviewStartedNotification{} },
|
||||
MethodItemAutoApprovalReviewCompleted: func() any { return &ItemGuardianApprovalReviewCompletedNotification{} },
|
||||
MethodAutoApprovalReviewStrictReviewRequired: func() any { return &StrictReviewRequiredNotification{} },
|
||||
MethodItemCompleted: func() any { return &ItemCompletedNotification{} },
|
||||
MethodItemAgentMessageDelta: func() any { return &AgentMessageDeltaNotification{} },
|
||||
MethodItemPlanDelta: func() any { return &PlanDeltaNotification{} },
|
||||
MethodCommandExecOutputDelta: func() any { return &CommandExecOutputDeltaNotification{} },
|
||||
MethodProcessOutputDelta: func() any { return &ProcessOutputDeltaNotification{} },
|
||||
MethodProcessExited: func() any { return &ProcessExitedNotification{} },
|
||||
MethodItemCommandExecutionOutputDelta: func() any { return &CommandExecutionOutputDeltaNotification{} },
|
||||
MethodItemCommandExecutionTerminalInteraction: func() any { return &TerminalInteractionNotification{} },
|
||||
MethodItemFileChangeOutputDelta: func() any { return &FileChangeOutputDeltaNotification{} },
|
||||
MethodItemFileChangePatchUpdated: func() any { return &FileChangePatchUpdatedNotification{} },
|
||||
MethodServerRequestResolved: func() any { return &ServerRequestResolvedNotification{} },
|
||||
MethodItemMcpToolCallProgress: func() any { return &McpToolCallProgressNotification{} },
|
||||
MethodMcpServerOauthLoginCompleted: func() any { return &McpServerOauthLoginCompletedNotification{} },
|
||||
MethodMcpServerStartupStatusUpdated: func() any { return &McpServerStatusUpdatedNotification{} },
|
||||
MethodMcpServerEventStreamNotification: func() any { return &McpServerEventStreamNotification{} },
|
||||
MethodAccountUpdated: func() any { return &AccountUpdatedNotification{} },
|
||||
MethodAccountGatewayOAuthChanged: func() any { return &GatewayOAuthChangedNotification{} },
|
||||
MethodAccountRateLimitsUpdated: func() any { return &AccountRateLimitsUpdatedNotification{} },
|
||||
MethodAppListUpdated: func() any { return &AppListUpdatedNotification{} },
|
||||
MethodRemoteControlStatusChanged: func() any { return &RemoteControlStatusChangedNotification{} },
|
||||
MethodExternalAgentConfigImportProgress: func() any { return &ExternalAgentConfigImportProgressNotification{} },
|
||||
MethodExternalAgentConfigImportCompleted: func() any { return &ExternalAgentConfigImportCompletedNotification{} },
|
||||
MethodFsChanged: func() any { return &FsChangedNotification{} },
|
||||
MethodItemReasoningSummaryTextDelta: func() any { return &ReasoningSummaryTextDeltaNotification{} },
|
||||
MethodItemReasoningSummaryPartAdded: func() any { return &ReasoningSummaryPartAddedNotification{} },
|
||||
MethodItemReasoningTextDelta: func() any { return &ReasoningTextDeltaNotification{} },
|
||||
MethodThreadCompacted: func() any { return &ContextCompactedNotification{} },
|
||||
MethodModelRerouted: func() any { return &ModelReroutedNotification{} },
|
||||
MethodModelVerification: func() any { return &ModelVerificationNotification{} },
|
||||
MethodModelProviderAuthRecoveryStarted: func() any { return &AuthRecoveryNotification{} },
|
||||
MethodModelProviderAuthRecoveryCompleted: func() any { return &AuthRecoveryNotification{} },
|
||||
MethodTurnModerationMetadata: func() any { return &TurnModerationMetadataNotification{} },
|
||||
MethodModelSafetyBufferingUpdated: func() any { return &ModelSafetyBufferingUpdatedNotification{} },
|
||||
MethodWarning: func() any { return &WarningNotification{} },
|
||||
MethodGuardianWarning: func() any { return &GuardianWarningNotification{} },
|
||||
MethodDeprecationNotice: func() any { return &DeprecationNoticeNotification{} },
|
||||
MethodConfigWarning: func() any { return &ConfigWarningNotification{} },
|
||||
MethodFuzzyFileSearchSessionUpdated: func() any { return &FuzzyFileSearchSessionUpdatedNotification{} },
|
||||
MethodFuzzyFileSearchSessionCompleted: func() any { return &FuzzyFileSearchSessionCompletedNotification{} },
|
||||
MethodThreadRealtimeStarted: func() any { return &ThreadRealtimeStartedNotification{} },
|
||||
MethodThreadRealtimeItemAdded: func() any { return &ThreadRealtimeItemAddedNotification{} },
|
||||
MethodThreadRealtimeItemStarted: func() any { return &ThreadRealtimeItemStartedNotification{} },
|
||||
MethodThreadRealtimeItemTranscriptDelta: func() any { return &ThreadRealtimeItemTranscriptDeltaNotification{} },
|
||||
MethodThreadRealtimeItemCompleted: func() any { return &ThreadRealtimeItemCompletedNotification{} },
|
||||
MethodThreadRealtimeTranscriptDelta: func() any { return &ThreadRealtimeTranscriptDeltaNotification{} },
|
||||
MethodThreadRealtimeTranscriptDone: func() any { return &ThreadRealtimeTranscriptDoneNotification{} },
|
||||
MethodThreadRealtimeOutputAudioDelta: func() any { return &ThreadRealtimeOutputAudioDeltaNotification{} },
|
||||
MethodThreadRealtimeSdp: func() any { return &ThreadRealtimeSdpNotification{} },
|
||||
MethodThreadRealtimeError: func() any { return &ThreadRealtimeErrorNotification{} },
|
||||
MethodThreadRealtimeClosed: func() any { return &ThreadRealtimeClosedNotification{} },
|
||||
MethodWindowsWorldWritableWarning: func() any { return &WindowsWorldWritableWarningNotification{} },
|
||||
MethodWindowsSandboxSetupCompleted: func() any { return &WindowsSandboxSetupCompletedNotification{} },
|
||||
MethodAccountLoginCompleted: func() any { return &AccountLoginCompletedNotification{} },
|
||||
}
|
||||
|
||||
var serverRequestParams = map[string]func() any{
|
||||
MethodItemCommandExecutionRequestApproval: func() any { return &CommandExecutionRequestApprovalParams{} },
|
||||
MethodItemFileChangeRequestApproval: func() any { return &FileChangeRequestApprovalParams{} },
|
||||
MethodItemToolRequestUserInput: func() any { return &ToolRequestUserInputParams{} },
|
||||
MethodMcpServerElicitationRequest: func() any { return &McpServerElicitationRequestParams{} },
|
||||
MethodItemPermissionsRequestApproval: func() any { return &PermissionsRequestApprovalParams{} },
|
||||
MethodItemToolCall: func() any { return &DynamicToolCallParams{} },
|
||||
MethodAccountChatgptAuthTokensRefresh: func() any { return &ChatgptAuthTokensRefreshParams{} },
|
||||
MethodAttestationGenerate: func() any { return &AttestationGenerateParams{} },
|
||||
MethodCurrentTimeRead: func() any { return &CurrentTimeReadParams{} },
|
||||
MethodApplyPatchApproval: func() any { return &ApplyPatchApprovalParams{} },
|
||||
MethodExecCommandApproval: func() any { return &ExecCommandApprovalParams{} },
|
||||
}
|
||||
|
||||
var serverRequestResponses = map[string]func() any{
|
||||
MethodItemCommandExecutionRequestApproval: func() any { return &CommandExecutionRequestApprovalResponse{} },
|
||||
MethodItemFileChangeRequestApproval: func() any { return &FileChangeRequestApprovalResponse{} },
|
||||
MethodItemToolRequestUserInput: func() any { return &ToolRequestUserInputResponse{} },
|
||||
MethodMcpServerElicitationRequest: func() any { return &McpServerElicitationRequestResponse{} },
|
||||
MethodItemPermissionsRequestApproval: func() any { return &PermissionsRequestApprovalResponse{} },
|
||||
MethodItemToolCall: func() any { return &DynamicToolCallResponse{} },
|
||||
MethodAccountChatgptAuthTokensRefresh: func() any { return &ChatgptAuthTokensRefreshResponse{} },
|
||||
MethodAttestationGenerate: func() any { return &AttestationGenerateResponse{} },
|
||||
MethodCurrentTimeRead: func() any { return &CurrentTimeReadResponse{} },
|
||||
MethodApplyPatchApproval: func() any { return &ApplyPatchApprovalResponse{} },
|
||||
MethodExecCommandApproval: func() any { return &ExecCommandApprovalResponse{} },
|
||||
var notificationMethods = map[string]struct{}{
|
||||
MethodError: {},
|
||||
MethodThreadStarted: {},
|
||||
MethodThreadStatusChanged: {},
|
||||
MethodThreadReadStateChanged: {},
|
||||
MethodThreadArchived: {},
|
||||
MethodThreadDeleted: {},
|
||||
MethodThreadUnarchived: {},
|
||||
MethodThreadClosed: {},
|
||||
MethodThreadReverted: {},
|
||||
MethodSkillsChanged: {},
|
||||
MethodThreadNameUpdated: {},
|
||||
MethodThreadAttachmentUpdated: {},
|
||||
MethodThreadGoalUpdated: {},
|
||||
MethodThreadPredictionUpdated: {},
|
||||
MethodThreadGoalCleared: {},
|
||||
MethodThreadQueueChanged: {},
|
||||
MethodProjectChanged: {},
|
||||
MethodThreadProjectUpdated: {},
|
||||
MethodThreadEnvironmentConnected: {},
|
||||
MethodThreadEnvironmentDisconnected: {},
|
||||
MethodThreadSettingsUpdated: {},
|
||||
MethodThreadTokenUsageUpdated: {},
|
||||
MethodTurnStarted: {},
|
||||
MethodHookStarted: {},
|
||||
MethodTurnCompleted: {},
|
||||
MethodHookCompleted: {},
|
||||
MethodTurnDiffUpdated: {},
|
||||
MethodTurnPlanUpdated: {},
|
||||
MethodItemStarted: {},
|
||||
MethodItemAutoApprovalReviewStarted: {},
|
||||
MethodItemAutoApprovalReviewCompleted: {},
|
||||
MethodAutoApprovalReviewStrictReviewRequired: {},
|
||||
MethodItemCompleted: {},
|
||||
MethodItemAgentMessageDelta: {},
|
||||
MethodItemPlanDelta: {},
|
||||
MethodCommandExecOutputDelta: {},
|
||||
MethodProcessOutputDelta: {},
|
||||
MethodProcessExited: {},
|
||||
MethodItemCommandExecutionOutputDelta: {},
|
||||
MethodItemCommandExecutionTerminalInteraction: {},
|
||||
MethodItemFileChangeOutputDelta: {},
|
||||
MethodItemFileChangePatchUpdated: {},
|
||||
MethodServerRequestResolved: {},
|
||||
MethodItemMcpToolCallProgress: {},
|
||||
MethodMcpServerOauthLoginCompleted: {},
|
||||
MethodMcpServerStartupStatusUpdated: {},
|
||||
MethodMcpServerEventStreamNotification: {},
|
||||
MethodAccountUpdated: {},
|
||||
MethodAccountGatewayOAuthChanged: {},
|
||||
MethodAccountRateLimitsUpdated: {},
|
||||
MethodAppListUpdated: {},
|
||||
MethodRemoteControlStatusChanged: {},
|
||||
MethodExternalAgentConfigImportProgress: {},
|
||||
MethodExternalAgentConfigImportCompleted: {},
|
||||
MethodFsChanged: {},
|
||||
MethodItemReasoningSummaryTextDelta: {},
|
||||
MethodItemReasoningSummaryPartAdded: {},
|
||||
MethodItemReasoningTextDelta: {},
|
||||
MethodThreadCompacted: {},
|
||||
MethodModelRerouted: {},
|
||||
MethodModelVerification: {},
|
||||
MethodModelProviderAuthRecoveryStarted: {},
|
||||
MethodModelProviderAuthRecoveryCompleted: {},
|
||||
MethodTurnModerationMetadata: {},
|
||||
MethodModelSafetyBufferingUpdated: {},
|
||||
MethodWarning: {},
|
||||
MethodGuardianWarning: {},
|
||||
MethodDeprecationNotice: {},
|
||||
MethodConfigWarning: {},
|
||||
MethodFuzzyFileSearchSessionUpdated: {},
|
||||
MethodFuzzyFileSearchSessionCompleted: {},
|
||||
MethodThreadRealtimeStarted: {},
|
||||
MethodThreadRealtimeItemAdded: {},
|
||||
MethodThreadRealtimeItemStarted: {},
|
||||
MethodThreadRealtimeItemTranscriptDelta: {},
|
||||
MethodThreadRealtimeItemCompleted: {},
|
||||
MethodThreadRealtimeTranscriptDelta: {},
|
||||
MethodThreadRealtimeTranscriptDone: {},
|
||||
MethodThreadRealtimeOutputAudioDelta: {},
|
||||
MethodThreadRealtimeSdp: {},
|
||||
MethodThreadRealtimeError: {},
|
||||
MethodThreadRealtimeClosed: {},
|
||||
MethodWindowsWorldWritableWarning: {},
|
||||
MethodWindowsSandboxSetupCompleted: {},
|
||||
MethodAccountLoginCompleted: {},
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -524,11 +481,6 @@ const (
|
||||
ModeKindDefault ModeKind = "default"
|
||||
)
|
||||
|
||||
const (
|
||||
ThreadUnreadPositionTypeThreadStart = "threadStart"
|
||||
ThreadUnreadPositionTypeTurn = "turn"
|
||||
)
|
||||
|
||||
const (
|
||||
AdditionalContextKindUntrusted AdditionalContextKind = "untrusted"
|
||||
AdditionalContextKindApplication AdditionalContextKind = "application"
|
||||
@@ -588,6 +540,11 @@ const (
|
||||
RateLimitReachedTypeWorkspaceMemberUsageLimitReached RateLimitReachedType = "workspace_member_usage_limit_reached"
|
||||
)
|
||||
|
||||
const (
|
||||
ThreadUnreadPositionTypeThreadStart = "threadStart"
|
||||
ThreadUnreadPositionTypeTurn = "turn"
|
||||
)
|
||||
|
||||
const (
|
||||
ThreadAttachmentOperationCreated ThreadAttachmentOperation = "created"
|
||||
ThreadAttachmentOperationDeleted ThreadAttachmentOperation = "deleted"
|
||||
@@ -1562,32 +1519,6 @@ type ThreadForkResponse struct {
|
||||
Thread Thread `json:"thread"`
|
||||
}
|
||||
|
||||
type ThreadCompactStartParams struct {
|
||||
ThreadID string `json:"threadId"`
|
||||
}
|
||||
|
||||
type ThreadCompactStartResponse = map[string]any
|
||||
|
||||
type ThreadReadParams struct {
|
||||
IncludeTurns bool `json:"includeTurns,omitempty"`
|
||||
ThreadID string `json:"threadId"`
|
||||
}
|
||||
|
||||
type ThreadUnreadPosition struct {
|
||||
TurnID string `json:"turnId,omitempty"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type ThreadReadState struct {
|
||||
FirstUnread *ThreadUnreadPosition `json:"firstUnread,omitempty"`
|
||||
Revision string `json:"revision"`
|
||||
}
|
||||
|
||||
type ThreadReadResponse struct {
|
||||
ReadState *ThreadReadState `json:"readState,omitempty"`
|
||||
Thread Thread `json:"thread"`
|
||||
}
|
||||
|
||||
type AdditionalContextKind string
|
||||
|
||||
type AdditionalContextEntry struct {
|
||||
@@ -1745,6 +1676,16 @@ type ThreadStatusChangedNotification struct {
|
||||
ThreadID string `json:"threadId"`
|
||||
}
|
||||
|
||||
type ThreadUnreadPosition struct {
|
||||
TurnID string `json:"turnId,omitempty"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type ThreadReadState struct {
|
||||
FirstUnread *ThreadUnreadPosition `json:"firstUnread,omitempty"`
|
||||
Revision string `json:"revision"`
|
||||
}
|
||||
|
||||
type ThreadReadStateChangedNotification struct {
|
||||
ReadState ThreadReadState `json:"readState"`
|
||||
ThreadID string `json:"threadId"`
|
||||
|
||||
@@ -41,22 +41,6 @@ type Event struct {
|
||||
Read time.Time
|
||||
}
|
||||
|
||||
func (e Event) Decode() (any, error) {
|
||||
ctor, ok := notificationParams[e.Method]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown notification %q", e.Method)
|
||||
}
|
||||
if ctor == nil {
|
||||
return nil, nil
|
||||
}
|
||||
v := ctor()
|
||||
err := json.Unmarshal(e.Params, v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decode %s: %w", e.Method, err)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
type RPCError struct {
|
||||
Code int64 `json:"code"`
|
||||
Message string `json:"message"`
|
||||
@@ -92,11 +76,10 @@ type Session struct {
|
||||
pendingMu sync.Mutex
|
||||
pending map[int64]chan rpcResult
|
||||
|
||||
stateMu sync.Mutex
|
||||
threadID string
|
||||
turnID string
|
||||
model string
|
||||
userAgent string
|
||||
stateMu sync.Mutex
|
||||
threadID string
|
||||
turnID string
|
||||
model string
|
||||
|
||||
servers map[string]*mcp.Server
|
||||
reminder string
|
||||
@@ -264,10 +247,6 @@ func (s *Session) initialize(ctx context.Context) error {
|
||||
return fmt.Errorf("initialize: %w", err)
|
||||
}
|
||||
|
||||
s.stateMu.Lock()
|
||||
s.userAgent = init.UserAgent
|
||||
s.stateMu.Unlock()
|
||||
|
||||
return s.notify("initialized", nil)
|
||||
}
|
||||
|
||||
@@ -314,10 +293,6 @@ func (s *Session) Events() <-chan Event {
|
||||
return s.events
|
||||
}
|
||||
|
||||
func (s *Session) Done() <-chan struct{} {
|
||||
return s.done
|
||||
}
|
||||
|
||||
func (s *Session) Pid() int {
|
||||
return s.cmd.Process.Pid
|
||||
}
|
||||
@@ -341,18 +316,6 @@ func (s *Session) ID() string {
|
||||
return s.threadID
|
||||
}
|
||||
|
||||
func (s *Session) TurnID() string {
|
||||
s.stateMu.Lock()
|
||||
defer s.stateMu.Unlock()
|
||||
return s.turnID
|
||||
}
|
||||
|
||||
func (s *Session) UserAgent() string {
|
||||
s.stateMu.Lock()
|
||||
defer s.stateMu.Unlock()
|
||||
return s.userAgent
|
||||
}
|
||||
|
||||
func (s *Session) SetModel(model string) {
|
||||
s.stateMu.Lock()
|
||||
s.model = model
|
||||
@@ -417,10 +380,6 @@ func (s *Session) Steer(text string) (*Reply, error) {
|
||||
return s.request(context.Background(), MethodTurnSteer, params)
|
||||
}
|
||||
|
||||
func (s *Session) Compact(ctx context.Context) error {
|
||||
return s.Call(ctx, MethodThreadCompactStart, ThreadCompactStartParams{ThreadID: s.ID()}, nil)
|
||||
}
|
||||
|
||||
const rpcTimeout = time.Minute
|
||||
|
||||
type Reply struct {
|
||||
@@ -603,7 +562,7 @@ func (s *Session) handleLine(line []byte) {
|
||||
return
|
||||
}
|
||||
|
||||
if _, ok := notificationParams[m.Method]; !ok {
|
||||
if _, ok := notificationMethods[m.Method]; !ok {
|
||||
s.badLine(line, fmt.Sprintf("unrecognized method %q", m.Method))
|
||||
}
|
||||
|
||||
|
||||
@@ -75,6 +75,34 @@ func AddWorktree(ctx context.Context, repo, path, branch, base string) (string,
|
||||
return sha, nil
|
||||
}
|
||||
|
||||
func EnsureBranchCommit(ctx context.Context, repo, branch string, env []string) error {
|
||||
_, _, code, err := runCode(ctx, repo, nil, "rev-parse", "--verify", "--quiet", "refs/heads/"+branch+"^{commit}")
|
||||
if err != nil || code == 0 {
|
||||
return err
|
||||
}
|
||||
refs, err := run(ctx, repo, "for-each-ref", "--count=1")
|
||||
if err != nil || refs != "" {
|
||||
return err
|
||||
}
|
||||
tree, err := run(ctx, repo, "mktree")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
commit, err := runEnv(ctx, repo, env, "commit-tree", tree, "-m", "Initial commit")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, stderr, code, err := runNetwork(ctx, repo, "push", Remote, commit+":refs/heads/"+branch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if code != 0 {
|
||||
return fmt.Errorf("pushing %s to %s: exit status %d: %s", branch, Remote, code, stderr)
|
||||
}
|
||||
_, err = run(ctx, repo, "update-ref", "refs/heads/"+branch, commit, "")
|
||||
return err
|
||||
}
|
||||
|
||||
func AddDetachedWorktree(ctx context.Context, repo, path, commit string) error {
|
||||
_, err := run(ctx, repo, "worktree", "add", "--detach", path, commit)
|
||||
return err
|
||||
|
||||
@@ -345,14 +345,14 @@ func (s *Server) GetProject(w http.ResponseWriter, r *http.Request, projectID st
|
||||
writeJSON(w, http.StatusOK, p)
|
||||
}
|
||||
|
||||
func (s *Server) ListTasks(w http.ResponseWriter, r *http.Request, projectID string) {
|
||||
func (s *Server) ListActiveTasks(w http.ResponseWriter, r *http.Request, projectID string) {
|
||||
_, err := s.store.GetProject(r.Context(), projectID)
|
||||
if err != nil {
|
||||
writeStoreError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
tasks, err := s.store.ListTasks(r.Context(), projectID)
|
||||
tasks, err := s.store.ListActiveTasks(r.Context(), projectID)
|
||||
if err != nil {
|
||||
writeStoreError(w, err)
|
||||
return
|
||||
@@ -840,6 +840,60 @@ func (s *Server) ForkSnapshot(w http.ResponseWriter, r *http.Request, taskID str
|
||||
writeJSON(w, http.StatusCreated, fork)
|
||||
}
|
||||
|
||||
func (s *Server) ContinueSession(w http.ResponseWriter, r *http.Request, sessionID string) {
|
||||
req := api.MessageCreate{}
|
||||
if !readJSON(w, r, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
images, ok := s.images(w, r, req.Images)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if strings.TrimSpace(req.Text) == "" && len(images) == 0 {
|
||||
writeError(w, http.StatusBadRequest, errors.New("text or images are required"))
|
||||
return
|
||||
}
|
||||
|
||||
model := ""
|
||||
if req.Model != nil {
|
||||
model = *req.Model
|
||||
}
|
||||
sendID := ""
|
||||
if req.SendId != nil {
|
||||
sendID = *req.SendId
|
||||
}
|
||||
|
||||
src, err := s.store.GetSession(r.Context(), sessionID)
|
||||
if err != nil {
|
||||
writeStoreError(w, err)
|
||||
return
|
||||
}
|
||||
t, err := s.store.GetTask(r.Context(), src.TaskId)
|
||||
if err != nil {
|
||||
writeStoreError(w, err)
|
||||
return
|
||||
}
|
||||
p, err := s.store.GetProject(r.Context(), t.ProjectId)
|
||||
if err != nil {
|
||||
writeStoreError(w, err)
|
||||
return
|
||||
}
|
||||
u, err := s.store.GetUser(r.Context(), actor(r))
|
||||
if err != nil {
|
||||
writeStoreError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
task, err := s.agents.Continue(r.Context(), src, t, p, u, req.Text, model, sendID, r.Header.Get("Byre-Client"), images...)
|
||||
if err != nil {
|
||||
writeAgentError(w, err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusCreated, task)
|
||||
}
|
||||
|
||||
func (s *Server) ListSessions(w http.ResponseWriter, r *http.Request, taskID string) {
|
||||
_, err := s.store.GetTask(r.Context(), taskID)
|
||||
if err != nil {
|
||||
|
||||
+16
-23
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"g.fc.run/ai/byre/internal/api"
|
||||
"g.fc.run/ai/byre/internal/ids"
|
||||
"g.fc.run/ai/byre/internal/schedule"
|
||||
)
|
||||
|
||||
@@ -89,32 +90,24 @@ func writeWeekdays(ctx context.Context, tx *sql.Tx, sc api.Schedule) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) CreateSchedule(ctx context.Context, sc api.Schedule, actor string) (api.Schedule, error) {
|
||||
func (s *Store) SaveSchedule(ctx context.Context, sc api.Schedule, actor string) (api.Schedule, error) {
|
||||
sc.NextRun = schedule.Next(sc, time.Now())
|
||||
err := s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
|
||||
_, err := tx.ExecContext(ctx, `INSERT INTO schedules (`+scheduleColumns+`, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
sc.Id, sc.ProjectId, sc.Owner, sc.Title, string(sc.Every), sc.Hour, sc.Minute, sc.DayOfMonth, sc.Prompt, sc.Model, sc.Workflow, formatTime(sc.NextRun), now())
|
||||
if err != nil {
|
||||
return err
|
||||
if sc.Id == "" {
|
||||
sc.Id = ids.New(ids.Schedule)
|
||||
_, err := tx.ExecContext(ctx, `INSERT INTO schedules (`+scheduleColumns+`, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
sc.Id, sc.ProjectId, sc.Owner, sc.Title, string(sc.Every), sc.Hour, sc.Minute, sc.DayOfMonth, sc.Prompt, sc.Model, sc.Workflow, formatTime(sc.NextRun), now())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
_, err := tx.ExecContext(ctx, `UPDATE schedules SET title = ?, every = ?, hour = ?, minute = ?, day_of_month = ?, prompt = ?, model = ?, workflow = ?, next_run_at = ? WHERE id = ?`,
|
||||
sc.Title, string(sc.Every), sc.Hour, sc.Minute, sc.DayOfMonth, sc.Prompt, sc.Model, sc.Workflow, formatTime(sc.NextRun), sc.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = writeWeekdays(ctx, tx, sc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return emit(api.Event{Kind: "schedule.saved", ProjectId: &sc.ProjectId, Actor: actor, Payload: ToPayload(sc)})
|
||||
})
|
||||
return sc, err
|
||||
}
|
||||
|
||||
func (s *Store) UpdateSchedule(ctx context.Context, sc api.Schedule, actor string) (api.Schedule, error) {
|
||||
sc.NextRun = schedule.Next(sc, time.Now())
|
||||
err := s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
|
||||
_, err := tx.ExecContext(ctx, `UPDATE schedules SET title = ?, every = ?, hour = ?, minute = ?, day_of_month = ?, prompt = ?, model = ?, workflow = ?, next_run_at = ? WHERE id = ?`,
|
||||
sc.Title, string(sc.Every), sc.Hour, sc.Minute, sc.DayOfMonth, sc.Prompt, sc.Model, sc.Workflow, formatTime(sc.NextRun), sc.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = writeWeekdays(ctx, tx, sc)
|
||||
err := writeWeekdays(ctx, tx, sc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -410,6 +410,10 @@ func (s *Store) ListTasks(ctx context.Context, projectID string) ([]api.Task, er
|
||||
return s.queryTasks(ctx, `SELECT `+taskColumns+` FROM tasks WHERE project_id = ? ORDER BY created_at DESC`, projectID)
|
||||
}
|
||||
|
||||
func (s *Store) ListActiveTasks(ctx context.Context, projectID string) ([]api.Task, error) {
|
||||
return s.queryTasks(ctx, `SELECT `+taskColumns+` FROM tasks WHERE project_id = ? AND status = ? ORDER BY created_at DESC`, projectID, string(api.TaskStatusActive))
|
||||
}
|
||||
|
||||
func (s *Store) GetTask(ctx context.Context, id string) (api.Task, error) {
|
||||
t, err := scanTask(s.db.QueryRowContext(ctx, `SELECT `+taskColumns+` FROM tasks WHERE id = ?`, id))
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
@@ -958,8 +962,8 @@ func (s *Store) TranscriptEvents(ctx context.Context, taskID string, after int64
|
||||
cut := int64(0)
|
||||
err = s.db.QueryRowContext(ctx, `
|
||||
SELECT coalesce(max(seq), 0) FROM events
|
||||
WHERE task_id = ? AND kind = 'snapshot.created' AND json_extract(payload, '$.n') = ?
|
||||
`, from, at).Scan(&cut)
|
||||
WHERE task_id = ? AND (? = 0 OR kind = 'snapshot.created' AND json_extract(payload, '$.n') = ?)
|
||||
`, from, at, at).Scan(&cut)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1750,6 +1754,8 @@ func scanTask(row scanner) (api.Task, error) {
|
||||
}
|
||||
if forkedFrom != "" {
|
||||
t.ForkedFrom = &forkedFrom
|
||||
}
|
||||
if forkedAt != 0 {
|
||||
t.ForkedAt = &forkedAt
|
||||
}
|
||||
if mergeQueuedAt != "" {
|
||||
|
||||
@@ -41,5 +41,11 @@
|
||||
</dict>
|
||||
<key>NSAppleEventsUsageDescription</key>
|
||||
<string>byre's agents drive Orion on this Mac through AppleScript when a task asks them to.</string>
|
||||
<key>NSDesktopFolderUsageDescription</key>
|
||||
<string>byre's agents read and write files on your Desktop when a task asks them to.</string>
|
||||
<key>NSDocumentsFolderUsageDescription</key>
|
||||
<string>byre's agents read and write files in Documents when a task asks them to.</string>
|
||||
<key>NSDownloadsFolderUsageDescription</key>
|
||||
<string>byre's agents read and write files in Downloads when a task asks them to.</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -4,6 +4,9 @@ import HTTPTypes
|
||||
import OpenAPIRuntime
|
||||
import OpenAPIURLSession
|
||||
import os
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
let log = Logger(subsystem: "run.fc.byre.mac", category: "byre")
|
||||
|
||||
@@ -104,7 +107,8 @@ private let clientID = newID("cli")
|
||||
#if os(macOS)
|
||||
let deviceName = Host.current().localizedName ?? ProcessInfo.processInfo.hostName
|
||||
#else
|
||||
let deviceName = deviceKind
|
||||
let deviceNameKey = "deviceName"
|
||||
let deviceName = UserDefaults.standard.string(forKey: deviceNameKey) ?? deviceKind
|
||||
#endif
|
||||
|
||||
final class API: Sendable {
|
||||
@@ -112,9 +116,10 @@ final class API: Sendable {
|
||||
let session: URLSession
|
||||
let client: Client
|
||||
|
||||
init(origin: URL) {
|
||||
init(origin: URL, timeout: TimeInterval = 60) {
|
||||
self.origin = origin
|
||||
let config = URLSessionConfiguration.default
|
||||
config.timeoutIntervalForRequest = timeout
|
||||
config.httpAdditionalHeaders = [
|
||||
"Byre-Build": Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as! String,
|
||||
"Byre-Client": clientID,
|
||||
|
||||
@@ -104,7 +104,7 @@ struct ByreCommands: Commands {
|
||||
PasteboardCommands()
|
||||
CommandMenu("Go") {
|
||||
goButton("Switch Task", "k")
|
||||
goButton("Project", "p")
|
||||
goButton("Task List", "p")
|
||||
goButton("Lead", "l")
|
||||
goButton("Merge", "s")
|
||||
serverButton("Previous Server", "[", -1)
|
||||
|
||||
@@ -6,6 +6,26 @@ enum ChatKind: Equatable {
|
||||
case task(String?)
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
private let leadPlaces = "Tasks open on the right; [switch between them](byre-app://switcher) with ⌘K. Their questions, finished work and reviews wait for you there."
|
||||
private let leadSettings = "pick the default model for new tasks, turn on notifications, and check this Mac's permissions so agents can see and control it and read your Desktop, Documents and Downloads. The [Server](byre-app://tab?name=server) tab checks the same for the server's agents."
|
||||
#else
|
||||
private let leadPlaces = "Tasks live in the Tasks tab, with the ones waiting on you marked; [find or start one](byre-app://switcher) any time."
|
||||
private let leadSettings = "pick the default model for new tasks, turn on notifications, and choose this server's window color."
|
||||
#endif
|
||||
|
||||
private let leadMore = "As you work, the lead keeps [Notes](byre-app://tab?name=notes) on how you like things done, [Workflows](byre-app://tab?name=workflows) run several agents on one job, such as a review, and [Status](byre-app://tab?name=status) shows the turns running now and how much of each vendor's limits you've used."
|
||||
|
||||
private let leadWelcome = """
|
||||
**Welcome to byre.** This is your lead, the agent you talk to about all your work. Tell it what you want done and it starts tasks: each one is an agent working on its own branch of a project, which you merge when you're happy with it. Ask it to add a project first, giving the path of a git repository on the server.
|
||||
|
||||
\(leadPlaces)
|
||||
|
||||
\(leadMore)
|
||||
|
||||
Start in [Settings](byre-app://tab?name=settings): \(leadSettings)
|
||||
"""
|
||||
|
||||
struct AttentionGroup: Identifiable {
|
||||
let id: String
|
||||
let label: String
|
||||
@@ -114,7 +134,17 @@ final class ChatModel {
|
||||
member != nil
|
||||
}
|
||||
|
||||
var closed: Bool {
|
||||
guard let taskId, let task = store.findTask(taskId) else {
|
||||
return false
|
||||
}
|
||||
return task.status != .active
|
||||
}
|
||||
|
||||
var placeholder: String {
|
||||
if closed {
|
||||
return "Continue in a new task, started from main, whose agent picks up this conversation. Paste or drop images to attach them."
|
||||
}
|
||||
if team {
|
||||
return "Post to the team's board; every agent of the team gets it."
|
||||
}
|
||||
@@ -157,6 +187,10 @@ final class ChatModel {
|
||||
kind == .lead && store.leadHasOlder()
|
||||
}
|
||||
|
||||
var welcome: Bool {
|
||||
kind == .lead && !loading && !hasOlder && builder.root.rows.isEmpty && builder.pending.isEmpty
|
||||
}
|
||||
|
||||
func start() {
|
||||
if started {
|
||||
return
|
||||
@@ -444,6 +478,14 @@ final class ChatModel {
|
||||
case .lead:
|
||||
_ = try await api.messageLead(body: .json(.init(text: text, images: images, model: sessionId != nil ? model : firstModel, sendId: sendID))).accepted
|
||||
case .task(let id):
|
||||
if closed {
|
||||
guard let sessionId else {
|
||||
throw ApiError(status: 0, message: "This task has no agent conversation to continue.")
|
||||
}
|
||||
let task = try await api.continueSession(path: .init(sessionId: sessionId), body: .json(.init(text: text, images: images, model: team ? nil : model, sendId: sendID))).created.body.json
|
||||
store.focusTask(task.id)
|
||||
return
|
||||
}
|
||||
if team {
|
||||
if images != nil {
|
||||
throw ApiError(status: 0, message: "The board takes text only; drop the images to post.")
|
||||
@@ -790,6 +832,10 @@ private struct TranscriptScroll: View {
|
||||
PendingView(pending: p, model: model)
|
||||
.padding(.top, 12)
|
||||
}
|
||||
if model.welcome {
|
||||
MarkdownView(blocks: parseMarkdown(leadWelcome))
|
||||
.padding(.top, 12)
|
||||
}
|
||||
}
|
||||
}
|
||||
.scrollTargetLayout()
|
||||
@@ -1448,6 +1494,7 @@ private struct ReadyBubble: View {
|
||||
if current, let task {
|
||||
let busy = store.taskBusy(task.id)
|
||||
let commented = comments(task).count
|
||||
let other = !trimmed(store.drafts?.textAnswer(draftKey(task)) ?? "").isEmpty || store.images.has(draftKey(task))
|
||||
AttentionReply(key: draftKey(task), placeholder: "Something else to tell the agent; sending it dismisses this", busy: dismissing, focus: $focus, send: { send(task) }) {
|
||||
Button("Dismiss") {
|
||||
dismiss(task, then: nil)
|
||||
@@ -1462,6 +1509,12 @@ private struct ReadyBubble: View {
|
||||
}
|
||||
.buttonStyle(WebButtonStyle(primary: true))
|
||||
.help("Send your comments to the agent in one message; sending dismisses this")
|
||||
} else if other {
|
||||
Button("Send") {
|
||||
send(task)
|
||||
}
|
||||
.buttonStyle(WebButtonStyle(primary: true))
|
||||
.help("Send this to the agent; sending dismisses this")
|
||||
} else if !discard {
|
||||
MergeButton(task: task, busy: busy, unmerged: unmerged, keepOpen: true, primary: task.readyMerge == ._continue)
|
||||
MergeButton(task: task, busy: busy, unmerged: unmerged, keepOpen: false, primary: task.readyMerge != ._continue)
|
||||
|
||||
@@ -482,7 +482,7 @@ struct ComposerView: View {
|
||||
}
|
||||
|
||||
private func modelState() -> ModelState {
|
||||
let live = chat.liveSession
|
||||
let live = chat.closed ? chat.session : chat.liveSession
|
||||
let current = live.map { $0.model.isEmpty ? chat.builder.context[$0.id]?.model ?? "" : $0.model } ?? ""
|
||||
let offered = live.map { s in models.filter { vendorOf($0) == s.vendor.rawValue } } ?? models
|
||||
let options = !current.isEmpty && !offered.contains(current) ? [current] + offered : offered
|
||||
@@ -632,12 +632,13 @@ struct ComposerView: View {
|
||||
.buttonStyle(WebButtonStyle(danger: true))
|
||||
.disabled(!running)
|
||||
.fixedSize()
|
||||
Button("Send") {
|
||||
Button(chat.closed ? "Continue" : "Send") {
|
||||
submit(modelState())
|
||||
}
|
||||
.buttonStyle(WebButtonStyle())
|
||||
.disabled(!shown)
|
||||
.fixedSize()
|
||||
.help(chat.closed ? "Start a new task from main whose agent continues this conversation with your message" : "Send the message")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -668,7 +669,7 @@ struct ComposerView: View {
|
||||
text = ""
|
||||
let sent = drafts.startSend(draftKey, value, imgs.count)
|
||||
let sendID = newID("snd")
|
||||
let cancel = chat.team ? { true } : chat.builder.showPending(value, imgs, sendID)
|
||||
let cancel = chat.team || chat.closed ? { true } : chat.builder.showPending(value, imgs, sendID)
|
||||
let sessionId = m.live?.id
|
||||
Task {
|
||||
var ids: [String]? = nil
|
||||
@@ -686,7 +687,7 @@ struct ComposerView: View {
|
||||
return
|
||||
} catch {
|
||||
let refused = (rootError(error) as? ApiError).map { $0.status < 500 } ?? false
|
||||
if chat.team || refused {
|
||||
if (chat.team && !chat.closed) || refused {
|
||||
guard cancel() else {
|
||||
log.notice("send failed after the server confirmed it: \(errorText(error), privacy: .public)")
|
||||
drafts.endSend(sent, true)
|
||||
@@ -698,7 +699,7 @@ struct ComposerView: View {
|
||||
overlays.showAlert(errorText(error))
|
||||
return
|
||||
}
|
||||
guard chat.builder.unconfirmed(sendID) else {
|
||||
guard chat.closed || chat.builder.unconfirmed(sendID) else {
|
||||
drafts.endSend(sent, true)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ struct OverlayAlert: Identifiable {
|
||||
let content: AnyView
|
||||
}
|
||||
|
||||
enum LeftTab {
|
||||
enum LeftTab: String {
|
||||
case lead, status, notes, workflows, server, settings
|
||||
}
|
||||
|
||||
@@ -51,7 +51,9 @@ final class Overlays {
|
||||
var switcher: SwitcherModel?
|
||||
var leftTab = LeftTab.lead
|
||||
var leadFocus = 0
|
||||
var tabFocus = 0
|
||||
var projectFocus = false
|
||||
var taskList = false
|
||||
@ObservationIgnored var pickerFrame: CGRect = .zero
|
||||
@ObservationIgnored var menuHover = false
|
||||
var paneFrame: CGRect = .zero
|
||||
|
||||
@@ -435,16 +435,21 @@ private struct WebButton: View {
|
||||
|
||||
var body: some View {
|
||||
let fill = danger ? Palette.danger : Palette.accent
|
||||
configuration.label
|
||||
.font(Fonts.font(13, weight: primary ? 700 : 400))
|
||||
.foregroundStyle(primary ? Palette.bg : danger ? Palette.danger : Palette.text)
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.vertical, touchOnly ? 7 : 4)
|
||||
.background(primary ? fill : Palette.raised, in: RoundedRectangle(cornerRadius: 6))
|
||||
.overlay(RoundedRectangle(cornerRadius: 6).stroke(primary ? fill : hover && enabled ? Palette.muted : Palette.border))
|
||||
.opacity(enabled ? (configuration.isPressed ? 0.8 : 1) : 0.45)
|
||||
.contentShape(Rectangle())
|
||||
.onHover { hover = $0 }
|
||||
ZStack {
|
||||
configuration.label
|
||||
.font(Fonts.font(13, weight: 700))
|
||||
.hidden()
|
||||
configuration.label
|
||||
.font(Fonts.font(13, weight: primary ? 700 : 400))
|
||||
}
|
||||
.foregroundStyle(primary ? Palette.bg : danger ? Palette.danger : Palette.text)
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.vertical, touchOnly ? 7 : 4)
|
||||
.background(primary ? fill : Palette.raised, in: RoundedRectangle(cornerRadius: 6))
|
||||
.overlay(RoundedRectangle(cornerRadius: 6).stroke(primary ? fill : hover && enabled ? Palette.muted : Palette.border))
|
||||
.opacity(enabled ? (configuration.isPressed ? 0.8 : 1) : 0.45)
|
||||
.contentShape(Rectangle())
|
||||
.onHover { hover = $0 }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -181,7 +181,7 @@ private final class Forward: @unchecked Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
private final class Splice: @unchecked Sendable {
|
||||
final class Splice: @unchecked Sendable {
|
||||
private let conn: NWConnection
|
||||
private let fd: Int32
|
||||
private let io: DispatchIO
|
||||
|
||||
@@ -66,6 +66,11 @@ func openAppLink(_ url: URL, _ store: Store, _ overlays: Overlays) -> OpenURLAct
|
||||
let taskId = q["task"] ?? ""
|
||||
store.route("#/task/\(taskId)")
|
||||
store.openPage(.preview(taskId: taskId, port: Int(q["port"] ?? "") ?? 0))
|
||||
case "tab":
|
||||
overlays.leftTab = LeftTab(rawValue: q["name"] ?? "") ?? .lead
|
||||
overlays.tabFocus += 1
|
||||
case "switcher":
|
||||
overlays.openSwitcher(store)
|
||||
case "todo":
|
||||
store.startTodo(q["project"] ?? "", q["title"] ?? "", discuss: q["discuss"] == "1", overlays)
|
||||
case "suggest":
|
||||
@@ -676,6 +681,10 @@ private final class FlowTextView: NSTextView {
|
||||
NSCursor.arrow.set()
|
||||
return
|
||||
}
|
||||
if clickable(event) {
|
||||
NSCursor.arrow.set()
|
||||
return
|
||||
}
|
||||
super.cursorUpdate(with: event)
|
||||
}
|
||||
|
||||
@@ -684,6 +693,13 @@ private final class FlowTextView: NSTextView {
|
||||
return
|
||||
}
|
||||
super.mouseMoved(with: event)
|
||||
if clickable(event) {
|
||||
NSCursor.arrow.set()
|
||||
}
|
||||
}
|
||||
|
||||
private func clickable(_ event: NSEvent) -> Bool {
|
||||
onTap != nil && !link(at: convert(event.locationInWindow, from: nil))
|
||||
}
|
||||
|
||||
private func frontmost(_ event: NSEvent) -> Bool {
|
||||
|
||||
@@ -192,7 +192,9 @@ private struct TaskPane: View {
|
||||
VStack(spacing: 0) {
|
||||
TaskBar()
|
||||
VStack(spacing: 0) {
|
||||
if store.selectedTaskId == nil {
|
||||
if overlays.taskList {
|
||||
TaskList()
|
||||
} else if store.selectedTaskId == nil {
|
||||
ProjectView()
|
||||
} else {
|
||||
TabStrip()
|
||||
@@ -208,6 +210,12 @@ private struct TaskPane: View {
|
||||
}
|
||||
.environment(\.paneAccent, accent)
|
||||
.environment(\.inTask, true)
|
||||
.onChange(of: store.reveals) {
|
||||
overlays.taskList = false
|
||||
}
|
||||
.onChange(of: store.focus) {
|
||||
overlays.taskList = false
|
||||
}
|
||||
.onGeometryChange(for: CGRect.self) { g in
|
||||
g.frame(in: .global)
|
||||
} action: { frame in
|
||||
@@ -260,7 +268,7 @@ struct TaskBar: View {
|
||||
var body: some View {
|
||||
let task = store.selectedTaskId.flatMap { store.findTask($0) }
|
||||
let project = store.focusedProject()
|
||||
let label = task.map { "\(project?.name ?? "") ▸ \($0.name)" } ?? project?.name ?? "No task"
|
||||
let label = overlays.taskList ? "All tasks" : task.map { "\(project?.name ?? "") ▸ \($0.name)" } ?? project?.name ?? "No task"
|
||||
HStack(spacing: 0) {
|
||||
Color.clear.frame(maxWidth: .infinity, maxHeight: 1)
|
||||
Button {
|
||||
@@ -290,6 +298,20 @@ struct TaskBar: View {
|
||||
PreviewLink(preview: p)
|
||||
}
|
||||
}
|
||||
#if os(macOS)
|
||||
Button {
|
||||
overlays.taskList.toggle()
|
||||
} label: {
|
||||
Image(systemName: "list.bullet")
|
||||
.font(Fonts.font(13, weight: 600))
|
||||
.foregroundStyle(accent)
|
||||
.padding(.vertical, 6)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.help(overlays.taskList ? "Back to the task" : "Task list")
|
||||
.accessibilityLabel("Task list")
|
||||
#endif
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ struct PhoneView: View {
|
||||
@Environment(Overlays.self) private var overlays
|
||||
@ViewState private var tab = PhoneTab.lead
|
||||
@ViewState private var path: [PhoneRoute] = []
|
||||
@ViewState private var moreRoute = MoreRoute.status
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
@@ -73,7 +74,7 @@ struct PhoneView: View {
|
||||
}
|
||||
}
|
||||
shown(.more) {
|
||||
MoreScreen()
|
||||
MoreScreen(route: $moreRoute)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
@@ -104,6 +105,24 @@ struct PhoneView: View {
|
||||
.onChange(of: overlays.leadFocus) {
|
||||
tab = .lead
|
||||
}
|
||||
.onChange(of: overlays.tabFocus) {
|
||||
switch overlays.leftTab {
|
||||
case .lead, .server:
|
||||
tab = .lead
|
||||
case .status:
|
||||
tab = .more
|
||||
moreRoute = .status
|
||||
case .notes:
|
||||
tab = .more
|
||||
moreRoute = .notes
|
||||
case .workflows:
|
||||
tab = .more
|
||||
moreRoute = .workflows
|
||||
case .settings:
|
||||
tab = .more
|
||||
moreRoute = .settings
|
||||
}
|
||||
}
|
||||
.onChange(of: tab == .tasks && !path.isEmpty, initial: true) { _, shown in
|
||||
store.paneFocus.setShown(shown)
|
||||
}
|
||||
@@ -258,69 +277,9 @@ private struct ConnectionMark: View {
|
||||
}
|
||||
|
||||
private struct TaskListScreen: View {
|
||||
@Environment(Store.self) private var store
|
||||
@Environment(Overlays.self) private var overlays
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
ForEach(store.projects, id: \.id) { project in
|
||||
let active = (store.tasks[project.id] ?? []).filter { $0.status == .active }
|
||||
Button {
|
||||
store.sheet = nil
|
||||
store.focusProject(project.id)
|
||||
} label: {
|
||||
HStack(alignment: .firstTextBaseline, spacing: 6) {
|
||||
Text(project.name)
|
||||
.font(Fonts.font(13, weight: 700))
|
||||
Text(project.defaultBranch)
|
||||
.font(Fonts.font(11))
|
||||
.foregroundStyle(Palette.muted)
|
||||
Spacer(minLength: 0)
|
||||
Image(systemName: "chevron.right")
|
||||
.font(Fonts.font(11))
|
||||
.foregroundStyle(Palette.muted)
|
||||
}
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.top, 16)
|
||||
.padding(.bottom, 6)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
if active.isEmpty {
|
||||
Text("No active tasks.")
|
||||
.foregroundStyle(Palette.muted)
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 6)
|
||||
}
|
||||
TaskRows(active: active)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.bottom, 16)
|
||||
}
|
||||
.safeAreaInset(edge: .top, spacing: 0) {
|
||||
HStack {
|
||||
Spacer(minLength: 0)
|
||||
Button("New task") {
|
||||
overlays.openSwitcher(store)
|
||||
}
|
||||
.buttonStyle(WebButtonStyle())
|
||||
.onGeometryChange(for: CGRect.self) { g in
|
||||
g.frame(in: .named("window"))
|
||||
} action: { frame in
|
||||
overlays.pickerFrame = frame
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 6)
|
||||
.background(Palette.bg)
|
||||
.overlay(alignment: .bottom) {
|
||||
Rectangle().fill(Palette.border).frame(height: 1)
|
||||
}
|
||||
}
|
||||
.background(Palette.bg)
|
||||
.toolbar(.hidden, for: .navigationBar)
|
||||
TaskList()
|
||||
.toolbar(.hidden, for: .navigationBar)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,7 +327,7 @@ private func dismissKeyboard() {
|
||||
}
|
||||
|
||||
private struct MoreScreen: View {
|
||||
@ViewState private var route = MoreRoute.status
|
||||
@Binding var route: MoreRoute
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
|
||||
@@ -215,6 +215,72 @@ private func rowIndent(_ task: ByreTask, _ depth: Int) -> CGFloat {
|
||||
task.parentTaskId != nil ? 20 * CGFloat(depth - 1) : 0
|
||||
}
|
||||
|
||||
struct TaskList: View {
|
||||
@Environment(Store.self) private var store
|
||||
@Environment(Overlays.self) private var overlays
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
ForEach(store.projects, id: \.id) { project in
|
||||
let active = (store.tasks[project.id] ?? []).filter { $0.status == .active }
|
||||
Button {
|
||||
store.sheet = nil
|
||||
store.focusProject(project.id)
|
||||
} label: {
|
||||
HStack(alignment: .firstTextBaseline, spacing: 6) {
|
||||
Text(project.name)
|
||||
.font(Fonts.font(13, weight: 700))
|
||||
Text(project.defaultBranch)
|
||||
.font(Fonts.font(11))
|
||||
.foregroundStyle(Palette.muted)
|
||||
Spacer(minLength: 0)
|
||||
Image(systemName: "chevron.right")
|
||||
.font(Fonts.font(11))
|
||||
.foregroundStyle(Palette.muted)
|
||||
}
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.top, 16)
|
||||
.padding(.bottom, 6)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
if active.isEmpty {
|
||||
Text("No active tasks.")
|
||||
.foregroundStyle(Palette.muted)
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 6)
|
||||
}
|
||||
TaskRows(active: active)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.bottom, 16)
|
||||
}
|
||||
.safeAreaInset(edge: .top, spacing: 0) {
|
||||
HStack {
|
||||
Spacer(minLength: 0)
|
||||
Button("New task") {
|
||||
overlays.openSwitcher(store)
|
||||
}
|
||||
.buttonStyle(WebButtonStyle())
|
||||
.onGeometryChange(for: CGRect.self) { g in
|
||||
g.frame(in: .named("window"))
|
||||
} action: { frame in
|
||||
overlays.pickerFrame = frame
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 6)
|
||||
.background(Palette.bg)
|
||||
.overlay(alignment: .bottom) {
|
||||
Rectangle().fill(Palette.border).frame(height: 1)
|
||||
}
|
||||
}
|
||||
.background(Palette.bg)
|
||||
}
|
||||
}
|
||||
|
||||
struct TaskRows: View {
|
||||
let active: [ByreTask]
|
||||
@Environment(Store.self) private var store
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
import Foundation
|
||||
import Network
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
private let succeeded: [UInt8] = [5, 0, 0, 1, 0, 0, 0, 0, 0, 0]
|
||||
private let hostUnreachable: UInt8 = 4
|
||||
private let commandNotSupported: UInt8 = 7
|
||||
private let addressNotSupported: UInt8 = 8
|
||||
|
||||
final class TailnetProxy: @unchecked Sendable {
|
||||
let credential = (0..<32).map { _ in String(UInt8.random(in: 0..<16), radix: 16) }.joined()
|
||||
private(set) var port: UInt16 = 0
|
||||
private let queue = DispatchQueue(label: "run.fc.byre.proxy")
|
||||
private var listener: NWListener?
|
||||
private var splices: [ObjectIdentifier: Splice] = [:]
|
||||
|
||||
init() {
|
||||
let bound = DispatchSemaphore(value: 0)
|
||||
queue.async {
|
||||
self.listen {
|
||||
bound.signal()
|
||||
}
|
||||
}
|
||||
bound.wait()
|
||||
#if os(iOS)
|
||||
NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: nil) { [queue] _ in
|
||||
queue.async {
|
||||
self.listen()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// iOS reclaims a suspended app's listening sockets, so the listener is bound again on the same port each time the app returns to the foreground.
|
||||
private func listen(_ bound: (@Sendable () -> Void)? = nil) {
|
||||
self.listener?.cancel()
|
||||
self.listener = nil
|
||||
let params = NWParameters.tcp
|
||||
params.allowLocalEndpointReuse = true
|
||||
params.requiredLocalEndpoint = .hostPort(host: .ipv4(.loopback), port: port == 0 ? .any : NWEndpoint.Port(rawValue: port)!)
|
||||
let listener: NWListener
|
||||
do {
|
||||
listener = try NWListener(using: params)
|
||||
} catch {
|
||||
failed(nil, error)
|
||||
return
|
||||
}
|
||||
self.listener = listener
|
||||
listener.stateUpdateHandler = { [weak self, weak listener] state in
|
||||
guard let self, let listener, self.listener === listener else {
|
||||
return
|
||||
}
|
||||
switch state {
|
||||
case .ready:
|
||||
if self.port == 0 {
|
||||
self.port = listener.port!.rawValue
|
||||
}
|
||||
bound?()
|
||||
case .failed(let error):
|
||||
self.failed(listener, error)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
listener.newConnectionHandler = { [weak self] conn in
|
||||
self?.accept(conn)
|
||||
}
|
||||
listener.start(queue: queue)
|
||||
}
|
||||
|
||||
private func failed(_ listener: NWListener?, _ error: any Error) {
|
||||
if port == 0 {
|
||||
fatalError("tailnet proxy listener: \(error)")
|
||||
}
|
||||
log.error("tailnet proxy listener on port \(self.port): \(String(describing: error), privacy: .public)")
|
||||
listener?.cancel()
|
||||
queue.asyncAfter(deadline: .now() + 1) {
|
||||
if self.listener === listener {
|
||||
self.listen()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func accept(_ conn: NWConnection) {
|
||||
conn.start(queue: queue)
|
||||
read(conn, 2) { head in
|
||||
self.read(conn, Int(head[1])) { methods in
|
||||
guard head[0] == 5, methods.contains(2) else {
|
||||
self.send(conn, [5, 0xff], close: true)
|
||||
return
|
||||
}
|
||||
self.send(conn, [5, 2])
|
||||
self.authenticate(conn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func authenticate(_ conn: NWConnection) {
|
||||
read(conn, 2) { head in
|
||||
self.read(conn, Int(head[1])) { _ in
|
||||
self.read(conn, 1) { length in
|
||||
self.read(conn, Int(length[0])) { password in
|
||||
guard head[0] == 1, String(decoding: password, as: UTF8.self) == self.credential else {
|
||||
self.send(conn, [1, 1], close: true)
|
||||
return
|
||||
}
|
||||
self.send(conn, [1, 0])
|
||||
self.request(conn)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func request(_ conn: NWConnection) {
|
||||
read(conn, 4) { head in
|
||||
guard head[0] == 5, head[1] == 1 else {
|
||||
self.refuse(conn, commandNotSupported)
|
||||
return
|
||||
}
|
||||
let connect = { @Sendable (host: String) in
|
||||
self.read(conn, 2) { port in
|
||||
self.connect(conn, "\(host):\(Int(port[0]) << 8 | Int(port[1]))")
|
||||
}
|
||||
}
|
||||
switch head[3] {
|
||||
case 1:
|
||||
self.read(conn, 4) { ip in
|
||||
connect(ip.map(String.init).joined(separator: "."))
|
||||
}
|
||||
case 3:
|
||||
self.read(conn, 1) { length in
|
||||
self.read(conn, Int(length[0])) { name in
|
||||
connect(String(decoding: name, as: UTF8.self))
|
||||
}
|
||||
}
|
||||
case 4:
|
||||
self.read(conn, 16) { ip in
|
||||
connect("[\(IPv6Address(Data(ip))!)]")
|
||||
}
|
||||
default:
|
||||
self.refuse(conn, addressNotSupported)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func connect(_ conn: NWConnection, _ address: String) {
|
||||
DispatchQueue.global().async {
|
||||
do {
|
||||
let fd = try tailnetNode.dial(address)
|
||||
self.queue.async {
|
||||
self.send(conn, succeeded)
|
||||
let splice = Splice(conn, fd, self.queue)
|
||||
let id = ObjectIdentifier(splice)
|
||||
self.splices[id] = splice
|
||||
splice.start {
|
||||
self.splices[id] = nil
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
log.error("tailnet proxy: \(errorText(error), privacy: .public)")
|
||||
self.queue.async {
|
||||
self.refuse(conn, hostUnreachable)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func refuse(_ conn: NWConnection, _ code: UInt8) {
|
||||
send(conn, [5, code, 0, 1, 0, 0, 0, 0, 0, 0], close: true)
|
||||
}
|
||||
|
||||
private func read(_ conn: NWConnection, _ count: Int, _ then: @escaping @Sendable ([UInt8]) -> Void) {
|
||||
if count == 0 {
|
||||
then([])
|
||||
return
|
||||
}
|
||||
conn.receive(minimumIncompleteLength: count, maximumLength: count) { data, _, _, error in
|
||||
guard error == nil, let data, data.count == count else {
|
||||
conn.cancel()
|
||||
return
|
||||
}
|
||||
then([UInt8](data))
|
||||
}
|
||||
}
|
||||
|
||||
private func send(_ conn: NWConnection, _ bytes: [UInt8], close: Bool = false) {
|
||||
conn.send(content: Data(bytes), completion: .contentProcessed { _ in
|
||||
if close {
|
||||
conn.cancel()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -409,11 +409,11 @@ struct ServerView: View {
|
||||
Text("Agents' permissions")
|
||||
.font(Fonts.font(13, weight: 700))
|
||||
.padding(.top, 16)
|
||||
Text("The macOS privacy grants the server's agents use to see and control this Mac. Checking asks macOS for any that are missing.")
|
||||
Text("The macOS privacy grants the server's agents use to see and control this Mac and read its protected folders. Checking asks macOS for any that are missing.")
|
||||
.foregroundStyle(Palette.muted)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
PermissionsCheck {
|
||||
try await API(origin: origin).client.checkPermissions().ok.body.json
|
||||
try await API(origin: origin, timeout: 150).client.checkPermissions().ok.body.json
|
||||
}
|
||||
Text("Usage commands")
|
||||
.font(Fonts.font(13, weight: 700))
|
||||
|
||||
@@ -22,6 +22,13 @@ struct SettingsView: View {
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
#if os(iOS)
|
||||
heading("This iPhone's name")
|
||||
Text("byre shows this iPhone by this name, and names its tailnet node after it. A new name takes effect the next time byre opens.")
|
||||
.foregroundStyle(Palette.muted)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
DeviceNameField()
|
||||
#endif
|
||||
heading("Window color")
|
||||
Text("Windows for this server have a border in this color, on every device you use.")
|
||||
.foregroundStyle(Palette.muted)
|
||||
@@ -56,7 +63,7 @@ struct SettingsView: View {
|
||||
SyncState()
|
||||
#if os(macOS)
|
||||
heading("This Mac's permissions")
|
||||
Text("The macOS privacy grants byre's app needs for agents to see and control this Mac. Checking asks macOS for any that are missing.")
|
||||
Text("The macOS privacy grants byre's app needs for agents to see and control this Mac and read its protected folders. Checking asks macOS for any that are missing.")
|
||||
.foregroundStyle(Palette.muted)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
PermissionsCheck(run: checkLocalPermissions)
|
||||
@@ -79,6 +86,40 @@ struct SettingsView: View {
|
||||
}
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
private struct DeviceNameField: View {
|
||||
@ViewState private var name = UserDefaults.standard.string(forKey: deviceNameKey) ?? deviceKind
|
||||
@ViewState private var saved = UserDefaults.standard.string(forKey: deviceNameKey) ?? deviceKind
|
||||
|
||||
var body: some View {
|
||||
let trimmed = name.trimmingCharacters(in: .whitespaces)
|
||||
let valid = !slug(trimmed).isEmpty && trimmed != saved
|
||||
HStack(spacing: 6) {
|
||||
TextField("Name", text: $name)
|
||||
.modifier(FieldStyle())
|
||||
.onSubmit {
|
||||
save(trimmed, valid)
|
||||
}
|
||||
Button("Save") {
|
||||
save(trimmed, valid)
|
||||
}
|
||||
.buttonStyle(WebButtonStyle())
|
||||
.disabled(!valid)
|
||||
}
|
||||
.padding(.top, 8)
|
||||
}
|
||||
|
||||
private func save(_ trimmed: String, _ valid: Bool) {
|
||||
if !valid {
|
||||
return
|
||||
}
|
||||
UserDefaults.standard.set(trimmed, forKey: deviceNameKey)
|
||||
saved = trimmed
|
||||
name = trimmed
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private struct ServerColorPicker: View {
|
||||
@Environment(Store.self) private var store
|
||||
|
||||
|
||||
@@ -97,7 +97,8 @@ final class Store {
|
||||
@ObservationIgnored private var historyLoads: Set<ObjectIdentifier> = []
|
||||
@ObservationIgnored private var requestSeqs: [String: Int] = [:]
|
||||
@ObservationIgnored private var taskReloads: Set<String> = []
|
||||
@ObservationIgnored private var taskReloadAgain: Set<String> = []
|
||||
@ObservationIgnored private var listReloads: Set<String> = []
|
||||
@ObservationIgnored private var taskRefetches: [String: Set<String>] = [:]
|
||||
@ObservationIgnored private var terminalChanges = 0
|
||||
@ObservationIgnored private var streamOpens = 0
|
||||
@ObservationIgnored private var globalStream: EventStream?
|
||||
@@ -368,12 +369,70 @@ final class Store {
|
||||
}
|
||||
|
||||
func loadTasks(_ projectId: String) async throws {
|
||||
let latest = claimRequest("tasks:\(projectId)")
|
||||
let list = try await api.client.listTasks(path: .init(projectId: projectId)).ok.body.json
|
||||
if !latest() {
|
||||
try await reloadTasks(projectId, list: true)
|
||||
}
|
||||
|
||||
// Loads run one at a time per project, so a slower, older response never lands over a newer one.
|
||||
private func reloadTasks(_ projectId: String, list: Bool = false, task: String? = nil) async throws {
|
||||
if list {
|
||||
listReloads.insert(projectId)
|
||||
}
|
||||
if let task {
|
||||
taskRefetches[projectId, default: []].insert(task)
|
||||
}
|
||||
guard taskReloads.insert(projectId).inserted else {
|
||||
return
|
||||
}
|
||||
tasks[projectId] = list
|
||||
defer {
|
||||
taskReloads.remove(projectId)
|
||||
}
|
||||
var failure: (any Error)?
|
||||
while listReloads.contains(projectId) || taskRefetches[projectId] != nil {
|
||||
do {
|
||||
if listReloads.remove(projectId) != nil {
|
||||
try await loadActiveTasks(projectId)
|
||||
} else {
|
||||
try await refetchTasks(projectId, taskRefetches.removeValue(forKey: projectId)!)
|
||||
}
|
||||
} catch {
|
||||
failure = error
|
||||
}
|
||||
}
|
||||
if let failure {
|
||||
throw failure
|
||||
}
|
||||
}
|
||||
|
||||
private func loadActiveTasks(_ projectId: String) async throws {
|
||||
let list = try await api.client.listActiveTasks(path: .init(projectId: projectId)).ok.body.json
|
||||
let fresh = Set(list.map(\.id))
|
||||
let held = (tasks[projectId] ?? []).filter { !fresh.contains($0.id) }
|
||||
for t in held where t.status == .active {
|
||||
taskRefetches[projectId, default: []].insert(t.id)
|
||||
}
|
||||
try await applyTasks(projectId, list + held)
|
||||
}
|
||||
|
||||
private func refetchTasks(_ projectId: String, _ ids: Set<String>) async throws {
|
||||
let api = api
|
||||
let fetched = try await withThrowingTaskGroup(of: ByreTask.self) { group in
|
||||
for id in ids {
|
||||
group.addTask {
|
||||
try await api.client.getTask(path: .init(taskId: id)).ok.body.json
|
||||
}
|
||||
}
|
||||
var out: [ByreTask] = []
|
||||
for try await t in group {
|
||||
out.append(t)
|
||||
}
|
||||
return out
|
||||
}
|
||||
let fresh = Set(fetched.map(\.id))
|
||||
try await applyTasks(projectId, fetched + (tasks[projectId] ?? []).filter { !fresh.contains($0.id) })
|
||||
}
|
||||
|
||||
private func applyTasks(_ projectId: String, _ list: [ByreTask]) async throws {
|
||||
tasks[projectId] = list.sorted { $0.createdAt > $1.createdAt }
|
||||
for t in list where t.status == .active && !t.background {
|
||||
_ = taskFeed(t.id)
|
||||
}
|
||||
@@ -384,26 +443,15 @@ final class Store {
|
||||
}
|
||||
}
|
||||
|
||||
private func reloadTasks(_ projectId: String) async throws {
|
||||
guard taskReloads.insert(projectId).inserted else {
|
||||
taskReloadAgain.insert(projectId)
|
||||
private func lookUpTask(_ taskId: String) {
|
||||
if findTask(taskId) != nil {
|
||||
return
|
||||
}
|
||||
defer {
|
||||
taskReloads.remove(projectId)
|
||||
taskReloadAgain.remove(projectId)
|
||||
}
|
||||
var failure: (any Error)?
|
||||
repeat {
|
||||
taskReloadAgain.remove(projectId)
|
||||
do {
|
||||
try await loadTasks(projectId)
|
||||
} catch {
|
||||
failure = error
|
||||
spawn("load task") {
|
||||
let t = try await self.api.client.getTask(path: .init(taskId: taskId)).ok.body.json
|
||||
if self.findTask(taskId) == nil {
|
||||
self.tasks[t.projectId] = ((self.tasks[t.projectId] ?? []) + [t]).sorted { $0.createdAt > $1.createdAt }
|
||||
}
|
||||
} while taskReloadAgain.contains(projectId)
|
||||
if let failure {
|
||||
throw failure
|
||||
}
|
||||
}
|
||||
|
||||
@@ -674,6 +722,7 @@ final class Store {
|
||||
markVisited(taskId)
|
||||
selectedTaskId = taskId
|
||||
if let taskId {
|
||||
lookUpTask(taskId)
|
||||
_ = taskFeed(taskId)
|
||||
}
|
||||
pinned = nil
|
||||
@@ -798,11 +847,7 @@ final class Store {
|
||||
forwards.close(taskId)
|
||||
}
|
||||
notifyTask(self, ev)
|
||||
if let projectId = ev.projectId {
|
||||
spawn("load tasks") {
|
||||
try await self.reloadTasks(projectId)
|
||||
}
|
||||
}
|
||||
reloadTask(ev)
|
||||
case "session.created", "session.state", "session.subagents", "session.model":
|
||||
if ev.owner != nil {
|
||||
spawn("load lead sessions") {
|
||||
@@ -821,11 +866,7 @@ final class Store {
|
||||
spawn("load inbox") {
|
||||
try await self.loadInbox()
|
||||
}
|
||||
if let projectId = ev.projectId {
|
||||
spawn("load tasks") {
|
||||
try await self.reloadTasks(projectId)
|
||||
}
|
||||
}
|
||||
reloadTask(ev)
|
||||
case "inbox.updated":
|
||||
clearInbox(try! decodePayload(ev.payload, as: InboxItem.self))
|
||||
spawn("load inbox") {
|
||||
@@ -839,11 +880,7 @@ final class Store {
|
||||
}
|
||||
case "session.error", "session.rate_limited":
|
||||
notifyError(self, ev)
|
||||
if let projectId = ev.projectId {
|
||||
spawn("load tasks") {
|
||||
try await self.reloadTasks(projectId)
|
||||
}
|
||||
}
|
||||
reloadTask(ev)
|
||||
case "task.merge_facts":
|
||||
if let projectId = ev.projectId, let taskId = ev.taskId, let i = tasks[projectId]?.firstIndex(where: { $0.id == taskId }) {
|
||||
tasks[projectId]![i].mergeFacts = try! decodePayload(ev.payload, as: MergeFacts.self)
|
||||
@@ -876,6 +913,15 @@ final class Store {
|
||||
events.send(ev)
|
||||
}
|
||||
|
||||
private func reloadTask(_ ev: ByreEvent) {
|
||||
guard let projectId = ev.projectId, let taskId = ev.taskId else {
|
||||
return
|
||||
}
|
||||
spawn("load task") {
|
||||
try await self.reloadTasks(projectId, task: taskId)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateTask(_ id: String, _ change: (inout ByreTask) -> Void) {
|
||||
for (projectId, list) in tasks {
|
||||
if let i = list.firstIndex(where: { $0.id == id }) {
|
||||
|
||||
@@ -18,10 +18,7 @@ func runGo(_ key: String, _ store: Store, _ overlays: Overlays) {
|
||||
case "p":
|
||||
overlays.switcher?.close()
|
||||
store.sheet = nil
|
||||
if let project = store.focusedProject() ?? store.projects.first {
|
||||
overlays.projectFocus = true
|
||||
store.focusProject(project.id)
|
||||
}
|
||||
overlays.taskList = true
|
||||
case "l":
|
||||
overlays.switcher?.close()
|
||||
store.sheet = nil
|
||||
@@ -73,7 +70,7 @@ private let shortcuts: [(String, [(String, String)])] = [
|
||||
("Anywhere", [
|
||||
("⌘K", "Switch task, or create one"),
|
||||
("⌘N", "New task in the current project"),
|
||||
("⌘P", "Show the current project's dashboard"),
|
||||
("⌘P", "Show every project's active tasks"),
|
||||
("⌘L", "Open the Lead tab and its composer, closing any view opened over a pane"),
|
||||
("⌘S", "Merge the right pane's task, or discard it when the agent suggests discarding"),
|
||||
("⌘[ ⌘]", "Bring the previous or next server's window forward"),
|
||||
|
||||
@@ -27,9 +27,7 @@ func slug(_ text: String) -> String {
|
||||
final class TailnetNode: Sendable {
|
||||
let sd: tailscale
|
||||
let hostname = "byre-app-\(slug(deviceName))"
|
||||
private let proxyHost: String
|
||||
private let proxyPort: UInt16
|
||||
private let proxyCredential: String
|
||||
private let socks = TailnetProxy()
|
||||
|
||||
init() {
|
||||
let dir = URL.applicationSupportDirectory.appending(path: "run.fc.byre.mac/tsnet", directoryHint: .isDirectory)
|
||||
@@ -43,17 +41,6 @@ final class TailnetNode: Sendable {
|
||||
Self.check(sd, "set dir", unsafe tailscale_set_dir(sd, dir.path))
|
||||
Self.check(sd, "set ephemeral", tailscale_set_ephemeral(sd, 0))
|
||||
Self.check(sd, "start", tailscale_start(sd))
|
||||
var addr = [CChar](repeating: 0, count: 64)
|
||||
var proxyCred = [CChar](repeating: 0, count: 33)
|
||||
var apiCred = [CChar](repeating: 0, count: 33)
|
||||
Self.check(sd, "loopback", unsafe tailscale_loopback(sd, &addr, addr.count, &proxyCred, &apiCred))
|
||||
let address = String(decoding: addr.prefix { $0 != 0 }.map { UInt8(bitPattern: $0) }, as: UTF8.self)
|
||||
guard let colon = address.lastIndex(of: ":"), let port = UInt16(address[address.index(after: colon)...]) else {
|
||||
fatalError("tsnet loopback address \(address)")
|
||||
}
|
||||
proxyHost = String(address[..<colon])
|
||||
proxyPort = port
|
||||
proxyCredential = String(decoding: proxyCred.prefix { $0 != 0 }.map { UInt8(bitPattern: $0) }, as: UTF8.self)
|
||||
}
|
||||
|
||||
static func check(_ sd: tailscale, _ what: String, _ result: Int32) {
|
||||
@@ -69,8 +56,8 @@ final class TailnetNode: Sendable {
|
||||
}
|
||||
|
||||
func proxy() -> ProxyConfiguration {
|
||||
let proxy = ProxyConfiguration(socksv5Proxy: .hostPort(host: NWEndpoint.Host(proxyHost), port: NWEndpoint.Port(rawValue: proxyPort)!))
|
||||
proxy.applyCredential(username: "tsnet", password: proxyCredential)
|
||||
let proxy = ProxyConfiguration(socksv5Proxy: .hostPort(host: .ipv4(.loopback), port: NWEndpoint.Port(rawValue: socks.port)!))
|
||||
proxy.applyCredential(username: "byre", password: socks.credential)
|
||||
return proxy
|
||||
}
|
||||
|
||||
|
||||
@@ -536,11 +536,16 @@ final class TranscriptBuilder {
|
||||
continue
|
||||
}
|
||||
if ev.kind == "task.forked", let fork = str(p, "fork") {
|
||||
if let row = ctx.snapshots[snapKey(int(p, "forked_at") ?? 0)] {
|
||||
let at = int(p, "forked_at") ?? 0
|
||||
if at == 0 {
|
||||
put(Row(.note(Note(parts: [.text("\(ev.actor) continued this task as "), .task(fork)], when: ev.createdAt)), taskId: rowTask))
|
||||
continue
|
||||
}
|
||||
if let row = ctx.snapshots[snapKey(at)] {
|
||||
row.forks.append(fork)
|
||||
continue
|
||||
}
|
||||
put(Row(.note(Note(parts: [.text("\(ev.actor) forked snapshot #\(jsString(p["forked_at"])) as "), .task(fork)], when: ev.createdAt)), taskId: rowTask))
|
||||
put(Row(.note(Note(parts: [.text("\(ev.actor) forked snapshot #\(at) as "), .task(fork)], when: ev.createdAt)), taskId: rowTask))
|
||||
continue
|
||||
}
|
||||
if ev.kind == "review.started" || ev.kind == "review.finished" || ev.kind == "review.finding" {
|
||||
@@ -913,6 +918,12 @@ final class TranscriptBuilder {
|
||||
return note([.text("\(jsString(p["error"])) (\(jsString(p["vendor"])) \(limit["rateLimitType"].map { jsString($0) } ?? "") limit resets \(resets))")], .error)
|
||||
|
||||
case "task.created":
|
||||
if let from = str(p, "forked_from"), p["forked_at"] == nil {
|
||||
if let task = rowTask, task != taskId {
|
||||
return note([.text("\(ev.actor) continued "), .task(from), .text(" as "), .task(task), .text(" here")])
|
||||
}
|
||||
return note([.text("\(ev.actor) continued "), .task(from), .text(" here in a new task, started from main; everything above happened in that task, and its agent continues that conversation")])
|
||||
}
|
||||
if let from = str(p, "forked_from"), let task = rowTask, task != taskId {
|
||||
return note([.text("\(ev.actor) forked "), .task(task), .text(" here from "), .task(from), .text(" at snapshot #\(jsString(p["forked_at"]))")])
|
||||
}
|
||||
|
||||
+4
-2
@@ -7,5 +7,7 @@ mac=$(cd "$(dirname "$0")" && pwd)
|
||||
rm -rf "$mac/.build/phone/iphoneos/export"
|
||||
xcodebuild -exportArchive -archivePath "$mac/.build/phone/iphoneos/Byre.xcarchive" -exportOptionsPlist "$mac/phone/export.plist" -exportPath "$mac/.build/phone/iphoneos/export" -allowProvisioningUpdates
|
||||
version=$(plutil -extract CFBundleShortVersionString raw "$mac/.build/Byre.app/Contents/Info.plist")
|
||||
dmg=$mac/.build/release/$version/byre-$version.dmg
|
||||
"$mac/install.sh" "$dmg"
|
||||
mkdir -p "$HOME/byre-deploy"
|
||||
cp "$mac/.build/release/$version/byre-$version.dmg" "$HOME/byre-deploy/byre.dmg"
|
||||
cp "$mac/install.sh" "$HOME/byre-deploy/install.sh"
|
||||
echo "$HOME/byre-deploy/byre.dmg"
|
||||
|
||||
@@ -10,3 +10,11 @@ rm -rf /Applications/Byre.app
|
||||
mv /Applications/Byre.app.new /Applications/Byre.app
|
||||
plutil -extract CFBundleVersion raw /Applications/Byre.app/Contents/Info.plist
|
||||
plutil -extract ByreCommit raw /Applications/Byre.app/Contents/Info.plist
|
||||
app='^/Applications/Byre.app/Contents/MacOS/Byre$'
|
||||
if pgrep -q -a -f "$app"; then
|
||||
osascript -e 'tell application id "run.fc.byre.mac" to quit'
|
||||
while pgrep -q -a -f "$app"; do
|
||||
sleep 0.2
|
||||
done
|
||||
open /Applications/Byre.app
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user