Repository navigation
Releases: obutora/stagent
Release list
stagent v0.14.0
Added
- Tasks: a task can be tied to a name instead of an Issue or PR (
kind: name,Task.name): a worktree on a branch of that name (or the app's)
made from the base without gh, with the same copied files, shared
directories, setup and 片付け as an issue's. Its key is the checkout
and the name (an issue's number never matches it); it is never
in_place.task.createandtask.registergainname;
task.createrefuses an invalid name, a missingworktree_folderand
a place that is a non-empty directory (bad_request).task.preview
gainsname?andbranch?and answersname_problem(invalid,
task,folder,branchorno_folder) while the app's name is
typed. A push about a named task's session puts its name where an
issue's#Ngoes (#877). config.jsongainsworktree_folder, the worktree folder (worktree
の置き場): named tasks go in<folder>/<checkout name>/<name>, and
with it set Issue and PR tasks in<folder>/<checkout name>/<number>-<slug>instead of next to the checkout.config.set
expands a leading~with the host's home and refuses a path that is
not absolute (#877).
Fixed
- Host screen, Android: on Linux without
$XDG_RUNTIME_DIR(an SSH
session in a container without logind,su), the emulator's discovery
file is also read where the emulator falls back to when/run/user/<uid>
does not exist:avd/runningin its user directory (~/.android, or
$ANDROID_EMULATOR_HOME,$ANDROID_PREFS_ROOT,$ANDROID_SDK_HOMEas
the emulator takes them). Such an emulator was listedno_grpcand
host_screen.startansweredunavailable(#705).
stagent 0.14.0 — server binary for SSH Term. Built from this tag (f4509af) with go1.26.1 via scripts/release.sh (reproducible: CGO_ENABLED=0, -trimpath, no VCS stamp, empty build id); verify downloads against SHA256SUMS.
stagent v0.13.0
Added
- Host screen: an iOS simulator takes
textof any script, Japanese
included (it took ASCII only and answered other textunsupported;
#649, #665). The text is put on the simulator's clipboard (xcrun simctl pbcopy) and pasted by what the app in front may do, read
(never written) from the simulator's TCC.db: an app allowed to paste
from other apps gets ⌘V, and keys (the Return ofenter, the next
text) wait 3 s after it, while iOS drops them under its "pasted from"
banner; otherwise ASCII is typed after the 英数 key (LANG2), and other
text is pasted with the edit menu's Paste, opened by tapping the user's
cursor (textgainsx?,y?). An app that never asked first gets a
⌘V iOS refuses, which shows its 「ほかのアプリからペースト」 in Settings.
host_screen.inputanswers such a text{via: "cmd_v" | "edit_menu" | "keys", app?}; without the edit menu's Paste it fails with the new
errorpaste_by_hand, the text left on the clipboard. The companion
gainsaccessibility_infoandlist_appsin stagent's subset of
idb.proto.
Fixed
- Host screen, Android: the discovery file an emulator leaves behind
when it stops (avd/running/pid_<pid>.ini) is no longer read once its
<pid>is no running process (signal 0 on Linux and macOS, the
process's handle on Windows; the file is left as it is). An emulator
started with-portonly on the serial of such a file was listed
connectable: truewith the file's gRPC port, andhost_screen.start
answered{}, thengonefor good; it is nowno_grpc.startof
an emulator whose gRPC does not answer whileadb deviceslists its
serial isunavailable(no_grpc) instead of{}andgone; one
adb does not list still waits for its AVD to run again (#679).
stagent 0.13.0 — server binary for SSH Term. Built from this tag (c177d9b) with go1.27.1 via scripts/release.sh (reproducible: CGO_ENABLED=0, -trimpath, no VCS stamp, empty build id); verify downloads against SHA256SUMS.
stagent v0.12.0
Added
host_screen.list: on a host where stagent runs in Windows' WSL, the
browserandandroidsections havenote: {code: "wsl", message}
(Section<T>gainsnote?), since the browsers and emulators on the
Windows side are not visible from WSL (#675).- Host screen: an Android emulator or iOS simulator that stops no longer
ends the stream. Afterstate: "gone"stagent looks for it every 3
seconds and, once it runs again, connects anew and sendsstate: "live"and frames in the same tab: an emulator by its AVD on any
serial (Targethasavd;grpc.portandgrpc.tokenread again),
a simulator by itsudid(its companion started again).startof
one that is not running answers{}and sendsgone, then waits the
same way; so does an iOSstartwhile simctl fails to list (a
timeout, say), which wasnot_found. Only no Xcode is still
not_found(#662). - Host screen:
host_screen.pauseandhost_screen.resume(no params,
{}) stop and restart capturing while the app does not show the tab
(another tab, the app in the background, the screen off; #634).
pausedrops the frame kept and stops the target's capture: a
browser'sPage.stopScreencast(its WebSocket stays open), an Android
emulator'sstreamScreenshotcut, an iOS simulator's MJPEG video
stream closed (the companion kept, askeddescribeevery 5/3 s so it
does not shut down idle).resumesends one full frame at once, then
streams again. Likeack, both are answered at once, never behind a
slowinput; without a stream →bad_request;pausewhile paused
andresumewhile not paused →{}. Acks are answered while paused
(#651).
Changed
host_screen.inputon an Android emulator pastes everytext, ASCII
too (gRPCsetClipboard, thenKEYCODE_PASTE), instead ofadb shell input text, which a Japanese Gboard typed as romaji to kana ("wifi
test" became "ウィフぃてst").enteralone sends onlyKEYCODE_ENTER
and leaves the clipboard alone (#672).- Host screen: an Android emulator's image with the same pixels as the
last one is dropped before JPEG encoding, so a still display sends no
frame (the emulator sends about 20 a second); an iOS simulator's MJPEG
is quality 0.5 (was 0.7) at scale 0.5 andfps0 (#662). - Host screen: at most 10 frames a second are sent. A frame captured
within 100 ms of the last one sent waits in the place that keeps the
newest frame (the one used while two frames wait for their ack), and
is sent once 100 ms passed (#651).
Fixed
- Host screen, Android: an
emulator-<port>thatadb devicesshows
offlinewithout a discovery file (any process listening on an odd
port of 5555–5585) is no longer listed as ano_grpcemulator, nor
askedemu avd name(which heldhost_screen.listup ~2 s);
host_screen.startfinds no emulator there. Booting emulators (offline
with a file) and ones without a file in thedevicestate are still
listed (#673). host_screen.listlists browsers on Linux (WSL too): Chromium writes
its command line over its arguments as one space-joined string, so no
browser's--remote-debugging-port/--remote-debugging-pipewas
found and none was listed. Such a command line is now read for its
executable and switches (split at--; a--user-data-dirwith
spaces is found, child processes with--type=are still left out);
NUL-separated command lines read as before (#671).
stagent 0.12.0 — server binary for SSH Term. Built from this tag (0eb7f98) with go1.27.1 via scripts/release.sh (reproducible: CGO_ENABLED=0, -trimpath, no VCS stamp, empty build id); verify downloads against SHA256SUMS.
stagent v0.11.0
Added
- Host screen (ホストの画面, ADR 0008):
host_screen.list,
host_screen.start,host_screen.ack,host_screen.stopand the
notificationshost_screen.frameandhost_screen.stateshow a page of
a browser an agent drives in an app tab, over the tab's own bridge. The
browsers listed are the user's Chromium-family browsers started with
--remote-debugging-port(0read fromDevToolsActivePort), each
port checked with/json/version, with their page targets and the tool
told by the profile path. A page is shown with one
Page.captureScreenshotand thenPage.startScreencast(JPEG, quality
70); at most two frames wait for the app's ack, of the frames taken
meanwhile only the newest is kept, and a stalled app gets the
screencast paused and resumed with a full frame. Served to bridges
under a coding agent too (#640). host_screen.list: a browser started with--remote-debugging-pipe
only is listed withconnectable: falseandreason: "pipe_only"
(with its tool), andcdp_portsnames ports on 127.0.0.1 by hand: each
that answers/json/versionas CDP is listed withmanual: true, in
one item with a browser found on the same port; a port outside 1–65535
isbad_request(#642).- Host screen: Android emulators.
host_screen.listhas anandroid
section: the emulators ofadb devices -l(never a physical device),
matched by serial with the files they advertise their gRPC port in
(avd/running/pid_<pid>.ini), with the AVD name; one without gRPC
(started with-portonly) isconnectable: false,reason: "no_grpc", and a host without adb getsmissing: {code: "adb_not_found"}.host_screen.start {target: {kind: "android", serial}}shows the emulator through its gRPCstreamScreenshot
(Bearergrpc.token, RGB888 at half resolution, encoded to JPEG
quality 70);widthandheightare the display's px and swap when
it turns, and a stopped emulator sendsstate: "gone"(#643). - Host screen of an iOS simulator (macOS only, capability
host_screen_ios):host_screen.listhas aniossection with the
booted iOS simulators (xcrun simctl;missingxcode_not_found
without Xcode,simctl_failedwith simctl's error as the message when
it fails or runs out of time, a simulatorcompanion_not_foundwithout
idb_companion), andhost_screen.start {target: {kind: "ios", udid}}
shows one through an idb_companion started for the stream on a private
Unix socket (no TCP port): its MJPEGvideo_stream(half resolution,
quality 0.7, redrawn screens only), sized in points. The companion is
stopped with the stream (stop, anotherstart, the bridge exiting;
--idle-shutdown-timeshould stagent die), and a simulator shut down
sendsstate: "gone". Other hosts answerunsupported(#645). host_screen.inputoperates the page shown:tap,drag,scroll,
textandnavigate(back,forward,reload,url), points as
fractions of the frame turned into CSS px with the screencast's
metadata (deviceWidth,deviceHeight,offsetTop,
pageScaleFactor). A browser gets onlyInput.*(insertTextfor
text in any script, an Enter key event forenter),Page.navigate,
Page.reloadand the history'sPage.getNavigationHistoryand
Page.navigateToHistoryEntry;keyisunsupported. Inputs
run in order onstart's queue while acks are answered at once, so a
slow input never stops the frames (#641).host_screen.inputoperates an Android emulator:tap,dragand
scroll(one swipe from the cursor) through gRPCsendTouchin the
display's px, turned back with the emulator's rotation;keyback,
home,recentsthroughadb shell input keyevent(works with
hw.keyboard=no, no gRPCsendKey);textthroughadb shell input textwhen printable ASCII, else gRPCsetClipboardand
KEYCODE_PASTE, withKEYCODE_ENTERforenter.navigateis
unsupported(#644).host_screen.inputoperates an iOS simulator:tap,drag,scroll
(one swipe from the cursor),textof ASCII andkey: "home", as HID
events on the stream's idb_companionhidstream, kept open and
reopened when the companion ends it (no program per input). Points are
fractions of the frame in pt; text is typed as key presses of a US
keyboard (Shift for capitals and the upper symbols),enteras Return.
back,recents,navigateand text with a character other than
ASCII (Japanese input is pending, #649) areunsupported. A drag cut
short (the bridge ending) lifts its touch, and stopping the stream lets
the companion perform what was sent (up to a second) before stopping
it (#646).
stagent 0.11.0 — server binary for SSH Term. Built from this tag (0208b4f) with go1.27.1 via scripts/release.sh (reproducible: CGO_ENABLED=0, -trimpath, no VCS stamp, empty build id); verify downloads against SHA256SUMS.
stagent v0.10.1
Fixed
- Windows: a task's shared directories (
worktree.sharedDirectories) of a
source checkout on a network share (a UNC path or a mapped drive) are
no longer junctions. An administrator could make one there, but the
share's server resolves it as its own path, so the copy stage said
linkedand the worktree's directory did not open. Such a directory is
now a directory symbolic link, or copied with the one warning line when
that cannot be made either (#531). follow: an omp chat showed the conversation of another agent in the
same directory, the one the PC was just working in, while the terminal
showed its own: an omp that has not written since it started (omp -r
after picking a session,omp -c, a session resumed or switched to in
omp, a new session before its first answer), and on macOS, where
stagent cannot see open files, any omp once the other agent wrote last.
The session omp records for the agent's terminal
(~/.omp/agent/terminal-sessions/) now decides, and a new session not
written yet shows no conversation instead of a guess.
stagent 0.10.1 — server binary for SSH Term. Built from this tag (5e66294) with go1.27.1 via scripts/release.sh (reproducible: CGO_ENABLED=0, -trimpath, no VCS stamp, empty build id); verify downloads against SHA256SUMS.
stagent v0.10.0
Added
github.status {items[{repo, kind, number, branch?}]}(Windows,
capabilitygithub) reports the state gh gives each issue or pull
request (OPEN,CLOSED,MERGED), and for an issue withbranch
the state of that branch's pull request aspr_state(the answer
echoingbranch), so the app can
mark tasks whose issue was closed or whose pull request was merged;
github.activityonly lists open ones. Items gh cannot answer are left
out (#502).- Tasks (タスク): an Issue or PR worked on in a worktree of a source
checkout, or in the checkout itself (in_place). The daemon keeps them
instate/tasks.json(mode 0600; only the daemon writes it, only
uninstall --level purgeremoves it), keyed by the checkout's
canonical path and the number.task.listanswers every task in the
order they were created,watchgainstasks[], andtask.updated
(the wholeTask) andtask.removed {id}follow the changes. Aready
task whose worktree directory is gone or thatgit worktree listno
longer lists becomesmissingontask.list; apreparingtask whose
prepared session ended or never registered becomesfailedwith
error: "interrupted", also after a daemon restart, and a task still
removingwhen the daemon starts isreadywith that error, keeping
itsstage. A changetasks.jsoncannot take is not made: the call
answersinternal. After a daemon restart the bridge's restored watch
sends every task astask.updatedand gone ones astask.removed
(#502). Session.task_id: the daemon ties a session to the worktree task whose
worktree holds itscwd(also live sessions, when the task is
registered), and to anin_placetask only its prepared session and
sessions started with the newsession.spawn {task_id}(#502).- Local IPC for the bridge and
stagent task:task.register(atomic by
the key; afailedtask is prepared again, another number'sin_place
task on the checkout is the new errorin_use),task.progress {id, stage, error?, session_id?}andtask.forget {id}, refused with
agent_refusedto a coding agent's process tree likeconfig.set.
stopon a task alreadyremovingisbusy, so only one removal runs;
stageagentwith an error from the prepared session fails aready
task. Error codesnot_cleanandbusyare reserved fortask.remove
(#502). - Pushes of a session tied to a task are titled
<host> · #N <title>on
ntfy, the generic webhook and chat destinations; a digest of several is
not (#502). task.create(bridge, spawn queue) creates a task and starts its first
session: the daemon registers it ({task, session?, existed}: aready
task starts nothing, apreparingone answers its live prepared
session, afailedone is prepared again), the bridge makes the empty
directory<parent of the checkout>/<checkout name>-<N>and starts the
session there (in the checkout itself forin_place), so itscwdis
the task's place from the start. In it the internalstagent task run <id> -- <command…>fetches, makes the branch (an issue's<N>-<slug>
or the givenbranch, linked withgh issue develop, made locally
frombasewhen gh fails; a PR's head branch withgh pr checkoutin
a detached worktree), adds or adopts the worktree, runsorca.yaml's
scripts.setup(login shell; on Windows a.cmdrunner, or Git for
Windows' bash for a#!script) and then becomes the agent, reporting
each stage withtask.progressand showing everything in the
terminal. A failing stage leaves the taskfailedwithout an agent —
also when the agent itself cannot be started after stageagent;
task.createagain carries on. An emptycommandopens a terminal.
Refused withagent_refusedto a coding agent's process tree (#502).- A task's prepared session reports the harness of the command
stagent task runruns (Session.harness), and a Codex there gets
--no-daemonlike any other (#502). - A worktree task gets its files to copy and shared directories in the
newcopystage ofstagent task run, betweenworktreeandsetup:
orca.yaml'sworktree.sharedDirectoriesof the source checkout that
are gitignored are linked (a symbolic link; on Windows a junction, else
a directory symbolic link; one inside another listed one goes with it),
then the gitignored files the checkout's
.worktreeinclude(Orca's rules: literal paths — a line with*,?
or[is invalid —, at most 1,000, a file
up to 256 KiB) and the app's patterns (config.json
repos.<checkout>.worktree_include, git pathspec globs) select are
copied, up to 2 GiB and 50,000 entries. Tracked, not ignored, missing,
unmatched, invalid and over-the-limit entries and what lies in a linked
directory are skipped with a line in the terminal (tracked ones
silently); a destination that exists is left alone, so a retry copies
only what is missing. A directory that cannot be linked is one warning
line and what is listed under it is copied instead; nothing here fails
the task (#502). task.preview {repo, worktree_include?}(bridge, its own queue,
allowed for agent descendants) answers what a new worktree of the
checkout would get, resolved by the same code:copies(at most 500,
truncated),links,skippedwith the reason (tracked,
not_ignored,missing,no_match,under_link,invalid,
limit),total_bytes, the checkout'sorca.yamlsetupand
orca_error.worktree_includetries patterns that are not saved
(#502).config.jsonrepos.<checkout>.worktree_include, set one checkout at
a time withconfig.set(nullremoves one);config.setrefuses
withbad_requesta key that is not an absolute clean path and a
pattern that is empty, a negation, absolute or has a..or.git
component (#502).task.remove {id, force?, stop_sessions?, skip_archive?, dry_run?}
(bridge, its own queue) removes a task (片付け).dry_runreports the
worktree's uncommitted changes asgit status --porcelainlines
(changes[], ignored files and shared-directory links left out),
the branch's commits on no remote (unpushed) and the task's live
sessions (sessions[]). Changes needforce(not_clean), live
sessionsstop_sessions(busy): they get hangup, then kill after 5 s,
and sessions bound to the task meanwhile are stopped too. Apreparing
task isbusywithoutstop_sessions; with it its sessions are stopped
first (it fails, interrupted) and the removal goes on,in_placetoo.
Thenorca.yaml'sscripts.archiveof the source checkout runs in the
worktree — in a session of the source checkout running the internal
stagent task remove <id>, so its output shows in a terminal — unless
skip_archive; only the shared directories' links are unlinked,git worktree removeruns, and the branch is deleted when git sees it
merged or gh says its pull request was merged at the branch's commit
(a squash merge; a PR task's pull request is looked up by its number,
so a fork's is found too), otherwise kept and named inkept_branch; remote
branches are never deleted. Each stage isTask.stagewhile
removing; a failing one (archive,git worktree remove) takes the
task back with itserror, keeping thatstage(the app offers to skip
the archive when it isarchive), and answersinternal; a second
removal of a task being removed isbusy. The reply comes when
the removal has finished. Amissingtask is forgotten and its
worktree pruned, anin_placeone only forgotten. Refused with
agent_refusedto a coding agent's process tree (#502).task.forgettakeskept_branch?andtask.removedcarries it, so
every watcher learns the branch a removal kept (#502).
Fixed
- A chat message no longer lands on claude's workspace trust prompt
(Accessing workspace:, shown at startup in a folder not trusted yet).
Its options have no numbers, so the holder did not see a menu, and the
message's Enter picked the highlightedNo, exit, ending claude. Now
session.inputrefuses the message withmenu_openwhile the prompt is
up, as on Codex's folder trust prompt; the user answers it in the
terminal, and claude then sends the initial prompt it held. stagent
writes no trust (#502). session.spawnof a program that ends at once no longer fails with
holder exited during startwhen the holder ended with it before the
bridge reached it: the answer is the session the daemon lists. The same
race madetask.createreport a task run that failed at once as a
start failure, andtask.removean archive that failed at once (#502).- Under proot's fake root (
proot -0, an Ubuntu guest on Android), the
chat view and the agent probe find the agents: stagent took its
own uid fromgeteuid, which proot fakes, and every process's from
/proc/<pid>/status, which proot leaves at the kernel's uid, so every
process counted as another user's. On Linux stagent now reads its own
uids from/proc/self/statustoo; elsewhere the uids agree and nothing
changes (#529).
stagent 0.10.0 — server binary for SSH Term. Built from this tag (abda34b) with go1.27.1 via scripts/release.sh (reproducible: CGO_ENABLED=0, -trimpath, no VCS stamp, empty build id); verify downloads against SHA256SUMS.
stagent v0.9.0
Added
- The GitHub screen works on native Windows hosts, which have no
shfor
the app's scripts: on Windows the bridge announcesgithuband serves
github.repos(the git working trees the user worked in last, newest
first, with the fields of the app's sh script; candidates are the
working directories of the user's processes under their SSH connections
and holders, read from the PEB, the sessions'cwdand the latest 50
conversations'cwd; at most 20 are inspected) andgithub.activity
(gh's open pull requests and issues, the branch's pull request, the
repository's description;gh: missingwithpackage_manager: winget
orunauthenticated). Both run git and gh on their own queue, so the
daemon and spawns never wait for them, and answer agent descendants
too. Linux and macOS bridges neither announce nor serve them. A bare
PowerShell a holder starts (ashell: truesession of a host whose
sshd runs PowerShell) gets a prompt wrapper that keeps the process's
working directory at the shell's location, soSet-Location/sl/
cd..move the repository the GitHub screen sees; the session keeps
its command as given. The shell wrapper's PowerShell profile block has
the same prompt wrapper, for the terminals sshd starts in PowerShell
(an update rewrites the block on hosts that have it) (#502).
stagent 0.9.0 — server binary for SSH Term. Built from this tag (064c4ab) with go1.27.1 via scripts/release.sh (reproducible: CGO_ENABLED=0, -trimpath, no VCS stamp, empty build id); verify downloads against SHA256SUMS.
stagent v0.8.0
Added
- Pushes can go to Discord, Slack and Telegram:
notify.chat {enabled, url}, a chat destination next to the generic webhook (both can be on
and both get every push). The one-lineurlnames the service — a
Discord webhook (https://discord.com/api/webhooks/<id>/<token>,
discord://<id>/<token>), a Slack Incoming Webhook
(https://hooks.slack.com/services/…,slack://<a>/<b>/<c>) or a
Telegram bot and chat (tgram://<bot token>/<chat id>) — and stagent
builds each service's message itself: the reason's emoji, the
host-labelled title and the body, as a Discord embed (yellow for a
warning, no mentions), Slack text (&<>escaped, no unfurling) or
Telegram plain text (no link preview).config.setrefuses any other
url, Slack Workflow webhooks included, withbad_request. Theurl
is a secret: it never shows inlast_error(channelchat),
notify.testerrors,stagent doctoror the log. The bridge announces
push_chat(#471). notify.test {channels?: ["ntfy" | "webhook" | "chat"]}tests only the
channels named; withoutchannels, every enabled one as before. An
unknown name isbad_request, named channels that are all off
not_configured(#471).notify.click_page: the open page (https://sshterm.iru-yo.com/open,
set by the app) that chat destinations and the generic webhook link to,
withh=<host_id>ands=<session id>in its fragment
(<click_page>#h=…&s=…). A push of one session gets an "Open in SSH
Term" link (innotify.lang): a link button on Discord
(?with_components=true) and Telegram (inline keyboard), a link at the
end of the text on Slack; a digest,notify.testand a push of no
session get none. The generic webhook's JSON gainsclick_url(the
host only,#h=…, for a digest andnotify.test); its other keys stay.
Noclick_page, no link. ntfy'sClickstill usesnotify.click_base.
config.setrefuses aclick_pagethat is not an absolute https URL
without query and fragment withbad_request(#472).- On Discord and Telegram, the chat destination replaces and resolves
like ntfy: the next message of a session is sent first, then the
older one deleted (DiscordDELETE …/messages/<id>, Telegram
deleteMessage; one Telegram no longer deletes after 48 hours is
marked resolved instead), and when the session settles its message is
edited, not deleted: ☑️ in place of the reason's emoji, "(resolved)"
after the title innotify.lang(ja(解決済み)), the body as it
was and the same "Open in SSH Term" button (Discord: grey embed,
components sent again; Telegram:editMessageTextwithreply_markup
sent again). A digest's message is marked resolved once all its
sessions have settled and is never deleted by a later message of one
of them. The unresolved messages survive a daemon restart in
state/chat-messages.json(mode 0600: ids, sent time and the SHA-256
ofnotify.chat.url, never the URL or its token); a session gone over
the restart has its message marked resolved at once. Changing
notify.chat.urlor removingchatforgets the messages of the old
URL without editing them. A failed delete or edit is never retried nor
a failed push (last_error): a 404 lets the message go, a 429 or a
connection error lets it go with one log line. Slack messages are only
sent, and the generic webhook still gets no clear (#473). - A failed push to the chat destination says why:
last_error.chatgains
kind(revoked,unreachable,rate_limited,rejected,server,
network; Telegram byerror_code) and itserroradds the service's
short reason after the status line (404 Not Found: Unknown Webhook (10015); Discord'smessageand code, Telegram'sdescription,
Slack's error body; 200 characters at most, dropped whole if it holds
the URL or a token). A queued push answered 429 with a wait of 5 s or
less is sent once more after the wait (notify.testis not); only a
failed resend is recorded. Pushes keep going after a failure, and the
log shows only the first of a run of the same kind.config.set
changingnotify.chat.urlremoveslast_error.chat;stagent doctor
names the kind and, forrevoked, says to set a new URL from the app
(#474).
stagent 0.8.0 — server binary for SSH Term. Built from this tag (00e7220) with go1.27.1 via scripts/release.sh (reproducible: CGO_ENABLED=0, -trimpath, no VCS stamp, empty build id); verify downloads against SHA256SUMS.
stagent v0.7.2
Fixed
- A chat message whose turn to be written comes after an approval menu
came up is no longer typed into the menu. The holder looked for a menu
only whensession.inputarrived, then wrote the message's text from
its input queue without looking again, so a menu shown in between could
take the text's first characters as its keys. The queue now looks again
right before writing the text and drops the whole message (text and
Enter) if a menu is up;session.inputanswers a chat message only once
its text is written, and withmenu_openwhen it was dropped, so the
app sends it again after the menu as it does for one refused on arrival
(#423). - A chat message sent while Codex shows its update notice or folder trust
prompt at startup is no longer typed into it. The holder refused chat
messages only on approval menus (1. Yes…), so the message's Enter
picked1. Update now(Codex updated itself) or1. Trust and continue
(the folder was trusted).session.inputwithpasteandsubmitnow
fails withmenu_openwhile any menu is on the screen — numbered
options with one under the cursor and a key hint below them, as the app
reads menus — and an Enter that would land on one is held as for an
approval (#440). - A numbered prompt sent to Codex no longer reads as a menu while Codex
works on it. Codex's status line (• Working (3s • esc to interrupt))
blinks its bullet to◦, which the holder did not take for a
transcript entry, so the line'sescread as a menu's key hint under
the prompt's numbered lines and chat messages were refused with
menu_open(#440). - Windows: a
\\.\pipe\stagent-<SID>another user created first with a
DACL that refuses the user is now reported with its owner —blocked
inhelloandstagent doctor,foreign_ownerfor requests — instead
of failing as a plain access denied. stagent reads the owner by name
(READ_CONTROLonly); a pipe that refuses that too still fails as
before, and one the user owns, orBUILTIN\Administratorsowns (an
administrator's elevated daemon dialed unelevated), keeps its access
denied (#422).
stagent 0.7.2 — server binary for SSH Term. Built from this tag (3e63066) with go1.26.1 via scripts/release.sh (reproducible: CGO_ENABLED=0, -trimpath, no VCS stamp, empty build id); verify downloads against SHA256SUMS.
stagent v0.7.1
Security
- On macOS, stagent now also refuses connections from processes in a
sandbox (Seatbelt), as Claude Code and Codex run their commands. The
macOS sandbox has no PID namespace, so a sandboxed command allowed to
reach~/.ssh-term/agent/run(sandbox.network.allowUnixSockets)
could double fork (orsetsid) out of the agent's process tree, and
so past the parent walk of 0.7.0, while staying sandboxed. Such a
connection, and one handed to such a child, now getsagent_refused
as a holder or the daemon answers an agent's; hooks and reads are
still served. stagent askssandbox_check, which Apple does not
document; if a macOS lacks it or it answers otherwise, this check is
left out and the parent walk alone decides, as in 0.7.0 (ADR 0004,
#415).
stagent 0.7.1 — server binary for SSH Term. Built from this tag (1406849) with go1.26.1 via scripts/release.sh (reproducible: CGO_ENABLED=0, -trimpath, no VCS stamp, empty build id); verify downloads against SHA256SUMS.