mirror of
https://github.com/esengine/DeepSeek-Reasonix.git
synced 2026-11-07 02:36:01 -10:00
studio
32
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
81729ee851 | test(relay): drive a hosted page against a real Studio through a stand-in relay in CI | ||
|
|
3f35e37388 | feat(examples): add a runnable stdio MCP plugin | ||
|
|
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. |
||
|
|
04e9bbe4a0 |
chore: remove site/, the main-v2 website, from the studio line
reasonix.io is built and deployed from main-v2 only. pages.yml, removed
in
|
||
|
|
0b8b28e3c3 |
chore: ignore the tool-surface-cache build artifact
The benchmark is a command, so building it writes a binary at the repo root. repolint refuses one that nothing ignores. |
||
|
|
e6db271d9f |
feat(config): the DeepSeek catalog is one declaration, and its rates are one table
The flash model is `deepseek-flash` everywhere: the id it is served under lost the generation it never needed. An installed entry is renamed rather than left behind, and the rename moves every key that named the old id with it — a price or an effort list stranded under the old key stops answering for the model that replaced it, which reads downstream as a setting the user edited reverting to its default. A preset is now what settles which of its models read images. Reading a name misses every Kimi, Qwen, MiniMax and Claude model these presets declare, and `deepseek-flash` spells nothing at all; the spelling stays the last resort for an address nothing has declared, and stays a correctable suggestion rather than a claim. The connection that existed only to reach an image-taking model folds into the entry beside it once that entry reaches the same model on the same key — unless its user configured it, because folding would answer their edit by deleting where it lived. A preset changing shape reaches installs holding the old one through one declared shape per generation and one upgrade that brings a matching entry forward. The guard is the whole shape, because an entry holding what we shipped is ours to move and anything else its user curated. That retires nine hand-written migrations that each recognised one vendor by hand. Official list prices live once, in `officialRates`, oldest generation first. A price change appends rather than edits: the superseded rate is what lets an installed config be recognised as holding one of ours and brought forward, instead of quoting a retired rate for the rest of its life. `config` reads them through `billing.CurrentRate` and `SupersededRates` and restates none of them — a table written twice is one that drifts, which is how a vendor's USD rate ended up published on one side only. `make pricecheck` reads each vendor's own page and compares it against that table, and says how long ago a person last confirmed each one. It reaches the network, so it stays out of lint and out of CI. A page it cannot read exits non-zero: unread is its own verdict and never counts as agreement. |
||
|
|
babe9be7e1 |
feat(workgroup): freeze the natural-value protocol before its sample exists
Whether a turn's execution is worth offering as one foldable unit is a question about ordinary use, and the only honest way to answer it is to watch ordinary use. Designing tasks to cover the interesting cases would put the task designer's choices into the granularity being measured, which is the one thing this cannot afford. So the protocol is fixed now, while no sample exists to fit it to. The first twenty eligible authored turns after the freeze timestamp, in the order they happen — not the last twenty, not a chosen twenty, and not twenty-four because the answer landed near a line. Eligible means the turn carries a named authored turn, which only the current schema emits and a synthetic continuation never gets, and that its trajectory reads complete, because a prefix cannot answer a question about a whole turn. A turn with no tool calls is in the sample. The question is how much of ordinary use has something to fold, and dropping the quiet turns would answer a different one and flatter the feature. There are no route quotas either: the mix of one-model and two-model turns is reported as a description of what happened, never selected for. The tool refuses to say anything but the count until the sample is whole. Reading a partial result and acting on it is the sequential decision the freeze exists to prevent, so there is nothing to read. The verdict is one gate with both halves required — most groups holding a single call AND few turns having three or more to fold — and failing it is not an automatic go: a partition can be coarse and still rare, or common and still thin, and those are different products. The fold this measures with is the one the semantics pass settled, moved out of that test rather than copied: two implementations of a partition drift, and the assay and the fixtures have to be reading the same rules. The gate itself runs on samples built to sit on each side of it, because an instrument nobody has seen produce a number is one nobody has checked. |
||
|
|
37fdd2ac17 |
bench(catalog-detector): who owns "the listing is owed again"
12a fixed the skills listing by having each writer announce itself, which is correct and does not scale: the same probe showed a skill file written past those writers — an editor, a script, the model's own write tool — is in the canonical registry and still owes nothing. A contract that reads "every writer remembers to announce" is one forgotten call from the defect it just fixed. So this measures the three candidates rather than picking one. Rebuild the projection and compare it; fingerprint path, size and mtime; or trust that every write announced itself. Owed is measured, not declared: the block is rendered before and after each transition, and owed means those differ. Under that definition rebuilding the projection is not an estimate of the truth, it is the truth, and what the arms establish is where the two cheap detectors diverge from it. Both diverge, in opposite directions. Metadata misses a change whose length and mtime are preserved, which is not exotic — a branch switch restores content and stamps it with the mtime the file had — and it fires on a touch and on a body edit, neither of which changes a line the model sees. Observed-write is blind to every out-of-band write by construction; it can be an optimization, never the owner of correctness. The cost argument for the cheap ones then mostly evaporates. Rebuilding the projection costs 166µs on a workspace with no skills of its own, 668µs at ten, 2.4ms at fifty and 6.4ms at two hundred; metadata saves 25% of that at two hundred and 60% at ten, because the walk is the cost and reading the files is not. A turn costs seconds. Paying the truth once per turn is noise, and it was avoided on an assumption nobody had measured. Two measurement traps are worth naming. Timing a detector with its own accounting inside it measures the accounting: attaching the file/byte counter to the projection detector reported it 2.5x slower than it is. And pointing the project root and the home root at one directory walks it twice, which doubled every number until they were separated. |
||
|
|
9d6a8b80b4 |
build: a command's binary is not something git can notice
`go build ./tools/repolint` writes the binary into the working directory, and two of those — repolint and covergate — had been committed. Nothing builds them that way on purpose: every caller, Makefile target and CI step runs them with `go run`. So any probe, agent or script that builds one produced a diff on a tracked file that had nothing to do with what it was doing. Untracking them is half of it. The other half is that the same accident is available to all thirteen commands in the root module, and only three were ignored, so the ignore list is completed from the package list rather than from the two names that happened to get caught. `build-artifact` holds the two lists together: every main package in the root module must have its output name covered by a root-anchored .gitignore entry. It reads the package list, so a command added tomorrow fails before anyone builds it, and it passes on a clean checkout where there is nothing lying around to observe — a rule that only fired on a dirtied tree would be silent in CI, which is the one place this needs to hold. A pattern that is not root-anchored, or that names a directory, does not count as covering the drop. The walk stops at a nested go.mod: a command in another module builds into that module's directory, and the root .gitignore is not what covers it. |
||
|
|
fe476b8494 |
test(bench): preflight the request the model is handed, not the projection
The fixtures were checked against the model-visible projection. That is what the agent believes; a provider.Request is what the model is told, and rendering, sanitization and tool-schema construction sit in between. So each of the 36 arm states now resumes the way a restart does — transcript from disk, projection from its sidecar, the shipped tool surface at that arm — runs one round against a capture provider, and is judged on the request it produced. The capture answers with a clean terminal response. An empty one would buy an empty-final retry and a second request, and the round under inspection is the first. The turn's request is the one carrying tools: a fold on the way in asks the same provider for a digest, and taking request[0] blindly inspected the summarizer's prompt instead. The scan enumerates the provider-visible surface rather than marshalling a Message whole — content, reasoning_content, tool-call names and arguments, responses_items, and every tool name, description and parameter schema — so a leak is reported where it is, and local-only fields the boundary is supposed to strip cannot produce a false hit. Those are asserted absent in their own right: raw_content, LocalOnly, decision receipts, tool execution and memory citations crossing the boundary is a regression whether or not a marker rides along. It found what the projection check could not. i05's answer marker "session" was readable in tools[0].parameters and tools[2].parameters — in all four arms, because a tool schema is not part of any projection. r01's "partial" was one ordinary word away from the same fate. Both are nonces now. Search arms are held to the ablation's definition: both keep positions, only the control has query, and the search-off arm's request may not contain the wording that offers a search — checked over the whole surface, not just the schema, so a description or an index line cannot reintroduce it. Getting a resumed fixture to keep its projection turned up one more thing worth recording: projectionCoversTail cannot revalidate a projection that covers the entire transcript, since it has neither a matching transcript version nor a live tail to sit in front of. Real sessions always have one, because work continues after a fold. The fixtures now do too. Verified by making each fail: stop scanning tool schemas and i05's leak goes quiet; leave the search tool unswapped and the search-off arm is named twice, once for the parameter and once for the wording. benchmarks/* join cmd/ and desktop/ as hosts in the layering rule. Each is a main that assembles a real agent, and a harness reproducing the shipped tool surface has to reach the frontend that defines it. Nothing is opened to the kernel by this: the rule that matters is that nothing below a frontend may import one, and a benchmark is above every frontend. No provider is called anywhere in this commit. This is the gate that says the API spend would measure what it claims to. |
||
|
|
72dfeaadf8 |
fix(installsource): a symlink case skips on what the filesystem answers, not on the OS name
TestApplyLocalSkillLinkMode asked runtime.GOOS, and asked it after asserting resp.OK — so a machine that cannot create a symlink failed on the refusal the tool had correctly reported, rather than skipping. Windows allows symlinks under Developer Mode or elevation, which the platform name cannot tell apart from a plain account, so the check is now the read three cases in this file already make: try it, and skip on the error the filesystem gives back. A Windows host that can make one now runs the case instead of skipping it. Also ignore *.exe~ — what the linker leaves when it cannot replace a binary that is still running, which on Windows is every rebuild while Studio is open. |
||
|
|
c777cb73ab |
fix(repo): a built repolint binary is not a source file
go build ./tools/repolint leaves repolint.exe in the tree, and it went in with the previous commit. Removed, and *.exe ignored so the next one cannot. |
||
|
|
84c257e56b |
feat(bench): measure delegation independence and anchor resistance (#8154)
Reasonix isolates a child's session but not its conclusions. The context capsule records, field by field, what a child inherited from its parent and every field is false — yet the one channel that actually carries information, the parent's free-text prompt, is not modelled at all. Two host-recorded instruments close that gap. Evidence origin splits what a delegation's own text pointed at into directory scope, which is the unavoidable cost of handing work off, and named files, which say where the answer is. The child is credited only for paths no named file already covered, so a scope hint never erases its work. The two are counted apart because one number reports the cheap act as the expensive one — the combined form read 0-3% independence where the split reads 94-100%. The -anchor arm hands the agent a hypothesis before it has read anything: the task's real cause, a plausible wrong one, or none. Tasks without an authored seed are skipped rather than run as the control, so a seeded denominator never absorbs a control run. Measured over 210 runs across two corpora (deepseek-flash, 5 reps each), anchor resistance is 1.00 on both — including one whose answers no command can check — while a wrong hypothesis costs ~2x wall and ~1.35x tokens to unwind. Children discovered 94-100% of their own evidence: this parent hands scope, not answers. Also guards two failures found while running it: the bench read metrics tags the agent had stopped emitting, and a failed grader's output never reached the record that had to explain the failure. |
||
|
|
63d409359d |
build(lint): make function size and complexity a gate, not a report
The complexity monitor ran funlen and cyclop with issues-exit-code forced to 0 and said so in its own script: "Future work: block only new violations relative to this baseline." A report nobody is blocked by is a report nobody reads, and that future is now buildable — repolint's ratchet already carries five rules. Measuring in repolint rather than parsing golangci-lint output keeps one set of semantics (weight is the excess over the limit, per file and repo-wide) and widens the scope from internal/agent to the whole tree. The thresholds are the ones the monitor already used: 120 lines, complexity 30, tests excluded. What that surfaces is not marginal: build is 1932 lines at complexity 309, and chatTUI.update is 1005 at 252. Both are frozen now — they cannot gain a line or a branch. The report-only script, its config, and its two CI steps are deleted rather than kept alongside: the same metrics now have a gate that stops people. |
||
|
|
ee8a85ec04 |
chore(standards): machine-enforce comment, file-size, and layering rules (#7806)
* chore(standards): gate comments, file size, and layering in CI The comment and file-size rules lived only in an untracked CLAUDE.md written for a TypeScript project, so nothing ever checked them, and REASONIX.md told editors to match the surrounding comment density -- which is what reproduced the essays. tools/repolint makes the rules machine-checkable and ratchets them: recorded debt passes, anything new fails the required lint check. Weights are excess lines rather than counts, so trading a short violation for a long one still trips the gate. Layering encodes the contract REASONIX.md already claimed and the import graph already satisfies, with one recorded exception (capdiag reaching into boot). * chore(standards): point CLAUDE.md at REASONIX.md Claude Code loads CLAUDE.md and Reasonix loads REASONIX.md, so a repo with only the latter leaves Claude Code sessions with no standing instructions. A committed one-line pointer keeps the single source of truth without the two files drifting apart again; the negation un-ignores it for contributors whose global gitignore drops CLAUDE.md. |
||
|
|
b6da01dbdc |
feat(release): separate Stable and Preview histories
Context: Stable and Preview need independent reviewed identities while CLI, Desktop, npm, and changelog publication remain one protected release event. Changes: - Add a protected Preview orchestrator with a shared ordinal and SignPath preflight. - Bind reviewed release notes to immutable all-surface publication markers. - Route downloads and changelog archives by channel with accessible theme-aware navigation. - Harden release authorization, manifests, tests, and operator documentation. Compatibility: - Legacy catalog entries without managed status remain readable and public. - Existing Stable pointers remain unchanged, and Preview continues mirroring the legacy canary pointer for older Desktop clients. - No persisted user state or Wails JSON contract changes. Verification: - bash scripts/release-workflows.test.sh - node --test scripts/release-notes.test.mjs - cd desktop && go test ./cmd/sign ./internal/update - cd site && npm test - cd site && npm run build - actionlint for all release workflows |
||
|
|
26b256b5d5 |
refactor(agent): simplify run/tool state and harden live tests / 重构 Agent 状态并隔离真实测试 (#7010)
* refactor(agent): add TaskTool Options constructor / 增加 TaskTool Options 构造入口 Problem: NewTaskTool takes 18 positional parameters, which is hard to extend and easy to mis-order at call sites. Root cause: Construction was only exposed as a long positional helper with no structured options type. Fix: - Add TaskToolOptions and NewTaskToolWithOptions as the internal standard entry point (empty SysPrompt still maps to DefaultTaskSystemPrompt). - Keep the 18-arg NewTaskTool as a compatibility wrapper for one full iteration cycle. - Migrate boot wiring and the shared test helper to Options. - Add equivalence tests for config, schema, empty prompt, resolver, and execute behavior. Verification: go test ./internal/agent -run 'TestNewTaskToolWithOptions|TestTaskTool' -count=1 go test ./internal/boot -count=1 * refactor(agent): split Run and executeOne state machines / 拆分 Run 与 executeOne 状态机 Problem: Agent.Run and executeOne mixed lifecycle, turn policy, and tool policy in two very large methods, raising cognitive load without changing behavior. Root cause: Turn-loop counters and tool-call gating lived inline instead of package- private staged helpers with a single ownership model for locks/defers. Fix: - Keep Run as the lifecycle owner of workspace lease, evidence commit, delivery checkpoint, steer queue, and active-turn cleanup. - Extract runLoopState plus beginRunTurn, runToolLoop, handleFinalResponse, and handleToolRound without changing stream/persist/batch/compact order. - Split executeOne into parseToolCall, resolveToolPolicy (plan/proxy, delivery, recovery/permission), prepareToolExecution, and finishToolExecution behind toolCallPlan, preserving permission-before- lease and PreToolUse-before-execute semantics. Verification: go test ./internal/agent/... -count=1 go test -race ./internal/agent/... -count=1 REASONIX_RELEASE_CACHE_GUARD=1 go test ./internal/agent -run '^TestReleaseCacheHitGuard$' -count=1 * ci: report-only agent complexity monitor / 增加 agent 复杂度报告模式监控 Problem: The repo already runs gofmt, vet, build, full tests, race, and golangci-lint, but had no trend signal for remaining internal/agent hotspots after structured extraction. Root cause: Main lint intentionally avoids funlen/cyclop hard fails that would generate a flood of pre-existing noise. Fix: - Add a report-only funlen+cyclop config scoped to internal/agent production code with generous thresholds (not a hard 50-line rule). - Run it beside the existing lint job with issues-exit-code=0, write a CI summary, and upload the report as an artifact. - Ignore the generated local report file. Verification: bash scripts/agent-complexity-report.sh * test(provider): isolate live DeepSeek cache probe / 隔离 DeepSeek 真实缓存探针 Problem: The environment-gated DeepSeek cache probe ran during ordinary test suites whenever a stale API key was present, and its diagnostics still claimed tool-call reasoning replay should have zero prompt-token cost. Root cause: The probe had no explicit live build tag and retained commentary from the obsolete policy that stripped reasoning_content from DeepSeek tool-call turns. Fix: Gate the probe behind the live build tag, document the tagged invocation, describe required replay versus the empty-key recovery fallback, and fail when the required replay request is rejected. Verification: - go test ./... - go test -race ./internal/provider/openai -count=1 - env -u DEEPSEEK_API_KEY go test -tags live ./internal/provider/openai -run TestRealDeepSeekCacheProbe -count=1 -v - go vet ./internal/provider/openai - ./scripts/cache-guard.sh |
||
|
|
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. |
||
|
|
7f00d2c260 |
feat(desktop): Redesign theme experience with official packs / 主题体验改造与官方主题 (#6614)
* feat(desktop): Add Theme Pack V1 engine with theme library / 新增 Theme Pack V1 引擎与主题库 Controlled, non-executable desktop skins: semantic color tokens, density and corner recipes, and an optional local background image with focus and safe area. Includes manifest validation, atomic import with replace confirmation, content-addressed asset serving, live preview with cancel restore, density consumers, transparent layout under backgrounds, contrast warnings, and safe-mode fallback to Graphite. / 受控不可执行的桌面皮肤:语义色令牌、密度 与圆角配方、可选本地背景图(焦点与安全区)。包含清单校验、原子导入与覆盖 确认、内容寻址资源服务、可取消的实时预览、密度消费者、背景下布局透明化、 对比度警告与安全模式回退 Graphite。 * feat(desktop): Redesign theme experience with official packs / 主题体验改造与官方主题 Problem: Appearance settings exposed two competing base-style pickers and applied theme packs immediately on card click, while activeThemeId could store base style ids. Official skins and a gallery/overview IA were still missing. Root cause: Theme Pack V1 layered packs on top of desktop.theme_style without a unified state model or select-then-apply UX, so base styles, official themes, and user packs competed for the same controls. Fix: - Bump desktop-theme-state to schema v2: activeThemeId is official/user only; migrate legacy base-style active ids into desktop.theme_style. - Add GetThemeExperience, ActivateBaseStyle, DisableThemePack, and RestoreGraphiteAppearance; reject base ids in ActivateThemePack. - Embed eight official themes with fixed order, asset routing, and budgets. - Ship AppearanceOverview + ThemeGallery (select ≠ apply, temp preview, immersive detail) driven by themeExperience.ts. - Document the IA and provenance; keep procedural art as placeholders. Verification: - cd desktop && go test ./... - cd desktop/frontend && pnpm typecheck && pnpm test:all && pnpm build - wails generate module (bindings match, no drift) * feat(desktop): complete the theme gallery and editor experience Problem: The theme gallery still had fragmented preview actions, placeholder flagship art, generic base-style previews, and a minimal custom-theme dialog that could not tune semantic colors or independent scenes. Root cause: Theme Pack V1 modeled one background and the gallery treated selection, preview, and persistence as separate interactions without a shared localized editor state. Fix: Upgrade the pack contract compatibly to V2 with independent home and workspace scenes, rebuild the gallery around immediate reversible preview and explicit apply, add localized semantic color and scene controls, replace the eight official artworks, and align base-style previews and actions with the settings UI. Verification: - cd desktop && go test ./... - cd desktop/frontend && pnpm test:all - cd desktop/frontend && pnpm typecheck - cd desktop/frontend && pnpm check:css - cd desktop/frontend && pnpm build - Browser-verified Chinese and English labels, live preview, scene switching, and cancel restoration * fix(desktop): satisfy the full-module formatting gate Problem: The desktop CI job stopped before tests because its repository-wide gofmt check reported main.go and theme_assets.go. Root cause: The earlier theme integration left one struct field with manual alignment and one extra trailing blank line; package tests compile these files but do not enforce gofmt byte formatting. Fix: Run gofmt on both reported files without changing runtime behavior. Verification: - cd desktop && gofmt -l . - cd desktop && go test ./... * fix(desktop): clarify theme scene content placement Problem: The scene editor exposed a vague content-safe-area label without explaining that it controls foreground text and card placement. The desktop CI lint gate also found four empty or unused Theme Pack leftovers. Root cause: The persisted safeArea field was surfaced directly as user-facing copy, while placeholder branches and helpers remained after the Theme Pack integration. Fix: Rename the control to Interface content area, add localized guidance and radio semantics, and remove or complete the four lint findings without changing theme behavior. Verification: - cd desktop && go test ./... - cd desktop && go vet ./... - cd desktop/frontend && pnpm typecheck - cd desktop/frontend && pnpm check:css - cd desktop/frontend && pnpm test:all - cd desktop/frontend && pnpm build - Theme Pack contract: 195 passed - Verified the localized control in the running browser UI * fix(desktop): unify theme confirmation dialogs Problem: Theme deletion, replacement imports, and export-rights checks used window.confirm, which surfaced platform-native dialogs that did not match the Reasonix settings UI. Root cause: The Theme Pack flows lacked a reusable in-app confirmation primitive, so both the gallery and legacy library called the browser API directly. Fix: Add an accessible portal-based Reasonix confirmation dialog with danger styling, safe focus behavior, Escape and backdrop cancellation, and focus restoration. Migrate all six theme confirmation call sites and localize their titles and actions. Verification: - pnpm typecheck - pnpm check:css - pnpm test:all - pnpm build - Browser: delete confirmation stayed in-app, Cancel received focus, Escape restored focus, and the temporary theme was deleted without a native dialog * fix(desktop): restore theme preview and custom fonts Problem: - Custom UI and monospace fonts could no longer be configured from the redesigned appearance page. - Selecting a user theme updated details without starting the advertised global preview. Root cause: - AppearanceOverview discarded both custom font name callbacks and omitted their editors. - ThemeGallery limited non-immersive previews to the catalog tab. Fix: - Restore localized custom font editors and their persistence callbacks. - Preview every non-immersive theme selection, including user packs. - Add focused source-contract coverage for both behaviors. Verification: - pnpm test:all - pnpm build - pnpm check:css - Browser verification of custom font variables, user-pack preview, and Back restoration |
||
|
|
7e83df9641 |
chore(gitignore): ignore local temp/ scratch directories
Co-Authored-By: Claude Fable 5 <[email protected]> |
||
|
|
11963ffa3b |
chore(desktop): refresh app icon assets (#4321)
Replace the desktop app icon with a blue-background single-mark design (R + whale), add the SVG source under the build-asset ignore exceptions, and regenerate the Windows ICO so macOS, Linux, and Windows share consistent artwork. Closes #4321 |
||
|
|
ea0c3a9b69 |
Improve crash reporting diagnostics / 增强崩溃上报诊断 (#4285)
* feat: enrich crash reporting pipeline * fix: address crash report worker review |
||
|
|
3025f5af2a |
feat(agent): deterministic tool-result pruning + cache-TTL-aware cold-resume maintenance (#3968)
* feat(agent): prune stale tool results before folding and on cold resume Stale tool results are re-derivable (files re-read, commands re-run), so eliding them is a free, lossless alternative to the paid summarize fold. Prune runs only where a cache reset is already being paid: at the compact trigger, where it skips the fold entirely when eliding alone clears the threshold, and on resume after the provider prefix cache has expired (cacheColdAfter), where rewriting history costs no extra misses and directly shrinks the full-price first request. No message is ever removed, so tool_call/result pairing and signed reasoning stay intact by construction; originals are archived like fold drops. * bench(e2e): context-maintenance driver — cold-resume A/B + placeholder comprehension Three real-API scenarios for the prune work: seed two identical-shape fat sessions, A/B the cold-restart miss tokens with and without pruning after the provider cache has expired, and verify the model re-reads a file behind a prune placeholder instead of answering from nothing. * fix(control): widen cold-resume prune threshold to a risk-asymmetric default Pruning a still-cached session costs ~4x the miss tokens of leaving it alone (measured warm-cache A/B), while a threshold that is too large only forgoes a free prune. Default to 24h until the cache-ttl probe pins the real retention; never set below it. * bench(e2e): check write/unmarshal errors in the maintenance driver (errcheck) * fix(control): derive cold-resume idle from branch meta only (CodeQL go/path-injection) The os.Stat mtime fallback fed a user-influenced path straight into a filesystem call. Branch meta is guaranteed for every session the controller has snapshotted, so the fallback only ever covered never-saved imports — those now skip one prune until their first snapshot creates the meta. --------- Co-authored-by: reasonix <[email protected]> |
||
|
|
842aff852a |
site: serve downloads from dl.reasonix.io, bake contributor avatars into the build (#3923)
Mainland-China visitors hit two walls on the new site: the contributor wall loads 40+ images from avatars.githubusercontent.com (blocked there), and downloads went through the rate-limited pub-*.r2.dev dev subdomain. - a prebuild script now fetches community stats and downloads every contributor avatar into public/av/, so the wall is served same-origin with zero third-party requests; any avatar that fails to fetch falls back to its remote URL, and the whole script degrades to the committed snapshot rather than failing the build - download links and the runtime version check now use the R2 bucket's custom domain dl.reasonix.io (Cloudflare-cached, range requests verified) instead of pub-*.r2.dev Co-authored-by: reasonix <[email protected]> |
||
|
|
b87a878cad |
feat(desktop): project topic tabs and context inspector (#2925)
* feat(desktop): add project tabs and context inspector * feat(desktop): refine project workspace UI * fix 打开一致加载 * 顶部统一logo+左侧边栏+统一菜单 * 底部最低宽度 * update much * update code * update code * feat(desktop): refine workspace layout and message actions * fix(desktop): scope file refs to active tab root * test(desktop): stabilize capabilities config dirs * fix(desktop): protect sessions open in other tabs * fix(desktop): resume scoped history in matching tab * chore(desktop): drop committed scratch artifacts; ignore tmp/ and .codex/ The project-tabs branch carried 30 throwaway screenshots, a mock HTML, and a codex env file into the tree because tmp/ and .codex/ were never ignored. Remove them and add the ignore rules so scratch output stays out of future commits. * test(desktop): isolate AppData per test so the suite passes on Windows The new project/topic/workspace tests only overrode HOME and XDG_CONFIG_HOME, but desktopConfigDir() and config.MemoryUserDir() resolve through os.UserConfigDir(), which reads %AppData% on Windows and ignores those vars. With AppData pointed at one shared TestMain dir, the project/topic/workspace registries leaked across tests: full-package runs failed (project tree len 2 want 1, trash mixing other tests' sessions, Plugins[0] reading a neighbor's MCP entry) while each test passed alone. Route every test through isolateDesktopUserDirs, which already sets HOME, USERPROFILE, XDG_CONFIG_HOME, and AppData to a per-test temp dir. go test ./... (and -race) now pass on Windows. --------- Co-authored-by: reasonix <[email protected]> |
||
|
|
324c3bb6fb |
feat(site): rebuild the landing page in Astro, deploy via Actions (#2818)
Replace the hand-written single-file page with a real Astro project (site/) and a GitHub Actions Pages deploy. Minimal, editorial design aligned with Codex / Claude Code: warm paper bg, near-black type with one restrained accent, Inter Variable, generous whitespace, a typing terminal demo, and an elevated install widget in the hero (npm / Homebrew / Download) so the very first thing a visitor sees is how to install or download — downloads go through the R2 CDN. Pages source is switched from the legacy branch build to the Actions workflow. Co-authored-by: reasonix <[email protected]> |
||
|
|
45927ce7e6 |
feat(release): npm + Homebrew distribution for the CLI (#2775)
* feat(release): npm + homebrew distribution for the CLI The CLI only shipped as raw GitHub-release archives; the README promised `npm i -g reasonix` and brew, neither of which existed. - goreleaser (.goreleaser.yaml) now owns archives, SHA256SUMS, the GitHub release, and an auto-maintained esengine/homebrew-reasonix formula, replacing the hand-rolled scripts/release.sh. - npm/ ships the binary the modern way: a `reasonix` wrapper whose bin shim exec's a per-platform `@reasonix/cli-<os>-<arch>` optionalDependency, so npm installs only the matching native binary — no postinstall, offline-OK. npm/build.mjs cross-compiles all six targets and publishes the set. - release.yml fans out to goreleaser + npm jobs on a v* tag. Publishing needs three one-time setup items: the @reasonix npm org, the esengine/homebrew-reasonix tap repo, and NPM_TOKEN + HOMEBREW_TAP_TOKEN secrets. Desktop (desktop-v* / Wails + R2 updater) is unchanged. * fix(release): homebrew_casks + tag npm prereleases Two issues caught running the release locally (goreleaser snapshot + npm publish --dry-run): - goreleaser removed `brews` in v2.16; the `~> v2` action would have failed the release. Migrate to `homebrew_casks` (binary cask + a postflight that strips the macOS quarantine xattr, since the archives aren't notarized). Cask install is macOS-only, so the README brew line drops the Linux claim — Linux installs via npm or the release tarball. - npm refuses to publish a SemVer prerelease without an explicit dist-tag, so a `v1.0.0-rc1` tag would have errored. build.mjs now publishes prereleases under `next` and stable versions under `latest`. --------- Co-authored-by: reasonix <[email protected]> |
||
|
|
ca5c6a204b |
fix(gitignore): update ignores for the Go v2 layout (#2415)
Refresh root .gitignore for v2: ignore Go test/profiling scratch, Wails desktop build outputs, frontend deps/caches, Windows artifacts, and local .reasonix state, while keeping tracked placeholders (desktop/build/appicon.png, .reasonix/commands/review.md) via negation rules. Verified no tracked file is newly ignored. |
||
|
|
fd0dfff0fd |
ci: add CI and release workflows
CI (ci.yml): on push/PR to main-v2, run gofmt check (root module), go vet, build, and test on ubuntu + macOS, with the Go version pinned to go.mod. Release (release.yml + scripts/release.sh): on a v* tag, cross-compile all six targets, bundle the matching CodeGraph runtime beside the binary on darwin/linux (verified against CodeGraph's published SHA256SUMS), package per-platform archives, and publish a GitHub release with a SHA256SUMS manifest. Windows ships a bare binary for now — its bundle layout is not yet verified, so it falls back to a PATH-installed codegraph. Verified release.sh end-to-end locally: all six archives build, the four darwin/linux ones carry codegraph/bin/codegraph beside reasonix (so the bundled-path resolver finds it), checksums match. |
||
|
|
1e68e2e018 |
feat(codegraph): integrate CodeGraph as a built-in MCP server
Wire CodeGraph (tree-sitter + SQLite symbol/call graph) in as a built-in stdio MCP server so the agent gains codegraph_* search/context/explore/ trace/node tools — replacing the old embedding semantic search with a zero-API-cost, cross-language code-intelligence layer. - internal/codegraph: Resolve (config path > bundle beside the executable > PATH) and EnsureInit. Init is a bare `codegraph init` (~100ms, size- independent); serve's daemon indexes in the background, so startup never blocks even on a large repo. - plugin.Spec gains Dir; the stdio transport honors it so the cwd-aware server is pinned to the project root (also fixes a latent cwd issue). - boot injects the server when [codegraph].enabled (default true) and the bundle resolves; a missing bundle is a silent no-op (falls back to grep). - mcp list reports the built-in server's resolved status, doubling as a headless preflight. - .gitignore the per-project .codegraph/ index. Tested: unit tests for Resolve/EnsureInit, plus an env-gated end-to-end (make e2e-codegraph) that handshakes a real CodeGraph bundle over MCP and calls codegraph_search. Distribution (bundling the binary in the release archive) is intentionally left as a follow-up. |
||
|
|
7de6a2474f |
feat: import Go implementation as v2 kernel (renamed duo → reasonix)
Bring in the finished Go rewrite (developed under the placeholder name "duo") as the v2 codebase. Code-only import — no upstream git history. Config- and plugin-driven coding agent: registry core, OpenAI-compatible providers, builtin tools, MCP (stdio + HTTP) plugins, permission + sandbox, custom slash commands, @-references, two-model collaboration. Renamed throughout: duo→reasonix, Duo→Reasonix, DUO→REASONIX — module path, imports, CLI name, cmd/ dirs, reasonix.toml, .reasonix/commands, and $REASONIX_* env prefixes. Kept the v2 MIT LICENSE (the Go project had none). Verified: go build ./... · go vet ./... · go test ./... all green; gofmt clean. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> |
||
|
|
32a4c02e54 |
chore: initialize v2 — ground-up rewrite
Clean-slate rebuild on an orphan branch: - Kernel rewritten in Go (was TypeScript) - Web client removed - Desktop client redone from scratch Legacy TS implementation remains on `main` for reference. Seeded with LICENSE (carried over), a Go-oriented .gitignore, and a minimal README. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> |