56 Commits
Author SHA1 Message Date
yhh 83bdeb81f0 feat(studio): the workbench gets a terminal of your own, as the Terminal board draws it
A fourth kind of tab opens a real shell (ConPTY on Windows, a pty elsewhere) in
the project folder, outside the model's permissions and sandbox and with the
host's provider keys left out of its environment. The kernel keeps each shell's
output, so a tab left or a window reloaded comes back as it was; a tab is named
for the command submitted while the shell is busy and carries a blue dot, and
closing a busy one asks first.

Also: the composer's note stack no longer clips to a few pixels when it holds
two notes, which left the second one overlapping the input.
2026-11-06 04:44:41 -08:00
dependabot[bot] 34707b672f chore(deps): bump the go group with 4 updates
---
updated-dependencies:
- dependency-name: charm.land/bubbletea/v2
  dependency-version: 2.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go
- dependency-name: github.com/bmatcuk/doublestar/v4
  dependency-version: 4.10.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go
- dependency-name: github.com/pkg/sftp
  dependency-version: 1.13.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go
- dependency-name: github.com/tdewolff/parse/v2
  dependency-version: 2.8.20
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-11-01 11:56:25 +00:00
esengine 80ebd88bf7 fix(deps): bump golang.org/x/net to v0.62.0 for GO-2026-6860 2026-10-27 19:33:39 -07:00
esengine 682ecbe392 build(deps): bump modernc.org/sqlite to 1.61.0 and tdewolff/parse to 2.8.19 2026-10-25 06:56:32 -07:00
esengine 751def6d32 chore(deps): bump the go group with 11 updates, including the desktop module 2026-10-18 06:37:48 -07:00
esengine 674712eadf feat(ext): a static preview of a mod's hooks module from its syntax tree 2026-10-18 00:35:27 -07:00
dependabot[bot] ded403ab37 build(deps): bump the go group with 2 updates
Bumps the go group with 2 updates: [github.com/mattn/go-runewidth](https://github.com/mattn/go-runewidth) and [golang.org/x/sys](https://github.com/golang/sys).


Updates `github.com/mattn/go-runewidth` from 0.0.30 to 0.0.31
- [Commits](https://github.com/mattn/go-runewidth/compare/v0.0.30...v0.0.31)

Updates `golang.org/x/sys` from 0.48.0 to 0.49.0
- [Commits](https://github.com/golang/sys/compare/v0.48.0...v0.49.0)

---
updated-dependencies:
- dependency-name: github.com/mattn/go-runewidth
  dependency-version: 0.0.31
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go
- dependency-name: golang.org/x/sys
  dependency-version: 0.49.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-10-11 11:53:35 +00:00
yhh c8622f7a8c chore(deps): bump Go toolchain to 1.26.9 and golang.org/x/net to v0.60.0 2026-10-08 19:27:43 -07:00
dependabot[bot] 87d1ebd1d4 build(deps): bump the go group with 2 updates
Bumps the go group with 2 updates: [github.com/klauspost/compress](https://github.com/klauspost/compress) and [modernc.org/sqlite](https://gitlab.com/cznic/sqlite).


Updates `github.com/klauspost/compress` from 1.20.0 to 1.20.1
- [Release notes](https://github.com/klauspost/compress/releases)
- [Commits](https://github.com/klauspost/compress/compare/v1.20.0...v1.20.1)

Updates `modernc.org/sqlite` from 1.59.0 to 1.60.1
- [Changelog](https://gitlab.com/cznic/sqlite/blob/master/CHANGELOG.md)
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.59.0...v1.60.1)

---
updated-dependencies:
- dependency-name: github.com/klauspost/compress
  dependency-version: 1.20.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go
- dependency-name: modernc.org/sqlite
  dependency-version: 1.60.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-10-04 11:53:05 +00:00
dependabot[bot] d222a50302 build(deps): bump the go group with 13 updates
Bumps the go group with 13 updates:

| Package | From | To |
| --- | --- | --- |
| [charm.land/bubbletea/v2](https://github.com/charmbracelet/bubbletea) | `2.0.9` | `2.0.10` |
| [github.com/bmatcuk/doublestar/v4](https://github.com/bmatcuk/doublestar) | `4.10.0` | `4.10.2` |
| [github.com/mattn/go-runewidth](https://github.com/mattn/go-runewidth) | `0.0.28` | `0.0.30` |
| [github.com/yuin/goldmark](https://github.com/yuin/goldmark) | `1.8.5` | `1.8.6` |
| [golang.org/x/crypto](https://github.com/golang/crypto) | `0.56.0` | `0.57.0` |
| [golang.org/x/image](https://github.com/golang/image) | `0.45.0` | `0.46.0` |
| [golang.org/x/mod](https://github.com/golang/mod) | `0.40.0` | `0.41.0` |
| [golang.org/x/net](https://github.com/golang/net) | `0.58.0` | `0.59.0` |
| [golang.org/x/sys](https://github.com/golang/sys) | `0.47.0` | `0.48.0` |
| [golang.org/x/term](https://github.com/golang/term) | `0.45.0` | `0.46.0` |
| [golang.org/x/text](https://github.com/golang/text) | `0.41.0` | `0.42.0` |
| [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) | `1.58.0` | `1.59.0` |
| [mvdan.cc/sh/v3](https://github.com/mvdan/sh) | `3.14.0` | `3.14.1` |


Updates `charm.land/bubbletea/v2` from 2.0.9 to 2.0.10
- [Release notes](https://github.com/charmbracelet/bubbletea/releases)
- [Commits](https://github.com/charmbracelet/bubbletea/compare/v2.0.9...v2.0.10)

Updates `github.com/bmatcuk/doublestar/v4` from 4.10.0 to 4.10.2
- [Release notes](https://github.com/bmatcuk/doublestar/releases)
- [Commits](https://github.com/bmatcuk/doublestar/compare/v4.10.0...v4.10.2)

Updates `github.com/mattn/go-runewidth` from 0.0.28 to 0.0.30
- [Commits](https://github.com/mattn/go-runewidth/compare/v0.0.28...v0.0.30)

Updates `github.com/yuin/goldmark` from 1.8.5 to 1.8.6
- [Release notes](https://github.com/yuin/goldmark/releases)
- [Commits](https://github.com/yuin/goldmark/compare/v1.8.5...v1.8.6)

Updates `golang.org/x/crypto` from 0.56.0 to 0.57.0
- [Commits](https://github.com/golang/crypto/compare/v0.56.0...v0.57.0)

Updates `golang.org/x/image` from 0.45.0 to 0.46.0
- [Commits](https://github.com/golang/image/compare/v0.45.0...v0.46.0)

Updates `golang.org/x/mod` from 0.40.0 to 0.41.0
- [Commits](https://github.com/golang/mod/compare/v0.40.0...v0.41.0)

Updates `golang.org/x/net` from 0.58.0 to 0.59.0
- [Commits](https://github.com/golang/net/compare/v0.58.0...v0.59.0)

Updates `golang.org/x/sys` from 0.47.0 to 0.48.0
- [Commits](https://github.com/golang/sys/compare/v0.47.0...v0.48.0)

Updates `golang.org/x/term` from 0.45.0 to 0.46.0
- [Commits](https://github.com/golang/term/compare/v0.45.0...v0.46.0)

Updates `golang.org/x/text` from 0.41.0 to 0.42.0
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.41.0...v0.42.0)

Updates `modernc.org/sqlite` from 1.58.0 to 1.59.0
- [Changelog](https://gitlab.com/cznic/sqlite/blob/master/CHANGELOG.md)
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.58.0...v1.59.0)

Updates `mvdan.cc/sh/v3` from 3.14.0 to 3.14.1
- [Release notes](https://github.com/mvdan/sh/releases)
- [Changelog](https://github.com/mvdan/sh/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mvdan/sh/compare/v3.14.0...v3.14.1)

---
updated-dependencies:
- dependency-name: charm.land/bubbletea/v2
  dependency-version: 2.0.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go
- dependency-name: github.com/bmatcuk/doublestar/v4
  dependency-version: 4.10.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go
- dependency-name: github.com/mattn/go-runewidth
  dependency-version: 0.0.30
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go
- dependency-name: github.com/yuin/goldmark
  dependency-version: 1.8.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go
- dependency-name: golang.org/x/crypto
  dependency-version: 0.57.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: golang.org/x/image
  dependency-version: 0.46.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: golang.org/x/mod
  dependency-version: 0.41.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: golang.org/x/net
  dependency-version: 0.59.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: golang.org/x/sys
  dependency-version: 0.48.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: golang.org/x/term
  dependency-version: 0.46.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: golang.org/x/text
  dependency-version: 0.42.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: modernc.org/sqlite
  dependency-version: 1.59.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: mvdan.cc/sh/v3
  dependency-version: 3.14.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-09-27 11:53:16 +00:00
yhh 887c17e4bf feat(delta): update an install by the chunks it lacks, not a whole installer
Every Studio update downloads the whole installer (115 MB on Windows),
even when a release changes a few megabytes. Measured on the real
v2.18.1 -> v2.19.0 Windows builds: the Electron binary is identical but
for its version resource, the kernel changes, and the frontend changes
by about half a megabyte.

internal/platform/delta describes a release as a signed-to-be Index: every
file's path, size and SHA-256, and the content-defined chunks it is made
of (normalized gear CDC, 4/16/64 KiB). Chunks are stored zstd-compressed
under the SHA-256 of their plain bytes, so any chunk two releases share
is stored and fetched once. An update cuts the running install with the
same chunker; content-defined boundaries move with an insertion instead
of shifting every chunk after it. It fetches only the missing chunks,
checking each against its name, and writes the new tree to a staging
directory, checking every file against the Index. Validate refuses
paths that leave the tree, backslashes and drive letters, two paths
that are one file on Windows, and chunks that do not add up to their
file.

cmd/studio-delta builds an Index and chunk store from a release zip, and
replays an update from an unpacked older release. On the real pair it
fetches 21.9 MB instead of 115 MB, reuses 344 of 387 MB, and verifies
the staged tree, in about five seconds.

Nothing publishes or consumes this yet. Wiring it into the release
workflow and the updater comes next.

Adds github.com/klauspost/compress for zstd.
2026-09-24 03:47:15 -07:00
yhh 74f76057fe feat(studio): a phone on the same network pairs by QR code and drives the window's panes
Settings > Remote > Phone access opens a second listener on a private
address the person picks (Ethernet/Wi-Fi first, Tailscale next, tunnels
and virtual switches last, read from the interface's flags and hardware
address). The window shows a QR code carrying a one-time pairing code
in the URL fragment; the page trades it at POST /pair for an HttpOnly,
SameSite=Strict device cookie. A code pairs once and lapses after five
minutes; closing the share or quitting Studio unpairs every device, and
each device can be disconnected on its own.

A paired device holds what a networked serve client holds, and no more.
The window's grants (provider edits, account sign-in, workspace switch,
editor open, provider setup) are now read per request, so they stay shut
for device reach even though the device reaches the same handler. The
window's own routes (folder picker, SSH remotes, tray, notifications,
browser host, updates, asks, and the share controls themselves) answer
a device with 404 device.host_only, which the page already reads as a
kernel with no window.

The device gate refuses a Host other than the listener's own address
(rebinding), a state change from a foreign Origin, and every kernel
route without a known credential; the page's own files stay public so
an unpaired phone can load the page it pairs from.

The listener is plain HTTP, and the panel says so: fine on a home
network, not on a public one. Pairings live in memory only.
2026-09-24 02:14:55 -07:00
yhh fc902b846c feat(script): run_script lets the model call tools from a short Starlark program
[agent] code_mode offers run_script. The model writes a Starlark
script. Inside it, call(name, **args) runs a tool and returns its output,
try_call returns ok/output/error, json encodes and decodes, and print
writes the result. Several dependent steps then cost one round trip, and
only what the script prints enters the conversation.

Every call inside a script goes through tool.Invoker, which the agent
binds to each tool call. The agent's invoker runs executeOne for it, so
a nested call passes the same parsing, policy, permission, hooks,
evidence and mutation observation as one the model made. It gets its own
tool card under the caller's id (s1.1, s1.2, ...), and its result
reaches only the script. A nested call's external origin labels the
script's result when the script declares none.

The interpreter reaches nothing else: no files, network or clock. Limits:
20M steps, 64 tool calls, 64 KiB of printed output and a 32 KiB script;
the interpreter is cancelled with the turn. run_script cannot run inside
a script, directly or through a proxy.

Adds go.starlark.net as a dependency.
2026-09-24 01:42:34 -07:00
esengine 6e7f189e76 fix(ci): the heap corruption's two upgradable causes, and a floor set on the wrong platform
The windows-latest crash is the GC finding a bad pointer while marking:
runtime.scanObject in mgcmark_greenteagc.go, under gcDrain. Green Tea is
the detector, not the cause — turning it off would move where the
corruption surfaces, not stop it, so this does not do that.

Two things in that binary can produce it and were both behind. The Go
toolchain moves 1.26.6 -> 1.26.8, which carries compiler and runtime
fixes; and modernc.org/sqlite moves 1.57.0 -> 1.58.0, taking libc from
1.74.4 to 1.75.6. libc is the only unsafe-heavy code linked into that
test binary that is not the standard library, and internal/bot reaches
it through historycatalog and projectiondb. Neither upgrade is proven to
be the fix: one crash cannot attribute itself, and golang/go#81238 —
windows/amd64 exception recovery writing below a goroutine stack — is
open, unfixed, and the same class. If it recurs on these versions, that
issue is where it goes.

covergate was red on a third path this had not read: internal/sandbox is
78.0% on linux against a 78.1% floor, while the same tree measures 78.1%
on darwin. The package compiles a different set of files per GOOS, so a
floor recorded on one platform can be unreachable on the one the gate
runs on. Rather than lower it, three statements nothing reached are now
covered — podman's per-user socket, an authority the table does not
govern, and a nil context carrying no escape approver — which takes
darwin to 78.9% and lifts linux above its floor by the same edges.

covergate now names the lines it is short of. A percentage alone left
whoever has to restore it measuring a package by hand on a platform they
may not be on, which is how this one sat red across three runs.
2026-09-06 09:47:46 -07:00
esengine a7521c51ef fix(ci): four red gates, each with the reason it was red
govulncheck: x/crypto v0.55.0 reaches the advisory through
remote.newSSHClient. Bumped to v0.56.0, which is where it is fixed.

race: matrixSink folds graph deltas in place, and a fan-out publishes
from every worker's own goroutine. The sink now holds its own lock;
agentgraph.Graph is a pair of slices, and the production reader that
folds deltas does it on one goroutine.

covergate: two branches added with the adjudication barrier were never
reached. liveBarrierIDs was only ever asked about asks, so a live tool
approval read as an abandoned obligation; and Ask's refusal when the
barrier cannot be recorded had no case at all. Both are now tests, not
a lowered floor.

macOS: TestDeniedAuthorityIsNotInheritedThroughADescriptor read the
inherited socket once, immediately after dialling, while the peer writes
the marker only after its Accept returns. The positive control lost that
race and the test correctly refused to pass vacuously. The scan now
retries for five seconds, which is also the stronger claim: a clean
sandboxed result means seconds of trying found nothing, rather than one
instant. Reproduced by delaying the peer's write, and the fix holds with
that delay still in place.

The windows-latest crash in internal/bot is not addressed here: it is a
runtime GC fault in a package with no unsafe and no cgo, it passed on
the three preceding runs, and nothing in this tree explains it.
2026-09-06 09:19:19 -07:00
esengine 71c7af07da refactor(update): move the updater to the kernel, and wire the guard it found
The Electron shell's host binary is built from the root module
(cmd/reasonix-studio-host), and Go's internal rule is about import paths,
not modules: nothing outside reasonix/desktop/ can ever import
reasonix/desktop/internal/update. So while the updater lived there, the
shell that is meant to replace Wails could not reach it at all -- not as a
thing to be written later, but as a thing the language forbids. Moving it
is a precondition for the crossing rather than tidying.

It moves whole because it was already independent: 44 files, zero imports
from the desktop module, and the only things it reaches for -- config,
fileutil, installlayout, repair -- are kernel packages it was already
importing across the boundary. The five desktop commands that use it keep
working, since reasonix/desktop/... may import reasonix/internal/...

The move surfaced TrustedRedirect as an orphan, and it is not dead code:
it is the CheckRedirect an update client must use, written with its own
comment saying a redirect could otherwise walk an authenticated download
onto another host, and never referenced. The shells assemble their client
from netclient, which sets no redirect policy, so every catalog read and
artifact download has been following redirects wherever they pointed.
Minisign verification is what stood between that and an installed
artifact, which is why this is a missing layer rather than an open door.

New now guards copies of the clients it is handed -- copies, because the
shells share one client with the rest of the app, and a caller that
already chose a policy keeps it. Recording that orphan in the baseline
instead would have filed an unwired security guard as debt, which is why
the wiring is in this commit rather than after it.

The baseline carries forward under -allow-widen: the repository totals
only tighten, and what enters internal/update is exactly what left
desktop/internal/update -- complexity 42, essay 3, function-size 180.

TrustedRedirect had no test either. It has one per field it reads, because
a guard checked only through one bad hostname would still pass after it
stopped reading the scheme. Checked against the live catalog too: eleven
published versions still resolve with the guard installed.
2026-08-31 08:42:11 -07:00
esengine 5e1ba919d1 chore(deps): update every dependency, and pay what go 1.26 asks
mvdan.cc/sh v3.14.0 requires go 1.26.0, so the directive moves with it,
and the directive is what turns modernize's newexpr rule on across the
tree. The 75 findings are the price of this upgrade rather than debt it
inherited: lint reported 0 issues on the same tree one version back.

Inlining the pointer wrappers leaves nine of them with no caller, and
two different gates are needed to see that: unused catches the
unexported ones, repolint's orphan catches exported tool.IntPtr. The
//go:fix inline directives the fixer writes are dropped rather than
kept, because govet reads one as a demand to inline every call site --
which would delete names that carry meaning. provider.TemperaturePtr
tells an explicit 0 from "not set, use the provider default"; new(0.0)
at twelve call sites says neither. Keeping the new(v) body satisfies
modernize without spending the name.

Go: bubbles, bubbletea, lipgloss, x/ansi, lark, go-runewidth,
jsonschema, x/mod, x/net, sqlite, sh; wails 2.13 -> 2.15 in desktop.
Frontend: vite 8 on rolldown, typescript 7, plugin-react 6, vitest,
electron 44.1.0. Both lockfiles carry the linux and windows native
binaries, which CI installs and this machine never does.

Verified past every gate CI runs, plus the two it does not: the Electron
smoke against a real window, and Studio itself built and launched under
wails 2.15.
2026-08-31 07:49:01 -07:00
esengine ba415e863e build(deps): update x/crypto for GO-2026-6303
golang.org/x/crypto v0.54.0 does not enforce the source-address critical
option for non-public-key auth callbacks in its ssh package. govulncheck
traces the call through sshtest.Server.handleConn, so the scan fails on
every branch until both modules move to v0.55.0.
2026-08-29 20:25:55 -07:00
esengine 45397daadb chore(deps): gorilla/websocket 1.5.0 to 1.5.3
govulncheck reported seven reachable call paths into the 1.5.0 advisories —
the feishu adapter's socket, and two readers that reach a compressed frame
through io.ReadAll. It now finds no vulnerability this code calls.
2026-08-25 17:13:02 -07:00
yhh bd88008520 chore(deps): close the seven vulnerabilities govulncheck reports
Six are standard library, fixed in go1.26.6: net/url, crypto/tls, net/http,
encoding/xml, encoding/asn1, and x/net/idna's Punycode label check. The seventh
is golang.org/x/image, fixed in v0.45.0 — both modules use it directly, for webp
decoding and for the image compressor.

CI pins no Go version of its own, reading go.mod through go-version-file, so the
toolchain line is the whole change there. govulncheck locally: 7 affected before,
0 after.
2026-08-17 05:50:37 -07:00
SivanCola bf19574d3a feat(desktop): add resilient session catalog
Problem:
Desktop startup and project-tree queries could synchronously migrate and decode legacy session JSONL, blocking navigation, creation, and shutdown.

Root cause:
Session discovery, metadata repair, controller admission, and plugin startup shared UI-critical paths and broad lifecycle locks.

Fix:
- add a disposable SQLite session catalog with incremental reconciliation, repair checkpoints, corruption recovery, and memory fallback
- expose revisioned cursor pagination and migrate the desktop project tree to lazy loading
- move controller construction outside lifecycle admission and isolate incompatible plugin startup
- add session doctor and reindex commands while preserving authoritative JSONL and metadata
- retain CGO-free builds with platform-specific fallbacks

Verification:
- go test ./...
- cd desktop && go test ./...
- go test -race ./internal/sessioncatalog ./internal/agent
- cd desktop && go test -race .
- go vet ./... in root and desktop
- cd desktop/frontend && pnpm test:all && pnpm build
- CGO_ENABLED=0 cross-builds for supported macOS, Windows, and Linux targets
2026-08-10 17:16:26 +08:00
SivanColaanddependabot[bot] db44780c27 chore(deps): batch Dependabot Go/npm upgrades on main-v2
Problem
Several open Dependabot PRs stacked overlapping Go and npm updates
against older main-v2 tips, making independent merges conflict-prone.

Root cause
Grouped and per-package bumps touch the same lockfiles across root Go,
desktop Go, desktop frontend, and the site package tree.

Fix
Re-apply the durable version targets from Dependabot PRs #8002, #7825,
#7824, #7765, #7602, #6959, and #6795 on current main-v2, regenerating
lockfiles with go get/mod tidy, pnpm install, and npm install.

Verification
- go build ./cmd/reasonix
- desktop package compile via go test -c
- go test ./internal/provider/... ./internal/control/...
- desktop/frontend: pnpm exec tsc --noEmit
- site: npm test (58 pass)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-09 05:19:41 +08:00
SivanCola 03cc774973 Fix Remote Workbench release blockers / 修复远程工作台发版阻断 (#6790)
* fix(remote): harden workbench restore and detach

Problem:
- Physical Windows-to-Linux acceptance exposed null Git pages, a crash restoring sessions after provider catalog changes, and leaked attach/runtime processes after disconnect.

Root cause:
- Empty pages cloned into nil slices, typed-nil controllers crossed an interface boundary, and runtime children shared the SSH session while proxy teardown waited indefinitely.

Fix:
- Preserve allocated empty result pages, normalize typed-nil controllers, detach and reap runtime children, and make proxy teardown bounded.
- Add regression coverage for clean Git pages, provider removal restore, child reaping, and blocked stdin shutdown.

Verification:
- go test ./... and desktop go test ./...
- go test -race ./... and desktop go test -race ./...
- go vet ./... and desktop go vet ./...
- pnpm frontend test:all and build
- cache guard, protocol generation check, six-platform CLI cross-build, and npm release dry run

* fix(deps): update x/text for GO-2026-5970

Problem:
- The release CI govulncheck gate detected reachable GO-2026-5970 in golang.org/x/text v0.38.0.

Root cause:
- Both the root and Desktop modules pinned the vulnerable x/text release used through proxy normalization.

Fix:
- Upgrade golang.org/x/text to v0.39.0 in both modules and refresh their checksums.

Verification:
- govulncheck ./... reports no reachable vulnerabilities in both modules.
- Root and Desktop full tests, vet, and race suites pass.
2026-07-22 10:58:58 +08:00
SivanCola 7fe7d2b3f3 Simplify MCP authorization for installed servers / 简化已安装 MCP 授权 (#6767)
* fix(mcp): trust installed readers in subagents

Problem:
User-installed MCP tools that declared readOnlyHint could be exposed as readers but then fail before dispatch in planners and read-only subagents. Users were told to configure a second per-tool reader policy after already installing or authorizing the server.

Root cause:
Strict reader trust did not treat explicit install or an exact project launch grant as authority for non-destructive server metadata. Direct, lazy, and on-demand paths also duplicated cached-to-live safety reconciliation, while trusted_read_only_tools remained wired through configuration and Desktop contracts.

Fix:
Authorize non-destructive readOnlyHint tools for installed or launch-authorized servers, carry reader intent only for trusted readers, and share one cached-to-live safety reconciliation boundary across lazy and on-demand adapters. Remove trusted_read_only_tools from runtime, Desktop, examples, and docs while accepting and dropping legacy input on the next save.

Verification:
- env -u DEEPSEEK_API_KEY go test -count=1 ./...
- cd desktop && go test -count=1 ./...
- cd desktop/frontend && pnpm test:typecheck
- cd desktop/frontend && pnpm exec tsx src/__tests__/capabilities-panel-actions.test.ts
- ./scripts/cache-guard.sh
- ./scripts/check-cache-impact.sh
- git diff --check

* refactor(mcp): simplify installed server authorization

Problem:
User-installed MCP servers still depended on official-catalog and per-tool reader trust layers, so direct, lazy, and on-demand paths could disagree and strict child sessions could reject an already authorized reader.

Root cause:
Server authorization, signed package status, cached reader trust, and legacy plan-mode allowlists were modeled as separate overlapping decisions.

Fix:
Remove the official MCP catalog, signature/revocation plumbing, package verification, per-tool cached trust, and obsolete reader settings. Use one server-level authorization source for user installs, host-session installs, and exact project grants while retaining live read-only, destructive, and capability-fingerprint checks.

Verification:
- go test ./internal/plugin ./internal/agent ./internal/boot ./internal/config ./internal/installsource ./internal/pluginpkg ./internal/mcplaunch ./internal/planmode
- go test ./internal/acp ./internal/agent ./internal/plugin ./internal/boot
- cd desktop && go test ./...
- ./scripts/cache-guard.sh
- git diff --check

* refactor(mcp): make install authorization final

Problem:
Installed MCP servers could still encounter server, per-tool, writer, or destructive approval branches, and users had to understand settings that duplicated the install decision.

Root cause:
MCP authorization, cached reader trust, approval policy, and process sandboxing evolved as separate paths across direct, lazy, and on-demand execution.

Fix:
Treat install or exact project identity confirmation as the server authorization boundary, keep only explicit global deny plus Plan/read-only and sandbox safety checks, remove catalog and approval configuration, and unify the persistent MCP process sandbox.

Verification:
env -u DEEPSEEK_API_KEY go test ./...
go build ./...
cd desktop && go test ./...
cd desktop/frontend && pnpm typecheck
cd desktop/frontend && pnpm exec tsx src/__tests__/capabilities-panel-actions.test.ts
cd desktop/frontend && pnpm exec tsx src/__tests__/decision-surface.test.tsx

* chore(mcp): remove retired cache helper

Problem:
CI lint found writeBoolKV left unused after per-tool reader trust was removed.

Root cause:
The cached reader-map fingerprint was deleted, but its serialization helper remained.

Fix:
Remove the dead helper.

Verification:
go test ./internal/plugin
go vet ./internal/plugin
go build ./...

* refactor(mcp): stop blocking schema-only drift

Problem:
Authorized MCP calls could be rejected when a cached tool schema differed from the live server even though authorization and safety classification were unchanged.

Root cause:
A capability fingerprint mixed schema compatibility with read-only and destructive execution safety across direct, lazy, and on-demand paths.

Fix:
Remove the tool-level schema fingerprint chain, keep live server authorization and read-only/destructive reclassification checks, and refresh changed schemas for the next session without interrupting the current authorized call.

Verification:
- go test ./internal/plugin ./internal/agent ./internal/tool
- env -u DEEPSEEK_API_KEY go test ./...
- go vet ./...
- go build ./...
- ./scripts/cache-guard.sh
- pnpm --dir site test
- cd desktop && go test ./...

* fix(mcp): preserve short Windows temp paths

Problem:
Windows stdio MCP servers inherited a deeply nested Reasonix private TEMP path. Servers such as MATLAB create watchdog Unix sockets below TEMP and exit before initialization when the path exceeds the platform limit.

Root cause:
MCP private-state preparation redirected TMP, TEMP, and TMPDIR below the workspace-scoped mcp-state directory on every platform even though Reasonix does not currently provide an OS sandbox on Windows.

Fix:
Keep the host temporary-directory environment for Windows stdio MCP processes while retaining private cache and state directories. Preserve the existing private temporary directory behavior on other platforms and add focused regression coverage for both branches.

Verification:
- go test ./internal/plugin -count=1
- GOOS=windows GOARCH=amd64 go test -c ./internal/plugin
- go test ./...
- go vet ./internal/plugin
- git diff --check

* Simplify MCP runtime authorization state

Use Spec.Authorized as the sole resolved server authorization result so installed and host-session MCP servers no longer depend on launch-manager presence.

Keep exact identity grants for project sources, align planner and strict read-only registries with non-destructive readOnlyHint metadata, and document the cooperative trust boundary.

Add source and agent-surface matrix coverage while preserving the main provider-visible MCP tool surface.

* Limit MCP identity digests to project servers

Problem: User-installed and host-session MCP servers still resolved and hashed their executable identity during every startup even though installation was already the authorization decision.

Root cause: Project launch authorization and schema-cache ownership shared generic fingerprint terminology, and start() computed the project identity before checking whether launch approval was required.

Fix: Skip project identity resolution for installed servers, remove dead and non-participating identity state, and rename the remaining concepts to ProjectLaunchIdentityDigest and SchemaCacheKey while preserving existing JSON keys and digest bytes.

Verification: go test ./internal/mcplaunch ./internal/plugin ./internal/capability ./internal/boot ./internal/control ./internal/agent; go test ./...; cd desktop && go test .; go vet ./...; ./scripts/cache-guard.sh; git diff --check.

* Restore MCP 0.53 compatibility surfaces

Problem: MCP authorization was simplified, but the current client still lacked four compatibility surfaces present in the 0.53 line: legacy SSE, workspace roots, progress notifications, and registry browsing/install.

Root cause: the Go transport abstraction dropped inbound notifications and rejected legacy SSE; runtime specs did not carry a separate workspace root; the registry UI and CLI had no official MCP Registry client.

Fix: add inbound request/progress routing across transports, implement legacy SSE with same-origin endpoint protection, advertise roots/list, and add an explicit cached official Registry marketplace to CLI and desktop. Remove stale approval documentation and the dead helper that broke lint.

Verification: go test ./...; focused go test -race for plugin/registry/CLI/control; go vet in root and desktop; desktop go test ./...; frontend production build and marketplace UI test; Windows amd64 cross-build; official Registry browse smoke test.

* Avoid MCP progress capacity overflow

* Align MCP authorization documentation

Problem: CLI help and Chinese documentation still contained ambiguous or obsolete wording that implied installed MCP writer or destructive calls could require per-call approval.

Root cause: The server-level authorization simplification removed MCP approval modes, but not every user-facing explanation was updated consistently.

Fix: State explicitly that install or exact project identity confirmation authorizes the server, that writer and destructive hints do not prompt per call, and that deny, Plan, and strict read-only filtering remain separate boundaries.

Verification: go test ./internal/cli; go vet ./internal/cli; reasonix mcp help smoke test; stale-wording scan; git diff --check.

* Harden MCP registry installs and SSE replies

Problem:
Registry cache freshness was shared across queries, cached entries could flow into install paths, and legacy SSE server requests spawned an unbounded reply goroutine per request.

Root cause:
The cache stored one global timestamp, Resolve reused Search's offline fallback, the desktop installed browse payloads directly, and the SSE reader delegated every reply to a new goroutine.

Fix:
Track freshness per Registry query with legacy-cache compatibility, require live resolution before CLI and desktop installs, and serialize SSE replies through a bounded single-worker queue with finite deadlines.

Verification:
- env -u DEEPSEEK_API_KEY go test -count=1 ./...
- env -u DEEPSEEK_API_KEY go test -race -count=1 ./internal/mcpregistry ./internal/plugin
- env -u DEEPSEEK_API_KEY go test -count=1 ./... (desktop)
- env -u DEEPSEEK_API_KEY go vet ./... (root and desktop)
- pnpm run test:all
- pnpm run build
- env -u DEEPSEEK_API_KEY ./scripts/cache-guard.sh
- git diff --check

* Reject unavailable live registry installs

Problem:
A Registry entry could become non-installable between browsing and the desktop's live re-resolution, leaving the generic add flow with an invalid empty connection. The SSE concurrency regression also used a fixed delay after routing the client response.

Root cause:
The desktop resolver verified freshness but not the resolved entry's installability, and the test inferred worker startup from elapsed time.

Fix:
Validate the live entry through PluginEntry before returning it to the installer, and synchronize the SSE test on the first reply POST instead of sleeping.

Verification:
- env -u DEEPSEEK_API_KEY go test -count=5 ./internal/plugin
- env -u DEEPSEEK_API_KEY go test -race -count=3 ./internal/plugin
- env -u DEEPSEEK_API_KEY go test -count=1 ./...
- cd desktop && env -u DEEPSEEK_API_KEY go test -count=1 ./...
- git diff --check
2026-07-22 01:58:43 +08:00
SivanCola 65fcd46525 Add a Remote SSH module (remote serve + tunnel) / 新增远程 SSH 模块(远端 serve + 隧道) (#6673)
* feat(serve): support --port-file/--token-file/--pid-file for supervised headless serve

Extract RunGracefulListener so callers can bind first (--addr 127.0.0.1:0),
record the real port, and keep the auth token out of argv. Groundwork for the
remote-SSH module's detached remote serve bootstrap.

* feat(config): add user-global-only [remote] section for SSH hosts

[[remote.hosts]] entries carry connection/forward/workspace settings and name
credential env vars (passphrase_env/password_env) instead of secrets, mirroring
the provider api_key_env idiom. Like [secrets], loadForRoot pins the section
back to the user-global value after the project merge — with a deep copy,
because TOML decoding mutates slice backing arrays in place and a shallow pin
would alias the project-injected hosts. Remote credential env names flow into
CredentialEnvNames so they are filtered from tool subprocess environments.
Adds RemoteStateDir/RemoteKnownHostsPath and the internal/store name helpers
for the remote-side serve state layout.

* feat(remote): SSH transport kernel — dial, auth, host keys, forwards, SFTP

New internal/remote package group provides the connection layer for the
remote module:

- client.go: supervised connection with a single-goroutine state machine,
  keepalive ([email protected]), full-jitter exponential-backoff
  reconnect, shared SFTP (generation-checked), and forward re-attach.
- auth.go: OpenSSH-like method order (agent, identity file, default
  identities, password, keyboard-interactive); interactively-obtained
  secrets are cached in-process so reconnects never re-prompt.
- knownhosts.go: system known_hosts read-only + a Reasonix-managed TOFU
  file; key mismatch is a non-promptable hard error.
- dial.go: ProxyJump chains (netclient proxy applies to the first hop only).
- forward/: -L (persistent local listener across reconnects) and -R
  (re-registered on each attach) with typed ErrBindBusy/ErrDuplicateForward.
- sftpfs/: list/stat/capped-read-with-text-detection/atomic-write/mkdir/
  rename/remove, quarantining the new github.com/pkg/sftp dependency.
- netclient/dialer.go: StreamDialer honoring the user's proxy policy
  (SOCKS5 via x/net/proxy, http/https via CONNECT).
- sshconfig.go: ~/.ssh/config import via github.com/kevinburke/ssh_config.
- sshtest/: in-process SSH+SFTP server for tests.

New deps: github.com/pkg/sftp, github.com/kevinburke/ssh_config (both pure
Go). x/* versions kept at main-v2's pins.

Cache-impact: none — no prompt/tool/provider path touched.
Cache-guard: n/a (new leaf subsystem, not in the system-prompt prefix).

* feat(remote): remote serve bootstrap over SSH

internal/remote/bootstrap ensures a detached 'reasonix serve' runs for a
(host, workspace) pair: reuse a live process (the reconnect path), else detect
the remote OS/arch (uname), locate/install/upload reasonix, write a 32-byte
token 0600 via SFTP, launch serve bound to 127.0.0.1:0 with --token-file/
--port-file (token never in argv), poll the port file, and persist ServeState
JSON. Status/Stop/Logs manage the recorded process. Every remote command
operand is POSIX single-quote-escaped (golden test covers '; rm -rf ~').
V1 targets Linux/macOS remotes; cross-platform upload without a matching local
binary is a documented limitation directing to serve_install = npm.

Cache-impact: none — new leaf subsystem, no prompt/tool/provider path.
Cache-guard: n/a.

* feat(cli): reasonix remote command group + /remote slash

Adds the remote subcommand tree (add/list/remove/import/test/connect/open/
status/forward/serve/fs), mirroring the mcp command shape. Host CRUD writes to
the user-global config only (remote hosts are pinned there); connect runs a
foreground supervisor that dials, bootstraps remote serve, forwards the serve
port with the token in a loopback URL, applies configured -L/-R forwards, and
holds the tunnel until Ctrl-C while the remote serve keeps running. Terminal
prompts handle key passphrases, passwords, and TOFU host-key confirmation;
dialing honors the user's proxy settings via netclient. The /remote slash
command shows a read-only host summary. New i18n fields added to all three
catalogs.

Cache-impact: none — CLI surface + i18n only, no prompt/tool/provider path.
Cache-guard: n/a.

* feat(desktop): remote SSH Go bindings (remote_app.go)

25 App bindings behind a narrow remoteKernel interface (fake-injected in
tests): host CRUD (user-global config), Connect/Disconnect/status,
status-driven TOFU host-key confirmation (ConfirmRemoteHostKey resumes a
parked dial), SFTP list/read/write-with-mtime-conflict/mkdir/rename/delete,
port-forward CRUD, and remote serve EnsureServer/Open/Stop/Status/Logs.

desktopRemoteManager wraps internal/remote per host; status/forward/server
transitions bridge to remote:status / remote:forwards / remote:server through
the asyncRuntimeEmitter (never blocking the kernel). The manager is created
lazily on first use and closed in App.shutdown, rejecting any pending host-key
prompt. OpenRemoteWorkspace forwards the serve port and opens the tunneled URL
via Wails BrowserOpenURL; last workspace/host persist to desktop-remote.json
(all-optional fields, backward-compatible).

desktop/go.mod gains pkg/sftp + kevinburke/ssh_config via go mod tidy.

Cache-impact: none — desktop bindings only, no prompt/tool/provider path.
Cache-guard: n/a.

* feat(desktop): remote SSH frontend (host manager, explorer, ports, server)

- lib/types.ts: Remote* TS mirrors + SettingsTab "remote".
- lib/bridge.ts: 25 Remote* AppBindings + browser-dev mock + onRemoteStatus/
  onRemoteForwards/onRemoteServer subscribers with a mock fan-out for tests.
- store/remote.ts: zustand store; pending_hostkey status drives the global
  fingerprint dialog and resolves on the next non-pending status.
- RemoteHostsPage (Settings > Remote SSH tab): host list with status chip,
  connect/disconnect/open, add/edit form, ssh_config import picker.
- RemotePanel: overlay drawer with Files (lazy tree + textarea editor with
  mtime-conflict Reload/Overwrite), Ports (live forward rows + add form), and
  Server (bootstrap phases + open-in-browser + logs) tabs.
- RemoteHostKeyDialog: global TOFU modal off the store's pendingFingerprint.
- StatusBar remote chip (worst live state, click opens explorer); App.tsx
  subscribes remote:* app-wide and renders the panel + dialog.
- i18n keys added to en / zh / zh-TW (key sets stay aligned).
- Editor is a monospace textarea (no CodeMirror/Monaco) per lean-deps.
- Tests: src/__tests__/remote-store.test.ts (added to the test script).

Cache-impact: none — desktop frontend only.
Cache-guard: n/a.

* docs: document the Remote SSH module (SPEC, GUIDE, CONFIG_PATHS, CHANGELOG)

Adds the internal/remote layout + acyclic dependency note to SPEC 2, a bilingual
Remote SSH section to GUIDE.md / GUIDE.zh-CN.md (config, CLI, reconnect/persistence
semantics, host-key TOFU, remote-side state layout, desktop UI), the managed
known_hosts path to CONFIG_PATHS(.zh-CN), and a CHANGELOG entry.

Cache-impact: none — docs only.
Cache-guard: n/a.

* chore: ignore stray .reasonix state under package dirs

A bootstrap test whose RealPath resolved to CWD once wrote a .reasonix/remote/
token under the package dir and it was swept into a commit. The offending file
has been scrubbed from history; this guards against recurrence.

* fix(remote): CI green — errcheck + cross-platform bind-busy and Windows test skips

- client.go: check cl.Wait() return (errcheck lint).
- forward: classify WSAEADDRINUSE as bind-busy on Windows (build-tagged
  addrinuse_windows.go), so ErrBindBusy is reported cross-platform.
- sftpfs / bootstrap integration tests skip on Windows: their in-process SFTP
  server serves the LOCAL filesystem, so on Windows it serves drive paths and
  the POSIX-remote path translation breaks — a harness limitation, not a
  product one (V1 remotes are Linux/macOS). Linux/macOS CI covers the flows.

Cache-impact: none — test/lint-only + a Windows errno check.
Cache-guard: n/a.

* test(remote): skip EnsureServe reuse/relaunch/stop tests on Windows at setup

TestEnsureServeReusesLiveProcess and siblings pre-write state via pathsFor()
(POSIX path.Join) before newFakeConn runs, so on Windows the temp-dir remote
home yields a malformed drive path and the setup os.WriteFile fails. Move the
Windows skip to the top of each such test (shared skipOnWindows helper) so it
fires before any pathsFor/os setup, not just inside the harness ctor.

* fix(remote): address review — P1 security / data-integrity / lifecycle

Resolves the 14 findings from code review (all verified against the code):

Security
- Serve token no longer leaks to the remote log: runServe suppresses the
  token share line under --port-file (supervised), and the launch script
  creates the log 0600 (umask 077 + chmod). (F1)
- ProxyJump no longer sends the target's password to jump hosts: each hop gets
  its own credential-free AuthOptions with a per-hop cache (persisted on the
  Client so reconnects don't re-prompt). (F2)
- CLI surfaces a misconfigured proxy instead of silently dialing direct; the
  https-proxy path now TLS-wraps the connection before CONNECT; desktop passes
  the proxy Dialer it previously omitted. (F14)

Data integrity
- `remote fs get` streams the full file via sftpfs.Download instead of the
  4 MiB-capped preview reader that silently truncated. (F3)
- Workspace slug carries an FNV hash of the exact path, so /srv/a-b and
  /srv/a/b no longer share serve state files. (F10)
- Desktop host edit merges: passphrase_env/password_env/forwards (absent from
  the desktop input) are preserved instead of wiped. (F7)

Lifecycle / correctness
- Bootstrap gates on `serve --help` advertising --port-file, not a version
  number (the flags ship in this change, so no released version qualifies). (F4)
- Launch uses setsid only when present, so stock macOS (no setsid) works. (F5)
- Desktop no longer uploads the GUI binary as the remote CLI (LocalBinary=""
  makes the upload path fail clearly toward npm). (F6)
- Serve tunnel re-binds to the current serve address on every EnsureServer and
  is torn down on Stop; per-host serveMu serializes concurrent ensures. (F8)
- Reuse/Stop verify the pid is actually a reasonix serve (ps args), not just
  kill -0, so a recycled PID is never mistaken or signalled. (F9)
- Desktop connect retries after failure (failed Start drops the managed host)
  and builds a fully-populated host under the lock so a concurrent Disconnect
  can always close it. (F11)
- SSH handshake is bounded by a conn deadline (ClientConfig.Timeout does not
  cover NewClientConn), so a banner-less host can't hang the dial or Close. (F12)
- ScanSSHConfig returns a non-nil slice so an empty result encodes as [] not
  null. (F13)

Tests: per-hop auth isolation, full-file download vs preview cap, slug
collision (/srv/a-b vs /srv/a/b), serve-alive/launch/locate command shape,
desktop edit-merge and non-nil scan. go test ./... green in both modules;
gofmt/vet clean; Windows cross-build green; cache-guard pass.

Cache-impact: none — no prompt/tool/provider/memory path touched.
Cache-guard: ./scripts/cache-guard.sh — all cases pass.
System-prompt-review: not required — no system-prompt-feeding config field changed.

* Harden Remote SSH lifecycle and packaging

Problem:
Remote SSH could misapply ProxyJump credentials, race connection and service lifecycles, interrupt live forwarding during replacement, and ship Desktop builds without the CLI sidecar required by upload installation. Several UI failures were also silent or left stale state behind.

Root cause:
Jump aliases shared endpoint-keyed authentication, bootstrap ownership relied on weak state checks without cross-process serialization, and Desktop operations lacked generation-scoped cancellation. Forward replacement stopped the working listener before the replacement was ready, while packaging and updater contracts did not include the CLI binary.

Fix:
Resolve and authenticate every SSH hop independently, make dialing and Desktop operations context-aware, serialize bootstrap with an atomic remote lock, verify exact process ownership, and make forward replacement failure-atomic. Harden token, SFTP, agent, config, and ssh_config handling; package and update the CLI sidecar on macOS, Windows, and Linux; and surface actionable validation and lifecycle errors in the Desktop UI.

Verification:
- go test ./...
- cd desktop && go test ./...
- go test -race for Remote, CLI, store, and Desktop modules
- go vet ./... for root and Desktop modules
- pnpm test:all and pnpm build
- Windows amd64 cross-compilation for Remote, bootstrap, forwarding, SFTP, and CLI packages
- clean merge snapshot against latest origin/main-v2 with root, Desktop, and frontend checks passing

* Add native Remote SSH workspace windows

Problem:
Remote SSH was buried in settings, and opening a remote workspace handed the authenticated loopback URL to the system browser instead of preserving a desktop workspace experience.

Root cause:
The desktop had no window-level Remote SSH control and no isolated Wails launch mode for a second native workspace window.

Fix:
Add a VS Code-style status entry, host switcher, command actions, and Remote dock. Launch remote workspaces in a lightweight Reasonix process that skips local session ownership, single-instance focus, tray services, and Wails bindings. Pass the loopback URL through a one-shot permission-restricted ticket so the token never appears in process arguments.

Verification:
- go test ./...
- pnpm test:all
- pnpm build
- wails build -clean
- native macOS multi-window smoke test
- git diff --check

* Tidy Remote SSH module metadata

Record the SSH config and SFTP packages as direct dependencies after rebasing onto the latest main-v2 baseline. Remove stale module checksums so the root module remains tidy in CI.

* Fix Remote SSH Windows test assumptions

Load the fixture SSH config by its explicit path while setting both platform home variables, and use the Windows .exe suffix for the desktop CLI fixture. This keeps the Remote SSH coverage portable across the CI matrix.
2026-07-19 21:48:20 +08:00
SivanCola c636f017a8 feat(mcp): enforce trusted execution boundaries 2026-07-15 13:55:34 +08:00
SivanCola ed113b4909 fix(mcp): quarantine tools with invalid schemas 2026-07-14 12:17:57 +08:00
SivanCola a99256d523 feat(cli): align terminal interactions with Claude Code 2026-07-13 19:27:20 +00:00
SivanCola 5fe20ebda8 chore: internalize Windows sandbox backend 2026-07-06 15:11:47 +08:00
SivanCola 1054c188d3 Bump windows-sandbox for run-local residue markers 2026-07-05 23:54:13 +08:00
SivanCola a8e9421bf9 Bump windows-sandbox to survive marker PID reuse
Picks up b79e9a5: the crash-residue marker is keyed by PID alone, so a
helper process that drew a crashed predecessor's PID used to skip the
stale marker in its run-start sweep (pid == self), append its own lines
into the same file, and then delete the whole file on exit — orphaning
the predecessor's forbid_read deny or tool-directory grant beyond any
future sweep's reach. The library now tracks marker ownership
in-process: cleanup only deletes a marker this process wrote, and an
unowned file at the run's own PID is consumed at run start as a dead
predecessor's residue. The filename stays <pid>.txt so older binaries
sharing the marker directory keep parsing PIDs correctly during
upgrade windows.
2026-07-05 23:06:13 +08:00
SivanCola bd045b0dfd Bump windows-sandbox to harden the residue sweep
Picks up the follow-up (3058fd1) that re-checks the system-root
invariant at sweep time: the crash-residue marker lives in the user's
%TEMP% and is untrusted, so a legacy or planted line naming System32 /
Program Files could previously drive the next run's sweep to strip the
broad built-in package SIDs from a system directory's factory ACLs. The
sweep now refuses system paths independently of the recorder-side
filter.
2026-07-05 22:09:21 +08:00
SivanCola 39bfc0b045 Bump windows-sandbox to never mutate system executable dirs
Picks up two upstream fixes: exe-dir contention coverage with the
close of the deny-residue window (2a86cf8, 0e09612), and the follow-up
that stops grantAppContainerExecutable from snapshotting, granting, or
recording crash-residue on system directories (1a3e77e) — previously a
crashed elevated run could leave a residue marker that made the next
run strip built-in package SIDs from System32 / Program Files ACLs.
2026-07-05 14:48:13 +08:00
SivanCola 2ecc3bbee0 Bump windows-sandbox for per-root lock thread pinning
Pick up the follow-up that pins the per-root lock to one OS thread, so a Go
goroutine migrating threads cannot release a Windows mutex from a non-owning
thread or re-enter it within the process. Cross-process serialization (each
sandboxed command is its own helper process) was already correct; this
tightens the within-process semantics the library's unit tests cover.
2026-07-05 14:44:28 +08:00
SivanCola ece9c41f8f Bump windows-sandbox with ACL lifecycle hardening
Update the native Windows sandbox backend to the release that fixes the
mutation-based-isolation failure modes: recursive icacls timeout on large
workspaces, residual Low integrity labels after cleanup, concurrent runs
corrupting each other's ACL boundary, forbid_read deny residue after a crash,
forbid_read entries pointing at files being silently unprotected, and
PATH-resolved icacls/taskkill.

Correct the docs to match the actual Windows boundary: writable low-integrity
commands can still write the narrow set of locations Windows leaves writable to
any low-integrity process (e.g. AppData\LocalLow) in addition to the configured
roots, concurrent commands on a shared workspace are serialized, forbid_read
now covers files, and crash residue is swept by the next run.
2026-07-05 14:44:28 +08:00
SivanCola f541bd353a Bump Windows sandbox test coverage dependency 2026-07-05 14:44:28 +08:00
SivanCola a7582e3d20 Add native Windows sandbox helper 2026-07-05 14:44:28 +08:00
SivanColaandJuly-X 174ea10844 refactor: harden parsers with maintained libraries
Replace several hand-rolled parsers and text processors with maintained libraries while preserving Reasonix-specific policy boundaries and fallbacks.

Add focused regression coverage for shell argv policy, HTML extraction, frontmatter decoding, dotenv parsing, glob matching, diff output, grapheme clipping, and optional tree-sitter code indexing.

Co-authored-by: July-X <[email protected]>
2026-07-04 01:11:34 +08:00
SivanColaandJuly-X 8664212c45 fix(bash): parse shell syntax with Bash AST
Replace hand-rolled Bash scanners with mvdan.cc/sh AST parsing for background keepalive detection, permission matching, plan-mode read-only checks, safe redirect normalization, and verification command matching.

Add heredoc regressions for issue #5624 so command execution and cancellation both release turns promptly.

Co-authored-by: July-X <[email protected]>
2026-07-03 22:41:16 +08:00
SivanCola 98ee87101b feat(desktop): add system notifications for turns 2026-07-03 14:58:09 +08:00
qiaone 535993a271 serve: add token and password authentication for HTTP frontend
Add authentication to 'reasonix serve' so it can safely bind to
non-localhost addresses. Two modes:

- Token mode (--auth token): a pre-shared token in the URL query
  string sets a HttpOnly cookie; share the link with collaborators.
- Password mode (--auth password): a login page (bcrypt) with
  HMAC-signed session cookies, no username needed, persistent login.

Design priorities: simplicity, security, backward compatibility.
Default is still --auth none (no authentication).

Security highlights:
- Constant-time token comparison (crypto/subtle)
- bcrypt cost 12 for password hashing
- HMAC-SHA256 session tokens with expiry + cryptographic nonce
- HttpOnly / Secure / SameSite=Lax cookies
- Per-IP rate limiting on /login (5 attempts/minute)
- Periodic cleanup of rate-limit state
- X-Forwarded-* headers only trusted when behind_proxy is set
- /login path bypasses CSRF guard (form POST, not JSON)
- Error messages are intentionally generic (no oracle)

Files:
- internal/serve/auth.go (new): authGate middleware, session
  management, login handler, rate limiter
- internal/serve/auth_test.go (new): 28 tests covering token,
  password, session HMAC, rate limiting, IP extraction
- internal/serve/login.html (new): dark-themed login page
- internal/config/config.go: add ServeConfig struct
- internal/serve/serve.go: integrate auth into middleware chain
- internal/cli/cli.go: --auth, --token, --password, --hash-password,
  --behind-proxy flags
- i18n: updated serve help text (en, zh, zh-TW)
- reasonix.example.toml: [serve] section
2026-06-23 15:52:13 +08:00
Sivan b71d11cf5a chore(deps): consolidate frontend and Go updates 2026-06-22 15:44:08 +08:00
Sivan a148c43d2c Add credential store backends 2026-06-16 19:27:08 +08:00
dependabot[bot] 71cea5adad build(deps): bump the go group with 3 updates
Bumps the go group with 3 updates: [charm.land/lipgloss/v2](https://github.com/charmbracelet/lipgloss), [github.com/larksuite/oapi-sdk-go/v3](https://github.com/larksuite/oapi-sdk-go) and [golang.org/x/net](https://github.com/golang/net).


Updates `charm.land/lipgloss/v2` from 2.0.3 to 2.0.4
- [Release notes](https://github.com/charmbracelet/lipgloss/releases)
- [Commits](https://github.com/charmbracelet/lipgloss/compare/v2.0.3...v2.0.4)

Updates `github.com/larksuite/oapi-sdk-go/v3` from 3.9.4 to 3.9.5
- [Release notes](https://github.com/larksuite/oapi-sdk-go/releases)
- [Changelog](https://github.com/larksuite/oapi-sdk-go/blob/v3_main/changelog.md)
- [Commits](https://github.com/larksuite/oapi-sdk-go/compare/v3.9.4...v3.9.5)

Updates `golang.org/x/net` from 0.55.0 to 0.56.0
- [Commits](https://github.com/golang/net/compare/v0.55.0...v0.56.0)

---
updated-dependencies:
- dependency-name: charm.land/lipgloss/v2
  dependency-version: 2.0.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go
- dependency-name: github.com/larksuite/oapi-sdk-go/v3
  dependency-version: 3.9.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go
- dependency-name: golang.org/x/net
  dependency-version: 0.56.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-06-14 11:52:44 +00:00
YHH 62645d1c53 feat(vision): downscale attached images before sending; add detail knob (#4210)
Follow-up to #4204. Sent images had no size control — an attached photo went out
at full resolution (up to the 10 MB cap), wasting request bytes and image tokens
since vision models downscale server-side anyway.

- internal/control: a vision-only send path (visionImageDataURL) downscales an
  oversized image to 1568px on its longest side and re-encodes it — PNG/GIF stay
  lossless (screenshots, text, transparency), JPEG/WebP go to JPEG q85 — guarded
  against decompression bombs. Best-effort: an undecodable format passes through
  untouched. The desktop preview path (ImageDataURL) is unchanged, full res.
- A per-model `vision_detail` (low|high) config flag sets the openai image_url
  detail hint; empty = auto/omit. "low" pins an image to ~85 tokens.
- Deliberately no request-body gzip: it only helps the wire (~25%, and
  provider-support-dependent) and nothing for tokens, so downscaling is the lever.

Also corrects the #4204 comment that claimed images "break prefix-cache
stability" — they don't (vision-gated, append-only, byte-stable); the real
concern was always cost.
2026-06-12 23:34:07 +08:00
lanshi 19f6bcdcf6 feat(cli): add self-update command (reasonix upgrade/update) (#4005)
Adds `reasonix upgrade` / `update`: queries the GitHub releases API, filters to the CLI tag namespace (v*, skipping desktop-v*/npm-v*), compares with x/mod/semver, downloads the platform archive, fail-closed SHA256 verification (mismatch or missing entry aborts), extracts the binary in memory, and replaces the running executable (atomic rename on Unix; two-phase .old/.new rename with rollback on Windows). Includes --check and --force flags and full en/zh i18n.

Closes #3859
2026-06-12 11:42:03 +08:00
SivanCola 28bedc6b7f feat(bot): add bot gateway with Feishu/Weixin/QQ adapters (#3763)
Lands the bot gateway as its own feature, extracted from #3752: gateway/session plumbing plus Feishu, Weixin, and QQ adapters, CLI/config support, and the desktop Settings UI for bot channel configuration and guided setup.

Co-authored-by trailers preserved from the source branch.
2026-06-09 21:05:02 -07:00
SivanCola c80ba8ad76 feat(desktop): themeable workspace UI, tool-approval modes, and /goal loop (#3752)
Lands three changes from #3752:

- Desktop: graphite UI redesign with slate/carbon/nocturne/amber theme
  directions (light+dark), Cmd-K command palette, and a status bar /
  composer / sidebar refresh. Default desktop appearance is now light.
- Controller: separate tool approvals from business asks and plan
  approval. Posture is ask/auto/yolo; deny rules still block upstream,
  auto keeps explicit ask rules while auto-approving the writer fallback,
  and plan approval plus the ask tool stay user decisions in every mode.
- Controller: opt-in /goal autonomous loop that self-continues up to 50
  turns, driven by [goal:complete|blocked|continue] markers, bounded and
  cancellable, with a repeated-block stop.
2026-06-09 20:32:18 -07:00
dependabot[bot] 6a1267bb57 chore(deps): bump the go group across 1 directory with 3 updates (#3678)
Bumps the go group with 3 updates in the / directory: [golang.org/x/sys](https://github.com/golang/sys), [golang.org/x/term](https://github.com/golang/term) and [golang.org/x/text](https://github.com/golang/text).


Updates `golang.org/x/sys` from 0.45.0 to 0.46.0
- [Commits](https://github.com/golang/sys/compare/v0.45.0...v0.46.0)

Updates `golang.org/x/term` from 0.43.0 to 0.44.0
- [Commits](https://github.com/golang/term/compare/v0.43.0...v0.44.0)

Updates `golang.org/x/text` from 0.37.0 to 0.38.0
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.37.0...v0.38.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-version: 0.46.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: golang.org/x/term
  dependency-version: 0.44.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: golang.org/x/text
  dependency-version: 0.38.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-09 04:51:15 -07:00
YHHandreasonix 7605ace4b2 chore(deps): bump dependencies to latest, toolchain go1.26.4 (#2941)
* chore(deps): bump dependencies to latest, toolchain go1.26.4

Clears every govulncheck finding from #2932's new job:
- golang.org/x/net v0.35.0 → v0.55.0 (GO-2026-5026/4918 and others)
- golang.org/x/sys v0.44.0 → v0.45.0, golang.org/x/text v0.24.0 → v0.37.0
- toolchain go1.26.4 picks up the stdlib fixes (crypto/x509, net/http,
  net/textproto) that the 1.25 build was missing.

Also refreshes chroma v2, bubbletea/v2 (v2.0.7), and go-runewidth.
govulncheck now reports no vulnerabilities; build, vet, golangci-lint,
and the full test suite pass under 1.26.4.

* chore(deps): sync desktop module go.sum after root bump

The desktop module replaces the root, so bumping root's x/net, x/sys, and
x/text moved its transitive requirements; `go mod tidy` in desktop/ keeps
go.mod/go.sum in sync (the CI desktop job enforces a clean tidy).

---------

Co-authored-by: reasonix <[email protected]>
2026-06-03 06:50:50 -07:00