Compare commits

...
Author SHA1 Message Date
esengine a6329ea5f4 fix(settings): declare attachments.max_stream_bytes as Guarded
The key landed with the streaming attachment ceiling without a class, so the
settings package panics at registration and every test importing it fails.
2026-10-17 19:11:19 -07:00
YHH 5e53be32ab Merge pull request #13685 from esengine/fix/13682-versions-nav-update-dot
fix(settings): mark the Version row when an update is known
2026-10-17 19:00:48 -07:00
YHH 2d6656cd14 Merge pull request #13544 from SivanCola/fix/native-file-streaming-studio
fix(studio): reference native files and stream attachment uploads / 大文件路径引用与流式上传
2026-10-17 18:49:56 -07:00
YHH ce7c69ca75 Merge pull request #13681 from esengine/fix/browser-minimized-click
fix(desktop): raise a minimized window unseen so the agent's clicks reach the built-in browser
2026-10-17 18:42:53 -07:00
esengine edaccd5f4c fix(settings): mark the Version row when an update is known 2026-10-17 18:37:55 -07:00
YHH 8dbf869fa8 Merge pull request #13680 from esengine/a5/settings-catalog
feat(settings): declare every user-config key with a class, an effect timing and a secret flag
2026-10-17 18:37:34 -07:00
YHH 0ea504ea77 Merge pull request #13679 from esengine/fix/dispatch-switch-off-identity
fix(peers): say that the dispatch switch is off, apart from a host that cannot open panes
2026-10-17 18:30:26 -07:00
YHH e88e02556a Merge pull request #13676 from esengine/fix/11577-editor-text-size
fix(desktop): the code editor's text and find panel follow the interface text size
2026-10-17 18:24:46 -07:00
esengine 2529f757f4 test(desktop): restore the focused-window assertion 2026-10-17 18:22:57 -07:00
esengine 466e44779b fix(desktop): a window that fails to raise gets its opacity and mouse back 2026-10-17 18:22:18 -07:00
esengine f9714be60d feat(settings): a writable value is judged by the config's own setter, a bound, or a stated freedom 2026-10-17 18:21:48 -07:00
esengine 8ae413c9ef fix(settings): never read a structured key, and declare protection-relaxing keys Elevated 2026-10-17 18:20:05 -07:00
esengine 6efead8677 fix(settings): read and write an optional key through its pointer 2026-10-17 18:19:40 -07:00
esengine 792c1a0fcd fix(settings): declare only effect timings a plain file write delivers 2026-10-17 18:17:16 -07:00
esengine 8a968bb900 test(desktop): the live browser check submits each turn after the last has ended 2026-10-17 18:16:55 -07:00
esengine 2180caa3d8 test(desktop): the person who restores the window also activates it 2026-10-17 18:16:55 -07:00
esengine 8e105ba1fd fix(desktop): raise a minimized window unseen for the agent's input to the built-in browser 2026-10-17 18:16:55 -07:00
esengine 245928be33 test(desktop): the live browser check uses the controls the panel has now 2026-10-17 18:16:55 -07:00
esengine 0b3c709ed1 style(settings): use reflect.TypeFor and range over int 2026-10-17 18:16:09 -07:00
esengine 32a79410d2 feat(settings): declare every user-config key with a class, an effect timing and a secret flag 2026-10-17 18:15:44 -07:00
esengine aa67e223cc fix(peers): say that the dispatch switch is off, apart from a host that cannot open panes 2026-10-17 18:12:06 -07:00
YHH bafb4c0818 Merge pull request #13674 from esengine/fix/flaky-frontend-tests
fix(desktop): the branch menu survives the chip settling, and its list is re-read when the root moves
2026-10-17 18:03:41 -07:00
YHH b79d5d0836 Merge pull request #13675 from esengine/a5/parity-exemptions
chore(repolint): scope five frontend-parity edges where a frontend reaches the same behaviour another way
2026-10-17 17:40:31 -07:00
esengine 7a1bbc51b9 fix(desktop): the code editor's text and find panel follow the interface text size 2026-10-17 17:37:37 -07:00
esengine 791c00f496 chore(repolint): scope five frontend-parity edges where a frontend reaches the same behaviour another way 2026-10-17 17:29:55 -07:00
YHH 31f230cf49 Merge pull request #13673 from esengine/docs/studio-2.44.0-notes
docs(release): studio 2.44.0 notes
2026-10-17 16:19:24 -07:00
esengine f6a3680d09 docs(release-notes): name the pull request in the fragment 2026-10-17 16:19:00 -07:00
esengine adc02158fb docs(release): studio 2.44.0 notes 2026-10-17 16:11:02 -07:00
esengine 4fd0dab507 fix(desktop): the branch menu survives the chip settling, and its list is re-read when the root moves 2026-10-17 16:09:42 -07:00
YHH 481aa4a0eb Merge pull request #13672 from esengine/fix/shell-probe-test-hedge
test(boot): count launches of the planted bash, not hedged candidates, in the proven-shell effect test
2026-10-17 15:46:19 -07:00
esengine 7bf97efccb test(boot): count launches of the planted bash, not hedged candidates, in the proven-shell effect test 2026-10-17 15:27:48 -07:00
YHH b50bb0c29e Merge pull request #13470 from esengine/fix/13197-slash-catalog-memo
fix(skill): memoize slash-list discovery for two seconds
2026-10-17 15:21:14 -07:00
esengine 1c718f8547 test(skill): a skill added outside the store shows once the slash-list memo has aged out 2026-10-17 15:08:07 -07:00
YHH 1339e794d8 Merge pull request #13530 from esengine/fix/12900-adopted-hint
feat(desktop): say what a shipped report is on the feedback level card
2026-10-17 15:04:52 -07:00
YHH 6a6342446d Merge pull request #13668 from esengine/feat/open-document-default-app
feat(studio): open a local HTML, SVG or PDF file in the system default program
2026-10-17 15:04:23 -07:00
YHH f743552038 Merge pull request #13671 from esengine/feat/13526-provider-retries
feat(agent): agent.max_provider_retries sets how often a failed provider connection is retried, 0 until cancelled
2026-10-17 15:03:52 -07:00
esengine 188f100cc5 fix(serve): check an opened document's path against the roots as strings before touching the disk 2026-10-17 14:37:08 -07:00
YHH e1211b7715 Merge branch 'studio' into fix/12900-adopted-hint 2026-10-17 14:34:21 -07:00
YHH c926b5306b Merge branch 'studio' into fix/13197-slash-catalog-memo 2026-10-17 14:34:17 -07:00
esengine 0fe1bbc31b test(boot): name the status the retry probe returns 2026-10-17 13:17:16 -07:00
esengine 3b0ab63f4a feat(agent): agent.max_provider_retries sets how often a failed provider connection is retried, 0 until cancelled 2026-10-17 13:07:38 -07:00
YHH df91037c1c Merge pull request #13670 from esengine/feat/peers-wire-parity
test(repolint): hold the desktop's PeersSettings mirror to the kernel's
2026-10-17 12:59:33 -07:00
esengine 1bb970d834 test(repolint): hold the desktop's PeersSettings mirror to the kernel's 2026-10-17 12:48:51 -07:00
YHH 1593ac424b Merge pull request #13661 from esengine/a5/acp-mcp-session-control
feat(acp): reconnect and disconnect MCP servers per session, submit extension forms
2026-10-17 12:40:31 -07:00
esengine a4781fa524 Merge origin/studio into a5/acp-mcp-session-control 2026-10-17 12:26:20 -07:00
YHH 1d4c43e98b Merge pull request #13669 from esengine/test/machine-list-explicit-times
test(cli): the machine-list fixture keeps the update times it asks for
2026-10-17 12:23:12 -07:00
esengine 47addbe945 test(opendoc): hold the Windows opener's argument list whole, and make the opener tests path-portable 2026-10-17 12:15:49 -07:00
esengine c0cf65568c test(cli): the machine-list fixture keeps the update times it asks for 2026-10-17 12:10:41 -07:00
YHH 987c13b4cb Merge pull request #13667 from esengine/a5/goal-usage
feat(goal): show a goal's usage and stop cause in the desktop and the terminal
2026-10-17 12:05:31 -07:00
YHH 414bec6f4d Merge pull request #13664 from esengine/fix/pricecheck-deepseek-cny
feat(pricecheck): check DeepSeek's yuan page as well as the dollar one
2026-10-17 12:01:36 -07:00
esengine 42d4313907 feat(studio): open a local HTML, SVG or PDF file in the system default program 2026-10-17 11:56:49 -07:00
esengine 813884f9d2 feat(goal): show a goal's usage and stop cause in the desktop and the terminal 2026-10-17 11:38:18 -07:00
esengine d6b6acb309 feat(pricecheck): check DeepSeek's yuan page as well as the dollar one 2026-10-17 11:35:16 -07:00
YHH b2f9c25ae7 Merge pull request #13656 from esengine/fix/skill-warnings-once
fix(skill): write each distinct discovery diagnostic once per store
2026-10-17 11:31:31 -07:00
YHH 504465f8de Merge pull request #13657 from esengine/fix/narrow-line-limit
refactor(config): side conversations and queued prompts are held with the other agent limits
2026-10-17 11:14:40 -07:00
esengine 2301cb0fd7 feat(acp): scope the persistent MCP capabilities away, with the trust gate documented 2026-10-17 11:14:37 -07:00
esengine c1617dfc69 feat(acp): reconnect and disconnect MCP servers for one session, submit extension forms 2026-10-17 11:14:37 -07:00
YHH 876a4ae39d Merge pull request #13658 from esengine/docs/win-port-note
docs(release): word the Windows port note for users
2026-10-17 11:14:18 -07:00
esengine 299a10f39a docs(release): word the Windows port note for users 2026-10-17 11:03:48 -07:00
esengine 7c9d16c82f refactor(config): side conversations and queued prompts are held with the other agent limits 2026-10-17 10:59:04 -07:00
esengine fc501c8219 fix(skill): write each distinct discovery diagnostic once per store 2026-10-17 10:55:42 -07:00
YHH ee92b3f4d7 Merge pull request #13654 from esengine/a5/narrow-size
refactor(config): move the workspace grants out of heldScope.narrow
2026-10-17 10:53:54 -07:00
esengine e6cb790416 fix(skill): memoize slash-list discovery for two seconds 2026-10-17 10:48:44 -07:00
esengine fcb4264282 refactor(config): move the workspace grants out of heldScope.narrow 2026-10-17 10:43:07 -07:00
YHH 34efa76dd3 Merge pull request #13572 from esengine/test/resource-diag
test(boot): a red resource-tool run names what the schema cache held
2026-10-17 10:38:45 -07:00
YHH 43a515dc27 Merge pull request #13599 from esengine/fix/subagent-done-status
fix(studio): a finished sub-agent says it is handed back, not only how long it took
2026-10-17 10:38:34 -07:00
YHH 1caab22b6a Merge pull request #13651 from esengine/fix/subagent-view-image-by-own-model
fix(delegation): a child on an image model gets view_image whatever the parent's model reads
2026-10-17 10:36:02 -07:00
YHH af3e7e973b Merge pull request #13633 from esengine/feat/13526-queued-inputs
feat(inbox): the session queue holds as many prompts as asked unless the user sets agent.max_queued_inputs
2026-10-17 10:32:56 -07:00
YHH b4c126d66e Merge pull request #13648 from esengine/fix/tree-open-pane-identity
fix(serve): a pane belongs to a folder however the folder is spelled, and a pane with no row is logged
2026-10-17 10:28:49 -07:00
YHH 5e6fe553c7 Merge pull request #13649 from esengine/fix/win-port-retry-eacces
fix(cli): a Windows bind refused as forbidden moves the web server to the next port
2026-10-17 10:27:51 -07:00
YHH f0ab419902 Merge pull request #13646 from esengine/a5/acp-session-info
feat(acp): report session info and configured MCP servers
2026-10-17 10:22:41 -07:00
YHH d4f5a36c1b Merge pull request #13647 from esengine/test/pipe-status-product-bash
test(builtin): the pipe-status probe runs under the bash the product resolves
2026-10-17 10:20:41 -07:00
YHH 73dbd8e8c4 Merge pull request #13645 from esengine/fix/13565-versions-memo
fix(update): share one version-catalog read between Settings, the versions panel and the update check
2026-10-17 10:16:43 -07:00
esengine e7b2cbd8f5 fix(delegation): a child on an image model gets view_image whatever the parent's model reads 2026-10-17 10:14:32 -07:00
esengine c6b36276a8 fix(cli): a Windows bind refused as forbidden moves the web server to the next port 2026-10-17 10:08:40 -07:00
esengine 2fa53d3cc4 test(builtin): the pipe-status probe runs under the bash the product resolves, not the first bash on PATH 2026-10-17 10:07:06 -07:00
YHH 6f653cbfaf Merge pull request #13643 from esengine/a5/video-windows-skip
test(model): skip the swap-after-open video tests on Windows
2026-10-17 10:06:42 -07:00
esengine 2f1c6648b1 feat(acp): report session info and configured MCP servers, scope four lifecycle capabilities 2026-10-17 10:04:56 -07:00
YHH 75883d7f04 Merge pull request #13641 from esengine/chore/release-fragments-2.44
docs(release): merge the two session-messages fragments and rewrite the scratch-spill one
2026-10-17 10:04:00 -07:00
esengine 544d17f6dd fix(serve): a pane belongs to a folder however the folder is spelled, and a pane with no row is logged 2026-10-17 10:02:45 -07:00
esengine fafb92b935 test(model): skip the swap-after-open tests on Windows, which cannot replace an open file 2026-10-17 09:54:22 -07:00
YHH 9696d762ed Merge pull request #13602 from esengine/feat/mcp-signin-follow-ui
feat(desktop): sign in to an MCP server that answered 401, and follow a server's tool changes
2026-10-17 09:50:55 -07:00
esengine 9f1d20cd46 fix(update): share one version-catalog read between Settings, the versions panel and the update check 2026-10-17 09:50:12 -07:00
esengine 70e5060ec3 docs(release): merge the two session-messages fragments and rewrite the scratch-spill one 2026-10-17 09:49:20 -07:00
esengine b3b39feefe Merge origin/studio into feat/mcp-signin-follow-ui 2026-10-17 09:35:51 -07:00
YHH 9374e1eda7 Merge pull request #13617 from esengine/a5/video-native
feat: let read_video hand an MP4 to a model that watches video natively
2026-10-17 09:34:52 -07:00
YHH 37ccec6449 Merge pull request #13595 from esengine/feat/12356-follow-tool-changes
feat(mcp): follow a server's tool changes instead of accepting each one
2026-10-17 09:18:23 -07:00
YHH a75e30b5de Merge pull request #13637 from xxoingr/fix/desktop-account-panel-language
fix(desktop): the account panel says its failures in the window's language
2026-10-17 09:15:38 -07:00
esengine 443d9afb6d docs(model): give the unavailable-video code its own line 2026-10-17 09:08:42 -07:00
esengine 30b2cf89de fix(model): open only the videos a tool in this process handed out
A stored reference carried a path the adapter opened as written, so a session
file that was edited, restored or imported could name a file no tool had
checked. read_video now records the path it verified under the file's hash in a
process-local registry; the adapter resolves through it and reports an
ungranted reference as video.file_unavailable.
2026-10-17 09:08:20 -07:00
xxoingr 783584d3f1 fix(desktop): the account panel says its failures in the window's language
Account caught with String(e), so a coded refusal such as
account.signin_disabled printed "Error: " and the kernel's English
fallback instead of the wording kernel.ts already has, and its
expired-request message was set without t() and had no English. The
port's accountPoll also hand-rolled its fetch and threw the raw
response text, a JSON envelope with no code to word.

Catch with reason(e) like every other panel, put the expiry message
through t() with an English entry, and send accountPoll through post0.
2026-10-18 00:01:00 +08:00
esengine 1010be9737 refactor(plugin): copy the approval map with maps.Copy 2026-10-17 09:00:29 -07:00
esengine 8d7870b067 feat(mcp): follow a server's tool changes instead of accepting each one 2026-10-17 09:00:29 -07:00
SivanCola 18eeb0c8ff fix(attachments): enforce a user-owned streaming disk ceiling
Problem: ordinary binary uploads can consume unbounded workspace disk, and the
review needs direct coverage of file versus directory grants at read consumers.

Root cause: the streaming saver copies until EOF without a byte policy; the
project-config merge must not become an authority for a user disk safeguard.

Fix: enforce a named 1 GiB default while counting real bytes, retain rooted
cleanup on overflow, and return typed ceiling/source facts. Allow user config
to set the limit or explicit zero, hold it across project merges, render it
only at user scope, and strictly read it without degraded config fallback.
Cover selected-file siblings and directory link escape through actual reference
and tool consumers. Incorporate current Studio without rewriting history.

Verification: the former 8-byte/9-byte acceptance fails first and now refuses
cleanly. Full config race and focused control/serve/builtin races, frontend
tests/build, actual Electron rejection/retry, native Windows ARM64 boundary
and cleanup regressions, pinned lint and repolint pass.
2026-10-17 23:38:14 +08:00
YHH d7707f5183 Merge pull request #13597 from esengine/feat/web-access-settings-ui
feat(desktop): web search and fetch backends are set from Settings, and consent to be billed is its own switch
2026-10-17 08:37:04 -07:00
YHH cb2059a3df Merge pull request #13634 from esengine/fix/brand-badge-outline
fix(studio): draw the STUDIO badge as an outline
2026-10-17 08:29:31 -07:00
YHH 26fcf1134a Merge pull request #13632 from esengine/fix/spxx-file-image
test(desktop): live checks that a local image and the side question are seen with the built-in browser open
2026-10-17 08:24:53 -07:00
esengine efc38208bd fix(perf): restore the web-access harness override's closing brace after the merge 2026-10-17 08:21:37 -07:00
esengine e644290329 fix(studio): draw the STUDIO badge as an outline 2026-10-17 08:03:05 -07:00
esengine 00fcacca11 Merge remote-tracking branch 'origin/studio' into feat/web-access-settings-ui
# Conflicts:
#	.github/workflows/studio.yml
#	desktop/frontend-next/perf/harness.tsx
#	desktop/frontend-next/src/i18n/en.ts
#	desktop/frontend-next/src/styles/peers.css
2026-10-17 08:01:04 -07:00
SivanCola 8b94e2d5da merge: reconcile attachment intake with current Studio composer
Problem: the attachment PR conflicts with Studio's extracted composer helpers
and the newer worktree setup surface.

Root cause: both branches changed the Composer's chip type and helper ownership.

Fix: merge origin/studio, use its shared composerChips owner and worktree setup
flow, and retain cancellable attachment uploads and native path references.
Remove the redundant local chip type instead of duplicating the shared owner.

Verification: TypeScript checking, 155 focused frontend tests, production build,
attachment/CSRF/cloud/control/fileutil Go regressions, 169 Electron unit and
packaging tests (one existing conditional skip), pinned lint and repolint pass.
2026-10-17 22:59:32 +08:00
YHH 4d3b21e333 Merge pull request #13571 from esengine/fix/queue-row-image-reason
fix(inbox): say why a queued line with a picture waits for the turn to end
2026-10-17 07:59:09 -07:00
YHH ac3085a257 Merge pull request #13583 from esengine/feat/peers-settings-ui
feat(desktop): session messages are switched on from Settings, and a held message can be let through for its project
2026-10-17 07:48:05 -07:00
esengine 4f8852dbd2 Merge remote-tracking branch 'origin/studio' into feat/web-access-settings-ui
# Conflicts:
#	.github/workflows/studio.yml
2026-10-17 07:33:06 -07:00
esengine 80dbb55361 test(desktop): the refusal is worded from the kernel's code now that its wording is on studio 2026-10-17 07:32:34 -07:00
esengine 5ffb22c1f5 Merge remote-tracking branch 'origin/studio' into feat/peers-settings-ui
# Conflicts:
#	.github/workflows/studio.yml
#	desktop/frontend-next/src/port/mock_boundary.ts
#	desktop/frontend-next/src/port/port.ts
#	desktop/frontend-next/src/port/sse_boundary.ts
2026-10-17 07:24:27 -07:00
YHH a30c58ff94 Merge pull request #13631 from esengine/fix/price-fallback-explained
feat(studio): say why a price cannot be shown in the pinned currency, and offer the official one
2026-10-17 06:58:33 -07:00
esengine b225a3889f feat(inbox): the session queue holds as many prompts as asked unless the user sets agent.max_queued_inputs 2026-10-17 06:55:59 -07:00
esengine a56f1241e8 test(desktop): live checks that a local image and the side question are seen with the built-in browser open 2026-10-17 06:55:58 -07:00
YHH a4da20d96a Merge pull request #13629 from esengine/fix/feedback-reply-while-received-app
fix(studio): offer a reply on a released report that is still received
2026-10-17 06:48:06 -07:00
YHH 23708e88cb Merge pull request #13438 from chuiwenwei/fix/sessioninbox-read-failure-not-empty-queue
fix(sessioninbox): a queue that cannot be read is not an empty queue
2026-10-17 06:38:16 -07:00
YHH ef40245efc Merge pull request #13628 from esengine/fix/13605-ime-composition
fix(desktop): the agent's browser loads and navigates out of the window, so a composition in progress is not cut off
2026-10-17 06:32:52 -07:00
esengine 879c1c8ae6 feat(studio): say why a price cannot be shown in the pinned currency, and offer the official one 2026-10-17 06:29:27 -07:00
YHH abf120c2ce Merge pull request #13590 from esengine/feat/12357-mcp-signin
feat(mcp): sign-in asks which host the server sends you to before the browser opens
2026-10-17 06:22:13 -07:00
YHH b21ae6ee3c Merge pull request #13579 from esengine/fix/13560-cache-by-spec
fix(plugin): the MCP handshake cache keeps one entry per server name and spec
2026-10-17 06:20:59 -07:00
YHH 21ae289cc9 Merge pull request #13586 from SivanCola/fix/group-worktree-sessions
fix(studio): group worktree chats under their source project / 将隔离会话归入原项目
2026-10-17 06:18:59 -07:00
Will 3e9dcd0e53 test(sessioninbox): hold the transaction lock through filelock, not unix.Flock
Review follow-up: the audit helper used unix.Flock with no build tag, so lint's
typecheck and test (windows-latest) failed with 'undefined: unix.Flock'. Take the
lock through internal/base/filelock's TryAcquire, which has the same unix and
windows implementations the store uses, so the held-lock tests run on every platform.
2026-10-17 21:15:12 +08:00
YHH d46fa91cb5 Merge pull request #13585 from esengine/feat/peers-settings
feat(peers): a real switch for messages between sessions, applied at the next turn
2026-10-17 06:13:40 -07:00
esengine 3d69508aca test(desktop): drop the leftover debugging from the hung-navigation check 2026-10-17 06:09:11 -07:00
esengine ae8397df0a test(desktop): do not wait on a navigation that never answers 2026-10-17 06:07:07 -07:00
esengine 164bc5d258 wip debug3 2026-10-17 06:06:26 -07:00
esengine 1bd238e593 wip debug2 2026-10-17 06:05:44 -07:00
esengine f78b2a86fe fix(studio): offer a reply on a released report that is still received 2026-10-17 06:05:22 -07:00
esengine c3f2befaa3 wip debug 2026-10-17 06:05:05 -07:00
esengine 1ddfa7206a fix(desktop): a page held out of the window for a navigation comes back within a fixed limit 2026-10-17 06:04:23 -07:00
esengine 335ab3ad8b fix(plugin): forget sign-in consent through a root opened on the server's state directory 2026-10-17 06:00:37 -07:00
esengine 0a804c33ad docs(release): note for the input-method fix 2026-10-17 05:59:35 -07:00
esengine f8a6d19980 fix(desktop): the agent's browser loads and navigates out of the window, so the app page keeps the keyboard and its composition 2026-10-17 05:59:35 -07:00
esengine fd822b3623 test(desktop): a live check that an IME composition survives the agent's browser 2026-10-17 05:59:35 -07:00
esengine 1ac226ac6d fix(plugin): list a server's cache entries from the fixed directory, not a pattern holding the name 2026-10-17 05:59:28 -07:00
YHH 771427b639 Merge branch 'studio' into feat/peers-settings 2026-10-17 05:58:36 -07:00
esengine addbb5bf05 fix(model): hash the opened video before sending a byte of it 2026-10-17 05:48:17 -07:00
YHH 06c4340cc0 Merge pull request #13608 from esengine/a5/web-access-settings
feat(serve): read and save the web search and fetch settings over HTTP
2026-10-17 05:47:27 -07:00
esengine dedc3e5af3 fix(model): open a video file once and send exactly what was checked 2026-10-17 05:45:21 -07:00
YHH 55e73ec8fa Merge pull request #13612 from esengine/fix/13610-prefs-nav-selected-hover
fix(studio): keep the selected settings section's look while it is hovered
2026-10-17 05:44:37 -07:00
esengine c472732883 feat(desktop): web access settings read as read-only in a window that was not granted editing 2026-10-17 05:40:29 -07:00
esengine 957c90e3e8 fix(studio): keep the selected settings section's look while it is hovered 2026-10-17 05:33:10 -07:00
YHH b0c6cf2dd8 Merge pull request #13621 from xxoingr/fix/desktop-settings-refusal-codes
fix(desktop): keep the refusal's code when network or wallpaper settings fail
2026-10-17 05:32:13 -07:00
esengine 9a45df0c8b Merge remote-tracking branch 'origin/a5/web-access-settings' into feat/web-access-settings-ui 2026-10-17 05:31:51 -07:00
esengine abcf61d321 fix(serve): web access settings are written only with the edit grant 2026-10-17 05:30:56 -07:00
YHH ea7be33211 Merge pull request #13436 from chuiwenwei/fix/e2ebench-unsplit-runs-not-in-medians
fix(e2ebench): keep runs without a recorded split out of the boundary medians
2026-10-17 05:29:03 -07:00
YHH 610a6a99d9 Merge pull request #13623 from esengine/fix/13604-running-count
fix(desktop): a status read lost to the network is asked again, so a pane nobody is looking at stops reading as running
2026-10-17 05:24:29 -07:00
YHH 08198846ef Merge pull request #13622 from esengine/fix/scratch-spill-race
fix(sessionstore): scratch spill sweep keeps its directory so a concurrent spill is not truncated
2026-10-17 05:22:38 -07:00
YHH 1bf59ce841 Merge pull request #13618 from xxoingr/fix/desktop-port-shadowed-faces
fix(desktop): stop the real port's own copies from hiding its faces
2026-10-17 05:20:57 -07:00
YHH c4825325f6 Merge pull request #13620 from esengine/fix/flaky-oversize-spill-scratch
test(agent): give the scratch-spill test its own temp directory
2026-10-17 05:17:25 -07:00
YHH 7a1a9ef810 Merge pull request #13619 from esengine/fix/win-flake-walk-lock
test: Windows waits sized for a loaded runner
2026-10-17 05:13:59 -07:00
xxoingr b8ddd82efb fix(desktop): keep the refusal's code when network or wallpaper settings fail
saveNetwork, uploadWallpaper and clearWallpaper hand-rolled their fetch
and threw a plain Error, so the code the kernel sent never reached
reason(): an unsupported wallpaper read as the kernel's English fallback
in a Chinese window, a failed clear as a path and a status, and the
existing wording for network.rejected and wallpaper.* went unused. They
now go through post0 and del, whose fail() keeps it.

SaveNetworkSettings also stops prefixing its refusal with a Chinese
sentence: the frontend frames network.rejected itself, and the prefix
would read twice in one language and as a fragment in the other.
2026-10-17 20:09:15 +08:00
esengine c1992ffe7f fix(sessionstore): the scratch spill sweep leaves its directory in place so a concurrent spill is not truncated 2026-10-17 05:08:12 -07:00
esengine 7b726d70fa fix(desktop): a status read lost to the network is asked again, so a pane nobody is looking at stops reading as running 2026-10-17 05:04:26 -07:00
esengine a8bfcd0944 test(agent): give the scratch-spill test its own temp directory 2026-10-17 05:02:48 -07:00
xxoingr 8d267f265a fix(desktop): stop the real port's own copies from hiding its faces
SsePort still declared exportPlugin, dryRunHook, trustMcp, reconnectMcp,
parseMcp and probeProvider after they were split into its faces, and a
method on the outer class hides the face's. So the faces' dryRunHook and
probeProvider, which go through post0 to keep a refusal's code, never
ran: probeProvider threw the raw response text, and onboarding showed a
refusal envelope as its message and could never offer the vendor-console
link it keys on provider.probe.unauthorized. exportPlugin was the other
way round, the face holding the stale copy.

Drop the six copies, move the current exportPlugin into SseExtensions,
and guard the chain so no face redeclares one below it.
2026-10-17 20:02:29 +08:00
YHH 022b82d2d5 Merge pull request #13606 from esengine/a5/acp-paid-persist
fix(acp): offer a saved choice for paid search consent and keep it on a replayed card
2026-10-17 04:57:39 -07:00
Will d8c0c249cb fix(e2ebench): keep a run with no recorded split out of the correct-boundary medians
renderSolveProfiles folded every run that reached a correct checkpoint into
the boundary medians, including runs whose trajectory was never read. With
-checkpoints and no -trajectories those runs carry zero round and
verification tallies, so the report printed "rounds before 0 · rounds after
0 · verifications after 0" as if they had been measured.

Only runs whose split was actually recorded now fold in, and a suite where no
run recorded one says the boundary was never split instead of printing zeros.
The all-measured medians are unchanged.
2026-10-17 19:55:50 +08:00
esengine f40b87d144 test: Windows waits sized for a loaded runner (walk re-stat window, grant-lock helper retry) 2026-10-17 04:55:39 -07:00
YHH 132deb676b Merge pull request #13611 from esengine/a5/title-wait
fix(serve): a stale title result must not overwrite a newer one, and a changed first message is a new request
2026-10-17 04:53:46 -07:00
YHH 75a4e7e2fe Merge pull request #13598 from SivanCola/codex/workspace-flow
Complete workspace preparation and branch switching / 补齐工作目录准备与分支切换流程
2026-10-17 04:47:59 -07:00
YHH a45f8abe4d Merge pull request #13552 from esengine/feat/peer-threads
feat(desktop): an icon on each session this one has written to, saying where the exchange stands
2026-10-17 04:42:48 -07:00
esengine 6c53e4d6a4 refactor: walk the messages backward with slices.Backward 2026-10-17 04:35:17 -07:00
esengine 506ef7e5de style: align the native video line with the card body 2026-10-17 04:35:17 -07:00
esengine d45454a300 feat: show a natively sent video on the card and in the terminal; document it 2026-10-17 04:35:17 -07:00
esengine d0f220096b refactor: keep the video change inside the file, function and comment budgets 2026-10-17 04:35:17 -07:00
esengine d1488d5d0c feat(boot): wire native video through the real build and prove it at the vendor request boundary 2026-10-17 04:35:17 -07:00
esengine a91aaa6c4d feat(config): a user-level ceiling below the documented native video limit 2026-10-17 04:35:17 -07:00
esengine 1502533094 feat(tools): read_video hands an MP4 to a model that watches video natively 2026-10-17 04:35:17 -07:00
esengine e47de51cab feat(model): send a referenced video to endpoints that declare a protocol, and say so when one cannot 2026-10-17 04:35:17 -07:00
esengine 4b297506db feat(provider): a message can reference a video file; it rides only the request before the model answers 2026-10-17 04:35:17 -07:00
esengine 2a51c686bc feat(config): declare which models read video natively and how a file reaches them 2026-10-17 04:35:16 -07:00
YHH 189f7672c7 Merge pull request #13614 from xxoingr/fix/control-pdf-cancel-bounded-taskkill
fix(control): cancel a PDF extraction through proc's bounded tree kill
2026-10-17 04:32:12 -07:00
YHH ffe2885867 Merge pull request #13446 from chuiwenwei/fix/e2ebench-unmeasured-run-not-accounted
fix(e2ebench): account a sidecar-recovered run as a lower bound
2026-10-17 04:27:53 -07:00
esengine f11d657c32 Merge remote-tracking branch 'origin/studio' into feat/peer-threads
# Conflicts:
#	.github/workflows/studio.yml
2026-10-17 04:26:24 -07:00
SivanCola 5284c18764 fix: restore worktree preparation focus and truthful terminal states
Problem: creating a worktree dropped keyboard focus when the composer became
inert, returning to editing did not restore it, and terminal pages still
reported waiting or mapped cleanup failure to a confirmation state.

Root cause: draft restoration attempted focus before React removed inert;
the preparation page had no focus owner, phase fallbacks conflated terminal
states, and details/edit actions shared a configuration identity.

Fix: focus the preparation heading on entry, recover focus when a removed
phase action leaves the body active, preserve surviving detail focus, and
restore the composer only after the stage-clearing commit. Use distinct
view action IDs and explicit terminal labels, and show elapsed waiting only
for active phases without changing existing typography or button recipes.

Verification: failure/cancellation interaction regressions, phase/disclosure
unit tests, real-browser focus and draft-retention checks, English dark-page
labels and shared button hierarchy, frontend typecheck/build/census, and
repository lint/check; full frontend: 634 files and 5,257 tests passed.
No Git or backend behavior changed.
2026-10-17 19:25:03 +08:00
xxoingr 2234b0f8a0 fix(control): cancel a PDF extraction through proc's bounded tree kill
setShellKillTree on Windows ran its own taskkill /F /T inside
exec.Cmd.Cancel with no time limit. #13323 bounded the taskkill in
proc.KillTree because Wait does not return until the cancel hook has,
but this hook never went through KillTree, so a PDF text extraction past
its 8 s deadline could still hang on a taskkill that never answers.

Delegate to proc.SetCancelKillsTree, the helper proc.RunCommand already
uses, so the only taskkill left is the bounded one in proc.
2026-10-17 19:12:32 +08:00
esengine 71cd039be4 Merge remote-tracking branch 'origin/studio' into feat/web-access-settings-ui
# Conflicts:
#	.github/workflows/studio.yml
2026-10-17 04:10:40 -07:00
Will 27c92d2dfb fix(e2ebench): a lower-bound run stays partial across legs and out of the meter ratio
Review follow-up: Partial now accumulates across legs (a killed first leg plus a
complete second leg is still a lower bound), and the meter comparison skips a
Partial run the same way it skips an unaccounted one, since its missing tail is
not a divergence. Rebased onto current studio; the stub tests use requireShellStub
so test (windows-latest) can run.
2026-10-17 19:06:09 +08:00
SivanCola 865399363f fix: preserve MCP shutdown metadata and confine worktree rollback
Problem: hosted atomic coverage intermittently lost the MCP resource tool,
and CodeQL reported an unchecked filesystem probe during worktree cleanup.

Root cause: a queued cache callback read clients after Host.Close cleared
handshake metadata. Cleanup accepted a result path without independently
checking the trusted managed root used by its construction owner.

Fix: snapshot advertised capabilities and instructions before releasing the
deferred spawn; keep only persistence asynchronous. Capture the managed root
once for creation and rollback, validate a local relative descendant before
probing the filesystem, and retain registration and unchanged-head checks.

Verification: deterministic shutdown/cache and wrong-boundary regressions;
plugin atomic coverage and race tests; boot atomic coverage; worktree/serve
race checks; native Windows rollback cases; local CodeQL path query; root
lint, Windows lint and changed-file repository checks.
2026-10-17 19:05:38 +08:00
Will d74e5ad218 fix(e2ebench): account a sidecar-recovered run as a lower bound
A leg whose final record was missing but whose <metrics>.partial snapshot was
read was marked Unaccounted, which gatherSuiteStats skips outright. That
contradicted Partial's meaning — real numbers that stop at the last snapshot
and belong in the totals — so the recovered tokens, cost and lower-bound hint
were all unreachable. Only a leg with neither record is now Unaccounted; a
recovered snapshot stays accounted and sets Partial, matching the semantics
swebench mode already uses.

renderMeterAccounting also treated PromptTokens+CompletionTokens == 0 as an
absent harness measurement, inferring state from a value. A persisted record
saying zero is a valid zero, so the guard is now the explicit Unaccounted flag
alone.

Tests: the copied audit case that asserted unaccounted=1 for a sidecar-recovered
run encoded the wrong semantics and now asserts Partial=true, Unaccounted=false
and that 1200/80 and the cost reach the aggregates; the divergence fixture that
relied on zero tokens sets Unaccounted explicitly, and a new case pins that a
persisted zero is priced on both sides. The record-less case and the healthy-run
control are unchanged in intent.
2026-10-17 19:05:20 +08:00
YHH 6d74605fd0 Merge pull request #13609 from esengine/fix/13605-browser-steals-focus
fix(desktop): the agent's built-in browser no longer takes the keyboard from the page you are typing in
2026-10-17 04:03:12 -07:00
Will fd9792e536 i18n(desktop): translate inbox.foreign_run 2026-10-17 19:00:33 +08:00
esengine df88dde9ba fix(serve): a stale title result must not overwrite a newer one, and a changed first message is a new request 2026-10-17 03:45:53 -07:00
esengine b8fa5ed0f6 docs(release): note for the agent browser keyboard fix 2026-10-17 03:45:51 -07:00
esengine 3f6e122e30 test(desktop): stage the person's click the way the platform delivers it 2026-10-17 03:45:51 -07:00
esengine 1ce9cb84c5 test(desktop): the person's own click into the agent's page keeps the keyboard there 2026-10-17 03:45:51 -07:00
esengine 03c71ce0ee fix(desktop): hand the keyboard back a turn after the platform gives it to the agent's page 2026-10-17 03:45:51 -07:00
esengine 02bdb8b8bc wip debug 2026-10-17 03:45:50 -07:00
esengine 41318d5da9 fix(desktop): the agent's browser no longer takes the keyboard from the app page 2026-10-17 03:45:50 -07:00
esengine 5bea200f3a wip probe 2026-10-17 03:45:50 -07:00
esengine 639a53278c test(desktop): a live check of who owns the keyboard while the agent drives its browser 2026-10-17 03:45:50 -07:00
YHH d53132fdf6 Merge pull request #13603 from esengine/a5/mux-attach-baseline
fix: keep the open pane across a failed pane-list read and resume a first mux attach from where it attached
2026-10-17 03:42:47 -07:00
YHH e5d01a32ce Merge pull request #13561 from par73e/fix/11860-compaction-cache-prefix
fix(compaction): reuse eligible sent request prefixes safely
2026-10-17 03:41:27 -07:00
SivanCola f58bd3866b fix(worktree): normalize Windows rollback paths and explain recovery
Problem: failed or cancelled worktrees remain registered on Windows, and the
cleanup-failed screen leaves users without a concrete recovery procedure.
Root cause: native backslash paths are compared directly with Git porcelain's
slash paths. Cleanup uncertainty deliberately fences replacement creation.
Fix: normalize both registered and expected paths while preserving symlink
resolution and the unchanged-head guard. Cover native rollback and changed
branches, wire the Windows regressions into PR CI, and explain draft retention,
manual reconciliation, runtime closure and retry from the source folder.
Restore the existing design QA content and metadata, append only workspace
acceptance, and remove all eight screenshots from the final PR tree.
Verification: native Windows regression fails before the fix; the fixed worktree
package passes 36 tests with 6 platform skips. macOS worktree suite, focused
serve/worktree race tests, Windows lint, workflow contracts, 5,245 frontend tests,
typecheck/build, census and repository lint pass. Hosted CI still needs approval.
2026-10-17 18:37:23 +08:00
esengine a10b5f6758 Merge remote-tracking branch 'origin/studio' into feat/peers-settings-ui
# Conflicts:
#	.github/workflows/studio.yml
2026-10-17 03:28:54 -07:00
esengine f0a9ce4720 feat(serve): read and save the web search and fetch settings over HTTP 2026-10-17 03:28:46 -07:00
YHH 8e4fe65a87 Merge pull request #13580 from esengine/feat/11577-text-size
feat(desktop): an interface text size, separate from the interface zoom
2026-10-17 03:27:56 -07:00
esengine b5c2f8216d Merge remote-tracking branch 'origin/studio' into feat/peer-threads
# Conflicts:
#	.github/workflows/studio.yml
2026-10-17 03:27:24 -07:00
esengine 8b536210dd fix(acp): offer a saved choice for paid search consent and keep it on a replayed card 2026-10-17 03:25:57 -07:00
SivanCola 595f9cbbef test(boot): await discovery before disabled MCP assertions
Problem: Windows smoke reported an empty live catalog in the disabled-MCP
effect test's fresh always-loaded phase; the fixture expected one read tool.

Root cause: Build returns while cold discovery and always-loaded preconnection
can still be in flight. The fixture immediately issued an explicit connection,
leaving competing handshakes and initial catalog publication in its setup.
The adjacent always-load effect tests already await the boot-owned connection.

Fix: Wait for that connection in cold and always-loaded phases before explicit
registration. Keep cached deferred startup explicit and retain all filtering,
restart, catalog and provider assertions. Report held tools and server catalog
diagnostics if the allowed tool is still missing.

Verification: Related disabled/always-loaded/held-definition effect tests pass
normally and under the race detector. go vet, make lint and make check pass.
Full builtin and boot suites pass as well.
Native Windows CI must confirm the replacement candidate.
2026-10-17 18:24:33 +08:00
YHH a4bc92bcc9 Merge pull request #13549 from esengine/feat/0b-peer-threads
feat(peers): where each hand-off stands: waiting, working, replied or declined
2026-10-17 03:24:16 -07:00
par73e b739448a83 fix(compaction): reuse eligible sent request prefixes safely 2026-10-17 18:22:08 +08:00
SivanCola fdae95f92e fix(studio): fence stale worktree source reads and update palette guard
Problem: an explicit source refresh can repopulate the palette after switching
workspace, port or session, and an older initial read can overwrite a later read.
Root cause: only the initial fetch had a lifetime fence; manual reads published
without a shared generation. The browser guard still awaited a retired hover card.
Fix: put initial and manual reads behind one latest-read generation, invalidate
it on scope changes, and verify the unified palette before and after task delivery.
Verification: four deferred-response regressions fail without the fix and pass
with it; 633 frontend files / 5,245 tests pass, typecheck/build, census invariants
and fixture verification, repository checks, and browser palette interactions.
Upstream workflows remain pending repository-admin approval, not passed CI.
2026-10-17 18:17:44 +08:00
esengine d586ffdb67 fix: keep the open pane across a failed pane-list read and resume a first mux attach from where it attached 2026-10-17 03:15:39 -07:00
esengine 13e6f6fed0 Merge remote-tracking branch 'origin/feat/12356-follow-tool-changes' into feat/mcp-signin-follow-ui 2026-10-17 03:10:59 -07:00
esengine 21c9ecfec8 chore(desktop): leave followsChanges to the kernel PR that sends it 2026-10-17 03:10:58 -07:00
esengine 476c301df9 feat(desktop): sign in to an MCP server that answered 401, and follow a server's tool changes 2026-10-17 03:10:28 -07:00
YHH 42a50deb7e Merge branch 'studio' into feat/0b-peer-threads 2026-10-17 03:09:06 -07:00
SivanCola cc0c4beacf Merge latest Studio base into workspace preparation flow
Problem: the PR overlaps the current Studio font-scaling update.
Root cause: both changes replace the workspace stylesheet, and hub additions
bring its combined source over the repository file-size ceiling.
Fix: retain the new palette with shared, scalable typography tokens; preserve
all upstream behavior and extract hub close/shutdown ownership together.
Verification: 633 frontend files and 5,241 tests; typecheck/build; affected
worktree, serve, control and boot suites and race tests; Desktop Go suite;
make lint/check; fresh production browser palette with no console errors.
2026-10-17 18:05:08 +08:00
SivanCola 2d65cb07b0 feat(studio): complete branch and worktree preparation flow
Problem: workspace configuration disappeared after the first task and creating
an isolated worktree left the composer without visible progress or recovery.

Root cause: workspace selection, Git branch switching and runtime construction
had separate UI lifetimes, with no cancellable preparation/publication boundary.

Fix: keep one searchable workspace palette across conversation states; expose
real preparation phases with operation-ID reconciliation, retry and cancellation.
Serialize cancellation against publication, terminate checkout descendants and
clean unpublished resources before acknowledging cancellation. Preserve task
and attachment drafts, and reuse existing typography throughout status screens.

Verification: 5,231 frontend tests, typecheck and production build; root and
Desktop Go suites; affected backend race tests; lint and repository checks;
real Git browser checks for switch, failure/retry, cancellation and handoff.
Include light/dark and wide/narrow before-and-after screenshots and design QA.
2026-10-17 17:59:47 +08:00
esengine e0577c3381 fix(studio): a finished sub-agent says it is handed back, not only how long it took
The sub-agent list replaced the status with the duration whenever one was recorded. It now shows the status word and the duration, in the ok ink.
2026-10-17 02:59:28 -07:00
esengine 907aba3e90 Merge remote-tracking branch 'origin/studio' into feat/11577-text-size 2026-10-17 02:56:59 -07:00
SivanCola bc47cd69e9 fix: confine worktree registration reads to Git metadata
Problem: CodeQL still reported a path-injection alert at the worktree
registration read, and a registration symlink could read outside its metadata
directory before assigning the checkout to a project.

Root cause: Validating checkout identity and the registration backlink did not
constrain the filesystem operation that reads that backlink. The plain
os.ReadFile call followed symlinks outside the selected Git directory.

Fix: Validate the checkout metadata path relative to the shared repository,
open that directory through os.Root, and read the fixed gitdir entry through a
second root confined to this checkout's metadata. Preserve relative backlinks
and internal symlinks while refusing escaping registrations.

Verification: A real-Git symlink-escape regression fails on the original code
and passes with the fix. Worktree/workspace identity suites, grouped-project
HTTP regressions, focused race tests, Windows test compilation, go vet,
make lint and make check pass. Local CodeQL CLI 2.27.1 with go-queries 1.6.11
reproduces the exact remote alert before the change; afterward that alert is
removed and the same path-injection query introduces no new results.
Builtin and boot integration suites also pass.
2026-10-17 17:46:10 +08:00
esengine b2d1b1aa5a docs(release): name the change the web-access note belongs to 2026-10-17 02:41:56 -07:00
esengine ba51af6781 feat(desktop): web search and fetch backends are set from Settings, and consent to be billed is its own switch 2026-10-17 02:41:13 -07:00
YHH fed3ca2b75 Merge pull request #13592 from xxoingr/test/secrets-sinks-vanished-walk
test(secrets): let the sink registry walk past an in-tree temp folder
2026-10-17 02:39:38 -07:00
esengine 0d13220cc2 refactor(plugin): copy the approval map with maps.Copy 2026-10-17 02:35:53 -07:00
SivanCola 76ee9db5e9 fix: keep checkout labels inside narrow title bars
Problem: Checkout labels could push a conversation title beyond its breadcrumb
at phone widths and high interface scale.

Root cause: The fixed-width directory badge consumed the title's reserved
space even after project and folder controls were hidden by the narrow layout.

Fix: Group project and checkout context in a shrinkable clipped flex child,
allow labels to ellipsize, and preserve the title's existing four-em floor.
Document the shared layout constraint without weakening browser guards.

Verification: The eight failing light/dark width-and-zoom cases now pass in
perf/narrow.mjs. Title spacing and overflow, intrinsic layout and hover guards,
12 related interaction tests, typecheck, production build, vet and repository
checks pass. The Go implementation is unchanged by this layout correction.
2026-10-17 17:33:20 +08:00
SivanCola dbc39493d6 fix: harden grouped checkout ownership and sync studio
Problem: Grouped projects could open the source after an explicit worktree
selection, hide source-alias history, omit inferred launcher exclusions, or
remain unusable when their source folder disappeared. Copied Git metadata
could also claim another checkout's project identity.

Root cause: Display identity was reused as execution and catalog authority.
Project inference duplicated an unbounded marker walk without validating
managed checkout containment or the reverse Git registration.

Fix: Preserve selected directories and all catalog members, choose a surviving
project directory, and use complete member roots for editor discovery and
launch. Reuse shared checkout identity and validate its managed boundary,
local prefix and registration backlink. Merge studio, move root resolution to
its project owner to preserve the file-size budget, and align the title guard
with the intended checkout label while retaining spacing and overflow limits.

Verification: Failing original-head HTTP/path regressions pass after the fix.
Frontend tests (629 files, 5,221 tests), production build, focused race tests,
go vet, make lint and make check pass. Added a 100-root tree benchmark and a
deterministic delayed-header test; repository suites and browser guards cover
adjacent behavior. Hosted checks must validate the replacement PR head.
2026-10-17 17:28:59 +08:00
esengine 29e3125d07 Merge remote-tracking branch 'origin/studio' into feat/web-access-settings-ui 2026-10-17 02:27:36 -07:00
YHH b952a3fbd6 Merge pull request #13575 from esengine/feat/11577-fs-scale
refactor(desktop): every interface font size rides one scale, so a text-size setting can reach it
2026-10-17 02:27:33 -07:00
YHH e35748e0ba Merge pull request #13589 from esengine/feat/granted-folder-file-open
feat(workbench): open a file in a folder the user granted, read-only
2026-10-17 02:17:58 -07:00
xxoingr d0f3e547fa test(secrets): let the sink registry walk past an in-tree temp folder
TestCredentialSinkRegistry walks internal/frontend/serve, where serve's
own tests create and remove extra-* folders through tempAside. Run in
parallel with those tests, the walk can list such a folder and fail when
it is gone a moment later, the same race that failed the storage
state-path guard. Skip a vanished entry with testenv.VanishedMidWalk.
2026-10-17 17:14:33 +08:00
esengine 4db4e8b319 feat(mcp): follow a server's tool changes instead of accepting each one 2026-10-17 02:13:47 -07:00
esengine 842aa364ce Merge remote-tracking branch 'origin/feat/11577-fs-scale' into feat/11577-text-size 2026-10-17 02:12:53 -07:00
esengine 1eecbc113b Merge remote-tracking branch 'origin/studio' into feat/11577-fs-scale
# Conflicts:
#	.github/workflows/studio.yml
#	desktop/frontend-next/src/styles/sidepanel.css
2026-10-17 02:05:28 -07:00
esengine a3337ebbf7 feat(mcp): sign-in asks which host the server sends you to before the browser opens 2026-10-17 01:52:04 -07:00
SivanCola 86c3630d17 fix: group managed worktree chats under their source project
Problem: Studio lists a managed worktree and its source checkout as two
same-named sidebar projects, scattering one project's conversations.

Root cause: Sidebar ownership, session catalogs, and project operations use
checkout paths as project identities even when they share a Git repository.

Fix: Resolve project ownership from managed checkout Git identity while
retaining each transcript and runtime working directory. Merge session
catalogs before paging and keep registration, ordering, capacity, and removal
consistent with the project group. Show the checkout type in the chat header
and mark isolated history without migrating persisted data.

Verification: Serve/worktree/workspace identity and builtin/boot suites,
focused race tests, Desktop Go tests, frontend tests and production build,
go vet, make lint, and make check passed. Browser interactions and an installed
macOS package verified grouping and original/isolated conversation navigation.
2026-10-17 16:51:22 +08:00
esengine b72c6b4506 feat(workbench): open a file in a folder the user granted, read-only
GET /workspace/file takes an optional root, checked against the grant list and re-vetted on read through the decision the picture route uses. The workbench carries the folder, never saves, and says why.
2026-10-17 01:51:12 -07:00
YHH 3d983ca0f3 Merge pull request #13550 from esengine/feat/13526-concurrency
feat(delegation): sub-agent concurrency and parallel writers default to no limit; depth keeps a named stop at 8
2026-10-17 01:49:28 -07:00
esengine b334cd31d9 fix(desktop): session message settings follow the kernel's refusal codes and its available flag 2026-10-17 01:42:53 -07:00
esengine 747c79dcc8 test(peers): compare wrapped errors with errors.Is 2026-10-17 01:27:16 -07:00
YHH f2b47cdfe0 Merge pull request #13567 from esengine/fix/13253-composer-feel
feat(desktop): the composer keeps the 1.x feel — a border sweep on hover and focus, no state word inside the box
2026-10-17 01:26:54 -07:00
YHH 0e740d3299 Merge pull request #13581 from esengine/a5/mux-soak-clean
test(serve): per-pane delivery metrics on the multiplexed stream and a hundred-pane soak
2026-10-17 01:26:32 -07:00
YHH 522b9f61e4 Merge pull request #13577 from esengine/feat/13540-view-image
feat(tools): view_image shows a model the picture in a workspace file, for models that read images
2026-10-17 01:22:56 -07:00
esengine c127511bf2 feat(peers): English wording for the new refusal codes 2026-10-17 01:22:34 -07:00
esengine 5c27e9c6f3 docs(release): shorten the session-messages note 2026-10-17 01:16:32 -07:00
esengine 978914283b feat(desktop): session messages are switched on from Settings, and a held message can be let through for its project 2026-10-17 01:16:32 -07:00
esengine a0a1a860e8 feat(peers): a real switch for messages between sessions, applied at the next turn 2026-10-17 01:16:13 -07:00
esengine 0bd263426b test(serve): per-pane delivery metrics on the multiplexed stream and a hundred-pane soak 2026-10-17 01:08:45 -07:00
esengine 8300f72870 feat(tools): view_image shows a model the picture in a workspace file, for models that read images 2026-10-17 01:08:33 -07:00
YHH b4e9ae74bb Merge pull request #13574 from esengine/a5/relay-harness
test(relay): drive a hosted page against a real Studio through a stand-in relay in CI
2026-10-17 01:05:49 -07:00
YHH 9a67cbaa26 Merge pull request #13558 from par73e/fix/13288-side-panel-layout
fix(desktop): collapse side questions without losing their state
2026-10-17 01:03:21 -07:00
YHH 824e406f40 Merge pull request #13576 from esengine/fix/mcp-same-name-activation
fix(mcp): projects of one repository that declare a server by the same name keep their enables side by side
2026-10-17 00:55:28 -07:00
YHH 8809ed83bb Merge pull request #13548 from esengine/feat/0b-dispatch
feat(peers): a model tool that hands a task to a session in another project, default off
2026-10-17 00:54:34 -07:00
esengine 14db96264c fix(plugin): the MCP handshake cache keeps one entry per server name and spec 2026-10-17 00:48:38 -07:00
YHH ddb3d81907 Merge pull request #13556 from par73e/fix/12750-windows-remote-script
fix(remote): launch detached Windows kernels from a script file
2026-10-17 00:47:42 -07:00
YHH 154c8a8d9e Merge pull request #13551 from esengine/a5/ds-fetch
feat(fetch): DeepSeek Fetch API as a paid fallback for web_fetch, behind the billing-consent card
2026-10-17 00:41:17 -07:00
esengine d0c3dd5212 docs(release): shorten the text-size note 2026-10-17 00:40:54 -07:00
esengine bf02618c64 feat(desktop): an interface text size, separate from the interface zoom 2026-10-17 00:40:29 -07:00
YHH 5c0ea6f90a Merge branch 'studio' into fix/13288-side-panel-layout 2026-10-17 00:36:55 -07:00
YHH 295a981d83 Merge pull request #13568 from esengine/fix/job-stalled-notice-localized
fix(jobs): word the stalled background job warning in the interface language
2026-10-17 00:31:24 -07:00
par73e 121c0cf7ca fix(desktop): collapse side questions without losing their state 2026-10-17 15:31:06 +08:00
esengine f2e3ff8433 ci(relay): build the host from the repository root, where its module is 2026-10-17 00:31:05 -07:00
esengine 0648b575d0 feat(serve): read and save the web search and fetch settings over HTTP 2026-10-17 00:30:03 -07:00
esengine 1a0732b97e fix(mcp): projects of one repository that declare a server by the same name keep their enables side by side 2026-10-17 00:27:54 -07:00
YHH e9388db72b Merge pull request #13547 from esengine/a5/ds-search-paid
feat(search): DeepSeek Search API as a paid fallback behind a billing-consent card
2026-10-17 00:26:06 -07:00
esengine 5d99a796ce test(desktop): the type-scale tests read sizes that ride the text-size scale 2026-10-17 00:21:51 -07:00
esengine af765d6b82 refactor(desktop): every interface font size rides one scale, so a text-size setting can reach it 2026-10-17 00:21:51 -07:00
esengine 4eef35a0b5 ci(relay): pass the base ref through the environment 2026-10-17 00:20:56 -07:00
YHH 720dc69c75 Merge pull request #13570 from esengine/fix/state-root-walk-wait
test(config): the churn in the walk test never reuses a directory name
2026-10-17 00:19:26 -07:00
esengine 81729ee851 test(relay): drive a hosted page against a real Studio through a stand-in relay in CI 2026-10-17 00:19:05 -07:00
esengine 28af7a784f feat(inbox): say why a queued line with a picture is waiting for the turn to end
A queued message that froze an image cannot be admitted as mid-turn guidance. The manifest now records that (images), and the queue row says so instead of 'not delivered yet'.
2026-10-17 00:16:06 -07:00
par73e bb62bb8057 fix(remote): launch detached Windows kernels from a script file 2026-10-17 15:14:22 +08:00
esengine fdb5096f0c Merge a5/ds-search-paid into a5/ds-fetch 2026-10-17 00:11:34 -07:00
esengine e930472cf7 Merge studio into a5/ds-search-paid 2026-10-17 00:08:48 -07:00
esengine a2d77f97f5 test(boot): a red resource-tool run names what the schema cache held 2026-10-17 00:07:14 -07:00
esengine d6302bfae2 test(config): the churn in the walk test never reuses a directory name 2026-10-17 00:04:07 -07:00
YHH 8bf412accd Merge pull request #13564 from esengine/fix/mux-open-timeout
fix(studio): panes read their own event streams when the shared one never opens
2026-10-16 23:50:55 -07:00
YHH 72de55dc11 Merge pull request #13490 from xxoingr/test/storage-guard-vanished-walk
test(storage): let the state-path guard walk past an in-tree temp folder
2026-10-16 23:49:20 -07:00
esengine 0264316a83 fix(jobs): word the stalled background job warning in the interface language
recordStalled emitted a notice with no code, so the frontend showed the kernel's English sentence as is. It now carries job_stalled and a JobNotice payload with the quiet span, like the other job notices; the English text stays the fallback.
2026-10-16 23:48:02 -07:00
esengine a0efc71493 feat(desktop): the composer keeps the 1.x feel — a border sweep on hover and focus, no state word inside the box 2026-10-16 23:41:10 -07:00
YHH 507debd468 Merge branch 'studio' into feat/13526-concurrency 2026-10-16 23:32:00 -07:00
YHH 4e57ca2009 Merge branch 'studio' into feat/0b-dispatch 2026-10-16 23:31:56 -07:00
esengine 99ec610785 test(studio): every way a hub is built has to put a kernel frame into the pane 2026-10-16 23:28:26 -07:00
YHH f19d2fc208 Merge pull request #13545 from esengine/a5/ds-search
feat(search): web_search can answer from the user's own SearXNG, through an ordered backend chain
2026-10-16 23:27:12 -07:00
esengine faa5520f45 docs(release): shorten the open-wait note 2026-10-16 23:26:46 -07:00
esengine 9ab3feecde docs(release): note for the shared-stream open wait 2026-10-16 23:26:28 -07:00
esengine b6e34eb144 fix(studio): panes read their own event streams when the shared one never opens 2026-10-16 23:25:50 -07:00
YHH 801f53bf46 Merge pull request #13555 from esengine/feat/13526-workspaces
feat(desktop): the sidebar remembers as many projects as you open; desktop.max_workspaces is your own limit
2026-10-16 23:22:43 -07:00
YHH 28fc37a14a Merge pull request #13557 from esengine/fix/13539-side-panel-anchor
fix(desktop): the side-conversation panel opens above the composer, inside its pane
2026-10-16 23:22:35 -07:00
YHH a51bb50d07 Merge pull request #13559 from esengine/fix/project-mcp-tests-share-cache
test(boot): run the three tests that share the docs-helper cache entry one after another
2026-10-16 23:19:54 -07:00
YHH 91aa427a7c Merge pull request #13562 from tongsailong/fix/web-remote-mux
fix(web): preserve live remote updates with older Studio kernels
2026-10-16 23:15:16 -07:00
阿稳 b543d8629e fix(web): keep remote event streams compatible with older kernels
Hosted Web enables the multiplexed event stream for every pane, but a
Studio kernel without GET /events/mux answers it with the SPA shell. The
mux client waits for the stream's open frame to name its connection, so
no pane ever subscribed and none fell back to its own stream: the page
kept loading and stopped receiving events until a reload rebuilt it from
a snapshot.

RemoteEventSource is a polled stand-in that cannot carry that protocol,
so a hub reached over the relay now reads each pane's own /events. A
local hub keeps the shared stream.

The event contract is unchanged: attach, resume, cursor, watermark and
the pending-prompt attach all keep the code paths PR #13499 fixed.
2026-10-17 14:02:37 +08:00
esengine 7f5cdad445 test(boot): run the three tests that share the docs-helper cache entry one after another 2026-10-16 22:37:53 -07:00
SivanCola f18883380c fix(attachments): retain rooted storage ownership through upload cleanup
Problem: CodeQL reports uncontrolled paths in streamed upload cleanup and
attachment creation. A replaced storage directory can redirect failed-upload
cleanup to an external same-name file; a linked .reasonix parent can escape too.

Root cause: filename validation and path-based writes/removals do not preserve
directory identity across filesystem changes during I/O.

Fix: use os.Root for workspace-contained directory/file creation, share an owned
file lifecycle between streamed and legacy byte writes, and retain the directory
handle for failure cleanup. Publish the existing untracked marker atomically
through the same rooted directory rather than reopening its pathname.

Verification: full control/serve/fileutil suites, focused race tests, vet,
pinned linter and repolint passed. Native Windows 11 ARM64 regressions passed
with directory junctions and protected cleanup, plus large bounded streaming.
The independent replay test failure also reproduces with the PR's Go changes
removed through a base-source overlay; no replay behavior or CI gate is changed.
2026-10-17 13:08:06 +08:00
YHH 2b3b7c4d0c Merge pull request #13554 from esengine/docs/studio-2.43.0-notes-2
docs(release): fold the remaining fragments into the studio 2.43.0 notes
2026-10-16 21:55:15 -07:00
YHH 2d21bbc582 Merge pull request #13553 from esengine/fix/gitcmd-expired-ctx
fix(gitcmd): a git stopped by the deadline is the context's error, not a refusal of the folder
2026-10-16 21:52:58 -07:00
esengine a49133cc46 fix(desktop): the side button draws its own panel into the composer and takes the focus back 2026-10-16 21:50:26 -07:00
esengine a481f2d0b1 fix(desktop): the side-conversation panel opens above the composer, inside its pane 2026-10-16 21:45:42 -07:00
esengine 63618012a3 feat(desktop): the sidebar remembers as many projects as you open; desktop.max_workspaces is your own limit 2026-10-16 21:39:16 -07:00
esengine 83437904bb docs(release): fold the remaining fragments into the studio 2.43.0 notes
Fold the fragments merged after the first notes commit into 2.43.0.md and remove every consumed fragment, so the release commit leaves release-notes/studio/unreleased empty.
2026-10-16 21:38:42 -07:00
esengine ebe180c771 review: refuse names that cannot be public before a URL goes to DeepSeek; say the query string is sent as written 2026-10-16 21:34:57 -07:00
esengine 9fb2bbb542 review: refuse names that cannot be public before a URL goes to DeepSeek; say the query string is sent as written 2026-10-16 21:33:48 -07:00
esengine ccb2a7ad64 fix(gitcmd): a git stopped by the deadline is the context's error, not a refusal of the folder 2026-10-16 21:32:13 -07:00
esengine 1dffc620bb Merge remote-tracking branch 'origin/studio' into feat/peer-threads
# Conflicts:
#	.github/workflows/studio.yml
#	desktop/frontend-next/perf/harness.tsx
#	desktop/frontend-next/src/main.tsx
2026-10-16 21:28:53 -07:00
esengine 38c0e02514 refactor(desktop): the first-run read leaves App so the file stays inside its budget 2026-10-16 21:28:41 -07:00
esengine db06675c87 feat(delegation): sub-agent concurrency and parallel writers default to no limit; depth keeps a named stop at 8 2026-10-16 21:21:30 -07:00
esengine 955040469a feat(desktop): an icon on each session this one has written to, saying where the exchange stands 2026-10-16 21:20:53 -07:00
YHH 97c9c5b24e Merge pull request #13536 from esengine/fix/13467-gate-stops-readiness
fix(control): a recorded await_user ends the readiness rounds the host would start
2026-10-16 21:16:41 -07:00
YHH d74c6bec95 Merge pull request #13502 from esengine/feat/13478-await-auto
feat(control): continue a task list handed back to the user once the ask timeout passes unanswered
2026-10-16 21:14:47 -07:00
YHH 639b852ed1 Merge pull request #13537 from esengine/feat/mux-page
feat(studio): carry every local pane on the hub's one multiplexed event stream
2026-10-16 21:14:28 -07:00
YHH 55b61653ef Merge pull request #13546 from esengine/feat/card-grammar-all
feat(desktop): card grammar slices 3–5 and the one-row brand lockup
2026-10-16 21:14:27 -07:00
YHH e997e19599 Merge pull request #13535 from esengine/fix/lease-wait-flake
test(workspacelease): wait for the first wait report instead of a fixed deadline
2026-10-16 21:10:46 -07:00
esengine 427d1a24b4 feat(peers): where each hand-off stands: waiting, working, replied or declined 2026-10-16 21:06:21 -07:00
esengine 16d82778ff feat(fetch): DeepSeek Fetch API as a paid fallback for web_fetch, behind the billing-consent card 2026-10-16 21:05:11 -07:00
esengine 88260228ad Merge origin/studio into feat/mux-page
Resolved by taking the page commit as applied on top of studio, where the
stream budget it builds on already landed under a different commit.
2026-10-16 20:57:50 -07:00
YHH c13c7fde60 Merge branch 'studio' into fix/lease-wait-flake 2026-10-16 20:56:10 -07:00
YHH 436726ef56 Merge branch 'studio' into fix/13467-gate-stops-readiness 2026-10-16 20:56:07 -07:00
YHH 52a10d8793 Merge branch 'studio' into feat/13478-await-auto 2026-10-16 20:56:03 -07:00
YHH 880a9e0b94 Merge branch 'studio' into feat/card-grammar-all 2026-10-16 20:55:58 -07:00
esengine a317b0998e feat(peers): a model tool that hands a task to a session in another project, default off 2026-10-16 20:54:32 -07:00
YHH 58e94afbf3 Merge pull request #13542 from esengine/test/replay-wait-for-log
test(serve): wait for the ask to reach the log before asserting what a resume replays
2026-10-16 20:53:33 -07:00
esengine f61017127a review: snippet cut test, cap rationale, privacy note for always allow 2026-10-16 20:49:30 -07:00
esengine 49c08b17c7 feat(search): DeepSeek Search API as a paid fallback behind a billing-consent card 2026-10-16 20:43:48 -07:00
SivanCola fc65229584 test(studio): validate Windows intake and document streaming bounds
Problem: Review requested a user-visible release fragment and clearer memory
bounds. The live regression also assumed new-session activation must create
a second runtime, which is false when an existing blank pane is reused.

Root cause: A workspace may already own a reusable blank pane, particularly
when native Windows roots have matching canonical spelling. The old setup
waited for a POST and a new pane identity instead of editor readiness.

Fix: Wait for boot completion at a stable viewport, allow a blank-pane reuse,
and preserve assertions for the captured workspace, one shared intake route,
saved upload size and four ready attachments. Allow an explicit test host
binary. Add bilingual release notes and state that memory, not total disk
consumption, is bounded by streaming.

Verification: Windows 11 ARM64 control, stream, HTTP upload/image-limit and
exact-file read-tool regressions passed. Electron 44.2.0 exercised picker,
paste and drop of a 111 MiB file and a 65 MiB streamed File with four ready
attachments. Release-note tests, repository checks and script syntax passed.
2026-10-17 11:42:29 +08:00
YHH dbb6ded60a Merge pull request #13483 from esengine/fix/history-search-test-race
test(history): close the shared catalog before resetting it in the session-search test
2026-10-16 20:42:07 -07:00
esengine 5304594bb0 Merge remote-tracking branch 'origin/studio' into feat/card-grammar-all 2026-10-16 20:39:11 -07:00
YHH cd81a8aea4 Merge branch 'studio' into fix/13467-gate-stops-readiness 2026-10-16 20:38:34 -07:00
YHH b6b6001e06 Merge branch 'studio' into feat/13478-await-auto 2026-10-16 20:38:29 -07:00
YHH 4e4597c630 Merge branch 'studio' into test/replay-wait-for-log 2026-10-16 20:38:25 -07:00
esengine 31370255fb test(studio): the card shell guard counts four cards now that a remembered fact is a row 2026-10-16 20:38:20 -07:00
YHH 7d870bdef3 Merge pull request #13533 from esengine/a5/busy-mux
feat(serve): carry every local pane on one multiplexed event stream
2026-10-16 20:36:39 -07:00
YHH 370b5a252c Merge pull request #13534 from esengine/fix/mcp-add-persists-schema
fix(plugin): a server connected by hand leaves its handshake in the cache
2026-10-16 20:34:00 -07:00
esengine 4c6ea3b000 docs(release): one fragment for the card grammar slices and the brand lockup 2026-10-16 20:31:08 -07:00
esengine c23b749298 Merge remote-tracking branch 'origin/feat/brand-lockup-badge' into feat/card-grammar-all
# Conflicts:
#	.github/workflows/studio.yml
2026-10-16 20:30:58 -07:00
esengine c37f2656d8 Merge remote-tracking branch 'origin/feat/card-grammar-slice5' into feat/card-grammar-all
# Conflicts:
#	.github/workflows/studio.yml
#	desktop/frontend-next/src/styles/LAYOUT.md
2026-10-16 20:30:58 -07:00
esengine 1cb9b4300c Merge remote-tracking branch 'origin/feat/card-grammar-slice4' into feat/card-grammar-all
# Conflicts:
#	.github/workflows/studio.yml
#	desktop/frontend-next/src/styles/LAYOUT.md
2026-10-16 20:30:57 -07:00
esengine 1f424fbff0 Merge remote-tracking branch 'origin/feat/card-grammar-slice3' into feat/card-grammar-all
# Conflicts:
#	.github/workflows/studio.yml
2026-10-16 20:30:57 -07:00
SivanCola 6f1e59b750 test(studio): certify upload cancellation signal listeners
Problem: The full frontend suite rejects the upload abort listener as an
unresolved interaction registration.

Root cause: The syntax-based census does not infer a Request.signal member's
AbortSignal type and cannot distinguish it from a possible user-input target.

Fix: Keep an explicitly typed upload signal and teach the shared receiver
certifier to recognize scoped AbortSignal bindings. Preserve unresolved
receivers and refuse borrowing an outer signal type across shadowing.
Do not add a file exception or raise any census or lint ceiling.

Verification: Abort receiver, actions census, cloud upload and attachment
regressions, frontend typecheck, production build and repository checks pass.
2026-10-17 11:15:10 +08:00
esengine 3ae1b7baef fix(i18n): English wording for the search backend notices 2026-10-16 20:11:48 -07:00
esengine 0d68e0a986 feat(search): web_search can answer from the user's own SearXNG, through an ordered backend chain 2026-10-16 20:11:48 -07:00
YHH 19640573dc Merge pull request #13516 from esengine/feat/13458-task-description
feat(tools): bash takes an optional description that names a background job
2026-10-16 20:09:12 -07:00
SivanCola 7f53665cd0 fix(studio): reference native files and stream attachment uploads
Problem: Large ordinary file attachments cause avoidable whole-file
allocations and exceed the legacy JSON attachment transport limits.

Root cause: Picker and paste intake Base64-encode files even when the shell
already knows their native paths. Pathless uploads also pass through JSON.

Fix: Share native path references across picker, paste and drop. Stream raw
ordinary-file bodies to bounded disk writes and clean up failed or cancelled
uploads. Grant external-file access only to the selected file. Preserve
image, legacy JSON and cloud relay limits; bound cloud intake before
materializing an oversized body. Document the transport and live regression.

Verification: Owning Go package suites, focused race tests, go vet, lint,
repository checks, frontend typecheck, production build and attachment
regressions passed on the original tested baseline. Real macOS Electron
exercised native picker, paste and drop with a 111 MiB file and an exact
65 MiB pathless upload. Windows native runtime verification remains open.
2026-10-17 11:08:58 +08:00
esengine 79e0e9e73d fix(plugin): a server connected by hand leaves its handshake in the cache 2026-10-16 20:08:11 -07:00
esengine ec014ebd0a Merge remote-tracking branch 'origin/studio' into feat/brand-lockup-badge
# Conflicts:
#	.github/workflows/studio.yml
2026-10-16 20:05:02 -07:00
esengine 2b103885e8 Merge remote-tracking branch 'origin/studio' into feat/card-grammar-slice5
# Conflicts:
#	.github/workflows/studio.yml
#	desktop/frontend-next/src/styles/LAYOUT.md
2026-10-16 20:04:26 -07:00
esengine b46a1e11ec Merge remote-tracking branch 'origin/studio' into feat/card-grammar-slice4
# Conflicts:
#	.github/workflows/studio.yml
#	desktop/frontend-next/perf/harness.tsx
#	desktop/frontend-next/src/main.tsx
#	desktop/frontend-next/src/styles/LAYOUT.md
2026-10-16 20:03:37 -07:00
esengine 3306732c50 Merge origin/studio into a5/busy-mux 2026-10-16 20:03:09 -07:00
esengine ca2da5c53a Merge remote-tracking branch 'origin/studio' into feat/card-grammar-slice3
# Conflicts:
#	.github/workflows/studio.yml
#	desktop/frontend-next/src/styles/LAYOUT.md
2026-10-16 20:02:28 -07:00
esengine b5d3c529f2 test(serve): wait for the ask to reach the log before asserting what a resume replays 2026-10-16 19:56:37 -07:00
YHH 0cac9ae4b9 Merge pull request #13518 from esengine/perf/13176-cold-list
perf(sessionstore): read the sidecars of a cold session folder on a bounded pool
2026-10-16 19:47:43 -07:00
esengine e5d3291c03 fix(jobs): bound the model-written description for display 2026-10-16 19:38:28 -07:00
YHH 03f9c22072 Merge pull request #13538 from esengine/fix/tidy-perf-guards
test(studio): the receipt card guard turns the card on, and the reask probe draft goes
2026-10-16 19:36:25 -07:00
esengine dbfaf19aae Merge remote-tracking branch 'origin/studio' into feat/13458-task-description
# Conflicts:
#	.github/workflows/studio.yml
2026-10-16 19:28:52 -07:00
esengine f375354301 chore(studio): drop the committed perf build output and ignore every dist-perf directory 2026-10-16 19:28:47 -07:00
YHH 6fbeb4ce21 Merge pull request #13499 from tongsailong/fix/v2.42.0-dev
fix(web): hand pending prompts to late clients and stop false stale reports
2026-10-16 19:22:57 -07:00
esengine dfb0ff4bc4 test(studio): the receipt card guard turns the card on, and the reask probe draft goes 2026-10-16 19:16:19 -07:00
esengine 1f5aa55677 fix(control): a recorded await_user ends the readiness rounds the host would start 2026-10-16 19:14:39 -07:00
esengine 21200e3312 test(workspacelease): wait for the first wait report instead of a fixed deadline 2026-10-16 19:07:58 -07:00
YHH 3dcc52b6d2 Merge pull request #13529 from esengine/feat/13526-side-unlimited
feat(side): no cap on open side conversations; agent.max_side_sessions is the user's own limit
2026-10-16 19:07:37 -07:00
YHH 872ed93d49 Merge branch 'studio' into perf/13176-cold-list 2026-10-16 19:07:05 -07:00
YHH 574723954c Merge branch 'studio' into fix/history-search-test-race 2026-10-16 19:07:01 -07:00
YHH f5e9b01e91 Merge pull request #13496 from esengine/feat/card-grammar-slice2
feat(desktop): draw host notices as a one-line bar, severity in the glyph ink only
2026-10-16 19:05:43 -07:00
esengine 477ce369bb feat(studio): carry every local pane on the hub's one multiplexed event stream 2026-10-16 19:05:41 -07:00
esengine f46ac4f856 feat(control): continue a task list handed back to the user once the ask timeout passes unanswered 2026-10-16 19:05:39 -07:00
esengine 0d64b3ec14 fix(desktop): only a few panes hold a live event stream, the rest resume from their cursor 2026-10-16 19:02:00 -07:00
YHH 4aae38f890 Merge pull request #13522 from esengine/fix/peers-body-read
fix(peers): reduce a hand-written message body where the spool is read
2026-10-16 18:57:36 -07:00
YHH 1778e4cc43 Merge pull request #13520 from esengine/fix/peers-read-side-names
fix(peers): reduce the names in another session's record where they are read
2026-10-16 18:54:12 -07:00
esengine 6a2e9af854 test(serve): a retired mux subscription leaves no goroutine behind 2026-10-16 18:53:17 -07:00
esengine 5e5996d059 Merge remote-tracking branch 'origin/studio' into feat/brand-lockup-badge 2026-10-16 18:49:52 -07:00
esengine f1a5b77c80 Merge remote-tracking branch 'origin/studio' into feat/card-grammar-slice5
# Conflicts:
#	.github/workflows/studio.yml
#	desktop/frontend-next/src/styles/LAYOUT.md
2026-10-16 18:49:20 -07:00
YHH 6397a75614 Merge pull request #13531 from esengine/fix/13491-stream-limit
fix(desktop): only a few panes hold a live event stream, the rest resume from their cursor
2026-10-16 18:49:09 -07:00
esengine 7ad1771a91 feat(desktop): say what a shipped report is on the feedback level card
The card showed "已落地 N 条" without saying what counts. One muted line
under the progress bar now states it.
2026-10-16 18:48:35 -07:00
esengine 07705bb0d1 Merge studio into feat/card-grammar-slice4
# Conflicts:
#	.github/workflows/studio.yml
#	desktop/frontend-next/src/styles/LAYOUT.md
#	desktop/frontend-next/src/ui/cards/RememberCard.tsx
2026-10-16 18:46:19 -07:00
esengine 618807518b Merge studio into feat/card-grammar-slice3
# Conflicts:
#	.github/workflows/studio.yml
#	desktop/frontend-next/src/styles/LAYOUT.md
2026-10-16 18:45:16 -07:00
esengine 95936b5167 feat(serve): carry every local pane on one multiplexed event stream
A window that gives each pane its own EventSource spends one HTTP/1.1 connection per pane, and a browser origin holds six, so the sixth stream starves every stop, submit and read behind it. GET /events/mux is one stream for all panes of a window; PUT/DELETE /events/mux/{conn}/panes/{id} subscribe a pane with its own cursor. /events and /events/replay are unchanged.
2026-10-16 18:44:09 -07:00
esengine 84199bf82d Merge studio into feat/card-grammar-slice2
# Conflicts:
#	.github/workflows/studio.yml
#	desktop/frontend-next/src/styles/LAYOUT.md
#	desktop/frontend-next/src/ui/cards/NoticeCard.tsx
2026-10-16 18:42:12 -07:00
YHH d40dc23eab Merge pull request #13504 from esengine/feat/13478-egress-open
feat(sandbox): a user setting that stops the egress question for hosts outside allowed_domains
2026-10-16 18:35:20 -07:00
esengine 450243769c fix(desktop): only a few panes hold a live event stream, the rest resume from their cursor 2026-10-16 18:34:32 -07:00
esengine 63a4755b12 feat(side): no cap on open side conversations; agent.max_side_sessions is the user's own limit 2026-10-16 18:27:07 -07:00
esengine b13f478809 feat(sandbox): a user setting that stops the egress question for hosts outside allowed_domains 2026-10-16 18:25:27 -07:00
YHH bcf0f3f5cb Merge pull request #13515 from esengine/perf/13176-tree-versions
perf(serve): the sidebar tree no longer resolves or opens every session on each refresh
2026-10-16 18:24:27 -07:00
YHH 4296a8365b Merge pull request #13524 from esengine/fix/13472-chevron-risk-colour
fix(desktop): colour the permission picker's chevron and icon like its label
2026-10-16 18:11:01 -07:00
YHH f8747c5abd Merge pull request #13521 from esengine/fix/13345-storage-shared-root
fix(storage): do not credit a root with the entries a neighbour declares
2026-10-16 18:04:56 -07:00
esengine bc89385120 fix(desktop): colour the permission picker's chevron and icon like its label
In the risk state the chevron kept its grey and, in the light theme, the
warning icon used a lighter orange than the label.
2026-10-16 17:56:56 -07:00
esengine 20eb088e61 fix(peers): reduce a hand-written message body where the spool is read 2026-10-16 17:53:33 -07:00
YHH d596bf63c2 Merge pull request #13517 from esengine/fix/13513-idle-cost-placeholder
fix(desktop): show no session cost before anything has been priced
2026-10-16 17:52:36 -07:00
阿稳 6560ae0c62 fix(web): keep the tree stream's attach asking past the watermark
RemoteEventSource now leaves the attach cursor empty so the kernel hands a late client the pending prompt. The tree stream shares that class but reads the tree rather than history, so an empty attach for it re-read the tree on every poll and failed tree_events.test.ts. The attach query is now chosen once from the path: the main stream attaches with no cursor, the tree attaches past the watermark as it always did.

Verified with the whole frontend suite: typecheck, 5139 tests, census, census:verify, build, and no fixtures in the bundle.
2026-10-17 08:50:48 +08:00
esengine 42dbb39367 fix(storage): do not credit a root with the entries a neighbour declares
Home declares no entries, so the survey treated it as sole occupant of its
directory and counted the sessions and archive that state declares in the
same folder (one folder on a default Windows install).
2026-10-16 17:50:24 -07:00
esengine e122025906 feat(desktop): the sidebar brand is one row, wordmark and a bordered badge 2026-10-16 17:49:07 -07:00
esengine 052a7424b4 fix(peers): reduce the names in another session's record where they are read 2026-10-16 17:48:05 -07:00
YHH 6767b6baff Merge pull request #13514 from esengine/docs/studio-2.43.0-notes
docs(release): studio 2.43.0 notes
2026-10-16 17:43:42 -07:00
esengine bbe3163ad8 perf(sessionstore): read the sidecars of a cold session folder on a bounded pool 2026-10-16 17:43:16 -07:00
esengine 65e973a63d fix(desktop): show no session cost before anything has been priced
The cost chip drew a literal "¥" default, so a fresh session read
"本轮 ¥0.00" in a currency nothing had priced, whatever the display
currency was. Leave it out until the first priced round or restored total.
2026-10-16 17:39:34 -07:00
esengine c412db874b docs(release): studio 2.43.0 notes
Gather the 67 pending fragments into release-notes/studio/2.43.0.md,
Chinese then English, and remove them.
2026-10-16 17:34:43 -07:00
esengine 9c29eace57 perf(serve): the sidebar tree no longer resolves or opens every session on each refresh 2026-10-16 17:32:09 -07:00
esengine 042c7fbe9f feat(desktop): name a background job for a person, with its full text on hover and focus 2026-10-16 17:31:39 -07:00
esengine a95d08ddb6 test(boot): regenerate the tool schema baseline for the bash description 2026-10-16 17:31:39 -07:00
esengine b73ac5f285 feat(tools): bash takes an optional description that names a background job 2026-10-16 17:31:39 -07:00
YHH 138167ec7d Merge pull request #13509 from esengine/fix/13472-policy-chevron
fix(desktop): draw the permission picker's chevron with the shared icon
2026-10-16 17:31:19 -07:00
YHH 7b7b98faec Merge pull request #13508 from esengine/feat/13478-goal-retry
feat(goal): retry a Goal paused on its completion check, within a user-set allowance
2026-10-16 17:30:08 -07:00
YHH 9cb5fbace7 Merge pull request #13506 from esengine/feat/remember-choice
feat(desktop): one remember-choice footer for approvals, with where each choice is kept
2026-10-16 17:28:43 -07:00
YHH 87d1aa19b4 Merge pull request #13507 from esengine/feat/13478-plan-approval-switch
feat(studio): a Plan approval switch in Settings
2026-10-16 17:27:51 -07:00
YHH e958985ec1 Merge pull request #13477 from esengine/fix/13023-sidebar-dup-search
fix(desktop): drop the duplicate magnifier beside the session filters
2026-10-16 17:24:38 -07:00
阿稳 e5f440d2c3 fix(web): read the attach watermark inside the prompt-snapshot lock
The polled attach took its pending-prompt snapshot under promptEmitMu but read the broadcaster watermark after that lock was released. A prompt raised in between was numbered at or below the watermark the client then adopted as its cursor, so the next resume skipped it — the original bug in a narrow window.

The watermark is now read in the same critical section as the snapshot, and before it: a prompt raised after that point is numbered above the cursor the client adopts, so the worst case is a prompt named twice rather than one lost. writeEventsReplay reads the watermark once and passes it in, instead of the writer reading it after the lock.

The shell's no-store comment is trimmed to the comment budget so the lint gate passes.
2026-10-17 08:23:07 +08:00
esengine 4c57f2293c fix(desktop): draw the permission picker's chevron with the shared icon
The picker used a 10px text glyph while every sibling picker uses the
StudioIcon down svg, so it rendered smaller and below the centre line.
2026-10-16 17:19:39 -07:00
YHH c982081998 Merge pull request #13503 from esengine/feat/13385-resource-templates
feat(mcp): let the resource tool read addresses a server's declared templates build
2026-10-16 17:14:26 -07:00
YHH 7260e9b303 Merge pull request #13505 from esengine/fix/13493-slash-hints-locale
fix(control): localize slash command argument hints
2026-10-16 17:12:02 -07:00
esengine 22cacc9001 docs(release): shorten the note 2026-10-16 17:09:22 -07:00
YHH 97bbdea635 Merge pull request #13489 from esengine/test/syntax-wrapped-differential
test(shellsafe): differential test for read-only commands wrapped in shell syntax and quoting
2026-10-16 17:06:55 -07:00
esengine 227ca9aa00 feat(goal): retry a Goal paused on its completion check, within a user-set allowance 2026-10-16 17:03:54 -07:00
esengine bc9b0a7429 feat(studio): a Plan approval switch in Settings 2026-10-16 16:49:41 -07:00
esengine 1a1f7c7834 feat(mcp): resource templates, plus a pinned test for the server+uri subject 2026-10-16 16:48:39 -07:00
esengine 1c36b1ab7d fix(control): localize slash command argument hints
The argument completion of /goal, /memory, /mcp, /skills, /plugins, /theme,
/currency, /reasoning-language, /ask-timeout and /feedback returned English
literals; read them from the i18n catalog like /language and /effort do.
2026-10-16 16:47:02 -07:00
esengine c2b66a05fa feat(desktop): one remember-choice footer for approvals, with where each choice is kept 2026-10-16 16:44:03 -07:00
YHH 656ebacea5 Merge pull request #13501 from esengine/test/view-keys-palette-timeout
test(desktop): give the palette test the same ten seconds the other lazy-chunk tests get
2026-10-16 16:36:15 -07:00
esengine febabc561a feat(mcp): the resource tool also admits addresses a declared resource template builds 2026-10-16 16:29:11 -07:00
esengine 04b43991a6 docs(release): do not imply the name filter is always visible 2026-10-16 16:27:43 -07:00
YHH 18a6ee58db Merge pull request #13495 from esengine/feat/card-grammar-slice1
feat(desktop): draw a brought-to-the-end result as the same Line as the Execution group
2026-10-16 16:25:24 -07:00
esengine 74ceafaf16 test(desktop): give the palette test the same ten seconds the other lazy-chunk tests get 2026-10-16 16:23:26 -07:00
YHH e0cd5e5d97 Merge pull request #13457 from esengine/feat/remove-timeline-line
feat(desktop): remove the timeline rail from the conversation
2026-10-16 16:23:24 -07:00
esengine c4009946fd test(desktop): drop the guard for the magnifier that is gone 2026-10-16 16:22:26 -07:00
YHH 739d22e0fe Merge pull request #13484 from esengine/fix/network-probe-codes
fix(i18n): word the network test steps from codes so the English window stays English
2026-10-16 16:19:37 -07:00
YHH f7bc5e1e16 Merge pull request #13487 from esengine/feat/13478-egress-always
feat(sandbox): offer always-allow on the egress card, writing the host to the user's allowed_domains
2026-10-16 16:19:02 -07:00
YHH 8dd7b6dee3 Merge pull request #13485 from esengine/feat/13385-mcp-resource-tool
feat(mcp): a read_mcp_resource tool for resources a server lists, off by default and user-file only
2026-10-16 16:18:05 -07:00
YHH 0d7a0aedc7 Merge pull request #13479 from esengine/feat/13478-plan-auto-approve
feat(plan): let the user's own config start a submitted plan without the approval click
2026-10-16 16:16:39 -07:00
YHH 9350f7f125 Merge pull request #13482 from esengine/fix/differential-vanishing-file
test(shellsafe): tolerate git's background maintenance in the read-only differential snapshot
2026-10-16 16:15:34 -07:00
阿稳 24ab1a0a4f Merge remote-tracking branch 'origin/studio' into fix/v2.42.0-dev 2026-10-17 07:04:54 +08:00
esengine dd1d63b3ce feat(desktop): an opened step's detail is a bordered panel, not a left rule 2026-10-16 15:54:06 -07:00
esengine 0b1c62be41 feat(desktop): a low-risk allowed guardian review and a remembered fact are rows 2026-10-16 15:29:57 -07:00
esengine ec08aa36b1 feat(desktop): one shell for the cards the reader reads or acts on 2026-10-16 14:44:21 -07:00
esengine 10472be8d4 feat(desktop): draw host notices as a one-line bar, severity in the glyph ink only 2026-10-16 13:58:00 -07:00
esengine 9400099593 feat(desktop): draw a brought-to-the-end result as the same Line as the Execution group 2026-10-16 13:26:25 -07:00
YHH 99e0c42d37 Merge pull request #13494 from esengine/fix/a11y-focus-rings
fix(a11y): keep Tab inside modal dialogs and show focus on the session delete button
2026-10-16 12:47:51 -07:00
esengine 6924986399 fix(a11y): keep Tab inside modal dialogs and show focus on the session delete button 2026-10-16 12:20:10 -07:00
YHH 4244fead38 Merge pull request #13492 from esengine/perf/pane-rerender
perf(desktop): keep hidden panes from re-rendering on a tab switch
2026-10-16 11:53:20 -07:00
esengine 288416be16 perf(desktop): keep hidden panes from re-rendering on a tab switch 2026-10-16 11:41:32 -07:00
YHH df9ac7c6bc Merge pull request #13488 from xxoingr/test/worktree-branch-checkout-crlf
test(worktree): compare a branch checkout's content, not its line ending
2026-10-16 11:24:14 -07:00
YHH 17b3541ff1 Merge pull request #13486 from xxoingr/test/plugin-legacy-cache-audit-windows
test(plugin): deny cache-dir writes through an ACL on Windows
2026-10-16 11:23:02 -07:00
xxoingr 144d2ff802 test(storage): let the state-path guard walk past an in-tree temp folder
TestEveryTopLevelStatePathIsAnOwnedEntry walks every package under
internal/ and failed the moment WalkDir could not open an entry. The
serve, control and config tests take their extra folders from their own
package directory (tempAside), so when go test runs packages in
parallel the guard can list one of those folders and find it gone a
moment later:

  open .../internal/frontend/serve/extra-3867033970: no such file or directory

The config package's state-root walk already tolerates this through
vanishedMidWalk, including the sharing-violation and access-denied
errors Windows gives for a directory being deleted. Move that helper to
testenv as VanishedMidWalk, unchanged, and use it from both walks.
2026-10-17 02:14:32 +08:00
esengine 48f0e7c207 test(shellsafe): differential test for read-only commands wrapped in shell syntax and quoting 2026-10-16 11:12:09 -07:00
xxoingr 2fd609387e test(worktree): compare a branch checkout's content, not its line ending
TestCreateFromBranchKeepsSourceAndSeparatesTasks required the checked
out README to be exactly "base\n". Git for Windows ships core.autocrlf
true in its system config, so the worktree holds "base\r\n" and the
test failed on Windows CI. Trim it as the sibling dirty-source test in
worktree_test.go already does; the check still tells the selected
base apart from the current head and the dirty source.
2026-10-17 02:01:30 +08:00
esengine 0f5fc69554 feat(sandbox): offer always-allow on the egress card, writing the host to the user's allowed_domains 2026-10-16 11:00:29 -07:00
xxoingr b4f819872f test(plugin): deny cache-dir writes through an ACL on Windows
TestAuditLegacyCacheUpgradeReportsOnlySuccess blocks the legacy-key
upgrade write by chmod-ing the cache dir to 0555. On Windows that only
sets the read-only attribute, which does not stop new files in a
directory, so the upgrade reached disk and the test failed at its own
fixture check on every Windows CI run since it landed.

Deny write to Everyone with icacls there instead, the mechanism
internal/contract/config's makeDirReadOnly already uses.
2026-10-17 01:56:49 +08:00
YHH 01d1e7cfb1 Merge pull request #13432 from imcreazy/fix/host-tool-suspension
fix(boot): keep the host tool groups out of the schema when no helper can run them
2026-10-16 10:45:46 -07:00
esengine da480c3181 feat(mcp): a read_mcp_resource tool for resources a server lists, off by default and user-file only 2026-10-16 10:39:19 -07:00
esengine 7cf009fbf0 fix(i18n): word the network test steps from codes so the English window stays English 2026-10-16 10:28:54 -07:00
YHH 1c4e89b0aa Merge pull request #13459 from SivanCola/codex/fix-studio-reference-enter
fix(studio): confirm @ references before sending / 修复目录引用回车误发送
2026-10-16 10:21:00 -07:00
YHH 566f48044c Merge pull request #13481 from esengine/test/debt-4
test(evidence): cover child evidence merge, background leases and the unreviewed baseline
2026-10-16 10:20:31 -07:00
esengine e53d5e348f test(history): close the shared catalog before resetting it in the session-search test 2026-10-16 10:19:14 -07:00
esengine 022dd7faee test(shellsafe): tolerate git's background maintenance in the read-only differential snapshot 2026-10-16 10:15:19 -07:00
YHH ee10c06d63 Merge pull request #13480 from esengine/fix/narrow-model-list-header
fix(desktop): keep the model list heading on one line at phone widths
2026-10-16 10:13:29 -07:00
esengine cab7fb6afb test(evidence): cover child evidence merge, background leases and the unreviewed baseline 2026-10-16 10:08:06 -07:00
esengine 3ff71f0882 fix(desktop): keep the model list heading on one line at phone widths 2026-10-16 09:58:51 -07:00
SivanCola 46a139b533 fix(studio): publish completion selection with candidates
Problem: Studio CI caught ArrowDown selection returning to the default row
before Enter could confirm the selected reference.

Root cause: candidates rendered before a passive effect reset selection and
keyboard ownership. That later update could overwrite navigation after the
rows had already committed.

Fix: store the completion answer, active row, and keyboard ownership in one
state value. Publish each answer with its default selection and remove the
deferred reset. Guard navigation against an answer that has become empty.

Verification: four deterministic reference/slash publication and early
navigation regressions fail on the old hook. The PR merge snapshot with this
patch passes 616 frontend files / 5,132 tests; 92 completion/composer cases
pass with --repeats 10. Typecheck, build, census and census verification,
go vet, make lint, make check, and builtin/boot tests pass. Chromium confirms
ArrowDown plus Enter accepts the selected reference and slash command.
2026-10-17 00:56:20 +08:00
esengine f3079d585d fix(i18n): English wording for the plan-approval refusals 2026-10-16 09:48:56 -07:00
esengine 82d939bf94 feat(plan): let the user's own config start a submitted plan without the approval click 2026-10-16 09:46:29 -07:00
YHH ac59488c0c Merge pull request #13474 from esengine/test/debt-3
test(evidence): cover completion adjudication and path evidence
2026-10-16 09:33:23 -07:00
esengine 6773367d05 fix(desktop): drop the duplicate magnifier beside the session filters 2026-10-16 09:30:37 -07:00
YHH 6f5e7a1a6d Merge pull request #13473 from esengine/fix/upgrade-page-name-collisions
fix(upgrade): refuse a page whose entries would land on one another
2026-10-16 09:28:26 -07:00
YHH 3260a68023 Merge pull request #13468 from esengine/feat/11376-s4-peer-tools
feat(peers): list_peers and send_peer_message tools for the model
2026-10-16 09:27:31 -07:00
SivanCola daa4cf4044 test(serve): verify exact references through the completion route
Problem: Review requested proof that production completion removes exact
reference candidates independently of the frontend MockPort fixture.

Root cause: The frontend interaction tests and the existing control unit
test did not exercise the registered GET /complete route together with its
controller data, scoped filesystem lookup, and UTF-16 boundary.

Fix: Add seven real HTTP cases using the production handler and disposable
workspace files. Cover partial paths, exact filenames, prose and emoji
prefixes, escaped nested paths, retaining a genuine longer alternative,
and an exact final alternative. No production filtering behavior changes.

Verification: The completion HTTP tests and full serve package pass, along
with go vet, make lint, make check, and the builtin/boot pre-commit suites.
Removing only the existing control.dropTyped call via a temporary Go build
overlay makes six HTTP subtests fail on the retained identity insertion;
this checks regression sensitivity and is not a production defect claim.
2026-10-17 00:24:16 +08:00
esengine 8e6aad3ee0 test(evidence): cover completion adjudication and path evidence 2026-10-16 09:22:55 -07:00
YHH 5b93b29c97 Merge pull request #13434 from imcreazy/fix/crashreport-pid-reuse
fix(crashreport): tell a recycled pid from the reader that wrote the dump
2026-10-16 09:22:46 -07:00
esengine dfd8d1a89c fix(upgrade): refuse a page whose entries would land on one another 2026-10-16 09:18:09 -07:00
esengine 47a4aad04e docs(release-notes): note for #13457 2026-10-16 09:11:28 -07:00
esengine bdaf57e287 feat(desktop): remove the timeline rail from the conversation 2026-10-16 09:11:28 -07:00
YHH 9082368925 Merge pull request #13431 from imcreazy/fix/serve-dir-flag
fix(serve): take the project root from --dir, as other verbs do
2026-10-16 09:09:05 -07:00
YHH 2dffa4d0ed Merge pull request #13465 from esengine/chore/coverage-floors
test(shellsafe): run what the classifier calls a read; uniq treats a word after its file as an output
2026-10-16 09:07:34 -07:00
YHH b148ae453d Merge pull request #13454 from esengine/feat/read-video-frames
feat(tools): read_video samples frames from a video with ffmpeg
2026-10-16 09:05:26 -07:00
esengine 95ecd55391 test(permission): pin the generic to/text subject rule 2026-10-16 08:59:15 -07:00
esengine 440fc18a62 feat(peers): list_peers and send_peer_message tools for the model (S4a of #11376) 2026-10-16 08:56:03 -07:00
YHH b806b594d0 Merge pull request #13469 from esengine/feat/12040-provider-draft
feat(providers): fold the web search, thinking and continuation switches into the connection draft
2026-10-16 08:51:01 -07:00
YHH acc368ee2a Merge pull request #13466 from esengine/fix/peer-frame-tag-casing
fix(peers): the message frame cannot be closed or reopened by a differently cased or spaced tag
2026-10-16 08:50:09 -07:00
esengine 6ca0c2cd53 fix(skill): memoize slash-list discovery for two seconds 2026-10-16 08:43:10 -07:00
imcreazy 40bd3f06b8 fix(crashreport): tell a recycled pid from the reader that wrote the dump 2026-10-16 23:28:39 +08:00
imcreazy da4bb5886d fix(boot): keep the host tool groups out of the schema when no helper can run them 2026-10-16 23:28:04 +08:00
imcreazy 063ad81319 fix(serve): take the project root from --dir, as other verbs do 2026-10-16 23:27:26 +08:00
esengine f7e19e9ee5 feat(providers): fold the web search, thinking and continuation switches into the connection draft 2026-10-16 08:23:17 -07:00
esengine a0301e55fc test(shellsafe): run the git commands the classifier calls reads against a real repository 2026-10-16 08:12:26 -07:00
esengine d02933aeaf fix(peers): the message frame cannot be closed or reopened by a differently cased or spaced tag 2026-10-16 08:07:38 -07:00
esengine 5c33fb2e06 test(shellsafe): run what the classifier calls a read; uniq treats a word after its file as an output 2026-10-16 08:04:01 -07:00
阿稳 144d1817ad Merge remote-tracking branch 'origin/studio' into fix/v2.42.0-dev
Brings the three Web fixes onto the current studio line: the hosted page's own-fetch stale check, the pending approval/ask handed to an attaching polled client, and the no-store shell.

The one conflict, internal/frontend/serve/web_entry_test.go, was both sides appending a test at the same point; both are kept.
2026-10-16 23:03:16 +08:00
esengine ff224380d2 test(shellsafe): run what the classifier calls a read; uniq treats a word after its file as an output 2026-10-16 08:03:15 -07:00
YHH c3c19e0748 Merge pull request #13273 from SivanCola/codex/13264-session-worktree
feat(studio): 会话 worktree 配置与统一聊天区菜单 / session workspace setup
2026-10-16 08:00:39 -07:00
YHH 82697031ca Merge pull request #13464 from esengine/test/control-permission-rules
test: cover permission rule saving, network settings and bash path evidence
2026-10-16 07:57:55 -07:00
SivanCola c99dcf42bf test(studio): pin exact reference Enter behavior
Problem: Review requested coverage for Enter after a manually completed
reference, including the case where another candidate is still available.

Root cause: The kernel already removes an insertion equal to the typed
token, but MockPort kept that row and manufactured an extra confirmation
step in development fixtures.

Fix: Keep MockPort's candidate filtering aligned with the kernel and add
four composer regressions. A full path with no remaining candidates sends
in one Enter. An open menu containing a genuine alternative still confirms
that alternative before submission, as required by the interaction contract.

Verification: The three exact-path cases failed against the old fixture.
All 5099 frontend tests, typecheck, build, make check, go vet, and make lint
pass. The existing kernel dropTyped regression passes. Real-browser checks
confirm single-Enter submission for exact paths at wide/light and phone/dark
sizes. The builtin and boot pre-commit suites pass.
2026-10-16 22:54:44 +08:00
YHH 76b326c602 Merge pull request #13463 from esengine/test/control-mcp-install
test(control): cover MCP install guards and import
2026-10-16 07:54:26 -07:00
esengine a691b52eb9 test: cover permission rule saving, network settings and bash path evidence 2026-10-16 07:46:29 -07:00
esengine 38f8a56f1b test(control): cover MCP install guards and import 2026-10-16 07:42:26 -07:00
YHH adc41fdd97 Merge pull request #13462 from esengine/test/safety-control-debt
test: cover the inbox edit verbs, completion reports and write inspection
2026-10-16 07:39:54 -07:00
YHH 94d1976713 Merge pull request #13461 from esengine/feat/pane-fold-notice
feat(desktop): say which session a full window folded out of the tab bar
2026-10-16 07:34:19 -07:00
YHH 95d7cb2cf6 Merge pull request #13460 from esengine/feat/11376-s3-peers-ui
feat(desktop): messages between sessions in Studio (S3 of #11376)
2026-10-16 07:33:37 -07:00
esengine 92d084515c feat(tools): read_video samples frames from a video with ffmpeg
A read-only tool that turns a video into a few time-labelled pictures for a
model that reads images. The container is named from the file's own bytes and
ffmpeg is forced to it with file-only protocols; failures carry typed codes.
It is reached through use_capability and stays out of the schema prefix (#13399).
2026-10-16 07:31:37 -07:00
esengine f33caaca90 test: cover the inbox edit verbs, completion reports and write inspection 2026-10-16 07:28:32 -07:00
阿稳 8d49317e5d fix(web): hand pending prompts to late clients and stop false stale reports
The polled transport attached past the watermark and dropped what came back, so a page opened after a prompt was raised never saw the approval or ask the run was waiting on. An attach now carries the pending prompt to that client alone, and the client reads the frames it is sent.

A hosted page checked for a newer build through the global fetch, which remoteHub has already replaced with a tunnel to the kernel; it compared its own build against the kernel's and reported itself behind forever. The check now reads through the page's own fetch, bound before the swap.

The shell names which build is current and was served with no cache policy, so a reused copy left an open page behind and reloading landed on that same copy. It is now served no-store; the content-hashed bundles under /assets keep their long cache.
2026-10-16 22:22:53 +08:00
YHH 3765035e3d Merge pull request #13455 from esengine/feat/shellsafe-git-readonly
feat(shellsafe): classify read-only git listings as reads
2026-10-16 07:22:10 -07:00
esengine f82277b80d feat(desktop): say which session a full window folded out of the tab bar
With an open-session limit set, opening one more session reuses or closes an
idle pane. A calm note now names the conversation, the limit, and that it is
still in the list; a note is a toast tone with no report button (#13419).
2026-10-16 07:17:56 -07:00
esengine db902db79d refactor(desktop): session-coordination settings groups in their own component 2026-10-16 07:16:11 -07:00
SivanCola f5b06287a1 fix(studio): confirm reference completion before sending
Problem: Pressing Enter while an @ file or directory candidate was visible
could submit an unfinished prompt instead of confirming the reference.

Root cause: The menu highlighted its first item but only owned Enter after
explicit navigation. Directory acceptance also shared the descent path.

Fix: Let the visible selection own Enter and distinguish confirmation from
Tab descent. Finish accepted references with a separator, preserve existing
whitespace and trailing text, and clear the old completion before drafting
continues. Update both locale hints.

Verification: Twelve new composer regressions reproduce the old behavior
and cover files, directories, nested Tab descent, pointer acceptance,
escaped paths, send preferences, Shift keys, IME, and Escape. The full
frontend suite passes (609 files, 5095 tests), alongside typecheck, build,
UI census, make check, go vet, and make lint. Browser interaction was
verified with the local MockPort workspace.
2026-10-16 22:14:53 +08:00
esengine ae30efbfca feat(desktop): messages between sessions in Studio (S3 of #11376) 2026-10-16 07:13:42 -07:00
esengine 23eafa84f5 fix(shellsafe): git config reads one key and never a credential; remote show needs -n 2026-10-16 06:55:36 -07:00
esengine 07a5c0d8d3 feat(shellsafe): classify read-only git listings as reads 2026-10-16 06:38:49 -07:00
YHH 1fbb650324 Merge pull request #13450 from esengine/feat/shellsafe-gh-readonly
feat(shellsafe): classify read-only gh calls as reads
2026-10-16 06:20:23 -07:00
YHH e3be160d52 Merge branch 'studio' into feat/shellsafe-gh-readonly 2026-10-16 06:04:38 -07:00
YHH eaff7c8ae2 Merge pull request #13430 from esengine/feat/11376-s2-peer-send
feat(peers): send messages to another session, /peer and HTTP verbs
2026-10-16 05:59:00 -07:00
YHH 7f37c01a91 Merge pull request #13445 from esengine/feat/tool-result-to-end
feat(desktop): bring a tool call's result to the end of the transcript
2026-10-16 05:56:04 -07:00
esengine 7a41081c4f feat(desktop): bring a tool call's result to the end of the transcript
A button on a finished tool card repeats that call's own recorded card in a
section at the end of the transcript, as a view only: nothing is stored or
sent, and the original stays where it happened (#13394).
2026-10-16 05:44:38 -07:00
esengine 7dace9cfb0 Merge studio into feat/11376-s2-peer-send 2026-10-16 05:43:03 -07:00
YHH a4cf9d0591 Merge pull request #13437 from chuiwenwei/fix/sessionstore-unreadable-sidecar-not-absent
fix(sessionstore): an unreadable sidecar is not an absent one
2026-10-16 05:40:45 -07:00
YHH 4c549631c5 Merge pull request #13448 from esengine/fix/e2ebench-windows-stubs
test(e2ebench): skip the shell-stub tests on Windows
2026-10-16 05:39:31 -07:00
esengine 42a1d24662 fix(shellsafe): a gh filter that loads jq modules is not a read 2026-10-16 05:39:12 -07:00
esengine 783fef7409 fix(shellsafe): a gh filter that can reach the process environment is not a read 2026-10-16 05:36:44 -07:00
esengine f5116c49fa docs(release-notes): shorten the note for #13450 2026-10-16 05:33:11 -07:00
esengine 423c906b25 docs(release-notes): note for #13450 2026-10-16 05:32:48 -07:00
esengine 82e8118db2 feat(shellsafe): classify read-only gh calls as reads 2026-10-16 05:31:41 -07:00
esengine 0836d7a2c6 test(e2ebench): skip the shell-stub tests on Windows 2026-10-16 05:18:27 -07:00
Will 6ac611bdf1 fix(sessioninbox): the lock-free read may not adopt another run's queue
The transaction-free fallback loaded the disk manifest without the RunID
isolation the locked path applies, so a queue another run (or a previous run)
left behind could be dispatched while the lock was momentarily unavailable.
Refuse a foreign manifest with ErrForeignRun, which the dispatcher already maps
to a retry, and give the frontend a coded refusal for it.
2026-10-16 20:16:21 +08:00
YHH 06a4b4622a Merge pull request #13447 from esengine/fix/13441-notice-english
fix(i18n): give the fifteen untranslated notices English and guard the table
2026-10-16 05:13:39 -07:00
YHH 1a1b953233 Merge pull request #13443 from esengine/fix/provider-key-origin
fix(providers): a draft address on another origin never borrows the saved key
2026-10-16 05:01:43 -07:00
YHH 7c406be221 Merge pull request #13440 from esengine/fix/import-keeps-source-titles
fix(sessionstore): an imported 1.x conversation keeps the title 1.x showed
2026-10-16 04:59:11 -07:00
esengine b456bd34e1 fix(i18n): give the fifteen untranslated notices English and guard the table 2026-10-16 04:57:09 -07:00
YHH b7b7687e17 Merge pull request #13442 from esengine/fix/13433-host-exit-keeps-reason
fix(host): a failing exit keeps its crash file with the code and the reason
2026-10-16 04:47:16 -07:00
esengine 7eeebe89be fix(peers): a sent message carries no process writer id, which can exceed the id limit 2026-10-16 04:42:45 -07:00
YHH c929e444b9 Merge pull request #13427 from chuiwenwei/fix/plugin-cache-upgrade-not-silently-unpersisted
fix(plugin): a legacy cache upgrade is not claimed unless it reached disk
2026-10-16 04:40:09 -07:00
YHH 6b383ad920 Merge pull request #13423 from esengine/feat/cli-ships-page
feat(release): the CLI archives carry the web page
2026-10-16 04:38:19 -07:00
YHH 8cb8af0c14 Merge pull request #13428 from chuiwenwei/fix/e2ebench-unmeasured-run-not-accounted
fix(e2ebench): report a suite run whose metrics never landed as unmeasured
2026-10-16 04:38:17 -07:00
esengine 8cf0b5e94f fix(host): a failing exit keeps its crash file with the code and the reason 2026-10-16 04:36:41 -07:00
esengine c0aad054f4 fix(providers): a draft address on another origin never borrows the saved key 2026-10-16 04:33:13 -07:00
YHH d952d8c7fd Merge pull request #13426 from chuiwenwei/fix/sessionstore-unreadable-checkpoint-blocks-rewrite
fix(sessionstore): refuse a degraded rewrite when the checkpoint cannot be read
2026-10-16 04:32:46 -07:00
YHH 1e312b66d8 Merge pull request #13425 from chuiwenwei/fix/stats-flush-reports-write-failure
fix(stats): Flush reports the first append that failed
2026-10-16 04:32:12 -07:00
esengine eb384283d1 fix(sessionstore): an imported 1.x conversation keeps the title 1.x showed 2026-10-16 04:29:55 -07:00
Will 457c68e30d fix(sessioninbox): surface a queue read failure instead of an empty one
NextQueued returns (InboxItemMeta, bool, error) and, when the disk
transaction is unavailable, reads the published manifest without the lock
(commits land by atomic rename, so that snapshot is complete). The
dispatcher maps a read error to inboxDispatchRetry instead of reporting
the inbox idle.

Narrowed per review: the permanent 1s long stop (armInboxDispatchLongStop,
its constant, its arming and its stop/reset wiring) is removed, so the
retry schedule past the existing bounded burst is exactly what it was
before this branch. The dispatcher audit case is rewritten to assert the
in-scope contract - a failed queue read schedules a retry and does not
report idle, and the durable item survives - rather than delivery after
the burst was exhausted, which was the removed policy.
2026-10-16 19:29:33 +08:00
YHH 5829f2a1c5 Merge pull request #13422 from esengine/feat/upgrade-refreshes-page
feat(cli): upgrade keeps the page beside the binary in step with the kernel
2026-10-16 04:25:52 -07:00
Will 71c7cacf55 fix(sessioninbox): report a queue that could not be read instead of an empty one
NextQueued discarded beginDiskTransactionLocked's error and answered from
the in-memory snapshot, so a transaction that could not run — another
process holding the lock, EACCES, ENOSPC, a transient EIO — looked exactly
like an empty queue. dispatchInboxOnce read that as inboxDispatchIdle and
stopped with the durable item still queued, permanently.

NextQueued now reports that failure as an error, and before it will claim
the queue is empty it falls back to reading the published manifest without
the transaction: commits land by atomic rename, so that snapshot is
complete, and repair work stays behind the lock. A genuinely empty or
paused queue is still idle.

The bounded burst (three attempts, ~750ms) used to abandon work its own
scans had seen. Past the burst the dispatcher now parks on a slow
production timer that keeps re-checking until the item advances or the
queue reads empty; reset and Close cancel the pending timer.

A read error maps to inboxDispatchRetry, so an unreadable queue retries
instead of ending dispatch.
2026-10-16 19:07:03 +08:00
esengine 8370d9cf45 feat(peers): send messages to another session, /peer and HTTP verbs (S2 of #11376) 2026-10-16 04:05:54 -07:00
esengine bfba90b356 feat(cli): upgrade keeps the page beside the binary in step with the kernel
When an install already has frontend-next/dist beside the binary, upgrade
reads the page out of the verified archive, validates it in memory, swaps it
in before the binary and puts it back if the binary does not land (#13406).
2026-10-16 04:04:18 -07:00
Will 2a3756b9e5 fix(sessionstore): an unreadable sidecar is not an absent one
ListSessionOrder keeps a row whose branch-meta sidecar cannot be read, so one
broken sibling cannot hide a folder, but every column of that row is then a zero
value. recoveryArchiveLineage consumed that row as 'not a recovery copy of this
conversation' and SetSessionLineageArchived reported success while a copy of the
row stayed active and listed on its own. The lineage walk now reads each listing
row's sidecar itself and refuses the transaction, so the archive endpoint
reports the read failure instead of a partial success.

ListBranches dropped a branch whose sidecar could not be decoded while a missing
sidecar built defaults and kept it; interruptedTurnContinuedOnRecoveryBranch
read that absence as 'the turn did not continue on a recovery branch' and
stripped the interrupted turn's messages. An unreadable sidecar is now handled
exactly like a missing one.
2026-10-16 19:00:23 +08:00
YHH 95c4c6da28 Merge pull request #13419 from esengine/feat/tab-limit-setting
feat(studio): a setting for how many session tabs stay open before one is folded away
2026-10-16 03:59:44 -07:00
YHH cc66ec38d3 Merge pull request #13418 from esengine/feat/nav-to-top-bar
feat(desktop): move the pane views to the window bar and drop the view strip
2026-10-16 03:58:34 -07:00
YHH 5d4676a277 Merge pull request #13416 from esengine/fix/13254-status-running-excludes-finishing
fix(control): report a turn as running only while it executes, not while its TurnDone is delivered
2026-10-16 03:57:58 -07:00
YHH 93c13cf4e7 Merge pull request #13424 from esengine/fix/linux-folder-panel
fix(desktop): a Linux folder panel that cannot be drawn asks for the path instead of doing nothing
2026-10-16 03:51:57 -07:00
SivanCola 3687254d05 docs(studio): describe narrow composer focus behaviour
Problem: release notes did not mention the focus behaviour shared by all
narrow composer toolbars, making the compact empty state look accidental.

Root cause: the behaviour and pointer stability rationale were documented
only in the internal style contract.

Fix: add matching Chinese and English release-note entries explaining that
empty narrow toolbars remain compact on focus and expand for draft text,
attachments or an open menu, keeping pointer clicks on the same button.

Verification: 12 release-note rendering and Markdown reference tests passed;
make check and git diff --check passed. Documentation-only change.
2026-10-16 18:50:20 +08:00
YHH 7180fc96fc Merge pull request #13420 from esengine/fix/findpage-symlinked-exe
fix(serve): find the page beside the real executable of a link
2026-10-16 03:40:41 -07:00
esengine 3959932434 feat(release): the CLI archives carry the web page
The cli job builds the page once and packs it beside the executable under
frontend-next/dist, where serve.FindPage looks, through a packer and a checker
that are run by the job and by tests (#13406).
2026-10-16 03:39:02 -07:00
esengine bee7a23b66 fix(desktop): a Linux folder panel that cannot be drawn asks for the path instead of doing nothing 2026-10-16 03:38:01 -07:00
esengine cb195c983d feat(studio): a setting for how many session tabs stay open before one is folded away 2026-10-16 03:29:38 -07:00
SivanCola 1f30e8e747 fix(studio): align composer menu density with the application
Problem: the five composer menus appeared oversized beside the application's
compact toolbar and other menus.

Root cause: the shared composer menu style imposed 15px labels, 13px notes,
380px panels, 20px corners and generous spacing on every menu. Workspace
toggle and note borders also produced adjacent divider lines.

Fix: use 13px labels and 11px notes, 12px corners and 8px panel insets; choose
280-340px widths by content. Tighten rows, headings, search, chips, checks
and the workspace switch together, leaving one workspace note divider.
Keep all changes scoped to composer menus and update their style contract
and bilingual release note.

Verification: 22 related suites / 226 tests; production build; make lint,
make check and git diff --check. In-app browser checks cover all five menus
in light/dark themes, 390px and 320px viewport bounds, model/branch search,
Escape dismissal and focus restoration. Mock data only.
2026-10-16 18:29:00 +08:00
YHH a1f63d7964 Merge pull request #13417 from esengine/feat/11376-s1-peer-hold
feat(peermail): hold what other sessions send for the user, and deliver it only once approved
2026-10-16 03:28:12 -07:00
esengine d72352b23d fix(serve): find the page beside the real executable of a link
FindPage now also looks beside the file an executable link resolves to,
after the directory it was invoked from, so a package manager that links the
binary onto the PATH still finds the page next to it (#13406).
2026-10-16 03:27:24 -07:00
SivanCola 450a59d0f7 merge: sync studio and preserve inline composer workspace controls
Problem: the workspace PR conflicted with the current studio composer, and
restoring its toolbar slot exposed narrow-pane overlap and pointer failures.

Root cause: the new side-question control shares the same toolbar; a fixed
model minimum could overrun its flex group. Focus-dependent phone folding
also moved buttons between pointer press and release.

Fix: retain both worktree setup and the upstream side-question button; allow
model shrinking and internal wrapping at the narrowest widths. Keep empty
phone toolbar geometry stable on focus and extend browser guards to cover
both workspace variants, pointer opening and nested button overlap.

Verification: 5,021 frontend tests; affected suites 126 tests after CSS edits;
production and perf builds; make lint, make check and diff whitespace checks;
hoverradius, branch-menu, modelrows and extended narrow browser guards;
light/dark worktree selection, Escape/focus, menu bounds and first send.
2026-10-16 18:17:31 +08:00
esengine 38ebf5f677 feat(desktop): move the pane views to the window bar and drop the view strip
Run analysis and Workbench become icons on the window bar for the focused
pane; the Conversation / Run analysis / Workbench row, the plan peek and
their styles are removed (#13369).
2026-10-16 03:14:10 -07:00
SivanCola 340bbd24b6 fix(studio): restore inline workspace controls and shared pill styling
Problem: workspace and branch controls were moved outside the composer, and
the footer-only 8px override broke the shared toolbar radius browser guard.

Root cause: the workspace slot bypassed the existing branch toolbar layout
and overrode the common mode button geometry.

Fix: restore both controls to the original toolbar slot, reuse branch layout
and pill styling, remove the footer overrides and keep runtime statistics
below the card. Cover new and existing conversations in the browser guard,
load the production menu stylesheet in the perf fixture and align the docs.

Verification: reproduced both layout assertions and the light/dark radius
failure before the fix; 4,831 frontend tests passed; production build,
make lint and make check passed; hoverradius, branch-menu, modelrows and
narrow browser guards passed.
2026-10-16 18:12:21 +08:00
esengine 5cec67c0d4 feat(peermail): hold what other sessions send for the user, and deliver it only once approved 2026-10-16 03:10:54 -07:00
YHH 25dcf4d60f Merge pull request #13411 from esengine/feat/11376-s0-peermail-spool
feat(peermail): the spool, the live record and the forbidden-read directory for messages between sessions
2026-10-16 02:55:05 -07:00
esengine 448e7fe468 fix(control): report a turn as running only while it executes, not while its TurnDone is delivered 2026-10-16 02:46:28 -07:00
YHH c6f134a4c7 Merge pull request #13415 from esengine/feat/13384-write-lease-toggle
feat(studio): a switch for how sessions in one project wait on each other
2026-10-16 02:29:16 -07:00
esengine cb138521d5 feat(peermail): the spool, the live record and the forbidden-read directory for messages between sessions 2026-10-16 02:24:00 -07:00
YHH 1ea7d64cf3 Merge pull request #13413 from esengine/fix/import-registers-workspaces
fix(migration): list the projects a recovered session was routed back into
2026-10-16 02:17:58 -07:00
esengine c79454f8ef feat(studio): a switch for how sessions in one project wait on each other 2026-10-16 02:09:28 -07:00
esengine e0bbc694d7 fix(migration): list the projects a recovered session was routed back into 2026-10-16 02:03:14 -07:00
YHH 8c35e9c8b2 Merge pull request #13262 from xxoingr/fix/cli-stop-offering-light-preset
fix(cli): stop offering the retired light execution setting
2026-10-16 01:58:41 -07:00
YHH b60a5cfe0e Merge pull request #13408 from esengine/feat/12040-last-check-dot
feat(providers): the list remembers how each source's last test ended
2026-10-16 01:56:02 -07:00
YHH 1bccebf836 Merge pull request #13409 from esengine/fix/13406-readable-page-not-built
fix(serve): tell a browser what to do when this server has no web page
2026-10-16 01:37:03 -07:00
esengine df2a1f6769 fix(serve): tell a browser what to do when this server has no web page 2026-10-16 01:25:02 -07:00
YHH caeef8eb94 Merge pull request #13407 from esengine/fix/13402-open-rows
fix(serve): a session open in a pane keeps its titled row whatever the list leaves out
2026-10-16 01:17:46 -07:00
esengine 0b8457466e feat(providers): the list remembers how each source's last test ended 2026-10-16 01:14:36 -07:00
esengine ece69f0930 feat(providers): the list remembers how each source's last test ended 2026-10-16 01:12:47 -07:00
esengine 064f04c1e9 fix(serve): a session open in a pane keeps its titled row whatever the list leaves out 2026-10-16 01:05:51 -07:00
YHH 2332e65698 Merge pull request #13405 from esengine/fix/12040-draft-aware-check
fix(providers): test the address, key and protocol on the form, not only the saved ones
2026-10-16 00:59:20 -07:00
YHH b94404c2f2 Merge pull request #13404 from esengine/feat/feedback-mine-filter
feat(desktop): filter my feedback by progress and new replies
2026-10-16 00:59:15 -07:00
YHH c32febd274 Merge pull request #13240 from chuiwenwei/fix/memory-suppression-audit-symmetry
fix(session): audit every automatic-recall suppression identically
2026-10-16 00:51:24 -07:00
YHH 02c7d24ec4 Merge pull request #13239 from chuiwenwei/fix/checkpoint-turn-reuse-after-unreadable
fix(checkpoint): keep turn numbering above unreadable checkpoint files
2026-10-16 00:49:21 -07:00
YHH fc65474e6b Merge pull request #13261 from xxoingr/test/boot-thought-pause-after-receipt
test(boot): start the scripted thought's pause after the host takes it
2026-10-16 00:46:38 -07:00
YHH 040e48f927 Merge pull request #13260 from xxoingr/fix/filelock-free-lock-after-deadline
fix(filelock): take a free lock even after the caller's deadline has passed
2026-10-16 00:44:03 -07:00
YHH 9765c5db5e Merge pull request #13401 from esengine/feat/12040-typed-connection-check
feat(studio): ask for protocol, address and key before the source name
2026-10-16 00:40:16 -07:00
YHH 9c36cb3a3b Merge pull request #13400 from esengine/feat/side-question-composer
feat(desktop): move the side question entry into the composer toolbar
2026-10-16 00:40:07 -07:00
esengine f4010fc6b6 fix(providers): test the address, key and protocol on the form, not only the saved ones 2026-10-16 00:36:13 -07:00
esengine e778a376b5 feat(desktop): filter my feedback by progress and new replies
A sticky bar of groups (all, new replies, needs your reply, open, closed)
with counts sits at the top of My feedback; rows that do not match are
hidden rather than unmounted so open threads and reply drafts survive (#13388).
2026-10-16 00:33:51 -07:00
esengine de77acf72d feat(desktop): move the side question entry into the composer toolbar
The side question becomes an icon after the attach button, with a tooltip
and a pressed state, instead of a text button on the view strip (#13369).
2026-10-16 00:26:15 -07:00
esengine e2c861d525 feat(studio): ask for protocol, address and key before the source name 2026-10-16 00:19:17 -07:00
YHH 115de56961 Merge pull request #13389 from esengine/feat/4783-job-detail
feat(studio): a running background task can be opened to read its output
2026-10-16 00:18:46 -07:00
YHH 2350eb91b1 Merge pull request #13398 from esengine/feat/13111-origin-enum
fix(agent): label a sub-agent's report with the origin of what it fetched
2026-10-16 00:12:34 -07:00
YHH 3c3b000d81 Merge pull request #13397 from esengine/fix/13395-claim-wording
fix(delegation): say what a writer without write_paths claims under the optimistic write lease
2026-10-16 00:09:16 -07:00
YHH 67baf20bd2 Merge pull request #13396 from esengine/design/pane-nav
feat(desktop): show the goal in the composer plan fold and add view shortcuts
2026-10-16 00:06:04 -07:00
esengine f4a14f3325 feat(studio): a running background task can be opened to read its output 2026-10-16 00:05:48 -07:00
YHH c6446343b8 Merge pull request #13393 from esengine/feat/11942-cross-session-search
feat(history): search conversation text from the sidebar
2026-10-16 00:05:21 -07:00
esengine d5544f8368 fix(agent): label a sub-agent's report with the origin of what it fetched 2026-10-16 00:02:09 -07:00
esengine b634815e3a fix(delegation): say what a writer without write_paths claims under the optimistic write lease 2026-10-15 23:59:14 -07:00
esengine 7d21a467c4 feat(desktop): show the goal in the composer plan fold and add view shortcuts
The composer's plan fold now carries the /goal text when there is no plan,
and the three pane views get Ctrl/Cmd 1-3 shortcuts and palette entries.
Prerequisite for removing the top plan peek and view strip (#13369).
2026-10-15 23:49:21 -07:00
esengine 8ae2737bf8 feat(history): search conversation text from the sidebar 2026-10-15 23:44:36 -07:00
YHH 74f8239944 Merge pull request #13391 from esengine/feat/12254-cancel-one-call
feat(agent): stop one running tool call without ending the turn
2026-10-15 23:38:44 -07:00
esengine 22ba1cdf2d feat(agent): stop one running tool call without ending the turn 2026-10-15 23:06:05 -07:00
YHH 438f1e7d4a Merge pull request #11299 from Guan0923/codex/short-user-bubble
feat(studio): create conversation branches from final replies
2026-10-15 22:58:01 -07:00
YHH 82a0084226 Merge pull request #13387 from esengine/feat/11962-ask-interface-size
feat(studio): ask once whether the interface is too small on a wide display at 100% scaling
2026-10-15 22:54:09 -07:00
YHH d75669c7c6 Merge pull request #13386 from esengine/fix/13377-archived-open
fix(serve): an open archived session no longer returns as an unlisted new-session row
2026-10-15 22:27:53 -07:00
esengine f8e132334a feat(studio): ask once whether the interface is too small on a wide display at 100% scaling 2026-10-15 22:25:54 -07:00
esengine e14e031993 fix(serve): an open archived session no longer returns as an unlisted new-session row 2026-10-15 22:15:22 -07:00
Guan09 ad01fab3d6 fix(studio): preserve BranchAt checkpoints and source settings 2026-10-16 13:14:18 +08:00
Guan09 80d7b978c0 fix(studio): scope fork UI and preserve conversation checkpoints 2026-10-16 13:13:16 +08:00
Guan09 83b530f0d3 feat(studio): 调整消息操作并支持从最终答复创建对话分支 2026-10-16 13:13:16 +08:00
YHH 27f0dd00fc Merge pull request #13383 from esengine/fix/13345-tree-index
perf(sessionstore): remember the session listing so a restart does not open every sidecar
2026-10-15 22:05:03 -07:00
esengine 70a21569fd test(sessionstore): use the current idioms in the listing index tests 2026-10-15 21:53:59 -07:00
esengine d9a3e7b930 docs(sessionstore): shorten the listing comment 2026-10-15 21:51:08 -07:00
esengine a440eed706 docs: shorten the listing index note 2026-10-15 21:51:08 -07:00
esengine ec66acdf93 perf(sessionstore): remember the session listing so a restart does not open every sidecar 2026-10-15 21:51:08 -07:00
YHH d51242c956 Merge pull request #13382 from esengine/fix/13379-ask-error-style
fix(studio): give the ask card's delivery error room and colour
2026-10-15 21:43:17 -07:00
YHH e64d1e0b49 Merge pull request #13381 from esengine/fix/12883-empty-skill
fix(skill): a review skill chosen with no task reviews the pending changes by default
2026-10-15 21:36:41 -07:00
esengine 678a7f6eb5 fix(studio): give the ask card's delivery error room and colour 2026-10-15 21:34:30 -07:00
YHH 15507b8e22 Merge pull request #13380 from esengine/fix/13148-fallback-notice
fix(agent): say why a fold that stopped matching is no longer used
2026-10-15 21:33:59 -07:00
esengine d76f5e1594 test(control): drop an unused helper 2026-10-15 21:24:55 -07:00
esengine c1c5676e69 refactor(control): the typed subagent slash moves to its own method 2026-10-15 21:23:53 -07:00
esengine b1e369773c fix(skill): a review skill chosen with no task reviews the pending changes by default 2026-10-15 21:23:14 -07:00
esengine f02c20db16 fix(agent): say why a fold that stopped matching is no longer used 2026-10-15 21:22:55 -07:00
YHH f47ee0f2fb Merge pull request #13378 from esengine/fix/13345-tree-retry
fix(studio): the sidebar offers a retry when the first read is slow, and a retry does not queue
2026-10-15 21:18:24 -07:00
esengine 6c9e696290 style(studio): keep the retry rule within the stylesheet budget 2026-10-15 21:07:10 -07:00
esengine c57dc18a7a docs(studio): shorten the sidebar retry note 2026-10-15 21:05:25 -07:00
esengine 2ca0e9b771 fix(studio): the sidebar offers a retry when the first read is slow, and a retry does not queue 2026-10-15 21:05:25 -07:00
YHH 21f226e608 Merge pull request #13376 from esengine/fix/kept-failures-effect
test(boot): hold the failed-result retention bound at the provider boundary
2026-10-15 21:03:56 -07:00
YHH e9b5b44c1d Merge pull request #13374 from esengine/feat/13125-granted-root-images
feat(studio): conversation pictures load from folders the user granted the workspace
2026-10-15 20:58:38 -07:00
YHH c1150171e3 Merge pull request #13373 from esengine/fix/13368-user-images
fix(studio): a tall picture sent with a message no longer spills out of its thumbnail
2026-10-15 20:46:54 -07:00
esengine 1b55821b6f test(boot): hold the failed-result retention bound at the provider boundary
#13366 bounded the failed tool results a fold keeps, with component tests. This
adds the effect test through the real Build: 60 turns of failing commands fold
seven times, and the request right after a fold must not carry more failed
results than the retention budget plus the recent tail allow (4 with the bound,
9 without it, where the 50% ceiling is the only thing that clears them).

Refs #13148
2026-10-15 20:45:48 -07:00
esengine 5c7992e840 docs(studio): shorten the user picture note 2026-10-15 20:36:54 -07:00
esengine 8325582049 fix(studio): a tall picture sent with a message no longer spills out of its thumbnail 2026-10-15 20:36:33 -07:00
esengine 02107977f4 feat(studio): conversation pictures load from folders the user granted the workspace 2026-10-15 20:33:56 -07:00
esengine 4215adc58a feat(serve): /workspace/image can read from a folder the user granted the workspace 2026-10-15 20:33:56 -07:00
YHH d06ffdddd5 Merge pull request #13366 from esengine/fix/kept-failures-budget
fix(agent): bound the failed tool results a fold keeps verbatim
2026-10-15 20:33:30 -07:00
YHH 1a140858c5 Merge pull request #13367 from esengine/feat/update-download-mode
feat(update): a Download update mode that fetches but never installs or restarts
2026-10-15 20:26:45 -07:00
Will b96b977897 fix(e2ebench): report a suite run whose metrics never landed as unmeasured
runSegments read only the final metrics file. When a child was killed or
timed out it folded the missing record in as an accounted zero, deleted the
<metrics>.partial snapshot the sink had been writing, and left Unaccounted
unset, so every published total omitted what the run spent.

Read the sidecar when the final record is absent, keep the file it came from,
set Partial, and mark the run Unaccounted when neither exists.
renderMeterAccounting no longer prices a run whose harness reported no tokens
against the meter, which invented a -100% divergence.
2026-10-16 11:24:20 +08:00
Will e795d822f6 fix(plugin): stop reporting a legacy cache-key upgrade that did not persist
LoadCachedSchemaForSpec rewrote a legacy-keyed entry in memory, discarded the
SaveCachedSchema error, and returned the hit with the new CacheKey. With a
read-only or full cache dir the file still held the legacy key, so boot was
handed a key that a later read cannot reproduce and pinned the session's MCP
tool identity to a key with no on-disk counterpart
(internal/assembly/boot/mcp_registration.go:76).

Keep the hit but claim the upgraded key only once the rewrite reached disk; on
failure the returned object keeps the legacy key the file holds, which matches
the package's best-effort, silent-degradation policy. A successful upgrade is
unchanged, and no caller can now observe a key the file disagrees with.
2026-10-16 11:20:39 +08:00
Will 9f33c8c45e fix(sessionstore): refuse a degraded image rewrite when the checkpoint is unreadable
checkpointImages reported "no such image" for a checkpoint it could not
inspect, so refuseDegradedCheckpointRewrite read a stat failure, a read
failure, a non-regular file, or a file past the replay ceiling as "nothing to
protect" and let a transcript missing an image replace the only copy of that
image — the exact case where the guard cannot know.

Return the inspection error from checkpointImages, and have the guard fail
closed under ErrSessionImagesUnavailable for any checkpoint that exists but
cannot be read whole. A genuinely missing checkpoint still proceeds: there is
no second copy to protect. The restore path stays best-effort and now logs why
it could not inspect the checkpoint.
2026-10-16 11:20:20 +08:00
Will 6858c06de7 fix(stats): Flush reports the first append that failed
recordDispatcher.run discarded Writer.Append's error, so Flush — the only
success/failure signal the stats pipeline has — returned nil even when every
queued append failed. The dispatcher now keeps the first failure since the last
flush sentinel and hands it to the waiter through that sentinel, so a caller
that gates a deterministic read on Flush cannot mistake a failed write for a
durable one.

The failure is consumed by the flush that reports it, so a later flush over a
recovered directory returns nil, while a still-broken one keeps reporting. A
full queue may still drop a record; that stays documented and unchanged.

Tests: the audit red test plus controls for a healthy flush persisting records
and for failure-then-convergence.
2026-10-16 11:20:05 +08:00
esengine e715e2eac3 fix(agent): bound the failed tool results a fold keeps verbatim
A fold keeps failed tool results since the latest digest. The projection places
them right after that digest, so the next fold read last fold's survivors as
"since the latest digest" and kept them again: retention grew by every round's
failures and only stopped when the candidate crossed the 50% checkpoint ceiling.

Failed results now have their own retention budget (5% of the window, like kept
user turns), newest first; the rest fold into the digest, whose coverage guard
still names them. [[keep]] messages stay outside the budget.

Refs #13148
2026-10-15 20:18:53 -07:00
esengine 4342f7033f feat(studio): register the restart notice actions 2026-10-15 20:10:13 -07:00
esengine fad7beb5f8 feat(studio): the restart notice uses the calm surface; harness can stage an update 2026-10-15 20:10:13 -07:00
esengine b08c7b0ea9 docs(studio): shorten the download mode note 2026-10-15 20:10:13 -07:00
esengine 23618027e5 feat(studio): the Download update mode in settings and the restart notice 2026-10-15 20:10:13 -07:00
esengine 1ade11a727 feat(studio): the update watch downloads in download mode and reports the release waiting 2026-10-15 20:10:13 -07:00
esengine 5a2df57570 feat(config): a download update mode that fetches but never installs or restarts 2026-10-15 20:10:13 -07:00
YHH ed121465e6 Merge pull request #13364 from esengine/fix/13343-theme-contrast
test(theme): every shipped pack's grey inks read on the surfaces they are set on
2026-10-15 20:09:05 -07:00
YHH 900cf4c064 Merge pull request #13362 from esengine/fix/13354-md-local-images
fix(studio): local pictures in a reply load from the workspace channel or say why not
2026-10-15 19:59:43 -07:00
esengine ad67dd9d1b test(theme): every shipped pack's grey inks read on the surfaces they are set on 2026-10-15 19:58:50 -07:00
esengine 5f4dc69ebf fix(studio): local pictures in a reply load from the workspace channel or say why not 2026-10-15 19:50:23 -07:00
YHH 02aa56aaa8 Merge pull request #13361 from esengine/fix/13358-skill-hint
fix(studio): announce loading only while a slash word is waiting for the list
2026-10-15 19:39:28 -07:00
YHH 1a06a37546 Merge pull request #13360 from esengine/fix/12780-switching-state
fix(studio): the branch chip says which branch it is switching to
2026-10-15 19:37:44 -07:00
esengine e76f82b095 fix(studio): announce loading only while a slash word is waiting for the list 2026-10-15 19:29:19 -07:00
esengine ce3a71736d fix(studio): the branch chip says which branch it is switching to 2026-10-15 19:25:57 -07:00
YHH af7e4166e6 Merge pull request #13357 from esengine/fix/13343-running-actions
fix(studio): a reply's actions wait until the tools under it have finished
2026-10-15 19:22:36 -07:00
YHH fb2bab37ca Merge pull request #13356 from esengine/fix/13353-autoname-reason
fix(serve): say why automatic session naming failed
2026-10-15 19:21:03 -07:00
YHH 6f073db2ab Merge pull request #13355 from esengine/feat/13346-run-elapsed
feat(studio): the running chip says how long the turn has been running
2026-10-15 19:13:37 -07:00
esengine 7cd0830d47 refactor(studio): the empty-conversation hero moves out of Transcript.tsx 2026-10-15 19:11:00 -07:00
esengine 653d49bdb2 fix(studio): a reply's actions wait until the tools under it have finished 2026-10-15 19:08:15 -07:00
esengine 5add43c159 fix(serve): say why automatic session naming failed 2026-10-15 19:04:01 -07:00
esengine 343d100610 docs(studio): shorten the running chip note 2026-10-15 18:59:15 -07:00
esengine 5cd4b0347d test(studio): the run start is unknown until a turn starts in this window 2026-10-15 18:57:20 -07:00
esengine 13404bd977 feat(studio): the running chip says how long the turn has been running 2026-10-15 18:57:03 -07:00
YHH ff55e2dfa2 Merge pull request #13351 from esengine/fix/13350-feedback-shot-zoom
fix(studio): attached feedback screenshots open at full size
2026-10-15 18:54:15 -07:00
esengine ecc8138c11 fix(studio): attached feedback screenshots open at full size 2026-10-15 18:42:58 -07:00
YHH d2020b1309 Merge pull request #13348 from esengine/fix/13220-toast-names-config-file
fix(studio): a refused settings save names the config file that did not parse
2026-10-15 18:38:44 -07:00
YHH 7df661b5ef Merge pull request #13344 from esengine/fix/13293-unproven-gap
fix(agent): stop telling the model that making an unproven change again settles it
2026-10-15 18:34:37 -07:00
esengine 5d79d0f450 fix(studio): a refused settings save names the config file that did not parse 2026-10-15 18:27:08 -07:00
esengine bc2c691ba4 fix(agent): stop telling the model that making an unproven change again settles it 2026-10-15 18:23:53 -07:00
YHH fe1dceced9 Merge pull request #13340 from esengine/test/13295-system-prompt-change-effect
test(boot): a changed system prompt says the saved fold was not reused
2026-10-15 18:11:45 -07:00
esengine 81bb10fae1 test(boot): a changed system prompt says the saved fold was not reused
The covered-prefix hash includes the system message, so editing the system
prompt between two visits retires a saved compaction. #13336 added the notice
at the agent and control layers; this pins it at boot.Build, and pins that an
unchanged prompt stays quiet.

Refs #13295
2026-10-15 16:48:04 -07:00
YHH 55c3d81bbc Merge pull request #13339 from esengine/docs/release-studio-2.42.0
docs(release): Studio 2.42.0 notes
2026-10-15 16:40:16 -07:00
esengine ffd73e115e docs(release): Studio 2.42.0 notes 2026-10-15 16:28:01 -07:00
YHH 1a33ce5692 Merge pull request #13337 from esengine/fix/13335-lease-domain-notice
fix(workspacelease): say which git repository folds two folders into one write domain
2026-10-15 14:06:21 -07:00
esengine 6e2ed58a34 fix(workspacelease): say which git repository folds two folders into one write domain 2026-10-15 13:56:32 -07:00
YHH 2be1dba2da Merge pull request #13336 from esengine/fix/projection-drop-attribution
fix(control): say why a saved fold was not reused when a session is opened
2026-10-15 13:14:19 -07:00
esengine a717691db9 docs(release-notes): shorten the fold-not-reused fragment 2026-10-15 13:01:14 -07:00
esengine dfdbfbd4d8 fix(control): say why a saved fold was not reused when a session is opened 2026-10-15 12:59:32 -07:00
YHH 7b7af1a78c Merge pull request #13334 from esengine/fix/overview-cell-align
fix(studio): overview figures on one row share a baseline
2026-10-15 12:57:08 -07:00
YHH 59526e5156 Merge pull request #13333 from esengine/docs/release-note-gaps-1016
docs(release-notes): fragments for the expired-pin fix and the branch-switch latency
2026-10-15 12:54:14 -07:00
esengine 935200d9e8 fix(studio): overview figures on one row share a baseline 2026-10-15 12:46:41 -07:00
YHH 9006ff804c Merge pull request #13332 from esengine/feat/overview-context
feat(studio): the context window in the session overview
2026-10-15 12:45:42 -07:00
esengine c57cf43aeb docs(release-notes): fragments for the expired-pin fix and the branch-switch latency 2026-10-15 12:43:14 -07:00
esengine a9e2a12927 docs(release-notes): shorten the overview window fragment 2026-10-15 12:30:49 -07:00
esengine 97dee44a3d fix(studio): the overview's hand-over button has its own action value 2026-10-15 12:28:53 -07:00
esengine 2192bfb20c feat(studio): the context window in the session overview, stated as the next request's content 2026-10-15 12:28:09 -07:00
YHH 5516d5a7d8 Merge pull request #13331 from esengine/fix/context-card-guard
test(studio): scope the context-card hover guard to the context anchor
2026-10-15 12:18:39 -07:00
esengine d10871190c test(studio): scope the context-card hover guard to the context anchor 2026-10-15 12:09:08 -07:00
YHH ccdd8a721f Merge pull request #13330 from esengine/feat/session-overview
feat(studio): a session overview behind the cache meter, each figure beside what it counts
2026-10-15 12:03:58 -07:00
esengine 941b66d0fc feat(studio): a session overview behind the cache meter, each figure beside what it counts 2026-10-15 11:49:28 -07:00
YHH 008d5c5e35 Merge pull request #13329 from esengine/test/lazy-swap-poll
test(plugin): wait for the registry swap, not just the server
2026-10-15 11:41:33 -07:00
YHH 18abe2b8b0 Merge pull request #13328 from esengine/feat/session-usage-status
feat(serve): /status carries the session's usage by source, from the cost ledger
2026-10-15 11:37:57 -07:00
esengine e43caacac5 test(plugin): wait for the registry swap, not just the server 2026-10-15 11:30:17 -07:00
YHH b3056b2326 Merge pull request #13327 from esengine/fix/scroll-jump-ends
fix(studio): the jump buttons go straight to the ends; Shift+click steps through expanded blocks
2026-10-15 11:12:16 -07:00
esengine 4f2a495578 feat(serve): /status carries the session's usage by source, from the cost ledger 2026-10-15 11:10:37 -07:00
esengine d3e45aff25 fix(studio): the jump buttons go straight to the ends; Shift+click steps through expanded blocks 2026-10-15 10:59:38 -07:00
YHH 0fe09f3a1d Merge pull request #13323 from esengine/fix/bound-taskkill
fix(proc): bound the taskkill a Windows stop depends on
2026-10-15 10:56:47 -07:00
YHH 3d18fee663 Merge pull request #13325 from esengine/fix/steer-resend-dedupe
fix(inbox): the same words sent again while the first copy waits are not another item
2026-10-15 10:54:30 -07:00
esengine ee9e753baf style(tools): range over SplitSeq in the dev server test 2026-10-15 10:47:18 -07:00
esengine da3359c59b fix(inbox): the same words sent again while the first copy waits are not another item 2026-10-15 10:42:29 -07:00
YHH d7f0055d5e Merge pull request #13267 from esengine/feat/13228-browser-tab-rename
feat(studio): rename a built-in browser tab by double-clicking it
2026-10-15 10:31:29 -07:00
YHH f5a010780c Merge pull request #13324 from esengine/fix/skill-update-record-heal
fix(skills): retry a failed update record and let the next update heal it
2026-10-15 10:21:46 -07:00
esengine fbfdf8bdbe feat(studio): rename a built-in browser tab by double-clicking it 2026-10-15 10:20:57 -07:00
esengine b238636ce4 fix(skills): retry a failed update record and let the next update heal it 2026-10-15 10:10:10 -07:00
YHH a6b496de58 Merge pull request #13322 from esengine/fix/job-stop-in-progress
fix(jobs): say when a background job's stop is already under way, and refresh the list after a refused stop
2026-10-15 10:09:03 -07:00
esengine 9edb081dab test(tools): a dev server's process tree ends on Stop and on kill_shell, in both Windows shells 2026-10-15 10:05:54 -07:00
esengine 95ec1dd78b fix(proc): bound the taskkill a stop depends on 2026-10-15 10:02:55 -07:00
esengine 421dd778cd test(proc): a hung taskkill must not hold the stop 2026-10-15 10:01:04 -07:00
esengine 0c199e0c25 fix(jobs): say when a background job's stop is already under way, and refresh the list after a refused stop 2026-10-15 09:54:32 -07:00
YHH 878baaae07 Merge pull request #13321 from esengine/feat/skill-source-update
feat(skills): update an installed skill from its source folder, after previewing the change
2026-10-15 09:53:36 -07:00
esengine 373c1e773c feat(skills): update an installed skill from its source folder, after previewing the change 2026-10-15 09:38:45 -07:00
YHH 9bd7e7f104 Merge pull request #13317 from esengine/feat/skill-source-lock
feat(skills): remember where an installed skill came from, and say whether it is still in step
2026-10-15 09:33:34 -07:00
YHH 824975d344 Merge pull request #13313 from esengine/feat/update-mode
feat(update): an update mode setting, with a background notify tier
2026-10-15 09:29:06 -07:00
esengine 04c74845ea feat(skills): remember where an installed skill came from, and say whether it is still in step 2026-10-15 09:24:32 -07:00
esengine 65192c35a5 test(config): the update mode survives a desktop save 2026-10-15 09:19:20 -07:00
YHH c39c9e2155 Merge pull request #13316 from esengine/docs/release-note-gaps
docs(release-notes): fragments for four merged changes that had none
2026-10-15 09:07:08 -07:00
YHH 8a36ceab41 Merge pull request #13314 from xxoingr/docs/guide-rewind-confirm-key
docs(guide): list the rewind picker's partial-coverage confirmation
2026-10-15 09:04:16 -07:00
YHH b9b482df5e Merge pull request #13315 from esengine/fix/skill-store-grant-refusal
fix(config): the skills folder refuses a write grant with its own identity and the way forward
2026-10-15 09:03:39 -07:00
esengine d2f075edeb docs(release-notes): fragments for four merged changes that had none 2026-10-15 08:57:30 -07:00
SivanCola 1d90810a02 feat(studio): configure session worktrees and unify composer menus
Problem: concurrent tasks need separate code directories rooted at a chosen
branch, and composer configuration menus use inconsistent visual scales.

Root cause: isolated workspaces only started from HEAD and their entry was
outside the new-conversation composer. Menu surfaces had independent styles.

Fix: stage worktree and exact base-ref selection before the first submit,
carry draft attachments through confined roots, preserve session settings,
reject stale preparation and roll back unpublished worktrees on failure.
Keep existing conversations and older servers on the branch control.
Share composer menu surfaces, moderate 15px labels, 13px descriptions and
neutral selection states across mode, workspace, permissions, model and effort.
Move workspace controls below the input card beside runtime statistics, retain
branch labels in narrow panes, and wrap model/effort at the smallest widths.

Verification: 4831 frontend tests; TypeScript and production build; real Git
worktree/source/refusal and attachment confinement tests; serve, control,
builtin and boot suites; go vet, make lint and make check. Browser interaction
and light/dark responsive captures are kept outside the repository.

Fixes #13264
2026-10-15 23:56:52 +08:00
esengine 3336055f25 fix(config): the skills folder refuses a write grant with its own identity and the way forward 2026-10-15 08:53:21 -07:00
xxoingr 65d5a9b8ed docs(guide): list the rewind picker's partial-coverage confirmation
A file restore the checkpoint covers only partly stops for one more
answer, taken with Enter or y (the panel's own hint says so); the key
table in both guides listed neither the step nor y. The new test pins
that step: it does not commit before the answer, Esc steps back without
restoring, and Enter or y commits.
2026-10-15 23:50:55 +08:00
esengine 5e45f2481a feat(update): an update mode setting, with a background notify tier 2026-10-15 08:40:28 -07:00
YHH c8bf792009 Merge pull request #13310 from esengine/fix/scrolljump-quiet
fix(studio): quieter jump-to-top/bottom buttons
2026-10-15 08:29:20 -07:00
YHH 3fd20922ce Merge pull request #13309 from esengine/test/v5-import-markers
test(migration): 24-digit by-id sessions import beside empty legacy markers
2026-10-15 08:25:07 -07:00
YHH 879debe477 Merge pull request #13308 from esengine/test/cancel-hung-gateway
test(openai): cancelling ends a stream wherever the gateway goes silent
2026-10-15 08:22:35 -07:00
esengine bdf9629c4b fix(studio): quieter jump-to-top/bottom buttons 2026-10-15 08:18:28 -07:00
YHH 23fb0e3b3d Merge pull request #13307 from esengine/fix/13302-plan-chip-right
fix(studio): keep the plan chip at the right edge of the tab row
2026-10-15 08:14:42 -07:00
esengine fc0ddd5082 test(migration): 24-digit by-id sessions import beside empty legacy markers 2026-10-15 08:14:13 -07:00
YHH 8d5b0e79f5 Merge pull request #13282 from esengine/feat/13276-model-prices
feat(providers): per-model price entry in the connection editor
2026-10-15 08:13:31 -07:00
esengine 786395c23e test(openai): cancelling ends a stream wherever the gateway goes silent 2026-10-15 08:11:00 -07:00
YHH 8d83e5322f Merge pull request #13306 from esengine/fix/update-stall-fact
fix(update): say when a download stalls and keeps stalling
2026-10-15 08:09:32 -07:00
esengine b610ed563d fix(studio): keep the plan chip at the right edge of the tab row 2026-10-15 08:03:31 -07:00
YHH c671e7aee7 Merge pull request #13298 from esengine/fix/openwith-labels
fix(studio): translate the open-with names and drop the duplicate file manager entry
2026-10-15 07:59:41 -07:00
esengine cf0b55cbf6 fix(update): say when a download stalls and keeps stalling 2026-10-15 07:58:51 -07:00
YHH 3536abdb11 Merge pull request #13287 from esengine/feat/13258-mirror-reply-images
feat(feedback-sync): bring a mirrored reply comment up to date when its images change
2026-10-15 07:57:50 -07:00
esengine 8d76564db6 test(studio): tolerate the model id appearing in its price row 2026-10-15 07:54:25 -07:00
YHH 86b7f399ba Merge branch 'studio' into feat/13276-model-prices 2026-10-15 07:52:04 -07:00
esengine 28e8874cb4 fix(studio): translate the open-with names and drop the duplicate file manager entry 2026-10-15 07:49:37 -07:00
YHH 723f0fabcf Merge pull request #13277 from esengine/feat/13247-shell-args
feat(shell): launcher arguments for a pinned shell (wsl.exe -d <distro> -- /bin/bash)
2026-10-15 07:49:32 -07:00
YHH 81742a513c Merge pull request #13300 from esengine/fix/13293-unproven-ask
fix(agent): tell the turn what discharges a change the host could not read
2026-10-15 07:43:30 -07:00
esengine 369fbf83b4 feat(shell): launcher arguments for a pinned shell (wsl.exe -d <distro> -- /bin/bash) 2026-10-15 07:35:36 -07:00
YHH 465882cad6 Merge pull request #13296 from esengine/fix/legacy-import-24hex-ids
fix(sessionstore): import 1.x Desktop sessions whose ids are 24 digits
2026-10-15 07:34:22 -07:00
esengine ece5895330 fix(agent): tell the turn what discharges a change the host could not read 2026-10-15 07:32:49 -07:00
esengine 1be3ce4c17 fix(sessionstore): import 1.x Desktop sessions whose ids are 24 digits 2026-10-15 07:23:45 -07:00
YHH 6891c67dc7 Merge pull request #13241 from chuiwenwei/fix/sessionstore-torn-checkpoint-replay
fix(sessionstore): keep the turns a torn checkpoint still decodes
2026-10-15 07:22:45 -07:00
YHH 6bcb1b3f6a Merge pull request #13238 from chuiwenwei/fix/context-batch-failed-runs-not-scored
fix(context-retrieval): stop scoring runs that never produced a transcript
2026-10-15 07:19:54 -07:00
YHH cfb6e5436d Merge pull request #13289 from esengine/feat/13284-compaction-remains
feat(compaction): say what a fold still leaves behind when it is a large share of the window
2026-10-15 07:11:31 -07:00
esengine c9d5f416e6 feat(compaction): say what a fold still leaves behind when it is a large share of the window 2026-10-15 07:01:28 -07:00
YHH 41a6830338 Merge pull request #13285 from esengine/fix/feedback-capture-teardown
fix(studio): stop the window capture quietly when its dialog is already gone
2026-10-15 06:52:50 -07:00
esengine a8d4af21e8 feat(feedback-sync): bring a mirrored reply comment up to date when its images change 2026-10-15 06:50:06 -07:00
YHH 9d1e1387fa Merge pull request #13283 from esengine/fix/optimistic-test-userconfig
test(boot): write the optimistic-claims test's user config where the platform reads it
2026-10-15 06:48:24 -07:00
esengine 86acd819a4 fix(studio): stop the window capture quietly when its dialog is already gone 2026-10-15 06:43:17 -07:00
YHH 457f44d28b Merge pull request #13265 from esengine/feat/13227-open-with-header
feat(studio): open the project in other programs from the file panel
2026-10-15 06:40:48 -07:00
esengine 63599d05ec test(boot): write the optimistic-claims test's user config where the platform reads it 2026-10-15 06:37:14 -07:00
YHH 5df01bceb6 Merge pull request #13201 from chuiwenwei/fix/memory-expired-pinned-guidance
fix(memory): keep expired facts out of pinned guidance
2026-10-15 06:36:55 -07:00
esengine 20e70f1e49 feat(providers): per-model price entry in the connection editor 2026-10-15 06:31:54 -07:00
YHH 8deaa510f0 Merge pull request #13281 from esengine/feat/13274-subagent-claims-optimistic
feat(delegation): optimistic claims for sub-agents that declare no write_paths
2026-10-15 06:23:31 -07:00
YHH 9911440dba Merge pull request #13280 from esengine/feat/13212-thinking-preview
feat(studio): optional first-sentence preview beside folded thinking
2026-10-15 06:11:41 -07:00
esengine 30b102ea55 feat(delegation): optimistic claims for sub-agents that declare no write_paths 2026-10-15 06:09:33 -07:00
esengine 236520bc6d feat(studio): optional first-sentence preview beside folded thinking 2026-10-15 06:01:38 -07:00
YHH 0bbc7aa3fe Merge pull request #13279 from esengine/fix/13275-isolated-config-locks
fix(config): keep config edit locks inside an isolated Reasonix home
2026-10-15 05:49:40 -07:00
esengine 143c407f8c fix(config): keep config edit locks inside an isolated Reasonix home 2026-10-15 05:41:12 -07:00
YHH 43b70aeab4 Merge pull request #13272 from esengine/fix/13269-denied-capability-message
fix(agent): say why a write_paths sub-agent may not run a capability, and what to do
2026-10-15 05:18:09 -07:00
esengine ba5ae25730 Merge remote-tracking branch 'origin/studio' into feat/13227-open-with-header
# Conflicts:
#	desktop/frontend-next/src/i18n/en.ts
#	desktop/frontend-next/src/ui/WorkbenchFileBar.tsx
#	desktop/frontend-next/src/ui/WorkbenchPanel.tsx
2026-10-15 05:12:31 -07:00
YHH 4d752dbe46 Merge pull request #13263 from esengine/feat/13255-file-reveal
feat(studio): show the open file in the file manager from its toolbar
2026-10-15 05:09:15 -07:00
esengine d546028843 fix(agent): say why a write_paths sub-agent may not run a capability, and what to do 2026-10-15 05:06:55 -07:00
YHH e89f4b86c3 Merge pull request #13271 from esengine/feat/13264-worktree-base
feat(worktree): create an isolated copy from a chosen branch
2026-10-15 05:00:31 -07:00
YHH f133f0354b Merge pull request #13270 from esengine/feat/13258-reply-images-ui
feat(feedback): replies can carry screenshots
2026-10-15 04:54:48 -07:00
esengine b6fdbb89dd feat(worktree): create an isolated copy from a chosen branch 2026-10-15 04:50:49 -07:00
esengine 856f2fd3f9 feat(feedback): replies can carry screenshots 2026-10-15 04:35:47 -07:00
YHH b1ca562c42 Merge pull request #13257 from esengine/feat/13254-tab-pip-motion
feat(studio): the working session's tab dot breathes
2026-10-15 04:29:24 -07:00
esengine 31afb4a8e6 refactor(studio): the unread mark has one form now that the tab dot carries it 2026-10-15 04:20:23 -07:00
YHH 4ad336d7e7 Merge pull request #13266 from esengine/fix/13259-composer-toolbar
fix(studio): keep the composer toolbar on one line in a narrow conversation column
2026-10-15 04:18:06 -07:00
esengine 349281ede0 fix(studio): keep the composer toolbar on one line in a narrow conversation column 2026-10-15 04:07:13 -07:00
esengine 4974aa9755 feat(studio): open the project in other programs from the file panel 2026-10-15 04:06:24 -07:00
esengine aab5763204 feat(studio): an unread finished session is shown by the tab dot itself 2026-10-15 03:32:17 -07:00
esengine 59996652db feat(studio): show the open file in the file manager from its toolbar 2026-10-15 03:27:56 -07:00
xxoingr c02541014b fix(cli): stop offering the retired light execution setting
agentpreset.Normalize maps "light" and "economy" to balanced, and All()
lists balanced and delivery; light's own policy no longer exists. The CLI
still put light forward as a choice, in seven hand-written strings: the
--preset help of run, tui and serve, the --profile deprecation warning of
each, and parseRuntimeProfile's refusal ("want light, balanced, or
delivery"). The guides went further and described a Light policy (direct-
first planning, targeted verification) that nothing enforces, so a user
who picked it got balanced without being told.

The seven strings now come from agentpreset.All() through livePresets in
preset_flags.go, and the docs (GUIDE, GUIDE.zh-CN, CLI.zh-CN, TOOL_CONTRACT)
offer balanced and delivery, drop the Light policy bullet, and say the
retired names select balanced. light and economy still parse.
2026-10-15 18:27:31 +08:00
xxoingr dcc5616f94 test(boot): start the scripted thought's pause after the host takes it
TestEffectThinkingTimeIsKeptWithTheTurn failed in the race job on studio
(run 38635511666): "thought kept 46 ms, framed 46 ms; want both at least
50". The scripted provider wrote the reasoning chunk into a buffered
channel and began its 60 ms pause at once, while the host times the
thought from when it takes that chunk; a host that reads the first chunk
late measures less than the pause. Returning the stream 30 ms late
reproduces it (29 ms measured).

The channel is now unbuffered, so the pause starts only after the host has
taken the reasoning chunk and the measured thought cannot be shorter than
it.
2026-10-15 18:18:07 +08:00
xxoingr c66e7f5e17 fix(filelock): take a free lock even after the caller's deadline has passed
acquireLocal waited on the in-process token and ctx.Done in one select.
When both were ready (a lock nobody held, and a deadline already spent)
select picked one at random, so half the time Acquire refused a free lock
with the context error. The file-lock half of acquire already tries once
before it looks at ctx; the in-process half now takes an available token
first too. A held lock still fails at once once the deadline has passed.

This is the failure behind the unsealed evidence bundles on Windows CI.
TestPlanCriterionIsVerifiedByItsStepsCommands (studio run 38664267053)
reported "bundle not sealed: failure trusted_state.unwritable, contract
failure """ after 3.09 s: sealShadowBundle's two-second sealWait covers
the contract seal and object writes as well as the lock wait, so slow
writes spent it, and the bundle's own Append then met a free stream lock
with an expired ctx. With the first write slowed by 2.2 s, 1 of 6 turns
ended unsealed with that exact signature before this change, 0 of 6 after.
2026-10-15 18:14:22 +08:00
esengine b44514d8b1 feat(studio): the working session's tab dot breathes 2026-10-15 03:02:04 -07:00
YHH d7ce590b16 Merge pull request #13218 from esengine/fix/13213-stale-page
feat(studio): a hosted page left open across a deploy says it is not the latest build
2026-10-15 03:01:53 -07:00
YHH 92f8237356 Merge branch 'studio' into fix/memory-expired-pinned-guidance 2026-10-15 02:56:05 -07:00
YHH 20c01d470a Merge pull request #13256 from esengine/fix/13013-fold-unseen-changes
fix(agent): do not refuse a fold for changes the summarizer was never shown
2026-10-15 02:39:49 -07:00
esengine ed488806dc fix(agent): do not refuse a fold for changes the summarizer was never shown 2026-10-15 02:29:47 -07:00
YHH 2ca2a9b53c Merge pull request #13216 from esengine/feat/13210-ask-countdown
feat(ask): show the countdown to an ask-timeout skip and stop it when the question is taken over
2026-10-15 01:40:34 -07:00
YHH 8372c30ed1 Merge pull request #13224 from esengine/ci/let-studio-pushes-finish
ci: let a push to studio finish instead of cancelling the run before it
2026-10-15 01:32:30 -07:00
esengine dbf09924f9 feat(ask): show the countdown to an ask-timeout skip and stop it when the question is taken over 2026-10-15 01:29:16 -07:00
YHH a563a06e24 Merge pull request #13243 from esengine/feat/feedback-account-link
feat(feedback): link an install to the signed-in account only when the person asks
2026-10-15 01:24:12 -07:00
esengine e96af1ff8d feat(feedback): link an install to the signed-in account only when the person asks 2026-10-15 01:16:25 -07:00
YHH 201424f7f7 Merge pull request #13234 from par73e/fix/12780-workspace-branch-latency
perf(studio): reduce latency after selecting a Git branch
2026-10-15 00:51:47 -07:00
YHH f8223c51b4 Merge pull request #13225 from esengine/feat/13221-delete-with-copies
feat(studio): deleting a conversation can take its recovery copies along
2026-10-15 00:50:45 -07:00
YHH 0290335f25 Merge pull request #13203 from chuiwenwei/fix/memory-recall-freshness
fix(memory): re-check a delivered fact by reading its own file, not its stat
2026-10-15 00:50:00 -07:00
Will c438c5532c fix(sessionstore): recover only a torn final checkpoint record
Keeping the decoded prefix across ANY decode error dropped every record after
a mid-file break, and that truncated prefix then competed as a checkpoint that
extends the replay. Damage before the last record now refuses the checkpoint.
2026-10-15 15:18:43 +08:00
Will 9044392d84 fix(session): audit every automatic-recall suppression identically
recallTail had three outcomes and only two reached the audit channel: a
carried memory note and a normal recall each recorded a content-free
decision, while the retrieval ablation switch returned silently and left
lastRecall pointing at an earlier turn. A trajectory could not tell
"retrieval switched off" from "recall found nothing" from "suppressed by a
carried note".

All three now record a RecallResult and emit one memory-recall audit. The
identity travels as a typed memory.SuppressionCode on its own field, from
RecallResult through event.MemoryRecallAudit into the trajectory record's
suppression_code. The prose sentence stays exactly as it was, for readers
only: consumers match the typed field and never split the code out of the
text, so wording cannot become load-bearing again. The new field is additive
and omitempty, so existing records and batches decode unchanged.

The ablated turn also records lastRecall, matching the note path: the
diagnostic surface is defined as the last real turn's decision, and that
turn did decide not to retrieve.
2026-10-15 15:13:33 +08:00
Will cd355b6e4a fix(context-retrieval): keep failed runs out of the boundary report
reportBoundaries folded every row into byKey, so a run that never produced a
transcript re-entered the cue-boundary table as a zero measurement after the
funnel, dose and reformulation exits had excluded it. Skip failed rows, count
them, and say scored=0 with the failed count when nothing is left to compare.
2026-10-15 15:12:52 +08:00
Will 0485537024 fix(context-retrieval): stop scoring runs that never produced a transcript
A run that failed before its first model turn was appended as a zero-valued
contextMetrics, so results.jsonl gained an empty row, summarize counted it in
Scored and its stage tables, and batch.json reported it as completed with an
empty task id. Stamp such a run with its task, arm and NoEvidence stage, mark it
failed, and keep it out of every statistic while counting it in the manifest as
failed_runs.
2026-10-15 15:03:53 +08:00
par73e 416dd550d2 perf(studio): reduce latency after selecting a Git branch 2026-10-15 14:56:01 +08:00
Will c8054adf8e fix(checkpoint): keep turn numbering above unreadable checkpoint files
load() dropped a turn file that existed but would not read, so a resumed
session numbered the next turn below it and Begin overwrote the only copy
of that snapshot. The store now reserves those turn numbers in memory:
NextTurn() counts them and UnreadableTurns() names them, while Bounds()
and List() still expose only readable checkpoints. No new on-disk state.
2026-10-15 14:51:40 +08:00
Will a345688087 test(checkpoint): pin turn-number reuse after an unreadable checkpoint
A persisted turn-N.json that cannot be read is dropped by load() exactly
like an absent one, so NextTurn() restarts below it and the next Begin
overwrites the only copy. This test operates on a real truncated file
written by the store's own persist path.
2026-10-15 14:51:37 +08:00
Will ac625731ba fix(sessionstore): keep the turns a torn checkpoint still decodes
checkpointExtendingReplay read the .jsonl checkpoint with the strict loader
and folded a decode error into "this checkpoint does not extend the log".
The in-place append in appendSessionDisplayReadModel is exactly the write a
crash leaves half done, so the torn checkpoint's own intact lines were
discarded with it: resolveReplayedMessages then returned the shortened
replay with err == nil, and the next save rewrote both the checkpoint and
the log from that shorter transcript. Every turn the checkpoint still held
was lost permanently.

Decode tolerantly the way transformAndCopyJsonl already does, and keep the
existing prefix checks, so the tolerant result is adopted only when it
still extends the replayed prefix. A checkpoint that cannot yield a usable
prefix (unreadable, shorter, contradicting) keeps today's "does not extend"
behaviour; there is no third state.
2026-10-15 14:47:15 +08:00
Will ea10a83fd0 fix(memory): track the remembered fact and settle a reloaded change
Two gaps in the delivered-fact re-check this branch added:

- applyWrite kept the pre-reload stamps of the facts the model already
  holds, so a change that one reload had settled was re-detected by every
  later recall: the snapshot reloaded and the whole store was re-tokenized
  on each turn until a recall returned hits. Re-stamp the kept facts
  against the snapshot just loaded, dropping what it found gone, so the
  steady state stays one read of each delivered fact's own file. The
  pre-branch code cleared the set on reload, which is why this is not a
  new promise.

- The remember tool's typed Memory was discarded at the queue: the note
  carries prose and the controller must not parse it back into memory, so
  a fact the model wrote itself was absent from the delivered set and an
  external edit before the next recall served the stale session snapshot.
  memory.Queue now carries the typed facts the tool wrote, remember passes
  result.Memory, and the controller records it as delivered. The model's
  own write still queues no turn-tail note.

Adds the delivery/change matrix (100 cells over delivery x change time x
change shape) with the reload-count companion, and the provider-boundary
regression for the remember path.
2026-10-15 14:36:54 +08:00
Will 2c5ed024f5 fix(memory): re-check a delivered fact by reading its own file, not its stat
Problem: a fact saved through Controller.SaveMemory and then edited outside the
session is still recalled from the stale snapshot on the next turn; the per-turn
re-check that catches external edits compared each recalled fact with a stat, so
a rewrite that preserved the file's byte length and modification time stayed
invisible and the model kept reading the stale body; and the identity read behind
that stat, Store.Read, resolves a name by listing the whole store directory and
parsing every fact in it.

Root cause: the re-check set held only the previous recall's hits. A save queues
a turn-tail note, and a note suppresses recall for that turn, so a just-saved
fact was never a hit and so was never re-checked. Its check also rested on size
plus modification time, which is not semantic equality -- the repository's
context-projection rule says freshness detection must not rest on mtime.

Fix: baseline the facts the previous turn handed the model -- the previous
recall's hits, plus the facts a write note carried -- with a digest of each
fact's own file, read once at the canonical path its scope and slug name. Before
a recall tail is built those files are re-read directly and their digests
compared: different bytes reload the snapshot, identical bytes never do. The
stamp carries no size and no modification time, so no stat result can decide
that a fact is unchanged; the bytes are the semantic version because they are
exactly what the renderer turns into the model-visible fact. Store.Read stays as
the fallback for a fact whose file is not where its name says.

Cost: one read of each delivered fact's own file per recall, plus one when it is
baselined and, on that baseline, one parse of the same file to confirm the stamp
describes the delivered fact; a snapshot reload only when a delivered fact's
bytes differ or its file is gone. A formatting-only external rewrite re-reads the
fact into the snapshot even though the parsed fact is equal -- the safe
direction. A fact edited into relevance that the previous turn never handed over
stays invisible until the session reloads; no full scan, which
docs/SESSION_MEMORY_RETRIEVAL.md now states, along with the no-hit retention and
the identity fallback.

This extends the merged fix in 99fe3dbbf, which covers the recall-hit path only.

Review round 1 fixed two regressions in this baseline:

- A fact edited between the snapshot load and its first recall was tracked with
  the edited file's own digest, so the stamp described bytes the turn never
  carried and SameContent() then called the stale snapshot body unchanged
  forever. stampFact now marks a fact tracked only when the one file it just
  digested re-parses to the very fact being delivered (memory.FactAt + sameFact,
  the comparison the untracked fallback already used); otherwise it records the
  fact untracked and the identity read decides. The probe stays on that one file,
  so no listing and no second scan come back.

- recordDelivered overwrote the delivered set on every recall, so a turn that hit
  nothing erased the facts the previous turn was still holding and the next
  recall had nothing left to re-check. It now keeps the previous set when this
  recall returned no hits, while still appending the facts a write note carried.

Verification: a same-size/preserved-mtime rewrite is asserted first at the
Compose boundary (red on the stat-based check) and then at the true provider
boundary, through the real Build stack with a recording provider that shows the
refreshed body in the request the executor actually sends. The tracked path's
freedom from directory listing is asserted by arming the memory package's
read-dir seam to fail while stamping and while re-checking; FactAt is pinned to
the same single file. Both new tests go red with the stat-based conclusion
restored behind the same API and green with it removed.
TestRecallReloadsAFactEditedBeforeItsFirstRecall (seed, load, rewrite, Compose
twice) and TestRecallKeepsCheckingAfterATurnWithNoHits (recall A, an unrelated
no-hit turn, edit A, ask again) are red on the pre-fix behaviour and green now.
Each review fix was reverted alone with both tests kept: the first test goes red
and the second passes, and the reverse for the other. Go tests, vet, gofmt,
golangci-lint (0 issues) and make check (repolint: clean).

Refs #12948
2026-10-15 14:28:17 +08:00
YHH 61337d9940 Merge pull request #13226 from esengine/fix/13217-retry-inconclusive-open
fix(git): give a session whose git did not answer one second attempt at its repository
2026-10-14 23:15:59 -07:00
esengine d83661f9b0 fix(git): give a session whose git did not answer one second attempt at its repository 2026-10-14 23:04:02 -07:00
YHH b7eba48e91 Merge pull request #13223 from esengine/fix/13221-delete-with-copies
fix(studio): a refused settings save says where to see and repair the broken config
2026-10-14 23:03:54 -07:00
YHH d97c576e03 Merge pull request #13222 from esengine/fix/13217-git-absence-reason
fix(git): say why a folder with a .git has no repository identity
2026-10-14 22:48:32 -07:00
esengine ddc48fd74b fix(studio): a refused settings save says where to see and repair the broken config 2026-10-14 22:47:46 -07:00
esengine 35ba5eb42d ci: let a push to studio finish instead of cancelling the run before it 2026-10-14 22:46:24 -07:00
esengine d8dbd38070 feat(studio): deleting a conversation can take its recovery copies along 2026-10-14 22:46:17 -07:00
YHH 1d1d8730ff Merge branch 'studio' into fix/memory-expired-pinned-guidance 2026-10-14 22:44:52 -07:00
esengine 38e7d7a628 fix(git): say why a folder with a .git has no repository identity 2026-10-14 22:38:53 -07:00
esengine adb00dd7fa feat(studio): a hosted page left open across a deploy says it is not the latest build 2026-10-14 22:19:52 -07:00
YHH a20d34351f Merge pull request #13211 from esengine/test/request-cost-budget
test(serve): budget the plugin package parses a hot endpoint may cost
2026-10-14 21:44:22 -07:00
esengine 2e8634d735 test(serve): budget the plugin package parses a hot endpoint may cost 2026-10-14 21:33:05 -07:00
YHH b58734ee4e Merge pull request #13209 from esengine/perf/complete-args-loads
perf(control): completing command arguments loads each config once
2026-10-14 21:31:14 -07:00
YHH 87823024e6 Merge pull request #13208 from esengine/fix/13204-close-unknown-runtime
fix(serve): closing a runtime the hub no longer holds is typed, and the port treats it as closed
2026-10-14 21:25:54 -07:00
YHH 557a387518 Merge pull request #13206 from esengine/feat/13108-task-slots
fix(mcp): promoted task jobs do not hold the inline task slots
2026-10-14 21:22:43 -07:00
YHH 0437ca8982 Merge pull request #13207 from esengine/perf/complete-abort
perf(studio): abandon a completion request a newer keystroke has made stale
2026-10-14 21:21:38 -07:00
esengine 8feb1adb9b perf(control): completing command arguments loads each config once 2026-10-14 21:19:00 -07:00
esengine 2a3d9b7d61 fix(serve): closing a runtime the hub no longer holds is typed, and the port treats it as closed 2026-10-14 21:17:23 -07:00
esengine 2b191c3dc5 perf(studio): abandon a completion request a newer keystroke has made stale 2026-10-14 21:12:15 -07:00
esengine 28ccd2db41 fix(mcp): promoted task jobs do not hold the inline task slots 2026-10-14 21:07:33 -07:00
YHH f87b34184a Merge pull request #13200 from chuiwenwei/fix/memory-cross-scope-identity
fix(memory): identify the cross-scope name refusal and hand back both ids
2026-10-14 21:05:42 -07:00
YHH e598d482e7 Merge pull request #13205 from esengine/perf/13197-complete-loads
perf(control): completing a command name reads no package, config or memory data
2026-10-14 20:59:18 -07:00
esengine 38632c72d7 perf(control): completing a command name reads no package, config or memory data 2026-10-14 20:49:53 -07:00
YHH 16936dc139 Merge pull request #13199 from esengine/feat/13195-menu-autoname
feat(studio): auto-name a session from the sidebar menu
2026-10-14 20:49:01 -07:00
YHH c2ebf7bd56 Merge pull request #13202 from esengine/feat/13108-mcp-tasks-job
feat(mcp): continue a task that outran its timeout as a background job
2026-10-14 20:48:11 -07:00
esengine efb2418dfa feat(mcp): continue a task that outran its timeout as a background job 2026-10-14 20:41:05 -07:00
esengine d0d73fcc0f feat(studio): auto-name a session from the sidebar menu 2026-10-14 20:39:27 -07:00
YHH b47f0e45d2 Merge pull request #13198 from esengine/fix/13194-flow-top-feather
fix(studio): fade the tab strip's lower edge over a wallpaper
2026-10-14 20:37:05 -07:00
esengine 1cd7705536 fix(studio): fade the tab strip's lower edge over a wallpaper 2026-10-14 20:27:51 -07:00
YHH 95888d14fd Merge pull request #13193 from esengine/feat/13108-mcp-tasks-sync
feat(mcp): follow a tool call the server answers as a task to its result
2026-10-14 20:01:58 -07:00
YHH e506bf7de2 Merge pull request #13190 from esengine/feat/13110-skill-dir-install
feat(skill): read and show the specification's license, compatibility and metadata
2026-10-14 19:55:55 -07:00
esengine 091995c737 fix(skill): bound the served license, compatibility and metadata 2026-10-14 19:40:29 -07:00
YHH 4234059fa6 Merge pull request #13192 from esengine/fix/13048-feedback-entry
fix(studio): draw the sidebar's feedback entry in the accent colour
2026-10-14 19:39:54 -07:00
Will d2464b59c4 fix(memory): keep expired facts out of pinned guidance
An explicit expiry is a hard boundary: the fact index and automatic recall drop
the fact past it, and the memory tool paths report it as expired. Pinned guidance
did not check it, so an expired body kept riding the stable prefix, an expired
project fact kept shadowing the live global guidance it used to override, and an
expired body kept consuming the pinned budget that bounds what the prefix sends.

Both places now skip facts that are past their expiry, so the prefix, the shadow
rule and the budget agree with the index and recall. This covers facts that are
already expired when the session loads them; withdrawing a pinned body that
crosses its expiry mid-session is a separate prefix question.
2026-10-15 10:37:42 +08:00
Will bbda70c670 fix(memory): identify the cross-scope name refusal and hand back both ids
An unqualified save whose name was active in two scopes was refused with a bare
sentence. A code caller could not tell that refusal from a write failure, and
the model received neither the two ids the name could have meant nor a way to
say which fact it wanted.

The refusal now wraps ErrNameActiveInTwoScopes, so errors.Is separates a
cross-scope name collision from a write failure, and its sentence renders that
code, both active ids with their scopes, and the two ways out: update the fact
you meant by its stable id, or save with a scope-qualified name. The remember
tool returns this error verbatim, and the agent renders it as the model-facing
tool result, so the identity and both ids reach the model without a second,
drifting sentence.

Tests cover the identity and both ids at the store and at the tool, the refusal
leaving both facts at their own bytes and revision, and the two unambiguous
saves (stable id, scope-qualified name) still succeeding. A neighbouring test
pins that a name active in exactly one scope is still updated, not refused.
2026-10-15 10:37:42 +08:00
Will e4f6eb117b fix(memory): refuse an unqualified save whose name is active in two scopes
An unqualified name that was active in another scope was archived after the save
was published, so a failure there left the same name active twice, and the
archiving itself removed a fact the caller never named.

The collision is now reported before anything is written, and the post-commit
archiving is gone.
2026-10-15 10:37:42 +08:00
esengine ad37cd5302 feat(mcp): follow a tool call the server answers as a task to its result 2026-10-14 19:37:41 -07:00
esengine bcddb2963b fix(studio): draw the sidebar's feedback entry in the accent colour 2026-10-14 19:30:25 -07:00
YHH 0e41cd7947 Merge pull request #13189 from esengine/fix/trace-fixture-flake
test(serve): the trace fixtures do not compare two more measured clocks
2026-10-14 19:29:05 -07:00
esengine 02cd4622d9 feat(skill): read and show the specification's license, compatibility and metadata 2026-10-14 19:20:31 -07:00
esengine 29bca265b3 test(serve): the trace fixtures do not compare two more measured clocks 2026-10-14 19:19:39 -07:00
YHH b1c2bc6756 Merge pull request #13187 from esengine/fix/13177-branch-stale-boundary
fix(control): refuse a running-turn branch whose recorded boundary names another prompt
2026-10-14 19:03:08 -07:00
YHH 361e56c298 Merge pull request #13186 from esengine/perf/13176-tree-canonical
perf(serve): do not canonicalise every listed session path in /tree
2026-10-14 18:58:21 -07:00
esengine 25c2b4ec91 fix(control): refuse a running-turn branch whose recorded boundary names another prompt 2026-10-14 18:53:18 -07:00
YHH c05f72c582 Merge pull request #13185 from esengine/feat/13177-branch-closed-turn-while-running
feat(studio): branch a conversation from its latest finished reply while it runs
2026-10-14 18:49:38 -07:00
esengine 0392ffed05 perf(serve): do not canonicalise every listed session path in /tree 2026-10-14 18:49:13 -07:00
YHH c442b018c4 Merge pull request #13183 from esengine/fix/13180-open-error-class
fix(serve): a file that cannot be opened is not reported as missing
2026-10-14 18:40:50 -07:00
esengine e9da355450 feat(studio): branch a conversation from its latest finished reply while it runs 2026-10-14 18:39:33 -07:00
YHH acee6497d3 Merge pull request #13182 from esengine/fix/13179-daily-desc
fix(studio): list the newest day first in the daily usage table
2026-10-14 18:38:56 -07:00
YHH abac8621e9 Merge pull request #13181 from esengine/fix/13178-context-card-hover
fix(studio): keep the context card open while the pointer crosses to it
2026-10-14 18:34:00 -07:00
esengine a39b4b1cac fix(serve): a file that cannot be opened is not reported as missing 2026-10-14 18:31:25 -07:00
esengine 6a3cd375cb fix(studio): list the newest day first in the daily usage table 2026-10-14 18:29:13 -07:00
esengine dffb4787e6 fix(studio): keep the context card open while the pointer crosses to it 2026-10-14 18:24:33 -07:00
YHH 9862aa5f84 Merge pull request #13175 from kevin9327/fix/gitstatus-rename-numstat
fix(gitstatus): count lines on renamed and copied paths
2026-10-14 18:11:12 -07:00
kevin9327 281caed006 fix(gitstatus): count lines on renamed and copied paths
git diff --numstat -z writes a rename or copy as the counts, a trailing
tab, then the old and new paths as their own NUL fields. Trimming that
record removed the tab, so the parser treated it as a short line and
dropped it. Status and Summary then reported no line counts for the
change. The counts stay on the new path, which is the name status lists.
2026-10-15 00:14:07 +00:00
kevin9327 1023ea7bcb test(gitstatus): cover rename and copy numstat records
git diff --numstat -z emits a rename or copy as the counts, a tab, NUL,
the old path, NUL, and the new path. Status and Summary need those counts
on the destination, beside an ordinary edit in the same diff.
2026-10-15 00:14:07 +00:00
YHH 5d784ed6d6 Merge pull request #13174 from esengine/release/2.41.0
docs(release): Studio 2.41.0 notes
2026-10-14 17:07:43 -07:00
esengine 3524a9fb6a docs(release): Studio 2.41.0 notes 2026-10-14 16:57:39 -07:00
YHH 16a07336fa Merge pull request #13173 from esengine/fix/stream-failure-recovery-flake
test(session): bound the stream-failure recovery turns by the hang budget
2026-10-14 15:40:44 -07:00
esengine 372acad69d test(session): bound the stream-failure recovery turns by the hang budget 2026-10-14 15:31:45 -07:00
YHH 0694a186ae Merge pull request #13172 from esengine/sweep/390-leftovers
fix(studio): narrow-width step rows, meter rail and config error block
2026-10-14 15:18:01 -07:00
esengine 690681194a fix(studio): narrow-width step rows, meter rail and config error block 2026-10-14 15:09:35 -07:00
YHH e5f5b75801 Merge pull request #13171 from esengine/docs/frag-13169
docs(release): fragment for the narrow-width layout fixes
2026-10-14 14:48:12 -07:00
YHH ca121eaf9b Merge pull request #13170 from esengine/sweep/tools-args
test(studio): i18n and fault sweeps take a viewport and wait for the settings control
2026-10-14 14:47:25 -07:00
esengine 90effbe6b2 docs(release): fragment for the narrow-width layout fixes 2026-10-14 14:40:43 -07:00
esengine 0d9e678ea8 test(studio): i18n and fault sweeps take a viewport, wait for the settings control and print it 2026-10-14 14:37:04 -07:00
YHH e0ca9a4779 Merge pull request #13169 from esengine/sweep/narrow
fix(studio): narrow-width layout and contrast defects found by the sweep
2026-10-14 14:29:57 -07:00
esengine 47831de6bf fix(studio): narrow-width layout and contrast defects found by the sweep 2026-10-14 14:22:05 -07:00
YHH 1345212f38 Merge pull request #13165 from esengine/fix/bwrap-probe-net
fix(sandbox): probe bubblewrap with the network flag the launch will use
2026-10-14 13:50:56 -07:00
YHH 34ebef5524 Merge pull request #13168 from esengine/perf/11885-effect-tests
test(boot): build SDK examples against the host go build cache
2026-10-14 13:08:40 -07:00
YHH b661446471 Merge pull request #13167 from esengine/docs/frag-13166
docs(release): fragment for keeping a folded paste as a file in the terminal
2026-10-14 13:05:50 -07:00
esengine ddedaffdf0 test(boot): build SDK examples against the host go build cache 2026-10-14 12:56:51 -07:00
esengine 0c45db5968 docs(release): fragment for keeping a folded paste as a file in the terminal 2026-10-14 12:55:33 -07:00
YHH 399e232141 Merge pull request #13166 from esengine/feat/13148-tui-paste
feat(tui): keep a folded paste as a file with Ctrl+G
2026-10-14 12:51:39 -07:00
esengine 108f41e16d feat(tui): keep a folded paste as a file with Ctrl+G 2026-10-14 12:43:24 -07:00
esengine 5418317cf8 test(plugin): pin that a confined MCP starts unconfined when bwrap cannot unshare the network 2026-10-14 12:37:47 -07:00
YHH ce7460d071 Merge pull request #13164 from esengine/docs/guide-allow-devices
docs(guide): document the sandbox device allowlist
2026-10-14 12:34:21 -07:00
esengine cda2915da4 fix(sandbox): probe bubblewrap with the network flag the launch will use 2026-10-14 12:32:52 -07:00
esengine 845edced30 docs(guide): document the sandbox device allowlist 2026-10-14 12:27:00 -07:00
YHH 4636880a69 Merge pull request #13163 from esengine/docs/frag-13162
docs(release): fragment for the keep-a-paste-as-a-file chip action
2026-10-14 12:02:55 -07:00
esengine 6dd6a23388 docs(release): fragment for the keep-a-paste-as-a-file chip action 2026-10-14 11:55:47 -07:00
YHH de12671c0f Merge pull request #13162 from esengine/feat/13148-paste-to-file
feat(studio): keep a long paste as a file on request
2026-10-14 11:37:55 -07:00
esengine d519e83100 test(paste): range over an integer 2026-10-14 11:26:20 -07:00
esengine abf8ec53b2 refactor(paste): move the paste chip's keep action into its own module 2026-10-14 11:26:20 -07:00
esengine 2fa2487e0d feat(paste): style the save action 2026-10-14 11:26:20 -07:00
esengine d6b5e34bc8 feat(paste): keep a long paste as a file on request 2026-10-14 11:26:20 -07:00
esengine 94dc1671c1 wip(paste): keep a long paste as a file on request 2026-10-14 11:26:20 -07:00
YHH 75f2d3c38f Merge pull request #13161 from esengine/feat/13157-allow-devices
feat(sandbox): bind user-allowed host device nodes into the Linux bash sandbox
2026-10-14 11:25:37 -07:00
YHH a7bf4b1205 Merge pull request #13160 from esengine/feat/13140-x25519-fallback
feat(remote): fall back to a software X25519 when the browser has none
2026-10-14 11:22:22 -07:00
esengine 9ed45e1e9f feat(sandbox): bind user-allowed host device nodes into the Linux bash sandbox 2026-10-14 11:11:32 -07:00
esengine 69400e57ac feat(remote): fall back to a software X25519 when the browser has none 2026-10-14 11:06:06 -07:00
YHH adb7bf8cc7 Merge pull request #13159 from esengine/fix/13152-say-which-path-check
fix(serve): resume says whether the transcript is gone, outside, or unreadable and why
2026-10-14 10:54:58 -07:00
YHH b3523730f5 Merge pull request #13158 from esengine/docs/frag-11851
docs(release): fragment for the naming model and per-role effort
2026-10-14 10:50:03 -07:00
YHH 922c1bfe85 Merge pull request #13155 from esengine/feat/13141-ideas-entry
feat(studio): point ideas and questions from the feedback dialog to Discussions
2026-10-14 10:46:18 -07:00
esengine a5e966e98e fix(serve): resume says whether the transcript is gone, outside, or unreadable and why 2026-10-14 10:44:14 -07:00
YHH f690a0537c Merge pull request #13156 from esengine/docs/release-fragments-2-41
docs(release): fragments for the user-visible changes merged since 2.40.0
2026-10-14 10:42:31 -07:00
esengine ffe680d3f5 docs(release): fragment for the naming model and per-role effort 2026-10-14 10:40:08 -07:00
YHH b9a87f8bda Merge pull request #11851 from Guan0923/codex/model-role-preferences
feat(studio): add an independent naming model and per-role reasoning effort
2026-10-14 10:37:43 -07:00
YHH 5247d0acdc Merge pull request #13154 from esengine/fix/oobe-skip-contrast
fix(studio): lift the intro hint and the changed-files counts over AA contrast
2026-10-14 10:37:18 -07:00
esengine 1ff55e8026 docs(release): fragments for the user-visible changes merged since 2.40.0 2026-10-14 10:32:41 -07:00
esengine e788d83765 feat(studio): point ideas and questions from the feedback dialog to Discussions 2026-10-14 10:28:02 -07:00
YHH fd9c4e44dd Merge branch 'studio' into codex/model-role-preferences 2026-10-14 10:23:10 -07:00
esengine 9d34d5ecab fix(studio): lift the intro hint and the changed-files counts over AA contrast 2026-10-14 10:18:47 -07:00
YHH f8c262da03 Merge pull request #13151 from esengine/feat/13148-precheck
feat(compaction): refuse a fold that cannot fit before paying for its summary
2026-10-14 10:04:07 -07:00
YHH be0305d665 Merge pull request #13150 from esengine/feat/13110-spec-name
feat(skill): report front matter that departs from the Agent Skills spec
2026-10-14 09:57:17 -07:00
esengine f5a442c133 feat(compaction): refuse a fold that cannot fit before paying for its summary 2026-10-14 09:55:00 -07:00
esengine 7d15f23c63 feat(skill): report front matter that departs from the Agent Skills spec 2026-10-14 09:47:08 -07:00
YHH 4bee13505c Merge pull request #13149 from esengine/fix/13148-compaction-accept
feat(compaction): say what holds a rejected candidate above the trigger
2026-10-14 09:41:47 -07:00
esengine ce60d21610 feat(compaction): say what holds a rejected candidate above the trigger 2026-10-14 09:33:56 -07:00
YHH 4db0f505d0 Merge pull request #13147 from esengine/fix/12948-recall-stale-fact
fix(memory): re-read the facts the last recall returned before recalling again
2026-10-14 09:29:39 -07:00
YHH bc6f415d98 Merge pull request #13144 from xxoingr/docs/guide-tui-keys-as-built
docs(guide): describe the terminal keys 2.x has
2026-10-14 09:27:59 -07:00
YHH 5d985236c2 Merge pull request #13146 from esengine/feat/bwrap-unrunnable
feat(sandbox): a bwrap that cannot be started is not reported as a denied namespace
2026-10-14 09:25:35 -07:00
esengine 99fe3dbbf1 fix(memory): re-read the facts the last recall returned before recalling again 2026-10-14 09:19:58 -07:00
YHH d7fa4c3b0f Merge pull request #13143 from esengine/feat/13052-context-parts
feat(studio): open a context class to the messages that made it heavy
2026-10-14 09:18:58 -07:00
YHH f1003c74a0 Merge pull request #13139 from esengine/feat/13108-list-ttl
feat(mcp): re-read a tool catalog when the server's ttlMs runs out
2026-10-14 09:10:19 -07:00
YHH 98ebd85258 Merge pull request #13145 from esengine/fix/13140-x25519-fallback
fix(remote): say the browser is too old when it has no X25519
2026-10-14 09:09:31 -07:00
esengine ee27c844d7 feat(sandbox): a bwrap that cannot be started is not reported as a denied namespace 2026-10-14 09:06:03 -07:00
YHH e591109795 Merge pull request #13138 from esengine/feat/bwrap-typed-reason
feat(sandbox): say why bubblewrap is unusable: missing, or installed but denied a namespace
2026-10-14 09:03:16 -07:00
esengine 612f4ad550 feat(mcp): re-read a tool catalog when the server's ttlMs runs out 2026-10-14 09:00:45 -07:00
esengine 7668609b08 fix(remote): say the browser is too old when it has no X25519 2026-10-14 08:59:37 -07:00
xxoingr 717baf5ecd docs(guide): describe the terminal keys 2.x has
The CLI/TUI shortcut tables in docs/GUIDE.md and docs/GUIDE.zh-CN.md still
described four 1.x behaviours the 2.x terminal UI does not have (#12144
lists them as missing):

- Up/Down "navigate queued follow-up feedback" while a turn runs; in 2.x
  they recall history there too (recallAtEdge has no running case), and
  queued items are managed with /queue.
- Esc "un-sends a just-submitted turn before any reply"; escape() cancels a
  running turn, clears input, or leaves ! shell mode, and nothing un-sends.
- Composer text selection by drag with replace-on-type; onMouse ignores a
  left click below the transcript viewport, so only transcript text is
  selectable, and Ctrl+C copies only that.
- The rewind picker's f (fork); rewindActions has b c d s u.

Two tests pin the rows as now written: Up recalls while a turn runs, and
Esc on an empty composer leaves shell mode.
2026-10-14 23:54:03 +08:00
esengine efdbf04694 feat(studio): open a context class to the messages that made it heavy 2026-10-14 08:54:02 -07:00
esengine 7d75708986 feat(sandbox): say why bubblewrap is unusable: missing, or installed but denied a namespace 2026-10-14 08:39:42 -07:00
YHH 33730cb13c Merge pull request #13137 from esengine/feat/13132-row-quick-actions
feat(studio): opt-in hover Archive and Delete buttons on session rows
2026-10-14 08:39:03 -07:00
YHH 258269c30c Merge pull request #13136 from esengine/perf/12533-migrations-once
perf(boot): read the user config once for the startup migrations and leave an untouched config unlocked
2026-10-14 08:29:34 -07:00
esengine 212388b682 feat(studio): opt-in hover Archive and Delete buttons on session rows 2026-10-14 08:28:25 -07:00
YHH 2af6ef5422 Merge pull request #13135 from esengine/feat/13110-allowed-tools-spec
feat(skill): accept space-separated allowed-tools through one shared splitter
2026-10-14 08:24:39 -07:00
esengine cc6c305649 perf(boot): read the user config once for the startup migrations, and leave an untouched config unlocked 2026-10-14 08:21:07 -07:00
YHH 351c1fa9a3 Merge pull request #13128 from esengine/feat/12927-side-session
feat(session): a read-only side question beside the main conversation
2026-10-14 08:18:21 -07:00
esengine ebca87f6ec feat(skill): accept space-separated allowed-tools through one shared splitter 2026-10-14 08:14:23 -07:00
YHH c7715a4a7a Merge pull request #13133 from esengine/fix/13125-missing-file
fix(studio): a requested file that is not there is said so, not opened as a blank preview
2026-10-14 08:04:44 -07:00
esengine d9424dacdc fix(side): refusal copy, dev proxy route and the direct-runner entry for side conversations 2026-10-14 08:03:53 -07:00
esengine c812fbc2e5 feat(session): a read-only side question beside the main conversation 2026-10-14 08:03:48 -07:00
YHH 59fc67f43a Merge pull request #13016 from SivanCola/fix/12950-memory-directory-errors
fix(memory): report unreadable store directories / 修复记忆目录读取错误被忽略
2026-10-14 07:58:17 -07:00
YHH fdee835f9e Merge pull request #13131 from esengine/fix/13129-wallpaper-side
fix(studio): the sidebar footer takes the rail's tint over a wallpaper instead of a plate of its own
2026-10-14 07:56:44 -07:00
esengine ecbbbcf428 fix(studio): a requested file that is not there is said so, not opened as a blank preview 2026-10-14 07:54:22 -07:00
YHH 38620addcc Merge pull request #13130 from esengine/feat/13114-acp-tool-name
feat(acp): name the invoked tool on tool_call updates
2026-10-14 07:47:15 -07:00
esengine 106ee3670a fix(studio): the sidebar footer takes the rail's tint over a wallpaper instead of a plate of its own 2026-10-14 07:46:30 -07:00
esengine 8f137304cf feat(acp): name the invoked tool on tool_call updates 2026-10-14 07:36:33 -07:00
YHH 20e300871f Merge pull request #13127 from esengine/fix/13107-home-root
fix(serve): do not list a launch folder nobody picked and nothing was said in
2026-10-14 07:32:13 -07:00
YHH 2688a5b34b Merge pull request #13123 from xxoingr/test/plugin-oauth-removal-budget
test(plugin): bound the mid-browser OAuth removal by the hang budget
2026-10-14 07:27:53 -07:00
YHH ebe1858ad2 Merge pull request #13122 from xxoingr/test/session-lock-release-budget
test(session): let a released save lock be waited for under load
2026-10-14 07:27:28 -07:00
YHH 6de37f872e Merge pull request #13121 from xxoingr/test/plugin-writable-roots-windows
test(plugin): give the writable-roots host-context tests absolute paths on Windows
2026-10-14 07:27:02 -07:00
esengine b3e683ad75 fix(serve): do not list a launch folder nobody picked and nothing was said in 2026-10-14 07:18:44 -07:00
YHH a12e814476 Merge pull request #13124 from esengine/fix/openwith-test-windows
test(openwith): skip fakes of a POSIX disk on a Windows host
2026-10-14 06:52:46 -07:00
esengine 5c7fc811a1 test(openwith): skip fakes of a POSIX disk on a Windows host 2026-10-14 06:43:23 -07:00
xxoingr c587f9e7dc test(plugin): bound the mid-browser OAuth removal by the hang budget
TestAuthorizeHTTPMCPDoesNotHoldStateLockDuringBrowser failed on Windows
CI (studio run 38416026313) with "removal during browser flow blocked on
OAuth state lock" after 1.09 s. The test runs ReconcileHTTPMCPOAuthAfterRemoval
from inside openURL and gave it one second. AuthorizeHTTPMCP holds no lock
there (captureMCPOAuthGeneration releases its lock before the browser
opens), so a slow removal on a loaded runner read as a held lock. Starting
the removal 1.2 s late reproduces the message.

The wait is now testenv.Budget(t). A lock really held across the browser
is still caught: the removal's own lock wait gives up after five seconds
and returns an error, which fails the test.
2026-10-14 21:39:58 +08:00
xxoingr d2bf651d53 test(session): let a released save lock be waited for under load
TestSaveSnapshotSucceedsWhenCrossProcessFileLockReleasesBeforeDeadline
failed on Windows CI (studio run 38502171680) with "lock session file:
session file lock held" after 1.28 s. A goroutine releases the competing
lock after 30 ms and the save waits at most 500 ms for it; on a loaded
runner the release came later than that. Releasing after 600 ms
reproduces the message.

The save returns as soon as the lock is free, so the wait now is
testenv.Budget(t). TestSaveSnapshotBoundsCrossProcessFileLockWait keeps
its 40 ms, since it asserts that a lock never released makes the save
fail.
2026-10-14 21:33:21 +08:00
xxoingr 64501d34bf test(plugin): give the writable-roots host-context tests absolute paths on Windows
TestHostContextCarriesTheExtraWritableFoldersToLocalServersOnly and
TestHostContextCapsHowManyWritableFoldersAreTold fail on every Windows run
since e6f70622b (studio run 38537039922): their roots are written as
"/data/shared" and "/data/000", which have no drive and so are not
absolute on Windows, and metaWritableRoots drops every root that is not.
The key is never sent and both tests see nil.

The roots are now built from the temp directory's volume, the repo's
usual way to spell an absolute path on any OS. The same change makes
TestHostContextSaysNothingOfWritableFoldersWhenThereAreNone exercise the
workspace exclusion on Windows: its "/w" was dropped as relative there,
so it passed whether or not the workspace was excluded.
2026-10-14 21:30:34 +08:00
YHH ce2a950525 Merge pull request #13112 from esengine/release/2.40.0
docs(release): Studio 2.40.0 notes
2026-10-14 05:55:26 -07:00
esengine f207c88732 docs(release): Studio 2.40.0 notes 2026-10-14 05:46:49 -07:00
YHH e89d7be726 Merge pull request #13101 from esengine/feat/12788-turn-narration
feat(agent): a setting for how much the model says between tool calls
2026-10-14 05:44:34 -07:00
esengine dc0274076d feat(agent): a setting for how much the model says between tool calls 2026-10-14 05:34:37 -07:00
Guan09 24f9b73cd7 feat(studio): add an independent naming model and per-role reasoning effort
Add a separately selectable naming model and adjustable reasoning preferences
for chat roles, with typed roles/efforts responses and user-only role settings.

Keep title generation fixed off with temperature zero and a 60-token budget;
reject explicit title effort instead of storing an unused setting. Use the
configured startup chat model when no naming model is selected, including
servers without a running controller.

Integrate the on-demand naming and rename paths from #11591 with grouped title
provider state. Preserve default-model persistence, per-profile model overrides,
busy-save notices, and inherited effort validation against the executing model.

Share the model-preference row's existing layout declarations to keep the CSS
line count below the studio baseline without changing rendered layout.

Preserve each provider's saved effort when a role has no stored preference;
use the model default only for explicitly selected auto. Add a real boot
assembly effect test for planner and guardian-derived reviewer/evaluator
providers, while leaving subagent effort inheritance unchanged.
2026-10-14 20:27:50 +08:00
YHH 3892810d1d Merge pull request #13100 from esengine/feat/12858-open-with
feat(studio): open a project in another program (#12858)
2026-10-14 05:17:26 -07:00
YHH d33f88bba5 Merge pull request #13104 from esengine/fix/13094-page-assets-fixed-mime
fix(serve): the page's files keep their declared type whatever the machine registered
2026-10-14 05:08:51 -07:00
esengine dcaa53ba07 Merge studio into feat/12858-open-with 2026-10-14 05:02:11 -07:00
YHH faa0d5bc6e Merge pull request #13103 from esengine/feat/12959-shared-meter
feat(studio): a shared meter, with the queue's capacity limits on it
2026-10-14 05:00:48 -07:00
esengine 0423298c95 fix(serve): the page's files keep their declared type whatever the machine registered 2026-10-14 04:59:05 -07:00
esengine 2e5e4f9bec feat(studio): a shared meter, with the queue's capacity limits on it 2026-10-14 04:50:40 -07:00
esengine 71f648e2b6 fix(openwith): resolve links before judging whether a program is inside a project 2026-10-14 04:50:23 -07:00
YHH 3df18bdb5f Merge pull request #13096 from esengine/feat/12736-local-config-export
feat(studio): export and import the configuration as an encrypted file (#12736)
2026-10-14 04:45:55 -07:00
YHH b9db827dd4 Merge pull request #13098 from esengine/feat/fault-report-prefill
feat(studio): report a page defect with its error name and where it came from (#13055)
2026-10-14 04:45:36 -07:00
YHH acd58cdcaf Merge pull request #13091 from esengine/feat/13077-scroll-jump
feat(studio): jump-to-top / jump-to-latest buttons in the conversation (#13077)
2026-10-14 04:45:26 -07:00
esengine 7f60b15571 Merge studio into feat/fault-report-prefill 2026-10-14 04:36:56 -07:00
esengine e6ac8c9add feat(studio): open a project in another program
Refs #12858
2026-10-14 04:32:29 -07:00
YHH 0633035468 Merge pull request #13097 from esengine/feat/12964-hide-legacy-sessions
feat(studio): a setting to keep 1.x sessions out of the sidebar lists
2026-10-14 04:25:29 -07:00
YHH 7b10eefb3d Merge pull request #13095 from esengine/feat/13067-one-workbench-entry
feat(studio): one way into the workbench (#13067)
2026-10-14 04:19:37 -07:00
esengine 298bb73476 test(configbackup): drop a fixture assumption 2026-10-14 04:19:13 -07:00
YHH dbf2764c14 Merge pull request #13093 from esengine/fix/13055-no-null-lists
fix(serve): answer an empty list as [] in branches, event replay and the queue (#13055)
2026-10-14 04:18:55 -07:00
esengine 9702283b4d fix(configbackup): ask the disk only about plain absolute words in a backup 2026-10-14 04:18:54 -07:00
esengine 61e43ef64d Merge studio into feat/13077-scroll-jump 2026-10-14 04:18:02 -07:00
esengine be15d7a6d0 feat(studio): report a page defect with its error name and where it came from
Refs #13055
2026-10-14 04:16:20 -07:00
esengine bbcf3dff69 feat(studio): a setting to keep 1.x sessions out of the sidebar lists 2026-10-14 04:12:30 -07:00
esengine 72e4c58b63 feat(studio): an imported file starts with only what this machine lacks ticked 2026-10-14 04:11:43 -07:00
esengine c88d079723 feat(studio): export and import the configuration as an encrypted file
Refs #12736
2026-10-14 04:10:39 -07:00
esengine c044e821c0 test(serve): build the read list in one append 2026-10-14 03:59:40 -07:00
YHH 4f7b9fd280 Merge pull request #13088 from esengine/feat/13076-add-project-opens-session
feat(studio): adding a project selects it and opens a new session (#13076)
2026-10-14 03:57:25 -07:00
esengine aeb46aeb4e feat(studio): one way into the workbench
The title-bar Browser button and the Workbench tab opened the same column but
the button also asked for a blank browser page. The tab is now the only entry.
2026-10-14 03:56:09 -07:00
YHH 4c10ec72de Merge pull request #13089 from esengine/feat/13075-ttft
feat(agent): report time to first token and show it in the speed detail (#13075)
2026-10-14 03:54:08 -07:00
YHH 32132d3bf2 Merge pull request #13090 from esengine/fix/13086-nil-transport-in-interface
fix(plugin): a failed transport build is a nil interface, not a typed nil
2026-10-14 03:52:43 -07:00
esengine 8bd7dccc14 fix(studio): keep the jump buttons and the dock tab off the scroll rail
The buttons sit left of the rail, and the shut dock's tab sits flush with the
window edge instead of over the rail's ticks. The browser guard asserts no two
of the buttons, rail, tab and composer intersect at four widths.
2026-10-14 03:46:54 -07:00
esengine 3dad9ceb74 fix(serve): answer an empty list as [] in branches, event replay and the queue
Refs #13055
2026-10-14 03:41:38 -07:00
esengine c34c9c31b4 Merge studio into feat/13076-add-project-opens-session 2026-10-14 03:35:00 -07:00
esengine 312e7e9247 feat(studio): floating jump-to-top / jump-to-latest buttons in the conversation
Refs #13077
2026-10-14 03:34:26 -07:00
YHH a22ee52eaa Merge pull request #13087 from esengine/fix/13055-list-reads-tolerate-missing
fix(studio): treat a missing list in three reads as empty (#13055)
2026-10-14 03:32:39 -07:00
esengine 06eb4c34a6 fix(plugin): a failed transport build is a nil interface, not a typed nil 2026-10-14 03:28:01 -07:00
esengine 1bd4ddfe38 feat(agent): report each round's time to first token and show it in the speed detail
Refs #13075
2026-10-14 03:24:38 -07:00
esengine 6ca9b32695 feat(studio): adding a project selects it and opens a new session in it
Refs #13076
2026-10-14 03:15:29 -07:00
YHH 7a92d0cb30 Merge pull request #13084 from esengine/feat/13065-skip-setup
feat(studio): let first-run setup be put off (#13065)
2026-10-14 03:12:19 -07:00
esengine 30d5487c71 docs(release-notes): keep the stack-logging note, add the list-read note separately 2026-10-14 03:08:07 -07:00
esengine 73e4709340 fix(studio): treat a missing list in three reads as empty
Refs #13055
2026-10-14 03:07:27 -07:00
YHH 9fe33e78b3 Merge pull request #13062 from esengine/feat/13047-corner-notices
feat(studio): show non-blocking failures as dismissible corner notices with a report action
2026-10-14 02:55:14 -07:00
esengine 80c2b8dea6 feat(studio): let first-run setup be put off, with a way back above the input box
Refs #13065
2026-10-14 02:47:15 -07:00
YHH 877f021c16 Merge pull request #13080 from esengine/feat/13056-chunked-upload
feat(remote): carry a request too large for one relay message as numbered parts
2026-10-14 02:40:24 -07:00
YHH 37f320a4ef Merge pull request #13082 from esengine/feat/release-notes-language-switch
feat(studio): switch a release's notes between Chinese and English
2026-10-14 02:37:35 -07:00
esengine 7959dfe77e Merge studio into feat/13047-corner-notices 2026-10-14 02:35:16 -07:00
YHH 60f28d0405 Merge pull request #13073 from esengine/feat/12052-wait-names-holder
feat(delegation): say whose write claim a queued sub-agent is waiting on
2026-10-14 02:34:40 -07:00
YHH 0f119c9c5c Merge pull request #13074 from esengine/fix/13043-add-folder-visible
fix(studio): keep the add-folder button on the machine row visible without hover
2026-10-14 02:32:51 -07:00
esengine 5436dc9ae1 feat(studio): switch a release's notes between Chinese and English 2026-10-14 02:28:39 -07:00
YHH 574f69bdde Merge pull request #13079 from esengine/fix/13068-session-row-quiet
fix(studio): drop the hover archive and delete icons from session rows
2026-10-14 02:14:27 -07:00
esengine 3dc9249204 feat(remote): carry a request too large for one relay message as numbered parts 2026-10-14 02:06:04 -07:00
esengine 6e2647b8f6 fix(studio): drop the hover archive and delete icons from session rows 2026-10-14 02:04:15 -07:00
esengine 5eb6691ae0 Merge studio into feat/13047-corner-notices
# Conflicts:
#	desktop/frontend-next/perf/harness.tsx
#	desktop/frontend-next/src/actions.ts
#	desktop/frontend-next/src/i18n/en_composer.ts
2026-10-14 01:56:48 -07:00
esengine 9be7ea6cbc fix(studio): keep the add-folder button on the machine row visible without hover 2026-10-14 01:51:47 -07:00
YHH 7b13eaef56 Merge pull request #13071 from esengine/chore/13055-fault-stack
chore(studio): log the stack of a page fault that reaches the pane's error bar
2026-10-14 01:42:31 -07:00
esengine 9eea85f892 feat(delegation): say whose write claim a queued sub-agent is waiting on 2026-10-14 01:39:40 -07:00
YHH 4ac6a344a8 Merge pull request #13070 from esengine/fix/13056-remote-upload-limit
fix(remote): refuse a request too large for the relay link instead of dropping the link
2026-10-14 01:37:06 -07:00
YHH 2701be6327 Merge pull request #13069 from esengine/feat/12291-chart-render-p0
feat(studio): write values beside sparse chart marks
2026-10-14 01:32:23 -07:00
esengine 478131f826 chore(studio): log the stack of a page fault that reaches the pane's error bar 2026-10-14 01:30:41 -07:00
esengine 566ff0121b fix(remote): refuse a request too large for the relay link instead of dropping the link 2026-10-14 01:26:58 -07:00
esengine 4ce75ea335 feat(studio): write values beside sparse chart marks 2026-10-14 01:22:55 -07:00
YHH e7f55a8415 Merge pull request #13064 from esengine/feat/12926-model-menu-filter
feat(studio): set provider headings apart and filter the model menu by provider
2026-10-14 01:16:14 -07:00
YHH cc5d129293 Merge pull request #13063 from esengine/fix/13060-outside-workspace-write-is-a-gap
fix(checkpoint): a write outside the workspace is a coverage gap, not a recording failure
2026-10-14 01:12:10 -07:00
esengine 18e2a47e19 fix(studio): anchor the corner notices above the composer and guard it in a browser 2026-10-14 01:10:25 -07:00
esengine 751a697589 feat(studio): show non-blocking failures as dismissible corner notices with a report action 2026-10-14 01:10:25 -07:00
YHH 4a4718c7bf Merge pull request #13058 from esengine/feat/13053-writable-roots-meta
feat(mcp): tell a local stdio server the extra writable folders
2026-10-14 01:04:33 -07:00
esengine 431a87652e feat(studio): set provider headings apart and filter the model menu by provider 2026-10-14 01:04:18 -07:00
YHH fb99d78c33 Merge pull request #13059 from esengine/fix/13047-errbar-clears
fix(studio): clear a status-read error when that read next succeeds
2026-10-14 01:04:05 -07:00
esengine 186b2ee2df fix(checkpoint): a write outside the workspace is a coverage gap, not a recording failure 2026-10-14 01:00:22 -07:00
YHH 79597b19db Merge pull request #13031 from esengine/feat/13027-open-agent-page-externally
feat(studio): open the page the agent is working on in the default browser
2026-10-14 00:54:31 -07:00
YHH 7a1113d5e6 Merge pull request #13018 from esengine/fix/context-calibration-per-model
fix(agent): the gauge follows the provider's count, not the larger estimate
2026-10-14 00:46:34 -07:00
esengine 426b16f95a fix(studio): clear a status-read error when that read next succeeds 2026-10-14 00:45:50 -07:00
esengine e6f70622b5 feat(mcp): tell a local stdio server the extra writable folders 2026-10-14 00:40:52 -07:00
esengine f757912b74 Merge studio into feat/13027-open-agent-page-externally
# Conflicts:
#	desktop/frontend-next/perf/harness.tsx
#	desktop/frontend-next/src/i18n/en.ts
2026-10-14 00:37:11 -07:00
esengine c0b0770a4a test(boot): report prompt tokens the way a provider counts them in the not-a-digest fixture 2026-10-14 00:36:47 -07:00
YHH e7ce2480c3 Merge pull request #13054 from esengine/fix/13044-branch-tip-over-menu
fix(studio): hide the branch hover card while the branch menu is open
2026-10-14 00:36:39 -07:00
YHH af6d91a9bb Merge pull request #13050 from esengine/fix/13045-settings-nav-quiet
fix(studio): quiet the settings sidebar's selected row and separate section titles from items
2026-10-14 00:32:12 -07:00
esengine cd2a779d3a Merge studio into fix/context-calibration-per-model 2026-10-14 00:31:54 -07:00
YHH cdf8fad699 Merge pull request #13049 from esengine/fix/13041-search-toggle
fix(studio): make the session-filter magnifier a real toggle
2026-10-14 00:23:25 -07:00
esengine b1d5f81459 fix(studio): quiet the settings sidebar's selected row and separate section titles from items 2026-10-14 00:22:29 -07:00
esengine a6eeef486b fix(studio): hide the branch hover card while the branch menu is open 2026-10-14 00:15:19 -07:00
YHH 9da4afb553 Merge pull request #13032 from esengine/codex/cua-installed-launch
fix(computer): recover hidden applications through CUA launch
2026-10-14 00:09:25 -07:00
esengine a318c609b2 fix(studio): make the session-filter magnifier a real toggle 2026-10-14 00:05:55 -07:00
YHH da5e098968 Merge pull request #13042 from esengine/fix/13036-rowcount-covers-buttons
fix(studio): let the faded row count pass the pointer through to the row actions
2026-10-13 23:56:44 -07:00
esengine 3bd031f4ea fix(studio): let the faded row count pass the pointer through to the row actions 2026-10-13 23:46:42 -07:00
esengine 820e25e333 fix(computer): compare launched executables by file, not by 8.3 or long spelling on Windows 2026-10-13 23:43:43 -07:00
YHH a88cb60824 Merge pull request #13040 from esengine/fix/13034-signal-rail-merge
fix(studio): merge crowded signal marks into one counted marker
2026-10-13 23:27:21 -07:00
YHH ab30b0c994 Merge pull request #13037 from esengine/fix/13013-stuck-compaction
feat(agent): say what holds an over-threshold context that cannot fold
2026-10-13 23:23:28 -07:00
YHH 829ca39c79 Merge pull request #12962 from hasandenizuk/fix/12915-remove-last-provider
test(config): cover removing the last provider
2026-10-13 23:14:28 -07:00
esengine 44761d3444 fix(studio): merge crowded signal marks into one counted marker 2026-10-13 23:10:09 -07:00
esengine 04d6f39378 test(computer): cover the abort transport, window and cancellation bounds of Act, and launchable listing 2026-10-13 23:05:01 -07:00
esengine d257e677c3 chore(memory): annotate the host-built directory reads for CodeQL 2026-10-13 23:01:37 -07:00
esengine 5d4e335e9e feat(agent): say what holds an over-threshold context that cannot fold 2026-10-13 22:53:44 -07:00
YHH 5ae84e2b89 Merge pull request #13033 from esengine/feat/11965-capture-window
feat(studio): capture the app window into a feedback report
2026-10-13 22:50:41 -07:00
esengine 16de6cfdde Merge studio into fix/12950-memory-directory-errors 2026-10-13 22:43:00 -07:00
esengine e0c8ac0723 fix(memory): report a regular file in a directory's place on Windows too
Windows answers "not found" for a file where a directory should be, or
beneath one, so the not-exist shortcut hid the very case the directory read
error exists for. The stat of the entry and its parent tells them apart.
2026-10-13 22:42:53 -07:00
YHH ebca63af9f Merge pull request #13028 from esengine/feat/12913-git-graph
feat(studio): show the commit graph in the workbench explorer
2026-10-13 22:42:29 -07:00
esengine 47c44e9298 feat(studio): capture the app window into a feedback report 2026-10-13 22:33:40 -07:00
esengine f4cdcba372 fix(studio): word the git.history_failed refusal in the kernel table 2026-10-13 22:33:01 -07:00
YHH 1c6550f75f Merge pull request #13029 from esengine/feat/12771-quote-file-selection
feat(studio): add text selected in an open file to the conversation from the right-click menu
2026-10-13 22:06:08 -07:00
yhh b0e85b06cf fix(computer): restore installed applications through CUA 2026-10-13 22:00:14 -07:00
esengine 2a8b0c4f03 feat(studio): open the page the agent is working on in the default browser 2026-10-13 21:58:49 -07:00
esengine 33b017c710 fix(studio): a quoted file's name cannot end the host line or close its bracket 2026-10-13 21:50:06 -07:00
esengine 32a641107f test(gitcmd): pin that the history log runs no repository-configured program 2026-10-13 21:48:04 -07:00
esengine 49b3aa4d53 feat(studio): add text selected in an open file to the conversation from the right-click menu 2026-10-13 21:44:13 -07:00
esengine 2b47ae1fbd feat(studio): show the commit graph in the workbench explorer
A read-only History read in gitstatus, a /workspace/git/log endpoint, and a
collapsed Commit history section that lays the commits out in lanes.
2026-10-13 21:29:23 -07:00
YHH 55ab4e8d92 Merge pull request #13021 from esengine/codex/cua-driver-performance
fix(computer): reduce CUA latency and normalize Windows app identities
2026-10-13 21:28:46 -07:00
YHH 5f8eae49ec Merge pull request #13022 from esengine/feat/image-preview-13012
fix(studio): share original image previews across conversation entrances
2026-10-13 21:25:55 -07:00
YHH 078359533b Merge pull request #13014 from esengine/fix/13002-subagent-row-title
feat(studio): name each sub-agent row by its task
2026-10-13 21:25:43 -07:00
YHH 8761026022 Merge pull request #13024 from esengine/feat/12300-project-order
feat(studio): reorder projects by dragging a row
2026-10-13 21:25:23 -07:00
YHH 62814aae7c Merge pull request #13026 from esengine/fix/13017-smaller-unread-dot
fix(desktop): draw the Windows unread dot smaller, smooth and ringed
2026-10-13 21:21:10 -07:00
YHH ebbddbac5a Merge pull request #13025 from esengine/fix/13020-explorer-bigger
fix(studio): make the workbench explorer's text and icons easier to read and hit
2026-10-13 21:19:04 -07:00
esengine f5867f5103 fix(desktop): draw the Windows unread dot smaller, smooth and ringed 2026-10-13 21:12:05 -07:00
esengine b98b4f7201 fix(studio): make the workbench explorer's text and icons easier to read and hit 2026-10-13 21:08:46 -07:00
esengine c5e6a5c3ae feat(studio): reorder projects by dragging a row
The move endpoint also takes a place to land before; the row turns pointer
events into that place and shows a landing line while dragging.
2026-10-13 21:07:29 -07:00
esengine b1a67e195d wip: workspace place test 2026-10-13 21:07:29 -07:00
esengine 4309227e31 fix(agent): the gauge follows the provider's count, not the larger estimate
With an anchor the reading is the reported prompt size plus the growth since.
The estimate can sit far above the provider's count for a whole session.
2026-10-13 21:00:42 -07:00
YHH 2bd6a19565 Merge pull request #13019 from esengine/fix/13011-surface-replay-truncation
fix(serve): a log kept to its cap is not a failed surface restore
2026-10-13 20:58:51 -07:00
esengine b522bd0b8b docs(studio): keep the image preview notes, drop the embedded screenshots
Screenshots belong in the pull request; add the release note.
2026-10-13 20:53:28 -07:00
yhh 65d6442012 fix(computer): reduce CUA discovery and observation overhead 2026-10-13 20:51:27 -07:00
SivanCola 5910c148f8 fix(studio): share original image previews across conversation entrances
Problem: Studio user-message and composer images only showed thumbnails,
while tool screenshots used a separate enlargement overlay.

Root cause: The three entrances did not share a preview interaction owner.

Fix: Use a common image trigger and body-portaled viewer with original
sources, keyboard access, focus restoration, safe double clicking, explicit
closing and unavailable-image handling. Remove the tool-only overlay and
document the restored attachment parity with main-v2.

Verification: 541 frontend test files and 4611 tests pass; typecheck,
production build, census verification, make lint, make check and go vet
pass. Builtin and boot Go tests pass. Chromium checks cover light/dark,
wide/narrow windows and 150% interface zoom. The browser guard is wired
into Studio CI. Correct the perf fixture to await committed language state.
2026-10-13 20:50:18 -07:00
esengine d0be627024 fix(serve): a log kept to its cap is not a failed surface restore
The wire log records whether a surface frame was dropped; the surfaces route
answers 200 with an incomplete mark only then, and an unreadable history has a
code of its own.
2026-10-13 20:48:11 -07:00
esengine 732ec45bfe feat(studio): name each sub-agent row by its task
The kernel puts the task description's first line on the delegation profile and
the popover row shows it, truncated, with the full text on hover.
2026-10-13 20:37:22 -07:00
YHH fcc9363a17 Merge pull request #13015 from esengine/carry/13004-black-gold-icons
fix(studio): restore black-and-gold application and tray icons (carries #13004)
2026-10-13 20:32:03 -07:00
SivanCola 39a1e8b432 fix(memory): surface unreadable store directories
Problem: permission and storage failures can hide existing memory revisions,
make restores report them as missing, and compute recovery revisions from an
incomplete history.

Root cause: directory discovery conflated every ReadDir error with absence.

Fix: unify all eleven store walks behind a typed reader that treats only
missing directories as empty. Propagate errors through reads, save guards,
recovery, recall, controller commands, and HTTP responses. Complete recovery
and legacy duplicate discovery before writing. Preserve persisted formats.

Verification: failing-first permission fixture; eleven-site permission,
I/O, and non-directory regressions; unchanged files on failed recovery or
save; missing-directory compatibility; tool/controller/HTTP and bilingual
error coverage; Go tests, vet, lint, repository checks, frontend tests and
typecheck, and Windows memory test cross-compilation.

Fixes #12950
2026-10-14 11:23:56 +08:00
SivanCola c184d41602 docs(studio): announce restored black-and-gold icons
Problem: the pending icon restoration was missing from the next-release
notes, so users would not be told why the application artwork changed.

Root cause: the original restoration commit only included icon assets.

Fix: add a bilingual release-note fragment covering the application,
taskbar/Dock, launcher and tray icons without changing any artwork.

Verification: the 16 release-note rendering/publication tests, repository
lint, Go vet, changed-file checks and diff whitespace checks pass.
2026-10-13 20:23:24 -07:00
SivanCola 0d92555c24 fix(studio): restore black-and-gold application and tray icons
Problem: Studio 2.39.0 replaced the expected black-and-gold application,
taskbar, Dock, launcher, and tray artwork with blue-and-yellow icons.

Root cause: the shared SVG and all generated platform resources were
recoloured together in #12897.

Fix: restore the black-and-gold SVG and its ten generated icon files,
preserving the platform crop, ICO/ICNS sizes, and Windows tray DPI scales.

Verification: regenerate all assets and compare their bytes with the
pre-#12897 artwork; decode and visually inspect all 26 raster sizes;
run the Electron unit suite, Go vet, repository lint, and changed-file
checks, plus the builtin and boot suites. No application or packaging
logic changes.
2026-10-13 20:23:24 -07:00
YHH 96c3da197f Merge pull request #13008 from esengine/fix/13001-degenerate-repeat
fix(agent): flag a stream that collapses to a few tiny lines
2026-10-13 20:11:02 -07:00
YHH d1eeef5318 Merge pull request #13003 from esengine/fix/12993-find-exec-readonly
fix(shellsafe): treat find -exec of a known read-only program as a read
2026-10-13 20:07:17 -07:00
YHH f4d88a3142 Merge pull request #13005 from esengine/fix/12747-commit-draft-failure
fix(studio): let a person write the commit message when the model cannot draft one
2026-10-13 20:06:07 -07:00
esengine 4c192248d5 fix(agent): flag a stream that collapses to a few tiny lines
The guard only saw byte-periodic blocks. A second structural reading of the same
tail - many short lines from very few distinct ones - catches a degraded stream
that never repeats a block.
2026-10-13 20:00:36 -07:00
esengine abf44246c5 fix(studio): let a person write the commit message when the model cannot draft one
ProposeCommit returns the staged set with a typed draftFailure instead of an
error, and the card opens its message box with the reason.
2026-10-13 19:49:59 -07:00
esengine 1dd0b8a247 fix(shellsafe): treat find -exec of a known read-only program as a read
The program find runs decides: a bare read-only name whose own arguments do not
write. Anything else, -ok, -delete and the -fprint family stay writers, so a
read-only find no longer takes the workspace write lease.
2026-10-13 19:43:39 -07:00
YHH ffd136f463 Merge pull request #12999 from esengine/feat/12985-tui-theme-preview
feat(tui): preview each theme as the /theme picker moves
2026-10-13 19:39:23 -07:00
YHH c1a243d46c Merge pull request #12998 from esengine/fix/npm-publish-wait-longer
fix(release): wait up to 15 minutes for npm to show a published CLI package
2026-10-13 19:32:13 -07:00
YHH 31ba7705ed Merge pull request #12996 from esengine/feat/12063-shadow-token
feat(theme): let a pack set the shadow menus, popovers and dialogs cast
2026-10-13 19:30:27 -07:00
esengine b5a79cda33 feat(tui): preview each theme as the /theme picker moves
A bare /theme opens the quick picker; moving repaints, Enter keeps and stores the
row, Escape restores the original. /theme list keeps the old listing.
2026-10-13 19:29:07 -07:00
YHH 903aa09cdc Merge pull request #12997 from esengine/fix/12925-files-toggle
fix(studio): fold the files panel when the Files button is pressed again
2026-10-13 19:27:43 -07:00
YHH a3ba9e4f5c Merge pull request #12995 from esengine/feat/12011-tree-last-active
feat(serve): carry each session's last-activity time on the tree row
2026-10-13 19:23:21 -07:00
esengine fb3a65146a fix(release): wait up to 15 minutes for npm to show a published CLI package 2026-10-13 19:23:02 -07:00
esengine 634f8c77d4 fix(theme): accept only ASCII in a shadow value
The stored string reaches CSS unchanged, which does not treat Unicode spaces as
separators, so a value that validated could paint nothing.
2026-10-13 19:21:20 -07:00
esengine 51446c4d27 fix(studio): fold the files panel when the Files button is pressed again
The workbench decides, since it owns whether its explorer is the open one.
2026-10-13 19:18:19 -07:00
YHH 9a0e636c3d Merge pull request #12994 from esengine/feat/12728-turn-times
feat(studio): say when a turn began beside how long it worked
2026-10-13 19:12:21 -07:00
esengine 40e06a52e2 feat(theme): let a pack set the shadow menus, popovers and dialogs cast
A shadowFloat token with a strict px/hex shadow grammar maps onto
--shadow-float, and onto --shadow-pack for the light menus that kept their
own default. Unset, nothing paints differently.
2026-10-13 19:07:36 -07:00
esengine 6d47f6c837 feat(serve): carry each session's last-activity time on the tree row
The tree already sorts by it; exposing it lets a client build a recency list.
2026-10-13 18:58:46 -07:00
esengine 12f018ffc5 feat(studio): say when a turn began beside how long it worked
TurnDone carries the turn's start time and /history carries when a user
message landed, so the worked line names the start live and after a reload.
2026-10-13 18:52:36 -07:00
YHH f289e3903f Merge pull request #12992 from esengine/release/2.39.0-notes-2
docs(release): add the last two changes to the 2.39.0 notes
2026-10-13 17:36:17 -07:00
esengine 40d577d9f6 docs(release): add the last two changes to the 2.39.0 notes 2026-10-13 17:28:17 -07:00
YHH 2234daf25f Merge pull request #12991 from esengine/fix/12988-gauge-reads-provider-floor
fix(agent): the context gauge reads the provider-reported size the fold trigger acts on
2026-10-13 17:23:54 -07:00
esengine 1d81b5b361 fix(agent): the context gauge reads the provider-reported size the fold trigger acts on 2026-10-13 17:13:45 -07:00
YHH 561f385778 Merge pull request #12990 from esengine/release/2.39.0-notes
docs(release): Studio 2.39.0 notes
2026-10-13 16:54:32 -07:00
YHH ac7c495553 Merge pull request #12138 from boscocp/feat/studio-11957-tools-list-changed
feat(plugin): refresh an MCP catalog on tools/list_changed / 收到 tools/list_changed 后刷新工具目录
2026-10-13 16:48:14 -07:00
esengine af03a12941 docs(release): Studio 2.39.0 notes 2026-10-13 16:46:14 -07:00
YHH 35011ad3d4 Merge pull request #12989 from esengine/fix/target-access-test-walk-race
test(agent): let the target-access wiring walk skip files that vanish mid-walk
2026-10-13 16:38:13 -07:00
esengine 60bee4b979 Merge remote-tracking branch 'origin/studio' into p12138
# Conflicts:
#	internal/ext/plugin/plugin.go
2026-10-13 16:37:36 -07:00
esengine a125fbc9b2 test(agent): let the target-access wiring walk skip files that vanish mid-walk 2026-10-13 16:27:35 -07:00
YHH d2d610d65b Merge pull request #12987 from esengine/fix/cua-fixture-socket-mode
test(desktop): create the CUA fixture socket at 0600 instead of chmod-ing it afterwards
2026-10-13 16:09:23 -07:00
esengine 7931e701ec test(desktop): create the CUA fixture socket at 0600 instead of chmod-ing it afterwards 2026-10-13 15:59:24 -07:00
YHH 6343236907 Merge pull request #12115 from Guan0923/codex/pretooluse-before-preview
fix(agent): gate file previews on target access and PreToolUse
2026-10-13 15:20:42 -07:00
esengine b520c59678 Merge studio into pr/12115; leave path-shape failures to the tool 2026-10-13 15:10:42 -07:00
YHH 3b9d7b3609 Merge pull request #12557 from SivanCola/fix/workbench-tab-menu-controller
feat(studio): use controller close in extracted tab menus / 标签菜单适配控制器关闭路径
2026-10-13 15:07:51 -07:00
esengine 6487ed6f21 Merge remote-tracking branch 'origin/studio' into p12557
# Conflicts:
#	desktop/frontend-next/src/i18n/en_settings.ts
2026-10-13 14:57:34 -07:00
Hasan Deniz fc23415309 test(config): cover removing the last provider
Two tests for the path #12966 settled: removing every provider saves an
empty list that loads back empty, and adding one afterwards replaces it.
Over HTTP, removing the last provider moves the runtime to the stand-in
and lists no provider.

Both fail without the providers = [] render line from #12966.

Refs #12915
2026-10-13 22:43:59 +01:00
esengine 1c23c7dcd6 Merge remote-tracking branch 'origin/studio' into p12557
# Conflicts:
#	desktop/frontend-next/src/i18n/en_kernel.ts
#	desktop/frontend-next/src/i18n/en_settings.ts
#	desktop/frontend-next/src/port/sse.ts
#	desktop/frontend-next/src/ui/BrowserTabMenu.tsx
#	desktop/frontend-next/src/ui/WorkbenchPanel.tsx
#	desktop/frontend-next/src/ui/browser-close.interaction.test.tsx
#	release-notes/studio/unreleased.md
2026-10-13 14:17:12 -07:00
YHH 4f2c5a2212 Merge pull request #12065 from KHG420/codex/studio-installed-capability-search
feat(studio): find installed packages by contributed capabilities
2026-10-13 14:14:24 -07:00
YHH 36de07d2fa Merge pull request #12896 from esengine/codex/intelligent-ui-12584-workspaces
feat(studio): add maps, comparisons and dashboards
2026-10-13 14:06:54 -07:00
esengine 91686f3c3f Merge studio into pr/12065
# Conflicts:
#	desktop/frontend-next/src/i18n/en_settings.ts
2026-10-13 14:02:13 -07:00
esengine 1ef2f9168d Merge studio into codex/intelligent-ui-12584-workspaces
Declares import_web_image's snip stance (a small JSON receipt takes the default) and regenerates
docs/TOOL_CONTRACT.md, the two contract tests that landed on studio after the branch point.
2026-10-13 13:56:03 -07:00
YHH 9d05547a00 Merge pull request #11591 from Guan0923/codex/session-rename-dialog
feat(studio): add a rename dialog with on-demand session auto-naming
2026-10-13 13:54:07 -07:00
esengine 82312cad7c Merge studio into codex/pretooluse-before-preview
move_file now asks for read access to its source on both paths (a forbidden-read source is refused
before the rename, not only when the copy fallback runs), and four agent tests built after the
studio branch point supply the permissive target-access callback the agent now requires.
2026-10-13 13:50:16 -07:00
esengine 6cd5d1a849 Merge studio into pr/12115 2026-10-13 13:45:26 -07:00
YHH a572624a6a Merge pull request #12513 from f1shsmell/feat/theme-pack-recipes
feat(theme): read the density and corner recipes a pack declares
2026-10-13 13:43:37 -07:00
esengine 8e65a19dde Merge studio into codex/session-rename-dialog
Keeps the row-level double-click rename from studio (and drops the title-level handler it replaced),
declares the dialog's title input as session.rename.title, and expects the inline mode in the
double-click test now that onRename carries it.
2026-10-13 13:41:48 -07:00
YHH 8730e31eab Merge pull request #12117 from KHG420/codex/studio-package-contribution-groups
feat(studio): identify installed package contribution kinds
2026-10-13 13:37:52 -07:00
esengine b678e56a32 Merge studio into feat/theme-pack-recipes 2026-10-13 13:32:09 -07:00
esengine 7da9815f14 Merge studio into codex/studio-package-contribution-groups 2026-10-13 13:26:38 -07:00
esengine a24430df38 Merge studio into pr/12117
Drops two blank lines from app.css so the two new contribution rules fit the recorded size.
2026-10-13 13:26:19 -07:00
YHH d97edf1631 Merge pull request #12329 from KHG420/codex/studio-skill-source-groups
fix(studio): group installed skills by source
2026-10-13 13:25:36 -07:00
esengine 1e4c8bd9c4 Merge studio into pr/12117 2026-10-13 13:24:41 -07:00
esengine ec668a1264 Merge studio into codex/intelligent-ui-12584-workspaces 2026-10-13 13:20:18 -07:00
YHH b1db7d8338 Merge pull request #12075 from KHG420/codex/studio-package-copy-invocations
feat(studio): copy package invocations and archive paths
2026-10-13 13:19:31 -07:00
YHH f2187c9d03 Merge pull request #12118 from xxoingr/feat/markdown-bidi
feat(studio): render right-to-left text blocks in their own direction
2026-10-13 13:15:24 -07:00
esengine 2367cd4d65 Merge studio into codex/studio-skill-source-groups 2026-10-13 13:14:22 -07:00
esengine fa22360007 Merge studio into feat/markdown-bidi 2026-10-13 13:02:27 -07:00
YHH e2bdbc322f Merge pull request #11735 from jf200s/fix/studio-live-turn-reducer
fix(session): keep the live turn through rebuilds and lost turn ends
2026-10-13 13:00:21 -07:00
YHH 43ba7f3b45 Merge pull request #12323 from KHG420/codex/studio-advanced-appearance-cards
fix(studio): align advanced appearance with settings cards
2026-10-13 12:54:41 -07:00
esengine 5207b65161 Merge studio into pr/12329 2026-10-13 12:54:19 -07:00
YHH 8d7a0189a6 Merge pull request #12313 from KHG420/codex/studio-chart-readouts
fix(studio): make chart values discoverable along dense lines
2026-10-13 12:50:47 -07:00
YHH ba2fb2e3e7 Merge pull request #12986 from esengine/fix/surface-form-test-race
test(studio): wait for the lazily loaded surface field instead of reading it at once
2026-10-13 12:47:52 -07:00
esengine ef0e34ccc4 Merge studio into codex/studio-advanced-appearance-cards
Keeps the new summary text, names the advanced-appearance disclosure in the settings catalogue (the catalogue test requires a titled block to name itself) and folds three selector lists onto one line each so app.css stays inside its recorded size.
2026-10-13 12:42:28 -07:00
esengine a32fe7f492 test(studio): wait for the lazily loaded surface field instead of reading it at once 2026-10-13 12:37:06 -07:00
YHH 46b22cf79b Merge pull request #12876 from esengine/codex/intelligent-ui-12584-actions
feat(studio): add nonblocking surface actions (RFC #12584 P2)
2026-10-13 12:34:08 -07:00
esengine f9d08b4811 Merge studio into codex/intelligent-ui-12584-actions 2026-10-13 12:26:29 -07:00
YHH 6f9643f976 Merge pull request #12854 from esengine/codex/intelligent-ui-12584-drafts
feat(studio): add editable drafts and styled text previews
2026-10-13 12:22:12 -07:00
esengine ec91de1853 Merge studio into fix/studio-live-turn-reducer
Folds the studio's retired-surface rows into the identity merge of a rebuild (rebuild.ts), and lets two
fixtures report the turn as running while it runs, since a status read now vouches for it.
2026-10-13 12:22:02 -07:00
YHH ab4dfc827a Merge pull request #12984 from esengine/refactor/saved-not-applied-note
refactor(studio): share the saved-not-applied predicate and the save note
2026-10-13 12:17:42 -07:00
esengine a3c43490fb Merge studio into codex/intelligent-ui-12584-drafts 2026-10-13 12:11:21 -07:00
esengine 783e56578b refactor(studio): share the saved-not-applied predicate and the save note
The same HttpError check and the same note block were copied into Compaction, Rules, Shell, Context,
Settings and EditConn. savedNotApplied lives next to SAVED_NOT_APPLIED and SaveNote draws the note once.
2026-10-13 12:07:11 -07:00
YHH cc0635b7c0 Merge pull request #12350 from KHG420/codex/studio-compaction-save-outcomes
fix(studio): distinguish settings save outcomes and allow retry
2026-10-13 12:06:56 -07:00
YHH ab7ded2e46 Merge pull request #12983 from esengine/fix/skillrow-tab-order
test(studio): expect the copy button first in a skill row's tab order
2026-10-13 12:01:03 -07:00
esengine e033f1bb11 Merge studio into codex/intelligent-ui-12584-drafts 2026-10-13 11:58:46 -07:00
YHH f5b89e246f Merge pull request #12830 from esengine/codex/intelligent-ui-12584-fields
feat(studio): add styled native fields and choice cards (RFC #12584 P1)
2026-10-13 11:53:48 -07:00
YHH 1d76e29525 Merge pull request #12314 from KHG420/codex/studio-refine-keyboard-help
fix(studio): keep prompt-refine help reachable and shortcut intact
2026-10-13 11:52:03 -07:00
esengine 9c44a56bbf style(studio): drop a blank line so app.css is back inside its budget
#12982 added one rule to a stylesheet held at its recorded size.
2026-10-13 11:51:35 -07:00
esengine 2d7cda8ef8 test(studio): expect the copy button first in a skill row's tab order 2026-10-13 11:50:36 -07:00
YHH 2f78502847 Merge pull request #12102 from KHG420/codex/studio-skill-execution-declarations
feat(studio): inspect subagent skill declarations
2026-10-13 11:41:06 -07:00
YHH 75552a6902 Merge pull request #12098 from KHG420/codex/studio-plugin-commit-preview
feat(studio): show resolved plugin commits in install previews
2026-10-13 11:35:58 -07:00
esengine 6fb3c3b65a test(studio): follow the refine button's aria-disabled selector 2026-10-13 11:30:13 -07:00
esengine 3228d69968 Merge studio into codex/studio-refine-keyboard-help 2026-10-13 11:30:05 -07:00
esengine 6a814cdc7c Merge studio into codex/intelligent-ui-12584-fields 2026-10-13 11:29:45 -07:00
xiexian 5dfb9a3eca feat(studio): copy installed package invocations 2026-10-14 02:28:40 +08:00
YHH eb7dd41e8f Merge pull request #12982 from esengine/fix/12945-fewer-version-rows
feat(studio): keep the newest earlier releases on the versions page and fold the rest
2026-10-13 11:23:26 -07:00
YHH 844aa7b04e Merge pull request #11589 from Guan0923/codex/studio-code-rewind-undo-entry
fix(studio): 从后端恢复撤销入口并持久化撤销失效状态
2026-10-13 11:16:08 -07:00
esengine 1c0c8e7d8a feat(studio): keep the newest earlier releases on the versions page and fold the rest 2026-10-13 11:12:57 -07:00
YHH 80e3680595 Merge pull request #12969 from esengine/feat/tool-json-highlight
feat(studio): colour the JSON input of a tool call
2026-10-13 11:12:02 -07:00
esengine a3420a00f2 Merge studio into codex/prompt-refine-help 2026-10-13 11:08:30 -07:00
YHH 4a78b32113 Merge pull request #12070 from KHG420/codex/studio-skill-copy-invocation
feat(studio): copy canonical skill invocations from Settings
2026-10-13 11:08:25 -07:00
YHH 5a25c18574 Merge pull request #12981 from esengine/fix/modelrows-guard-linux
fix(perf): open settings through its button in the model-rows guard
2026-10-13 11:07:50 -07:00
YHH 04329b5355 Merge pull request #12980 from esengine/fix/rewind-undo-test-race
test(studio): wait for the restore receipt instead of reading it at once
2026-10-13 10:57:40 -07:00
esengine 78c872fcf7 fix(perf): open settings through its button in the model-rows guard 2026-10-13 10:57:00 -07:00
YHH 706c2a16a4 Merge pull request #12595 from esengine/feat/remote-wsl-kind
feat(wsl): discover WSL distributions and probe one (RFC #12225, slice 1)
2026-10-13 10:56:58 -07:00
YHH 29ffc3d278 Merge pull request #12405 from keeyangyy/pr/legacy-session-badge
feat(studio): mark sessions kept in the 1.x store
2026-10-13 10:56:41 -07:00
esengine 4af61e3d92 fix(studio): give the surface submit button AA contrast in the light theme
Mantine's gray primary filled colour is #868e96, which is 3.4:1 against the button's white label.
The scoped provider now maps it to the app's text token.
2026-10-13 10:52:59 -07:00
esengine d72bc50a8f refactor(control): drop resolveAsk, replaced by resolveAskAnswer 2026-10-13 10:49:32 -07:00
esengine 3903638ded test(studio): wait for the restore receipt instead of reading it at once 2026-10-13 10:45:48 -07:00
YHH 68f01019f4 Merge pull request #12979 from esengine/fix/12920-one-scrollbar
fix(studio): one scrollbar on the provider model list, and a themed alias field
2026-10-13 10:45:14 -07:00
esengine 3a2a39528a chore: drop a scratch file 2026-10-13 10:40:25 -07:00
esengine fad9794bc9 fix(studio): keep the copy control inside the skill name column
In a narrow settings column the extra control pushed the scope and switch of the longest rows past the card edge. Next to the name it keeps the other columns where they were.
2026-10-13 10:39:26 -07:00
esengine 0a4908854f Merge studio into codex/studio-skill-copy-invocation 2026-10-13 10:38:35 -07:00
YHH 584eb2eadc Merge pull request #12978 from esengine/test/mcp-oauth-credential-binding
test(plugin): pin MCP OAuth credentials to the recorded authorization server
2026-10-13 10:35:10 -07:00
esengine 8cb659ef4a refactor(studio): build the launch pane's runtime options in one place
assemble sits at the 120-line ceiling; the native-surface option the surface work adds tips it over.
2026-10-13 10:34:17 -07:00
esengine 940e4f017e Merge studio into codex/intelligent-ui-12584-fields 2026-10-13 10:33:04 -07:00
esengine 7c7846d76c Merge studio into feat/tool-json-highlight
# Conflicts:
#	desktop/frontend-next/perf/harness.tsx
2026-10-13 10:31:47 -07:00
esengine a8b15e1f04 Merge studio into pr/legacy-session-badge 2026-10-13 10:31:05 -07:00
YHH 407b9bd0c1 Merge pull request #12977 from esengine/fix/12970-dblclick-rename
feat(studio): double-click a session row to rename it
2026-10-13 10:30:54 -07:00
esengine 9f7b4d1104 Merge studio into feat/remote-wsl-kind 2026-10-13 10:30:51 -07:00
esengine 824bb2faad fix(studio): one scrollbar on the provider model list, and a themed alias field 2026-10-13 10:30:00 -07:00
YHH e6db67a14d Merge pull request #12976 from esengine/feat/osc-7501-status
feat(tui): tell the terminal what the session is doing (OSC 7501)
2026-10-13 10:26:03 -07:00
esengine 8954d0302d Merge studio into codex/intelligent-ui-12584-fields
Keeps one /answer handler that serves both question engagement and surface answers, and
carries the computer driver and native-surface options through the workspace switch.
2026-10-13 10:17:58 -07:00
esengine f5ee8d785d feat(tui): tell the terminal what the session is doing (OSC 7501) 2026-10-13 10:16:47 -07:00
esengine 0d07d95c6c test(plugin): pin MCP OAuth credentials to the recorded authorization server 2026-10-13 10:15:57 -07:00
esengine 7641b1e249 feat(studio): double-click a session row to rename it 2026-10-13 10:15:20 -07:00
YHH 5186562d29 Merge pull request #12973 from esengine/fix/release-notes-tests-skip-fragments
fix: unbreak repolint and the release-notes script tests on studio
2026-10-13 10:13:35 -07:00
YHH 30d1638a52 Merge pull request #12825 from esengine/codex/intelligent-ui-12584
feat(studio): recover extension surfaces after reload (RFC #12584 P0)
2026-10-13 10:06:25 -07:00
YHH 5bcc7a4599 Merge pull request #12972 from esengine/feat/skill-chip-in-user-message
feat(studio): show a skill invocation as a compact chip in the conversation
2026-10-13 10:03:49 -07:00
YHH 87fd186f8f Merge pull request #12085 from KHG420/codex/studio-mcp-tool-search
feat(studio): search MCP server tool inventories
2026-10-13 10:01:40 -07:00
esengine 0ba5ac1f09 fix(config): tagged switch for the kind check 2026-10-13 09:57:43 -07:00
esengine 0dbc09fc1c fix(studio): drop the unused import 2026-10-13 09:56:38 -07:00
esengine da8d7944da refactor(studio): keep assemble and config.go inside their repolint budgets 2026-10-13 09:56:38 -07:00
esengine fd311693f6 test(release): read the fragment directory's notes too and skip directories 2026-10-13 09:56:38 -07:00
YHH 3f0463e2bf Merge pull request #12910 from KHG420/codex/studio-file-links
feat(studio): open conversation file links in the workbench
2026-10-13 09:55:58 -07:00
esengine 0ac031b168 Merge studio into pr/legacy-session-badge 2026-10-13 09:51:30 -07:00
esengine e4af6f2be7 fix(wsl): ask the OS for the system directory instead of reading the environment
repolint's process-input rule flags a kernel package that reads SystemRoot. GetSystemDirectory
is also the stronger answer: a changed environment cannot stand in for System32.
2026-10-13 09:49:58 -07:00
esengine 2baa0a1e19 Merge remote-tracking branch 'origin/studio' into wslfix 2026-10-13 09:49:31 -07:00
YHH cc73202dda Merge pull request #12971 from esengine/fix/composer-hover-radius
fix(studio): give the branch button the same hover shape as its neighbours
2026-10-13 09:47:53 -07:00
YHH 878b983b67 Merge pull request #12440 from keeyangyy/pr/remember-paired-devices
feat(serve): keep paired phones across restarts, with a settings switch
2026-10-13 09:47:48 -07:00
esengine 1597e64b40 feat(studio): show a skill invocation as a compact chip in the conversation
Fixes #12383
2026-10-13 09:45:47 -07:00
esengine 9432f13c4f test(serve): format the legacy-mark test and join its fixture lines with strings.Join 2026-10-13 09:38:40 -07:00
YHH 2211e22552 Merge pull request #12968 from esengine/chore/release-notes-per-file
docs(release): one release-note fragment per pull request
2026-10-13 09:37:13 -07:00
esengine 1cc5648b65 fix(studio): give the branch button the same hover shape as its neighbours
Fixes #12912
2026-10-13 09:34:45 -07:00
esengine 2e842b987a fix(wsl): an ended context is not an empty machine
List answered "no distributions" for any wsl.exe failure, so a probe that hit its
deadline or was cancelled read as a machine without WSL. The caller's ended context
is now returned as such.
2026-10-13 09:34:10 -07:00
YHH 0874955cff Merge pull request #12591 from esengine/feat/phone-page-installable
feat(studio): let the phone page be added to the home screen
2026-10-13 09:32:50 -07:00
YHH de2809cf64 Merge pull request #12587 from esengine/feat/tui-unviewed-mark
feat(tui): mark unviewed sessions in the resume picker and clear on view
2026-10-13 09:29:46 -07:00
esengine 7df54d6f91 Merge remote-tracking branch 'origin/studio' into HEAD 2026-10-13 09:26:37 -07:00
esengine 502e8f045c refactor(studio): keep the extension routes with the extension port
The studio merge left sse.ts at 803 lines, over the 800-line ceiling; the three extension calls belong to SseExtensions.
2026-10-13 09:24:08 -07:00
YHH d924132cc7 Merge pull request #12211 from KHG420/codex/studio-extension-form-result
fix(studio): keep failed extension forms editable
2026-10-13 09:22:45 -07:00
esengine 0a999b6857 feat(studio): colour the JSON input of a tool call
Fixes #12919
2026-10-13 09:22:39 -07:00
YHH c1e2840bab Merge pull request #12963 from esengine/fix/compaction-summarizer-budget
fix(compaction): carry the user's words through a degraded fold and cap the summarizer input
2026-10-13 09:17:26 -07:00
YHH 3a623c3ced Merge pull request #12937 from esengine/codex/cua-driver-migration
feat(studio): migrate computer use to CUA Driver
2026-10-13 09:13:17 -07:00
YHH b8f3ae549e Merge pull request #12967 from esengine/feat/mcp-meta-local-context
feat(plugin): tell local stdio MCP servers the workspace and the host's read-only posture
2026-10-13 09:12:23 -07:00
esengine dae22d82ce Merge remote-tracking branch 'origin/studio' into p0
# Conflicts:
#	desktop/frontend-next/src/port/sse.ts
2026-10-13 09:10:39 -07:00
esengine 0133febcf6 Merge studio and keep both release-note sets 2026-10-13 09:06:26 -07:00
esengine 2d9f6fd634 style(studio): drop the stylesheet comment
Stylesheets carry no comments in this repo; the selector and declarations are the statement.
2026-10-13 09:06:10 -07:00
esengine a2147d0474 docs(release): one release-note fragment per pull request 2026-10-13 09:05:16 -07:00
esengine db6c6a233b feat(plugin): tell local stdio MCP servers the workspace and the host's read-only posture
Fixes #12965
2026-10-13 09:02:44 -07:00
esengine aa7090472e Merge studio into codex/studio-extension-form-result 2026-10-13 08:55:02 -07:00
YHH 86c63d9ac6 Merge pull request #12214 from KHG420/codex/studio-extension-current-fields
fix(studio): submit only current extension form fields
2026-10-13 08:53:10 -07:00
YHH de6b70a6fd Merge pull request #12885 from par73e/fix/12764-sft-run-provenance
fix(e2ebench): bind SFT exports to graded run provenance
2026-10-13 08:51:55 -07:00
YHH c791b02d14 Merge pull request #12884 from par73e/fix/12862-ask-interaction-timeout
fix(studio): keep an engaged ask from timing out
2026-10-13 08:50:54 -07:00
YHH 44695d6106 Merge pull request #12966 from esengine/feat/start-without-provider
feat(config): start with no provider and open the connection guide
2026-10-13 08:49:19 -07:00
esengine d98a908641 test(agent): range over integers in the mechanical fold tests 2026-10-13 08:46:17 -07:00
yhh 88129d0f6a feat(studio): replace native computer helper with CUA Driver 2026-10-13 08:43:14 -07:00
esengine b84dfcc5cf feat(config): start with no provider and open the connection guide
Fixes #12915
2026-10-13 08:42:15 -07:00
esengine 698a963ab2 fix(compaction): carry the user's words through a degraded fold and cap the summarizer input
Refs #12961
2026-10-13 08:00:56 -07:00
YHH 25b996c08f Merge pull request #12955 from esengine/fix/crumb-title-beside-project
fix(studio): keep the session title beside the project name in the title bar
2026-10-13 07:44:49 -07:00
YHH 01161ca789 Merge pull request #12957 from esengine/feat/live-grace
feat(studio): keep finished sessions in the Running list for a chosen time (off by default)
2026-10-13 07:43:40 -07:00
YHH b795c20c93 Merge pull request #12960 from esengine/feat/disk-health
feat(notify): warn when the workspace or state volume is nearly full
2026-10-13 07:40:57 -07:00
esengine 2f9207b408 Merge studio and keep both release-note sets 2026-10-13 07:35:27 -07:00
esengine efa78dfd49 Merge studio and keep both release-note sets 2026-10-13 07:35:08 -07:00
esengine 5b15cd35d0 feat(notify): warn when the workspace or state volume is nearly full
Fixes #12958
2026-10-13 07:30:26 -07:00
Guan09 3ef990601f fix(checkpoint): make rewind undo lifecycle durable
Persist undo retirement before admitting new turns or tool writes, and refuse new activity when that persistence fails. Keep post-tool checkpoint-recording warnings separate from truthful tool results. Preserve the preceding undo after fully compensated rewind failures, and block activity until incomplete recovery settles. Reuse the existing Studio notice, availability route and change event; retain the failure-path tests.
2026-10-13 22:28:35 +08:00
xiexian 56378d2f33 feat(studio): search MCP server tool inventories 2026-10-13 22:26:40 +08:00
YHH d5a9618ffd Merge pull request #12956 from esengine/fix/notify-click-raises-window
fix(notify): log the toast click path end to end
2026-10-13 07:15:30 -07:00
esengine df95cc8535 feat(studio): keep finished sessions in the Running list for a chosen time (off by default)
Fixes #12312
2026-10-13 07:13:25 -07:00
esengine 357d8079a2 fix(notify): log the toast click path end to end
Refs #12817
2026-10-13 07:05:37 -07:00
YHH 05d65a05ed Merge pull request #12954 from esengine/fix/files-button-toggles
fix(studio): quiet the activity group so the reply stands out
2026-10-13 07:02:14 -07:00
esengine b575889b2f fix(studio): keep the session title beside the project name in the title bar
Fixes #12924
2026-10-13 06:54:33 -07:00
keeyang 5f667467e8 feat(studio): mark sessions kept in the 1.x store
A conversation whose log still lives in the 1.x store is read here and never
written, so it shares the sidebar with this build's own rows and cannot be told
apart. The tree now reports which store each row came from (the kernel decides
it through IsForeignSessionLog) and the row shows a small "Old" mark at its end,
which yields to the hover actions.
2026-10-13 21:50:36 +08:00
esengine 3bcbbf3dbc fix(studio): quiet the activity group so the reply stands out
Fixes #12953
2026-10-13 06:48:32 -07:00
keeyang 3b2b1f6e32 fix(studio): register the share.remember action and complete the phone-access mocks
The phone-access panel renders data-action="share.remember" and reads
hub.shareRemember(), but neither reached the action registry nor the test
hubs that build one by hand: actions.test.ts failed with "rendered but not
in src/actions.ts" (PhoneAccess.tsx:194), and PhonePop.interaction /
PhoneAccess.port failed with "hub.shareRemember is not a function".

Register the action beside the other share.* entries and give both test
hubs the hook the panel now reads.
2026-10-13 21:35:14 +08:00
keeyang 230bf814c7 feat(serve): keep paired phones across restarts, with a settings switch
A phone pairs by scanning a QR code, and the credential it keeps rides a
cookie scoped to the origin it scanned. The host held that pairing in memory
only: closing the share or restarting the process threw every device away, so
the next launch asked for a fresh scan.

[serve] remember_paired_devices, false by default and the user's alone (a
project reasonix.toml that sets it is ignored and reported), keeps three things
when it is on: the phones that paired (digests only, written to
<state>/device-trust.json and adopted at startup), the address the share last
listened on, and whether the share was open at all. The persist hooks read the
key when they run, so the panel switch takes effect without a restart, and
turning it off forgets on the spot: everything paired is revoked and both files
are removed. A snapshot taken before a later change is dropped by revision, so
a concurrent pairing write can never overwrite a revoke. Address ordering keeps
its upstream preference. A phone that paired never expires: the point of the
switch is that one's own LAN phone connects again next time, and that is
intentional.

Closing the share still unpairs everything; that is the person withdrawing it.
Only the process going away keeps the phones. The state directory is the
kernel's, outside every workspace, and agent-run commands have no path to it:
sandbox writes are confined to the workspace and ~/.reasonix, and the trust
file never enters them. A restart reopens the listener only when the switch
was on and the share was open when the process left — the state of things the
person chose, not a new one.

Fixes #11834
Refs #11878
2026-10-13 21:35:14 +08:00
YHH d00f905d4e Merge pull request #12952 from SivanCola/codex/html-preview-selection-settlement
test(studio): await HTML preview selection / 等待 HTML 预览选中状态落定
2026-10-13 06:24:28 -07:00
YHH f290a6377a Merge pull request #12946 from xxoingr/test/gitcommit-literal-path-windows
test(gitcommit): stage a literal glob name Windows can create
2026-10-13 06:24:18 -07:00
YHH a05b31f489 Merge pull request #12949 from xxoingr/test/acp-drain-prompt-budget
test(acp): bound drainPrompt's idle wait by the hang budget
2026-10-13 06:24:03 -07:00
YHH 33b725f56c Merge pull request #12921 from SivanCola/codex/studio-linux-arm64
test(studio): strengthen Linux package verification / 增强 Linux 包与发包验证
2026-10-13 05:59:00 -07:00
YHH 6fee91ef4e Merge pull request #12918 from SivanCola/codex/studio-sidebar-divider
fix(studio): use the divider as the desktop sidebar control / 桌面侧栏统一使用分隔条操作
2026-10-13 05:58:45 -07:00
SivanCola 9e6e1d1e28 test(studio): await settled HTML preview selection
Problem: frontend CI intermittently asserted that Files was hidden before a
fresh browser-tab follower had committed its selection.

Root cause: retiring the preview's request record does not settle the separate
Promise.allSettled callback that updates the visible workbench selection.

Fix: wait for the asserted workbench DOM state, as the sibling preview test
already does, while preserving the selected-tab and newer-intent guarantees.

Verification: all 49 HTML preview and neighbouring browser race/rejection tests,
frontend typecheck, and repository lint/check pass; prescribed Go preflight
checks run before commit. CI is monitored on the pushed head.
2026-10-13 20:56:05 +08:00
YHH e47cb489b7 Merge pull request #12936 from SivanCola/feat/studio-brand-wordmark
feat(studio): unify official brand marks / 四处品牌区统一到正式字标与产品符号
2026-10-13 05:50:33 -07:00
esengine 6b14a90f88 Merge studio and keep both release-note sets 2026-10-13 05:41:51 -07:00
xxoingr ced22c2265 test(acp): bound drainPrompt's idle wait by the hang budget
TestE2ESessionLoad failed on Windows CI on studio (run 38179453323) with
"session/prompt: timed out" after 7.64 s: drainPrompt gave up when five
seconds passed without a notification or the prompt's response. A full
prompt crosses the ACP server, controller, agent and transcript
persistence, and a loaded Windows runner can stay quiet that long without
anything being stuck. Pausing the scripted tool for 5.5 s reproduces it.

drainPrompt now waits rpcCallBudget(t), the testenv.Budget wait the same
file's other RPC helpers already use.
2026-10-13 20:41:01 +08:00
YHH 13e58f3ae8 Merge pull request #12947 from esengine/fix/remove-neutral-blue
revert(theme): drop the unreleased Neutral Blue pack in favour of Clear Blue
2026-10-13 05:39:48 -07:00
SivanCola 810156ad6e Merge studio and preserve sidebar and activity-header release notes
Problem: The latest activity-header note conflicts with this PR's sidebar
entry at the end of the unreleased notes.

Root cause: Both branches append their independent entries at one anchor.

Fix: Preserve all upstream notes and place the sidebar entries alongside the
session-list entries. Retain the narrow-only drawer control and the sticky
activity header in the auto-merged stylesheet.

Verification: Sidebar/divider, responsive layout and activity-fold tests;
the Chromium sticky-header guard in light and dark; production build,
repolint, release-note/reference tests, typecheck and UI census.
2026-10-13 20:34:18 +08:00
esengine f4acb67608 revert(theme): drop the unreleased Neutral Blue pack in favour of Clear Blue
Refs #12909
2026-10-13 05:30:22 -07:00
xxoingr 3a402a47d6 test(gitcommit): stage a literal glob name Windows can create
TestOnlyChangedPathsAreAcceptedAndTheyAreLiteral fails on every Windows
run since 40e097879 (studio run 38383059323): it writes a file named
"*.txt", and a Windows file name cannot hold "*" ("The filename,
directory name, or volume label syntax is incorrect.").

The name now is "[ot][nw][eo].txt". As a git pathspec glob it matches
one.txt and two.txt, as "*.txt" did, and every filesystem accepts it, so
the test still proves Stage passes each path as :(literal) on all three
platforms.
2026-10-13 20:26:00 +08:00
YHH b5b7cf49e6 Merge pull request #12569 from xxoingr/fix/skill-shadow-warning-literal
test(installsource): cover hostile skill paths in discovery warnings end to end
2026-10-13 05:13:15 -07:00
YHH 70025d2bf7 Merge pull request #12940 from chuiwenwei/fix/memory-suppressed-recall-audit
fix(memory): audit the recall a carried note suppresses
2026-10-13 05:08:47 -07:00
YHH 68801c9d0b Merge pull request #12939 from chuiwenwei/fix/memory-migrate-index-failure
fix(memory): repair the index when a migrated fact is revisited
2026-10-13 05:08:31 -07:00
YHH 80ea09baa3 Merge pull request #12938 from chuiwenwei/fix/memory-save-index-failure-recovery
fix(memory): withdraw a save whose index publication fails
2026-10-13 05:08:15 -07:00
YHH 5ba9a614f0 Merge pull request #12944 from esengine/fix/activity-group-sticky-header
fix(studio): keep an open activity group's header at the top while it scrolls
2026-10-13 05:07:33 -07:00
YHH 2d2988e92f Merge pull request #12899 from XTLine/fix/hook-backup-identity
fix(configbackup): preserve distinct native hook identities
2026-10-13 05:00:35 -07:00
esengine a5eab68235 fix(studio): keep an open activity group's header at the top while it scrolls
Fixes #12931
2026-10-13 04:55:54 -07:00
xiexian 472f1a89ca feat(studio): open conversation file links in the workbench 2026-10-13 19:52:54 +08:00
YHH 2f2919b55b Merge branch 'studio' into fix/skill-shadow-warning-literal 2026-10-13 04:52:44 -07:00
SivanCola 9e306466b0 Merge studio and preserve sidebar and session-list release notes
Problem: The latest studio session-list release notes conflict with the
sidebar-control entry in this PR.

Root cause: Both changes prepend their notes at the same unreleased anchor.

Fix: Preserve the upstream session-list and theme notes, append the sidebar
entries in the existing flat-list format, and retain narrow-only drawer
controls alongside the upstream spacing changes.

Verification: Sidebar/divider, session-list, remote-title and spacing tests;
production build, typecheck, repolint, UI census and fixture verification,
and release-note/reference tests.
2026-10-13 19:51:21 +08:00
YHH 02a9021e85 Merge pull request #12407 from f1shsmell/feat/spacing-scale
feat(styles): give layout spacing an eight-step scale
2026-10-13 04:49:15 -07:00
Will e0d4357c7c fix(memory): audit the recall a carried note suppresses
A turn that already carries a memory note skips automatic recall, because the
note says the same thing better. That decision was recorded for the session's own
diagnostics but never reached the audit channel, so a trajectory could not say
that a real recall was suppressed and why.

The suppression now goes through the same content-free audit as a recall that
returned hits. A turn whose retrieval is disabled by ablation is unchanged.
2026-10-13 19:41:18 +08:00
Will 1a0e08e79f fix(memory): withdraw a save whose index publication fails
The plain save path, which covers a new fact and an update that keeps its name,
wrote the fact and then published the index. A failed publication returned the
error with the fact already written, so a retry of the same intent either
advanced the revision again or was refused by the revision guard the caller had
just satisfied.

The write is now withdrawn when the publication fails: a new fact is removed, an
update is rewritten from the bytes and mode that were on disk — not re-rendered
from the parsed fact, which would silently drop frontmatter this code does not
know, comments and hand-written formatting. If the withdrawal itself fails the
error carries the state it left.
2026-10-13 19:40:31 +08:00
YHH 0876ccc422 Merge pull request #12935 from SivanCola/codex/studio-compact-session-list
fix(studio): compact session rows and relocate batch selection / 紧凑会话列表并迁移批量选择入口
2026-10-13 04:33:17 -07:00
SivanCola 9bda2b6328 fix(studio): integrate brand marks with current Studio contracts
Problem: the brand patch conflicted with Studio and failed the sidebar and
type-scale contracts; the empty-session symbol replayed an entrance on mount.

Root cause: the presentation change retained old fixture expectations, typed
subtitle sizes directly, and treated a mounted brand as a new event.

Fix: integrate the current Studio base, retain its sidebar controls, update the
wordmark assertion, reuse shared size tokens, keep the hero symbol still, and
document the final brand contract without stylesheet commentary.

Verification: frontend typecheck/build/census, affected tests, Chromium guards,
go vet, make lint/check, and builtin/boot tests passed. Full frontend validation
is recorded in the PR; CI is evaluated on this merge's head before delivery.
2026-10-13 19:27:05 +08:00
SivanCola 8d406040e3 test(studio): strengthen Linux package and release verification
Problem: Studio packages need payload and installed-shell acceptance, and a
complete artifact gate before publication. Running the Electron and matrix
contracts on Windows also exposed host-dependent test fixtures.

Root cause: #12914 supplies native Linux ARM64 packaging but checks only Go
payload architectures and selected artifact names. The macOS bundle fixture
used the host OS's temporary path, and the matrix fixture relied on PATH
lookup for a mock git command instead of binding it in the tested shell.

Fix: Keep upstream native runners and system FPM, consolidate ELF checks,
verify both Linux package formats, exercise the installed shell, and require
all twelve nonempty release artifacts. Guard installation acceptance with
GITHUB_ACTIONS=true. Use platform-specific workspace fixtures and a shell
function for the matrix's git fixture, keeping all Windows test coverage.
Rebase on current studio and preserve adjacent release notes.

Verification: Electron 143 tests and Linux/script 39 tests pass, along with
workflow contracts, desktop Go tests, builtin/boot regressions, vet, lint,
repository checks, shell syntax and Actionlint. Previous-head hosted native
Linux package verification and installed acceptance passed for amd64/arm64;
replacement-head Windows CI remains the platform acceptance gate.

Refs #12906
2026-10-13 19:18:07 +08:00
SivanCola e41eaa3720 Merge studio to resolve the sidebar PR release-note conflict
Problem: The sidebar-control change conflicts with the theme release notes
added to studio after the PR was opened.

Root cause: Both branches append their next-release entries at the same
location in the unreleased notes.

Fix: Preserve both sidebar and theme entries, use the accepted release-note
heading, and keep the bilingual sidebar entries within the prose budget.
The auto-merged stylesheet retains narrow-only drawer controls and current
theme foundations.

Verification: Full frontend tests, typecheck, production build, repolint,
UI census and fixture verification, and release-note/reference tests.
2026-10-13 19:15:44 +08:00
XTLine ece6e8d45f fix(configbackup): align restore identity and minimum format 2026-10-13 04:13:14 -07:00
SivanCola 4240e6cbf7 Merge studio to resolve release-note conflict
Problem: PR #12935 became conflicting after the Studio palette changes landed.
Root cause: Both branches appended bilingual entries to the same unreleased
notes, and both touched the shared Studio stylesheet.
Fix: Merge studio while preserving the session-list and palette release notes.
Keep the upstream theme tokens alongside the compact session-row rules.
Verification: gofmt, go vet, make lint and make check passed. Frontend typecheck,
production build, UI census, all 92 focused session and theme tests, and the
light/dark 34px row geometry guard passed. Builtin, boot and theme Go tests
passed. Hosted CI still requires upstream workflow approval.
2026-10-13 19:11:33 +08:00
SivanCola 94cac74614 feat(studio): 空会话态改用产品符号,RMark 退回它该干的活
RMark 是运行状态指示器,不是品牌:pane 会按状态把它染成 --net / --ok /
--accent-ink,stroke-dashoffset 说的是"这一轮还在跑"。可空会话态那个
78px 的 R 用的是同一个组件,等于让一枚状态灯去代表品牌。

换 SymbolMark —— logo-symbol.svg 的路径,也就是 main-v2 顶栏在 22px 上
用的那一枚。内联而非 <img>:这一处用 --faint 画,<img> 只会给我们文件
里写死的蓝。main-v2 的 channelIcons 也是这么内联的。

两处行为变化,都是换对方向的结果:

- 不再随运行状态变色。品牌标记不该在 pane 忙的时候变成 --net。
- 描边生长动画换成淡入升起。drawin 走的是 stroke-dashoffset,那是描边
  的把戏,填充没有。@keyframes drawin 随之删除 —— 它已无引用。

15px 那处(R 空闲)保留 RMark:尺寸上字标不可用,符号的装饰细节也会
糊,而且它本来就是在报状态。

reduced-motion 里补了 .hero .sym-mark —— motion.test.tsx 的原则是加了
动画就得同时声明它不许动。
2026-10-13 19:05:53 +08:00
YHH 98712b569b Merge pull request #12932 from SivanCola/codex/builtin-clear-blue-tea-green
feat(theme): add Clear Blue and Tea Green palettes / 新增清白蓝与茶绿内置配色
2026-10-13 04:05:03 -07:00
SivanCola 0b3698342f fix(studio): compact session rows and move batch selection into the machine menu
Problem: The session sidebar mixes one- and two-line titles and reserves too
much vertical space. The persistent local-selection entry adds another blank
row between the filters and the machine tree.

Root cause: Studio clamps titles to two lines with a 42px minimum row height
and vertical margins; SessionBatch renders its entry even outside selection.

Fix: Use 34px single-line rows with full-title hover hints, move local batch
selection into the machine menu, and restore focus to that menu on exit.
Keep batch archive/delete and neighbouring row actions intact. Add browser
geometry coverage to Studio CI, interaction regressions, parity/release notes,
and before/after screenshots for light/dark wide and narrow windows.

Verification: Go formatting, vet, lint, builtin/boot tests, frontend typecheck
and build, UI census, browser geometry and affected interactions passed.
The frontend full run passed 4156 of 4157 tests; the unchanged HTML-preview
failure passed all 26 tests on isolated replay. The optional translation scan
reports the same two missing test-only keys on the base and this branch.
2026-10-13 18:58:08 +08:00
SivanCola fc0a5c71f8 fix(studio): 欢迎页字标放大到 300px
200px 在整屏的空场里显得比改动前还弱 —— 原来那行 40px 等宽的
"Reasonix Studio" 约 310px 宽,换成字标后整体反而缩了一圈。

放大还有个附带好处:300 / 118(侧栏字标宽)≈ 2.5,副标 20px 正好
接近侧栏 7.5px 的等比放大值,比 200/20 的比例更协调。

窄屏折叠态同步从 168px 提到 240px。折叠断点是 640px,.oobe-scene
左右各 40px,内容区 560px,余量充足。
2026-10-13 18:56:58 +08:00
YHH 0261d13269 Merge pull request #12929 from xxoingr/test/plugin-reader-schema-race
test(plugin): wait for the handshake's cache write before changing a schema
2026-10-13 03:56:25 -07:00
YHH 9b91f76517 Merge pull request #12930 from xxoingr/test/survey-partial-count-budget
test(storage): give a stalled walk room to reach its first entry
2026-10-13 03:56:05 -07:00
SivanCola 5c1869a2c1 feat(studio): 欢迎页字标改用正式资产,保留品牌本色
原来那个大 R 是手写 SVG 描边路径,本仓库第四个各画各的 R。换用
logo-wordmark.svg,与侧栏、向导同一份资产。

这一处不 mask、不反白:整个产品里最该亮出品牌本色的就是这个开场,
染成场景的暖灰反而是退让。深色底上 #0153e5 约 2.5:1,低于非文本图形
3:1 的门槛,所以补一层稳定的 drop-shadow 托住。

原来 R 描边上那道暖色光痕(lume,1.25s 起 1.5s)保留成"点亮"的瞬间,
落在同一个节拍上。

删掉 oobedraw / oobetrace / oobesweep —— 前两个只服务那段描边路径,
第三个原本扫的是文字的渐变底,字标不再走那条路。

mono.test.ts 里 oobe-brand / oobe-sub 两条例外的说明随职责更新:字标
换成了品牌资产(自身不说话),留在等宽字体里的是 STUDIO 那行和标语。
2026-10-13 18:51:20 +08:00
YHH b22f8f5f44 Merge pull request #12934 from xxoingr/test/control-async-waits-budget
test(control): bound inbox and edit-persist waits by testenv.Budget
2026-10-13 03:49:04 -07:00
SivanCola 950a85f20b feat(studio): 向导顶栏同样改用正式字标
侧栏换掉之后,连接模型服务这一屏的顶栏还留着旧的字母 R 方块,两条
title bar 摆在一起时能看出是两个不同的品牌。用与侧栏相同的 lockup 结构
替换,尺寸、副标排版和深色主题处理都取同一组值。

.
2026-10-13 18:38:35 +08:00
xxoingr e55b9e40e6 test(control): bound inbox and edit-persist waits by testenv.Budget
Three control tests failed on Windows CI at a fixed one-second wait for
something the controller does asynchronously:

- TestInboxDispatchKickDuringEmptyScanIsNotLost (run 38178550657) and
  TestRepeatedJobCompletionIsIdempotent (run 38283959118): "timed out
  waiting for inbox dispatch" from waitForInboxDispatch.
- TestEditPromptPersistsOriginalPrompt (run 38367692250): "edited prompt
  was not persisted before deadline" after its one-second poll.

Delaying the scripted runner or the provider by 1.2 s reproduces all
three messages. These waits only catch a hang, which is what
testenv.Budget is for; every one-second wait in inbox_dispatch_test.go
and the edit-persist poll now uses it. The 150 ms waits that assert
nothing happened are unchanged.
2026-10-13 18:32:59 +08:00
SivanCola 0636552ae3 feat(theme): add Clear Blue and Tea Green foundation palettes
Problem: Studio offers no plain built-in alternatives matching the white/blue
and warm/tea-green directions requested in #12909.

Root cause: the default swatch inherits active pack colours, plain packs lose
the default reading surface and weight, and legacy Studio rules override
semantic accent colours on selected controls and dark send buttons.

Fix: ship two light/dark token-only packs, share canonical default preview
colours, preserve plain reading styles, and close the affected controls over
theme tokens. Add optional accentInk, localized labels and backdrop grouping.

Verification: 209 frontend tests, production build/typecheck, theme and HTTP
server tests, go vet ./..., make lint, make check, builtin and boot tests.
Browser checks cover system-following, default restoration, and light/dark
workbenches at desktop and phone widths; screenshots are included.
2026-10-13 18:27:58 +08:00
xxoingr 0f9c2195fb test(storage): give a stalled walk room to reach its first entry
TestSurveyReturnsPartialCountWhenAWalkStalls failed the coverage job on
studio (run 38341178074) with "the partial count must survive: 0 bytes,
0 files". It gave the walk a 50 ms budget and expected it to count the
one file before running out; the walk's callback checks the budget's
context first, so a walk goroutine scheduled late counts nothing. A
100 ms delay before the walk reproduces it, and the same delay fails
TestTruncatedRootKeepsTheErrorFoundBeforeTheBudgetRanOut, which relies on
the same 50 ms.

Both now use stalledWalkBudget, 500 ms: ten times what the failure needed,
while Survey's three stalled roots still return well inside the test's
5 s guard.
2026-10-13 18:27:37 +08:00
SivanCola 132696f9eb fix(studio): 字标与副标改为纵向堆叠
上一版把 <img> 和 <small> 都放在 .studio-brand 这个 flex 行里,STUDIO
被排到了字标右侧。真机跑起来才看出来 —— 单看代码不容易发现 flex 行里
多一个兄弟节点意味着什么。

套一层 .studio-brand-lockup 用 grid 排两行,字标在上、副标在下。
2026-10-13 18:26:25 +08:00
xxoingr dd498e42c2 test(plugin): wait for the handshake's cache write before changing a schema
TestReaderIntentRefusesDispatchAfterSafetyDrift failed the race job on
studio (run 38334190859): the test replaces rt.schema at
plugin_test.go:1765 while the handshake's detached cache write
(saveHandshakeSchema -> cacheableToolsOf, cache.go:44) can still be
reading it. Nothing in the product writes a remote tool's schema after the
handshake; only this test does.

The test now drains host.bgWrites, which every detached writer joins,
before replacing the schema. With the cache write delayed by 300 ms, the
cached schema was the test's replacement before this change and the
handshake's own after it.
2026-10-13 18:24:11 +08:00
YHH 90f6ccff18 Merge pull request #12928 from esengine/feat/builtin-neutral-theme
feat(theme): ship a neutral white and blue pack
2026-10-13 03:23:43 -07:00
SivanCola 429765e490 feat(studio): 侧栏品牌区改用正式字标,补回 STUDIO 副标
侧栏原来自己画了一套品牌:StudioIcon 里内联的描边 R,配 CSS 圆角方块和
HTML 排的 reasonix/STUDIO 文字。这个 R 和正式品牌资产没有任何关系,是本
仓库第四个各画各的 R(另三处在 Welcome.tsx、Onboarding.tsx 和托盘)。

改成用 logo-wordmark.svg —— 资产已经随 frontend-next 引进来了,内容与
main-v2 那份逐字节一致,只是之前没人接线,app.css:181-185 那条引用它的
.brand 规则一直是死的。

main-v2 的字标里没有 STUDIO,整块照搬会让这个界面不再自称 Studio,与
studio 作为独立品牌的前提冲突,所以副标用 HTML 补回,字标按 main-v2 的
尺寸(118×24)与负边距(-14px)照搬。

一并处理的:

- 深色主题沿用 main-v2 的 brightness(0) invert(1)。SVG 内写死了
  #0153e5,而 <img> 没有颜色接口,这是唯一能把它转白的手段 —— 代价是品牌
  色在深色下被整个作废。改走 CSS mask 可以避免,但那就不是照搬了。
- .studio-collapse 保留。studio 的品牌行右侧带这个按钮,main-v2 没有。
- .studio-brand-mark / -word / -accent 三组类删除,decorative-tokens.test.ts
  里那条断言 .studio-brand-accent 的用例随之移除(该测试对选择器找不到是
  直接失败的)。
- StudioIcon 里的 brand 路径随之失去唯一引用,一并删除。
2026-10-13 18:18:23 +08:00
esengine ace76d7fe2 feat(theme): ship a neutral white and blue pack
Refs #12909
2026-10-13 03:12:16 -07:00
YHH c027688879 Merge pull request #12922 from KHG420/codex/studio-completion-badge
feat(studio): badge unread task completions on the application icon
2026-10-13 03:08:14 -07:00
YHH 43a4bb1775 Merge pull request #12914 from esengine/feat/studio-linux-arm64
feat(release): ship Studio for Linux arm64 (.deb and .rpm)
2026-10-13 02:56:08 -07:00
esengine 22cfb53ac9 test(release): the publish gate now requires twelve packages, each Linux one by name 2026-10-13 02:46:47 -07:00
YHH 660e5a2e82 Merge pull request #12923 from esengine/feat/stage-from-changes
feat(studio): stage and unstage changes from the changes list
2026-10-13 02:46:06 -07:00
esengine 40e097879d feat(studio): stage and unstage changes from the changes list
The commit button only records what is staged, and Studio had no way to stage:
an empty index sent people to git add. The changes list now shows each file as
staged, partly staged or not, ticks and unticks them, and stages everything at
once; the kernel accepts only paths git itself lists.
2026-10-13 02:38:38 -07:00
SivanCola d097836293 fix(studio): use the divider as the desktop sidebar control
Problem: The desktop sidebar has a redundant top hamburger button alongside
its divider, making the sidebar affordances ambiguous.

Root cause: The drawer entry is mounted in all layouts, although desktop
layouts already expose a divider that toggles and resizes the workspace rail.

Fix: Mount the top entry only under the scene fold, remove its desktop chrome
spacing, and preserve the existing divider and remembered width. Document
the intentional control removal and include light/dark responsive screenshots.

Verification: 488 frontend test files and 4141 tests pass, including divider
interaction and responsive boundary coverage. Typecheck, production build,
repolint, UI census and fixture verification pass. Browser interactions and
an isolated macOS Electron window verify the retained controls.

Refs #12917
2026-10-13 17:23:23 +08:00
xiexian c6cf583607 feat(studio): badge unread task completions on the application icon 2026-10-13 17:17:59 +08:00
yhh 14f3d5c24c feat(studio): add rich task workspaces
Add linked maps, comparisons, dashboards and workspace-backed photo acquisition
and viewing for desktop task surfaces.

Certify Mantine viewer interactions and isolate Windows command-line and direct
DNS test fixtures from host process/proxy behaviour.

Cache impact: shortened write_file schema changes tool/prefix hashes once on
upgrade (5572 to 5524 schema tokens); system prompt stays unchanged. Update the
boot goldens for that deliberate contract change. Image discovery stays hidden.
2026-10-13 02:16:47 -07:00
esengine aebcc9ed26 feat(release): ship Studio for Linux arm64 (.deb and .rpm)
The release builds linux/arm64 on a native ubuntu-22.04-arm runner and requires
all four Linux packages by name. electron-builder's own fpm is x86_64-only, so
the arm64 leg installs one from RubyGems. The package job checks that the Go
binaries inside the .deb are built for the architecture the package is named for.
2026-10-13 02:03:57 -07:00
YHH a41ce9f980 Merge pull request #12904 from XTLine/fix/quiet-workbench-polls
fix(studio): avoid unchanged workbench polling renders
2026-10-13 01:53:42 -07:00
Guan09 9696ae040c feat(studio): add rename dialog and latest-message auto naming 2026-10-13 16:52:51 +08:00
YHH 6114a4f7ec Merge pull request #12889 from XTLine/fix/mcp-tool-name-collisions
fix(mcp): reject colliding model-visible tool names
2026-10-13 01:50:53 -07:00
YHH d1cc96d1c6 Merge pull request #12893 from XTLine/fix/studio-mcp-capability-identity
fix(capability): preserve raw MCP identities across runtime paths
2026-10-13 01:48:31 -07:00
YHH 1f2e9c9687 Merge pull request #12892 from XTLine/fix/mcp-lazy-raw-identity
fix(mcp): preserve cached raw identity during lazy dispatch
2026-10-13 01:44:32 -07:00
YHH 669720b3fc Merge pull request #12908 from esengine/fix/trust-level-bar
fix(studio): end a machine row's actions on the same edge as a project row's
2026-10-13 01:42:48 -07:00
esengine 7a1b699780 fix(studio): end a machine row's actions on the same edge as a project row's 2026-10-13 01:33:56 -07:00
YHH 181b0d8eed Merge pull request #12888 from esengine/fix/subagent-status-not-droppable
fix(serve): keep sub-agent status frames when a client falls behind
2026-10-13 01:19:17 -07:00
YHH 3897e34dc2 Merge pull request #12907 from esengine/fix/notification-click
fix(notify): hand the foreground right to the shell on a toast click, and stop discarding send failures
2026-10-13 01:17:23 -07:00
esengine 294da726f7 fix(serve): keep sub-agent status frames when a client falls behind
The status channel of a child's progress rides ToolProgress, which the
broadcaster treated as droppable. Its terminal phase has no successor, so a
backlogged client lost it and kept showing the child as running.
2026-10-13 01:12:15 -07:00
esengine 6896ae1040 fix(notify): hand the foreground right to the shell on a toast click, and stop discarding send failures
A click on a Windows toast grants the right to take the foreground to the
process it activates, which is the kernel host; the window to raise is the
shell's. The host now grants the right before forwarding the click, and the
shell lifts a window the platform refused to focus. A toast that could not be
delivered is written to the host log instead of being dropped.
2026-10-13 01:08:43 -07:00
YHH f88827ef93 Merge pull request #12905 from esengine/feat/session-rail-polish
feat(studio): tell the session tree's levels apart and make its row actions findable
2026-10-13 01:02:41 -07:00
XTLine 388ea158dd fix(configbackup): version distinct hook identity snapshots 2026-10-13 01:01:55 -07:00
esengine 09ce7f22c6 feat(studio): tell the session tree's levels apart and make its row actions findable
Machine rows become small section headers with counts in words, project rows keep
the strongest line with a numeric pill, scope filters hide zero counts and read at
12px, and the revealed + and ... buttons are outlined and share one hover, press,
expanded and busy state.
2026-10-13 00:53:52 -07:00
YHH 14c37fbc46 Merge pull request #12871 from xlyy1/codex/context-window-refresh-12752
fix(studio): refresh context after model settings change
2026-10-13 00:49:59 -07:00
XTLine dd7fe3cc0d fix(studio): keep unchanged file polls out of React commits 2026-10-13 00:49:53 -07:00
YHH 835977ec08 Merge pull request #12898 from XTLine/fix/preserve-workbench-file-drafts
fix(studio): preserve unsaved workbench file drafts
2026-10-13 00:37:39 -07:00
YHH ac006a6711 Merge pull request #12897 from esengine/feat/brand-app-icon
feat(studio): a flat, friendlier app icon, and the sidebar shows it
2026-10-13 00:30:26 -07:00
XTLine 3f00870fda test(configbackup): cover metadata and normalized hook imports 2026-10-13 00:29:01 -07:00
XTLine 7a112879d7 fix(configbackup): preserve distinct native hook identities 2026-10-13 00:28:32 -07:00
XTLine 54702a85d9 fix(mcp): classify and report rejected tool catalogs 2026-10-13 00:25:46 -07:00
XTLine f8e8d3add1 fix(studio): preserve file draft scope during initial status 2026-10-13 00:21:57 -07:00
XTLine d6b8820faf fix(studio): retain workbench drafts across file navigation 2026-10-13 00:21:46 -07:00
XTLine 7fdaf4ed2c fix(studio): scope file drafts and requests to their workspace 2026-10-13 00:21:37 -07:00
XTLine 6c9ba53a92 test(studio): reproduce unsaved workbench draft loss 2026-10-13 00:20:43 -07:00
esengine 0531f88217 feat(studio): a flat, friendlier app icon, and the sidebar shows it
The icon keeps the R-and-whale silhouette and drops the gold gradients for flat
colour: blue tile, yellow R, a blue whale with a pale belly, eye and cheek, and a
band of waves. The sidebar brand mark is the same file instead of a drawn glyph.
2026-10-13 00:20:07 -07:00
YHH 5c9e8cdb11 Merge pull request #12895 from esengine/feat/sync-reopen
feat(feedback): reopen the issue when a reporter replies to a fixed report
2026-10-12 23:59:39 -07:00
XTLine ccfe034d0a fix(mcp): reject unusable caches before registration 2026-10-12 23:53:42 -07:00
esengine 05a9bba1a0 feat(feedback): reopen the issue when a reporter replies to a fixed report
A reply to a fixed report means the fix did not hold. feedback-sync reopens an
issue closed as completed after mirroring the reply, and Studio offers the
reply box on fixed reports.
2026-10-12 23:47:19 -07:00
YHH 8ec2b41318 Merge pull request #12891 from esengine/feat/rail-new-session
feat(studio): start a new session from its own button on the icon rail
2026-10-12 23:40:28 -07:00
YHH 6811ad08c9 Merge pull request #12890 from esengine/fix/bare-skill-raw
fix(control): record the typed text as raw input for a bare skill invocation
2026-10-12 23:39:35 -07:00
XTLine 5626e1fccb fix(agent): require MCP bindings for callable grants 2026-10-12 23:36:46 -07:00
XTLine 9aa914a05c fix(agent): record the executed MCP capability identity 2026-10-12 23:36:35 -07:00
XTLine c7c15b1942 fix(mcp): preserve cached raw identity during lazy dispatch 2026-10-12 23:36:03 -07:00
XTLine 4e6207173e fix(capability): preserve MCP identities in catalog snapshots 2026-10-12 23:35:51 -07:00
YHH 8f010c8dce Merge pull request #12870 from esengine/fix/view-tab-slide
fix(studio): move one highlight between the pane's view tabs
2026-10-12 23:32:59 -07:00
esengine fc59f10772 feat(studio): start a new session from its own button on the icon rail 2026-10-12 23:32:36 -07:00
YHH 00aa05ccab Merge pull request #12887 from esengine/fix/git-bash-probe-timeout
fix(sandbox): retry a hung Git Bash probe once and print the fallback shell path unescaped
2026-10-12 23:28:56 -07:00
esengine 922fed0f32 fix(control): record the typed text as raw input for a bare skill invocation 2026-10-12 23:28:52 -07:00
XTLine 6348150ee7 fix(mcp): reject colliding model-visible tool names 2026-10-12 23:24:04 -07:00
esengine 1c24d30e41 fix(studio): move one highlight between the pane's view tabs
Switching views ran a view transition over the whole pane, so the tab bar was
cross-faded with it: the old tab's box faded out while the new one faded in,
read as two separate animations. The highlight is now one element that sits
under the chosen tab; during a switch the tab bar is not cross-faded and the
highlight slides and resizes to the new tab, with the labels above it.
2026-10-12 23:20:58 -07:00
yhh 1409a8ee35 feat(studio): add nonblocking surface actions 2026-10-12 23:16:33 -07:00
YHH df0d334dc5 Merge pull request #12886 from esengine/fix/close-blank-tab
fix(studio): close a browser tab without reading a body the kernel never sends
2026-10-12 23:15:26 -07:00
esengine 2679dcf7f6 fix(sandbox): keep control characters escaped in the fallback shell path 2026-10-12 23:14:21 -07:00
Will 3b8a9fcda5 fix(memory): repair the index when a migrated fact is revisited
MigrateV2 rewrote a legacy fact as V2 and then published its index line. If that
publication failed the migration reported the error with the fact already
rewritten, and the next run saw a fact that already had an id and a revision and
skipped it, so the index stayed stale with no path back.

A fact that already looks migrated is now reindexed instead of skipped, so the
second run converges on the index the first run could not publish.
2026-10-13 14:09:44 +08:00
YHH f00d90f4b5 Merge pull request #12877 from esengine/feat/file-add-to-chat
feat(studio): add a file or folder to the conversation from the Files menu
2026-10-12 23:08:22 -07:00
esengine 4f5a609bc9 fix(sandbox): retry a hung Git Bash probe once and print the fallback shell path unescaped 2026-10-12 23:07:04 -07:00
YHH 53774b03ef Merge pull request #12882 from esengine/feat/wait-streams-output
feat(tools): show what a waited-on job writes while wait runs
2026-10-12 23:06:24 -07:00
esengine 2705b57f2c fix(studio): close a browser tab without reading a body the kernel never sends
The kernel answers POST /browser/close with 204 and no body, and the port read
it as JSON, so every successful close reported "Unexpected end of JSON input".
2026-10-12 23:05:54 -07:00
esengine 3804d9f156 feat(studio): add a file or folder to the conversation from the Files menu 2026-10-12 23:00:08 -07:00
par73e 568b1792d7 fix(e2ebench): bind SFT exports to graded run provenance 2026-10-13 13:55:44 +08:00
par73e bd30cf6a5c fix(studio): keep an engaged ask from timing out 2026-10-13 13:55:37 +08:00
esengine d36858a9b1 feat(tools): show what a waited-on job writes while wait runs
wait now echoes the jobs' new output, as whole lines and capped at 64 KiB,
through the call's live progress, starting from their latest lines. The
frontend folded a progress frame like a dispatch, so the frame's empty name
blanked the step and each chunk replaced the last; it now appends the chunk
and keeps the call's name.
2026-10-12 22:53:03 -07:00
YHH d460758e7c Merge pull request #12878 from esengine/feat/ssh-hosts-group
feat(studio): gather remote machines under one foldable row in the sidebar
2026-10-12 22:30:46 -07:00
esengine 406e70378a feat(studio): gather remote machines under one foldable row in the sidebar 2026-10-12 22:21:00 -07:00
YHH 84c6690c61 Merge pull request #12875 from esengine/perf/workbench-switch
perf(studio): tween the workbench width on the pane, not on the whole app
2026-10-12 22:13:59 -07:00
esengine cb2552e5ae perf(studio): tween the workbench width on the pane, not on the whole app
--dock-w was a registered, inherited custom property tweened on .app, so every
frame of opening or closing the workbench restyled every node in the window.
It now does not inherit and lives on the pane whose grid reads it; the gutter
writes it there while dragging. Style recalculation while switching halves,
and the long frames on a throttled long transcript mostly go away.
2026-10-12 22:04:47 -07:00
YHH 861e35c3c6 Merge pull request #12874 from esengine/fix/hidden-folders-turn-currency
fix(studio): value a turn in the wallet's currency, and list hidden folders in the folder picker
2026-10-12 21:55:30 -07:00
esengine ab951a1bbf fix(studio): value a turn in the wallet's currency, and list hidden folders in the folder picker
With the display currency left automatic, the server adopted the wallet's
currency for its own ledger only: the quotes on usage events and the frontends
were never told, so the status bar showed this turn in US$ beside a CNY wallet.
The controller's Balance now adopts it and announces it as an operator notice,
the same path a chosen currency takes, so every quote and frontend follows;
a chosen currency still wins and nothing is written to the config.

The folder picker also shows hidden folders, where tools keep their state.
2026-10-12 21:44:49 -07:00
xlyy1 4d999069aa fix(studio): refresh context after model settings change 2026-10-13 12:38:23 +08:00
YHH d5fb91f607 Merge pull request #12869 from esengine/fix/window-edge-colour
fix(desktop): give the window frame the app's theme so Windows draws no light border
2026-10-12 21:38:17 -07:00
YHH 4041b48a8e Merge pull request #12865 from esengine/fix/session-tree-rows
fix(studio): one line per project in the session tree
2026-10-12 21:35:43 -07:00
Guan09 11b8ceb38d fix(agent): enforce file access before previews
Enforce workspace read policy before previews, diagnostics, hooks, and file execution.

Provide admitted previews to permission approval and Auto Guard, then capture fresh preimages after PreToolUse. Share target access checks across execution, planning, review, and delegated agents.

Keep genuinely write-only targets available, report existing-file overwrite refusals with write-specific messages, and avoid hook-write coverage gaps for read-only calls.

Preserve network-path protection before filesystem lookup while allowing authorized network roots and registered folder references.

Include baseline-compatible disclosure regressions, production target-access wiring checks, and localized tool-card tests.
2026-10-13 12:28:57 +08:00
esengine 1faa3c1bbd fix(desktop): give the window frame the app's theme so Windows draws no light border
Windows 11 draws a frameless window's border from the frame's scheme, which
Electron takes from nativeTheme. Studio never set it, so a dark theme chosen in
the app on a light system kept a light frame, seen as a light line around the
window. The frame now takes the stored theme (auto still follows the system),
at launch and whenever the preference is saved.
2026-10-12 21:28:15 -07:00
esengine 422715f257 fix(studio): one line per project in the session tree
A project row put its session count on a second line, so every project took
two rows. The count now sits where the row's actions are and gives way to them
on hover or keyboard focus, so a row is one line and the name keeps its width;
touch screens show the actions. A tag still takes a second line. The two add
buttons share one size and hover, and the filter labels and counts are larger.
2026-10-12 21:08:34 -07:00
YHH 40dd5cd9b2 Merge pull request #12856 from esengine/fix/sidebar-head-no-blank-strip
fix(studio): stop the sidebar head leaving a blank strip under the toggle
2026-10-12 20:59:02 -07:00
YHH fbd8d975b2 Merge pull request #12864 from esengine/fix/workbench-flush-divider
fix(studio): end the workbench card's tab strip on the pane's tab-row line
2026-10-12 20:55:47 -07:00
YHH 3e0b3a8aeb Merge pull request #12863 from esengine/fix/center-dialogs
fix(studio): centre dialogs on the window rather than below the title bar
2026-10-12 20:52:26 -07:00
esengine 90035ae541 fix(studio): end the workbench card's tab strip on the pane's tab-row line
The docked workbench card sat 8px into the pane with a 39px strip, so its
bottom line ran 7px below the conversation's tab row and the two read as
unrelated bars. The strip is now the shared tab-row height less the card's
inset and border, so both lines meet; the card keeps its corners and edge.
2026-10-12 20:45:43 -07:00
esengine a838930ad6 fix(studio): centre dialogs on the window rather than below the title bar
Settings, feedback, a full screenshot, a checkpoint preview and a sub-agent
transcript open in veils that start below the title bar, so centring inside
them put each dialog half a bar below the window's middle. Each veil now
carries a transparent bottom border as tall as the bar, which makes the box it
centres in symmetric; the phone layout keeps settings full screen.
2026-10-12 20:33:23 -07:00
yhh 6f7f0f8b78 feat(studio): add editable drafts and text diff previews 2026-10-12 20:20:12 -07:00
YHH 6e0e7f37cb Merge pull request #12860 from esengine/feat/verification-off-switch
feat(config): let the user turn off the checks the host asks of a changed turn
2026-10-12 20:19:38 -07:00
yhh 98395aafe4 feat(studio): add styled native surface forms and choices 2026-10-12 20:15:28 -07:00
YHH d15c8d5c89 Merge pull request #12859 from esengine/feat/esc-confirm-stop
feat(studio): ask for a second Escape before stopping a running turn
2026-10-12 20:14:34 -07:00
YHH 37760c66d3 Merge pull request #12857 from esengine/takeover/12288
feat(studio): open the project folder from the chrome folder mark
2026-10-12 20:13:57 -07:00
esengine f1a5eb9ebb feat(config): let the user turn off the checks the host asks of a changed turn
[agent] host_verification = "off" in the user file drops the check, review and
sign-off a turn that changed files owes before it may finish. The turn's own
unfinished plan and a capability the user required still hold it, and the
receipt still says what was and was not checked. A project file can neither
turn it off nor undo the user's choice.
2026-10-12 20:10:35 -07:00
esengine 3f3e3c9278 feat(studio): ask for a second Escape before stopping a running turn
Escape stopped a running turn at once, so a stray press ended a long task. The
first Escape now arms the stop and shows a hint for 2.5 seconds; a second one
inside that window stops. Appearance gets a per-machine choice to stop on the
first press again. The stop button is unchanged.
2026-10-12 19:55:47 -07:00
esengine 44e6f6adba fix(studio): show the folder button's pointer and hover only while it can be used 2026-10-12 19:41:29 -07:00
esengine 1b4792e8b0 Merge PR #12288 into studio and keep the project folder from launching an application bundle
openWorkspace now opens a folder only when it is a directory and, on macOS,
not an application bundle; anything else is selected in its parent as before.
2026-10-12 19:36:56 -07:00
YHH 2a951af216 Merge pull request #12855 from esengine/fix/sidebar-no-duplicate-nav
fix(studio): keep the sidebar toggle clickable in the desktop shell
2026-10-12 18:52:17 -07:00
esengine 5aeafaf9fa fix(studio): stop the sidebar head leaving a blank strip under the toggle
With the icon rail off or collapsed the whole sidebar head was indented 44px
further so the toggle had room beside the wordmark, which left a blank strip
beside the new-session, search and quick-entry rows. Only the wordmark row
makes room now; every other row sits at the same inset in all three modes.
2026-10-12 18:45:06 -07:00
esengine d0ec5b3d0b fix(studio): keep the sidebar toggle clickable in the desktop shell
The toggle was declared before the nav column, the sidebar head and the
chrome, all of which are window-drag regions in the desktop shell. Where drag
regions overlap, the one declared later wins, so a click on the toggle moved
the window instead. It is now the last child of the app; its z-index drops one
step so the settings sheet still covers it.
2026-10-12 18:38:24 -07:00
YHH c48ee0f80d Merge pull request #12852 from esengine/docs/release-2.38.0
docs(release): Studio 2.38.0 notes
2026-10-12 17:45:52 -07:00
esengine 10e929f3d5 docs(release): Studio 2.38.0 notes 2026-10-12 17:37:03 -07:00
YHH a750f83859 Merge pull request #12850 from esengine/fix/selection-from-data
fix(studio): hold the blocks a long selection passes through as plain text
2026-10-12 17:27:13 -07:00
YHH ff77cb75ff Merge pull request #12849 from esengine/fix/mcp-tool-name-128
fix(mcp): keep a model-visible tool name within the provider's 128-character limit
2026-10-12 17:26:10 -07:00
esengine 780d5e04fa fix(studio): hold the blocks a long selection passes through as plain text
A selection reaching into blocks that are not mounted mounted every block in
between in one synchronous render, so a drag across a long conversation froze
the window (22s at 1200 turns). A block the selection only passes through, with
neither end inside it, is now held as the conversation's own text at its
placeholder height; blocks with an end inside stay whole.
2026-10-12 17:14:37 -07:00
esengine b4060aa4fd fix(mcp): keep a model-visible tool name within the provider's 128-character limit
A tool whose prefixed name exceeded 128 characters made the provider refuse
every request in the session. toolName now shortens only the tool part,
keeping the server prefix and a hash of the full name so siblings stay
distinct and the name is deterministic for permission and hook rules.
2026-10-12 16:42:25 -07:00
Adalberto Pereira e66ef9f65a refactor(plugin): refresh a catalog through the one list-judge-build path
The refresh listed and built its own catalog, so it reached the model with
definitions the pin judgement #12597 added had never seen: a server could wait
until the session was running and announce a tool nobody approved. The judged
path now has one entry point for both listings — read, then publish — and the
refresh goes through it.

readToolCatalog takes no lock and publishes nothing, so the refresh keeps
building its candidate off the lock and swapping it in as one piece, and
publishToolCatalogLocked moves every field together: what was withheld was the
one a caller could forget, and it is the one that makes the judgement visible.
The digest covers the held set, so approving definitions the server had added
is itself a change the next listing publishes.

What the session does with that follows: the registry's held set is replaced
from the same judgement, as at every other registration site, and a pinned slot
whose tool is held says so instead of claiming the server dropped it.

hostNotices moves to sessionRuntime, which is its lifetime: a withdrawn slot
outlives the conversation that saw it. That also keeps agent.go inside its
file-size budget, and the catalog path moving to tool_catalog.go keeps
plugin.go inside its own, with no baseline spent.

Refs #11957
2026-10-12 19:33:59 -03:00
Adalberto Pereira ca39c4b7b3 fix(plugin): keep a pinned tool's schema when a refresh drops it / 刷新丢弃工具时保留已固定的 schema
A server that drops a tool it had pinned into the provider-visible array was
rewriting that array mid-session: the cache-stable prefix moved and every token
behind it was spent again. The entry now stays until the session ends, the call
it still invites fails with the live catalog's not-found, and the model is told
on the turn tail — the one place an append leaves the prefix byte-stable.

Registry.ReplacePrefix swaps a server's namespace under one lock. The removal
and the re-registration were two calls, so a turn composing its request between
them was built from a server that appeared to have no tools.
2026-10-12 19:08:21 -03:00
Adalberto Pereira d0c057dbd9 feat(plugin): refresh an MCP catalog on tools/list_changed / 收到 tools/list_changed 后刷新工具目录
A server that declared tools.listChanged may add, rename or drop tools after
startup and says so with notifications/tools/list_changed. All three transports
dispatched notifications/progress and dropped every other notification, so the
catalog stayed as first listed: a tool the server added was unreachable, and one
it removed was still offered to the model.

The transports now surface a notification by method, the client re-lists once a
burst settles, and the Host publishes a catalog that actually changed. The
session's registry re-registers it, so what the model can reach follows without
moving the cache-stable prefix.

Registrations live on the reconnecting transport and are rebound onto a
replacement connection, and a backend rolled in behind the stable proxy is
watched like any other, so a connection that dies does not leave the server
unheard for the rest of the session.
2026-10-12 19:08:21 -03:00
YHH ce84fe9ff8 Merge pull request #12848 from boscocp/fix/readiness-12567-unrecognized-command-write
fix(readiness): an unrecognised command clears the check only when it did not write / 未写入时才清除待验证
2026-10-12 15:03:15 -07:00
Adalberto Pereira 03aeb27e4e test(readiness): turn the shell jail off so the effect turn's commands run
The unrecognised-write effect test asserts on what the turn's commands
changed, but left the sandbox at its default. A runner that has no bwrap
refuses to run them at all, so the case that keeps the waiver owed the check
for a reason the test is not about: green on the `test` jobs, which install
bubblewrap, and red on `coverage`, which does not.

Every other effect test that needs a shell writes `[sandbox] bash = "off"`;
this one now does too. Reproduced with a PATH that hides bwrap, which fails
with the CI's exact line and passes with the fix.
2026-10-12 18:42:27 -03:00
Adalberto Pereira ea8f6c9ca6 fix(readiness): an unrecognised command clears the check only when it did not write / 未写入时才清除待验证
A turn that wrote code and then ran any command the classifier could not read
as a check was released as verified. The waiver was keyed on one fact — "not a
check" — while the receipt carried a second one it never read: whether the host
watched the command write. `echo … > generated.txt` after a code write is
graded MutationProven from the redirect itself, and the turn still finished
with the change unverified.

The waiver now also requires the grade the host already recorded, and requires
it of every unrecognised command after the write rather than the most recent
one: a quiet `ls` beside a writing command says nothing about that command. A
turn whose unreadable commands all left the tree as the host found it keeps the
waiver — that miss is the classifier's blind spot, not the turn's. A write the
host watched, and a change whose extent it could not establish, leave the check
owed: an incomplete observation never establishes scope, and unproven_mutation
is settled by observation rather than by a check that ran afterwards.

Ordinary turns only: Delivery gates verification through a corroborated
citation and never had this waiver.

Refs #12567
2026-10-12 17:37:55 -03:00
YHH 217702190c Merge pull request #12839 from esengine/takeover/12660
fix(theme): make wallpaper transcript glow configurable
2026-10-12 12:33:45 -07:00
YHH 71164ebf3c Merge pull request #12846 from xxoingr/test/session-viewed-late-heal
test(session): let the torn-sidecar view test outlast a late heal
2026-10-12 11:06:49 -07:00
YHH 057894edec Merge pull request #12847 from xxoingr/test/name-unsealed-evidence-cause
test: name the store's cause when a turn's evidence was not sealed
2026-10-12 11:04:48 -07:00
YHH 9f78a77e1c Merge pull request #12842 from XTLine/fix/studio-file-reference-boundaries
fix(studio): guard ambiguous file reference copies
2026-10-12 10:36:45 -07:00
YHH f9548ec3dd Merge pull request #12843 from thehui13/codex/fix-memory-recall-audit-12784
fix(memory): preserve recall omission causes in trajectories
2026-10-12 10:34:40 -07:00
xxoingr 1b6929aad4 test: name the store's cause when a turn's evidence was not sealed
Two tests that read a turn's sealed bundle have failed on Windows CI with
nothing that says why the bundle was missing:

- TestContractCriterionIsSatisfiedByItsOwnCheck (runs 38185765331 and
  38199681271): "trusted state tampered: malformed digest """, because the
  test read the empty Record of an unsealed audit as a digest.
- TestEffectSupportingWritesWithoutChecksMayFinish/markdown_only (run
  38252045191): "missing sealed evidence".

The audit already carries the store's typed cause. sealedRecord fails with
FailureCode and ContractFailure when the bundle was not sealed, and every
agent test that reads a sealed record goes through it; the boot test prints
the audits it found.
2026-10-13 01:30:28 +08:00
YHH cad221e1d6 Merge pull request #12845 from esengine/fix/pinned-sessions-lead
fix(studio): lead a project with its pinned conversations and mark them
2026-10-12 10:23:23 -07:00
xxoingr 458fbbcabf test(session): let the torn-sidecar view test outlast a late heal
TestSessionViewedToleratesASidecarCaughtMidReplace failed in the coverage
job (run 38264909143) with "a view that met a torn sidecar failed instead
of waiting it out" in 0.20 s. The view retries a torn sidecar for 20, 50
and 100 ms; the test heals it from a goroutine after 30 ms, so the heal had
140 ms of slack, and on a loaded runner the goroutine started later than
that. Delaying the heal to 250 ms reproduces the failure locally.

The test now appends a 5 s last retry to the product's schedule, the way
the meta-ledger tests already replace it, so a late heal is still waited
out. A reader that does not retry at all still fails the test.
2026-10-13 01:23:09 +08:00
yhh d68d34c811 fix(studio): lead a project with its pinned conversations and mark them
Pinning a conversation only fed the Pinned filter and the menu label, so in the
full list nothing moved and nothing showed it was pinned. Pinned conversations
now come first within their project, the kernel's order holding inside each
group, and each carries a small pin.
2026-10-12 10:14:40 -07:00
YHH 510915ba51 Merge pull request #11822 from esengine/feat/fix-11734-c
fix(plugins): publish a replaced plugin directory without renaming a watched tree on Windows
2026-10-12 10:14:13 -07:00
yhh 6db92a8904 refactor(plugins): keep the superseded-copy cleanup beside the copy helpers
plugin_package.go was over the 800-line ceiling.
2026-10-12 10:06:46 -07:00
YHH 586b3068c4 Merge pull request #12841 from esengine/takeover/11744
fix(studio): make session tabs opt-in
2026-10-12 10:04:37 -07:00
hashem e3985f1971 fix(memory): preserve recall omission causes in trajectories
Refs #12784
2026-10-13 00:52:29 +08:00
XTLine d9884c795c fix(studio): guard trailing backslash reference boundaries
(cherry picked from commit a33d4a2fe7af32b00bfd924e5897191e94911a48)
2026-10-13 00:52:12 +08:00
XTLine 6cc59ec78c fix(studio): avoid ambiguous workspace reference copies
(cherry picked from commit 1f547d9738e8bd030e84c8d2734ed25f79dac5c8)
2026-10-13 00:51:36 +08:00
yhh fbcc83cdc6 test(studio): turn the session tabs on in the tests that switch panes by tab
The tab strip is opt-in now, so tests written against an always-present strip
enable the setting first.
2026-10-12 09:47:02 -07:00
yhh 174571c2a9 Merge commit 'refs/pr11744' into takeover/11744
# Conflicts:
#	desktop/frontend-next/src/i18n/en_settings.ts
#	desktop/frontend-next/src/state/prefs.ts
#	desktop/frontend-next/src/ui/Appearance.tsx
#	desktop/frontend-next/src/ui/prefsnav.tsx
2026-10-12 09:41:51 -07:00
YHH d1f096b206 Merge pull request #12837 from esengine/takeover/12069
feat(studio): add a shared send shortcut preference
2026-10-12 09:26:35 -07:00
yhh 3bce33bff3 Merge commit 'refs/pr12660' into takeover/12660
# Conflicts:
#	desktop/frontend-next/src/port/look.ts
#	desktop/frontend-next/src/styles/TOKENS.md
#	internal/ext/theme/theme.go
2026-10-12 09:25:15 -07:00
YHH 2f27112578 Merge pull request #12836 from esengine/takeover/12311
feat(studio): quote selected reply text from the context menu
2026-10-12 09:18:47 -07:00
yhh a2ae61b9c0 Merge commit 'refs/pr12069' into takeover/12069
# Conflicts:
#	desktop/frontend-next/src/i18n/en_settings.ts
2026-10-12 09:17:26 -07:00
YHH 17589e87d5 Merge pull request #12835 from esengine/takeover/12349
fix(studio): batch-check models before adding a source
2026-10-12 09:09:26 -07:00
yhh 0818a41038 fix(studio): merge the selection menu with the reply branch action and copy label
CopyButton gained both a menuitem role and an aria label on studio, the
actions list gained reply.branch, and the new selection tests build a reply
action set that now also needs the branch pair.
2026-10-12 09:08:38 -07:00
yhh 4473c23aa5 fix(studio): batch-check models on the protocol being picked
The edit form can change a connection's protocol, so the shared batch
hook is given the draft protocol rather than the stored entry's.
2026-10-12 08:59:48 -07:00
yhh 0a647e12ff Merge commit 'refs/pr12349' into takeover/12349
# Conflicts:
#	desktop/frontend-next/src/ui/AddProvider.tsx
#	desktop/frontend-next/src/ui/EditConn.tsx
2026-10-12 08:55:21 -07:00
YHH f43cc2af32 Merge pull request #12818 from esengine/takeover/12808
feat(studio): add file path and reference copy actions
2026-10-12 08:46:09 -07:00
yhh 101e82b5bb fix(plugins): removing a plugin reclaims the copies a replacement left beside it
A replacement published beside the old tree keeps the old tree, so an
uninstall that removed only the active root left the managed directory
behind. Removal now also deletes the canonical copy and earlier staged
copies, skipping any directory the registry still points at.
2026-10-12 08:42:16 -07:00
yhh 21219f6890 test(plugins): compare the publication fault with errors.Is 2026-10-12 08:38:14 -07:00
yhh 02a8abeec3 Merge remote-tracking branch 'origin/studio' into own/12818
# Conflicts:
#	desktop/frontend-next/src/ui/WorkbenchPanel.tsx
2026-10-12 08:36:07 -07:00
YHH 7fbcc02b92 Merge pull request #12832 from esengine/takeover/12824
fix(studio): open the focused session files from navigation
2026-10-12 08:34:04 -07:00
YHH 17af885a67 Merge pull request #12831 from esengine/takeover/12814
feat(studio): add persistent event-driven rewind notice
2026-10-12 08:22:35 -07:00
yhh a81212d745 Merge commit 'refs/pr12824' into takeover/12824
# Conflicts:
#	desktop/frontend-next/src/ui/WorkbenchPanel.tsx
#	desktop/frontend-next/src/ui/browser-missed-read.interaction.test.tsx
#	desktop/frontend-next/src/ui/browser-open.test.ts
#	desktop/frontend-next/src/ui/html-preview.interaction.test.tsx
#	desktop/frontend-next/src/ui/useColumnOpening.ts
2026-10-12 08:21:06 -07:00
yhh cf1ca1ff06 fix(studio): classify the rewind change event and register the dismiss action
rewind_changed is a content-free invalidation a reopened pane replaces with
a read of /rewind/undo, so the wire log skips it, and the new close button
declares its action id.
2026-10-12 08:13:21 -07:00
YHH 7ee7a7fe8e Merge pull request #12826 from XTLine/fix/studio-workbench-launcher
fix(studio): offer a launcher from the workbench new-tab button
2026-10-12 08:05:40 -07:00
yhh c430ae5fe3 Merge commit 'refs/pr12814' into takeover/12814 2026-10-12 08:03:47 -07:00
YHH 06ff93fda2 Merge pull request #12829 from esengine/feat/mcp-held-approval
feat(studio): approve MCP tools whose definition changed
2026-10-12 07:54:54 -07:00
XTLine 9ea570231a test(studio): cover missed and superseded browser observations 2026-10-12 22:44:43 +08:00
XTLine ba06cdd16f fix(studio): reconcile browser navigation read boundaries 2026-10-12 22:44:33 +08:00
yhh 470fcfea8b feat(studio): approve MCP tools whose definition changed
A server whose tool definitions differ from what the user approved has those
tools withheld from the model, and the kernel already had an approval route
for them, but the settings row never said so and offered no way to approve.
The row now carries the withheld tools with how each differs, and a button
that sends back the digest of exactly that set, then reconnects.
2026-10-12 07:42:27 -07:00
XTLine 79f5f1f90b fix(studio): follow fresh activation after a missed tab read 2026-10-12 22:38:57 +08:00
XTLine 5a1e3655d4 fix(studio): preserve the latest eligible browser follow 2026-10-12 22:25:34 +08:00
XTLine c58f236dd8 fix(studio): track browser navigation read boundaries 2026-10-12 22:25:09 +08:00
yhh 50014383fb style(plugins): iterate directories backwards with slices.Backward 2026-10-12 07:13:53 -07:00
YHH 47056a93ee Merge pull request #11761 from esengine/feat/web-search-model
feat(studio): web search model setting in model preferences
2026-10-12 07:08:21 -07:00
yhh 6d98fbe7dd Merge remote-tracking branch 'origin/studio' into own/11822
# Conflicts:
#	desktop/frontend-next/src/port/plugin.ts
#	internal/ext/installsource/plugin_package.go
#	internal/ext/installsource/types.go
2026-10-12 07:06:41 -07:00
yhh 4395969694 fix(config): match the provider removal signature in the web search model test 2026-10-12 07:00:57 -07:00
YHH d48ba5e65b Merge pull request #11762 from esengine/feat/browser-fullscreen
feat(studio): wide mode for the browser pane that keeps the conversation visible
2026-10-12 06:57:23 -07:00
yhh 8931d3e612 Merge remote-tracking branch 'origin/studio' into own/11761
# Conflicts:
#	internal/model/anthropic/anthropic.go
2026-10-12 06:54:13 -07:00
XTLine c51e648adf test(studio): cover preview ownership and real browser closing 2026-10-12 21:50:46 +08:00
XTLine 6dd1650dca test(studio): cover launcher choices and interrupted browser opens 2026-10-12 21:50:34 +08:00
XTLine 0ee194932c fix(studio): open the workbench launcher from the new-tab button 2026-10-12 21:50:23 +08:00
yhh bcfbe73bc0 test(studio): identify the browser page by its address bar in the wide-mode tests
The page's iframe now exists only after a navigation and the browser opens
from the workbench launcher, so the tests open the launcher and compare the
address bar, which stays mounted across the toggle.
2026-10-12 06:49:46 -07:00
XTLine 06009af71b fix(studio): track browser opening and preparation lifetimes 2026-10-12 21:49:38 +08:00
YHH a0a0763fdd Merge pull request #12819 from chuiwenwei/fix/memory-archive-index-consistency
fix(memory): keep the index and the archive consistent when a publish fails
2026-10-12 06:46:13 -07:00
YHH 9ea250b5bf Merge pull request #12816 from chuiwenwei/fix/memory-restore-archived-retryable
fix(memory): keep a failed archived-memory recovery retryable
2026-10-12 06:44:48 -07:00
yhh 68eaa5069e Merge remote-tracking branch 'origin/studio' into own/11762 2026-10-12 06:41:44 -07:00
yhh 139554c91a feat(studio): recover extension surfaces and count question interactions 2026-10-12 06:37:28 -07:00
XTLine 6a30c3e3c6 test(studio): cover operation preparation, observation and reuse lifetimes 2026-10-12 21:34:55 +08:00
XTLine 0461622f70 test(studio): cover tab-bearing refusals and hidden preview reconciliation 2026-10-12 21:34:39 +08:00
XTLine 67da7b1930 test(studio): cover event and explicit file selection races 2026-10-12 21:34:21 +08:00
XTLine d5b7dd6809 fix(studio): route Files entries into the focused session explorer 2026-10-12 21:34:06 +08:00
XTLine 4357242a98 fix(studio): keep explicit selection ahead of superseded browser requests 2026-10-12 21:33:50 +08:00
XTLine 26768e54d6 fix(studio): track full browser-opening operations with API controls 2026-10-12 21:32:16 +08:00
YHH 8734ee8d7a Merge pull request #12821 from esengine/takeover/12805
feat(studio): add per-model display aliases
2026-10-12 06:19:36 -07:00
yhh 38e66576fc Merge remote-tracking branch 'origin/studio' into takeover/12805 2026-10-12 06:11:30 -07:00
yhh 49d8c158bc Merge commit 'refs/pr12805' into takeover/12805 2026-10-12 06:01:33 -07:00
yhh 931dda7061 fix(studio): drop the import the file menu extraction left unused 2026-10-12 05:55:20 -07:00
YHH abc23f9fea Merge pull request #12815 from esengine/takeover/12804
fix(studio): make crowded timeline signals inspectable
2026-10-12 05:54:39 -07:00
yhh 0626f197ff Merge commit 'refs/pr12808' into takeover/12808
# Conflicts:
#	desktop/frontend-next/src/ui/WorkbenchPanel.tsx
2026-10-12 05:52:09 -07:00
yhh ee85dd3481 Merge remote-tracking branch 'origin/studio' into takeover/12804 2026-10-12 05:44:22 -07:00
YHH 75910feefc Merge pull request #12803 from esengine/takeover/12328
feat(studio): show the current plan beside conversation navigation
2026-10-12 05:43:35 -07:00
Will 4f548643b0 fix(memory): keep the index and the archive consistent when a publish fails
Archive moved the active file into .archive and left the index flush to its
caller, so a failed flush kept the fact archived while MEMORY.md still named it,
with no way for a retry by stable id to tell which state the store was in.

The move is now undone when the flush fails, and a retry by stable id finds the
archived copy and drops the stale line. The test seam this needs is the one the
rename fix already added to the mainline.
2026-10-12 20:43:23 +08:00
YHH 113a01e1dc Merge pull request #12813 from esengine/fix/phone-quiet-keeps-branch-chip
fix(studio): keep the branch chip when the phone composer is quiet
2026-10-12 05:42:59 -07:00
yhh 9a26301d83 Merge commit 'refs/pr12804' into takeover/12804 2026-10-12 05:41:29 -07:00
XTLine 2e2b580bd8 merge(studio): retain aliases with browser and session controls 2026-10-12 20:41:01 +08:00
Will fa818864ee fix(memory): keep a failed archived-memory recovery retryable
RestoreArchived wrote the active file and reindexed it before removing the
archived copy, so a failure in either later step left the same id active and
archived at once, and the identity guard refused every retry.

Both steps now undo the restored file, so a failed recovery never leaves a double
identity and the same call succeeds once the obstruction is gone. If the undo's
own index write fails, a line naming the fact can remain; the retry rewrites it.
2026-10-12 20:39:50 +08:00
yhh c67fc94eb9 fix(studio): keep the branch chip when the phone composer is quiet
The quiet phone composer hid the branch pop along with the refine entry,
so the no-repository chip, which explains why git features are missing,
was unreachable at 390px and the branch-tooltip browser guard failed on
studio. Only the refine entry is hidden now; the composer guard asserts
the branch pop stays.
2026-10-12 05:35:31 -07:00
yhh 7e71b2dce4 fix(studio): translate the plan peek goal label
The English catalogue test fails on the untranslated PaneNav key.
2026-10-12 05:33:55 -07:00
YHH 2140e55491 Merge pull request #12809 from esengine/chore/perf-harness-styles
chore(perf): load the same stylesheets as the app
2026-10-12 05:27:55 -07:00
Guan09 7c93b587bf feat(studio): add persistent event-driven rewind notice 2026-10-12 20:26:26 +08:00
YHH 53a9bb6dc1 Merge pull request #12807 from esengine/takeover/12347
feat(studio): select local sessions for batch actions
2026-10-12 05:12:21 -07:00
yhh 0fbcd55ebd chore(perf): load the same stylesheets as the app
The perf page imported fewer stylesheets than main.tsx, so controls such
as the changed-files pill and the session row actions rendered unstyled
in anything captured from it.
2026-10-12 05:09:39 -07:00
XTLine fa278aedd5 test(studio): guard copied references and workspace changes 2026-10-12 20:09:13 +08:00
XTLine 567816d7d3 test(studio): cover file copy menu behavior 2026-10-12 20:09:04 +08:00
XTLine cfa255cc70 feat(studio): add file context copy actions 2026-10-12 20:08:36 +08:00
XTLine 459bc7f4f7 fix(studio): preserve activity duration while zooming 2026-10-12 20:06:11 +08:00
yhh be06337aa0 fix(studio): select the rows the paged sidebar actually shows
The sidebar no longer slices each project to a fixed 30 rows: it asks the
kernel for pages and draws every loaded row. Batch selection now takes
all displayed rows, the selection pruning moves into a hook beside the
toolbar so Workspaces.tsx stays under its ceiling, and the two tests that
assumed the old 30-row cut assert the loaded rows instead.
2026-10-12 05:04:28 -07:00
YHH d21d91f6b3 Merge pull request #12806 from esengine/takeover/12067
feat(studio): close browser pages individually or in bulk
2026-10-12 05:02:54 -07:00
yhh 6766cc0042 Merge commit 'refs/pr12347' into takeover/12347
# Conflicts:
#	desktop/frontend-next/src/actions.ts
#	desktop/frontend-next/src/ui/Workspaces.tsx
2026-10-12 04:59:51 -07:00
XTLine abbd487b95 feat(studio): edit and show model display aliases 2026-10-12 19:51:17 +08:00
XTLine 62ddce6804 test(studio): guard alias identity and persistence boundaries 2026-10-12 19:51:04 +08:00
XTLine f9e3f3a5e5 feat(serve): expose scoped model aliases in provider editing 2026-10-12 19:50:52 +08:00
XTLine a3518cc60a feat(config): persist exact per-model display aliases 2026-10-12 19:50:18 +08:00
XTLine b92becac71 fix(studio): make crowded timeline signals inspectable 2026-10-12 19:45:58 +08:00
yhh 91ceafc038 Merge commit 'refs/pr12067' into takeover/12067 2026-10-12 04:42:31 -07:00
yhh 8cb7d82c01 fix(studio): carry the plan peek rules in their own stylesheet
The rules pushed studio.css over its css-size budget, so they move to
planpeek.css, imported after studio.css.
2026-10-12 04:36:49 -07:00
YHH 1682ab7fad Merge pull request #12802 from esengine/takeover/12310
feat(studio): fold feedback conversations and preserve unread state
2026-10-12 04:36:37 -07:00
yhh a4f1729c22 Merge commit 'refs/pr12328' into takeover/12328 2026-10-12 04:34:00 -07:00
yhh 0ea0c0e0fc fix(studio): open feedback conversations that wait on the reporter
A folded thread hid the maintainer's question while the status said
the report needed an answer. A report with needsInput now starts open
and is acknowledged as seen like any opened thread. The existing
entry-badge test opens the threads it expects to be read, and the
needs-input test no longer clicks a thread that is already open.
2026-10-12 04:29:05 -07:00
YHH dfe972034d Merge pull request #12801 from esengine/takeover/12327
fix(studio): put the current model first in the composer picker
2026-10-12 04:24:16 -07:00
yhh d7b42cc072 Merge commit 'refs/pr12310' into takeover/12310 2026-10-12 04:23:49 -07:00
YHH 95822af03a Merge pull request #12800 from esengine/takeover/12331
fix(studio): compact the untouched phone composer
2026-10-12 04:15:49 -07:00
yhh a1e598fee9 Merge commit 'refs/pr12327' into takeover/12327
# Conflicts:
#	desktop/frontend-next/src/ui/modelmenu.ts
2026-10-12 04:12:34 -07:00
yhh 2ea1d8e01c fix(studio): carry the phone composer rules in their own stylesheet
The six rules move out of studio.css into phonecomposer.css, without
the two !important the budget does not have room for: the attach
button's display no longer forces itself, so the quiet-state hiding wins
by specificity. The quiet toolbar also stops wrapping its model group.
The composer guard clears the draft left by its earlier steps before the
phone section and the guard page imports the new stylesheet.
2026-10-12 04:07:10 -07:00
yhh 2b97f139d8 Merge commit 'refs/pr12331' into takeover/12331 2026-10-12 03:59:16 -07:00
YHH 9c371349a1 Merge pull request #12798 from esengine/fix/rail-toggle-in-settings
fix(studio): keep the sidebar toggle usable while a settings page is open
2026-10-12 03:51:55 -07:00
yhh 3478a1dc6e fix(studio): keep the sidebar toggle usable while a settings page is open
The toggle was disabled whenever the settings sheet was open, so after
pressing any icon in the rail the top-left button looked live and did
nothing. It now opens and closes the workspace column in settings too.
2026-10-12 03:40:37 -07:00
YHH 55d3ca45c1 Merge pull request #12785 from chuiwenwei/fix/memory-rename-active-identity
fix(memory): retire the old name before publishing a renamed fact
2026-10-12 03:35:45 -07:00
YHH fddc6512a8 Merge pull request #12793 from esengine/takeover/12466
fix(studio): refine default light theme hierarchy
2026-10-12 03:03:14 -07:00
yhh 5fb8925ca2 chore: keep the before/after screenshots out of the repository 2026-10-12 02:51:27 -07:00
yhh c8d2bac6fb Merge commit 'refs/pr12466' into takeover/12466 2026-10-12 02:51:26 -07:00
YHH f14e75f9b5 Merge pull request #12783 from esengine/fix/compaction-live-line
fix(studio): stop the transcript jumping while a conversation is being compacted
2026-10-12 02:51:06 -07:00
yhh 5b4f8f073a fix(studio): keep the live compaction progress line on one reserved line
The streaming summary line wrapped to a different height on every token
while the transcript was pinned to the bottom, so the card and everything
above it moved up and down for the length of the fold. The line is now
always drawn, one line high, truncated with an ellipsis.
2026-10-12 02:41:53 -07:00
YHH 7340ac3a68 Merge pull request #12782 from esengine/fix/tabs-blend
fix(studio): let the view tabs share the page's tone
2026-10-12 02:38:46 -07:00
yhh 22491271c0 fix(studio): let the view tabs share the page's tone
The conversation, run analysis and workbench tabs sat on their own lighter
band with a divider under it. The row now takes the page's tone and drops the
divider; with a wallpaper the existing wallpaper rule still gives it a stable
surface.
2026-10-12 02:28:11 -07:00
YHH 191abea93c Merge pull request #12778 from esengine/fix/browser-view-repaint
fix(electron): attach a parked browser page again when it is shown
2026-10-12 02:08:54 -07:00
YHH 2e46375e4f Merge pull request #12776 from esengine/takeover/12340
feat(studio): add project folders by dropping onto the sidebar
2026-10-12 02:03:13 -07:00
yhh 75a08f128c fix(electron): attach a parked browser page again when it is shown
Switching away from a built-in browser tab and back drew a white page until the
window was resized: a view parked offscreen and moved back with setBounds alone
keeps a widget that does not repaint. The shell already knows that attaching a
view again wakes its widget (it does so on minimize and restore); a page that
was parked now gets the same when it is shown.
2026-10-12 01:57:50 -07:00
esengine cbd9038b07 Merge studio into takeover/12340 2026-10-12 01:55:46 -07:00
YHH fe7c6c44e6 Merge pull request #12777 from esengine/takeover/12343
feat(studio): compact context from the ring and inspector
2026-10-12 01:54:05 -07:00
YHH 2874af8b0e Merge pull request #12775 from esengine/takeover/12318
feat(studio): offer quick archive and delete on session rows
2026-10-12 01:38:31 -07:00
esengine 6b9e50246a Merge branch 'pr12343' into verify/12343 2026-10-12 01:38:04 -07:00
esengine 091c1b2865 feat(studio): keep the folder-drop styles in their own stylesheet
#12340 added 18 lines to studio.css, which must not grow. The drop-hint rules
move to styles/sidebardrop.css, imported with the other feature sheets, and
the sidebar-drop fixture gains the Sidebar props that became required after the
branch point (navShown, paging) so the file typechecks again.
2026-10-12 01:32:44 -07:00
esengine bf0505e70e Merge branch 'pr12340' into takeover/12340 2026-10-12 01:30:21 -07:00
esengine 5851b802ad Merge studio into takeover/12318 2026-10-12 01:29:12 -07:00
YHH 970c91d138 Merge pull request #12774 from esengine/refactor/workspaces-extract
refactor(studio): draw the session menu in its own component
2026-10-12 01:22:10 -07:00
yhh 859ef4782c feat(studio): keep the quick-action styles in their own stylesheet
#12318 added 26 lines to studio.css, which must not grow. The quick-action
rules move to styles/sessionquick.css, imported with the other feature
sheets, and the paging test finds its filter chips inside the filter group so
the new archive and delete buttons, whose names also start with 归档, are not
mistaken for them.
2026-10-12 01:18:55 -07:00
verify 7f79763026 Merge branch 'pr12318' into takeover/12318 2026-10-12 01:15:47 -07:00
yhh 6d98474fdf refactor(studio): draw the session menu in its own component
Workspaces.tsx held the session row's menu inline, 63 lines of JSX that only
report which action was chosen. It is its own component now, with the same
markup and the same handlers, so the sidebar file has room for the quick
actions, folder drop and batch selection that are waiting to land in it.
2026-10-12 01:14:03 -07:00
YHH 8d1f46cb50 Merge pull request #12324 from KHG420/codex/studio-project-context-menu
feat(studio): open project actions from context menus
2026-10-12 01:10:58 -07:00
Will 58aef50d47 fix(memory): retire the old name before publishing a renamed fact
SaveWithOptions wrote the new active file, then removed the old one, then
repaired the index. A failure in either later step left one id readable as two
active facts or left MEMORY.md naming a file that no longer existed, and a retry
could not tell which state it was in.

The rename path now stages the new content, retires the old name out of the
active namespace, flushes the old index line, publishes the staged file, and
reindexes; every step before publication is undone from the retired copy. The
helper that removed the old file in place has no callers left and is deleted.
2026-10-12 16:04:52 +08:00
YHH f3287f7dfc Merge pull request #12772 from esengine/feat/write-lease-optimistic
feat(config): an optimistic write lease for sessions sharing a workspace
2026-10-12 00:46:21 -07:00
yhh 25c6d4c10d feat(config): an optimistic write lease for sessions sharing a workspace
Every writer that declared no extent (bash, MCP, a sub-agent given no
write_paths) held the whole workspace, so two sessions on one project queued
behind each other even when nothing they wrote overlapped. [agent] write_lease
= "optimistic" in the user file makes such a writer claim nothing; a writer
that declared paths, including paths that cannot be resolved, still excludes
other sessions. The mode is carried by each session's lease owner, so sessions
in one process keep their own, and a project file cannot change it.
2026-10-12 00:35:29 -07:00
YHH 00d7ece65a Merge pull request #12559 from XTLine/fix/studio-restore-undo
fix(studio): preserve restore undo across reload / 保留重载后的还原撤销入口
2026-10-12 00:33:10 -07:00
YHH fba020d9bb Merge pull request #12770 from esengine/feat/branch-from-reply
feat(studio): branch a conversation from any reply into its own session
2026-10-11 23:44:32 -07:00
yhh 4f4b43978b feat(studio): branch a conversation from any reply into its own session
Studio had no click entry for branching: /branch copied only the current tip and
swapped the pane onto it. A finished reply now carries a 'Branch into a new
conversation' button that copies the conversation up to the end of that reply's
turn into a new session, titled '<title> - branch' so it never reads as a second
copy of its source, and opens it while the original stays as it was.

Controller.BranchAt cuts at a live turn boundary and shares the file-and-meta
writing with Branch. POST /branch/at answers with the new path and refuses with
typed codes (running, unavailable, failed).
2026-10-11 23:36:04 -07:00
YHH e67f256d5b Merge pull request #12769 from esengine/feat/withdrawn-version-notice
feat(update): tell a user whose running version was withdrawn
2026-10-11 23:24:29 -07:00
yhh ee40a3b90e feat(update): tell a user whose running version was withdrawn
The catalog can now pull a published release, but a user already running it
saw an ordinary update. The version hub carries a withdrawn note for the
running version, mirrored on the desktop side, and the version panel leads
with it: which version, the publisher's reason, and where to move to.
2026-10-11 23:15:05 -07:00
YHH 0968731518 Merge pull request #12768 from esengine/feat/open-logs-menu
feat(studio): open the logs folder from the feedback form
2026-10-11 23:12:35 -07:00
yhh 3b131d66a4 feat(studio): open the logs folder from the feedback form
A report that needs a log (it will not start, the kernel keeps restarting) had
no way to say where the log is: Studio writes shell.log and host.log under the
user data folder but nothing opened it, and the Windows and Linux builds have
no menu bar to put an entry in. The feedback form now offers 'Open logs
folder' when the shell can open it; the shell names the folder, the page does
not.
2026-10-11 23:03:29 -07:00
YHH b71837c1e5 Merge pull request #12765 from xxoingr/test/goal-resume-notice-race
test(control): fix the resumed-goal notice before the goal can complete
2026-10-11 22:36:47 -07:00
YHH b88596a4fc Merge pull request #12763 from chuiwenwei/fix/e2ebench-analysis-failure-reporting
fix(e2ebench): restore before checking the base side and name what is missing
2026-10-11 22:34:49 -07:00
YHH 9290b3bc0d Merge pull request #12761 from xxoingr/fix/tui-esc-menu-cancels-turn
fix(tui): Esc in the completion menu also stops a running turn
2026-10-11 22:30:12 -07:00
YHH fb77904a9f Merge pull request #12762 from xxoingr/test/split-jump-resolve-real-ssh
test(remote): split jump resolution from the installed ssh
2026-10-11 22:29:46 -07:00
YHH 0522cd92e6 Merge pull request #12757 from chuiwenwei/feat/contextbench-batch-manifest
feat(bench): persist what a context-retrieval batch was beside its results
2026-10-11 22:22:13 -07:00
YHH 50e25025a0 Merge pull request #12563 from chuiwenwei/fix/e2ebench-tape-integrity
fix(e2ebench): record and report incomplete tape recordings
2026-10-11 22:21:06 -07:00
YHH 0c9d2455be Merge pull request #12767 from esengine/perf/rail-thin-marks
perf(studio): draw one conversation-rail mark per distinguishable position
2026-10-11 22:13:11 -07:00
YHH dc921776c3 Merge pull request #12682 from esengine/dependabot/go_modules/go-412d5d9764
build(deps): bump the go group with 2 updates
2026-10-11 22:10:50 -07:00
yhh 912659f0dd perf(studio): draw one conversation-rail mark per distinguishable position
A 2000-turn conversation drew 2000 buttons per rail, 0.27 px apart, and every
pointer move rewrote all of them. Marks closer than 2 px are not told apart by
eye or pointer, so only one of each cluster is drawn; the rest stay addressable
through their positions, so the pointer and the keys still land on every
message.
2026-10-11 22:06:15 -07:00
YHH 4ef274d02e Merge pull request #12766 from esengine/perf/noto-studio-slice
perf(studio): draw the first screen from a 1.9 MB core CJK face
2026-10-11 22:03:20 -07:00
yhh 79af107700 perf(studio): draw the first screen from a 1.9 MB core CJK face
The first screen fetched the 7.8 MB full Noto Studio font because every Chinese
character resolved to it. A core face with the common simplified-Chinese set,
Latin, punctuation and symbols now comes first, and the full font stays as a
later family limited to CJK ranges, so a character the core lacks still finds
its glyph there and only then pays for the download.
2026-10-11 21:55:36 -07:00
xxoingr c6a134cfa1 test(control): fix the resumed-goal notice before the goal can complete
TestGoalResumeCommandContinuesTheGoalAndSaysSo failed in the race job on
studio (run 38188087939) with "resuming the goal said nothing" in 0.00s,
while the turn ran and the goal completed. The test built the expected
notice from c.Goal() as it read each event, and completing a goal clears
its text; the buffered events channel lets the controller send the notice,
finish the turn and complete the goal before the test reads anything, so
the real notice was compared with one built from an empty goal.

The expected line is now built once, before /goal resume is submitted.
2026-10-12 12:46:20 +08:00
YHH 97b21b6ed7 Merge pull request #12759 from esengine/feat/withdraw-version
feat(update): withdraw a published Studio version from the catalog
2026-10-11 21:42:48 -07:00
xxoingr 8e9227ceff test(remote): split jump resolution from the installed ssh
TestResolveJumpHostsUsesConfiguredAliasesAndSSHConfig bound two things:
that ResolveJumpHosts reads ssh_config aliases and configured hosts, and
that this machine's real `ssh -G` answers. The second failed on Windows CI
again, twice on studio (runs 38179453323 and 38193303449), both at 15.0 s
with `ssh -G "bastion": exit status 1`: the product's own subprocess
timeout. The test's comment asked to split the two if it recurred rather
than shorten that timeout.

The resolution test now supplies the `ssh -G` answer through the source's
resolveOpenSSH seam, as the sshconfig tests already do, and keeps every
assertion. TestResolveJumpHostsThroughTheInstalledOpenSSH runs the same
alias through the real client, so a starved ssh fails under its own name.
2026-10-12 12:40:12 +08:00
Will 3b1a88db12 fix(e2ebench): restore before checking the base side and name what is missing
Reviewing #12562 left three points open in this code.

differentialPerTest checked the base side before registering the defer that
restores the source, so a failure from that check returned with the tree still on
base - the state the restore exists to prevent. The defer is now registered
before the check, and treeMatches is a variable so a test can inject the failure;
TestDifferentialPerTestRestoresWhenTheBaseCheckFails asserts the PR's content is
back after that failure, and deleting the reorder makes it fail.

The report printed one sentence for every analysis failure, claiming the pin and
mutation figures were both absent even when the pin table had already been
produced and only the mutation sweep failed. analysisFailureLine now says which
figures are missing, and a unit test pins both cases.

The comment above the restore helpers described what the code used to do; it now
states only the constraint it enforces.
2026-10-12 12:35:39 +08:00
YHH 900aa3aa3a Merge branch 'studio' into dependabot/go_modules/go-412d5d9764 2026-10-11 21:32:30 -07:00
YHH d18b248992 Merge pull request #12684 from esengine/dependabot/npm_and_yarn/desktop/electron/npm-c28431c45a
build(deps-dev): bump the npm group in /desktop/electron with 2 updates
2026-10-11 21:31:38 -07:00
YHH a6facc0adf Merge pull request #12758 from esengine/chore/update-acknowledgments
Update acknowledgments contributors / 更新致谢贡献者
2026-10-11 21:29:58 -07:00
xxoingr c9d1517846 fix(tui): Esc in the completion menu also stops a running turn
1.x closed an open slash or @ menu on Esc and, while a turn ran, cancelled
the turn as well. 2.x's menuKey consumed the key after closing the menu, so
a user who had started typing a follow-up during a run had to press Esc a
second time to stop it, while the thinking line said Esc stops it.

menuKey now cancels the running turn after closing the menu, except in vi
mode, where Esc never interrupts a stream. Idle, Esc on the menu still only
closes it and keeps the draft.
2026-10-12 12:27:58 +08:00
YHH 2e2c9248f5 Merge pull request #12760 from esengine/docs/sponsor-register-links
docs: add an inline registration link to each sponsor's paragraph
2026-10-11 21:21:30 -07:00
yhh 36d69bf9e1 docs: add an inline registration link to each sponsor's paragraph
The sponsor logos link out, but the paragraph beside them read as plain text,
so the entry point was easy to miss. Each paragraph now ends with a visible
link to the same registration address its logo uses.
2026-10-11 21:17:15 -07:00
yhh 8a4281abfb feat(update): withdraw a published Studio version from the catalog
A release found broken after publication could only be fixed by shipping a
newer one, and installs kept being offered it meanwhile. The catalog now
carries a withdrawn list: a withdrawn version leaves the list old clients
read, a new client never offers or installs it and names it when it is the
one running, and a rerun of its own publication cannot list it again.
scripts/withdraw-version.sh edits the catalog; a manual workflow runs it
against the Studio catalog and keeps the GitHub release off the latest slot.
2026-10-11 20:57:15 -07:00
SivanCola 268ccef0a8 docs: update acknowledgments contributors 2026-10-12 03:52:27 +00:00
Will 9eb14cc8cc fix(e2ebench): record and report incomplete tape recordings
A tape is recorded as three files per provider exchange, and every write
discarded its error. A recording that lost a file therefore looked recorded, and
replay could not tell the difference: with the recorded request missing,
requestDiff compared against nothing and reported a divergence, blaming the
harness for a tape that was never complete. Nothing in the run's own report
mentioned it either, so a run whose tape can never be replayed read as a success.

save now returns its error and writes request.json last, so that file's presence
marks an exchange complete; load reports a missing recorded request as an
incomplete tape instead of a divergence; and the meter counts failed writes and
names the first error, which the report prints.

Two review findings in the same boundary are fixed here too. The run path read
the meter through record, which snapshotted before the final handler's deferred
save could run; stopping the meter is what waits for those handlers, so record
now drains first and the last exchange cannot be missing from the report. And a
re-recording over an existing tape left that tape's completeness markers in
place, so a failure partway through read as an old request beside a new partial
response; recording now clears those markers before it starts.

The healthy path is pinned as well, so the failure cases are not the only ones
covered: a complete recording reports nothing incomplete and prints no line.
Deleting any of the four checks reproduces the old behaviour.
2026-10-12 11:45:41 +08:00
YHH 59b2b75e62 Merge pull request #12753 from esengine/fix/rail-icons-match-settings
fix(studio): draw each rail entry with the mark of the settings section it opens
2026-10-11 20:43:56 -07:00
Will 1724a154f8 feat(bench): persist what a context-retrieval batch was beside its results
A batch printed its own provenance to the terminal and kept the per-run records
on disk, so the two could not be read together afterwards. The fingerprint
(model, endpoint, window, generations, commit, start time) existed only in
scrollback, and every aggregate the report is read for — funnels, dose response,
pooled deltas — was printed rather than stored. Reconstructing a batch after the
fact, or attributing a figure in the README to the runs that produced it, meant
trusting a terminal transcript; the 24-run and 36-run totals in the last batch
are exactly that failure.

Each paying mode now writes batch.json beside results.jsonl: mode, start and
finish, the checkout's commit and dirty state, model, window, generations, the
task ids and arms the batch covered, planned against completed runs, the summed
usage of the runs that reported any, and the sha256 of both artifacts so a
manifest and the records it describes cannot drift apart.

Two things stay honest. Usage totals are absent when nothing reported any,
because an unmeasured cost must not read as a measured zero, while
usage_reported_runs is written even when it is zero, because that is a fact. And
a checkout git cannot describe is recorded as an unknown commit with no dirty
flag at all, rather than as a clean tree.
2026-10-12 11:38:25 +08:00
yhh 4c3eabc0ab fix(studio): draw each rail entry with the mark of the settings section it opens
The icon rail kept its own icon set, so the same destination showed one mark
on the rail and another on the page it opens. Entries that open a section now
read that section's mark from the settings table; only the conversation item
keeps its own.
2026-10-11 20:33:09 -07:00
YHH 4a0b3888cb Merge pull request #12734 from jf200s/fix/studio-win11-osversion
fix(feedback): report the Windows 11 product line in the environment version
2026-10-11 20:25:16 -07:00
YHH e17ef45e34 Merge pull request #12744 from jipeng6036-del/fix/12743-tree-empty-sessions
fix(serve): send an empty sessions list for a folder with no conversations
2026-10-11 18:48:13 -07:00
YHH a177a27c2a Merge pull request #12562 from chuiwenwei/fix/e2ebench-mutation-restore
fix(e2ebench): fail mutation and differential analysis when source restoration fails
2026-10-11 17:56:04 -07:00
YHH 6c3e554420 Merge pull request #12746 from esengine/notes237
docs(release): Studio 2.37.0 notes
2026-10-11 17:39:56 -07:00
yhh 6bb4839c93 docs(release): Studio 2.37.0 notes 2026-10-11 17:34:33 -07:00
YHH 2f0cf13a61 Merge pull request #12745 from esengine/argjson
fix(provider): replay a tool call with no arguments as an empty object
2026-10-11 17:27:45 -07:00
yhh 2f6b04b0eb fix(e2ebench): wrap the git error when the restored tree does not match
Clears the errorlint finding on restore.go.
2026-10-11 17:21:48 -07:00
yhh 64bfc5e0a0 fix(provider): replay a tool call with no arguments as an empty object
A history can carry a tool call whose arguments were recorded as an empty string
(a no-argument tool on a gateway that sends ""). Replayed as-is, a strict
gateway that parses every function_call.arguments as JSON refuses the whole
request with HTTP 400 ("Assistant tool call arguments must be valid JSON").

The repair that already closes truncated arguments now also turns empty or blank
arguments into {} on the way to a provider; a saved session keeps what it
recorded. The repair moves to its own file.
2026-10-11 17:17:50 -07:00
YHH 6f29d3d960 Merge branch 'studio' into codex/12026-browser-close 2026-10-11 17:04:14 -07:00
YHH b7ee3207da Merge branch 'studio' into codex/studio-feedback-thread-folding 2026-10-11 17:04:00 -07:00
YHH 80552ff2ff Merge branch 'studio' into codex/studio-refine-keyboard-help 2026-10-11 17:03:47 -07:00
YHH f808837d43 Merge branch 'studio' into codex/studio-plan-peek 2026-10-11 17:03:34 -07:00
YHH f08973d82e Merge branch 'studio' into codex/studio-skill-source-groups 2026-10-11 17:03:20 -07:00
YHH 99cb4c1754 Merge branch 'studio' into fix/e2ebench-mutation-restore 2026-10-11 17:02:54 -07:00
YHH 74fcba388c Merge pull request #12623 from esengine/fix/theme-remove-12351
feat(studio): delete a theme pack from Appearance
2026-10-11 16:52:23 -07:00
YHH 5e86094c4f Merge pull request #12594 from esengine/chore/remove-windows-update-helper
refactor(update): remove the unused Windows update helper from studio
2026-10-11 16:46:50 -07:00
YHH 3a9e5e4fc1 Merge pull request #12578 from esengine/fix/upgrade-npm-installs
fix(upgrade): leave npm-installed binaries to npm
2026-10-11 16:44:45 -07:00
yhh ff4e345924 fix(studio): declare the theme removal action in the action registry 2026-10-11 16:42:45 -07:00
yhh de3b4f9a8e Merge studio 2026-10-11 16:40:17 -07:00
yhh 8d869198bd Merge studio into chore/remove-windows-update-helper
The baseline had diverged from studio's. Took studio's baseline.json and
re-ran repolint -update, which only lowered budgets for the code this change
removes (70 lowered, 14 entries removed, none raised).
2026-10-11 16:39:33 -07:00
YHH 35e65c2ef2 Merge pull request #12555 from esengine/perf/boot-explicit-inputs
perf(boot): bind every storage root Build resolves to its stated home
2026-10-11 16:34:41 -07:00
yhh 980b34aa5f chore(i18n): keep the message catalogs at the 800-line ceiling
The managed-install notice added a line to messages_en.go and messages_zh.go and
pushed both over the ceiling. Drop one separator line from each.
2026-10-11 16:27:52 -07:00
yhh 6980dcfdc0 fix(upgrade): leave npm-installed binaries to npm
reasonix upgrade overwrote the prebuilt binary inside node_modules, so
npm kept recording the old version (npm outdated -g, npm ls) while the
binary reported the new one. An executable that lives in an npm package
(bin/ next to a package.json naming reasonix or @reasonix/cli-*) is now
refused before any download, with the npm command to run instead.
2026-10-11 16:26:03 -07:00
yhh 88706b8c97 chore(update): tidy what the removed Windows helper left behind
The no-op main builds on every platform but Linux so Windows no longer has an
empty package, fixes its typo, and drops two comments that still described the
Windows helper.
2026-10-11 15:57:57 -07:00
yhh 6694713cf9 refactor(update): remove the Windows update helper and the code only it reached
Studio on Windows runs the downloaded installer directly (Line.RunInstaller);
nothing builds or starts reasonix-update-helper.exe there. Delete its Windows
sources and tests, the WindowsHandoff staging code, the Windows payload
manifest and its sign subcommand, and move the repair/installlayout entry
points that only that helper called into test support.
2026-10-11 15:57:26 -07:00
yhh f21a6d1791 fix(config): judge a grant against the home the package already resolves
The process-input gate counts a direct os.UserHomeDir call; folder grants now use
the package's injectable osUserHomeDir like the other home lookups here.
2026-10-11 15:57:01 -07:00
yhh c349bab79e perf(boot): bind every storage root Build resolves to its stated home
A Build handed Options.Home still put three storage roots under the
process's home: isolated and best-of-n worktrees, browser profiles, and
the missing-reasoning recovery state. Each now resolves through the
build's config.Roots, which is the process binding whenever no home is
stated, as in every production frontend.

The new process-input repolint rule ratchets direct reads of the process
environment, working directory and user directories below
internal/frontend, so later slices can only move that count down.

40 boot tests now state their home and workspace instead of mutating the
process, and run in parallel. The isolation effect test runs two
sessions with different homes side by side and fails without the
binding fix.

The provider registry now holds a read-write lock: parallel Builds
register and resolve kinds concurrently, which -race reported as a
race and could crash with a concurrent map write.

Refs #12531
Refs #12521
2026-10-11 15:55:36 -07:00
YHH a3c025cc41 Merge pull request #12641 from esengine/fix/install-source-local-mcp-bound
fix(installsource): bound a local .mcp.json and name the cause of a refused install
2026-10-11 15:51:37 -07:00
YHH d6116a227e Merge pull request #12592 from esengine/perf/config-retired-keys-single-read
perf(config): run the retired-key migrations over one read of each config
2026-10-11 15:50:37 -07:00
YHH b2af298cba Merge pull request #12615 from esengine/fix/shell-discovery-bounded-12473
fix(sandbox): bound shell discovery as a whole so a stalled lookup cannot hold the kernel handshake
2026-10-11 15:47:10 -07:00
yhh da0e5942fb chore(installsource): state why the bounded manifest read is not a path injection
The path is a manifest inside the source the person asked to install, and the
read is capped at the fetch limit. The CodeQL alert on the open is a false
positive for this call; say so where the open is.
2026-10-11 15:33:37 -07:00
YHH b69d956bc3 Merge branch 'studio' into feat/tui-unviewed-mark 2026-10-11 15:28:25 -07:00
YHH 163828b644 Merge branch 'studio' into feat/phone-page-installable 2026-10-11 15:28:12 -07:00
YHH c33f7fefc0 Merge branch 'studio' into perf/config-retired-keys-single-read 2026-10-11 15:27:58 -07:00
YHH 953c592d74 Merge branch 'studio' into feat/remote-wsl-kind 2026-10-11 15:27:18 -07:00
YHH 718c724aec Merge branch 'studio' into fix/shell-discovery-bounded-12473 2026-10-11 15:27:04 -07:00
YHH 9c103e4f7a Merge branch 'studio' into fix/theme-remove-12351 2026-10-11 15:26:51 -07:00
YHH fda72c0730 Merge branch 'studio' into fix/install-source-local-mcp-bound 2026-10-11 15:26:38 -07:00
YHH fdabdeb34f Merge pull request #12645 from esengine/feat/provider-presets
feat(providers): offer the kernel's curated presets when adding a source
2026-10-11 15:11:52 -07:00
YHH c093e4f8ff Merge pull request #12598 from esengine/feat/turn-date-anchor
feat(control): tell the model the calendar date on the turn tail, once per day
2026-10-11 15:07:57 -07:00
yhh d613bda202 fix(studio): name the preset gallery and install form actions in the registry
The rebase onto studio put the unnamed-input surface three over its ceiling and
left provider.preset unregistered. The search, name and key inputs now carry the
provider.draft action with a value, and the gallery and install form buttons are
registered.
2026-10-11 15:04:23 -07:00
yhh e71e3330aa feat(providers): create a curated preset in the kernel from the add form
POST /providers/presets/install builds the entries a preset declares, whole
(catalog, windows, vision models, thinking shape, PresetID and version) with the
person's name and key, through the same storeProviderEntries the manual add
uses; the add form lists the kernel's presets grouped by host, searchable with
the form's own input style, and installs through that call.
2026-10-11 14:59:45 -07:00
yhh 211eb4cbf4 feat(control): tell the model the calendar date on the turn tail, once per day
The model cannot read the host clock, so a session left overnight keeps
reasoning from the date it started on. The date (with weekday and UTC offset)
is now a turn block owed through a projectionDebt: delivered on the first real
turn, absent on later turns of the same day so their bytes are identical,
re-owed on the first turn after the date changes and after a fold. It never
touches the cache-stable prefix. The clock is an explicit input
(control.Options.Clock); the boot assembly supplies the system clock and a
controller built without one tells the model nothing.
2026-10-11 14:56:00 -07:00
jipeng6036-del 203385e07e fix(serve): send an empty sessions list for a folder with no conversations
/tree sent "sessions": null for a workspace with no saved session, because
workspacePage returned a nil slice both when the listing failed and when
there was nothing to list. Studio iterates that field, so a fresh install
or a new project opened to a blank window.

Fixes #12743
2026-10-11 14:44:26 -07:00
YHH ee5c0290a7 Merge pull request #12589 from esengine/fix/provider-remove-report
feat(providers): report the roles a removal moved or cleared and prune provider_access
2026-10-11 14:42:09 -07:00
YHH f28fc07d08 Merge pull request #12560 from esengine/fix/readiness-anchor-skips-prose
fix(readiness): prose written after a passing check owes no second run
2026-10-11 14:37:26 -07:00
yhh 78f962ac31 feat(providers): report the roles a removal moved or cleared, drop the name from provider_access, bind the failure line to its service
RemoveProvider now returns which roles it handed to another service and which it
emptied, and also removes the name from desktop.provider_access. The remove
endpoint carries that report and the provider detail pane shows it, so a
removal that switches vision, advisor or decision off says so. A failure after
the removal was already written is shown where the service used to be instead of
in the detail of whichever service is selected next.
2026-10-11 14:29:47 -07:00
yhh 2fc75feb4e fix(readiness): a still-running background job is an unseen writer
A job the session started and has not collected writes into the tree
without any receipt, so prose after a passing check was waived as if no
writer could still change files. The contract now counts a running job of
the turn's session beside tool hooks and extension sidecars.
2026-10-11 14:26:22 -07:00
yhh b135b676a5 fix(readiness): prose written after a passing check owes no second run
The generic check was measured from the latest surviving write, so a
README or CHANGELOG edit after the tests passed sent the turn back to
re-run them. The anchor now steps over a trailing run of prose whose
extent the host established, when no check is declared and the role is
not Delivery, and only while a check stands passing over both the latest
change beyond prose and the anchor the turn would have had without the
prose. The prose judgement is the one #12520 introduced, now read per
receipt by both the whole-ledger waiver and this one.

A named write follows links, so its receipt now also names the file a
link resolved to; otherwise writing code through a .md link and removing
the link before the end of the turn would read as prose.

A configured tool hook, or an extension sidecar on a point every tool
call passes (tool.before, permission.decision, tool.after), runs outside
the bash sandbox and names no paths, so the check contract carries that
fact and neither prose waiver applies while one is present.
2026-10-11 14:26:22 -07:00
YHH 1bff13835e Merge pull request #12570 from esengine/fix/provider-probes-redirect-guard
fix(providers): keep key-carrying provider requests on the configured origin
2026-10-11 14:13:07 -07:00
esengine 11aa4014d0 fix(providers): keep key-carrying provider requests on the configured origin
Provider clients took netclient.NewHTTPClient, so a redirect from the
configured address was followed by Go's default policy and the key went with
it. Add redirectguard.StayOnOrigin, build the probe, provider, System One,
balance and doctor clients through endpointclient.New, and guard the default
clients of the model listing, balance and System One calls. A refused redirect
is a permanent error that names the target host and reaches the user as
redirect_refused instead of a network fault retried ten times.
2026-10-11 14:00:48 -07:00
YHH 0807e52c2d Merge pull request #12742 from esengine/fix/objectcache-racy-windows
test(trustedstate): pin the clock and the file time in the racy-window test
2026-10-11 11:39:37 -07:00
yhh 278e06b2bc test(trustedstate): pin the clock and the file time in the racy-window test
The test relied on a freshly written file's modification time being inside the
20ms window of the process clock. A file system stamps from its own clock, which
on a loaded Windows runner can lag the process clock by more than the window, so
the object was served from memory and the leg failed intermittently. The store
clock and the file time are now both set, which is what the test is about.
2026-10-11 11:30:22 -07:00
YHH 5be5f1ea14 Merge pull request #12741 from esengine/fix/win-tree-escape-test
test(serve): use a Windows-legal directory name in the tree JSON escaping test
2026-10-11 11:08:41 -07:00
XTLine a201497806 fix(studio): preserve restore through transcript updates
Merge Studio 0ac9826a9e while retaining Pane-owned undo and upstream work folding, running-tail handling and repeated-speaker grouping. Adapt three imported transcript fixtures to the existing undo ownership contract and cover folding, attachments and restore/undo together through the real Pane.

Ordinary upstream integration: 238 files, 7968 insertions and 704 deletions. Manual resolution and regression delta: 41 insertions and 9 deletions. Both histories are preserved.
2026-10-12 02:06:39 +08:00
yhh bdc108ec1c test(serve): use a Windows-legal directory name in the tree JSON escaping test
Windows refuses < and > in a file name, so TestTreeWriteKeepsJSONContentTypeAndEscaping
failed at mkdir on the Windows leg. & is the other character the JSON encoder
escapes, so the same assertion holds there.
2026-10-11 10:59:08 -07:00
YHH 0ac9826a9e Merge pull request #12716 from esengine/docs/studio-2.36.0-notes
docs(release): Studio 2.36.0 notes
2026-10-11 10:37:25 -07:00
yhh 49a967ca8f docs(release): add the changes merged since the first draft to the 2.36.0 notes 2026-10-11 10:30:39 -07:00
yhh 905e973bd5 docs(release): Studio 2.36.0 notes 2026-10-11 10:29:54 -07:00
YHH 777dfd978d Merge pull request #12721 from esengine/feat/receipt-display
feat: the receipt card is hidden by default; a line says what was left unproven; visibility is a kernel setting
2026-10-11 10:20:42 -07:00
YHH 23b6a565df Merge pull request #12648 from esengine/ui/workbench-start-12481
feat(studio): opening the workbench offers a start page instead of opening a browser
2026-10-11 10:16:50 -07:00
YHH ce634f0683 Merge pull request #12740 from esengine/fix/storage-move-live-writes
fix(storage): catch up on writes made during a move and say why a target is refused
2026-10-11 10:15:44 -07:00
YHH 4161a2e62d Merge branch 'studio' into feat/receipt-display 2026-10-11 10:07:42 -07:00
YHH 5281704297 Merge branch 'studio' into ui/workbench-start-12481 2026-10-11 10:07:28 -07:00
yhh 8dc28786e8 fix(storage): catch up on writes made during a move and say why a target is refused
A move copies the root while the app keeps running and writing to it. The
verification pass hashed every file with no progress report, failed the whole
move on the first file that had changed since it was copied, and the commit
then deleted the old folder even when the app had written to it since.

Verification now reports bytes and files as it goes, copies again what changed
(bounded rounds, then ErrSourceChanging), and the old folder is kept when it
was written to after the proof. A refused target names what it holds that the
data does not own.
2026-10-11 10:06:49 -07:00
YHH 251d6e48eb Merge pull request #12739 from esengine/fix/i18n-size-ceiling
chore(i18n): keep i18n.go at the 800-line ceiling
2026-10-11 10:01:13 -07:00
YHH b612222937 Merge pull request #12500 from chuiwenwei/test/studio-corpus-policy-registry
test(e2ebench): require every task corpus to declare its authoring guards
2026-10-11 09:56:51 -07:00
yhh 4a002c54fb feat: the delivery receipt card is hidden by default; one line says what was left unproven; its visibility is a kernel setting
Replaces the stack #12601 (hide by default), #12637 (hint line) and #12643
(kernel setting). [ui] show_receipt (default off) is read and written through the
controller (ReceiptSettings, GET/POST /receipt-settings) like show_changed_files;
Studio keeps a per-kernel answer and carries an old browser-local on across once;
the TUI draws a line, or the card when the key asks for it. The kernel grades the
receipt (Severity) and the hidden card leaves one line for a graded one.
2026-10-11 09:55:16 -07:00
yhh 621e381736 chore(i18n): keep i18n.go at the 800-line ceiling
#12655 added one Messages field and pushed the file to 801 lines, which fails
repolint on the baseline. Drop one separator line between two field groups.
2026-10-11 09:53:16 -07:00
yhh 476942bb93 feat(studio): opening the workbench offers a start page instead of opening a browser
The column opened with a browser tab already in it, whichever way it was
asked for. It now opens on a start page that offers the file explorer, the
changed files and the browser, and a browser exists only once it is chosen.
A page a followed link already has on its way is still shown as before.
2026-10-11 09:45:20 -07:00
YHH f1434b7ae4 Merge pull request #12647 from esengine/ui/sidebar-rail-unify-v2
fix(studio): one sidebar toggle that does not move, a conversation item that does something, one header per run of replies
2026-10-11 09:38:16 -07:00
YHH 8a421f14d2 Merge pull request #12655 from esengine/fix/goal-resume-starts-turn
fix(control): resuming a Goal runs it, and a reply to a blocked Goal puts it back in pursuit
2026-10-11 09:35:48 -07:00
YHH fe8e6dc78f Merge pull request #12651 from esengine/fix/handback-ends-turn
fix(readiness): an explicit hand-back ends the turn instead of being continued
2026-10-11 09:34:52 -07:00
YHH 7c5e35ea1e Merge pull request #12654 from esengine/fix/refused-input-notice-code
fix(control): a refused submission carries a notice code the queueing frontend can word
2026-10-11 09:34:24 -07:00
YHH dc3c84f903 Merge pull request #12607 from esengine/fix/windows-frontend-test-env
test(studio): give async queries room on a loaded machine
2026-10-11 09:33:24 -07:00
yhh 0c34adf412 fix(studio): one sidebar toggle that does not move, a conversation item that does something, and one header per run of replies
The sidebar had three toggles in two screen positions (header button,
title-bar button that shifted with the sidebar, edge grip), a rail
conversation item that did nothing from the conversation view, quick entries
repeated in the sidebar while the icon rail held them, and an R mark plus a
name on every reply. The title-bar button is now fixed to the window and is
the only button, the header one is gone; the conversation item opens and
closes the workspace column and returns home from settings; the sidebar's
quick entries show only when the rail is not drawn and the rail gains the
files entry; replies from one model after the first carry no header.
2026-10-11 09:28:42 -07:00
YHH c8fbbf70f4 Merge pull request #12714 from esengine/feat/fold-turn-work-12671
feat(studio): fold a turn's work under one line, off by default
2026-10-11 09:19:44 -07:00
yhh 86f6486b9b feat(studio): fold a turn's work under one line, off by default
[ui] fold_turn_work (default false) is a kernel setting read through the
controller like the changed-files pill. When it is on, a finished turn
shows its last sentence and gathers the tool calls, thinking and the
sentences in between into one closed line with the call count and any
failure; the line opens on click and keeps its state, and the turn that is
still running is never folded.
2026-10-11 09:08:32 -07:00
YHH 96ed4ea786 Merge pull request #12616 from esengine/fix/narrow-scaled-shell-rows-12479
fix(studio): shell rows no longer collide at high scale in a narrow window
2026-10-11 09:04:57 -07:00
YHH 5c91bd0a72 Merge pull request #12674 from XTLine/fix/studio-machine-settings
fix(studio): keep machine pages local / 修复远程窗格下本机页面错绑
2026-10-11 09:03:33 -07:00
YHH adc5167911 Merge pull request #12495 from chuiwenwei/docs/context-retrieval-v2-result
docs(context-retrieval): record the v2 Experiment I result in Status
2026-10-11 09:02:37 -07:00
yhh fc84278599 test(e2ebench): use slices.Contains and strings.Builder in the corpus policy test
Clears the two modernize findings golangci-lint reports in this test.
2026-10-11 09:01:27 -07:00
YHH 74e457467c Merge pull request #12494 from chuiwenwei/feat/contextbench-usage-in-results
feat(bench): account for what each context-retrieval run costs
2026-10-11 08:59:15 -07:00
yhh e6ca850e1d fix(studio): the title bar folds into one button instead of colliding at high scale
At a couple of hundred CSS pixels the composer's model group could neither
shrink nor wrap and the title bar's right-hand controls left its title no
room, so rows ran over each other. Below 300px of room (the width at which
the five controls leave the title under its 4em floor) the bar gathers phone,
find, theme, sandbox and browser under one button; each stays one press away.
The model group wraps, the tabs shrink, the meter row drops its cache figure
and the placeholder keeps to one line at the narrowest widths. The size
slider's fallback range parks the thumb mid-track while no range is announced.
2026-10-11 08:56:58 -07:00
YHH c41ad00d55 Merge branch 'studio' into test/studio-corpus-policy-registry 2026-10-11 08:41:11 -07:00
YHH 44feacc865 Merge branch 'studio' into docs/context-retrieval-v2-result 2026-10-11 08:41:01 -07:00
YHH 6b361d37e9 Merge branch 'studio' into feat/contextbench-usage-in-results 2026-10-11 08:40:49 -07:00
YHH 2f86d06a78 Merge pull request #12646 from esengine/ui/signal-rail-explained-12453
fix(studio): say what the signal rail is for and why its marks do not move
2026-10-11 08:37:53 -07:00
yhh 9a3d5bf432 fix(studio): say what the signal rail is for and why its marks do not move
The run analysis drew a rail labelled only 'Signal rail' and 'Shares the
timeline below'. A line now says it holds the run's records that have no
duration and are not tool calls, placed at the moment they happened, so the
position is the time and cannot be dragged.
2026-10-11 08:30:29 -07:00
YHH a5bc1bbefa Merge pull request #12715 from esengine/feat/settings-extra-folders-11527
feat(studio): manage a project's extra writable folders in Settings
2026-10-11 08:26:19 -07:00
YHH 68ba644676 Merge pull request #12717 from esengine/fix/extra-root-drive-letter
fix(config): an extra root must name a drive letter on Windows
2026-10-11 08:21:09 -07:00
yhh 25efbb20a9 fix(studio): declare the extra-folder settings actions in the action registry 2026-10-11 08:17:34 -07:00
YHH 3a7617813b Merge pull request #12702 from esengine/feat/task-done-sound
feat(notify): an optional chime for turn done, approvals and questions
2026-10-11 08:16:27 -07:00
yhh 49c116f771 fix(studio): the folder picker only fills the field; say what adding and removing do
Choosing a folder in the system dialog no longer grants it; Add does. The
panel states that the agent can change and delete anything in an added
folder and that panes already open narrow only on rebuild. A failed read
shows a retry, refusals use the error box, and the project row is laid
out like the other facts.
2026-10-11 08:16:15 -07:00
yhh 08a214570a feat(studio): manage a project's extra writable folders in Settings
Settings > Tools and permissions > Sandbox lists the project folder and
the extra folders granted to it, and adds or removes them through the
existing /workspace/roots routes. The kernel's refusals are worded by
their codes. The panel says that 'allow everything' only decides whether
to ask and does not widen where writes can land. No folder is added by
default.
2026-10-11 08:16:15 -07:00
YHH ddccefef09 Merge pull request #12720 from esengine/ui/running-tool-detail-11985
fix(studio): a running call with no output yet opens and says what it is doing
2026-10-11 08:14:53 -07:00
yhh 626068c473 fix(studio): declare the notification sound switch in the action registry 2026-10-11 08:08:13 -07:00
yhh 2ebd444b2d feat(notify): an optional chime for turn done, approvals and questions
[notifications] sound = true plays a short system chime for the events already
selected, independent of system notifications and of their muting, so a
minimised or backgrounded window is still audible. Off unless asked for. The
Windows, macOS and Linux senders implement Chime; the Settings notifications
block gets its own switch.
2026-10-11 08:07:50 -07:00
XTLine 92339c8580 merge(studio): align restore branch with CI fixes
Import Studio 71650fe7, including upstream desktop lint fix 85460ae and test-only sandbox portability fix 2b5f6448. Keep the running-jobs fixture deterministic by selecting its scripted provider explicitly and asserting the background launch.
2026-10-11 23:07:03 +08:00
XTLine a6bbceffbd merge(studio): inherit CI fixes and isolate the job-list fixture
Integrate Studio 71650fe7, including upstream desktop modernization 85460ae14 and the job-list test convention from 2b5f6448. Keep the latter test-only setting unchanged and use the supported scripted resolver with explicit selection and launch assertions. The only authored delta from the automatic merge is 17 additions and 10 deletions in that fixture; no coverage workflow or product sandbox changes.
2026-10-11 23:06:49 +08:00
yhh c842f014f4 fix(studio): a running call with no output yet opens and says what it is doing
In the activity group a running call that had printed nothing was a row that
did nothing when clicked, and a long command was clipped to one line, so a
call waiting out a timeout looked frozen. Such a row now opens to its input,
says it has no output yet and for how long it has run, names the command's
own timeout when it carries one, and its hover text holds the whole command.
2026-10-11 08:00:44 -07:00
YHH 18965b946b Merge pull request #12712 from esengine/fix/upload-size-guard
fix(studio): one size guard before every upload is read into memory
2026-10-11 07:59:52 -07:00
YHH cc4820ca9a Merge branch 'studio' into fix/extra-root-drive-letter 2026-10-11 07:59:46 -07:00
YHH d50a477297 Merge pull request #12704 from esengine/fix/local-model-check-timeout
fix(providers): name a model check that timed out against a local address and give it a longer wait
2026-10-11 07:56:27 -07:00
YHH d16404fe19 Merge pull request #12688 from esengine/feat/workspace-sessions-show-more
feat(studio): show a workspace's true session total and page in older sessions
2026-10-11 07:56:12 -07:00
YHH 71650fe7fd Merge pull request #12737 from esengine/fix/joblist-effect-test-sandbox
test(boot): turn the bash sandbox off in the running-jobs effect test
2026-10-11 07:51:51 -07:00
yhh 17bc7388d9 fix(studio): one size guard before every upload is read into memory
Attachments, wallpapers and theme packs were each turned into base64 before the
kernel could refuse them. One guard in the port checks the kernel's cap first,
using the image cap for pictures, and a Go test pins every number to the kernel's.
2026-10-11 07:43:51 -07:00
yhh 2b5f644882 test(boot): turn the bash sandbox off in the running-jobs effect test
The test starts a background bash job through the real Build assembly. On a
host without bubblewrap the sandbox refuses to run unconfined, so the job
never starts and the coverage leg fails there while passing on a developer
machine. Its sibling effect tests already declare [sandbox] bash = "off".
2026-10-11 07:42:56 -07:00
YHH 7d8164a281 Merge pull request #12707 from esengine/fix/nav-hint-delay-12692
fix(studio): icon-rail hints wait for a short hover before they appear
2026-10-11 07:29:32 -07:00
F 1ca349830d fix(feedback): report the Windows 11 product line in the environment version
Windows 11 kept the kernel's 10.0 major, so a build like 10.0.26200 read
as Windows 10 (issue #12696). Builds from 22000 report as 11.0.build,
matching the product version the darwin path already reports.
2026-10-11 22:06:05 +08:00
YHH 5f0bb7d845 Merge pull request #12719 from esengine/feat/logs-path-in-about
feat(studio): show where the logs and abnormal-exit records are kept
2026-10-11 07:05:11 -07:00
YHH fc7fbedf83 Merge pull request #12710 from esengine/fix/provider-400-wording
fix(i18n): a 400 says the service refused the request and that its reason follows, not that this is a program defect
2026-10-11 07:01:31 -07:00
YHH 34089143fc Merge pull request #12619 from esengine/fix/symlink-write-extent
fix(readiness): record where a named-path write through a symlink landed
2026-10-11 07:01:25 -07:00
YHH 5e196bc539 Merge pull request #12718 from esengine/chore/desktop-lint-stringsseq
chore(desktop): range over strings.SplitSeq in the packaged-names test
2026-10-11 06:52:34 -07:00
yhh e28123cf80 feat(studio): a workspace's true session total, paged by view and bounded
/tree windows each workspace by the request's scope (active, archived) and word,
always builds pinned active conversations, and reports the total, archived and
remaining counts for the whole folder. The sidebar counts from the kernel, so the
chips do not move with the window, an archived conversation far outside it is
reachable, search is filtered on the kernel and paged, and a folder holds at most
300 rows. The remote machine list shows the far kernel's total instead of a
truncated count.
2026-10-11 06:50:36 -07:00
yhh 9d3424f3bb fix(config): an extra root must name a drive letter on Windows, and a delegated write is pinned end to end
Windows accepted \??\C:\... because only a leading double backslash was
refused. A root's volume must now be a drive letter and a colon; UNC, \\?\, \\.\
and NT object paths are all refused. Adds a boot-level test where a delegated
child writes only a granted folder.
2026-10-11 06:48:31 -07:00
yhh 8f4ccbbb2b feat(studio): show where the logs and abnormal-exit records are kept 2026-10-11 06:48:23 -07:00
YHH 0d4e37682a Merge pull request #12706 from esengine/fix/wheel-scroll-jump-12691
fix(studio): scrolling up no longer jerks or jumps to another message
2026-10-11 06:47:19 -07:00
XTLine beb1b0082e Merge studio into fix/studio-restore-undo
Preserve pane-owned restore undo with canonical message attachments. Add a regression for attachment previews through restore and undo.
2026-10-11 21:44:54 +08:00
yhh 7cf1de5381 fix(sandbox): bound shell discovery as a whole so a stalled lookup cannot hold the kernel handshake
PowerShell candidates were launch-probed one after another with a 10 s
timeout each, and nothing bounded host construction or the lookups around
the probes. On a machine where Store aliases, a PATH entry or a registry
read stall, first start sat in shell discovery past the shell's 60 s
handshake wait.

PowerShell now shares bash's hedged search and budget, probe commands
cap their post-kill wait, and the whole resolution (host construction
included) gives up after both budgets plus a hedge: it uses a bash
already proven in this process, else the bare name with a warning that a
restart usually recovers.
2026-10-11 06:39:22 -07:00
xxoingr a88eec08d1 test(installsource): cover hostile skill paths in discovery warnings end to end
The skill-shadowing warning after an install and the not-selected warning
after a root is registered now render their paths through hostLiteral
(60f37ee7c), checked by a unit test that feeds an unterminated OSC to the
two sentence helpers. #12547 also asked for the newline, bidi and
zero-width cases.

Two end-to-end tests add them: a skill is installed, or a root
registered, while another skill under a configured path whose name holds
the character keeps the selection, and the warning must show the
character escaped for every path rather than carry it raw. The newline
case is skipped on Windows, which cannot hold one in a file name, and
runs on the Linux and macOS jobs.

Fixes #12547
2026-10-11 21:38:41 +08:00
yhh 85460ae14e chore(desktop): range over strings.SplitSeq in the packaged-names test
The modernize stringsseq check flagged strings.Split in a range loop; the loop
reads the same lines.
2026-10-11 06:38:30 -07:00
YHH 61ef913d4c Merge pull request #12697 from esengine/feat/refused-calls-turn-tail
feat(control): tell the model which calls the host refused, once, at the next turn
2026-10-11 06:36:39 -07:00
YHH aad83aaae9 Merge pull request #12713 from esengine/fix/unapplied-steer-row-12458
fix(studio): a queued line the kernel could not apply leaves the transcript
2026-10-11 06:34:50 -07:00
YHH 234db5d11f Merge pull request #12709 from esengine/fix/linux-tray-menu-repaint-stable
fix(studio): the tray menu is replaced only when its content changes
2026-10-11 06:33:40 -07:00
XTLine da98e04381 merge(studio): reconcile machine settings with runtime updates 2026-10-11 21:32:04 +08:00
YHH a8d9c95631 Merge pull request #12703 from esengine/feat/composer-full-width
feat(studio): an opt-in line width that widens the composer with the transcript
2026-10-11 06:31:01 -07:00
YHH 34ab4297db Merge pull request #12711 from esengine/fix/attachment-size-precheck
fix(studio): refuse an oversize attachment before reading it into memory
2026-10-11 06:30:19 -07:00
yhh 3e60275fb0 fix(studio): scrolling up no longer jerks or jumps to another message
Each transcript row asked the browser to skip rendering it while off screen
with an estimated height, and swapped the estimate for the measured height
the first time the row scrolled into view. The page shrank under the wheel
(12975px to 11217px in the probe), so the content jumped, once by 1858px.
Rows that are far off screen are already unmounted by their block, which
holds the measured height, so the estimate had no job left.
2026-10-11 06:28:06 -07:00
yhh bef076f523 fix(studio): a queued line the kernel could not apply leaves the transcript
unapplied_steer names the queue entry the kernel took out of the turn
without the model reading it and holds for a retry. The row that said it
was waiting stayed forever, while the queue panel, which the kernel
authors, showed the same line as blocked and retryable. The row now goes
when that notice names it; the notice card stays.
2026-10-11 06:22:46 -07:00
yhh 3b5a389193 fix(i18n): a 400 says the service refused the request and that its reason follows, not that this is a program defect
The 400 text told every reader it was probably a bug. The host cannot know that:
the reason the service gave is appended, so the preface now says whose refusal
it is, where the reason is, and what can be tried, without promising it works.
2026-10-11 06:22:12 -07:00
YHH 4a33d64e2b Merge pull request #12700 from esengine/feat/mcp-prompt-args-completion
feat(completion): show an MCP prompt's declared arguments in the slash menus
2026-10-11 06:22:09 -07:00
YHH dd24c520db Merge pull request #12708 from esengine/feat/more-font-choices-12693
feat(studio): suggest many more interface and code fonts
2026-10-11 06:21:23 -07:00
yhh 624182edbd fix(studio): redraw the tray checkbox after a refused save and retry a menu that failed to draw 2026-10-11 06:18:57 -07:00
yhh 23e36e9a99 fix(studio): the tray menu is replaced only when its content changes
The poll re-ran setContextMenu every five seconds with an identical menu.
Where the menu is handed to the desktop's indicator over D-Bus, replacing
it closes one the person has open. It is now rebuilt only when the
sentence, the words or the close-to-tray setting differ.
2026-10-11 06:18:57 -07:00
yhh dfefd3e765 fix(readiness): record where a named-path write through a symlink landed
The prose waiver judged paths by what the disk showed at the end of the turn,
so a documentation-named symlink to a code file that was written through and
then removed counted as a deleted prose file. The resolved target is now
recorded on the write receipt while the link still resolves.

Refs #12524
2026-10-11 06:15:10 -07:00
yhh f9f1b0b28f fix(studio): refuse an oversize attachment before reading it into memory
attach() turned the whole file into an ArrayBuffer, a binary string, base64 and a
JSON string before the kernel could refuse it at 25 MB, so a modpack-sized
archive could take the renderer down. The size is checked first and refused with
the kernel's own attachment.too_large identity.
2026-10-11 06:14:48 -07:00
yhh 48aa562625 fix(studio): icon-rail hints wait for a short hover before they appear
The rail's hints were drawn the instant the pointer touched an icon, so a
pass across the column flashed every label. They now appear after a 0.3s
rest (--tip-delay; VS Code waits 250ms), and keyboard focus still shows its
hint at once.
2026-10-11 06:14:00 -07:00
YHH 83a1a3fa01 Merge pull request #12705 from esengine/fix/host-turn-announces-start
fix(control): a queued host turn announces its start
2026-10-11 06:13:19 -07:00
YHH 827a78ce7a Merge pull request #12698 from esengine/fix/completion-reserve-in-window
fix(agent): keep a window-proportional margin for the completion and recover a codeless 400 that counts it
2026-10-11 06:11:29 -07:00
yhh f6fdbcdbd9 feat(completion): show an MCP prompt's declared arguments in the slash menus 2026-10-11 06:07:13 -07:00
yhh fa98929bfa fix(config): a saved full-all width survives the render
The [desktop] renderer wrote conversation_width only for full, so a full-all
choice reverted to standard on the next start. It now writes every non-standard
width; a render-and-reparse test, a page-attribute test and narrow-viewport
checks cover the new choice.
2026-10-11 06:06:40 -07:00
YHH c5477a2ead Merge pull request #12690 from esengine/fix/rebuild-dispatcher-notices
fix(boot): a rebuilt dispatcher keeps the missing-sidecar notice
2026-10-11 06:05:33 -07:00
YHH 455f9e6cf5 Merge pull request #12701 from esengine/fix/feedback-env-windows-product
fix(studio): the feedback form names Windows 11 from its build number
2026-10-11 06:05:21 -07:00
yhh 53e2d91f0a feat(studio): suggest many more interface and code fonts
The font fields took any family the machine has but suggested only a dozen,
and on Windows few of those are installed. The suggestions now cover the
common CJK, Latin and programming faces, filtered to the ones the machine
really has, as before. Nothing is chosen by default.
2026-10-11 06:05:15 -07:00
yhh 17aaad209d feat(studio): an opt-in line width that widens the composer with the transcript
The full measure widens the transcript only, by design. A third choice,
full-all, moves the composer's column with it; the default and the existing
full measure are unchanged.
2026-10-11 06:04:24 -07:00
yhh f6eb618bf8 fix(control): a queued host turn announces its start
A host-authored inbox item (a background job's completion, for one) ran
as a synthetic turn that opens no boundary and so emitted answer text and
a turn end with no turn start. A client that never heard the start kept
the send arrow, showed no stop button and counted nothing running, and
its status poll stayed off, so it could not stop a turn that was
running. The queued host turn now announces its own start.
2026-10-11 05:59:41 -07:00
yhh ad5ac1f6a9 feat(control): tell the model which calls the host refused, once, at the next turn
A refusal reached the model only in that call's result, which compaction can
fold away, and the posture refusal did not say not to retry. Calls refused by
the host (any permission refusal code) are now owed to the next real turn as a
refused-calls block, delivered once like the other turn projections, and the
posture refusal says the call and its rewordings will not be approved.

Refs #12253
2026-10-11 05:59:40 -07:00
YHH 682efd0516 Merge pull request #12699 from esengine/fix/windows-toast-click-12287
fix(notify): a clicked Windows toast brings Studio's window forward
2026-10-11 05:58:18 -07:00
yhh 8ed9f57f16 fix(providers): name a model check that timed out against a local address and give it a longer wait 2026-10-11 05:54:58 -07:00
yhh e5bedcb7b9 fix(agent): keep a window-proportional margin for the completion and recover a codeless 400 that counts it
On a 1M window the fixed 8K output margin was smaller than the prompt
estimate's own error, so a prompt a percent above its estimate plus a 64K
completion exceeded the window and the provider refused every turn. The margin
is now max(8K, window/32), and a codeless 400/413 is classified as an overflow
when the estimate plus the requested completion is past the window, so the
turn folds and replays instead of repeating the refusal.

Fixes #12694
2026-10-11 05:54:53 -07:00
YHH c6ab758469 Merge pull request #12661 from esengine/feat/ask-timeout
feat: skip an unanswered ask after a user-set wait, and tell the model it was a timeout
2026-10-11 05:54:07 -07:00
yhh 23999c4aa2 fix(studio): the feedback form names Windows 11 from its build number
Windows 11 reports NT 10.0, so the environment block read like Windows 10.
The form now shows 'Windows 11 (10.0.26200)' for builds from 22000; the
version string that is sent is unchanged.
2026-10-11 05:51:08 -07:00
YHH 0d8abb97ef Merge pull request #12640 from esengine/feat/workspace-extra-roots
feat(control): grant a workspace extra folders, with typed refusals
2026-10-11 05:47:13 -07:00
YHH 6148b172da Merge pull request #12650 from esengine/fix/dock-column-top-12617
fix(studio): the docked browser column starts at the top of the pane
2026-10-11 05:44:59 -07:00
yhh 3a93f38c94 fix(notify): a clicked Windows toast brings Studio's window forward
The kernel sent toasts through the Windows Runtime but never installed an
activation callback, so a click did nothing. It now registers one; the
host forwards it as a show act over the handshake pipe and the shell's
act handler (now its own module) shows and focuses the window.
2026-10-11 05:44:32 -07:00
yhh 7a6daf34a8 feat: skip an unanswered ask after a user-set wait, and tell the model it was a timeout
[agent] ask_timeout_seconds (user file only, default 0 = wait) bounds how long the
agent's own ask question waits. On expiry the host settles the card on every
frontend with a timed_out decision receipt and returns the model a result that
names the timeout, not an answer. External forms are never skipped.
Studio settings and the terminal's /ask-timeout drive the same controller
setting.
2026-10-11 05:44:21 -07:00
YHH 266ae5bfff Merge pull request #12653 from esengine/perf/turn-reads-12534
perf(state): read each session sidecar once per transaction and remember verified trusted objects
2026-10-11 05:42:49 -07:00
yhh ca730caab2 feat(control): grant a workspace extra folders, vetted and treated as workspace
Extra roots are the project grant's folders in the user's home. They are a set
of their own: write roots and the OS jail read them, the guard on Reasonix's
own state does not treat them as exemptions, the turn tail names them, and the
scan, readiness and receipt of the executor and of every sub-agent count them
as workspace. One judgement (FolderGrantRefusal, shared with workspace trust
and POST /sandbox) refuses drive roots, home and its ancestors, storage roots,
key and credential directories and the forbid list, in either direction,
comparing file identity rather than spelling, plus folders the user does not
own, UNC and device-namespace paths, and overlaps with other write roots. Every
load vets each grant again. Withdrawing a root rebuilds every pane on the
workspace, after this pane's lock is released.
2026-10-11 05:40:15 -07:00
yhh 871ccce82b fix(trustedstate): read every file-identity field so the unused check passes where it is not compared 2026-10-11 05:34:33 -07:00
yhh 70e55f0cac fix(trustedstate): openbsd takes the size-and-mtime fallback
Its Stat_t names the change time Ctim, not Ctimespec.
2026-10-11 05:33:17 -07:00
yhh 20f07162c7 perf(trustedstate): a cache hit also needs the file's inode and change time
The change time cannot be restored the way a modification time can, so an
in-place edit that restores size and mtime misses the cache. Platforms
without that identity fall back to size and time. Adds limit and
replacement tests and states the in-process memory in the package doc.
2026-10-11 05:33:17 -07:00
yhh 9bd0671ea0 perf(state): read each session sidecar once per transaction and remember verified trusted objects
A locked branch-meta read-modify-write passes what it read to the write
instead of reading again, and recording a revision no longer re-reads
what it just wrote. trustedstate.Object remembers a verified object with
its size and modification time and serves it while both are unchanged
and old enough to be meaningful; Verify always reads the bytes.
2026-10-11 05:33:17 -07:00
yhh 4a0c06448c fix(studio): the docked browser column starts at the top of the pane
The docked pane's grid kept the navigation and find rows across both
columns, so the browser column began 44px below the pane top and its own
8px padding made the gap 52px. The column now spans every row beside them;
under the scene fold the old stacked layout is kept explicitly.
2026-10-11 05:32:03 -07:00
yhh 4d3ea82f67 fix(boot): a rebuilt dispatcher keeps the missing-sidecar notice
stageDispatcher built the successor with empty options, so after a subgraph
rebuild an optional extension without a sidecar was skipped silently, right at
the moment an install or enable makes that most likely. The successor now takes
the reporting of the dispatcher it replaces.

Refs #12258
2026-10-11 05:29:37 -07:00
YHH 268b955eab Merge pull request #12687 from esengine/dependabot/npm_and_yarn/desktop/frontend-next/npm-0c84b57d5b
build(deps): bump the npm group in /desktop/frontend-next with 8 updates
2026-10-11 05:25:02 -07:00
YHH 0f175e404d Merge pull request #12686 from esengine/dependabot/github_actions/actions-690969f723
build(deps): bump the actions group with 5 updates
2026-10-11 05:24:13 -07:00
yhh 76552fb5a4 build(desktop): tidy go.mod and go.sum for the root module's dependency bump 2026-10-11 05:22:05 -07:00
YHH 881789cfe1 Merge pull request #12681 from esengine/dependabot/go_modules/desktop/go-6a10bec2cb
build(deps): bump golang.org/x/sys from 0.48.0 to 0.49.0 in /desktop in the go group
2026-10-11 05:20:27 -07:00
YHH c17dc72fb0 Merge pull request #12630 from esengine/fix/pluginpkg-shown-locator-12489
fix(pluginpkg): show package data inside host sentences as literals
2026-10-11 04:59:45 -07:00
YHH d9bf6c59d1 Merge pull request #12669 from XTLine/fix/studio-extension-warning-categories
fix(studio): separate extension warning categories / 区分扩展告警类别
2026-10-11 04:59:35 -07:00
YHH 183ccbb141 Merge pull request #12678 from xlyy1/fix/planmode-browser-open-12508
fix(planmode): explain browser_open opt-out and offer web_fetch
2026-10-11 04:59:25 -07:00
YHH 89d3965799 Merge pull request #12677 from esengine/fix/notify-copy
fix(studio): the notification setting says when it fires, that it is off by default, and who picks the sound
2026-10-11 04:59:15 -07:00
YHH 3b8bf8985b Merge pull request #12675 from esengine/fix/windows-update-keeps-pin-12670
fix(update): start Studio's Windows installer with --updated so an update keeps shortcuts and the taskbar pin
2026-10-11 04:59:01 -07:00
YHH 9b46939729 Merge pull request #12673 from esengine/fix/steer-splits-thinking-12672
fix(studio): a line typed mid-thought no longer splits the thought into two blocks
2026-10-11 04:58:50 -07:00
YHH 56fef30a31 Merge pull request #12668 from esengine/feat/changed-files-pill
feat(studio): changed-files pill above the composer
2026-10-11 04:58:40 -07:00
YHH 77274db339 Merge pull request #12664 from par73e/codex/11700-session-list-sync
fix(studio): refresh shared session trees on kernel changes
2026-10-11 04:58:30 -07:00
YHH e735775648 Merge pull request #12667 from esengine/fix/responses-input-image-detail
fix(responses): send the schema's required detail on input_image to the official OpenAI host
2026-10-11 04:58:20 -07:00
YHH 4d3cca5e54 Merge pull request #12666 from esengine/fix/responses-input-shape-diagnostic
fix(responses): send the schema's required detail on input_image and say what shape a refused request had
2026-10-11 04:58:10 -07:00
YHH 94f9ff00e0 Merge pull request #12656 from esengine/feat/turn-duration
feat: show how long each turn ran
2026-10-11 04:58:00 -07:00
YHH b0bef02583 Merge pull request #12627 from esengine/feat/list-background-jobs
feat(tools): bash_output without a job id lists the running background jobs
2026-10-11 04:57:30 -07:00
YHH 4afd827ace Merge pull request #12571 from xxoingr/fix/mcp-json-auto-refusal
fix(installsource): keep the cause of a refused .mcp.json under kind=auto
2026-10-11 04:57:20 -07:00
YHH c875f8440b Merge pull request #12636 from esengine/fix/shared-text-policy-12483
fix(textutil): one declared policy per surface kind for package-supplied text
2026-10-11 04:57:00 -07:00
YHH 362803d710 Merge pull request #12622 from esengine/fix/model-switch-keeps-background-jobs
fix(serve): let a model switch go through while only background jobs remain
2026-10-11 04:56:45 -07:00
YHH ea8d65e152 Merge pull request #12632 from esengine/fix/receipt-host-owned-exclusion
fix(receipt): judge host-owned paths when the call ran and treat links of every kind as not owned
2026-10-11 04:56:30 -07:00
YHH b17b4ae034 Merge pull request #12635 from esengine/fix/shell-launch-location
fix(sandbox): one rule for where a shell may be launched from
2026-10-11 04:56:20 -07:00
YHH 65e2fa36dd Merge pull request #12662 from esengine/fix/linux-update-helper-package-12657
fix(update-helper): accept the package the update path names, and guard it in the package
2026-10-11 04:56:10 -07:00
dependabot[bot] 66e36b1c69 build(deps): bump the npm group in /desktop/frontend-next with 8 updates
Bumps the npm group in /desktop/frontend-next with 8 updates:

| Package | From | To |
| --- | --- | --- |
| [@codemirror/language](https://github.com/codemirror/language) | `6.12.4` | `6.13.1` |
| [@codemirror/view](https://github.com/codemirror/view) | `6.43.13` | `6.43.14` |
| [katex](https://github.com/KaTeX/KaTeX) | `0.18.10` | `0.19.0` |
| [@babel/parser](https://github.com/babel/babel/tree/HEAD/packages/babel-parser) | `8.0.6` | `8.0.7` |
| [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) | `6.1.1` | `6.1.2` |
| [jsdom](https://github.com/jsdom/jsdom) | `30.1.1` | `30.1.2` |
| [playwright](https://github.com/microsoft/playwright) | `1.63.0` | `1.64.0` |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `8.3.2` | `8.3.3` |


Updates `@codemirror/language` from 6.12.4 to 6.13.1
- [Changelog](https://github.com/codemirror/language/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/language/commits)

Updates `@codemirror/view` from 6.43.13 to 6.43.14
- [Changelog](https://github.com/codemirror/view/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/view/commits)

Updates `katex` from 0.18.10 to 0.19.0
- [Release notes](https://github.com/KaTeX/KaTeX/releases)
- [Changelog](https://github.com/KaTeX/KaTeX/blob/main/CHANGELOG.md)
- [Commits](https://github.com/KaTeX/KaTeX/compare/v0.18.10...v0.19.0)

Updates `@babel/parser` from 8.0.6 to 8.0.7
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v8.0.7/packages/babel-parser)

Updates `@vitejs/plugin-react` from 6.1.1 to 6.1.2
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/[email protected]/packages/plugin-react)

Updates `jsdom` from 30.1.1 to 30.1.2
- [Release notes](https://github.com/jsdom/jsdom/releases)
- [Commits](https://github.com/jsdom/jsdom/compare/v30.1.1...v30.1.2)

Updates `playwright` from 1.63.0 to 1.64.0
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](https://github.com/microsoft/playwright/compare/v1.63.0...v1.64.0)

Updates `vite` from 8.3.2 to 8.3.3
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.3.3/packages/vite)

---
updated-dependencies:
- dependency-name: "@codemirror/language"
  dependency-version: 6.13.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: "@codemirror/view"
  dependency-version: 6.43.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: katex
  dependency-version: 0.19.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: "@babel/parser"
  dependency-version: 8.0.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: "@vitejs/plugin-react"
  dependency-version: 6.1.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: jsdom
  dependency-version: 30.1.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: playwright
  dependency-version: 1.64.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: vite
  dependency-version: 8.3.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-10-11 11:56:04 +00:00
dependabot[bot] 6fb606fd53 build(deps): bump the actions group with 5 updates
Bumps the actions group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [actions/setup-node](https://github.com/actions/setup-node) | `7.0.0` | `7.1.0` |
| [actions/upload-artifact](https://github.com/actions/upload-artifact) | `7.0.1` | `7.0.2` |
| [github/codeql-action/init](https://github.com/github/codeql-action) | `4.38.2` | `4.38.3` |
| [github/codeql-action/analyze](https://github.com/github/codeql-action) | `4.38.2` | `4.38.3` |
| [actions/download-artifact](https://github.com/actions/download-artifact) | `8.0.1` | `8.0.2` |


Updates `actions/setup-node` from 7.0.0 to 7.1.0
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/820762786026740c76f36085b0efc47a31fe5020...949feb2413d6458794dcd2491c4babbbce0c15c1)

Updates `actions/upload-artifact` from 7.0.1 to 7.0.2
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/043fb46d1a93c77aae656e7c1c64a875d1fc6a0a...cf430e030ddbb5b0abf93d22962f4752f3646cd9)

Updates `github/codeql-action/init` from 4.38.2 to 4.38.3
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2...24c54180a607b1449ed407dd24f251e4e9147c8d)

Updates `github/codeql-action/analyze` from 4.38.2 to 4.38.3
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2...24c54180a607b1449ed407dd24f251e4e9147c8d)

Updates `actions/download-artifact` from 8.0.1 to 8.0.2
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c...9000827ccba6bdab643e8b6fd33ac0654aef8333)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: 7.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
- dependency-name: github/codeql-action/init
  dependency-version: 4.38.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.38.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
- dependency-name: actions/download-artifact
  dependency-version: 8.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-10-11 11:55:46 +00:00
YHH 406771534e Merge pull request #12642 from esengine/fix/notice-wording-shared
fix(studio): word every placeholder notice through one shared resolver
2026-10-11 04:55:40 -07:00
dependabot[bot] beeb2865f0 build(deps-dev): bump the npm group in /desktop/electron with 2 updates
Bumps the npm group in /desktop/electron with 2 updates: [electron](https://github.com/electron/electron) and [playwright](https://github.com/microsoft/playwright).


Updates `electron` from 44.5.1 to 44.7.0
- [Release notes](https://github.com/electron/electron/releases)
- [Commits](https://github.com/electron/electron/compare/v44.5.1...v44.7.0)

Updates `playwright` from 1.63.0 to 1.64.0
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](https://github.com/microsoft/playwright/compare/v1.63.0...v1.64.0)

---
updated-dependencies:
- dependency-name: electron
  dependency-version: 44.7.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: playwright
  dependency-version: 1.64.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-10-11 11:54:59 +00:00
YHH 4e41e669b5 Merge pull request #12620 from esengine/fix/gutter-over-scrollbar-12467
fix(studio): a column's drag handle no longer covers the scrollbar beside it
2026-10-11 04:54:50 -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
XTLine f3c3385bbd fix(control): tolerate unreadable currency preferences on bind
Keep the already-built quote and broadcaster projections when canonical configuration cannot be read. Warn without rewriting the recoverable file, while still enrolling valid local controllers. Cover real Adopt, Open, model, extension and workspace publication with malformed configuration, including built-in-default fallback. Refs #12674.

Assisted-by: OpenAI Codex
2026-10-11 19:53:20 +08:00
dependabot[bot] 80835fc83d build(deps): bump golang.org/x/sys in /desktop in the go group
Bumps the go group in /desktop with 1 update: [golang.org/x/sys](https://github.com/golang/sys).


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: 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:12 +00:00
XTLine 1fe77558db fix(studio): classify retired currency owners as conflicts
Preserve the controller retirement identity at the HTTP boundary with a localized 409 refusal. Exercise selection racing a real model replacement, then delegate to the retired controller and assert no config or currency mutation. Refs #12674.

Assisted-by: OpenAI Codex
2026-10-11 19:53:00 +08:00
YHH fdab1d83ec Merge pull request #12679 from esengine/ci/pr-platform-legs
ci: run the macOS and Windows legs on pull requests only when a platform path changed
2026-10-11 04:47:00 -07:00
esengine 58f583e90b test(testenv): read the Windows legs through a substituted runs-on expression 2026-10-11 04:31:01 -07:00
YHH 24e000b195 Merge pull request #12624 from esengine/fix/restore-window-bounds-12475
fix(studio): reopen the window at the rectangle it was closed at
2026-10-11 04:23:38 -07:00
XTLine 4de056fa8b test(serve): cover currency projection across runtime rebuilds
Exercise real booted local runtimes, a remote pane, live ledger and balance projections, future wire quotes, model replacement and sequential stale calls after close. The canonical-config fixtures do not claim a successful durable settings write. Refs #12674.

Assisted-by: OpenAI Codex
2026-10-11 19:16:55 +08:00
XTLine aa4287afc6 test(control): guard ordered currency binding
Verify canonical quiet enrollment, failed binding atomicity, replacement and close membership, and projection ordering against the next notice. Refs #12674.

Assisted-by: OpenAI Codex
2026-10-11 19:16:49 +08:00
XTLine f01012d29f test(control): cover currency fanout and failed saves
Check full local sinks, future quotes, remote exclusion, retirement and failed-save atomicity. Retain the real HTTP currency-save assertion for CI; this sandbox cannot acquire the OS-user config lock. Refs #12674.

Assisted-by: OpenAI Codex
2026-10-11 19:16:42 +08:00
XTLine f041d15a95 fix(serve): bind currency to local runtime generations
Enroll only local controllers and reconcile their quote and broadcaster projections before publication. Share the same ordered replacement path across model, extension and workspace rebuilds, and retire membership when a runtime closes. Refs #12674.

Assisted-by: OpenAI Codex
2026-10-11 19:16:36 +08:00
XTLine f172ed7498 fix(control): synchronize local display currency ownership
Serialize persisted display-currency changes with local-controller membership. Quiet enrollment refreshes the exact quote context from canonical configuration; retired generations cannot save, and successful saves notify each full local sink. Refs #12674.

Assisted-by: OpenAI Codex
2026-10-11 19:16:29 +08:00
XTLine 671bec66cd fix(studio): restart feedback polling for local owner changes
Bind unread polling to availability and identity of the first local machine owner. Opening or replacing that owner starts a fresh read while ordinary active-pane switches keep the polling session. Cover remote-only startup, opening local sessions and retiring the first local owner through the real App. Refs #12674.

Assisted-by: OpenAI Codex
2026-10-11 19:16:21 +08:00
XTLine 646e4e32d9 fix(studio): save defaults through the selected local owner
Use the same pinned local catalogue owner for the default-model write and read. Cover a model exposed only by the second local runtime through the actual Settings picker, SSE route and HTTP catalogue validation. Retain the durable-write assertion for CI; this sandbox cannot acquire the OS-user configuration lock. Refs #12674.

Assisted-by: OpenAI Codex
2026-10-11 19:16:14 +08:00
esengine e8f79dfdbb ci: count renames and odd file names in the platform scope 2026-10-11 04:10:35 -07:00
esengine 276cdeb8ac ci: run the macOS and Windows legs on pull requests only when a platform path changed
Pull requests whose diff is provably platform-neutral run only the Linux legs;
the macOS and Windows legs keep their check names but execute on an ubuntu
runner with every step skipped. Pushes to main-v2/studio are unchanged.
2026-10-11 04:02:54 -07:00
xlyy1 90d6f3688b fix(planmode): classify browser_open as unsafe and suggest web_fetch 2026-10-11 19:00:57 +08:00
YHH 596d420244 Merge pull request #12618 from esengine/fix/edit-connection-protocol
feat(studio): change a connection's protocol when editing it
2026-10-11 03:43:56 -07:00
YHH 7b3260f74f Merge pull request #12628 from esengine/fix/plan-browser-open-and-stopped-worker
fix(delegation): a cancelled worker's transcript reaches the parent, but is never an answer
2026-10-11 03:29:40 -07:00
yhh d76b45a281 fix(studio): name the protocol select in the edit form 2026-10-11 03:28:33 -07:00
yhh 08653907e7 refactor(serve): extract the protocol change from editProvider 2026-10-11 03:27:24 -07:00
yhh a489d1e3aa fix(studio): follow the draft protocol for the thinking field and refuse a protocol change on a pinned endpoint 2026-10-11 03:27:24 -07:00
yhh 1851a1991c feat(studio): change a connection's protocol when editing it 2026-10-11 03:27:24 -07:00
XTLine f0371fbe70 fix(studio): preserve perf harness machine settings
Give the benchmark window one stable BenchPort owner so its language, zoom, theme and save instrumentation still reach usePaint. Exercise the real harness and paint hook without changing browser guard assertions. Refs #12674.

Assisted-by: OpenAI Codex
2026-10-11 18:08:41 +08:00
XTLine 5bd8d9678a fix(studio): follow the active local settings workspace
Select the active local pane, else the first local pane, for Memory, model catalogue resolution and legacy-session recovery. Keep machinewide pages on their stable host owner. Retire cached workspace settings ports after successful close and restore the unrelated portFor comment. Refs #12674.

Assisted-by: OpenAI Codex
2026-10-11 18:08:36 +08:00
XTLine ddefb6f48d fix(serve): validate local workspace settings owners
Allow host settings requests to pin an existing local runtime. Refuse stale or remote owners without falling back, while unpinned machine settings retain the first live local owner. Cover real workspace memory mutations, model routing and runtime replacement. Refs #12674.

Assisted-by: OpenAI Codex
2026-10-11 18:08:25 +08:00
YHH 7aca93426b Merge pull request #12634 from esengine/feat/user-message-attachments
feat(control): user messages carry their stored attachments as a field
2026-10-11 03:03:31 -07:00
YHH e8432591d3 Merge pull request #12631 from esengine/fix/e2ebench-passk-population
fix(bench): compute pass^k and pass@k only over tasks that ran every trial
2026-10-11 02:41:13 -07:00
YHH a796093857 Merge pull request #12629 from esengine/fix/e2ebench-reproduce-verdict
fix(bench): name what the reproduction verdict compares instead of claiming a version and a corpus
2026-10-11 02:39:00 -07:00
yhh 5878d58377 fix(studio): the notification setting says when it fires, that it is off by default, and who picks the sound
The copy claimed alerts appear while the window is in the background; the sender
fires whether or not the window is in front. It now says so, says the switch
ships off, and that the sound is the system's with no separate setting.
2026-10-11 02:33:22 -07:00
yhh 074337af99 test(update): assert the installer arguments reach ShellExecute 2026-10-11 02:29:27 -07:00
YHH 0f699f8e02 Merge pull request #12638 from esengine/fix/job-failure-typed-12480
feat(jobs): carry a failed background command's exit code and signal as typed fields
2026-10-11 02:22:07 -07:00
YHH 503b0429b1 Merge pull request #12633 from esengine/fix/shell-probe-memo
fix(sandbox): do not relaunch a bash probe that hung for the same file within a minute
2026-10-11 02:19:28 -07:00
YHH e9c487cd2f Merge pull request #12621 from esengine/fix/stdio-mcp-handshake
fix(plugin): send initialize to a fresh child when the discovery probe goes unanswered
2026-10-11 02:12:36 -07:00
yhh a516e8e709 fix(update): start Studio's Windows installer with --updated so an update keeps shortcuts and the taskbar pin
The update path started the NSIS installer with no arguments, so it ran
as a fresh install: the previous uninstaller deleted the shortcuts and
removed the AppUserModelID entries (the taskbar pin), and the installer
recreated the desktop shortcut. With --updated the installer takes
electron-builder's in-place update path and keeps them.
2026-10-11 02:12:22 -07:00
XTLine b8459b0eff fix(studio): keep appearance inventory and imports on this machine 2026-10-11 17:03:15 +08:00
XTLine 17bdaad0b0 fix(studio): keep window settings independent of active panes 2026-10-11 17:02:58 +08:00
XTLine d1d25e63e5 fix(studio): bind machine memory storage and usage to their owner 2026-10-11 17:02:40 +08:00
XTLine 3bf87b4d0a fix(serve): resolve machine settings through a live local owner 2026-10-11 17:02:24 +08:00
Guan09 1d3267a44c fix(studio): make session tabs opt-in
Add a default-off Appearance preference for session tabs, persist the choice locally, and show the existing strip only when multiple session panes are open. Preserve single-session behavior and cover preference changes, persistence, and unread tab marks with interaction tests.
2026-10-11 17:00:56 +08:00
yhh 8e483dde72 feat(tools): bash_output without a job id lists the running background jobs
The orchestrating agent had no way to ask which background jobs are still
running and for how long, so it kept a count of its own and miscounted. The
listing comes from the job manager that owns the jobs (id, kind, label,
elapsed), through the existing read-only tool so no tool schema is added.
list_subagents rows now report when a child started and, while it runs, for
how long.
2026-10-11 01:59:57 -07:00
yhh eeec501119 fix(studio): a line typed mid-thought no longer splits the thought into two blocks
The queued line is drawn after the message the model is still writing, and
appending the next chunk only looked at the last item, so the chunk opened a
new message and one stretch of thinking showed as two blocks. Appending now
continues the open message past lines that are still waiting; once a line has
been delivered and the earlier message settled, the next chunk is a new one.
2026-10-11 01:59:16 -07:00
YHH 99651759da Merge pull request #12613 from esengine/feat/mcp-auth-required-identity
feat(plugin): a 401 Bearer challenge carries a typed authentication identity
2026-10-11 01:50:33 -07:00
XTLine bc0b454ceb fix(studio): separate extension warning categories 2026-10-11 16:37:23 +08:00
XTLine 95de31ed20 style(studio): keep restore actions visibly clickable at rest 2026-10-11 16:29:04 +08:00
YHH 19cdcaab0a Merge pull request #12625 from esengine/fix/reasoning-only-turn
fix(agent): tell the model when a reasoning-only response was cut off at a limit
2026-10-11 01:24:26 -07:00
YHH 8f115d1702 Merge pull request #12614 from esengine/fix/receipt-write-survives-tool-after
fix(readiness): a write that reached disk stays a write when tool.after reports it failed
2026-10-11 01:24:17 -07:00
yhh ad8b9c71ff fix(agent): tell the model when a reasoning-only response was cut off at a limit 2026-10-11 01:11:34 -07:00
YHH 4f768a84e8 Merge pull request #12626 from esengine/fix/rewrite-box-width-12452
fix(studio): the rewrite box spans its row instead of a bare textarea's width
2026-10-11 01:09:34 -07:00
yhh 14a1ee8f7f feat(studio): show the workspace's changed files as a pill above the composer
The branch chip's small "N changes" moves to a centred pill right above the
input box reading "N files changed +x -y", from the same tree.changes the
workbench lists. Clicking it opens the workbench's change section. A kernel
setting [ui] show_changed_files (default on) switches it off, which restores
the chip's small count.
2026-10-11 00:53:14 -07:00
par73e ca788d0966 fix(studio): refresh shared session trees on kernel changes 2026-10-11 15:21:22 +08:00
yhh d934f1c9dd fix(responses): send the schema's required detail on input_image to the official OpenAI host 2026-10-11 00:05:18 -07:00
yhh ac59199914 fix(responses): keep only the refused-request shape line; annotate after the status is known 2026-10-11 00:04:04 -07:00
YHH b9840adf1c Merge pull request #12573 from esengine/fix/provider-delete-feedback
fix(providers): show a refused removal beside Delete and clear every role naming the removed service
2026-10-11 00:01:20 -07:00
yhh a90e3e2bcd fix(responses): send the schema's required detail on input_image and say what shape a refused request had 2026-10-11 00:00:17 -07:00
YHH 368df20ed2 Merge pull request #12568 from esengine/fix/edit-connection-model-read
fix(providers): offer no model-listing read for a protocol that declares none
2026-10-10 23:43:50 -07:00
yhh 0b2d5327f4 feat: show how long each turn ran, measured by the kernel
TurnDone carries the run time up to the final answer; history carries it on
the answer that closed a turn. The terminal UI and Studio draw it under the
answer, and the live status timer uses the same compact form.
2026-10-10 23:19:32 -07:00
YHH 5d3bce4810 Merge pull request #12582 from esengine/fix/readonly-agent-capability-search
fix(agent): let read-only agents search the capability catalog
2026-10-10 23:19:22 -07:00
yhh 5900acb119 fix(update-helper): accept the package the update path names, and guard it in the package
The Linux helper's accepted package name was a build-time variable that
linux-payload.js never set, so the helper in the .deb kept
reasonix-desktop and refused reasonix-studio as a different package.
It now takes the name from update.StudioLine, the one place the shell
asks dpkg by, so there is no build value to forget. A package command
prints it, studio.yml asserts the packaged helper accepts the .deb's own
package name, and a test ties electron-builder.yml's deb and rpm names
to it.
2026-10-10 22:03:58 -07:00
par73e 9add93a143 fix(theme): make wallpaper transcript glow configurable 2026-10-11 12:59:54 +08:00
esengine 9d63534b05 docs(readme): size the sponsor logos alike and centre them in their cells 2026-10-10 21:55:03 -07:00
YHH aab1a087b9 Merge pull request #12639 from esengine/docs/sponsor-rightcode
docs(readme): thank Right Code as a sponsor, listed first
2026-10-10 21:50:19 -07:00
par73e 1c1c18bd39 feat(studio): add a shared send shortcut preference 2026-10-11 12:45:59 +08:00
yhh b9b934e218 fix(delegation): a cancelled worker's transcript reaches the parent, but is never an answer
A read-only worker cut off by cancellation left a transcript the parent never
heard of: the aggregate said only context canceled. The reference now travels
with the cancellation (parallel_tasks, fleet, task) and read_subagent_result
renders what a cancelled child had said, called and read, labelled as not a
result and its tool output as untrusted. ReadFinalAnswer still refuses any
run that did not complete, so fleet adopt_ref cannot adopt one.
2026-10-10 21:44:25 -07:00
yhh a0802720dc feat(studio): delete a theme pack from Appearance
DELETE /themes/{id} removes a pack from the user's library only; the
listing marks those packs removable, and Appearance offers a two-step
delete on them. A bad id is a 400 theme.bad_id (ErrBadID), never a path
in the response. The config is settled before the files go and restored
if they fail, so it never names a pack that is gone.
2026-10-10 21:42:00 -07:00
yhh dbffc3d740 fix(studio): a column's drag handle no longer covers the scrollbar beside it
The workbench handle lay full height over the transcript's scrollbar while
shut, and the open handles reached into the scrollbar on either side of a
seam, so a press on the thumb resized the column instead of scrolling. The
workspace handle now starts inside the seam, the docked transcript steps in
from the workbench seam, and the shut workbench handle is only as tall as its
grip, centred and set left of the scrollbar. The grip starts the drag itself
once the press has travelled.
2026-10-10 21:39:42 -07:00
yhh fb96492170 fix(control): resuming a Goal runs it, and a reply to a blocked Goal puts it back in pursuit
/goal resume only flipped the state and said nothing, so the Goal sat idle until
another message arrived. It now says the Goal is resumed and runs the next turn
of it. A Goal the model or evaluator reported blocked also resumes on the user's
own next message, as one gated by await_user already did; the continuation epoch
is read after that lift so the lifted turn is advanced by the Goal instead of
being discarded as stale.

Refs #12554
2026-10-10 21:38:13 -07:00
yhh f25b8f79a8 fix(serve): let a model switch go through while only background jobs remain
The switch refused with busy.switch_model_jobs because closing the outgoing
controller cancelled its jobs. The replacement now adopts the job manager and
the workspace write lease, and the outgoing controller is retired without
closing them; its cleanup runs once the jobs it left have finished.
2026-10-10 21:34:36 -07:00
yhh 5a5b981965 fix(control): a refused submission carries a notice code the queueing frontend can word
The admission notices for a session switch, a workspace switch and a draining
runtime told every frontend to resend, but Studio already queues the refused
line on the typed busy code, so following the advice sent it twice. The notice
now carries input_refused; Studio words it as queued, other frontends keep the
English text.

Refs #12554
2026-10-10 21:31:07 -07:00
yhh 14125a17cb fix(studio): keep a long runtime notice whole and its dismiss button on screen
The notes ceiling clipped the dormant-rules sentence and pushed the dismiss
button to a hidden third row. The button now shares the sentence's first row,
the ceiling has a floor outside short windows, and the queue yields the
difference. Also keeps the diagnostic for payload codes whose detail was not
consumed and pins malformed-payload cases.
2026-10-10 21:29:16 -07:00
yhh bb1d478f7f fix(pluginpkg): keep the decoder error in the chain while showing its text as a literal 2026-10-10 21:24:32 -07:00
yhh 60f37ee7c6 fix(pluginpkg): finish rendering package data inside host sentences as literals
Skill shadow and unselected warnings, the migrated and remediation
sentences (plugin name), the persist-status warning, manifest read
warnings and the strict-decode error now show package data through
ShownLocator or ShownIdentity.
2026-10-10 21:24:32 -07:00
yhh 5047e081bc fix(pluginpkg): show package data inside host sentences as literals
Shadow warnings, the incompatible-plugin warning and the skipped
marketplace warning now write the whole sentence first and render the
package-supplied path, error text or reason with ShownLocator, so an
unterminated escape in that data cannot swallow the rest of the sentence.
2026-10-10 21:24:32 -07:00
yhh 775443b26e fix(readiness): an explicit hand-back ends the turn instead of being continued
conclude_blocked and await_user tell the model the turn ends, but a code write
with no check after it still produced a verification gap, so the host ran two
more rounds and reported the turn as failed. A successful hand-back after the
latest write now yields the continuation; the ledger still owes the change and
the completion receipt still reports it as unverified.

Refs #12554
2026-10-10 21:17:50 -07:00
yhh cea97c9b9d fix(textutil): one core for the deleting policies; launch marker leads and matches what was deleted
Display, launch and stored policies share one scrub core, so the report
that a launch line lost characters is produced by the same pass that
deleted them (ZWJ, ZWNJ, bidi, escapes) and never by a second rule. The
marker leads the line so tail truncation in serve and the TUI cannot cut
it, and is worded through i18n. observe.Sanitize delegates to the stored
policy; the policy table is declared in doc.go.
2026-10-10 21:03:16 -07:00
yhh 5e547cf514 fix(installsource): one bounded read for every local manifest 2026-10-10 21:00:33 -07:00
YHH 9e320a452f Merge pull request #12581 from esengine/fix/build-notices-once
fix(boot): emit build-time notices once per lineage across rebuilds
2026-10-10 20:52:00 -07:00
yhh c0b770b7cf feat(control): user messages carry their stored attachments as a field
TurnStarted and Steer events, and the rebuilt history, name the images under
.reasonix/attachments that a message references, resolved by the same
detectRefs pass that decides what the model sees and reported under their
normalized path. The transcript previews from that field instead of matching a
spelling in the text; the model still reads the @path text.
2026-10-10 20:48:32 -07:00
yhh 323a2a95f5 fix(receipt): normalize the host-owned verdict like the paths and fix the Windows tests 2026-10-10 20:44:43 -07:00
yhh 9b7dfce095 fix(sandbox): hold the bash and PowerShell PATH lookups to the shell launch rule 2026-10-10 20:43:27 -07:00
yhh 09cc425fd8 docs(readme): use the sponsor's own wording for the Right Code thanks 2026-10-10 20:37:35 -07:00
yhh c54a107f77 fix(studio): word every placeholder notice through one shared resolver
The runtime bar resolved variables for only two notice codes, so
permission_rules_dormant and job_* showed {n}/{list}/{rule}/{name} verbatim
plus the raw JSON payload. NoticeCard and RuntimeBar now share noticeWording();
a test fails when a code with placeholders has no registered reader.
2026-10-10 20:34:02 -07:00
yhh 581645829f fix(installsource): bound a local .mcp.json and name the cause of a refused install 2026-10-10 20:33:24 -07:00
yhh 6445e0a5e7 docs(readme): thank Right Code as a sponsor, listed first 2026-10-10 20:30:41 -07:00
yhh f2ab4b5f36 feat(jobs): carry a failed background command's exit code and signal as typed fields
The bash job function wraps the process's own ending in jobs.ExitError,
the failed-job notice payload carries exitCode and signal, and the
terminal UI and Studio word the sentence from them. The raw error text
stays on the payload and is what an untyped failure still shows.
2026-10-10 20:30:30 -07:00
yhh 93bfa7dd61 docs(bench): describe pass^k over the tasks that ran every trial 2026-10-10 20:29:09 -07:00
yhh 51625a22e5 docs(trusted-execution): describe the reproduction verdict by what it compares 2026-10-10 20:28:40 -07:00
yhh 23bb1985a1 fix(textutil): one declared policy per surface kind for package-supplied text
Display (prose) now drops bidirectional and format characters exactly as
the prose policy does, where it used to keep bidi controls. The launch
line keeps deleting invisible characters, because redaction has to match
a key a zero-width character split, but it now says so with a marker
instead of reading as an unaltered line. One hostile fixture is asserted
across the locator, prose, display and launch policies.
2026-10-10 20:24:39 -07:00
yhh aa55b66a75 fix(sandbox): one rule for where a shell may be launched from 2026-10-10 20:21:53 -07:00
yhh 6bd9f77ab6 fix(sandbox): do not relaunch a bash probe that hung for the same file within a minute 2026-10-10 20:19:41 -07:00
yhh 94e65f522d fix(receipt): judge host-owned paths when the call ran and treat links of every kind as not owned 2026-10-10 20:16:36 -07:00
yhh 6dab6f3c92 fix(plugin): stop the silent child before starting its replacement and bound the restart by the caller 2026-10-10 20:11:48 -07:00
yhh e3c382e66c fix(studio): reopen the window at the rectangle it was closed at
The shell always opened at the default size, so a window the person had
sized, moved or maximised came back as if nothing had been chosen. The
normal bounds and the maximised flag are now recorded as the window settles
and as it closes, and the next launch reopens there. A rectangle no display
shows any more keeps its size and lets the platform pick the place.
2026-10-10 20:09:32 -07:00
yhh e04d26ac82 fix(bench): compute pass^k and pass@k only over tasks that ran every trial 2026-10-10 20:08:50 -07:00
yhh f8c9bf5547 fix(bench): name what the reproduction verdict compares instead of claiming a version and a corpus 2026-10-10 20:06:44 -07:00
yhh dd46917414 fix(studio): a column's drag handle no longer covers the scrollbar beside it
The workbench handle lay full height over the transcript's scrollbar while
shut, and the open handles reached five to six pixels into the scrollbar on
either side of a seam, so a press on the thumb resized the column instead of
scrolling. Open handles now start one pixel inside the seam, a shut handle is
only as tall as its grip, and the grip starts the drag itself once the press
has travelled.
2026-10-10 19:58:55 -07:00
YHH d086618850 Merge pull request #12565 from esengine/fix/permission-rules-conditional-tools
fix(permissions): treat conditionally registered tools as known to the rule vocabulary
2026-10-10 19:57:07 -07:00
yhh 9a14a65e53 fix(studio): the rewrite box spans its row instead of a bare textarea's width
The message column shrinks to fit its content and a textarea at 100% of it
starts at its default width, so the rewrite box stayed about 270px wide in a
760px transcript. While a message is being rewritten its column now takes the
whole row.
2026-10-10 19:57:05 -07:00
yhh 1138f32e76 fix(plugin): send initialize to a fresh child when the discovery probe goes unanswered 2026-10-10 19:48:57 -07:00
YHH 56ece8f9af Merge pull request #12558 from XTLine/fix/studio-response-call-integrity
fix(responses): reject incomplete Studio tool calls / 拒绝 Studio 不完整工具调用
2026-10-10 19:36:48 -07:00
yhh 46c0f64d87 feat(plugin): a 401 Bearer challenge carries a typed authentication identity
The transport now returns ErrAuthenticationRequired (via AuthRequiredError with
server, resource and scope) for a 401 that carries a Bearer challenge on a
server without explicit credentials. InstallResultForError maps that identity
to action_required/authenticate and no longer matches words in the message or
promotes a bare 401/403 status. Adds a regression test that refresh stays at the
token endpoint stored at sign-in. The guide stops promising a Studio sign-in
control that does not exist yet.
2026-10-10 19:30:18 -07:00
yhh 1c5e59a200 fix(readiness): a write that reached disk stays a write when tool.after reports it failed
The receipt took its success from the result after extensions rewrote it, so
a code write refused or errored by tool.after left no mutation in the ledger
and, as the only change of the turn, no verification was asked for. A named-path
write now keeps the success of the tool's own execution.

Refs #12528
2026-10-10 19:28:48 -07:00
YHH 6e645e2d88 Merge pull request #12612 from esengine/test/cancel-inbox-hold-dispatch
test(control): make the scoped-cancel inbox test deterministic
2026-10-10 18:55:46 -07:00
yhh 87ccb646c2 test(control): hold the post-cancel dispatch so the scoped-cancel assertion sees the queue 2026-10-10 18:40:45 -07:00
YHH f1323bb2fa Merge pull request #12610 from esengine/docs/studio-2.35.0-notes
docs(release): Studio 2.35.0 notes
2026-10-10 17:51:37 -07:00
yhh 37a4e6e4ba docs(release): Studio 2.35.0 notes 2026-10-10 17:38:43 -07:00
yhh 898a5b1626 test(studio): keep the global test limits the one place that sets them
The feedback timer spy counted testing-library's own waitFor deadline once
asyncUtilTimeout became 10s; it now ignores timers armed from inside the
library. The settings-layers case swallowed a lookup that never matched its
textbox name, which cost a full asyncUtilTimeout; it now asserts the real name.
vitest gets testTimeout/hookTimeout of 60s, and find/zoom-keys drop their
private copies of the same limits.
2026-10-10 17:19:17 -07:00
YHH 80c6866e8c Merge branch 'studio' into fix/windows-frontend-test-env 2026-10-10 15:56:58 -07:00
YHH 0b016ab9e5 Merge pull request #12597 from esengine/feat/mcp-pin-tool-definitions
feat(plugin): pin MCP tool definitions at approval and hold changed tools
2026-10-10 15:56:08 -07:00
YHH 733091b9ca Merge pull request #12609 from esengine/fix/trusted-store-growth
fix(agent): stop copying the contract's criteria into every sealed bundle
2026-10-10 15:04:02 -07:00
YHH a3ab566e7f Merge pull request #12112 from KHG420/codex/studio-directory-copy-root-alias
fix(installsource): copy directory root aliases
2026-10-10 14:49:18 -07:00
YHH 53dfb1ace0 Merge pull request #11709 from esengine/feat/studio-sponsor
feat(studio): add a Support entry to Settings > About
2026-10-10 14:34:19 -07:00
YHH 16c63a2531 Merge pull request #11538 from esengine/fix/feedback-unread-refresh
feat(feedback): refresh the unread badge while open and announce new replies
2026-10-10 13:57:19 -07:00
YHH 9ce86fd5bb Merge pull request #12608 from esengine/feat/studio-rpm-package
feat(release): build and publish an rpm of Studio for Linux
2026-10-10 13:23:00 -07:00
YHH 6c5731c55b Merge pull request #12451 from esengine/perf/studio-gpu-mode
feat(studio): switch hardware acceleration off and show how the window is drawn
2026-10-10 12:49:36 -07:00
YHH fd5a34ca99 Merge pull request #12605 from xxoingr/fix/new-session-skipped-default
fix(config): report a skipped default on the new-session path too
2026-10-10 12:16:11 -07:00
yhh cfe4c4d4f8 fix(agent): stop copying the contract's criteria into every sealed bundle
The shadow bundle inlined the accepted contract's criteria and the full
verdict obligation list on every turn. Both scale with the number of
captured tests, and the bundle bytes changed each turn, so the
content-addressed store kept a fresh multi-megabyte copy per turn.

The bundle now names the contract revision by record digest only, and
files the obligations as their own object, which repeats nothing when a
turn leaves them unchanged.
2026-10-10 11:43:47 -07:00
YHH 9d4be2b6a1 Merge pull request #11535 from esengine/feat/auto-archive-11516
feat(studio): opt-in archiving of conversations inactive for N days
2026-10-10 11:40:36 -07:00
yhh 782e54b2fb fix(studio): point aria-controls at the QR block only while it exists 2026-10-10 11:31:27 -07:00
yhh 57be97a8dd Merge remote-tracking branch 'origin/studio' into upd-11709 2026-10-10 11:31:17 -07:00
yhh 2785a9b2fb refactor(i18n): move the notification texts into their own struct; stop polling feedback untouched for a month
Messages is back under the line ceiling with room for the next notification kind.
A report whose last update is over thirty days old no longer keeps the unread
poll running.
2026-10-10 11:31:11 -07:00
yhh cf78bab6b5 Merge remote-tracking branch 'origin/studio' into upd-11538 2026-10-10 11:30:18 -07:00
yhh 0a09f6c035 feat(release): build and publish an rpm of Studio for Linux
Fedora, RHEL and openSUSE cannot install the .deb. electron-builder now
also builds an .rpm, signed and mirrored with the same minisign flow, and
studio-manifest lists it under native_packages[linux-<arch>-rpm] beside the
unchanged .deb key. fpm spells the architecture of an .rpm name x86_64; the
packaging hook renames it to the release architecture and fails on any
spelling it does not know. The package job reads the rpm back for its name,
the helper's owner and mode, and the rpm's post-uninstall leaves the command
link alone on upgrade.
2026-10-10 11:13:32 -07:00
yhh c2b8c15626 fix(studio): report Electron's graphics status as stated and keep Settings off the line ceiling
The graphics panel no longer infers a driver fault from gpu_compositing: it
shows the raw status Electron reports, tells a saved choice from a launch-only
override, and says so when no status exists yet. The settings search moves into
its own hook so Settings.tsx has room again.
2026-10-10 10:38:33 -07:00
YHH 365b5ce0dd Merge pull request #12596 from esengine/fix/serve-submit-admission-and-turn-wait
fix(serve): judge POST /submit by what admission did with it
2026-10-10 10:35:43 -07:00
YHH 0adbdf9319 Merge pull request #12585 from esengine/fix/computer-snapshot-first-line
fix(computer): bound and escape application names in the snapshot and app list
2026-10-10 10:21:19 -07:00
yhh e7dd9a1625 test(studio): give async queries room on a loaded machine
Lazy-loaded Markdown and chart modules take close to a second to load on a
busy Windows runner, which is the whole default testing-library timeout.
2026-10-10 10:17:43 -07:00
yhh 6fd697a1c3 fix(studio): keep Settings under the file-size ceiling after the merge 2026-10-10 10:09:18 -07:00
YHH 8fdca29d84 Merge pull request #12588 from esengine/perf/gitcmd-driver-listing-scope
perf(gitcmd): skip the driver listing for subcommands that cannot run a driver
2026-10-10 10:07:29 -07:00
xxoingr dfbe397a0a fix(config): report a skipped default on the new-session path too
When the saved default_model is a decision source, a build that did not
ask to open on a fallback (reasonix run, the TUI) passes it over and
opens on the first conversation source, and said nothing about it:
ResolveNewSessionChatModel returned only (ref, fallback, ok), so the
boot had no skipped default to report, while the window path's
ResolveStartupChatModel carried one and emitted the notice.

Both resolvers now return one value, NewSessionModel{Ref, Fallback,
SkippedDefault}, and one helper decides that a saved default was passed
over: nothing configured serves it, or its source does not answer
conversation. newSessionModel takes SkippedDefault from whichever
resolver the build uses, so both paths emit the same
default_model_unavailable notice. What each path opens on is unchanged;
an unknown default is still kept on the no-fallback path for the CLI's
error, so it is not reported as skipped there.

Refs #12561
2026-10-11 01:07:07 +08:00
yhh 003a84b02a feat(plugin): pin MCP tool definitions at approval and hold changed tools
Record a digest of every tool definition the first time an authorized
server lists a non-empty catalog. A later listing that adds a tool or
changes any definition field is held out of the model's catalog and cache,
and a call to it is refused with the typed code mcp.tool_definition_held.
The user is told through a notice (mcp_tools_held), and accepts the held
definitions with 'reasonix mcp trust <name>' or POST /mcp/trust, which
advance the record and reconnect the server.

User-level servers keep one record for every workspace; project-level
servers keep one per workspace. Removing a server forgets its record.

Refs #12356
2026-10-10 10:06:10 -07:00
yhh 91c09a3653 Merge remote-tracking branch 'origin/studio' into upd-12451
# Conflicts:
#	desktop/electron/test/unit.mjs
2026-10-10 10:04:59 -07:00
yhh af86ae74f7 fix(feedback): announce a new maintainer reply even when the unread count does not change
The announcement compared only the aggregate unread count, so another reply on a
report that was already unread was never announced. Each report's latest
maintainer reply id is now tracked as well.
2026-10-10 09:52:52 -07:00
YHH 1234c49fbc Merge pull request #12590 from esengine/fix/wire-parity-embed
fix(repolint): wire-parity reads embedded structs and extends, declares PermissionRules
2026-10-10 09:41:23 -07:00
yhh 35e773c2c6 docs(hub): say what syncPins is for now 2026-10-10 09:40:49 -07:00
xxoingr 87dae2a950 fix(installsource): keep the cause of a refused .mcp.json under kind=auto
When a .mcp.json is refused while planning (over the server limit, an
invalid entry), kind=auto hid the reason. A fetched .mcp.json fell
through to the skill attempt, and with strict:false the JSON was planned
as a skill named "mcp". A GitHub repository whose root .mcp.json was
refused ended as a remote HTTP MCP plan at github.com (a repo named like
"mcp") or as ErrUnsupportedKind. A local .mcp.json file ended in "no
installable ... found", and a folder carrying a refused .mcp.json
dropped it without a word.

A source that is itself a .mcp.json, fetched or local, now answers with
its refusal under kind=auto as it already did under kind=mcp. A folder
or GitHub repository whose .mcp.json is refused keeps planning its other
installables; a folder warns with the path and the cause, and when
nothing else plans, the refusal is the error. A .mcp.json that is absent
or declares no server says nothing, as before.

readMCPJSON no longer returns a bare filesystem error: an absent file is
ErrManifestMissing and one that cannot be read (a directory, a denied
read) is ErrSourceUnreadable, so the caller can tell the cause.

Refs #12514
2026-10-11 00:31:41 +08:00
YHH 6c146088c7 Merge pull request #12599 from esengine/test/serve-trace-wait-on-liveness
test(serve): wait for a trace turn to go silent, not for a fixed time
2026-10-10 09:26:03 -07:00
yhh fe61b4c3d3 Merge remote-tracking branch 'origin/studio' into upd-11535 2026-10-10 09:22:23 -07:00
yhh 145285dcef fix(archive): never archive unread conversations, hold hand-edited days at the limit, draw pins from the kernel
An unread finish keeps a conversation out of the automatic sweep until it is
viewed. A hand-edited days value past the limit is held at the limit instead of
wrapping time.Duration. Pins are drawn from the tree's own pinned field;
localStorage only carries pins made before the kernel kept them and each is
sent once.
2026-10-10 09:22:23 -07:00
yhh 74d6e155f6 Merge remote-tracking branch 'origin/studio' into upd-11538 2026-10-10 09:18:38 -07:00
yhh 4bad69a161 Merge remote-tracking branch 'origin/studio' into upd-11709 2026-10-10 09:00:49 -07:00
yhh 4baacc9265 fix(serve): judge POST /submit by what admission did with it
POST /submit answered 409 busy.session_running whenever the turn it had just
started was already over by the time the handler sampled Running(), and also
for a submission parked behind a finishing turn that would run next. The
submit entry points now return the controller's admission result and the
handler refuses only a submission that was dropped.
2026-10-10 08:59:25 -07:00
yhh 5511d40317 test(serve): wait for a trace turn to go silent, not for a fixed time
runTrace gave a fixture turn a fixed 15s to close. A turn is several rounds of
host work, which a loaded Windows runner stretches well past that while the
turn is still emitting events. A turn is now hung only after a silent window,
with a total ceiling for one that never stops.
2026-10-10 08:42:10 -07:00
yhh 46efe8eba4 feat(studio): web search model setting in model preferences 2026-10-10 08:36:00 -07:00
yhh 4ee29685ef feat(studio): let the phone page be added to the home screen
Add a web app manifest, icons and the touch-icon and theme-color tags to the
page so a phone can install it and open it full screen. No service worker is
registered, so nothing cached can outlive a pairing or a credential. The serve
host now types .webmanifest as application/manifest+json.
2026-10-10 08:23:17 -07:00
yhh 783524cfac feat(tui): mark sessions that finished unseen in the resume picker and clear the mark on view
The picker leads a row with an accent dot when the kernel reports it unread.
The TUI posts /sessions/viewed when it binds a conversation (launch, resume)
and when a turn ends in the one on screen while the terminal has focus; a turn
that ends behind another window is marked when focus returns.
2026-10-10 08:11:18 -07:00
yhh 248071332a feat(studio): wide mode for the browser pane that keeps the conversation visible 2026-10-10 07:56:47 -07:00
yhh 8b5b05a8e4 feat(wsl): discover WSL distributions and probe one before installing anything
First slice of treating a WSL distribution as a machine a kernel can run on.
Adds a read-only utility package that lists distributions from the structure of
wsl -l -v and checks one by its architecture and user, plus
reasonix remote wsl list/test. Nothing is installed or started.
2026-10-10 07:52:44 -07:00
yhh dfd848e811 fix(repolint): wire-parity flattens embedded structs and extended interfaces, and declares PermissionRules
An embedded struct's fields are its outer type's own on the wire, and an
interface's extended parents are part of its shape, but the checker read neither,
so the outer PermissionRules type could not be declared and a field added to it on
one side went unnoticed. Same-file embedded structs and extends parents are now
flattened; PermissionLists and PermissionRules are registered.
2026-10-10 07:32:27 -07:00
yhh 0c5b7a625c perf(gitcmd): skip the driver listing for subcommands that cannot run a driver
Refs #12532
2026-10-10 07:30:28 -07:00
yhh 40826bfb13 perf(config): run the retired-key migrations over one read of each config
Refs #12533
2026-10-10 07:28:12 -07:00
yhh 411f0375a1 fix(studio): let threshold rows wrap under the label on narrow widths 2026-10-10 07:13:29 -07:00
yhh 778c97014d fix(computer): bound and escape application names in the snapshot and app list
The first line of a snapshot, the app list and the screenshot caption printed
the application's name raw, and the app list printed window titles escaped
but unbounded. Route them through the literal the modal names already use.
2026-10-10 07:06:48 -07:00
YHH d76a5c4b52 Merge pull request #12583 from esengine/release-notes-2.34.0
docs(release): Studio 2.34.0 release notes
2026-10-10 07:03:07 -07:00
YHH fa4eb9c8df Merge pull request #12552 from esengine/fix/glm-contract-by-declared-protocol
fix(effort): apply the GLM depth contract by declared protocol, not host
2026-10-10 06:55:56 -07:00
yhh 93d5c1eb7c fix(studio): keep pins the kernel has not confirmed from being auto-archived
A pin the kernel refused is rolled back and reported instead of staying
visible, and any remembered pin of a listed conversation the kernel does not
report as pinned is sent again on the next tree load, so a legacy pin in a
workspace added after the first sync is protected before its first sweep.
2026-10-10 06:41:45 -07:00
yhh 4631dc9d44 Merge remote-tracking branch 'origin/studio' into upd-11535 2026-10-10 06:36:12 -07:00
F e976667e4f fix(session): key the turn's seal on identity and end turns from /status
A re-delivered turn_started naming the turn already in front of us is the
same open twice. The transport folds the duplicate seq, but the reducer is
fed raw sequences, and sealing on order marked its live calls "no result"
and dropped an unanswered approval. The seal now keys the start's msgIndex
against the turn the live side holds; naming still runs on the duplicate,
because the row may have been sent between the two deliveries.

The record cannot end a turn. The kernel commits each text-only assistant
round before handleFinalResponse decides to continue it — baseline
criteria, executor-handoff nudge, empty-final retry, steer drain — and
/history carries no end marker, so rebuild stopped inferring "over" from a
record ending on a say. The typed fact rides every status read: Pane's
applyStatus dispatches __running into the reducer, equal answers fold to
the same state, and a payload that cannot say leaves the wire's running
standing. The identity merge stays; the record no longer decides the
turn's life. A lost turn_done self-corrects at the next status answer.

The rebase folds studio's extension retention into rebuild(): an
extension is not in the record either, so the tail keeps it the way the
old __restore filter did.

The status-inflight fixture now answers running over an emitted
turn_started: the pane reads that fact, and a port contradicting its own
wire is not a kernel these reads can be reasoned against.
2026-10-10 21:03:47 +08:00
par73e 6646758aad feat(studio): close browser pages individually or in bulk 2026-10-10 12:59:17 +00:00
XTLine 8d5655f96d Merge studio into fix/studio-restore-undo
Preserve upstream committed-file counts at the Pane-owned restore receipt.
2026-10-10 20:58:14 +08:00
F afa0a33442 fix(session): reconcile the running tail with the record on rebuild
A __restore re-read taken mid-turn — by hand, or by the stream-gap
rebuild — dropped the cards being written, letting the next delta mint
the answer anew: new identity, the entrance replayed over half a reply,
the text before the gap gone until the message frame. The tail now
merges with the record by identity instead of riding after it: the
kernel commits each assistant round at its boundary, before its calls
run, so the record can already hold a call the live side holds open —
the record wins where it is complete, the live card where it is not.

A record ending on the committed answer, with no call of ours still
out, is a turn_done the wire lost: the answer wins and the turn is
over, clearing a running flag nothing else would clear.

A turn whose end never arrived is sealed the way turn_done would have
sealed it when the next one starts, so its calls stop spinning through
a turn that has nothing to do with them.

session.ts stays under the 800-line ceiling: the merge lives in
state/rebuild.ts.
2026-10-10 20:13:48 +08:00
YHH afa05c1183 Merge pull request #12553 from esengine/fix/decision-source-not-a-chat-model
fix(models): a decision source is not offered, or accepted, as a conversation model
2026-10-10 05:03:19 -07:00
YHH 90bb7365ee Merge pull request #12546 from esengine/fix/typesafe-provider-probe
fix(providers): verify a decision source through its own protocol, not a model listing
2026-10-10 04:33:35 -07:00
yhh 2908a3575a docs(release): Studio 2.34.0 release notes 2026-10-10 04:14:17 -07:00
yhh d4dd348b14 fix(agent): let read-only agents search the capability catalog
The read-only execution guard admitted only list and inspect, so
use_capability action=search fell through to the unknown-action block even
though the tool declares it and resolves it exactly like list. The tool's
own description tells every model to search before calling a tool
unavailable, so a read-only subagent was steered into the block.

usecap now exports the declared actions and the catalog-read subset; the
guard reads that set instead of keeping its own copy. The refusal for an
action outside the declared set names the action and the allowed ones.
2026-10-10 04:06:14 -07:00
YHH d6529d4932 Merge pull request #11692 from Guan0923/codex/studio-rewind-file-count
fix(studio): count files across the full rewind range
2026-10-10 03:40:43 -07:00
yhh 333b435321 fix(boot): emit build-time notices once per lineage across rebuilds
Every boot.Rebuild re-derived each build-time notice (dormant permission
rules, held project MCP servers, default-model fallback, missing key) from
unchanged state and emitted it again. The runtime owner now carries a notice
ledger; each build runs through a window that drops notices the previous
successful build already showed, so only new or changed notices are emitted.
2026-10-10 03:39:38 -07:00
YHH 3eedd82740 Merge pull request #12579 from esengine/fix/installsource-windows-newline-test
test(installsource): make the alias quoting fixture creatable on Windows
2026-10-10 03:08:13 -07:00
yhh ba6cd5b843 test(installsource): drop the newline from the alias path fixture on Windows
A Windows file name cannot hold a newline, so the quoting test could not create its directory. The bidi override still exercises the literal-quoting path there.
2026-10-10 02:50:13 -07:00
xiexian 667368c3be fix(installsource): copy directory root aliases 2026-10-10 17:26:18 +08:00
XTLine 2ca8e60a01 style(studio): theme restore actions and bound error details 2026-10-10 17:06:53 +08:00
XTLine ac3673ff72 fix(studio): recover failed rewind presentation refreshes 2026-10-10 17:06:32 +08:00
XTLine 7613fd5d3e fix(studio): separate rewind mutation and receipt lifetimes 2026-10-10 17:06:18 +08:00
XTLine 65e391a401 fix(control): retain stream failure cause during cleanup 2026-10-10 17:00:43 +08:00
XTLine f9d401b6ea fix(responses): accept structurally closed no-argument calls 2026-10-10 17:00:34 +08:00
YHH 5a74b65bbe Merge pull request #12110 from KHG420/codex/studio-skill-root-alias
fix(installsource): follow skill collection root aliases
2026-10-10 01:54:26 -07:00
yhh aa38634842 fix(providers): show a refused removal beside the Delete button and clear every role naming the removed service
A removal refusal rendered in a banner at the top of the settings pane, out of
view once the pane scrolled, so Delete appeared to do nothing. The banner is
now an inline alert in the provider detail header. The list is re-read after a
failed removal too, because the kernel may have written the removal before the
failure. RemoveProvider now clears the guardian, recovery, vision, triage,
decision and advisor roles that named the removed service, walking the same
role table the other repairs use, and its doc comment states the behaviour the
code and tests already have.
2026-10-10 01:44:17 -07:00
YHH 6d7b27c718 Merge pull request #12542 from esengine/fix/computer-effect-grading-windows
feat(computer-helper/windows): report modals and grade typing by read-back
2026-10-10 01:27:42 -07:00
yhh 12bf175c64 fix(permissions): treat conditionally registered tools as known to the rule vocabulary
A rule naming a tool that assembly registers only under some configuration or
in a sub-registry (system_one, advise, best_of_n, apply_isolated,
discard_isolated, submit_plan, conclude_no_changes, review_report,
complete_subtask) was refused on save and reported dormant at load, because the
tool is absent from the registry at that moment. Each owner now declares its
conditional tool with tool.RegisterConditional and the controller's vocabulary
reads the declarations; a boot test reconciles every tool the assembly can
register against the built-ins plus the declarations. The dormant-rule item
shape is also held by wire-parity.
2026-10-10 01:16:59 -07:00
YHH 6e6234b833 Merge pull request #12517 from esengine/fix/plan-mode-refusal-cause-exit
fix(planmode): a refused call names its command, cause class and way out
2026-10-10 01:13:50 -07:00
yhh edccdc416e fix(providers): offer no model-listing read for a protocol that declares none
The edit form showed 'read available models from provider' for a decision
source, whose protocol has no model listing. The provider entry view now
carries canListModels, derived from the protocol table's Discovery
declaration, and the editor hides both listing controls from it.
2026-10-10 00:42:38 -07:00
Will 5559945e7f test(e2ebench): register corpus authoring policies
The authoring guards were three hand-written lists in corpus_test.go: which
corpora must carry a reference solution, which must have a grader that accepts
it, and which must let the agent decide when to stop. A seventh corpus could be
added and be checked by none of them, which is what happened to four of the
seven that exist.

Each corpus now declares, in one registry, how it stands against each rule:
enforced, or exempt with a reason. Discovery walks the corpus root, so an
unregistered corpus is a failure rather than an omission from a list. The three
guards read the registry instead of their own lists, and the stop-policy guard
covers every registered corpus rather than the three that happened to be listed.

An entry must name the corpus this root owns: its directory is compared with the
corpus's path under the root, not with its name, so a registry entry pointing at
some other directory that happens to share a name cannot stand in for the
committed one.

The pruning test the registry replaces was weakened rather than extended: a new
corpus used to be silently unchecked.
2026-10-10 15:35:26 +08:00
XTLine fe52643a86 test(agent): expand terminal lifecycle coverage 2026-10-10 15:04:17 +08:00
XTLine accb314eee test(agent): verify safe recovery and cancellation 2026-10-10 15:04:12 +08:00
XTLine a4fe926a4b test(agent): assert terminal execution and wire history 2026-10-10 15:04:03 +08:00
XTLine d76d47b899 test(agent): add a runnable terminal HTTP fixture 2026-10-10 15:03:49 +08:00
XTLine 9b716795cd test(responses): cover terminal integrity boundaries 2026-10-10 15:03:34 +08:00
XTLine ff1bada908 fix(responses): refuse terminal tool-call loss 2026-10-10 15:03:29 +08:00
YHH 9971114aa4 Merge pull request #12541 from esengine/fix/computer-effect-grading
feat(computer): grade each step's effect and carry the modal that holds input
2026-10-10 00:03:22 -07:00
XTLine f9a0a0926f fix(agent): preserve typed stream failure attribution 2026-10-10 15:03:20 +08:00
esengine 557b160d5e fix(models): a decision source is not offered, or accepted, as a conversation model
The model switcher listed every configured source, so a decision wire
(kind typesafe) appeared beside chat models and choosing one failed in the
provider registry as an unknown kind.

GET /models now answers one job: chat by default, answers=decision for the
decision sources, answers=all for the management views. The switch, the
default model and every role refuse a model whose wire does not answer that
job with a typed code (model.decision_only, model.not_decision_source), and
the provider builder refuses it with config.AnswersMismatchError. A saved
default that names a decision source is passed over like a stale one.
2026-10-09 23:58:44 -07:00
XTLine 172d61bba6 test(studio): cover restore reload and lifecycle boundaries 2026-10-10 14:53:14 +08:00
esengine 8b2ebe6e43 A connection that typed its own supported_efforts keeps its stored choice:
the contract translation (disabled -> none/low, enabled -> default) now
applies only when the menu is the contract, the same rule
InheritedEffortOfficial already used. Before, the Token Rhythm preset shape
sent thinking=enabled with reasoning_effort=max for a stored disabled.
2026-10-09 23:52:50 -07:00
XTLine 0d85a11055 fix(studio): keep restore undo outside transcript cards 2026-10-10 14:52:40 +08:00
XTLine 29fb53efca fix(studio): own rewind receipts at conversation scope 2026-10-10 14:51:36 +08:00
YHH 39d06cf514 Merge pull request #12539 from esengine/fix/boot-shell-probe-off-handshake
fix(boot): bound and persist Windows shell discovery so the handshake is not held behind it
2026-10-09 23:47:27 -07:00
1919833c48 feat(studio): route extracted tab menus through controller close
Problem: PR #12348's browser menu dismissed agent-owned UI entries instead
of closing the underlying pages, overlapping the controller close work in
PR #12067.

Root cause: The tab strip owned a presentation-only close path separate
from the controller's browser lifecycle.

Fix: Integrate the latest studio base with #12067, retain its useTabClose
owner, and adapt #12348's WorkbenchTabs extraction, close-to-the-right menu
and keyboard access to that path. Keep close-all, preserve file editors,
and retain failed pages while disabling repeated closes in flight.

Verification: 99 focused frontend tests, TypeScript/production and perf
builds, census and its 10 fixture comparisons, make check, go vet, make
lint, builtin/boot and browser/serve/control tests, and 11 release-note
tests passed. Browser keyboard/menu acceptance and eight fixture screenshots
cover light/dark and wide/narrow layouts. Intrinsic, contrast and side
guards passed; wheel exits successfully with its two existing advisory
findings. Native Electron acceptance and hosted CI remain outstanding.

Co-authored-by: KHG420 <[email protected]>
Co-authored-by: par73e <[email protected]>
2026-10-10 14:39:25 +08:00
Will 95a19946ed fix(e2ebench): fail mutation and differential analysis when source restoration fails
Both analyses edit a PR's source, run the tests, and put the source back. The
write-back discarded its error and then marked itself restored, so a failed
restore was indistinguishable from a successful one: the sweep went on to report
caught/survivor counts, and the differential went on to report pin results, for
a tree that was still mutated. A verdict from a tree that no longer holds the
PR's code is worse than no verdict, and the mutation would leak into the next
mutant and into the user's checkout.

restoreSource writes the bytes back and proves they came back: it re-reads the
file and compares it byte for byte. A git-based restore is checked with the tree
instead of the exit status of the command that was supposed to perform it, so
files that exist in neither revision now stop the analysis rather than being
dropped. Both failures are returned instead of noted, the differential's result
is recorded as an explicit analysis failure, and the report says the figures are
absent rather than zero.

The I/O entry points are variables so tests can inject a failure instead of
racing one: deleting either check makes TestRunMutationFailsWhenTheRestoreFails
and TestDifferentialPerTestFailsWhenRestoreFails reproduce the old behaviour
("a failed restore still produced a verdict: caught=1 total=1 survivors=[]").
2026-10-10 14:22:55 +08:00
YHH 6cda7afaf2 Merge pull request #12540 from esengine/fix/ask-card-answer-affordance
fix(studio): make the ask card's free-text answer discoverable and name the wait
2026-10-09 23:22:46 -07:00
YHH ccb89c69c0 Merge pull request #12538 from esengine/fix/legacy-import-skip-reasons
fix(import): list the 1.x sessions an import could not read, with a typed reason
2026-10-09 22:58:30 -07:00
esengine 998e0fcd3d style(providers): satisfy errorlint and usestdlibvars in the decision check 2026-10-09 22:36:51 -07:00
YHH 0ee3e741a1 Merge pull request #12482 from SivanCola/fix/studio-sidebar-footer-entries
fix(studio): consolidate sidebar footer entries / 合并侧栏重复底部入口
2026-10-09 22:30:34 -07:00
esengine bb7e5947f5 test(providers): cover decision-source check statuses, and give a refused request its own code 2026-10-09 22:13:31 -07:00
xiexian 85e6a14f87 fix(installsource): follow skill collection root aliases 2026-10-10 13:05:09 +08:00
esengine 8e7e41505f fix(providers): verify a decision source through its own protocol, not a model listing 2026-10-09 21:55:55 -07:00
YHH a7109c5bf1 Merge pull request #12536 from esengine/fix/permission-rule-unknown-tool
fix(permissions): report and refuse rules that name no registered tool
2026-10-09 21:55:50 -07:00
Guan09 b792200d24 fix(studio): align rewind counts with affected files 2026-10-10 12:50:16 +08:00
yhh 7219a57b82 fix(computer-helper/windows): grade typing on the exact change, and read no value for keys
key and hold_key need only the modal their keys go to, so they no longer
read the focused element's value. Typing is confirmed only when the
read-back differs from the value before by exactly the typed text put in at
one place, replacing what was selected there; the changed span is taken
from either end, since repeated characters make it ambiguous. A value that
merely changed and contains a short typed text, as autocomplete or another
writer can leave it, is unverifiable.
2026-10-09 21:41:05 -07:00
yhh 792be5d35d Merge branch 'fix/computer-effect-grading' into fix/computer-effect-grading-windows 2026-10-09 21:38:51 -07:00
yhh b304663fa3 fix(computer): bound and escape a modal's names before the model sees them
A modal's title and the name of the window it holds come from the
application. They were quoted but not bounded, so a 100000-character title
reached the model as about 100 KB, while every other label in the tree is
clipped to 160 by the helper. Both names now go through
textutil.BoundLiteral at that length: controls, bidi overrides and
invisible characters show as visible escapes, and so does a quote, so a
name cannot close its own quotation.
2026-10-09 21:38:05 -07:00
YHH cc4f6eaaf2 Merge pull request #12530 from esengine/perf/skill-roots-once
perf(skill): resolve each discovery root once per pass
2026-10-09 21:28:32 -07:00
yhh 0032370259 feat(computer-helper/windows): report modals and grade typing by read-back
The Windows helper sent keys to whatever held the application's focus and
answered success. With Notepad's error dialog up, `type "hello"` returned
Done:1 while the text went to the dialog's OK button (#11366).

The helper now reads two structural facts, never a title or a message:
- a Win32 modal: an enabled window of the process whose owner chain holds a
  window of the same process it has disabled (MessageBox, DialogBox, common
  dialogs);
- a dialog drawn inside its window that declares WindowPattern.IsModal, which
  is what Windows 11 Notepad shows: one top-level window, still enabled, with
  a XAML popup holding its input.

With them:
- type and paste are refused as computer.blocked, before anything is sent,
  when focus sits in a modal on an element that takes no text: a letter there
  can be the dialog's mnemonic. Typing into a field of a modal (a file name
  in a save dialog) still goes in;
- a ref or point step on an element in a window the application has disabled
  is refused as computer.blocked, naming the modal that holds it;
- key and hold_key still go (Enter and Escape answer a dialog) and say which
  modal they went to;
- type, paste and set_value read the focused element's value before and
  after: confirmed when what was typed is in, suspected_noop when nothing
  changed, unverifiable when it cannot be read (no value, a password, or a
  value too long to compare). Values stay in the helper; set_value no longer
  sends its read-back across the protocol;
- a snapshot lists the modals, and marks the modal's line.

The fixture gains a message box mode and an owned form with a text field;
the live tests run in the logged-in desktop session.
2026-10-09 21:25:41 -07:00
YHH edb496c8e1 Merge pull request #12529 from esengine/perf/control-image-routing-config
perf(control): read the config for image routing only when a turn has attachments
2026-10-09 21:12:36 -07:00
esengine 8a64d2b8db feat(computer): grade each step's effect and carry the modal that holds input
A computer_act step that the helper accepted read to the model as done,
whether or not anything reached the target. On Windows, typing into Notepad
while an error dialog held its input returned Done:1 and the text went
nowhere, and the dialog showed in the tree only as an unnamed window.

The helper protocol gains three optional fields, all read from structure:
- a step's result may carry an effect: a closed class (confirmed,
  unverifiable, suspected_noop), the evidence kinds it rests on
  (value_readback, value_unchanged), and the modal its input went to;
- a failure may carry blocked_by, with the new code computer.blocked for
  input a modal would swallow;
- a snapshot may list modals: ref, title, and the window each one holds.

The kernel owns the grading contract: a class without the evidence that
proves it, an unknown class, or no effect at all (an older helper, or the
macOS helper until it reports one) settles as unverifiable. A step that
sends nothing, such as wait, carries no effect. computer_act says each
step's effect after its note and names the modal in a blocked failure; a
snapshot opens with the modals holding the application's input.

Tool schemas and descriptions are unchanged, so the cache-stable prefix is
byte-identical. Approval is untouched: grading only adds information, and the
new refusal only sends less.
2026-10-09 21:12:23 -07:00
YHH 215bfe8dce Merge pull request #12523 from esengine/fix/host-crash-trail
fix(host): quiet routine request logging and keep a crash trail when the kernel dies
2026-10-09 20:58:04 -07:00
esengine 2b2651d9f5 test(planmode): range over strings.SplitSeq
Refs #12508
2026-10-09 20:51:19 -07:00
esengine aa9266c5da test(sandbox): make the swapped-during-probe test depend on the digest alone
The replacement now keeps the size and restores the mtime, so only the digest
comparison can tell it apart; it fails with the digest taken after the probe.

Refs #11434
2026-10-09 20:50:21 -07:00
YHH 7eab681aa9 Merge pull request #12512 from xxoingr/fix/mcp-json-server-limit
fix(installsource): refuse a .mcp.json with more servers than the limit
2026-10-09 20:44:30 -07:00
esengine cc66e5bb2b fix(sandbox): digest the shell before its probe and document proof scope
The digest recorded for a persisted proof is taken before the launch, so an
executable swapped during the probe is not vouched for. The Probe field comment
now says the memo is private while proofs still persist.

Refs #11434
2026-10-09 20:42:33 -07:00
xiexian 58ab25039e feat(studio): inspect subagent skill declarations 2026-10-10 11:33:12 +08:00
YHH 4f02f9c8bf Merge pull request #12520 from esengine/fix/md-only-turn-verification-waiver
fix(readiness): waive the verification ask when every change this turn is prose
2026-10-09 20:25:09 -07:00
xiexian 36c3719cb4 feat(studio): show resolved plugin commits in install previews 2026-10-10 11:25:06 +08:00
yhh f7a6153d2c fix(import): attribute read failures to corrupt, keep copy_failed for writes
A failure with no sentinel on the read side (missing object, bad content
reference, size or digest mismatch) was reported as a failed copy. Reads now
default to corrupt, writes are classified separately, a folder without a
manifest is not listed as a session, and each copy button names its session.
2026-10-09 20:18:53 -07:00
esengine 8d08136127 fix(boot): bound and persist Windows shell discovery so the handshake is not held behind it
Bash discovery launched each Git Bash candidate serially (10 s each), then a
PowerShell check, and remembered the proof only in process, so every cold start
repeated it in front of the Studio handshake. Candidates are now probed with a
hedge, the search shares one budget and falls back with probe_timeout, and a
successful launch is persisted keyed by path, size, mtime and content digest
(treated as a miss on any doubt). Boot gains shell, prompt and environment
phase marks so the start-up trace names every stretch.

Refs #11434
2026-10-09 20:04:14 -07:00
esengine ffe7638955 fix(studio): make the ask card's free-text answer discoverable and say so in the status line
The free-text box of a question card stayed display:none until Other or a
single pick was chosen, so a card with only fixed options showed nothing
typeable while the run line read "Waiting on you" and the open plan kept
turning its in-progress arc.

The box is now always present on a live card and typing into it is an
Other answer, so there is one control for the free-text path. The run line
names the wait ("Waiting for your answer") and the plan stops turning its
arc while a decision is pending.
2026-10-09 20:00:20 -07:00
yhh 8edecb10ce fix(import): report each 1.x session an import could not read, with a typed reason
The legacy import dropped unreadable sessions behind event.Discard and
returned only a warning count. The migration layer now produces one skip
record per session (name, source path, reason code taken from the reader's
sentinel errors), the import response carries them, and the storage panel
lists them with the fact that the originals are untouched.
2026-10-09 19:46:25 -07:00
SivanCola b4b9fb364e fix(studio): preserve navigation action identities
Problem: The browser contrast guard timed out opening Settings after the
duplicate sidebar footer was omitted beside visible icon navigation.

Root cause: The icon rail's Settings and Account controls did not carry the
chrome.settings and chrome.account identities declared for those actions.

Fix: Declare each footer action identity alongside its icon, label and
destination, and render it on the rail button. Extend the interaction test to
verify those identities and destinations through both footer locations.

Verification: The new identity assertion failed on the previous rail and
passed after the fix. All 36 related interaction tests, production and perf
builds, ui-census invariants and 10 fixture reports passed. The CI browser
guards (intrinsic, contrast and wheel) plus perf/side.mjs passed. Formatting,
go vet, make lint, make check, builtin/boot Go tests and diff checks passed.
2026-10-10 10:42:05 +08:00
esengine 68f7df17ff fix(permissions): report and refuse rules that name no registered tool
A rule such as ask=["rm"] parses but its tool name matches no tool, so it never
fires and the user believes a boundary exists that does not. Saving a rule whose
tool no registered tool (or MCP/extension namespace) can match is now refused
with permissions.rule_unknown_tool; rules already in the file are kept, listed
as dormant in the permissions panel, and announced once at load with a typed
permission_rules_dormant notice.
2026-10-09 19:16:42 -07:00
yhh bfe720403f perf(skill): resolve each discovery root once per pass and share one pass across the built-in subagent tools
Store.roots canonicalised every directory up to three times per call, and
BuiltinSubagentTools ran a full discovery pass for each of the five built-in
skills it checks. On Windows each canonicalisation is a chain of directory
lookups, so every boot.Build paid for around 120 of them in this one step.

Refs #12521
2026-10-09 19:00:31 -07:00
yhh f177f4fbe5 perf(control): read the config for image routing only when a turn has attachments
resolveTurnImages and withVisionRouting loaded the full project config on
every turn to decide how to route images, including turns with no image at
all, where neither answer is used.

Refs #12521
2026-10-09 19:00:31 -07:00
YHH 309e6fb68d Merge pull request #12525 from esengine/ci/windows-full-timeout-budget
ci: raise the Windows package timeout to 18m and print the slowest boot tests on failure
2026-10-09 18:36:49 -07:00
yhh 8ab28a89fb ci: raise the Windows package timeout to 18m and print the slowest boot tests on failure
internal/assembly/boot took 478-717s on the Windows runner across 12 push runs, so the 12m per-package timeout panicked. The full step now tees go test -json and, on failure, prints the failed tests' output and the 15 slowest boot tests.
2026-10-09 18:20:31 -07:00
yhh 69266faf66 fix(readiness): a failed named-path write keeps its targets in the changed set
tool.after can report a finished write as failed, and a move can stop
half-done, so a failure is not proof that nothing was written. Non-prose
targets of a failed named-path call now keep the generic check owed. The
declared-checks cause also covers checks captured at task start.

Refs #12519
2026-10-09 18:06:41 -07:00
yhh c77b9fd284 docs(host): say which log files are redacted; show the exit notice in the page's language
The crash files are the runtime's raw output and are not redacted, only
shell.log and host.log are. The notice now follows the same language the
page draws in instead of the machine locale.
2026-10-09 17:38:16 -07:00
yhh 9e600e1750 fix(host): keep routine polling out of the INFO log and leave a crash trail when the kernel dies
A Studio kernel that died of a runtime fatal left no readable trace: its
stderr sat in host.log, which request logging rotated away every few
minutes, and the shell quit without recording how the kernel ended.

Routes that are polled declare themselves with routine(); a successful
request to one logs at Debug, a refused one stays at INFO. The kernel
mirrors runtime crash output into a bounded crash directory the shell
names, the shell quotes the tail into shell.log and the next launch tells
the person once.
2026-10-09 17:04:16 -07:00
yhh fb0ecb026e fix(readiness): a failed command that may have written is not a proven prose-only change
Failed non-named-path mutations leave the extent of the change unestablished,
so the prose-only waiver no longer applies. The ask now names an unestablished
extent and declared checks as causes, spells paths from the workspace root, and
the trace fixtures are regenerated.

Refs #12519
2026-10-09 16:59:02 -07:00
YHH 65b51b7c02 Merge pull request #12518 from esengine/fix/gitcmd-no-path-format-dependency
fix(gitcmd): resolve repository paths without rev-parse --path-format
2026-10-09 16:11:12 -07:00
yhh 433059e4ba fix(readiness): waive the generic verification ask when every change this task made is prose
The waiver read the whole workspace scan, so a README edit in a repository
with any code was still held to a verification command. It now reads the paths
the task changed: all prose (.md/.rst), scope established, no declared check.
The ask also names the changed paths that keep it owed.

Refs #12519
2026-10-09 16:08:37 -07:00
YHH 98a522aa84 Merge pull request #12511 from xxoingr/fix/tui-ctrl-z-suspend
fix(tui): Ctrl+Z suspends the TUI to the shell
2026-10-09 15:57:37 -07:00
YHH 0f337099d6 Merge pull request #12510 from xxoingr/fix/tui-empty-enter-tail
fix(tui): Enter on an empty composer returns to the newest output
2026-10-09 14:59:31 -07:00
YHH 1bb48c6363 Merge pull request #12505 from esengine/fix/model-switch-while-bg-jobs-12335
fix(serve): say whether a turn or leftover background jobs block a model switch
2026-10-09 14:38:50 -07:00
YHH e1ff932a15 Merge pull request #12506 from esengine/fix/foreign-open-no-copy-12474
fix(session): opening a 1.x conversation no longer forks a copy
2026-10-09 13:15:00 -07:00
yhh 15a2ae11d9 fix(gitcmd): resolve repository paths without rev-parse --path-format
Git before 2.31 echoes the unknown option as an extra output line, which
Open reported as not-a-repository. Normalise the git dir, common dir and
work tree in Go instead, and do the same for the index path lookup.
2026-10-09 13:12:28 -07:00
YHH 58f599ca1c Merge pull request #12171 from XTLine/feat/workspace-branch-switch
feat(session): check the workspace out to another local branch
2026-10-09 12:07:31 -07:00
YHH b3e1811916 Merge pull request #12498 from esengine/feat/provider-reasoning-shape-copy
feat(providers): word the thinking parameter from the protocol's declared effort field
2026-10-09 10:27:05 -07:00
esengine cbc8086776 fix(planmode): bound and escape the echoed command, name dynamic subcommands
The refusal echoed the command verbatim, so a long or multi-line command
could flood the message or forge a Cause line. The subject now goes through
textutil.BoundLiteral. A compound statement whose subcommand is dynamic
(git $X; ls) is attributed to shell expansion instead of leaking the
internal placeholder as an unknown program. The declared-writer cause no
longer claims a tool declared that it changes state.

Refs #12508
2026-10-09 10:04:13 -07:00
yhh 1f8f3711c8 Merge remote-tracking branch 'origin/studio' into fix/plan-mode-refusal-cause-exit 2026-10-09 10:03:24 -07:00
YHH b5082f4f5b Merge pull request #12477 from esengine/fix/rewrite-box-fit-height
fix(composer): size the rewrite box to its text, as the composer does
2026-10-09 09:43:49 -07:00
xxoingr 85ed87426d fix(installsource): refuse a .mcp.json with more servers than the limit
The install preview always shows every high-risk action, and an entry
with "tier": "eager" is high risk. A marketplace is capped at 64 plugins
and a skill repository at 200 skills, but a .mcp.json had no cap: a
package could ship tens of thousands of eager entries, bounded only by
the 2 MB remote read, and every one of them would be displayed.

parseMCPJSON, which planning runs for both a local and a fetched
.mcp.json, now refuses a file declaring more than maxMCPJSONServers (64,
the marketplace cap) with ErrInvalidManifest, before any entry is turned
into an action. The refusal is a hostFactError, so the caller reads the
declared count and the limit rather than an omitted detail.

With kind=mcp, a fetched URL whose path names a .mcp.json now returns
its parse error, as a local .mcp.json already did, instead of
ErrUnsupportedKind; the over-limit refusal otherwise never reached a
remote caller. Any other document fetched with kind=mcp, such as a
SKILL.md, still answers ErrUnsupportedKind.

Refs #12488
2026-10-10 00:34:17 +08:00
XTLine 905b74156a fix(gitcmd): resolve canonical repository paths through aliases 2026-10-10 00:29:54 +08:00
esengine 89668ab2a9 fix(planmode): a refused call names its command, cause class and way out
The planning gate recorded every call it could not prove read-only as a
side effect and told the model it "would change state", with no command,
no cause and no alternative. The effect is now Unclassified when the host
only failed to prove a read, SideEffect when a tool declares itself a
writer or an argument makes a reader write, and the refusal carries the
classifier's structural proof (unknown program, shell construct from the
parser, writing arguments, background, undeclared tool). The marker now
defines read-only shell in the same terms.

Refs #12508
2026-10-09 09:27:18 -07:00
f1shsmell fde3581c08 feat(theme): read the density and corner recipes a pack declares
A pack could declare recipes.density and recipes.corners, the schema
published both tiers and eight of the nine shipped packs carried them,
while nothing in the tree read either one. A pack asking for compact
density loaded clean, warned about nothing, and did nothing.

The reader keeps the two tiers now and reports a value outside them the
way it reports a token that fails its grammar, so an author sees the
problem in the picker rather than a pack that stays default in silence.

A recipe is a factor, not a size: the stylesheet owns what a radius or a
gutter measures, and a pack only says how far it steps. corners writes
--r-scale and density --d-scale, and each declaration that takes one keeps
its own value as the default, so a window with no pack on it is laid out
at exactly the numbers tokens.css writes.

Recipes is a hand-written wire mirror like the pack's other blocks, so it
joins the pairs declared in tools/repolint/wireparity.go: a field added on
one side alone then fails the gate at the line that cannot read it.
2026-10-10 00:26:02 +08:00
YHH e3a4fa4c2b Merge pull request #12092 from XTLine/fix/composer-branch-chip
fix(studio): read the composer branch from git, not the capability scope
2026-10-09 09:13:53 -07:00
xxoingr 34b00e408d fix(tui): Ctrl+Z suspends the TUI to the shell
1.x suspended to the shell on Ctrl+Z, as terminal programs do; 2.x left
the key unbound, so it did nothing and there was no way to drop to the
shell without quitting.

Ctrl+Z now returns tea.Suspend from shortcutKey. Bubble Tea releases the
terminal (alternate screen, mouse tracking, bracketed paste) before
stopping the process and restores and repaints it on fg, so nothing of
1.x's manual mouse reset is needed. On Windows Bubble Tea has no job
control and ignores the request. GUIDE.md and its Chinese version list
the key.

Refs #12144
2026-10-09 23:52:26 +08:00
xxoingr 2c11679055 fix(tui): Enter on an empty composer returns to the newest output
After scrolling back through the transcript, Enter on an empty composer
did nothing; getting back to the live output took Ctrl+End or the wheel.
1.x jumped to the bottom and followed the tail again on an empty Enter,
idle or while a turn ran.

An empty (or blank) Enter now does the same in full-screen mode, through
followTail, which Ctrl+End also uses. It still sends nothing. Inline mode
has no in-app view to move and is unchanged. GUIDE.md and its Chinese
version note the behaviour on the Enter row.

Refs #12144
2026-10-09 23:49:34 +08:00
f1shsmell 9357a0ae36 feat(styles): give layout spacing an eight-step scale
Layout spacing carried 2364 distances across 48 distinct values, 1046 of them
on no scale at all, 10px 220 times and 6px 214 times. A pixel is not a level,
and those two are what a six-step scale could not hold: they are the values a
migration would have had to move 63% of the file's distances to reach. Eight
steps leave 44% off scale instead.

The tie rule is arithmetic rather than taste. 3, 5, 7, 9, 11, 14 and 20 each
sit exactly between two steps, and each sits above the step below it, so a tie
returns to that step and gives up only the air that was added by hand.

The scale is declared here; one surface draws from it. The activity group's
fold header takes --sp-6 for its inset and --sp-4 between the mark and its
label, and both were already those numbers: the two new steps renumbered the
ones below 12px, so the surface was renumbered with them and nothing moves.

TOKENS.md carries the scale, the step each role takes, and the tie table. Its
Space items were rewritten to fit the file's prose budget, which the first
draft of this change went one unit over.
2026-10-09 23:46:29 +08:00
xxoingr c21474751a feat(studio): render right-to-left text blocks in their own direction
A reply in Persian or Arabic was laid out left to right, and a sentence that
mixes in Latin words such as npm or a variable name came out jumbled, because
every block inherited the window's LTR base direction.

Markdown paragraphs, headings, lists, blockquotes and table cells now carry
dir="auto", so the browser takes each block's base direction from its own
first strong character: an RTL paragraph reads right to left and aligns to
the right beside English ones, and the bidi algorithm orders embedded Latin
runs correctly. Code blocks are left out and keep the document's direction.
The user's own message row and its edit box get the same treatment.

List indentation and the blockquote rule use logical properties
(padding-inline-start, border-inline-start), so they move to the right edge
for an RTL block and are unchanged for LTR.

Fixes #12114
2026-10-09 23:41:32 +08:00
esengine da4e3d97d7 fix(serve): tell a running turn from leftover background jobs when a model switch is refused 2026-10-09 07:12:27 -07:00
yhh 9ebbde6678 fix(session): a rebuilt 1.x conversation no longer forks a copy on open
Swapping in this build's system prompt after a model switch made the
transcript differ from the 1.x log, so the save that follows moved the
conversation to a new session. The system prompt is host contract, not
conversation; the unchanged check now leaves it out.

Refs #12474
2026-10-09 06:50:08 -07:00
YHH cdfe244aa3 Merge pull request #12037 from esengine/fix/bound-install-preview-text
fix(installsource): bound and sanitise package-supplied preview text
2026-10-09 06:45:16 -07:00
YHH d45419d5f1 Merge pull request #12476 from esengine/fix/job-completion-notice-code
fix(jobs): give background job completion notices a typed code and payload
2026-10-09 06:44:42 -07:00
XTLine dce0b0c2fb fix(studio): inherit tooltip guards and isolate branch fixture line endings 2026-10-09 20:55:30 +08:00
XTLine 4a123ae574 fix(studio): enforce tooltip regression guard and accessible naming 2026-10-09 20:53:56 +08:00
esengine f26b7cc8e4 test(studio): let the Chat fixture entry offer the thinking switch 2026-10-09 05:27:56 -07:00
esengine c3752645d7 fix(queue): keep the queued-line editor inside the rows the list shows
The list scrolls inside a height the composer's aux area caps, so an editor
grown to its text could be taller than the list and get cut off at an edge.
The fit hook now takes the room left by the list, and the edited row is
scrolled into view. The browser check asserts the whole row sits inside the
list's visible area at desktop and phone width.

Refs #12477
2026-10-09 05:19:34 -07:00
esengine 24f8a0fe2d fix(providers): resolve the effort field per entry and cover existing configs
The effort field is now resolved from the entry's reasoning protocol rather
than its kind, so an auto entry that resolves to a bespoke shape (Zhipu,
MiniMax, LongCat, DeepSeek) no longer claims reasoning_effort. The catalog
carries the protocols the field holds under and the panels read it instead of
keeping a copy. Adds the Anthropic none case and an end-to-end check that
existing effort-only configs keep their request bytes.
2026-10-09 05:18:30 -07:00
YHH 2b9aab4248 Merge pull request #12497 from esengine/fix/studio-wireparity-duplicate-look
fix(repolint): drop the duplicate look.ts mirror declarations that broke the studio build
2026-10-09 05:14:27 -07:00
Will 51ab972870 feat(bench): account for what each context-retrieval run costs
The harness measures context cost and could not report its own. It handed the
agent event.Discard, so the usage every billed completion already reports went
nowhere, and a batch's spend could only be read from a vendor console: this run's
index batch cost 604,544 prompt tokens (527,232 cached) plus 12,600 completion
tokens over 24 runs, and nothing in the tree would have said so.

The bench now installs a sink and totals what the agent reports — the same record
the product's own round path, compaction, command routing and failure paths bill
to. One path, not a second count of the same fact.

Two things stay honest about what the number means. A dry run prints an empty
suffix rather than "prompt-tok=0", because an unmeasured cost must not read as a
measured one. And the count is called usage_reported_requests: a request that
failed before reporting usage leaves no event, so it is not a count of attempts.
2026-10-09 20:08:15 +08:00
yhh cbc1a537ff docs(installsource): describe hiddenActions and previewTruncated as they behave
The package doc still said a cut of 50 actions sets previewTruncated, and the
field comment said escaped text sets it. The test-only PreparePlugin option
is marked as such.
2026-10-09 05:08:09 -07:00
Will 40e8f0b9a9 docs(context-retrieval): record the v2 Experiment I result in Status
The substrate the Status section asked for now has a result, and the result has a
direction, a size that is not yet quotable, and a reason.

The direction reproduces: six of six tasks, and the pooled paired batch searches
less and pages in less with the cue visible. A size is visible on the four tasks
whose absent-cue arm stayed out of a loop (44% less prompt cost), and it is
recorded as a post-hoc subset rather than an effect size: the two excluded tasks
are excluded because their arm looped here, and the qualification bar does not
screen for that. Saying 44% without that qualifier would mean picking the sample
after seeing the result, which is the mistake that invalidated the previous round.

The counts are stated separately because they differ: 36 runs executed, and the
token totals belong to the 24-run index batch that carried the usage line.
2026-10-09 20:01:23 +08:00
yhh 961f4e0c3a fix(repolint): drop the duplicate look.ts mirror declarations that broke the studio build
#12389 and #12035 each added the tsLookFile constant and an appearanceView mirror; the two merged cleanly as text and left tools/repolint unable to compile (tsLookFile redeclared). Keep one constant and one appearanceView entry, plus the zoomRangeView entry #12035 added.
2026-10-09 05:01:15 -07:00
YHH f1a9256ade Merge pull request #12486 from esengine/fix/windows-git-bash-discovery
fix(sandbox): find Git Bash through its registry install path and say when the shell falls back to PowerShell
2026-10-09 04:55:48 -07:00
yhh b51fd9cbb3 fix(installsource): report hidden steps and cut text separately
PreviewTruncated also fired when only whole steps were left out, so the
confirmation said text had been shortened when none had. It now means that
text was cut or escaped, and HiddenActions carries the step count. The card
no longer repeats the plan-level sentence; it shows a short marker aligned
with the other rows.
2026-10-09 04:52:57 -07:00
yhh 987f44cafa test(serve): locate the installed plugin through its recorded root 2026-10-09 04:48:27 -07:00
YHH d3c3e861de Merge pull request #12035 from esengine/fix/ui-scale-single-range-and-keys
feat(desktop): one declared interface-scale range, and Ctrl/Cmd +/-/0 to change it
2026-10-09 04:47:32 -07:00
XTLine 58807823ad fix(session): bound branch lease waits and distinguish workspace refusals 2026-10-09 19:44:27 +08:00
XTLine 9a114fd836 fix(studio): bound branch tooltips and merge latest studio 2026-10-09 19:39:22 +08:00
yhh 0eb5a7ca2c test(plugins): cover the re-check after a blocked rename without a new exported seam 2026-10-09 04:38:49 -07:00
esengine 70621ada23 feat(providers): word the thinking parameter from the protocol's declared effort field
The protocol table now declares where a chosen effort level lands in the
request body (reasoning_effort, reasoning.effort, output_config.effort). The
protocol catalog and the provider list carry it, and the add, detail and edit
panels word their help and example body from it instead of naming the Chat
field for every wire.

The Responses wire now takes the same thinking switch, reasoning_protocol and
declared levels as Chat in all three panels and in the edit endpoints.
reasoning_protocol=none is honoured when resolving the effective effort, and
a declared openai protocol lists its own ladder as the request vocabulary.
2026-10-09 04:36:50 -07:00
YHH 257f39e8c9 Merge pull request #12039 from esengine/fix/acknowledgments-owner-filter
fix(docs): keep the owner, aliases and tool identities out of the acknowledgments table
2026-10-09 04:34:33 -07:00
yhh c32b46c9fd fix(plugins): re-check the approved bytes before every publication attempt and keep the in-place swap off Windows 2026-10-09 04:28:55 -07:00
esengine 51fef65939 test(perf): name the viewport check for what it measures and tighten the queue editor bound
The shrunken-viewport case is renamed to say it checks the box stays within
60% of a shorter viewport with the caret at the end. The queue editor check
now compares against its computed cap and the room the list leaves. The
editor's own max-height was dead (the composer's textarea rule wins), so it
is removed again.
2026-10-09 04:27:24 -07:00
YHH f05a3e3b79 Merge pull request #12083 from esengine/fix/compaction-failure-visible-and-bounded-notice
feat(compaction): announce a held automatic compaction
2026-10-09 04:27:24 -07:00
yhh 293367b5cb fix(sandbox): name the interpreter in use when advising how to silence the PowerShell fallback
prefer=powershell tries Windows PowerShell 5.1 before pwsh, so advising it to a pwsh 7 user would trade away && chains. Advise prefer=pwsh when the interpreter in use chains.
2026-10-09 04:14:33 -07:00
YHH b7afe286eb Merge pull request #12469 from esengine/fix/compaction-truncated-summary-retry
fix(compaction): retry a length-truncated summary once with a larger output cap
2026-10-09 04:14:21 -07:00
YHH f33b563b13 Merge pull request #12468 from esengine/fix/nav-hint-clipped
fix(studio): icon-rail hover hints are no longer clipped or covered
2026-10-09 04:12:13 -07:00
YHH e58220852a Merge pull request #12465 from esengine/fix/receipt-card-noise
fix(receipt): keep host-owned state and the English sentence off the delivery card
2026-10-09 04:10:23 -07:00
yhh e4846a0c5a fix(sandbox): find Git Bash through its registry install path and say when the shell falls back to PowerShell
Auto-detection never looked at where Git for Windows records its own install, so a Git outside the standard roots whose git is not on PATH was invisible and the session silently ran PowerShell. Add that registry location as the last bash candidate source, make a failed probe say whether it timed out, give the probe 10s instead of 3s for a cold start, and write the fallback warning that the startup notice already carries.
2026-10-09 04:09:59 -07:00
yhh 81f310d55a test(market): hold the theme gate and every count field it sums
The theme category check had no test that reached ErrNotTheme. A marketplace
of fifty theme packages plus one with a hook must be refused and keep the
hook step visible in the envelope; sixty pure themes must pass. A table
sets each count field and the runtime alone, so dropping any term from the
sum is caught. install_source gains a test-only PreparePlugin option so the
market layer can plan a marketplace without a network.
2026-10-09 04:08:19 -07:00
YHH d256440031 Merge pull request #12109 from KHG420/codex/studio-registered-skill-discovery
fix(installsource): report registered skill discovery by source
2026-10-09 04:08:18 -07:00
yhh 2e0c5ef3cc fix(installsource): gate on the unprojected plan and keep host sentences whole
The theme gate read the bounded preview, which lists at most 50 steps while
a marketplace may hold 64 plugins and a skill repository 200 skills, so a
hook or runtime past the cut skipped the check and the confirmation. The gate
now reads the unprojected actions; the preview keeps every high-risk step,
caps the rest, and reports how many it left out; the real skill count is
shown. Package inventory returns the package's own text again, and the
display projection is a separate call.

The FULL TRUST reason embedded the runtime command inside a sentence that was
then stripped as prose, so an unterminated escape sequence in an argument
swallowed the warning. The command is now rendered literally after the host
sentence. BoundProse reports removal as well as truncation, BoundLiteral
cannot be mimicked by typed escapes, and colliding display keys are flagged.
2026-10-09 03:54:24 -07:00
YHH de4e0e6a9d Merge pull request #12107 from KHG420/codex/studio-skill-install-shadow-diagnostics
fix(installsource): report skill installation shadowing accurately
2026-10-09 03:50:43 -07:00
yhh 51f3100546 test(plugins): assert the planning-stage refusal of an unmaterializable symlink and an unchanged install after a refused replace 2026-10-09 03:45:57 -07:00
YHH 6a901e8458 Merge pull request #12099 from KHG420/codex/studio-installed-scenario-guide
docs(studio): run scenario exercises from installed packages
2026-10-09 03:35:06 -07:00
yhh cf1db1d307 feat(studio): launch-only GPU override (env or flag), graphics hint points at reduced effects, search hides the setting outside the shell 2026-10-09 03:26:07 -07:00
XTLine 3b839b0821 feat(session): check the workspace out to another local branch
Stack on the refreshed read-only branch chip. Guard checkout atomically against all active panes and unfinished jobs in this host, preserve queued input cancellation, honor existing writer leases, and protect ignored local files including late-created collisions.
2026-10-09 18:22:55 +08:00
SivanCola 019bd48bd8 fix(studio): consolidate sidebar footer entries
Problem: Usage, feedback, account/workspace and settings appeared twice when
the global icon navigation and workspace sidebar were both visible.

Root cause: Sidebar rendered its footer unconditionally, independent of the
navigation visibility already owned by App.

Fix: Pass the existing navigation visibility into Sidebar and render the
footer only when navigation is hidden. Preserve the fallback entries in off,
collapsed-only and narrow-window modes, plus existing feedback and settings
routing. Update layout/parity docs, pending release notes and fixture images.

Verification: 36 relevant frontend interaction tests, 11 release-note/link
tests and the frontend production build passed. Formatting, go vet, make lint,
make check, builtin/boot Go tests and diff whitespace checks passed. Browser
fixtures cover light/dark wide windows and narrow drawers.
2026-10-09 18:20:18 +08:00
YHH 4583c328c7 Merge pull request #12389 from f1shsmell/chore/repolint-look-wire-parity
chore(repolint): declare six look.ts wire mirrors
2026-10-09 03:19:35 -07:00
esengine c6ac527dc5 fix(queue): fit the queued-line editor to its text and cap heights in dvh
The queued-line editor took its height from the number of line breaks, the
same defect as the rewrite box, so it now uses the shared fit-to-content hook.
Both height caps use dvh (with a vh fallback on the same line) so a soft
keyboard shrinking the viewport shrinks the cap. The browser check gains the
queue editor and a shrunken-viewport case; the harness can read a seeded
queue line back.
2026-10-09 03:17:17 -07:00
esengine 0b34dac58d fix(jobs): name the job in a failed-job notice and cover the killed path
A failed job's notice now carries the same JobNotice payload as finished and
killed ones, plus the job function's own error text, so a frontend words
it from the label or id and shows the raw error beneath. A failure stored
without a payload still renders its text. Adds an emission test for the
killed notice.
2026-10-09 03:08:55 -07:00
YHH 6a822a2e20 Merge pull request #12104 from chuiwenwei/feat/memory-omitted-matches
feat(memory): report omitted matches on the memory tool paths
2026-10-09 03:03:47 -07:00
XTLine 4916417b29 fix(studio): read the composer branch from git, not the capability scope
Rebase the read-only branch chip onto the current Pane split. Keep the stylesheet within budget, register the complete workspace Git wire contract, and verify refresh after turn completion.
2026-10-09 17:59:33 +08:00
yhh 7c5ce2b2c4 fix(serve): register the auto-archive routes with the other session routes 2026-10-09 02:48:11 -07:00
YHH ccf26ffb48 Merge pull request #11572 from Guan0923/codex/studio-rewind-checkpoint-state
fix(checkpoint): sync file state after code rewind to prevent false conflicts
2026-10-09 02:45:55 -07:00
yhh 00b01ef3b9 test(desktop): drop an unused binding 2026-10-09 02:27:12 -07:00
yhh cf190c021b fix(desktop): show the interface-size keyboard hint only where the chords are registered 2026-10-09 02:26:14 -07:00
esengine 2d112a548a fix(composer): size the rewrite box to its text, as the composer does
The rewrite box took its height from the number of line breaks, so a long
paragraph written as one line showed about 60px of it. The composer's
fit-to-content logic is now a shared hook used by both, with the box capped
at 60vh. A browser check covers a long single line at desktop and phone
width in both themes.
2026-10-09 02:22:52 -07:00
esengine 01e2f80ad3 fix(jobs): give background job completion notices a typed code and payload
The completion notice carried only English text with a numeric id, so a
frontend could neither localize it nor name the job. Finished and killed
notices now carry a code and a JobNotice payload (kind, id, label); a
failure carries a code and keeps its diagnostic. Studio and the terminal
UI word them from the code; uncoded notices from older sessions still
render their stored text.
2026-10-09 02:18:50 -07:00
YHH f303de0326 Merge pull request #11374 from drakeo338/claude/11372-fix
fix(config): keep the config edit lock directory per user and refuse a foreign owner
2026-10-09 02:15:05 -07:00
yhh 87a6a8132c test(compaction): size the held-notice fixture so the retry stays held under the growth-bounded release 2026-10-09 02:12:45 -07:00
keeyang eb3f823174 feat(studio): open the project folder from the chrome folder mark
The folder mark beside the project name is decoration: it carries no click and
no meaning. A project can already be shown in the file manager, but showing it
reveals it in its parent folder; entering the folder itself needs a second
step.

This adds that step and wires it to the mark. The kernel answer and its refusal
are the ones reveal already asks for (/host/workspaces/locate), so the page
still names no location of its own and only the shell verb differs: openPath,
called on a folder the kernel itself named, instead of showItemInFolder. The
mark stays disabled when the shell cannot open a folder, or when no pane names
a workspace.
2026-10-09 17:06:49 +08:00
YHH 3da403da04 Merge pull request #12135 from KHG420/codex/studio-memory-draft-lifetime
fix(studio): preserve memory drafts and pending writes
2026-10-09 01:59:07 -07:00
yhh 911f9cf471 Merge studio into fix/compaction-failure-visible-and-bounded-notice 2026-10-09 01:53:45 -07:00
yhh 9de1e7adfb Merge remote-tracking branch 'origin/studio' into upd-11535
# Conflicts:
#	desktop/frontend-next/src/actions.ts
#	desktop/frontend-next/src/i18n/en_settings.ts
#	desktop/frontend-next/src/port/boundary.ts
#	desktop/frontend-next/src/port/hub.ts
#	desktop/frontend-next/src/port/mock_boundary.ts
#	desktop/frontend-next/src/port/port.ts
#	desktop/frontend-next/src/port/sse_boundary.ts
#	desktop/frontend-next/src/ui/App.tsx
#	desktop/frontend-next/src/ui/Workspaces.tsx
#	desktop/frontend-next/vite.config.ts
#	internal/contract/config/render.go
#	internal/frontend/serve/hub_tree.go
#	internal/frontend/serve/routes.go
#	internal/state/sessionstore/branch.go
2026-10-09 01:53:26 -07:00
yhh 8aed0bc8ab Merge studio into feat/fix-11734-c 2026-10-09 01:53:14 -07:00
yhh 0e76f564ad Merge remote-tracking branch 'origin/studio' into upd-12451
# Conflicts:
#	desktop/frontend-next/src/state/prefs.ts
2026-10-09 01:51:54 -07:00
yhh 43a443ca15 Merge remote-tracking branch 'origin/studio' into upd-11709
# Conflicts:
#	desktop/frontend-next/src/main.tsx
2026-10-09 01:51:44 -07:00
yhh 17ea70c8df Merge studio into fix/acknowledgments-owner-filter 2026-10-09 01:51:38 -07:00
YHH a20c5f107b Merge pull request #12126 from KHG420/codex/studio-package-install-connection
fix(studio): scope package installation forms to connection
2026-10-09 01:42:29 -07:00
yhh d81168f2a3 build(desktop): record rivo/uniseg checksums 2026-10-09 01:40:54 -07:00
yhh 3eee28cabe Merge studio into fix/ui-scale-single-range-and-keys 2026-10-09 01:40:53 -07:00
yhh 4e8b8e7963 Merge remote-tracking branch 'origin/studio' into upd-12037 2026-10-09 01:40:29 -07:00
yhh d6b2ef4de7 fix(compaction): restart the card's digest on retry, bound-test the retry, align docs
A text-less compaction_progress frame now tells a frontend the streamed
digest was discarded, so a retried summary does not append to the cut one.
The pressure effect test counts attempts as pairs with a larger retry cap
instead of capping raw request count. Adds a shared-window room test and
updates SPEC.md.
2026-10-09 01:36:07 -07:00
YHH 79cef29613 Merge pull request #12470 from esengine/docs/sponsor-packycode
docs(readme): add a sponsor slot for PackyCode
2026-10-09 01:27:24 -07:00
YHH 535c65d8d6 Merge pull request #12367 from KHG420/codex/studio-adopted-interceptors
fix(studio): keep adopted interceptors working after model switches
2026-10-09 01:26:59 -07:00
YHH 2ed7e86f14 Merge pull request #12456 from esengine/release-notes-2.33.0
docs(release): Studio 2.33.0 release notes
2026-10-09 01:15:44 -07:00
yhh 287370ca03 fix(compaction): retry a length-truncated summary once with a larger output cap
The summary request is capped at 16K output tokens and reasoning shares that
cap, so a model with no low reasoning tier can spend all of it and have the
digest discarded. Truncation is already a typed finish reason, so the host
retries once with the cap doubled (bounded by the model's configured output
ceiling) and fails with the cap and retry recorded when that is cut too.
2026-10-09 01:02:20 -07:00
yhh 0d80335152 fix(studio): icon-rail hover hints are no longer clipped or covered
The rail clipped itself to its animated width, which cut off the hints that
hang outside it, and shared a z-index with the workspace rail that paints
later. The rail now keeps its open width and slides off screen instead, and
sits one layer above the workspace rail.

Refs #12462
2026-10-09 00:45:03 -07:00
SivanCola 467707ce4b fix(studio): clarify default light theme surfaces and reading weight
Problem: Default light Studio uses nearly identical frame and reading surfaces,
medium-weight prose, and a broad composer shadow, making long answers feel dense.

Root cause: Reading panes share the page token, replies inherit the small-UI
weight, and the composer uses the generic light surface and shadow recipe.

Fix: Separate the default light reading and composer surfaces, use regular
weight for default reply prose, and shorten the composer shadow. Preserve
explicit weight choices, installed theme packs, wallpaper surface overrides,
dark styling, and layout. Consolidate the duplicate light palette and document
the tokens. Include mock-fixture before/after screenshots at both viewport sizes.

Verification: 68 focused frontend tests; TypeScript/Vite build; browser theme,
weight, wallpaper, draft-input, and geometry checks; gofmt; go vet ./...;
make lint; make check; go test ./internal/tools/builtin/ ./internal/assembly/boot/.
2026-10-09 15:02:36 +08:00
yhh 8fe0b30dc0 fix(receipt): keep explicit VCS writes counted and refuse links into the snapshot store
Only the walk skips VCS store entries. An explicit write into a VCS store is a
change like any other. The snapshot-store exclusion is a path convention for
de-noising, so a symlink anywhere on the way disables it.
2026-10-08 23:40:03 -07:00
yhh 23be1e3344 fix(receipt): drop the CACHEDIR.TAG exclusion
A marker file anyone can write, the model included, would shrink what the host
observes of a delivery. Only state the host itself writes is excluded.
2026-10-08 23:12:39 -07:00
yhh 49073a9648 fix(receipt): keep host-owned state and the English sentence off the delivery card
The unverified-change gap carried a sentence the kernel wrote in English, so
every language showed it; it now carries only the declined command. Changes the
host or a tool owns -- the task-snapshot store, VCS stores (including a .git
file), and directories tagged with CACHEDIR.TAG -- no longer count as work.

Refs #12457
2026-10-08 23:05:04 -07:00
YHH 5e17135b6f Merge pull request #12456 from esengine/release-notes-2.33.0
docs(release): Studio 2.33.0 release notes
2026-10-08 20:53:03 -07:00
YHH 24dee91241 Merge pull request #12450 from esengine/fix/feedback-sync-stable-tags
fix(feedback-sync): count only stable studio tags and merged closing PRs
2026-10-08 20:52:37 -07:00
yhh 4a13a49dc7 docs(release): Studio 2.33.0 release notes 2026-10-08 20:39:55 -07:00
YHH 471c40cff3 Merge pull request #12448 from esengine/perf/studio-idle-gpu
perf(studio): stop a sky pack and frosted panels from keeping the GPU busy
2026-10-08 20:37:20 -07:00
YHH ffca8ca15d Merge pull request #12446 from esengine/chore/bump-go-and-x-net
chore(deps): bump Go toolchain to 1.26.9 and x/net to v0.60.0
2026-10-08 20:21:44 -07:00
YHH 60d26453c8 Merge pull request #12447 from esengine/fix/tui-input-complexity
refactor(tui): bring onKey back under the complexity budget
2026-10-08 20:06:45 -07:00
YHH ee742a31fd Merge pull request #12454 from esengine/fix/mcp-binding-linux-tests
test(mcp): make the enable-binding tests hold on Linux
2026-10-08 19:56:06 -07:00
esengine 37c4f3d744 test(mcp): make the enable-binding tests hold on Linux
The tests that fake Windows name probing on every OS spelled PATHEXT in
upper case while their fixture files are lower case. Windows file lookups
ignore case, so the real lookup finds mcp.cmd for .CMD; a Linux filesystem
does not, so under the fake the probed name missed the fixture and the
tests failed there. The seam fakes the probing, not the filesystem: spell
the extensions as the fixtures are.

Adds the off-Windows counterpart: where nothing probes extensions, a .cmd
beside the command does not affect the binding, and the command file does.
2026-10-08 19:39:56 -07:00
yhh 27f10eaf95 feat(studio): let the user switch hardware acceleration off and see how the window is drawn
The shell exposed no way to leave the GPU out of it. Chromium fixes its GPU
use before the app is ready, so a saved choice (rx-hw-accel, in the existing
window-prefs.json) is read right after the profile directory is set and
before ready; the shell reports what this launch applied and what Chromium
ended with through a new shell:graphics handle. Appearance gets a Graphics
block, shown only in the desktop shell, saying which mode this launch uses
and that a change applies after a restart.
2026-10-08 19:37:02 -07:00
yhh daa13a418a fix(feedback-sync): count only stable studio tags and merged closing PRs
A fixed entry is upgraded from next to a concrete version only when a merged
closing pull request is contained in a studio tag whose release page is neither
a draft nor a prerelease. Prerelease status is read from the releases API, not
from a version cutoff.
2026-10-08 19:32:30 -07:00
yhh 39e4f4033d perf(studio): stop a sky pack and frosted panels from keeping the GPU busy
A pack with a sky drew a blurred cloud bank under six backdrop-filter panels,
so every frame re-blurred 41% of the window and the idle window held a GPU
engine near full on integrated graphics. Frosted panels that are already
72-94% opaque blurred nothing visible, and the grain layer forced a
full-window overlay blend on every frame.

- Sky: CSS breathing animations removed; the canvas redraws on a 15 fps
  timer and only while the window is shown and focused (presence.ts).
- backdrop-filter kept on the composer and the transcript backing only;
  the other panels get a higher flat tint instead.
- Grain drops mix-blend-mode.
- New Appearance setting, Visual effects: Full / Reduced (rx-effects,
  immediate, per machine). Reduced removes remaining blur, text glow, the
  grain and the sky.
- perf/effects.mjs guards all of it in a real browser.
2026-10-08 19:30:48 -07:00
yhh 0cfa0484ce refactor(tui): move the steer and history-edge key rules out of onKey 2026-10-08 19:28:51 -07: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
YHH 178dbb7934 Merge pull request #12443 from esengine/feat/studio-open-html-preview
feat(studio): open an HTML file from the explorer as a page preview
2026-10-08 18:23:00 -07:00
YHH a59623aedc Merge pull request #12442 from esengine/feat/studio-model-picker-parity
feat(studio): group, search and mark the composer model picker
2026-10-08 18:21:51 -07:00
YHH e28b76734c Merge pull request #12437 from xxoingr/fix/tui-ctrl-enter-steer
fix(tui): Ctrl+Enter steers a running turn
2026-10-08 18:20:42 -07:00
YHH 5180a74008 Merge pull request #12436 from xxoingr/fix/tui-right-click-paste
fix(tui): right-click with no selection pastes clipboard text
2026-10-08 18:19:33 -07:00
YHH 02395fea16 Merge pull request #12435 from xxoingr/fix/tui-recall-multiline
fix(tui): history recall walks past a multi-line entry
2026-10-08 18:18:28 -07:00
YHH be021ac52f Merge pull request #12332 from KHG420/codex/studio-sent-image-previews
fix(studio): preview saved image attachments in user messages
2026-10-08 18:17:28 -07:00
YHH 172678e1cd Merge pull request #12429 from icebear02/fix/device-panel-relay-status
fix(studio): explain why Internet device access is unavailable
2026-10-08 18:16:24 -07:00
YHH 449f8ca219 Merge commit from fork
fix(mcp): bind a project server's enable to its declaration
2026-10-08 18:10:26 -07:00
yhh 0dee6fd100 feat(studio): open an HTML file from the explorer as a page preview
Clicking .html/.htm in the workbench explorer shows the file as a page in the
shared browser through the existing disk-path open, reloading when the file
changes on disk. Windows that draw no browser views and remote workspaces keep
the source editor.
2026-10-08 18:09:36 -07:00
yhh 6062a8d011 feat(studio): group, search and mark the composer model picker
The model menu now always opens with a search field, groups models under a
sticky provider heading with a letter chip, uses compact one-line rows and
grows with the window (up to 640px) instead of a short fixed box. Rows keep
the wire format and show a reads-images badge where the model declares it.
Enter no longer lands on the manage action, and an empty search says so.
2026-10-08 17:57:50 -07:00
esengine e2d8eba20d fix(mcp): bind a project server's enable to its declaration
An enable recorded for a server that .mcp.json or a project reasonix.toml
declares held for the server's name. The row now records a digest of the
declaration as written (type, command, args, env, url, headers), the project
.env values it expands, and the content of the workspace files it names:
its arguments, and every file the launcher could start for its command,
found by the launcher's own lookup (base/proc): the declaration's PATH for a
bare name (every Windows spelling of it, in key order), and PATHEXT
siblings on Windows, dotted names included. A different declaration reads
as ActivationChanged: off and awaiting the user. Rows written before this
carry no digest and ask once.

- Specs for a repository-declared server carry a LaunchCheck run before
  every start and redial; env overrides merge in key order.
- Why a server waits is a typed code (awaiting_user_decision,
  changed_since_enabled) on MCPHealth, the serve and ACP wires and the TUI;
  Studio and the TUI word it in the reader's language; Studio shows the
  launch line beside the switch.
- A connect or reconnect by name refuses a repository server in any pending
  state with ErrMCPApprovalOwed (serve: mcp.approval_owed) and its launch
  line; a server from the user's own files is still answered by a connect.
- Build reports each held project server once, so `reasonix run` prints
  one warning line per server; serve prints the same at startup; /mcp and
  `reasonix mcp list` show them. Repository-chosen names are sanitised
  wherever they are printed, and run diagnostics sanitise every line.
- `reasonix mcp enable` prints the approved launch line; a project install
  records the decision for the declaration as written; doctor --live
  starts only what a session would.
2026-10-08 12:09:24 -07:00
esengine 7ba591ad13 test(mcp): an enable covers the declaration and the files it runs
Every test for the project MCP approval change, ahead of the change:

- boot effect tests through the real assembly and the settings switch: a
  rewritten declaration, a launcher changed mid-session and an executable
  swapped behind the declaration's own PATH must not start on an earlier
  enable; held servers are reported once each at build; a repository's
  server name reaches no notice as control sequences.
- config: the enable covers the declaration, the .env values it expands,
  the files it names, the executable the launcher resolves (declared PATH,
  every spelling of PATH, PATHEXT siblings including dotted names), legacy
  rows ask again; base/proc holds the launcher's lookup.
- plugin: the launch check runs before start and redial; env merges in a
  fixed order.
- control/cli/tui/frontend: a connect by name never approves a repository
  server that waits; why it waits is a typed code on every surface; /mcp,
  `reasonix mcp list`, serve's startup warning, run diagnostics and the TUI
  panel print a hostile name as plain text; doctor --live starts only what
  a session would.
2026-10-08 12:09:24 -07:00
YHH a90594dda2 Merge pull request #12439 from esengine/fix/flaky-frontend-2
fix(studio): move focus into the feedback body after the environment has committed
2026-10-08 11:55:51 -07:00
YHH 87b385f86e Merge pull request #12438 from esengine/fix/flaky-frontend-1
test(studio): wait for the pane's subscription before emitting into it
2026-10-08 11:54:53 -07:00
yhh fa985108d7 fix(studio): move focus into the feedback body after the environment has committed
The form moved focus from the tab into the body in a frame callback queued from the environment promise. The fieldset is disabled until that environment is committed, so a frame that fired before the commit called focus() on a disabled textarea and focus stayed on the tab. The move now runs in an effect on the environment, after the commit.
2026-10-08 11:40:32 -07:00
yhh d29c170e54 test(studio): wait for the pane's subscription before emitting into it
The unread interaction tests emitted wire events through a listener map that was only filled once the pane subscribed, and a missing listener was a silent no-op. The first test of the turn-ending group waited for the port to exist, which is earlier than the subscription, so an event could be dropped under load. emit now waits for the listener.
2026-10-08 11:40:22 -07:00
xxoingr bf55a5221b fix(tui): Ctrl+Enter steers a running turn
docs/GUIDE.md lists Ctrl+Enter (or /steer) as the key that adds guidance
to the active turn, and 1.x bound it so. 2.x bound only Ctrl+S, so on a
terminal that reports Ctrl+Enter the documented key did nothing.

While a turn runs, Ctrl+Enter now sends the composer as steer through the
same path as Ctrl+S. Idle it does nothing, as in 1.x, so a press meant as
a newline never submits the draft. Ctrl+S is unchanged.

Refs #12144
2026-10-09 02:29:23 +08:00
xxoingr de3c6bdcd2 fix(tui): right-click with no selection pastes clipboard text
With in-app mouse capture on, a right-click copied an active transcript
selection and otherwise did nothing. 1.x pasted the clipboard's text on
a right-click with no selection, and docs/GUIDE.md and docs/CLI.md say
2.x does too, so the documented paste never happened.

A right-click with no selection now pastes through the same path as
Shift+Insert: text only, through the normal paste handling, with the
existing hint over SSH. It pastes only while the composer is drawn; the
condition that draws it moves into composerShown so the view and the
mouse read one answer. A right-click on a selection still copies it.

Refs #12144
2026-10-09 02:27:16 +08:00
xxoingr ea148fed50 fix(tui): history recall walks past a multi-line entry
Up and Down recalled history only while the composer held a single line.
Once a recalled entry had several lines, Up moved the caret to its first
line and then stopped: nothing older could be reached, and a multi-line
draft could not step into the history at all.

Up now recalls from the composer's first line and Down from its last, as
1.x did; between them the keys move the caret as before. A single line,
soft-wrapped or not, behaves exactly as it did.

Refs #12144
2026-10-09 02:24:54 +08:00
YHH 41e8f3be6f Merge pull request #12421 from xxoingr/fix/tui-mcp-panel-keys
fix(tui): q, h/l and Left/Right in the MCP manager
2026-10-08 11:11:48 -07:00
YHH 034c9519f4 Merge pull request #12409 from esengine/dependabot/npm_and_yarn/workers/crash-report/multi-1f96396017
chore(deps): bump tinypool and vitest in /workers/crash-report
2026-10-08 10:53:41 -07:00
YHH 0188c0a5c7 Merge pull request #12408 from esengine/dependabot/npm_and_yarn/workers/crash-report/source-map-js-1.2.2
chore(deps-dev): bump source-map-js from 1.2.1 to 1.2.2 in /workers/crash-report
2026-10-08 10:52:29 -07:00
YHH edc780dbd3 Merge pull request #12420 from xxoingr/fix/tui-ask-jkhl
fix(tui): j/k and h/l drive the question card
2026-10-08 10:37:48 -07:00
YHH 75ccd2abcd Merge pull request #12419 from xxoingr/fix/tui-approval-jk
fix(tui): j/k and Ctrl+N/Ctrl+P move the approval cursor
2026-10-08 10:22:02 -07:00
icebear02 4c459ad337 test(studio): cover empty cloud status reason 2026-10-09 01:08:05 +08:00
icebear02 d3d4c03452 fix(studio): preserve neutral empty cloud status 2026-10-09 01:07:33 +08:00
YHH b6cd0df288 Merge pull request #12423 from KHG420/codex/studio-feedback-image-read-lifetime
fix(studio): keep pending screenshots within their feedback draft
2026-10-08 10:07:21 -07:00
icebear02 12129ac460 refactor(remotecloud): classify relay dial errors
Refactor error handling in relay dial logic and extract into a new function.
2026-10-09 01:06:16 +08:00
xiexian aedbba6314 fix(studio): retain saved image previews after a failed load 2026-10-09 01:01:58 +08:00
YHH 72f596d209 Merge pull request #12411 from KHG420/codex/studio-queue-edit-read-order-preflight-r2
fix(studio): keep the latest queued edit selection
2026-10-08 09:52:37 -07:00
YHH c47bdfd847 Merge pull request #12432 from esengine/docs/ui-quality-process
docs: UI-change section in the PR template and a 1.x parity table
2026-10-08 09:47:52 -07:00
YHH 40bac6c3c4 Merge pull request #12344 from KHG420/codex/studio-pane-startup-reads
perf(studio): avoid duplicate reads before the session is known
2026-10-08 09:41:30 -07:00
YHH f2112e6ad8 Merge pull request #12426 from KHG420/codex/studio-resend-rewind-failure
fix(studio): preserve message edits when rewind fails
2026-10-08 09:38:50 -07:00
yhh a0a53ca890 docs: add UI-change section to the PR template and a 1.x parity table
Cause: 30 days of Studio UI changes landed without screenshots or review, and a visible control was hidden without a linked issue or a release note.
The PR template had no UI requirement and no table tracked 1.x features.
2026-10-08 09:25:40 -07:00
YHH d779d89bcc Merge pull request #12424 from esengine/docs/readme-plain-language
docs(readme): say what Reasonix does in plain words
2026-10-08 08:12:37 -07:00
YHH d4e7fe771a Merge pull request #12428 from esengine/feat/studio-nav-rail-modes
feat(studio): icon rail at the far left, with feedback and three display modes
2026-10-08 08:03:46 -07:00
icebear02 129743f52f test(repolint): cover the cloud remote status mirror 2026-10-08 22:56:06 +08:00
icebear02 6efb57191f i18n(studio): translate cloud relay status messages 2026-10-08 22:55:52 +08:00
icebear02 25d938b61a test(studio): cover typed cloud status messages 2026-10-08 22:55:36 +08:00
icebear02 8afc203738 fix(studio): explain unavailable Internet access 2026-10-08 22:55:21 +08:00
icebear02 421c32c8dd test(studio): seed the mock cloud reason 2026-10-08 22:54:59 +08:00
icebear02 8cdfffca62 fix(studio): type the cloud remote status reason 2026-10-08 22:54:40 +08:00
yhh 8429cd2708 feat(studio): icon rail at the far left, with feedback and three display modes
Refs #12427. The rail moves left of the sidebar, gains the feedback entry, and the Appearance setting becomes always / only when the sidebar is collapsed / never. Stored on and off values keep their meaning.
2026-10-08 07:53:34 -07:00
icebear02 d1dac77fb5 fix(serve): expose the cloud remote status reason 2026-10-08 22:50:28 +08:00
icebear02 1e2171c7a2 fix(studio): carry the cloud status reason to the share bridge 2026-10-08 22:50:04 +08:00
icebear02 53a8a82896 fix(remotecloud): classify unavailable relay states 2026-10-08 22:49:38 +08:00
yhh 46658f5508 docs(readme): lead with where it runs, then what it does in plain verbs 2026-10-08 07:22:47 -07:00
yhh 008aca42b6 docs(readme): rewrite the plain-language copy in a calmer, more concrete voice 2026-10-08 07:07:27 -07:00
yhh 8dbd6583d7 docs(readme): say the model is a configured service, not something that runs on your machine 2026-10-08 06:30:24 -07:00
xiexian ff48415d47 fix(studio): preserve message edits when rewind fails 2026-10-08 21:23:27 +08:00
yhh f1a8525c44 docs(readme): say what Reasonix does in plain words before the technical detail 2026-10-08 06:21:45 -07:00
xiexian 174228553a fix(studio): keep screenshot reads with their feedback draft 2026-10-08 20:43:48 +08:00
xiexian 29be7e29ec perf(studio): coalesce reads while initial status is pending 2026-10-08 20:32:16 +08:00
xiexian c69e53e528 fix(studio): distinguish settings save outcomes and allow retry 2026-10-08 20:14:26 +08:00
YHH f73421aa65 Merge pull request #12422 from esengine/docs/release-notes-2.32.0
docs(release): Studio 2.32.0 notes
2026-10-08 05:00:52 -07:00
yhh 1128e4dab9 docs(release): Studio 2.32.0 notes 2026-10-08 04:59:12 -07:00
xxoingr 55476745c4 fix(tui): q, h/l and Left/Right in the MCP manager
The /mcp panel opened a server's tools with Enter and backed out with
Esc only. 1.x's MCP manager also went in with l / Right, backed out one
level with h / Left, and closed from anywhere with q, and docs/GUIDE.md
(both languages) still lists those keys, so pressing them did nothing.

The panel now takes l and Right as Enter, h and Left as Esc (back to the
list, or closed from the list), and q closes it from the list or a
server's tools. The repository-declared confirm line is unchanged: any
key but y still cancels it.

Refs #12144
2026-10-08 19:45:53 +08:00
xxoingr bd439b77ef fix(tui): j/k and h/l drive the question card
The ask card took only the arrows: Up/Down (and Tab) to move between
rows, Left/Right to move between questions. 1.x's question card also
took j/k and h/l, and docs/GUIDE.md (both languages) lists them for it,
so pressing them on 2.x did nothing.

The card now takes k as Up, j as Down, h as Left and l as Right, with the
existing wrap, clamp and auto-submit stop. A typed answer or note is
unaffected: the composer gets those keys as text before the card does.

Refs #12144
2026-10-08 19:43:08 +08:00
xxoingr cb755dac33 fix(tui): j/k and Ctrl+N/Ctrl+P move the approval cursor
The approval card moved its cursor with the arrows and Tab only. 1.x also
took j/k and Ctrl+N/Ctrl+P there, and docs/CLI.md says approval rows use
the same row-selection keys as the pickers, so pressing them did nothing.

The card now takes k and Ctrl+P as Up and j and Ctrl+N as Down. None of
them is a row shortcut (y/a/p/n/x), so no answer changes meaning.

Refs #12144
2026-10-08 19:40:00 +08:00
YHH 44f87ce11e Merge pull request #12413 from esengine/fix/running-delegation-marked-at-start
fix(agent): mark a proxied sub-agent call as a delegation while it runs
2026-10-08 04:35:13 -07:00
YHH d44551ac21 Merge pull request #12414 from esengine/fix/refused-call-not-interrupted
fix(studio): show a call the host refused as not run, not interrupted
2026-10-08 04:31:12 -07:00
YHH 680732c5ed Merge pull request #12416 from esengine/fix/recovered-inbox-notice-typed
fix(studio): word the recovered-inbox warning in the window's language
2026-10-08 04:26:44 -07:00
YHH 2e534ca1aa Merge pull request #12415 from esengine/fix/compaction-threshold-shows-effective
fix(studio): say on the compaction threshold field when the capacity guard caps it
2026-10-08 04:22:41 -07:00
YHH e44afbc368 Merge pull request #12342 from KHG420/codex/studio-rewind-restore-prompt
fix(studio): restore rewound prompts to the composer
2026-10-08 04:13:25 -07:00
YHH faf7999823 Merge pull request #12120 from xxoingr/perf/pane-checkpoint-reads
perf(studio): read a pane's checkpoints once on mount
2026-10-08 03:58:28 -07:00
yhh d44a65f3f1 fix(studio): say on the compaction threshold field when the capacity guard caps it 2026-10-08 03:58:04 -07:00
yhh 5edd77861a fix(agent): mark a proxied sub-agent call as a delegation while it runs
A call reached through use_capability only learned its delegation profile after the target finished, so the deck's sub-agent list and the running count missed every sub-agent until it ended. The profile is now announced when execution starts.
2026-10-08 03:53:32 -07:00
yhh 7866a7b1d0 fix(studio): word the recovered-inbox warning in the window's language
The notice now carries its count as a typed payload next to the unchanged English text.
2026-10-08 03:52:30 -07:00
YHH 94b57ca1c0 Merge pull request #12406 from esengine/feat/studio-version-notes
feat(studio): read a version's release notes from the Versions page
2026-10-08 03:48:26 -07:00
yhh 9092e5ede6 fix(studio): show a call the host refused as not run, not interrupted
The argument contract now names its refusal, and the delegation views read that identity instead of treating every failed delegate as interrupted.
2026-10-08 03:45:47 -07:00
YHH d67faee3b5 Merge pull request #12373 from KHG420/codex/studio-queue-send-now-order
fix(studio): send the selected queue item next
2026-10-08 03:42:30 -07:00
esengine 6dcd0d3cc7 fix(studio): keep only declared documents in the notes cache, bound the cache name, keep a row's label open
A notes response is accepted only when its declared type is markdown or plain
text, so an error page served with 200 is refused and never written. The cache
name is lower-cased with a digest for mixed-case prereleases and over-long
versions are refused. Expanding a row on a narrow window keeps its label and
date. Error wrapping and the wait group follow the linter.
2026-10-08 03:33:29 -07:00
dependabot[bot] aa139c9f15 chore(deps): bump tinypool and vitest in /workers/crash-report
Removes [tinypool](https://github.com/tinylibs/tinypool). It's no longer used after updating ancestor dependency [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest). These dependencies need to be updated together.


Removes `tinypool`

Updates `vitest` from 3.2.7 to 5.0.3
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v5.0.3/packages/vitest)

---
updated-dependencies:
- dependency-name: tinypool
  dependency-version:
  dependency-type: indirect
- dependency-name: vitest
  dependency-version: 5.0.3
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-10-08 10:31:17 +00:00
dependabot[bot] b128ecb2d2 chore(deps-dev): bump source-map-js in /workers/crash-report
Bumps [source-map-js](https://github.com/7rulnik/source-map-js) from 1.2.1 to 1.2.2.
- [Release notes](https://github.com/7rulnik/source-map-js/releases)
- [Changelog](https://github.com/7rulnik/source-map-js/blob/main/CHANGELOG.md)
- [Commits](https://github.com/7rulnik/source-map-js/compare/v1.2.1...v1.2.2)

---
updated-dependencies:
- dependency-name: source-map-js
  dependency-version: 1.2.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-10-08 10:30:55 +00:00
YHH 1da6e15962 Merge pull request #12404 from esengine/feat/studio-notes-publish
feat(release): publish each version's notes to the mirror before the catalog names them
2026-10-08 03:29:38 -07:00
xiexian 940d193d26 fix(studio): keep the latest queued edit selection 2026-10-08 18:25:39 +08:00
YHH 5f07a893ab Merge pull request #12369 from KHG420/codex/studio-folded-thought-reveal
fix(studio): pause reasoning reveal while thoughts are folded
2026-10-08 03:25:18 -07:00
esengine b9c95807f7 feat(studio): read a version's release notes from the Versions page
The kernel serves GET /studio/versions/{version}/notes from the release mirror,
keeps each fetched document on disk for good, shares one request between
concurrent readers and refuses by typed code. A row whose catalog entry names
notes gets an inline view with images off.
2026-10-08 03:19:27 -07:00
esengine 8de80b3fe7 fix(release): publish a version without notes when the notes upload fails
The notes are optional, so a failed upload or head-object warns and writes the
catalog entry without a pointer instead of failing the Mirror to R2 step.
2026-10-08 03:15:03 -07:00
YHH c450fad954 Merge pull request #12403 from esengine/fix/flaky-failed-job-phase
test(control): answer the ask that is on screen, not any registered one
2026-10-08 03:10:32 -07:00
YHH c9a6aab619 Merge pull request #12375 from KHG420/codex/studio-tree-refresh-failure
fix(studio): retain the sidebar tree after a failed refresh
2026-10-08 03:08:52 -07:00
esengine 97aab82b2d feat(release): publish each version's notes to the mirror before the catalog names them
Studio release workflow uploads studio/notes/<version>.md ahead of the catalog
write and gives the new entry an optional notes field. A one-time script
backfills the entries already in the catalog.
2026-10-08 03:00:31 -07:00
YHH fa9d78ce14 Merge pull request #12395 from esengine/perf/pluginpkg-single-pass
perf(pluginpkg): scan skills once per parse, parse outside the state lock
2026-10-08 02:58:49 -07:00
YHH a6374d9b9a Merge pull request #12400 from esengine/fix/flaky-wallet-source-flicker
fix(studio): do not blank the wallet when the first status names its account
2026-10-08 02:57:01 -07:00
yhh e1d65e0377 test(control): answer the ask that is on screen, not any registered one
pendingAsk returned an arbitrary entry of the ask map, including one still queued behind the prompt lock. When the second ask took the lock first, the test answered the queued one and then waited for it while the other held the lock for the user: a hang until the package timeout.
2026-10-08 02:55:57 -07:00
YHH 51a94e6602 Merge pull request #12385 from KHG420/codex/studio-queue-edit-escape
fix(studio): keep Escape in the queue editor from stopping the turn
2026-10-08 02:53:55 -07:00
YHH 70a990428b Merge pull request #12399 from esengine/fix/flaky-workspace-lock-owner
fix(workspacelist): let a caller's deadline bound the lock wait
2026-10-08 02:51:01 -07:00
yhh 226c567194 fix(studio): do not blank the wallet when the first status names its account
useWallet treated the empty account of a pane whose status had not arrived as a source, so the first status turned it into a source change: the wallet read that had already landed was emptied and read again.
2026-10-08 02:47:27 -07:00
yhh 32a293b12a fix(pluginpkg): tell a same-root reinstall apart from the entry a parse started from 2026-10-08 02:46:49 -07:00
YHH c6e3169e48 Merge pull request #12394 from esengine/fix/flaky-failed-job-phase
test(boot): make the failed-job provider independent of the job-finished notice
2026-10-08 02:42:02 -07:00
YHH 18e0cf8224 Merge pull request #12391 from esengine/perf/coalesce-package-loads
perf(config): concurrent plugin package reads share one parse
2026-10-08 02:41:16 -07:00
yhh 94d695393e fix(workspacelist): let a caller's deadline bound the lock wait
Update capped the lock wait at 5s even for a caller that supplied its own deadline, so the number of writers a queue could hold was fixed by the package rather than by the caller. The serve tests now pass a budgeted context and fail on the returned error instead of reading a swallowed one as a wrong count.
2026-10-08 02:39:16 -07:00
yhh 7fab14208a test(config): use WaitGroup.Go in the package-read coalescing test 2026-10-08 02:22:00 -07:00
yhh b7ebb884b9 perf(pluginpkg): scan a package's skills once per parse and parse outside the state lock 2026-10-08 02:21:13 -07:00
yhh 4b12befedf test(boot): key the failed-job provider on the START/CHECK turn, not the last user message
The host appends a background-job-finished notice as a user-role message once the job ends. When it landed before the CHECK turn composed, the provider read the notice as the last user message, took its default branch and never issued wait/read, so the answers it recorded for w1 and r1 were empty.
2026-10-08 02:07:58 -07:00
yhh 7ba9278f08 perf(config): concurrent package reads share one parse
Every config load re-parses each enabled plugin package, and a settings sheet
fires a dozen such loads at once, each queued behind the package store's lock.
With a 293-skill package a burst costs one full parse per request. Callers now
wait for a parse that starts after they arrived and share it, so a burst costs
at most two.
2026-10-08 02:07:25 -07:00
xiexian de06daf75a feat(studio): compact context from the ring and inspector 2026-10-08 17:06:23 +08:00
YHH eaac0e8399 Merge pull request #12387 from esengine/fix/import-1x-v5-workspace-routing
fix(migration): file imported 1.x Desktop sessions under their own workspaces
2026-10-08 01:54:43 -07:00
YHH 2872663c2d Merge pull request #12382 from esengine/chore/ui-foundations-f0
chore(studio): F0 ratchet for !important and stylesheet length, browser guards, wheel probe
2026-10-08 01:47:40 -07:00
YHH 1d592d4e61 Merge pull request #12386 from esengine/refactor/studio-pane-split
refactor(studio): split Pane.tsx by concern and lift the pane view into state
2026-10-08 01:42:57 -07:00
YHH e5eb7b892b Merge pull request #12384 from esengine/test/model-switch-keeps-projection
test(boot): a model switch keeps the compaction projection
2026-10-08 01:41:08 -07:00
f1shsmell ca9de5c0d9 chore(repolint): declare six look.ts wire mirrors
Every type the desktop re-declares by hand is held to the kernel's, and
src/port/look.ts was the one file under port/ that no pair named. The theme
a person is looking at is all of it: the pack listing, the pack's two shape
blocks, the import receipt, and the reader's size and wallpaper.

Declared and not inferred: no spelling tells a mirrored contract from a
struct that merely carries json tags.
2026-10-08 16:38:58 +08:00
yhh 40a53ecd8d fix(migration): file imported 1.x Desktop sessions under their own workspaces
The desktop-sessions-v5 by-id store carries no workspace, so every session
from it landed in the workspace picked in the dialog. The owner is recorded
in desktop/workspace-state-v1.json; read it from the picked folder and route
each session to its workspace while that folder still exists.
2026-10-08 01:36:39 -07:00
esengine be9631d18a refactor(studio): split Pane.tsx by concern and lift the pane view into state
Pane.tsx sat at the 800-line ceiling with twelve open pull requests editing
it. The view (flow, analysis, browser) lived in component state, out of reach
of the tab strip and of any setting that will place tabs.

The view now lives in state/paneview.ts, keyed by pane id and owned by the
mounted instance, so a remount for another conversation still starts on the
conversation. Submit, surface slots, the meter row, the plan fold and the run
line move to files of their own; props and the report type move to
panetypes.ts. No visual or behavioural change.
2026-10-08 01:24:34 -07:00
yhh dcb3becaf9 test(boot): a model switch keeps the compaction projection and the system prompt it covers 2026-10-08 01:22:56 -07:00
yhh 78aeea4eac chore(studio): ratchet !important and stylesheet length, wire browser guards, add wheel probe
Adds css-important, css-size and stray-file rules to repolint, deletes the
stray Settings.tsx-e, runs the intrinsic, contrast and concurrent browser
guards in the studio workflow, and adds a report-only wheel-scroll probe.
2026-10-08 01:22:44 -07:00
YHH cc4f466a66 Merge pull request #12136 from KHG420/codex/studio-hook-trial-receipt
fix(studio): associate hook receipts with their trial
2026-10-08 01:20:50 -07:00
xiexian fe483f46d5 fix(studio): consume Escape when dismissing a queued draft 2026-10-08 16:11:14 +08:00
xiexian 5b026929f4 fix(studio): associate hook receipts with their trial 2026-10-08 15:30:49 +08:00
YHH c1f9dc9e1c Merge pull request #12377 from esengine/feat/studio-nav-rail-setting
feat(studio): bring the icon rail back behind an Appearance setting
2026-10-08 00:22:17 -07:00
YHH e9f2b8a6ba Merge pull request #12374 from esengine/fix/window-ground-colour
fix(desktop): match the window background to the page ground
2026-10-08 00:16:46 -07:00
YHH 4cc3f056eb Merge pull request #12372 from esengine/fix/delete-folded-recovery-row
fix(serve): deleting a folded sidebar row also removes the covered recovery copies it hides
2026-10-08 00:14:39 -07:00
YHH f086775c3e Merge branch 'studio' into feat/studio-nav-rail-setting 2026-10-08 00:03:44 -07:00
YHH 8e5c0f3443 Merge branch 'studio' into fix/window-ground-colour 2026-10-08 00:03:06 -07:00
YHH 741b1da8cf Merge branch 'studio' into fix/delete-folded-recovery-row 2026-10-08 00:02:27 -07:00
YHH 63798bff1f Merge pull request #12376 from esengine/fix/settings-extension-models-test-default-model
test(settings): expect the two-call default-model write in the extension refresh test
2026-10-08 00:01:51 -07:00
yhh 6039ba386c feat(studio): bring the icon rail back behind an Appearance setting
The rail was hidden by the studio layer's prototype alignment, which left Nav.tsx rendered but display:none. It is drawn again, beside the workspace column, when this machine has it on (default) and the window is wider than the scene fold.
2026-10-07 23:57:35 -07:00
xiexian fd6b917c80 fix(studio): batch-check models before adding a source 2026-10-08 14:53:59 +08:00
yhh d64c204309 test(settings): expect the two-call default-model write in the extension refresh test 2026-10-07 23:50:03 -07:00
Guan09 f0c4d0828f fix(checkpoint): sync file state after code rewind to prevent false conflicts 2026-10-08 14:47:03 +08:00
yhh 9f2f03e703 fix(desktop): give the Studio window a background that matches the page ground
The BrowserWindow carried no backgroundColor, so any pixel the page does not
cover (frameless window at a fractional display scale) showed the default
white. The ground now follows the stored rx-theme, or the system for auto, at
creation and when preferences are saved or the system theme changes.
2026-10-07 23:37:55 -07:00
yhh 6c193b2783 fix(serve): iterate the removal guards with slices.Backward 2026-10-07 23:33:28 -07:00
YHH d383652b6f Merge pull request #12129 from KHG420/codex/studio-hook-editor-draft
fix(studio): preserve hook edits during trial and save
2026-10-07 23:32:01 -07:00
YHH e01c8ed11a Merge pull request #12093 from KHG420/codex/studio-market-publish-navigation
fix(studio): hold navigation during market publication
2026-10-07 23:31:05 -07:00
YHH 88514b8424 Merge pull request #12161 from KHG420/codex/studio-extension-model-refresh
fix(studio): refresh provider models after extension changes
2026-10-07 23:30:09 -07:00
xiexian 310b8f0702 fix(studio): keep session rows after a failed tree refresh 2026-10-08 14:27:00 +08:00
yhh e11b9b86f5 fix(serve): take every guard before deleting a folded row and erase the lead last 2026-10-07 23:16:58 -07:00
yhh 4f17c13990 fix(serve): keep diverged recovery copies when deleting a folded row 2026-10-07 23:10:16 -07:00
xiexian 49094ac587 fix(studio): send the selected queue item next 2026-10-08 14:09:03 +08:00
yhh de9ff2bed0 test(serve): cover open, versioned and held siblings when deleting a folded row 2026-10-07 23:00:13 -07:00
yhh ceacf5ab25 fix(serve): deleting a folded sidebar row removes its whole recovery lineage
The sidebar folds a conversation's recovery copies under one row, but
/tree/sessions/remove deleted only that row's file. The next copy then took
its place, so the delete looked like a no-op with the order shuffled.
Remove the lineage the way archive already addresses it, leaving siblings a
pane has open.
2026-10-07 22:50:01 -07:00
YHH 3a3a720cda Merge pull request #12359 from KHG420/codex/studio-tui-immediate-local-completion
fix(tui): offer local command completion before kernel replies
2026-10-07 22:48:39 -07:00
YHH ad8cb13030 Merge pull request #12363 from KHG420/codex/studio-memorybench-step-budget
fix(bench): let memorybench tasks run without a step cap
2026-10-07 22:40:21 -07:00
YHH 14004b18bb Merge pull request #12366 from KHG420/codex/studio-latest-followed-link
fix(studio): keep the latest followed browser page selected
2026-10-07 22:39:36 -07:00
YHH 8a315bd011 Merge pull request #12358 from KHG420/codex/studio-remote-reuse-missing-token
fix(remote): preserve a live serve when its token is unavailable
2026-10-07 22:38:50 -07:00
xiexian 4b3ad7c29e fix(studio): pause reasoning reveal while thoughts are folded 2026-10-08 13:18:26 +08:00
YHH cb5c0e036c Merge pull request #12125 from KHG420/codex/studio-runtime-confirmation-window
fix(studio): renew each runtime application confirmation
2026-10-07 22:11:30 -07:00
xiexian 5da56c79dd fix(boot): bind switched interceptors to adopted sidecars 2026-10-08 12:59:32 +08:00
xiexian a591b7044e fix(studio): keep the latest followed browser page selected 2026-10-08 12:45:41 +08:00
xiexian 1092e8c8e2 fix(bench): let memorybench tasks run without a step cap 2026-10-08 12:21:40 +08:00
xiexian 041f40672d fix(tui): offer local command completion before kernel replies 2026-10-08 11:46:31 +08:00
xiexian 3bb435aa10 fix(remote): preserve a live serve when its token is unavailable 2026-10-08 11:34:19 +08:00
YHH cb04e619db Merge pull request #12353 from esengine/fix/refusal-no-guessed-reasoning-cause
fix(provider): stop attributing every refused body to dropped assistant reasoning
2026-10-07 20:26:18 -07:00
yhh 7245b82c6f fix(provider): stop attributing every refused body to dropped assistant reasoning
A 400/422 on a request whose history carried reasoning the connection
could not replay was labelled "the endpoint wants the thinking content
sent back", whatever the endpoint actually said. Dropped reasoning is a
fact about the request, not a cause the host observed, and the endpoint's
refusal carries no typed signal separating the two. The hint is removed;
the refusal now shows the status guidance and the endpoint's own words.
2026-10-07 20:14:57 -07:00
Will ad85f2707f feat(memory): report omitted matches on the memory tool paths
Search and list dropped results silently: hits below the relative score
floor (KeepTopRelativeScore compacts in place, taking the count with it)
and everything past limit. A reader could not tell a complete result from
a truncated one, while the empty-result message already warns that no
result is not proof of absence.

Both paths now carry the note automatic recall already uses for its own
omissions, counting both reasons and staying silent when nothing was
dropped. The spelling lives in one helper the recall block calls too, so
the two cannot drift apart; its output is unchanged. The count is one
function as well, so the case where both reasons fire is tested directly
instead of only through fixtures. Empty results keep their guidance: the
relative floor always keeps the top hit, so empty still means nothing
matched.

Refs #11853.
2026-10-08 11:02:07 +08:00
xiexian dce96d1c5c fix(studio): keep prompt-refine help reachable and shortcut intact 2026-10-08 10:13:54 +08:00
YHH 44e9492be4 Merge pull request #12346 from esengine/docs/studio-2.31.0-notes-refresh
docs(release): refresh the Studio 2.31.0 notes
2026-10-07 18:19:56 -07:00
xiexian 22ca8f3f17 feat(studio): select local sessions for batch actions 2026-10-08 09:12:57 +08:00
esengine d8808e587b docs(release): add the network-path behaviour changes and the ARM64 upgrade note to the 2.31.0 notes 2026-10-07 18:08:49 -07:00
YHH 058e700b67 Merge commit from fork
Refuse network paths before touching the filesystem
2026-10-07 18:07:20 -07:00
esengine 040f77bde0 fix(shellsafe): treat network-path operands as non-read-only 2026-10-07 17:10:49 -07:00
yhh f551c4bb60 fix(tools): decide network-path scope before touching the filesystem
File tools resolved a path through the filesystem before comparing it with the
workspace, so a path spelled as a network share was looked up first. On Windows
that lookup is itself a connection to the machine the path names.

A network path is now answered by its spelling alone, with no filesystem call:
it passes only below a workspace root that is itself a network path, compared
lexically and case-insensitively, and is otherwise refused with
workspace.network_path_outside_scope. The check is one function in fileutil and
sits at the entry of the read tools, the writers, their previews and write-path
resolution, write claims and path observation. Local paths and network
workspaces behave as before.
2026-10-07 17:10:49 -07:00
esengine 389cc40beb fix(browser): refuse network paths before touching the filesystem
A path spelled as a network or device path is now answered by its spelling
alone, before any filesystem call, and is refused with its own code
(browser.network_path) instead of the generic outside-workspace one. The
check lives in fileutil next to the containment helpers and covers page
loads and file uploads.
2026-10-07 17:10:49 -07:00
xiexian 26bf6547d9 fix(studio): restore rewound prompts to the composer 2026-10-08 08:04:33 +08:00
YHH 9d04c2be14 Merge pull request #12341 from esengine/docs/studio-2.31.0-notes
docs(release): Studio 2.31.0 notes
2026-10-07 17:02:39 -07:00
yhh 5940eb155a docs(release): Studio 2.31.0 notes 2026-10-07 16:50:45 -07:00
xiexian bb7d51e7a9 feat(studio): add project folders by dropping onto the sidebar 2026-10-08 07:45:55 +08:00
YHH e4aafd6d8e Merge pull request #12339 from esengine/fix/touch-enter-rewrite-and-queue
fix(studio): Enter inserts a newline in the rewrite box and queue editor on touch devices
2026-10-07 14:38:14 -07:00
yhh 07472fb975 fix(studio): Enter inserts a newline in the rewrite box and queue editor on touch devices
Both boxes submitted on Enter regardless of pointer type; they now ask
touchKeyboard() like the composer does. The queue editor also ignores Enter
while an IME composition is open, as the rewrite box already did.
2026-10-07 14:24:37 -07:00
YHH 459deec872 Merge pull request #12333 from esengine/fix/studio-daily-ping-retry
fix(telemetry): retry the daily ping and re-send it each UTC day while the host stays open
2026-10-07 11:21:36 -07:00
esengine d138102a09 fix(telemetry): keep the daily ping retrying and re-send it each UTC day while open
The anonymous ping was a single attempt at launch with a 1 s total budget; a
failed attempt dropped the day's claim and nothing tried again until the next
launch, and a host left open past UTC midnight was never counted again. The
reporter now retries a failed ping with bounded backoff, waits for the next UTC
midnight (plus a splay) after a success, and stops with its context. Consent is
re-read before every attempt, so turning the setting off stops the next send
without a restart. A refusal the server marks permanent (400/401/403/404/410)
is not retried for the rest of the UTC day; 429, 5xx and network errors back
off. Request timeouts are relaxed since the work is off the launch path. The
payload is unchanged.
2026-10-07 10:45:19 -07:00
YHH 68efeef577 Merge pull request #12336 from esengine/fix/tui-telemetry-consent
fix(tui): ask for telemetry consent and report from the interactive terminal UI
2026-10-07 10:32:30 -07:00
esengine 6d0f9efcf6 fix(tui): ask for telemetry consent and report from the interactive terminal UI
The interactive session that carried the first-run consent and the reporter was
removed, and the replacement terminal UI never started either, so CLI 2.x users
were neither asked nor counted. runTUI now runs the documented consent on the
plain terminal before the UI takes the screen, starts the reporter only after a
mode is stored, and wraps the controller sink so the existing bounded counters
flow.
2026-10-07 10:20:41 -07:00
YHH 43bd7accc2 Merge pull request #12298 from esengine/fix/lease-yield-while-asking
fix(lease): release the workspace write claim while a turn waits on ask/approval
2026-10-07 09:37:22 -07:00
YHH 0848ebfd50 Merge pull request #12306 from esengine/fix/subagent-override-visible
fix(settings): say when a subagent_models entry outranks the subagent model
2026-10-07 09:25:35 -07:00
yhh 7a4a690284 fix(lease): release the workspace write claim while a turn waits on the user
An ask or tool-approval wait kept the session's claim for as long as the user
took to answer, so other sessions were blocked on paths nobody was writing.
The claim is now yielded for the wait and taken again, queueing behind any
session that claimed the extent meanwhile, when the user has answered.

Refs #11531
2026-10-07 09:25:32 -07:00
xiexian 8c644fa972 fix(studio): compact the untouched phone composer 2026-10-07 23:52:54 +08:00
YHH 5a1720f6c9 Merge pull request #12326 from esengine/fix/browser-typed-local-path
fix(browser): a typed path on disk opens as a file, not as a host named by its drive letter
2026-10-07 08:44:29 -07:00
esengine f3dc18f9b1 fix(browser): a typed path on disk opens as a file, not as a host named by its drive letter
The address bar read "D:/DevCode/x.html" as host "d" over http. A drive
letter plus a separator, a leading backslash pair or a single leading slash
now names a file: URL, parsed once in Go (browser.LocalPathURL, used by the
agent's checkURL) and once in the window (localpath.js), both held to one
shared table. The window opens local files for the person; a network share
is refused with its own code instead of a DNS error, and a page's own links
still never reach a file.
2026-10-07 08:29:06 -07:00
xiexian 6545e66e36 fix(studio): group installed skills by source 2026-10-07 23:29:02 +08:00
xiexian 504101cb17 feat(studio): show current plan beside conversation navigation 2026-10-07 23:20:11 +08:00
YHH 693d0fb9b7 Merge pull request #12322 from esengine/fix/summarizer-not-a-digest
fix(compaction): a summarizer reply that is not a briefing is a failed summary
2026-10-07 08:03:17 -07:00
xiexian 2a383830b6 fix(studio): put the current model first in the composer picker 2026-10-07 22:58:53 +08:00
esengine 62d5c3378b fix(compaction): a summarizer reply that is not a briefing is a failed summary
The summary request is one user message that ends on the last tool output, so
the model sometimes carries on the agent's work (pseudo tool calls, "next I
will ...") and the host installed that reply as the digest: a read-only fold has
no changed file for the coverage guard to compare.

- The user turn now ends with an explicit ask for the briefing.
- A reply with no level-2 heading, the shape the summary prompt requires, fails
  with the typed code summary_not_digest. It is billed to the compaction, is
  retried like any transient summary failure once the input has grown, and
  degrades to the mechanical digest when the fold must free space.
- The code has frontend text in the TUI catalogues and the desktop table.
- Test doubles that answer summary requests now speak the heading contract.
2026-10-07 07:51:24 -07:00
YHH 1c35080ea9 Merge pull request #12317 from esengine/fix/failure-pins-only-its-call
fix(compaction): a failing call pins its own result, not its parallel batch
2026-10-07 07:41:11 -07:00
YHH 1ecd331b15 Merge pull request #12319 from esengine/feat/feedback-levels-s2
feat(feedback): show the earned level, progress and the window a 429 names
2026-10-07 07:32:31 -07:00
xiexian 119dbd72ce feat(studio): open project actions from context menus 2026-10-07 22:32:30 +08:00
esengine 90005c538f feat(feedback): show the earned level, progress and the window a 429 names
The worker now states a profile on GET /v1/feedback/mine and names the
window in every feedback refusal. The kernel client dropped both: the
profile never reached a frontend and a 429 collapsed to "wait N seconds".

- feedback.Mine carries a validated Profile (null when absent or
  incoherent; the last confirmed one while offline, cleared with the
  install identity).
- Refusals decode error.params into a typed LimitError (window, reset
  time, wait) for rate_limited, reply_limit and busy; serve forwards the
  same fields through refuse.
- Studio shows the level row, progress track, limits, trust note and the
  badge artwork (single-colour SVG on text tokens) in My feedback, and
  says which window a refusal ran out of and when it resets.
- The terminal's /feedback list prints the same standing and refusals,
  worded through the i18n catalogue.

Refs #12025
2026-10-07 07:11:41 -07:00
xiexian f11cd2226b fix(studio): align advanced appearance with settings cards 2026-10-07 22:09:54 +08:00
xiexian 2b23801457 feat(studio): offer quick archive and delete on session rows 2026-10-07 21:36:48 +08:00
esengine 8c908a1fe8 fix(compaction): a failing call pins its own result, not the batch it ran in
keepToolCallGroup kept every result of a parallel tool-call group when any one
failed. Models batch 4-11 calls per turn, so one failing test pinned the whole
batch of reads, the candidate exceeded the 50% checkpoint ceiling, the billed
summary was discarded, and the non-transient block was never retried.

- The retained assistant turn now issues only the calls whose results stay; the
  folded copy carries the rest, so the summarizer and the coverage guard still
  see them and no request issues a call nothing answers. Turns whose call ids
  cannot be told apart stay whole.
- When the retained failures alone would exceed the ceiling, the fold gives up
  retaining them before the summary is requested; the coverage guard still makes
  the digest name each failed command.
- A candidate_above_ceiling block is released by input growth below the physical
  ceiling, and an overflow on a changed input may fold, instead of holding
  forever.
2026-10-07 06:33:26 -07:00
xiexian 1e0043d817 fix(studio): make chart values discoverable along dense lines 2026-10-07 21:09:38 +08:00
xiexian 69a046fed3 feat(studio): quote selected reply text from the context menu 2026-10-07 20:54:23 +08:00
xiexian ec6ffb4887 feat(studio): fold feedback conversations and preserve unread replies 2026-10-07 20:36:51 +08:00
esengine 34a8dea646 fix(settings): say when a subagent_models entry outranks the subagent model
The Settings subagent row writes subagent_model, while an entry the CLI
writes under subagent_models is read first for its profile, so the row
looked inert. The kernel now lists those entries per role with the file
that holds them, the row names each one, and a user-config entry can be
cleared from the row.

Refs #12151
2026-10-07 05:28:04 -07:00
par73e f5dc84673a feat(studio): close browser pages individually or in bulk 2026-10-07 20:09:22 +08:00
YHH 9a6c6828e6 Merge pull request #12301 from esengine/fix/default-model-home-machine
fix(settings): record the default model on the machine its models come from
2026-10-07 04:04:23 -07:00
YHH 4a563646c6 Merge pull request #12299 from esengine/fix/subagent-effort-per-model
fix(subagent): resolve inherited subagent effort against the model that runs
2026-10-07 03:47:54 -07:00
esengine 6ecdee4e7b fix(settings): record the default model on the machine its models come from
A brokered pane takes its default from the home machine's catalogue, so a
default_model written next to the pane's own kernel was never read and the
row snapped back. The default is now its own request, POST /default-model,
sent to this machine's pane; the pane's switch stays a plain /model. A
brokered pane refuses the request by code instead of ignoring it.

Refs #12043
2026-10-07 03:42:01 -07:00
YHH 28abc7995b Merge pull request #12297 from esengine/fix/session-data-hint-not-a-refusal
fix(bash): session-data notice is not a refusal; point spill reads at read_file/grep
2026-10-07 03:39:58 -07:00
yhh 924b1689d4 fix(subagent): resolve the inherited subagent effort against the model that runs
agent.subagent_effort was validated only against the parent model and then
applied to whichever model a subagent selected (subagent_models.task, a
skill's own model, a per-profile model, a per-call model) through
NormalizeEffort, which remaps across providers or lets an unsupported
level through. The default is now resolved per executing model by one
function; a level that model does not declare is dropped with a warning and
the model keeps its own default. Explicit efforts still go through
NormalizeEffort, whose refusal now satisfies errors.Is(err,
config.ErrEffortUnsupported) and names the model and level.

The ACP-specific subagent resolver and task defaults had no caller (ACP
sessions are built by boot.Build), so they are removed.

Refs #11871, Refs #11872
2026-10-07 03:35:37 -07:00
YHH fda37354be Merge pull request #12294 from esengine/fix/stop-unconfirmed-12232
fix(desktop): report an unconfirmed stop or decision by who held it up (#12232)
2026-10-07 03:30:00 -07:00
yhh 9fed189018 fix(bash): say the session-data notice is not a refusal and point spill reads at read_file/grep
The notice appended to bash output when a command mentions the session store
read like a refusal: it said to stop retrying and report, and when the command
matched nothing it was the only text returned. A model that had just been told
to read a spilled output gave the output up. State that the command was not
blocked and name read_file/grep for spilled outputs.
2026-10-07 03:27:44 -07:00
YHH b2e3576d20 Merge pull request #11354 from drakeo338/claude/11353-fix
fix(installsource): reject npm package names that resolve as paths, tags or tarballs
2026-10-07 03:18:49 -07:00
yhh 9ab569c529 fix(desktop): report an unconfirmed stop or decision by who held it up, and end a stop on the kernel's own idle report
Cancel had no bounded wait, so a stop the kernel never acknowledged left the
button on "stopping" forever, and stopping only cleared when the running flag
dropped. Cancel now shares the bounded acknowledged post with decisions, which
fails with a typed DeliveryError (kernel_busy, unreachable, ui_stalled). A
deadline that fires far past its time on the monotonic clock is reported as
this window being stalled instead of a busy kernel. A stop also ends when the
kernel's status reports no live turn.

Refs #12232
2026-10-07 03:18:27 -07:00
YHH c94edbcba3 Merge pull request #12283 from esengine/feat/keep-awake-while-running
feat(studio): keep the computer awake while a turn is running
2026-10-07 02:28:19 -07:00
YHH 8f2b8d74cc Merge pull request #12111 from xxoingr/feat/feedback-dialog-fill
feat(studio): let the feedback dialog fill the window
2026-10-07 02:24:28 -07:00
YHH c4e920fbf7 Merge pull request #12286 from esengine/feat/studio-windows-arm64-release
ci(release): ship a native Windows ARM64 Studio
2026-10-07 02:18:14 -07:00
yhh 5f9080ee5e feat(studio): keep the computer awake while a turn is running
The shell held nothing against system suspension, so a laptop that went to
sleep overnight froze every running session. The kernel now answers how many
panes have a turn in flight (GET /tray/running, local and remote, read from each
controller), and the Electron shell holds a prevent-app-suspension block for as
long as that is non-zero, reconciling against the answer on every poll so a
closed pane or a dead kernel cannot leave it held. A machine-level switch in
Settings > Appearance > Window turns it off.
2026-10-07 02:15:44 -07:00
yhh 0ae7693526 ci(release): ship a native Windows ARM64 Studio
Build windows/arm64 on a native windows-11-arm runner and carry it through the
signing chain as a second leg of every Windows stage: one tree per job, legs
serial under the certum-signing group, a failed leg reruns alone. The package
check learns app-arm64.7z and refuses any PE image that is not the leg's
architecture, a new install smoke runs each installer on its own architecture
before publish, and publish counts, names and hash-checks both architectures.

The arm64 installer is packed with the 7-Zip BCJ filter: the default ARM64
filter yields an archive the NSIS extractor cannot decode, and the installer
then exits 0 with every executable missing. Signing contract tests pin the
matrix, the serial legs and the secret-free stages.
2026-10-07 02:02:09 -07:00
YHH bbdcef877e Merge pull request #12272 from esengine/feat/studio-windows-arm64-ci-build
ci(studio): build and smoke-test a native Windows ARM64 package
2026-10-07 01:16:55 -07:00
yhh 46bd67824b ci(studio): build and smoke-test a native Windows ARM64 package
Add a windows-11-arm leg to the Studio package job: the kernel and the
computer-use helper build for the runner's architecture, every PE image in the
bundle must carry the package's machine type (the installer-written
elevate.exe and uninstaller are 32-bit by construction), and the package is
started, installed and uninstalled where it is meant to run. An arm64
installer now refuses a machine that is not ARM64 Windows instead of
installing nothing, and a second job checks that refusal on an x64 runner.

The arm64 installer is built with the 7-Zip BCJ filter: 7-Zip applies an
ARM64 filter to ARM64 executables by default, the NSIS extractor cannot decode
it, and the installer then exits 0 with every executable missing.

The ARM64 leg runs on pull requests only when files it verifies change.

Build and verification only: no secrets, nothing is uploaded to a release.
2026-10-07 01:02:41 -07:00
YHH a8cf3c3f0d Merge pull request #12066 from par73e/codex/12044-running-status
fix(studio): make running status easier to notice
2026-10-07 00:46:33 -07:00
YHH 5f59e763a7 Merge pull request #12285 from esengine/fix/link-page-no-blank-race
fix(desktop): a followed link and the empty column share one tab request
2026-10-07 00:45:44 -07:00
esengine 40a54854a3 fix(desktop): a followed link and the empty column share one tab request
With no browser tab, following a link asked the kernel for the link's tab
and the column, mounting at the same moment, asked for an about:blank of its
own. Which of the two the selection rules landed on depended on reply and
poll order, so the blank page was sometimes the one shown.

openPage remembers what is being opened until the kernel answers, and the
empty column takes that page instead of requesting another.

Refs #12251
2026-10-07 00:37:17 -07:00
YHH ae57da39c1 Merge pull request #12090 from KHG420/codex/studio-theme-inventory-owner
fix(studio): scope theme inventory reads to their connection
2026-10-07 00:35:14 -07:00
YHH e8d84adf69 Merge pull request #12088 from KHG420/codex/studio-theme-import-owner
fix(studio): isolate theme import feedback by connection
2026-10-07 00:34:59 -07:00
YHH d043eb6734 Merge pull request #12282 from esengine/fix/subagent-chip-counts-running
fix(studio): count only running sub-agents on the status chip
2026-10-07 00:15:30 -07:00
YHH 4f9072fd80 Merge pull request #12281 from esengine/fix/extension-sidecar-down-notice
fix(extension): report a missing sidecar as a typed, localized notice
2026-10-07 00:13:29 -07:00
esengine 2cd9b0412d fix(studio): count only running sub-agents on the status chip
The sub-agent chip summed every delegate in the session, finished ones
included, while the background-jobs chip counted only running jobs (and
fell back to the total when none ran). Both chips now show what is
running; the sub-agent popover reports "N running / M total" and keeps
the finished ones listed.
2026-10-07 00:06:09 -07:00
YHH 670cb0d20e Merge pull request #12280 from esengine/fix/vitest-teardown-window
test(studio): an elicitation answer settles inside its test
2026-10-07 00:03:12 -07:00
YHH 8f2ec9c243 Merge pull request #12279 from esengine/feat/model-catalog-discoverability
feat(desktop): make reading the provider's model list discoverable
2026-10-07 00:02:52 -07:00
YHH 6e1956a9bb Merge pull request #12275 from esengine/fix/queue-uncertain-retry
fix(studio): offer Retry on a held uncertain queue entry
2026-10-07 00:02:33 -07:00
esengine cdc88be082 fix(extension): report a missing sidecar as a typed, localized notice
An optional extension whose sidecar is not running was reported as an
English sentence with no code, which the composer's notice bar clipped
at its edge and which named no next step.

dispatch now exposes ErrNoLiveSidecar and a SidecarDown hook; boot turns
it into a notice coded extension_skipped whose Detail is a typed payload
(extension, point, reason). The desktop words it from the payload with a
button to Tools and integrations and lets the bar wrap, the TUI words it
from the i18n catalog, and ACP chunks carry the code and payload.

Refs #12258
2026-10-06 23:53:02 -07:00
YHH f601e38b00 Merge pull request #12274 from esengine/fix/semantic-text-ink
fix(styles): semantic hues read as text through their ink token
2026-10-06 23:47:42 -07:00
esengine 317a9acbe3 test(studio): an elicitation answer settles inside its test
The form card's submit awaits onAnswer and clears its in-flight flag in a
finally block. The tests resolved that answer on a 20ms timer they never
waited for, so the setState could run after the jsdom environment was torn
down and vitest exited non-zero on 'window is not defined'. The tests now
wait for the answer to settle, and an afterEach fails any test that leaves
one in flight.
2026-10-06 23:44:07 -07:00
yhh af36e46815 fix(studio): offer Retry on a held queue entry the kernel never sent
An entry parked as uncertain with code steer_unapplied (guidance the turn
never read, e.g. after a stop) kept the queue paused, and the panel offered
Retry only for blocked entries, so Resume was a no-op and the only way out
was withdrawing and resubmitting. Other uncertain codes may already have
run and stay take-back-only.
2026-10-06 23:43:20 -07:00
yhh c6a8c45a5f feat(desktop): make reading the provider's model list discoverable
Rename the model-section action to "Read available models from provider" and
offer the same action beside the add-model field, with the failure shown there.
2026-10-06 23:39:24 -07:00
esengine 304a1e1b03 fix(styles): semantic hues read as text through their ink token
Light --warn is 4.58:1 on white and 3.86:1 on the floating layer, under the
4.5 text line. Text rules that set color to a bare --warn, --ok or --err now
use the matching ink token; borders, washes and glyph marks keep the base.
A structural test fails a new bare hue used as text.
2026-10-06 23:30:36 -07:00
YHH 2ab5f89f2c Merge pull request #12270 from esengine/feat/studio-windows-arm64-updater-contract
test(update): pin the windows-arm64 platform contract
2026-10-06 23:26:52 -07:00
YHH 118caef283 Merge pull request #12271 from esengine/fix/wallet-follows-model-switch
fix(studio): the status-bar wallet follows the pane's model source
2026-10-06 23:25:59 -07:00
YHH 6a10aebb55 Merge pull request #12273 from xxoingr/test/boot-prefix-probe-timing
test(boot): the VCS prefix test takes its own probe answer
2026-10-06 23:23:46 -07:00
yhh f50b748304 fix(studio): the status-bar wallet follows the pane's account
useWallet now takes the account (connection) the wallet belongs to: a change
of account empties the wallet at once and reads again, and only the latest
read may land, so a quick double switch cannot leave the first account's
number up. Switching models within one account keeps the wallet and does not
re-read. An account with no wallet (204) or an unreadable one shows nothing
instead of the previous figure. A reading the source reports as unavailable
is drawn in the warn-ink token and carries a text label; the figure itself is
untouched.

Refs #12266
2026-10-06 23:16:58 -07:00
yhh dde8de4ded test(update): pin the windows-arm64 platform contract before the build ships
The updater and studio-manifest already key packages by GOOS-GOARCH; nothing
pinned what happens around a second Windows architecture. Cover both
installers resolving to their own keys, a manifest without the client's key
resolving nothing, a delta for another architecture meaning no delta here,
and document the platform-key rules in the release runbook.
2026-10-06 23:16:09 -07:00
xxoingr b76a0b6522 test(boot): the VCS prefix test takes its own probe answer
TestEffectVersionControlRidesTheTurnNotThePrefix builds two controllers and
requires byte-identical system messages, but the environment section in that
prefix comes from the process-wide probe cache, keyed on PATH and shared with
every earlier test. The boot package runs longer than the cache's five-minute
TTL on Windows CI, so an answer an earlier test left can expire between the
two arms: the first arm reads it, the second re-probes, and a tool that timed
out once (a cold docker --version) appears in only one prefix.

The test now appends an empty directory no other test uses to PATH, which
keys a fresh probe answer for the first arm that the second reuses seconds
later. PATH lookup order and what each probe resolves to are unchanged.
2026-10-07 14:10:10 +08:00
YHH c379851293 Merge pull request #12260 from esengine/feat/feedback-under-review
feat(feedback): show 'under review' for received reports a maintainer is reading
2026-10-06 22:41:39 -07:00
YHH 55141b6447 Merge pull request #12264 from esengine/fix/tui-close-open-prompts-at-turn-end
fix(tui): close prompts still open when the turn ends
2026-10-06 22:32:09 -07:00
YHH 5f018ae952 Merge pull request #12262 from esengine/feat/mimo-v26-catalog
feat(providers): MiMo V2.6 catalog and V2.5 preset upgrade
2026-10-06 22:26:08 -07:00
YHH fa244e9bb8 Merge pull request #12261 from esengine/fix/hook-fail-closed
fix(hook): block gating events when a hook cannot be evaluated
2026-10-06 22:25:17 -07:00
yhh 09b0b4c1a4 fix(tui): close prompts still open when the turn ends
A second TUI attached to a session kept an ask or approval open after the
turn ended without a decision receipt (the other screen skipped the ask or
cancelled the turn), blocking its input on a prompt the kernel had already
dropped. The desktop already seals these in sealTurn; the TUI transcript now
does the same on turn_done.
2026-10-06 22:06:43 -07:00
YHH 011ab6d31d Merge pull request #12263 from esengine/docs/reasonix-md-prose-budget
docs(memory): split the saved-fact list item to restore the repolint doc-prose budget
2026-10-06 22:00:58 -07:00
yhh af6dfb36a2 docs(memory): split the saved-fact list item to bring REASONIX.md back under its prose budget 2026-10-06 21:54:43 -07:00
yhh 420d408a65 feat(providers): add MiMo V2.6 to the catalog and move shipped V2.5 presets onto it
Xiaomi lists mimo-v2.6-pro, mimo-v2.6-flash and mimo-v2.6-pro-ultraspeed, and takes mimo-v2.5-pro and mimo-v2.5 offline at 2026-10-21 10:00 Beijing time with no replacement model.
2026-10-06 21:47:27 -07:00
yhh d3c728e1bb fix(hook): block gating events when a hook cannot be evaluated
A PreToolUse/UserPromptSubmit hook that could not be evaluated used to be
treated as absent: a malformed matcher never fired, a spawn error was
DecisionError (warn-level), and a payload json.Marshal error was dropped so
the hook ran against empty stdin and passed.

On gating events (and Claude-imported PermissionRequest) each of the three
now blocks, with a typed cause (ErrInvalidMatcher, ErrSpawnFailed,
ErrPayloadUnserializable) carried on Outcome and Notice. The model gets only
structured facts (code, step, event, scope, source, label), never the
script's output or the underlying error prose. Observing events still do not
block; they report the outcome to the user. Save rejects an invalid matcher
on tool-matcher events.

The tests that pinned the old behaviour (malformed regex "should not fire",
spawn-error => DecisionError on PreToolUse) are rewritten to the new contract.
2026-10-06 21:46:11 -07:00
yhh 31c29d9bf0 feat(feedback): show 'under review' for received reports a maintainer is reading 2026-10-06 21:37:15 -07:00
YHH 12c6f48b04 Merge pull request #12259 from esengine/docs/memory-index-not-in-prefix
docs(memory): the saved-fact index is not in the stable prefix
2026-10-06 21:37:12 -07:00
esengine 9c3ab6ffda docs(memory): the saved-fact index is not in the stable prefix
101f2ca12 stopped composing the fact index into the cached prefix, but
SESSION_MEMORY_RETRIEVAL.md and REASONIX.md still said it joins the prefix
at session start. Only pinned bodies and standing instructions do; the
index is reached through the memory tool.
2026-10-06 21:29:55 -07:00
YHH 28fdf58cbd Merge pull request #12256 from esengine/fix/scratch-holder-lock-gc
test(scratch): keep the holder's directory reachable so its owner lock survives GC
2026-10-06 20:56:25 -07:00
YHH 5e58a9ff20 Merge pull request #12252 from esengine/fix/probe-cache-expiry-keeps-prefix
fix(environment): keep the environment section stable when the in-memory probe cache expires
2026-10-06 20:47:36 -07:00
yhh 9d46b22774 test(scratch): keep the holder's directory reachable so its owner lock survives GC 2026-10-06 20:44:04 -07:00
yhh f5b5553295 fix(environment): keep the environment section stable when the in-memory probe cache expires
A cache hit never wrote the snapshot of the directory it was served to, so
when the 5 minute in-memory entry expired the next build probed live and
could render a different section, moving the cached prompt prefix. Adopt the
served answer into that directory's snapshot.

Also print the first differing bytes when the scheduled-run prefix test
fails, so a future flake is diagnosable.
2026-10-06 20:27:29 -07:00
YHH aa47d1afa4 Merge pull request #12248 from esengine/fix/remove-context-budget-push
refactor(agent): stop pushing context pressure; keep the context_budget query tool
2026-10-06 20:21:23 -07:00
YHH 47dec71c78 Merge pull request #12249 from esengine/fix/link-tab-stays-selected
fix(browser): a clicked link's tab stays selected when the agent already has a page
2026-10-06 20:20:10 -07:00
YHH 696b791be6 Merge pull request #12247 from esengine/fix/delta-size-guard
fix(update): skip the chunked update when it would download more than the full package
2026-10-06 19:58:03 -07:00
yhh 362c8381b5 refactor(agent): stop pushing context pressure to the model and the user
The host appended a <context-budget> block to the turn tail at 75% and 92% of
the compaction trigger and raised a matching warning card. The model can still
ask: the read-only context_budget tool and its figures are unchanged, and its
description now says when to check, since nothing reports it unprompted.
2026-10-06 19:56:33 -07:00
esengine e5c74a52c9 fix(browser): open a followed link's tab without ever making it the active one
The window opened a link in a new tab and then switched the active tab back
to the agent's. The page list was re-read between the two, so the panel saw
the agent's active tab move to the new tab and back, and its follow-the-agent
rule selected the agent's tab again: the link's page appeared but was not the
one shown. Session.VisitBeside opens the tab without activating it unless the
session has no active tab, so there is no transient to misread.
2026-10-06 19:56:24 -07:00
YHH 3b27b5c419 Merge pull request #12246 from esengine/fix/feedback-rig-test-race
test(control): guard the feedback rig's stub-server fields with its mutex
2026-10-06 19:40:12 -07:00
yhh 909ecd8544 fix(update): skip the chunked update when it would download more than the full package 2026-10-06 19:39:52 -07:00
yhh d784c31d2b test(control): guard the feedback rig's stub-server fields with its mutex 2026-10-06 19:30:51 -07:00
YHH 2ed678a37c Merge pull request #12242 from esengine/docs/studio-2.30.0-notes
docs(release): Studio 2.30.0 notes
2026-10-06 17:58:50 -07:00
yhh 0107a78275 docs(release): Studio 2.30.0 notes 2026-10-06 17:51:00 -07:00
YHH cc0206dd25 Merge pull request #12241 from esengine/fix/mcp-prompt-failure-attribution
fix(control): attribute a failed MCP prompt fetch and tell the model about it
2026-10-06 15:40:03 -07:00
yhh 47aff7d34f fix(control): attribute a failed MCP prompt fetch and tell the model about it
A prompts/get failure surfaced as the bare transport error, and the model
never learned the user had invoked a prompt. MCPPrompt now returns a typed
MCPPromptError (prompt, server, stage; matches ErrMCPPromptFetch) and owes
the model a turn-tail block describing the failed invocation.
2026-10-06 15:26:39 -07:00
YHH 43a9069b46 Merge pull request #12233 from esengine/feat/session-unread-ui
feat(studio): mark sessions that finished unseen in the sidebar and tabs
2026-10-06 10:19:04 -07:00
esengine 18465053 72d8d622dd feat(studio): mark sessions that finished unseen in the sidebar and tabs
Draw the kernel's typed `unread` on session rows (dot, bold title, spoken
name), as a count pill on folded project rows and as a dot on pane tabs.
A session is marked viewed through the pane's own endpoint when it
becomes the pane in front, when an unread row or pane is clicked, and
when a turn ends in the pane in front while the window has focus. The
mark is cleared optimistically and restored if the call is refused.

Refs #12230
2026-10-06 10:10:12 -07:00
YHH dd92486b5e Merge pull request #12239 from esengine/fix/attachment-refusal-codes
fix(attachments): say why an attachment was refused, with a code per class
2026-10-06 10:06:31 -07:00
yhh 431daf03c4 fix(attachments): say why an attachment was refused, with a code per class
/attachments refused with plain text, so the composer could only show
"Could not add" and left the reason in a tooltip. Refusals now carry
attachment.unsupported_image, too_large, empty, unreadable and write_failed
with params, the port keeps the code, the catalogues say each in both
languages, and the failed chip shows the reason with Retry.
2026-10-06 09:55:16 -07:00
YHH 7b653ddd84 Merge pull request #12237 from esengine/perf/background-panes-stream-rate
perf(studio): stop panes behind another from redrawing at stream rate
2026-10-06 09:52:31 -07:00
esengine 3f2a2760f4 perf(studio): stop panes behind another from redrawing at stream rate
Panes that are not on screen kept re-rendering on every streamed delta, and
each of those renders also rebuilt the reply actions handed to every settled
row. With nine sessions streaming the renderer's main thread saturated and
queued work (status reads, stop, settings) starved.

A hidden pane now takes the first token of each stream at once and coalesces
the rest into one batch per second; any other event flushes first and bringing
the pane forward flushes immediately. Reveal pacing, the thinking clock and the
wait clock only run for a pane that is shown. The reply actions keep their
identity while a streamed answer grows, and the folded thought's length is no
longer recounted per delta.

Refs #12232
2026-10-06 09:43:15 -07:00
YHH 42b1d2742a Merge pull request #12234 from esengine/fix/session-viewed-torn-sidecar
fix(session): let a view wait out a sidecar caught mid-replace
2026-10-06 09:32:41 -07:00
esengine 89a00b8310 fix(session): let a view wait out a sidecar caught mid-replace
MarkSessionViewed pre-checked the session sidecar with a plain unlocked
read. Concurrent views replace the sidecar, so on Windows that read can
meet it mid-replace (access denied, or torn JSON after the replace
fallback) and the view failed with a 500 instead of waiting it out. The
pre-check now uses the tear-tolerant read the other sidecar readers use.

Refs #12230
2026-10-06 09:21:16 -07:00
YHH 7f30fcbdb3 Merge pull request #12231 from esengine/feat/session-unread-kernel
feat(session): persist a finished-but-unviewed mark per session
2026-10-06 08:53:07 -07:00
YHH 7e6a90e954 Merge pull request #12216 from esengine/feat/compaction-progress-record
feat(agent): re-project finished work at the request tail once a fold is installed
2026-10-06 08:51:10 -07:00
YHH 95d0f747a8 Merge pull request #12229 from esengine/fix/remote-serve-attach-12228
fix(remote): attach to a running serve instead of replacing it
2026-10-06 08:41:19 -07:00
esengine 39fbadf232 feat(session): persist a finished-but-unviewed mark per session
The kernel now records two optional facts in the session sidecar,
finished_at (stamped when a turn ends) and viewed_at (stamped when the
person opens the session). A session is unread iff finished_at is after
viewed_at; the sidebar tree and GET /sessions carry it as a typed
`unread` boolean. A turn the person cancelled is stamped as viewed.

Controller.MarkSessionViewed (POST /sessions/viewed) writes only the
session sidecar and takes no workspace or transcript lease. Sidecars
without the fields read as seen.

Refs #12230
2026-10-06 08:40:09 -07:00
esengine 444a38cdd9 feat(agent): re-project finished work at the request tail once a fold is installed
A fold leaves the model with a digest, and the host re-projected only the
step ids of plan-seeded unfinished lists. A task list the model wrote itself
(no ids), a list with every item complete, the files changed and the checks
that passed all survived only as digest prose, which a weaker model reads as
work not yet done and repeats.

While a fold is installed the request now ends with one derived "Host
progress record": the canonical task list with statuses when no todo_write is
left in view, the files changed in the folded prefix, and the verification
commands whose latest outcome was a pass (marked when a file changed after
them). It is derived from tool receipts, bounded (30 files, 15 checks, with
the omitted count), memoised on the covered-prefix fingerprint, and ends with
the fold. Sessions that never folded carry nothing extra.
2026-10-06 08:37:36 -07:00
YHH 021685bc90 Merge pull request #12224 from xxoingr/fix/perf-tooltip-attach-lookup
test(studio): the tooltip guard measures the attach button in every language
2026-10-06 08:26:30 -07:00
YHH 2e0354050c fix(remote): attach to a running serve instead of replacing it
A live serve with no record is attached and recorded, launch no longer
deletes the port/pid files another serve published, and a live serve
whose provider source differs fails the connect with a coded error
instead of being terminated. Document what desktop exit does to the
remote runtime.
2026-10-06 08:25:55 -07:00
drakeo338 d651569af7 fix(config): keep the config edit lock directory per user and refuse a foreign owner
The lock directory sat at a shared, predictable /tmp path, so another
identity could create it first and every later run by this user failed
on os.Chmod with a bare EPERM.

Put it under the OS user's home (from the account database, so HOME and
TMPDIR overrides do not move it), as Windows already did. Keep the owner
uid check before the chmod as a backstop: it returns ErrLockDirWrongOwner,
naming the path and uid, without touching the directory. The /tmp name
is only a fallback when no home is recorded. The check is a no-op on
Windows; lockdir_owner_other.go is renamed lockdir_owner_windows.go.

Fixes #11372
2026-10-06 15:08:47 +00:00
drakeo338andClaude Sonnet 5.5 79cf8d34a1 fix(installsource): reject npm and GitHub source names that resolve as paths, tags or tarballs
Package segments made only of dots, leading dots, a leading underscore on
unscoped names and .tgz/.tar/.tar.gz names are no longer accepted by the
installsource and registry checks. parseGitHubRepoSource refuses dot-leading
owners and dot-only repos such as the one TrimSuffix(".git") leaves from
"...git".

Refs #11353

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-06 15:07:01 +00:00
YHH e7d8dea94f Merge pull request #12217 from xxoingr/fix/tui-menu-ctrl-pn
fix(tui): Ctrl+N and Ctrl+P move through the completion menu and resume picker
2026-10-06 08:05:18 -07:00
YHH cb934974a6 Merge pull request #12220 from esengine/fix/feedback-veil-no-backdrop-blur
fix(desktop): drop the feedback veil's backdrop blur so an open dialog does not pin the GPU
2026-10-06 07:51:18 -07:00
YHH e41e54e173 Merge pull request #12223 from xxoingr/fix/tui-ctrl-l-cls
fix(tui): Ctrl+L clears the screen like /cls
2026-10-06 07:46:20 -07:00
YHH e85f182fc8 Merge pull request #12222 from esengine/fix/compaction-trigger-reads-provider-usage
fix(compaction): let the provider's last reported prompt size trigger a fold when the local estimate runs low
2026-10-06 07:43:10 -07:00
par73e fe6e76d41b fix(studio): make running status easier to notice 2026-10-06 22:43:09 +08:00
YHH 4c2ac96604 Merge pull request #12213 from xxoingr/fix/bgjob-failure-tool-error
fix(tools): reading a background job that failed is a failed call
2026-10-06 07:39:33 -07:00
xxoingr 8c48421711 test(studio): the tooltip guard measures the attach button in every language
perf/tooltip.mjs looked the attach button up by its aria-label in each
language, and the English label it searched for, "Add attachment", is not
the shipped one ("Add an attachment"). The lookup found nothing and the loop
skipped on, so the four English attach combinations were never measured and
the guard stayed green.

Controls are now found by structure, data-action for refine and the attach
class for attach, and a control that cannot be found fails the guard rather
than being skipped. All 24 combinations (light/dark x zh/en x two widths x
three cases) are measured: 96 checks, where 80 ran before.

Refs #12156
2026-10-06 22:31:57 +08:00
yhh 04420cca07 fix(desktop): drop the feedback veil's backdrop blur so an open dialog does not pin the GPU
The veil blurred the whole window behind it, which the compositor redoes every frame while anything beneath animates. A flat, more opaque scrim dims the same way for free.
2026-10-06 07:31:38 -07:00
xxoingr de2b1d6240 fix(tui): Ctrl+L clears the screen like /cls
GUIDE.md lists "Ctrl+L or /cls" as clearing only the visible transcript, and
1.x bound the key, but the 2.x terminal UI left Ctrl+L unbound. It now runs
the same clearDisplay as /cls: the conversation, session and the kernel's
task list stay, and a running turn is refused the same way. Being a key
rather than a typed command, it leaves a half-typed draft in the composer.

Refs #12144
2026-10-06 22:28:01 +08:00
esengine d62e4bc71b fix(compaction): let the provider's last reported prompt size trigger a fold when the local estimate runs low
The fold trigger compared only a local estimate against the threshold. That
estimate is characters times one tokens-per-character ratio, and the ratio is
not learned from requests that carry images or provider-side tool results, so a
session with screenshots can sit well under the real size and defer the fold.

The trigger now takes the larger of the local estimate and the provider's
reported prompt_tokens (cached tokens included) for the last request plus the
local estimate of what the visible view gained since. The anchor is dropped when
a fold changes the projection, when the transcript is swapped, and when the
usage is estimated, aggregated over retries, or padded by provider-run tools.
The hard ceiling and the overflow paths still read the local estimate alone, so
a provider count that no fold can lower never ends a turn.
2026-10-06 07:26:33 -07:00
xxoingr b4564e5f8d fix(tui): Ctrl+N and Ctrl+P move through the completion menu and resume picker
The slash and @ completion menu and the /resume picker moved only with the
arrow keys, while 1.x also took Ctrl+N / Ctrl+P there and GUIDE.md and
CLI.md list both for these lists. The provider and quick pickers already
bind them. Both lists now take Ctrl+N as Down and Ctrl+P as Up; neither is
text, so a picker's filter is untouched.

Refs #12144
2026-10-06 22:20:27 +08:00
xiexian c8abba049a fix(studio): keep failed extension forms editable 2026-10-06 22:18:13 +08:00
YHH aa0d80f798 Merge pull request #12212 from xxoingr/fix/probe-refusal-redacted
fix(serve): probe refusals keep the code and leave the endpoint's echo behind
2026-10-06 07:03:50 -07:00
YHH 89df335f78 Merge pull request #12210 from esengine/feat/market-browse-cache
feat(market): serve the last good browse answer, marked, when the registry is unreachable
2026-10-06 06:58:13 -07:00
xxoingr da7ec275f6 fix(tools): reading a background job that failed is a failed call
bash_output and wait reported a background job that later failed or timed
out only in their text, with no tool error. The activity panel and the
failure count read the host-set error, so a job that failed after it started
never turned red and was not counted, while the model was told it failed.

Both tools now return errJobFailed, naming the failed jobs, whenever a job
they report ended as failed (a background command's non-zero exit and its
timeout both end that way). The output is returned alongside, so the model
still reads it under the error line. A job that finished, was killed, was
interrupted or is still running is not a failure and the call stays clean.

Fixes #12201
2026-10-06 21:57:09 +08:00
xiexian 645abda947 fix(studio): submit only current extension form fields 2026-10-06 21:51:43 +08:00
esengine 98ba2b7e4e feat(market): serve the last good browse answer, marked, when the registry is unreachable 2026-10-06 06:39:03 -07:00
YHH 9d4a2bd6d3 Merge pull request #12209 from esengine/feat/phone-browser-tabs
feat(studio): show the agent's browser tabs read-only on the phone web client
2026-10-06 06:36:24 -07:00
xxoingr a83f6c0229 fix(serve): probe refusals keep the code and leave the endpoint's echo behind
POST /providers/probe built its refusal message from probe.Error(), which
carries the model-list response body verbatim. That body can echo the
request, and this route receives the key typed into the add-provider draft,
so a 401 that quoted the key sent it back in the refusal. A failure that was
not a ProbeError came back as an untyped 502 with the error's text.

The message now carries the probe reason and only what endpointDetail lets
through (one bounded printable line, the draft key and credential-shaped
text redacted), the same treatment /providers/check gives its detail. A
failure without a probe identity is a kernel fault and refuses as 500
provider.probe.failed without its text.

Fixes #12188
2026-10-06 21:34:30 +08:00
esengine 8decb83466 feat(studio): show the agent's browser tabs read-only on shells that cannot draw them
A phone using the web client got an empty Workbench when the agent opened a
page: useBrowserTabs returned early because the web host draws no native
views, so the tab list was never read. Read it on every host and, where the
page cannot be drawn, show its title and address as plain text with no
controls.
2026-10-06 06:27:22 -07:00
xxoingr 8bb3f9d58c perf(studio): read a pane's checkpoints once on mount
Opening a session pane read GET /checkpoints three times against a real
kernel: the mount effect read it, the turn-boundary effect read it again on
its own first run, and a third time when the first status named the session
path.

useCheckpointRefresh now owns every read outside a session reload: the mount
read, a move from one known session to another, and both turn edges. The
kernel opens a turn's checkpoint when the turn starts and fills it while the
turn runs, so the read at turn start stays and the rewind entry still appears
on a running turn's message. The first status naming the session the mount
read covered no longer reads again, so opening a pane costs one request.

A failed read is tried once more after a second, since no later edge is owed
to it, and a read that a newer one superseded is dropped instead of
overwriting the newer list.

Refs #12080
2026-10-06 21:25:50 +08:00
YHH e13eb30dbc Merge pull request #12206 from KHG420/codex/studio-extension-transcript-restore
fix(studio): retain extension cards during history recovery
2026-10-06 06:09:18 -07:00
xiexian ae7fb0c1f5 fix(studio): retain extension cards during history recovery 2026-10-06 20:50:34 +08:00
YHH 7ac14f94af Merge pull request #12194 from KHG420/codex/studio-idle-extension-prompt
fix(studio): restore idle status after questions
2026-10-06 05:35:29 -07:00
YHH ed7dbd54e4 Merge pull request #12205 from KHG420/codex/studio-failed-switch-sidecar-rollback
fix(studio): restore sidecars after model selection fails
2026-10-06 05:33:05 -07:00
YHH 7f2365b4a0 Merge pull request #12202 from esengine/feat/provider-sticky-footer
feat(desktop): sticky Save bar with a phase dot on the provider form (RFC #12040 S1, 2b-2)
2026-10-06 05:22:28 -07:00
yhh 7fa84ab3cc feat(desktop): keep the provider form's Save bar and state at the bottom of the panel
The save area sat at the end of a long form, so its state was out of sight
exactly when it mattered. The bar is now sticky, shared by the edit and add
forms, and holds the failure or not-applied reason beside a single persistent
status line with a dot for each phase (clean, unsaved, saving, saved, saved but
not applied, save refused). Ctrl/Cmd+S saves a pending draft.
2026-10-06 05:13:07 -07:00
YHH c434fc423f Merge pull request #12199 from esengine/feat/skill-paths-listing-3
feat(skill): show a paths-gated skill only after a matching file is touched (3/4 for #11352)
2026-10-06 05:08:50 -07:00
xiexian 285b537f96 fix(studio): restore sidecars after model selection fails 2026-10-06 19:55:52 +08:00
esengine 69cfaefdcc feat(skill): show a paths-gated skill to the model only after a matching file is touched
A skill that declares paths: is held out of the per-turn listing, the
capability catalog, the slash_command listing and the unknown-name hint until
the session has touched a file its globs match. The set of touched files is
owned by the skill store; eligibility is memoised against it and dropped when
it changes, and the next turn after a match owes the listing again. The user's
/<name> and run_skill by exact name are not gated.
2026-10-06 04:43:43 -07:00
YHH 38c1519373 Merge pull request #12198 from esengine/fix/background-start-not-a-failure
fix(desktop): stop drawing a background start as a failed shell call
2026-10-06 04:37:00 -07:00
esengine 66723cc065 fix(desktop): stop drawing a background start as a failed shell call
The shell card and the activity group decided failure a second time from
execution.state and exitCode, so state background_started (no error, no exit
code) rendered a red BACKGROUND_STARTED badge and counted as one failure.
The host already classifies every call once: err is set for failed,
timed-out, cancelled and refused calls. Read only that.
2026-10-06 04:24:06 -07:00
YHH 36e059b7f2 Merge pull request #12196 from esengine/feat/provider-test-enabled-models
feat(desktop): Test enabled models button with a running summary (RFC #12040 S1, 2b-1)
2026-10-06 04:15:49 -07:00
YHH 5d4f4f0259 Merge pull request #12195 from esengine/feat/skill-paths-feed-2
feat(skill): record the files completed tool calls touched (2/4 for #11352)
2026-10-06 04:09:57 -07:00
yhh 34ea5a5d78 feat(desktop): test every enabled model at once from the provider edit form
Verifying a model was one click per row, and the form blocked while any one
ran. The model header gets a Test enabled models button that runs the existing
per-model check over the ticked rows, two at a time, and stops as soon as the
draft changes or the form is left. The summary line counts rows still being
verified, and the per-row live regions are dropped so a run is announced once
through the summary.
2026-10-06 04:08:34 -07:00
esengine 23eec9f92e feat(skill): record the files completed tool calls touched for paths eligibility
The agent tells a PathObserver which workspace files a completed call named,
through the ReadTargeter and WritePathResolver capabilities its tool already
declares; failed, refused and hook-blocked calls feed nothing and sub-agents
are not observed. Swapping the conversation rebuilds the set from the
transcript. skillSet wires its PathHits in; the catalog does not read it yet.
2026-10-06 04:00:42 -07:00
YHH 2a412969b0 Merge pull request #12158 from KHG420/codex/studio-offline-sdk-handoff
docs(sdk): verify starter extension with the offline provider
2026-10-06 03:50:10 -07:00
YHH 4669be38c6 Merge pull request #12105 from chuiwenwei/test/studio-corpus-guard-three
test(e2ebench): hold fanout-width, upstream-edge and project-check to the authoring guard
2026-10-06 03:47:53 -07:00
YHH 9a6936ceb7 Merge pull request #12193 from esengine/feat/provider-results-near-buttons
feat(desktop): provider test results next to their buttons, with a summary line (RFC #12040 S1, 2a)
2026-10-06 03:46:03 -07:00
yhh 26e2eba5ea feat(desktop): show provider test results next to their buttons with a one-line summary
A failed catalogue refresh surfaced under the form's footer and a silent
refresh that found nothing looked like a click that missed. The add form's
connection failure also rendered below its save button.

Refresh failures and outcomes (including 'no new models') now render under the
model header, verified rows are summed up in one status line over the ticked
models, and the add form's connection failure sits under the button that ran
it. Results clear when the address or key they depend on changes.
2026-10-06 03:34:05 -07:00
xiexian 4024c63a43 fix(studio): restore idle status after questions 2026-10-06 18:25:33 +08:00
xiexian 6386cd91c9 fix(studio): preserve memory drafts and pending writes 2026-10-06 18:18:05 +08:00
YHH 722ceb4017 Merge pull request #12162 from KHG420/codex/studio-publication-version-guidance
fix(studio): describe publication version auto-increment limits
2026-10-06 03:09:15 -07:00
YHH fa194e4f91 Merge pull request #12133 from KHG420/codex/studio-memory-history
fix(studio): refresh memory history after writes
2026-10-06 03:06:52 -07:00
YHH 647d7670ba Merge pull request #12192 from esengine/feat/skill-paths-frontmatter-1
feat(skill): parse paths frontmatter and track the files a session touched (1/4 for #11352)
2026-10-06 03:05:45 -07:00
YHH bd12c75dfa Merge pull request #12191 from esengine/feat/provider-leave-guard
feat(desktop): ask before switching service or adding discards unsaved provider edits (RFC #12040 S1, 1.5/3)
2026-10-06 03:03:50 -07:00
esengine 40e8ee8050 feat(skill): parse paths frontmatter and track the files a session touched
Skills may declare file globs in a paths: key. The registry now parses them
(rejecting negation, parent traversal and malformed globs into InvalidPaths,
which doctor reports) and a PathHits set records workspace files the host saw
touched, resolving symlinks so nothing outside the workspace counts. The set
is owned by skillSet; the catalog does not read it yet.
2026-10-06 02:54:26 -07:00
YHH f80741bbd5 Merge pull request #12190 from KHG420/codex/studio-auth-bootstrap-asset-reload
fix(studio): reload protected assets after fragment authentication
2026-10-06 02:53:04 -07:00
yhh 7f43425005 feat(desktop): ask before a provider switch or Add discards unsaved edits
Leaving the edit form for another service, the Add form, another service's
rename, or a protocol switch dropped pending edits without a word.

EditConn reports the pending state it already computes; Providers holds it
and routes every way out of the form through one guard that asks to keep
editing or discard. A save in flight is not interrupted and not asked about,
and the question closes by itself once nothing is pending.
2026-10-06 02:46:35 -07:00
YHH c050ffc1d4 Merge pull request #12155 from KHG420/codex/studio-package-remove-reload-result
fix(studio): settle runtime feedback after package removal
2026-10-06 02:44:56 -07:00
YHH 5fcdd4f970 Merge pull request #12097 from KHG420/codex/studio-skill-namespace-guide
docs(studio): clarify skill frontmatter namespaces
2026-10-06 02:40:11 -07:00
YHH cc24f23cda Merge pull request #12071 from KHG420/codex/studio-copy-fallback-focus
fix(studio): preserve focus after legacy clipboard copy
2026-10-06 02:37:25 -07:00
YHH 97e6bb3dc1 Merge pull request #12121 from KHG420/codex/studio-mcp-example-argument-schema
fix(studio): enforce the MCP example argument schema
2026-10-06 02:35:15 -07:00
xiexian c81f424100 fix(studio): reload protected assets after fragment authentication 2026-10-06 17:32:07 +08:00
YHH 9106bbe805 Merge pull request #12189 from esengine/feat/provider-form-dirty-revert
feat(desktop): unsaved-changes indicator, Revert and a Saved receipt on the provider edit form (RFC #12040 S1, 1/3)
2026-10-06 02:11:55 -07:00
yhh a9a726b921 feat(desktop): show unsaved changes, Revert and a Saved receipt on the provider edit form
The edit form had no way to tell pending edits from stored ones: Save was
always enabled, Cancel was a remount in disguise, and a successful save
silently turned into the same form with nothing said.

Pending state is the fingerprint of what a save would send, compared with the
one taken at mount, so typing a value back or toggling a row off and on is
clean again. Save is disabled when clean, Cancel becomes Revert, and the footer
says whether there are unsaved changes, no changes, or that the save landed
once the list has been re-read.
2026-10-06 02:01:50 -07:00
YHH 15cefe039e Merge pull request #12187 from KHG420/codex/studio-extension-panel-progress-name
fix(studio): name standing extension panel progress
2026-10-06 01:26:06 -07:00
YHH a6eb4e1742 Merge pull request #12185 from esengine/feat/provider-check-typed-failure
feat(providers): report why a whole-connection check failed as a typed finding
2026-10-06 01:25:14 -07:00
YHH de66ad3240 Merge pull request #12186 from xxoingr/fix/refine-tip-described
fix(studio): the prompt-refine button is described by its tooltip
2026-10-06 01:16:37 -07:00
xiexian ba338a2d6b fix(studio): name standing extension panel progress 2026-10-06 16:16:34 +08:00
esengine bfcbf2a8ff feat(providers): report why a whole-connection check failed as a typed finding
Cause: POST /providers/check returned a free-text `error` assembled from the
probe error's message. The probe already knows whether a key was refused, a
path is missing, nothing listed chat models or the deadline expired, but that
identity was flattened into prose on the way out, so the settings page could
only print the sentence (which also echoed the endpoint body unredacted), and
the refresh button invented its own literal error when a check came back empty.

Blast radius: /providers/check on failure now answers 200 with
`{ok:false, code, params?, httpStatus?, detail?}` and no `error`. `code` is the
dotted identity the add flow's refusals already use (provider.probe.*), plus a
new provider.probe.timeout split out of unreachable; `params` carries only the
numeric facts the sentence needs; `detail` is the endpoint's own text through
the same redaction the model check uses. Success responses are unchanged.
catalog.ProbeError gains Status and Body, read from the typed fetch error, and
the status/code table is shared by the refusal and the finding.

Neighbouring behaviours tested: refused, forbidden, payment, rate limit, missing
path, upstream error, no chat models, unreachable, timeout, success, key
redaction in the endpoint text, bounded one-line detail, missing grant,
unknown name, and the add flow's probe codes all translated.

Why this layer: the probe owns the class; serve only projects it; the page
maps the code to words.
2026-10-06 01:14:03 -07:00
xxoingr 80e8a1a82f fix(studio): the prompt-refine button is described by its tooltip
The refine button's tooltip carried the Ctrl+Shift+E shortcut, but nothing
pointed the button at it: it had no aria-describedby and the tooltip had no
id, so a screen reader announced only "Optimize prompt" and the keyboard
route stayed undiscoverable. The attach button beside it already describes
itself this way.

The tooltip now takes an id from useId and the button names it in
aria-describedby, matching the attach button.

Refs #12156
2026-10-06 16:04:44 +08:00
YHH 0afafb7a46 Merge pull request #12184 from KHG420/codex/studio-backup-delete-lifetime
fix(studio): hold backup deletions until receipts settle
2026-10-06 01:02:39 -07:00
xiexian e3e2ea39dd fix(studio): hold backup deletions until receipts settle 2026-10-06 15:50:22 +08:00
YHH 444ca4fa45 Merge pull request #12179 from esengine/feat/chart-desktop-renderer
feat(desktop): draw render_chart calls as inline SVG charts in the transcript
2026-10-06 00:41:01 -07:00
YHH 5fbb16f2d6 Merge pull request #12183 from KHG420/codex/studio-backup-restore-draft
fix(studio): hold restore choices while awaiting receipts
2026-10-06 00:36:59 -07:00
YHH 6ee31dedd0 Merge pull request #12180 from KHG420/codex/studio-backup-create-draft
fix(studio): hold backup drafts while creating
2026-10-06 00:36:28 -07:00
YHH defebb51b2 Merge pull request #12175 from KHG420/codex/studio-add-provider-save-draft
fix(studio): hold provider additions while saving
2026-10-06 00:29:00 -07:00
YHH 0427ff16f8 Merge pull request #12174 from KHG420/codex/studio-provider-save-draft
fix(studio): hold provider drafts while saving
2026-10-06 00:27:56 -07:00
yhh 8f5701324e feat(desktop): draw render_chart calls as inline SVG charts in the transcript
A settled render_chart call (direct or through use_capability) is re-validated
on the page, lifted out of the folded execution group, and drawn as a bar,
line or pie chart from the validated spec by our own React SVG components in
a lazy chunk. A running, refused or invalid call keeps the ordinary tool card.
The same card is rebuilt on reload from the stored call. Each chart offers an
accessible data table, keyboard-reachable points and no entrance motion.
2026-10-06 00:25:02 -07:00
YHH 0333c008a9 Merge pull request #12181 from esengine/feat/chart-tui-text
feat(tui): show a render_chart call as an aligned data table
2026-10-06 00:19:30 -07:00
yhh f2631f18fc feat(tui): show a render_chart call as an aligned data table
A settled chart call (direct or through use_capability) now draws its title,
marks and rows as a capped, width-fitted table in the terminal, with numbers
right-aligned and the fold key opening the rest, instead of a line count.
chartspec.FromCall and Spec.Table/Describe are the single projection; the TUI
history record and frame merge now keep the resolved tool name, without which
neither a reopened session nor a live call could be told from any other
use_capability call.
2026-10-06 00:08:07 -07:00
xiexian b924372004 fix(studio): hold restore choices while awaiting receipts 2026-10-06 15:01:06 +08:00
xiexian e2b02ed555 fix(studio): hold backup drafts while creating 2026-10-06 14:47:46 +08:00
YHH 65aef1e228 Merge pull request #12177 from esengine/fix/provider-save-remount-from-fresh-list
fix(providers): remount the editor only after the list has been re-read
2026-10-05 23:36:17 -07:00
YHH 03ac4a5197 Merge pull request #12178 from KHG420/codex/studio-backup-empty-selection
fix(studio): preserve empty backup selections on reload
2026-10-05 23:32:50 -07:00
xiexian d4af1d6527 fix(studio): preserve empty backup selections on reload 2026-10-06 14:23:34 +08:00
YHH 8156f643fc fix(providers): remount the editor only after the list has been re-read
A successful save remounted the editor synchronously while the list reload
was still in flight, so the new editor was seeded from the stale entry and
showed the pre-save values. onEdited now returns the reload, the editor
waits for it before remounting, and save keeps the form busy until then.
2026-10-05 23:19:30 -07:00
xiexian d194492f30 fix(studio): hold provider drafts while saving 2026-10-06 14:15:14 +08:00
YHH fd93411f81 Merge pull request #12169 from KHG420/codex/studio-market-vote-account-state
fix(studio): refresh market votes when account changes
2026-10-05 23:12:10 -07:00
YHH 49affe9889 Merge pull request #12163 from KHG420/codex/studio-package-hook-row-identity
fix(studio): replace colliding package hook rows
2026-10-05 23:10:08 -07:00
YHH 9aa9756b46 Merge pull request #12170 from KHG420/codex/studio-provider-probe-endpoint-state
fix(studio): discard probe findings after endpoint edits
2026-10-05 23:07:55 -07:00
YHH e17c91e44f Merge pull request #12167 from KHG420/codex/studio-provider-check-manual-proxy
fix(studio): honor draft proxy bypass during model checks
2026-10-05 23:07:20 -07:00
xiexian 93f408bfd9 fix(studio): hold provider additions while saving 2026-10-06 13:47:42 +08:00
xiexian f01e71ccfc fix(installsource): verify registered skills by source identity 2026-10-06 13:33:30 +08:00
xiexian 6166ba143f fix(installsource): attribute skill discovery to its installed path 2026-10-06 13:24:35 +08:00
xiexian a08a5b60a7 fix(studio): discard probe findings after endpoint edits 2026-10-06 12:31:59 +08:00
xiexian 099c1faa52 fix(studio): refresh market votes when account changes 2026-10-06 12:12:19 +08:00
xiexian a9e7e22636 fix(studio): honor draft proxy bypass during model checks 2026-10-06 11:52:39 +08:00
YHH 9ce1b8ba5e Merge pull request #12160 from esengine/docs/studio-2.29.0-notes
docs(release): Studio 2.29.0 notes
2026-10-05 20:49:58 -07:00
yhh 4c296c9320 docs(release): Studio 2.29.0 notes 2026-10-05 20:41:26 -07:00
YHH a45ff5a771 Merge pull request #12157 from esengine/fix/steer-into-paused-queue
fix(control): hold a mid-turn message as a follow-up when the queue is paused
2026-10-05 20:34:33 -07:00
xiexian 74b5580b83 fix(studio): replace colliding package hook rows 2026-10-06 11:33:20 +08:00
xiexian a4c97196ff fix(studio): describe publication version auto-increment limits 2026-10-06 11:15:05 +08:00
esengine 0e026c95f0 fix(control): hold a mid-turn message as a follow-up when the queue is paused
A steer sent while a stop had paused the queue was refused with inbox.paused
but its queued copy stayed behind, so retries piled up and all ran on resume.
Hold the item as a follow-up under the admission lock and answer with the
queued receipt that already carries paused=true. The pause itself is kept,
and send-now from the queue panel still refuses. The composer reads the
receipt, from the steer path and from an idle skill send, and says the queue
is paused, once, in the notice strip.

Part of #12153
2026-10-05 20:14:07 -07:00
YHH 1c85e79cc6 Merge pull request #12154 from esengine/fix/plan-gate-cancel
fix(control): return the plan to planning when a stop cancels the approval card
2026-10-05 20:03:47 -07:00
xiexian 33a56d2f8a fix(studio): refresh provider models after extension changes 2026-10-06 10:53:29 +08:00
xiexian 3a47847772 docs(sdk): verify starter extension with the offline provider 2026-10-06 10:39:44 +08:00
YHH 6b1e18db89 Merge pull request #12152 from esengine/fix/control-tooltip-wrap-and-fade
fix(studio): composer control tooltips wrap inside their box and no longer fade when the host is disabled
2026-10-05 19:34:41 -07:00
xiexian d2a7e55585 fix(studio): settle runtime feedback after package removal 2026-10-06 10:25:50 +08:00
esengine 972f0212fa fix(control): return the plan to planning when a stop cancels the approval card
Stopping a turn while the plan approval card is open left the plan in
AwaitingApproval with no card to answer it. Later messages then ran under
the planning barrier and no new card could open. Treat a cancelled or timed
out approval as a revise so the workflow can propose again.

Part of #12153
2026-10-05 19:23:59 -07:00
yhh 5561753a6d fix(studio): composer control tooltips wrap inside their box and no longer fade when the host is disabled
The refine and attach tooltips are children of their buttons. They inherited
the button's white-space: nowrap, so the hint spilled past the 220px box in
both locales, and the disabled refine button dimmed itself to 35% opacity,
which multiplied down onto the tooltip that explains the control.

The shared .studio-control-tip now wraps inside its max-width, and a disabled
host dims its glyph instead of itself.

Part of #12148
2026-10-05 19:17:14 -07:00
YHH cf1a147bcb Merge pull request #12150 from esengine/fix/subagent-model-saved-while-running
fix(settings): keep a role saved while busy and say it is not applied yet
2026-10-05 19:16:27 -07:00
esengine c4736c747c fix(settings): keep a role saved while the conversation is busy and say it is not applied yet
Cause: the settings page awaited setRole and only then re-read /roles. While a
turn, a question or a background job is active the kernel has already written
the value and answers 409 runtime.saved_while_running; run() treated that as a
failure, the re-read never ran, and the controlled select snapped back to
"follow the main model" although config.toml held the saved model. The banner
also called it "操作未完成".

Fix: the role row always re-reads /roles (promise finally), and the page's one
failure banner now distinguishes saved-but-not-applied (SAVED_NOT_APPLIED, the
set the provider edit form already uses) from a real failure, with the same
title, warn colour and status role.

Blast radius: Settings.tsx only. Every action routed through run() now shows
"已保存,尚未生效" instead of "操作未完成" when the kernel answers one of the
SAVED_NOT_APPLIED codes; other failures render as before. No kernel change.

Neighbouring behaviours tested: role saved while idle, role saved while busy
(value and notice), real refusal (alert, no saved notice), other role rows and
a reopened page after a busy save, default-model row refused while busy (stays
on the kernel's default; the kernel refuses before writing, so it is not the
same defect). Go: POST /roles mid-turn returns 409 runtime.saved_while_running
and GET /roles returns the saved value.

Why this layer: the page owns the re-read and the banner; the code identity
already comes from the kernel and the shared SAVED_NOT_APPLIED list.
2026-10-05 19:06:33 -07:00
YHH d1c534c8d6 Merge pull request #12146 from esengine/fix/cli-quit-ctrlc-tab-parity
fix(tui): restore 1.x /quit, second Ctrl+C while stopping, and Tab accept
2026-10-05 18:57:55 -07:00
YHH 10c6aade5f Merge pull request #12140 from esengine/fix/browser-open-failure-visible
fix(workbench): show why a browser tab could not open
2026-10-05 18:55:02 -07:00
YHH 01edf16dbc Merge pull request #12145 from esengine/fix/scratch-owned-temp-dirs
fix(jobs): reclaim temp roots abandoned by killed kernels via a lock-owned scratch dir
2026-10-05 18:51:36 -07:00
yhh ce66ba8742 fix(tui): restore 1.x /quit, second Ctrl+C while stopping, and Tab accept
Part of #12144.

Cause
- `/quit` and `/exit` are in the slash catalogue but the TUI had no handler,
  so since #11969 they were sent to the model as ordinary messages.
- Every Ctrl+C on a running turn re-sent cancel, so a turn that would not
  stop could not be left, while the footer says "Ctrl+C exits".
- Tab in the completion menu moved the highlight down (and did nothing
  visible with one item); the guides say Tab / Enter accept.

Blast radius
- internal/frontend/tui: send(), menuKey, the Ctrl+C branches of onKey and
  viKey, one new Options field. internal/frontend/cli passes the quit names
  from the slash registry. docs/GUIDE.md and GUIDE.zh-CN.md rows for Ctrl+C,
  Ctrl+D and /quit. No controller, wire or config change. The bare words
  exit, quit and :q stay ordinary prompts.
- Tab history: the cycling was part of the first completion commit
  (5ff9d7a0f) with no stated reason and no test; nothing later touched it.

Neighbouring behaviours tested
- Idle double Ctrl+C, Ctrl+D (idle and while running), double Esc opens
  rewind, a second Esc does not quit, Esc then Ctrl+C quits, one cancel call
  per first Ctrl+C, vi mode Ctrl+C, a finished cancelled turn needs two
  presses again, Up/Down still move the menu, Enter accepts, Tab accepts
  the highlighted row, file-mention directory descends, Tab without a menu
  still asks the kernel, /quit while a turn runs is not queued.

Why this layer
- Quitting is a frontend lifecycle action with no controller verb, so it
  lives in the TUI local-command switch beside /mouse; the names come from
  the registry through Options rather than a second alias list. Both
  interrupt paths share one helper so Esc and Ctrl+C agree on what
  "stopping" means. The unreachable shift+tab case in menuKey is removed.
2026-10-05 18:48:26 -07:00
yhh 731bc29ca5 fix(workbench): show why a browser tab could not open
When the kernel refuses browser.open, the workbench stored the reason in
`failed` but only drew it inside the file-tab branch. Opening the column
or pressing + with no page left the pane on its empty state, "choose a
file on the right, or open a browser", with no error.

Cause: `failed` was rendered only when the active surface is a file, while
the browser open paths (column opening with no agent tab, the + button)
also write it. The kernel also answered every failure with the generic
browser.open_failed, whose wording needs an {error} param it never sent,
so the typed engine_missing / engine_failed / profile_busy causes were
lost. Separately, `.workbench-error` used --danger, a token no stylesheet
defines, so such errors drew in the body colour.

Blast radius: WorkbenchPanel, one CSS declaration, serve's browser open
refusal and the kernel.ts / en_kernel.ts tables. The alert renders for any
non-file surface; file tabs keep their own copy and are unchanged.
`failed` is cleared when a tab is picked and when the active tab closes.
POST /browser/open now answers browser.engine_missing, browser.engine_failed
or browser.profile_busy for those causes (same 400, same params, plus the
detail in params.error) and browser.open_failed for the rest. The colour
fix also recolours the file-tab and explorer errors.

Neighbouring behaviours tested: the three codes and the generic one through
the real SsePort decoding path, on both open paths, asserting the exact
text and no leftover placeholder; the reason clearing on picking a file
and on a later successful open; a file read error staying single on its
tab and going away with it; serve end to end with real launch failures;
the refusal-code parity test; the whole WorkbenchPanel suite and tsc.

Why this layer: the pane owns the state and the draw; the kernel boundary
in serve is where the typed cause becomes a code, and internal/platform/
browser is not touched.

Fixes #12139
2026-10-05 18:42:59 -07:00
yhh 90de42bf12 fix(jobs): reclaim temp roots abandoned by killed kernels via a lock-owned scratch dir
Add internal/base/scratch: a temp directory claimed by a long-lived
exclusive .owner.lock (internal/base/filelock). Remove releases the lock
first and retries sharing violations for up to 2 s. Sweep/SweepOnce remove
only non-symlink directories past a one-minute grace whose lock can be
taken, so a live owner is never touched. The jobs manager now creates its
reasonix-jobs-* fallback root through it, which also sweeps roots left by
earlier killed kernels.

Part of #12142
2026-10-05 18:41:45 -07:00
YHH bc55e83c39 Merge pull request #12096 from KHG420/codex/studio-mcp-cached-inventory-notice
fix(studio): mark cached MCP tools without descriptions
2026-10-05 18:26:42 -07:00
YHH 7ee3a554c4 Merge pull request #12094 from KHG420/codex/studio-localized-settings-search
fix(studio): search settings by localized labels
2026-10-05 18:19:37 -07:00
xiexian 215dedc5b4 fix(studio): hold navigation during market publication 2026-10-06 09:13:19 +08:00
xiexian 20c883c724 fix(studio): renew each runtime application confirmation 2026-10-06 09:03:06 +08:00
xiexian b2628325b7 fix(studio): scope package installation forms to connection 2026-10-06 08:58:18 +08:00
YHH f1bae78d39 Merge pull request #12113 from xxoingr/fix/tui-resume-index
fix(tui): /resume <n> continues the nth saved session in place
2026-10-05 17:37:25 -07:00
YHH f0e735084a Merge pull request #12131 from KHG420/codex/studio-memory-connection
fix(studio): scope memory panels to their connection
2026-10-05 17:08:59 -07:00
YHH e9108c3133 Merge pull request #12127 from KHG420/codex/studio-backup-connection
fix(studio): scope backup forms to connection
2026-10-05 16:54:12 -07:00
YHH a91c2632d5 Merge pull request #12134 from esengine/docs/drop-open-pr-cap
docs(contributing): drop the open-PR cap
2026-10-05 15:33:08 -07:00
yhh 6afdcb47a0 docs(contributing): drop the open-PR cap
Contributors may keep any number of pull requests open. The policy keeps one suggestion for series of related changes.
2026-10-05 15:25:16 -07:00
YHH fa978db05e Merge pull request #12132 from esengine/docs/contributing-pr-policy
docs(contributing): open-PR cap, issue-first for features, AI-assist note
2026-10-05 15:18:42 -07:00
xiexian 57e6222d98 fix(studio): refresh memory history after writes 2026-10-06 06:13:43 +08:00
yhh 462a47f240 docs(contributing): open-PR cap, issue-first for features, AI-assist note 2026-10-05 15:09:23 -07:00
xiexian eca93427cc fix(studio): scope memory panels to their connection 2026-10-06 05:49:27 +08:00
xiexian c7a6704558 fix(studio): preserve hook edits during trial and save 2026-10-06 05:21:31 +08:00
xiexian 7f3b72967d fix(studio): scope backup forms to connection 2026-10-06 04:26:31 +08:00
xiexian d5c963523d fix(studio): enforce the MCP example argument schema 2026-10-06 01:47:42 +08:00
xiexian 4881f167dc feat(studio): identify installed package contribution kinds 2026-10-06 00:59:45 +08:00
xxoingr 34c84f7a73 fix(tui): /resume <n> continues the nth saved session in place
In 1.x, "/resume <n>" switches straight to the nth saved session. The Studio
TUI ignored the number and always opened the picker, the known gap left in
the CLI migration tracking issue.

An indexed /resume now resolves the session by its row in the same list the
picker numbers and resumes it, with the same refusals the picker gives: a
running turn, the session already open, or an index outside the list. A bare
/resume (or a non-numeric argument) still opens the picker.

The index lookup shared by /takeover <n> and /rename <n> <title> moves into
one helper so the three commands cannot drift apart.

Refs #11493
2026-10-06 00:35:42 +08:00
xxoingr 962771cbdb feat(studio): let the feedback dialog fill the window
The feedback dialog is fixed to a 720px column and thread bubbles are capped
at 560px, so a long maintainer reply on a wide window reads as a narrow strip
that needs constant scrolling.

Add a "Fill the window" toggle next to Close. While pressed, the dialog takes
the whole veil and thread bubbles widen to 92% of the column; pressing it
again returns to the reading column. The toggle is a view-only action
(feedback.expand) and the state lives only for the open dialog.

Refs #11575
2026-10-06 00:24:16 +08:00
Will 48365b2ecb test(e2ebench): hold fanout-width, upstream-edge and project-check to the authoring guard
The reference-solution half covered only the e2e, train and verification-stress
corpora, so three committed corpora with the same tasks/ layout carried no
evidence that any attempt can score: a grader that can never pass is
indistinguishable from a task with no solution, and a corpus of those teaches
that nothing is ever accepted.

Their pristine seeds already fail, checked across all eleven tasks, so this adds
the reference solutions the guard needs: one ANSWER.md each for the three fleet
tasks, and the non-positive-skipping calc.py the eight project-check graders
assert. The e2e suite stays the single declared exception, named in the skip.

Refs #11852.
2026-10-05 22:42:25 +08:00
YHH 198f60e6b4 Merge pull request #12101 from esengine/fix/side-models-effort-fallback
fix(boot): side models retry on the lowest effort when an endpoint refuses "disabled"
2026-10-05 07:36:50 -07:00
yhh 4396615092 fix(boot): side models retry on the lowest effort when an endpoint refuses "disabled"
Prompt refine and commit-message drafting built their provider with effort forced to "disabled". Generic OpenAI-compatible endpoints refuse that at construction, so the side model was nil and the user saw "no usable model" while chat worked. sideProvider retries only on provider.ErrEffortRefused, with the lowest effort the entry's vocabulary lists (automatic depth when it lists none); other construction errors are not retried and are logged at warn.
2026-10-05 07:22:55 -07:00
xiexian c76e155243 docs(studio): run scenario exercises from installed packages 2026-10-05 21:40:48 +08:00
xiexian 61c1e6cb68 docs(studio): clarify skill frontmatter namespaces 2026-10-05 20:53:20 +08:00
xiexian 8175a143f1 fix(studio): mark cached MCP tools without descriptions 2026-10-05 20:30:46 +08:00
xiexian 57ec3c2245 fix(studio): search settings by localized labels 2026-10-05 19:51:57 +08:00
xiexian 69d07fca45 fix(studio): scope theme inventory reads to their connection 2026-10-05 18:38:34 +08:00
xiexian cb6cd332d8 fix(studio): isolate theme import feedback by connection 2026-10-05 18:15:11 +08:00
yhh cbadfe1753 feat(compaction): announce an automatic compaction whose retry is held after a failed attempt
When a summary fails under the pressure trigger the retry is held, and
while it is held and compaction is due nothing reached the user. The
agent now emits a coded notice, compact_held, once per turn and cause,
with the failure's code as detail. The desktop and the TUI word it from
the code in the UI language; a code a build cannot word keeps the
kernel's English.
2026-10-05 02:13:15 -07:00
YHH 064405a2a7 Merge pull request #12082 from esengine/fix/compaction-failure-visible-and-bounded
fix(compaction): a failed summary's retry hold gives way before the window
2026-10-05 02:10:37 -07:00
YHH aba9fe6134 Merge pull request #12076 from esengine/fix/sidebar-select-immediately
fix(studio): select a session row on the click instead of after the kernel answers
2026-10-05 02:02:38 -07:00
YHH ea7721b15f Merge pull request #12079 from esengine/fix/status-no-config-load
perf(serve): answer /status model facts from the build, not a config load
2026-10-05 01:55:07 -07:00
YHH 31725ae561 Merge pull request #12077 from esengine/fix/catalog-frontmatter-only
perf(skill): leave references and scripts unread when listing skills
2026-10-05 01:53:56 -07:00
yhh 985853ac03 fix(compaction): a failed summary's retry hold gives way before the window, and a code-less overflow refusal is recovered
A transient summary failure held the retry until the input grew by an
eighth of the trigger. Recorded near 105% of the trigger that release
point is the window itself, so the next request went out uncompacted.
The step is now capped at half of the room left below the hard ceiling,
so the hold always lifts before it.

A relay that refuses an oversized request with a 400 and no error code
was not recognised as an overflow, so recovery never ran. A 400 or 413
whose request our own estimate puts over the window now takes the same
fold-and-replay path, judged by status and size, never by wording.
2026-10-05 01:47:19 -07:00
esengine 1e17d7f5c7 fix(studio): select a session row on the click instead of after the kernel answers
A session row was selected only once POST /runtimes (or /resume), GET /runtimes
and GET /tree had all returned, so every switch to a session without a pane
waited out the kernel's pane assembly with nothing but a dimmed row to show for it.

The window now owns which session is being opened (useSessionSwitch). The row
reads selected and busy from that state as soon as it is clicked, the active
pane shows aria-busy, a ticket orders concurrent opens so the last click wins
and a refusal from a superseded open changes nothing, and a failed open drops
the selection back to the pane that is still in front while the error is shown.
Opening moves out of App into useOpenPane so every caller shares the one path.

The pane burst after an open now asks for /runtimes and /tree through one
single-flight read instead of one per caller.
2026-10-05 01:40:40 -07:00
yhh 1a18ad9e0e perf(serve): answer /status model facts from the build, not a config load
/status called config.Load() on every poll, which re-parses every
installed plugin package under a process-wide lock. The controller now
carries the model entry boot resolved and /status reads effort, vision
and the model ref from it. The provider label still comes from the user
file because a rename rebuilds nothing.

Refs #12055
2026-10-05 01:39:45 -07:00
yhh 3cb8c26bd7 perf(skill): leave references and scripts unread when listing skills
Discovery read every directory-layout skill's references/ and scripts/
to build a Body that a catalog never uses, so opening a pane cost
O(bytes in the skill library). Listing now keeps only SKILL.md; Read,
ReadSlash, Prepare and the capability digest complete the skill when it
is invoked or inspected.

Refs #12055
2026-10-05 01:34:53 -07:00
YHH b9bc20a6cb Merge pull request #12036 from esengine/fix/summary-idle-bound
fix(compaction): bound the summary request by lack of progress, not total time
2026-10-05 01:22:30 -07:00
yhh 84688a91d8 fix(compaction): bound the summary request by lack of progress, not total time
A summary request was cut off at a fixed 90 s of wall time, so a local model
that was still streaming steadily failed every /compress with summary_timeout.
Bound the silence between streamed chunks instead (6 minutes, one minute past
the providers' own 300 s stream idle watchdog), and add a 30 minute overall
ceiling with its own typed code summary_ceiling so a trickling model cannot
hold the turn. Both failures stay transient: a later attempt may retry once
the input has grown. The notices state which bound fired and its length.
2026-10-05 01:13:31 -07:00
YHH 8511f820e9 Merge pull request #12074 from esengine/fix/skills-list-single-config-load
fix(serve): list skills with one configuration read instead of one per skill
2026-10-05 01:00:48 -07:00
esengine 9a6df69e00 fix(serve): list skills with one configuration read instead of one per skill
GET /skills resolved each skill's activation through Controller.SkillEnabled,
and every call re-ran config.Load(), which walks all installed plugin
packages. Resolve activation once per request via Controller.SkillActivation.

Refs #12055, #12051
2026-10-05 00:51:24 -07:00
xiexian 67f0b7b9f5 fix(studio): preserve focus after legacy clipboard copy 2026-10-05 15:16:01 +08:00
xiexian bb760c6684 feat(studio): copy canonical skill invocations from Settings 2026-10-05 14:47:08 +08:00
YHH 63df856224 Merge pull request #12064 from esengine/fix/outbound-user-agent
fix(net): send a non-Go User-Agent on catalog, relay and worker requests
2026-10-04 23:35:06 -07:00
xiexian c756ff26e7 feat(studio): find installed packages by contributed capabilities 2026-10-05 14:25:33 +08:00
yhh f96b3677c2 fix(net): send a non-Go User-Agent on catalog, relay and worker requests
Go's default User-Agent is answered with 403 by edge bot protection on
datacenter networks. Catalog and manifest reads now use the Studio update
identity shared with artifact downloads, the relay websocket handshake uses
the account client's identity, and feedback, telemetry, crash and account
requests fall back to the shared client identity when none is set.
2026-10-04 23:24:43 -07:00
YHH f154bd95e5 Merge pull request #12056 from esengine/fix/status-poll-never-stacks
fix(studio): status reads never stack, decision submits are bounded
2026-10-04 22:55:15 -07:00
yhh 5edd8ac898 fix(studio): status reads never stack, decision submits are bounded
The pane polled /status every 250ms with no in-flight guard. When the kernel
answered in 13-16s the unanswered reads filled the browser's per-origin
connection pool and the approve, plan-decision and answer POSTs queued behind
them.

Idempotent reads now share one request per path with a single trailing
re-read, the pane schedules its next poll when the previous read settles, and
decision submits get a bounded wait that fails with a typed KernelBusyError.
2026-10-04 22:48:03 -07:00
YHH ad5191854f Merge pull request #12050 from esengine/fix/retry-line-explains-itself
fix(retry): say why a request is retried, what it waits for, and expose idle_timeout_seconds
2026-10-04 22:42:30 -07:00
esengine 04007d942f fix(retry): say why a model request is retried, what it waits for, and expose idle_timeout_seconds
The retry status line showed "connection broke before the response headers"
for every retried failure, including 502/429 statuses, did not say what the
attempt was waiting for or when it counts as unanswered, and its seconds
counter ran from the first notice of the whole stall instead of per attempt.

The retrying event now carries a typed failure class (connection_closed,
timeout, upstream_status with the HTTP status, stream_idle, upstream_error),
the backoff before the next attempt, and the header wait of the coming
attempt. The producers set them: SendWithRetry knows the class, the adapters
pass their idle_timeout_seconds window, and the stream replay path maps the
stream-interrupt reason. The window words each class and phase (backoff
countdown, then a wait that restarts per attempt) from those fields, never
from text. The CLI machine record carries the same fields.

Provider settings gain one advanced row for idle_timeout_seconds (1..32767,
empty is the default), validated in the form and by the kernel's provider
edit endpoint.

Fixes #12046
2026-10-04 22:19:08 -07:00
YHH c701436015 Merge pull request #11991 from keeyangyy/pr/remote-host-switches
feat(remote): per-host switches to stop dialing a machine and to keep it out of the rail
2026-10-04 21:55:40 -07:00
keeyang 0d69ac0f95 feat(remote): per-host switches to stop dialing a machine and to keep it out of the rail
The host book has no per-machine state: every entry is dialed on demand and every entry is
listed in the rail. A machine that is temporarily off the network, or one kept for the
occasional job, cannot be marked as such - the only lever is removing it, which takes its
folders and its forward rules with it.

- RemoteHostEntry gains disabled, written as "disabled" rather than "enabled" so an entry
  written before the field existed still means what it always did, plus IsEnabled(); the
  renderer writes the key only when it is set.
- One switch per row decides whether the machine is dialed, and that is all it decides:
  live panes keep running and the rail still lists the machine, because a machine being
  off the network says nothing about what this window should show. Whether this window
  lists it stays a separate preference, and it is an immediate row action next to the
  row's other actions rather than a control inside the edit form, which saves on submit.
- remoteHostDialable is the single answer to whether a machine may be reached: POST
  /remotes/open refuses a disabled one with remote.disabled before dialing, and remoteLink
  (the tree and directory reads, and removing a far session) asks the same question, so a
  disabled machine is not reached for at all. A book that cannot be read is a refusal
  rather than a dial.
- GET /remotes reports disabled and the edit endpoint carries it.
2026-10-05 12:28:38 +08:00
YHH 0397a3bed6 Merge pull request #12047 from esengine/fix/phone-chip-idle-while-running
fix(studio): status chip no longer says idle while the turn is running on a late-attached client
2026-10-04 21:24:00 -07:00
yhh 4c64d7cc61 fix(studio): status chip no longer says idle while the turn is running on a late-attached client 2026-10-04 21:15:46 -07:00
yhh 95c63e6602 fix(installsource): bound and sanitise package-supplied preview text
Project every string and list of an install plan through one bounded
projection shared by all frontends: names, paths, commands and arguments
render hidden characters as visible escapes, prose has them removed, and
each field, list and the action count has a cap. A cut ends the field with
an ellipsis and sets previewTruncated on the action and the plan. The planId
still digests the complete unprojected plan, and the market ledger records
the unprojected paths. Installed-package detail views (inventory, record,
runtime, warnings) use the same limits from the shared text utility.
2026-10-04 21:03:30 -07:00
YHH 312ba89ae6 Merge pull request #11992 from keeyangyy/pr/default-model-display
fix(studio): the default-model row shows the configured default, not the session's model
2026-10-04 21:01:55 -07:00
YHH a3d1142d7d Merge pull request #12034 from esengine/fix/studio-stable-github-latest
release(studio): stable releases become GitHub latest, without a root latest.json
2026-10-04 20:57:16 -07:00
keeyang 5c3b9f0bed fix(studio): the default-model row shows the configured default, not the session's model
#11897 stopped a session's model switch from rewriting default_model, which left the
settings row reading status.modelRef - the model the session in front is running - so
switching models inside a conversation still moved that row while the file it claims to
describe did not change.

The row now shows the entry the model catalogue marks as default, falling back to the
session's model when the catalogue carries no default, and the column beside it follows
the same value. `main` stays the session's model for the vendor-protocol inference this
component does.

Tested: with a catalogue default the row shows it whatever the session runs; without one
it falls back to the session's model; the settings-catalogue, ModelUsage and i18n gates.
2026-10-05 11:52:13 +08:00
yhh 16933b79d4 fix(docs): keep the owner, aliases and tool identities out of the acknowledgments table 2026-10-04 20:47:45 -07:00
esengine 7aed16a34f feat(desktop): one declared interface-scale range, and Ctrl/Cmd +/-/0 to change it
The interface scale was declared four ways: the config comment said
0.8..1.6, the save clamped to 0.7..1.8, the slider ran 0.8..2.5 and the
presets covered 0.9..1.3. Dragging the slider to 2.0 showed 2.0 while the
kernel stored 1.8.

config.ZoomMin/ZoomMax/ZoomStep are now the only declaration. The save
clamp, the rendered config comment and the range /appearance announces
(mirrored to the page by wire-parity) all read them, and the slider is
drawn from the announced range instead of carrying its own. A stored
value outside the range reads as the nearest bound but is not rewritten
unless the user changes the zoom.

Ctrl/Cmd + '=' / '+' / '-' / '0' (read by key or by key position, so
shifted and non-QWERTY layouts work) step the interface scale through the
named presets (range ends outside them) and reset to standard, saving
through the same appearance setting as the Appearance control. Chromium
page zoom is not used.

The range is 0.8..1.8: 1.8 was the largest value the old save could
store, so nobody loses headroom. The keys exist only inside the desktop
shell; a browser tab keeps its own page zoom.

Fixes #12032
Part of #11962
2026-10-04 20:42:20 -07:00
yhh b7541b7c80 release(studio): stable releases become GitHub latest, without a root latest.json
A stable studio-vX.Y.Z that is the highest stable studio tag is created with
--latest and without --prerelease. Candidates (-preview.N, -rc.N) keep
--prerelease --latest=false, and recovering an older stable tag does not take
latest back. The decision lives in scripts/studio-release-flags.sh and reads
the tag grammar, not wording.

The GitHub release no longer carries a root latest.json. The 1.x desktop
updaters fall back to releases/latest/download/latest.json, and builds up to
1.18 do not validate the manifest and share Studio's minisign key, so a
Studio manifest there would offer them a Studio package. The file stays in
the R2 mirror under <tag>/, which is the only place Studio reads it. The
release now has 21 assets.

Adds a flags-script test and a workflow contract test, and updates the
runbook, READMEs, MIGRATING and ROADMAP.
2026-10-04 20:25:37 -07:00
YHH ac681f524d Merge pull request #12030 from esengine/theme-decorative-tokens
feat(theme): decorative roles get their own tokens (link, brand, halo, labelAgent)
2026-10-04 20:08:02 -07:00
yhh b977cbda91 feat(theme): give links, brand tint, halos and agent names their own tokens
Part of #12008.
2026-10-04 19:54:17 -07:00
YHH e6655a6054 Merge pull request #12014 from esengine/fix/pack-theme-generated-stylesheet
fix(studio): deliver theme packs through a generated stylesheet, not inline styles
2026-10-04 19:45:31 -07:00
YHH 3e6f700acb Merge pull request #12021 from esengine/fix/approval-allow-wording-12020
fix(studio): name the allow decision on session/persistent approval buttons
2026-10-04 19:44:36 -07:00
YHH 770f24b278 Merge pull request #12017 from esengine/fix/claude-modules-unsupported
fix(pluginpkg): report Claude Code modules as an unsupported capability
2026-10-04 19:43:45 -07:00
YHH d338338d54 Merge pull request #12022 from esengine/docs/studio-2.28.0-notes
docs(release): Studio 2.28.0 notes
2026-10-04 16:58:29 -07:00
yhh 8df12a049a docs(release): Studio 2.28.0 notes 2026-10-04 16:51:12 -07:00
YHH ab25352696 Merge pull request #12018 from esengine/fix/bash-rule-wrapper-peeling
fix(permission): match Bash deny and ask rules through transparent command wrappers
2026-10-04 16:33:38 -07:00
yhh 9d96517857 fix(studio): name the allow decision on the session and persistent approval buttons 2026-10-04 16:31:48 -07:00
yhh 1fa63d16f0 fix(permission): match Bash deny and ask rules through transparent command wrappers
A deny or ask rule such as Bash(rm:*) compared only the words the model wrote,
so `env X=1 rm`, `sudo rm`, `command rm`, `nohup rm`, `nice rm`, `time rm` and
`/bin/rm` were judged by fallback mode instead of the rule.

shellparse.PeelWrappers reads one simple command from the shell AST and
follows a fixed set of wrappers (env, sudo, doas, command, builtin, exec,
nohup, time, nice) down to the program they run, naming a path-spelled program
by its base name. Deny and ask matching test every layer; allow and session
rules are unchanged. A wrapper whose target cannot be named statically needs a
human instead of falling through to the mode.
2026-10-04 16:24:22 -07:00
yhh 4b4b324a81 fix(studio): deliver theme packs through a generated stylesheet, not inline styles
apply() wrote every pack token, and the ink variables even with no pack,
as inline custom properties on <html>, so any author stylesheet needed
!important to win. The values now live in one rule of a <style
id="pack-theme"> created after the built-in sheets, written through the
CSSOM so a rejected value is dropped. The contrast tiers carry the inks in
the stylesheet, with --ghost on --faint's value, and perf/tiers.mjs holds
each tier to the legibility users already had.
2026-10-04 16:08:54 -07:00
yhh f654aeabb6 fix(pluginpkg): report Claude Code modules as an unsupported capability
A Claude Code Mod declares "modules" in hooks/hooks.json instead of a
"hooks" map. The importer returned early on an empty hook map, so the
modules were dropped silently and a Mod with skills previewed as full.
2026-10-04 15:56:08 -07:00
YHH e40b3b070f Merge pull request #12006 from esengine/fix/model-list-two-keys-one-endpoint
fix(serve): keep models from two accounts at one endpoint in the model list
2026-10-04 14:12:44 -07:00
yhh fc2887582e fix(serve): keep models from two accounts at one endpoint in the model list
The model list collapsed entries by endpoint + model, so two providers
sharing a base URL with different API keys listed each model once and
the second account's refs vanished from the picker.
2026-10-04 13:58:32 -07:00
YHH 8f8785c4c4 Merge pull request #11697 from KHG420/codex/studio-plugin-profile-diagnostics
fix(studio): diagnose rejected plugin profile declarations
2026-10-04 13:58:02 -07:00
xiexian e6d5dab212 fix(studio): diagnose rejected plugin profile declarations 2026-10-05 04:39:40 +08:00
YHH 403ae88dd2 Merge pull request #12004 from esengine/fix/boot-flaky-tests
test(boot): deflake URL-interaction and fullsidecar effect tests
2026-10-04 13:25:38 -07:00
YHH f7eb4e26a6 Merge pull request #12002 from esengine/fix/flaky-instances-differ-test
test(context-retrieval): assert live runs are seeded from entropy, not that two answers differ
2026-10-04 13:19:31 -07:00
yhh a4e456a43e test(boot): wait for lazy MCP swap before URL-interaction run; give sidecar build a 5m bound and name its timeout 2026-10-04 13:16:21 -07:00
YHH 42455e61fb Merge pull request #11844 from KHG420/codex/studio-sdk-tool-call-identifiers
fix(sdk): validate provider tool call identifiers
2026-10-04 13:12:36 -07:00
esengine 677e3eabe2 test(context-retrieval): assert live runs are seeded from entropy, not that two answers differ
TestInstancesDifferBetweenRuns instantiated every task twice and failed
whenever two independent draws collided. Three tasks answer with a bounded
number (i70 quota 1200..9800, i47 lease 15..300, i112 lease 20..420), so a
run fails with probability 1/8601 + 1/286 + 1/401, about 0.61%.

liveRand now delegates to liveRandFrom(io.Reader). The test asserts the
invariant at the seed: two live generators do not share a stream, the same
entropy yields identical instances and different entropy yields different
values.
2026-10-04 12:59:18 -07:00
YHH 090060507c Merge pull request #11742 from KHG420/codex/studio-unicode-agent-inventory
fix(studio): match package agent inventory to runtime names
2026-10-04 12:56:01 -07:00
xiexian cb1b36d726 fix(studio): match package agent inventory to runtime names 2026-10-05 03:41:01 +08:00
YHH 255001cead Merge pull request #11843 from KHG420/codex/studio-sdk-usage-counts
fix(sdk): reject negative provider token usage
2026-10-04 12:30:56 -07:00
YHH 7ed449841b Merge pull request #12000 from esengine/fix/state-root-walk-windows-delete
test(config): treat a directory mid-deletion on Windows as vanished in the state-root walk
2026-10-04 12:30:30 -07:00
yhh 28455421fb test(config): treat a directory mid-deletion on Windows as vanished in the state-root walk
The walk tolerated only not-exist, but a directory being removed on Windows
fails to open with a sharing violation or access denied. Those errors now
count as vanished only when a re-stat shows the path gone.
2026-10-04 12:16:04 -07:00
YHH 8cfb5f75c7 Merge pull request #11702 from KHG420/codex/studio-directory-agent-inventory
fix(studio): guard agent inventory source residency
2026-10-04 12:00:42 -07:00
xiexian 116d0b4176 fix(studio): guard agent inventory source residency 2026-10-05 02:42:01 +08:00
YHH fc1f7fdcfa Merge pull request #11837 from KHG420/codex/studio-sdk-chunk-types
fix(sdk): reject unregistered provider chunk types
2026-10-04 11:33:32 -07:00
xiexian 9d5f2a7dd9 fix(sdk): validate provider tool call identifiers 2026-10-05 02:28:31 +08:00
xiexian 63754d56d2 fix(sdk): reject negative provider token usage 2026-10-05 02:25:05 +08:00
xiexian 0e06829846 fix(sdk): reject unregistered provider chunk types 2026-10-05 02:22:10 +08:00
YHH 54f4fbb19f Merge pull request #11667 from Harbor404/fix/studio-11605-copy-session-info
feat(studio): copy session handoff information
2026-10-04 11:02:22 -07:00
YHH 03147a71cd Merge pull request #11839 from KHG420/codex/studio-sdk-echo-provider
feat(sdk): add an installable offline provider example
2026-10-04 11:01:31 -07:00
YHH 65a06434fb Merge pull request #11997 from esengine/fix/sdk-unused-time-import
fix(sdk): drop the import the merged intercept-budget change left unused
2026-10-04 10:45:05 -07:00
yhh fdba7c3210 fix(sdk): drop the import the merged intercept-budget change left unused 2026-10-04 10:36:19 -07:00
YHH 254140a866 Merge pull request #11795 from KHG420/codex/studio-sdk-provider-start-cancel
fix(sdk): cancel provider streams while opening
2026-10-04 10:20:50 -07:00
YHH b02849f54f Merge pull request #11719 from KHG420/codex/studio-runtime-doctor-executable
fix(cli): detect non-executable plugin runtimes in doctor
2026-10-04 10:13:26 -07:00
YHH 66bfceb85b Merge pull request #11789 from KHG420/codex/studio-sdk-externalized-intercept-budget
fix(sdk): include externalized reads in the intercept budget
2026-10-04 10:10:08 -07:00
xiexian 39fe0acdc5 fix(sdk): cancel provider streams while opening 2026-10-05 01:04:37 +08:00
YHH 931b9aea42 Merge pull request #11968 from esengine/docs/cli-migration-fixes
docs(migrating): match the verified 2.x CLI behaviour for 1.x users
2026-10-04 09:45:01 -07:00
YHH cb2485a0b8 Merge pull request #11990 from esengine/fix/model-check-shows-status
fix(providers): show HTTP status and endpoint text when a model check is refused
2026-10-04 09:43:44 -07:00
YHH 172d05191a Merge pull request #11852 from chuiwenwei/test/studio-verification-stress-solutions
test(e2ebench): prove every declared-solvable verification-stress task can pass
2026-10-04 09:41:03 -07:00
YHH 8c87da114b Merge pull request #11982 from esengine/fix/link-open-failed-page-no-second-browser
fix(studio): a link whose page failed to load no longer also opens the system browser
2026-10-04 09:35:37 -07:00
yhh 8634cf3da2 fix(providers): show the HTTP status and the endpoint's words when a model check is refused
Cause: the per-model check classified any 4xx it could not name as
unknown/rejected and the row printed only "request refused, not confirmed".
The kernel already carried httpStatus; the settings UI dropped it, and the
endpoint's own error text was never sent, so a local Ollama 404 (base URL
without /v1, model id without its tag), a 400 for a model that cannot take
tools, and a missing key on a System One entry all read identically.

Blast radius: POST /providers/check/model gains an optional `detail` field:
the endpoint's error text as one printable line. It is built in this order:
control characters and ANSI sequences replaced, the probe key masked in raw,
URL-escaped and JSON-escaped form (skipped for keys under 8 characters),
secrets.RedactCredentials, then cut to 300 runes. Folding and the key
replacement see the whole text; only the pattern scrub is bounded (8 KiB cut
on a word boundary, after folding). The model row shows an HTTP badge and the text inline. Status and
reason are unchanged and never derived from the text.

Neighbouring behaviours tested: Ollama-shaped upstream (correct, base without
/v1, tagless id, System One kind); 400/404/405/422 classification; detail
equals the upstream text, masks raw/escaped/plain keys, Bearer, ?token=,
Cookie, neutralises newlines/ANSI/NUL, cuts after redaction, cuts at the scan cap with the key split at every offset, with and
without leading whitespace, bounds a 4 MB body, leaves short keys alone (each mutation-checked); add and edit flows
share one fact helper; full ui vitest suite and the serve package.

Why this layer: the serve handler owns the typed result and the row is the only
renderer of it; classification stays where the failure is first known.
2026-10-04 09:31:09 -07:00
YHH 0a66bd417d Merge pull request #11791 from KHG420/codex/studio-sdk-stream-context-lifetime
fix(sdk): cancel provider contexts when streams finish
2026-10-04 09:25:48 -07:00
YHH 72030d75a3 Merge pull request #11806 from KHG420/codex/studio-market-blocked-previews
fix(studio): keep blocked market previews out of confirmation
2026-10-04 09:21:30 -07:00
YHH 5dfcc2d086 Merge pull request #11987 from esengine/fix/acp-resume-status-notif
test(acp): resume invariant is no transcript replay, not no notification
2026-10-04 09:16:32 -07:00
yhh 42d4ab374c test(acp): resume invariant is no transcript replay, not no notification 2026-10-04 09:06:20 -07:00
YHH 2921ddff26 Merge pull request #11969 from esengine/fix/tui-unknown-slash-as-message
fix(tui): send an unknown slash command to the model with a visible notice
2026-10-04 09:06:09 -07:00
YHH 750ed087db Merge pull request #11981 from esengine/fix/list-folder-of-first-turn
fix(serve): list the folder a first turn is held in
2026-10-04 08:58:31 -07:00
YHH a2a5855f23 Merge pull request #11973 from esengine/fix/compaction-block-release
fix(compaction): release a transient summary failure once the input has grown
2026-10-04 08:56:26 -07:00
yhh 9052fd32f6 fix(studio): a link whose page failed to load no longer also opens the system browser
POST /browser/open answers 400 when the navigation fails even though the
window's tab already exists, and the link handler read every refusal as
"nothing opened" and sent the same address to the machine's browser.
The refusal now names the tab it left behind and the handler only falls
back when there is none.
2026-10-04 08:54:43 -07:00
yhh 3a3b474b10 fix(serve): list the folder a first turn is held in
A pane adopted at launch is not a folder anyone picked, so nothing listed it
once a later launch opened another one. Its conversations stayed on disk with
no sidebar row to open them from. The folder is now listed behind the existing
ones when the pane's first turn mints its session path.
2026-10-04 08:48:09 -07:00
YHH 8fb66a86da Merge pull request #11976 from esengine/fix/queue-withdraw-races
fix(studio): queue withdraw races (double click, stale pull, late receipt)
2026-10-04 08:47:03 -07:00
YHH 2766f23beb Merge pull request #11979 from esengine/fix/state-root-walk-vanishing-dir
test(config): the state-root guard skips directories removed mid-walk
2026-10-04 08:42:19 -07:00
yhh 58a94b30d6 fix(studio): keep taking back a queued line from erring, ghosting or flickering back
A queued line taken back could fail with a spurious error on a repeated click,
leave a pending transcript row when its receipt landed after the withdrawal,
and reappear in the panel when an older queue pull answered last.
2026-10-04 08:36:56 -07:00
esengine 7db5d0af31 fix(compaction): release a transient summary failure once the input has grown
A truncated, timed-out or failed summary request blocked automatic
compaction for the whole generation, so context kept growing past the
fold trigger with no further attempt. Block release now reads the typed
failure code: transient request failures are released after the input
grows by one retry step; every other class stays blocked.
2026-10-04 08:32:39 -07:00
YHH 779fe8a04d Merge pull request #11972 from esengine/fix/compaction-summary-effort
fix(compaction): summary request asks for the lowest approved reasoning depth
2026-10-04 08:31:42 -07:00
yhh 25eff5352d test(config): the state-root guard skips directories removed mid-walk 2026-10-04 08:31:16 -07:00
YHH 0a96b4bf8e Merge pull request #11975 from esengine/fix/compaction-notice-codes
fix(compaction): typed codes for /compact notices and aborted cards, localised by the frontends
2026-10-04 08:26:55 -07:00
esengine c24586d34d fix(compaction): summary request asks for the lowest approved reasoning depth
The digest request inherited the session's reasoning effort, so reasoning
tokens spent the same 16384-token cap the digest had to fit in and a
finish_reason=length dropped the whole summary. It now carries
EffortOverride=low; adapters apply it only when the endpoint's own effort
vocabulary admits it, so endpoints without a low tier keep their configured
depth.

Refs #11970
2026-10-04 08:21:01 -07:00
esengine 3c96792e13 fix(compaction): host notices and aborted cards carry a typed code the frontends word
/compact notices were raw English sentences built from err.Error(), and an
aborted card with no code read "compaction done / folded nothing". Add
compacted/compact_declined/compact_failed notice codes (detail = the
compaction code), give cancellation, unclassified failures and a busy
session their own codes, and word them in the desktop and TUI catalogues.

Refs #11970
2026-10-04 08:16:44 -07:00
esengine 294d179cb5 fix(tui): send an unknown slash command to the model with a visible notice
The terminal UI asked the kernel to refuse any slash line the registry does
not answer, so a typed path or half-remembered command dead-ended with
"unknown command". 1.x sends it as an ordinary message and says so (#5756).

The TUI client no longer sets refuseUnknownSlash; the controller's existing
default path runs the line through the normal turn pipeline. That notice is
now localised (en/zh/zh-TW) and carries the unknown_command code.

A built-in named first is that built-in whatever follows it, as in 1.x: the
kernel matches /context, /new and /clear by first word, and the TUI does the
same for /help, /clear, /setup, /version, /mouse, /resume and its display
commands, so none of them is sent as prose when given arguments.

Refs #11493, #5756
2026-10-04 08:08:01 -07:00
YHH adde7dd6a7 Merge pull request #11928 from esengine/feat/first-run-key-gate
feat(tui): a keyless first run says why and refuses the turn locally
2026-10-04 07:55:35 -07:00
esengine 610e28f6d7 feat(tui): a keyless first run says why and refuses the turn locally
With no API key the connection panel opened silently and a typed message went
to the provider, which answered 401 with a pointer to a shell command. The
panel now names the model that lacks a key, and /submit refuses a turn for it
with provider.key_missing before anything is sent. The TUI returns the line to
the composer and points at /setup.
2026-10-04 07:45:09 -07:00
YHH 27d6e6f2ad Merge pull request #11967 from esengine/fix/usage-test-no-time-local
test(serve): pass the usage window its zone instead of writing time.Local
2026-10-04 07:43:59 -07:00
esengine 7f453a62f3 docs(migrating): match the verified 2.x CLI behaviour for 1.x users
Correct the YOLO shortcut, the --inline form and the unknown slash command row, and list the permission, slash command and compaction differences.
2026-10-04 07:42:42 -07:00
esengine 5fcf468fb0 test(serve): pass the usage window its zone instead of writing time.Local
The usage range test assigned the process-global time.Local while goroutines from earlier tests still read it, so the race detector failed the package. usageWindow now delegates to usageWindowIn, which takes the zone trailing windows count days in; the test passes a fixed zone and never touches the global.
2026-10-04 07:33:03 -07:00
YHH abd863407f Merge pull request #11964 from esengine/fix/backup-malformed
fix(backup): accept the private credential slot a provider key lives in
2026-10-04 07:10:43 -07:00
esengine 2d43186219 fix(backup): accept the private credential slot a provider key lives in
A provider whose natural key slot is taken gets REASONIX_CONNECTION_<id>_KEY. Collect wrote that provider and its key, but restore validation denied every REASONIX_ name, so the backup sealed fine and then failed to open as malformed.
2026-10-04 06:59:49 -07:00
YHH d30d9cec70 Merge pull request #11960 from esengine/fix/11955-11956
fix(studio): feedback opens with no pane, visible refusals, backup button explains itself
2026-10-04 06:59:33 -07:00
esengine 542a6076a3 fix(studio): feedback opens with no pane, refusals are visible, backup button says why it is disabled
Settings and feedback are answered by a pane's kernel. With no pane open, feedback silently did nothing and the settings refusal never cleared. Opening either now starts a session in the current folder, a refusal is shown for both, and it is withdrawn once a folder or pane exists. The cloud-backup button now states what it is waiting for.
2026-10-04 06:50:58 -07:00
YHH 692aecd6b7 Merge pull request #11343 from par73e/fix/studio-acp-mcp-health-9904
fix(studio): report per-server MCP health over ACP
2026-10-04 06:49:46 -07:00
par73e c5b3dbbfe0 fix(studio): complete MCP health review fixes
Preserve ACP frame-order assertions while skipping only MCP status updates,
and use t.Context in the serve regression test.

Report servers requiring launch approval as pending rather than failed in
controller, ACP and /mcp snapshots. Keep pending rows disabled without a retry
action while retaining their recorded transport and sanitized error.

Document connecting and awaiting-approval notifications, style the pending
indicator in both themes, and derive frontend state coverage from Go constants.
Add controller, endpoint, ACP and row-rendering regression coverage.
2026-10-04 21:15:01 +08:00
par73e d550f754fc fix(studio): address review on per-server MCP health
- Map the new `pending` state in the desktop panel with an i18n label. A
  repository-declared server nobody has answered for is off, but not because
  the user switched it off, and the row rendered the raw state string.
- Announce what actually happened. RecordFailure and
  RecordLaunchApprovalRequired now share one store path and name their own
  state, so a project MCP awaiting authorization stops reporting a failed
  connection.
- Correct the MCPSurfaceReady contract: it fires on every change to what /mcp
  answers, not once per server. Audited every consumer before changing it —
  the ACP sink republishes a full snapshot, the serve broadcaster numbers the
  frame and the SPA refetches /mcp, the headless stream passes it through — so
  repeats are harmless and the documentation was the stale part.
- Keep the spawn claim beside the rest of the deferred-spawn bookkeeping
  instead of a file of its own.
- Add the /mcp endpoint regression test the refactor was missing (live
  transport and source, recorded status code, in-flight spawn, cached
  description and tools), and a panel test tying every state label to the
  English catalogue.
2026-10-04 21:06:00 +08:00
par73e b3a5541069 fix(studio): expose per-server MCP health over ACP 2026-10-04 21:05:59 +08:00
YHH 63195e95a5 Merge pull request #11935 from esengine/feat/tui-mcp-panel
feat(tui): /mcp opens a panel to enable, reconnect and inspect MCP servers
2026-10-04 06:02:53 -07:00
esengine 878fc8a53d feat(tui): /mcp opens a panel to enable, reconnect and inspect MCP servers 2026-10-04 05:51:34 -07:00
YHH f7d6d25930 Merge pull request #11929 from esengine/feat/tui-model-provider-pickers
feat(tui): /model and /provider open a searchable picker and switch the session
2026-10-04 05:50:26 -07:00
YHH 0594ee9e0c Merge pull request #11923 from esengine/fix/credential-roundtrip-11901
fix(config): a stored API key reads back byte-for-byte
2026-10-04 05:42:25 -07:00
esengine 290e787c47 feat(tui): /model and /provider open a searchable picker and switch the session 2026-10-04 05:38:56 -07:00
YHH dbb7b8498f Merge pull request #11950 from esengine/feat/settings-display-currency
feat(settings): choose the cost display currency (Auto / CNY / USD)
2026-10-04 05:32:04 -07:00
esengine 4e7067a17b fix(config): a stored API key reads back byte-for-byte (#11901)
SetCredential and SetCredentialIfRevision built a KEY=value line and parsed it
with the dotenv reader, so ${...} was expanded and wrapping quotes stripped
before the key was ever written. The writer also left a value containing $
bare, which the reader then expanded. Store the assignment directly and pick
the first encoding the reader returns unchanged; a value no encoding can hold
(for example a trailing backslash next to a quote or $) is refused before
anything is written, so one entry can never make the whole file unreadable.
2026-10-04 05:31:41 -07:00
YHH ac16c15950 Merge pull request #11934 from esengine/feat/tui-skills-panel
feat(tui): /skills opens a panel to search, filter by source and toggle skills
2026-10-04 05:29:20 -07:00
esengine c8f57aeb69 feat(tui): /skills opens a panel to search, filter by source and toggle skills 2026-10-04 05:16:09 -07:00
YHH 3134b0f18c Merge pull request #11953 from esengine/fix/response-language-11951
fix(control): constrain the visible answer language per turn in auto mode
2026-10-04 05:14:40 -07:00
YHH 6ab2051411 Merge pull request #11949 from esengine/dependabot/github_actions/actions-dfc3086b10
build(deps): bump pnpm/action-setup from 6.0.9 to 6.1.0 in the actions group
2026-10-04 05:12:21 -07:00
yhh 6a9a821998 feat(settings): choose the cost display currency (Auto / CNY / USD)
Adds GET/POST /display-currency over the controller's new
DisplayCurrencySettings/SaveDisplayCurrency (shared with /currency), a
Settings > Usage control, and makes the quote sink follow the
display_currency notice so rounds after a change are priced in it.
2026-10-04 05:08:29 -07:00
YHH 3083ef303d Merge pull request #11945 from esengine/dependabot/go_modules/go-284eff489b
build(deps): bump the go group with 2 updates
2026-10-04 05:07:02 -07:00
esengine 5bba3bf0b4 fix(control): constrain the visible answer language per turn in auto mode 2026-10-04 05:04:16 -07:00
YHH 35da5f70be Merge pull request #11948 from esengine/dependabot/npm_and_yarn/desktop/frontend-next/npm-b63839cc01
build(deps): bump the npm group in /desktop/frontend-next with 4 updates
2026-10-04 05:04:15 -07:00
YHH 27f78af346 Merge pull request #11944 from esengine/feat/render-chart-tool
feat(tools): add the render_chart built-in, hidden from the provider schema
2026-10-04 05:03:04 -07:00
YHH ae6f621bb8 Merge pull request #11946 from esengine/dependabot/npm_and_yarn/desktop/electron/npm-c15dc57556
build(deps-dev): bump electron from 44.4.5 to 44.5.1 in /desktop/electron in the npm group
2026-10-04 05:01:22 -07:00
YHH 28dd27d033 Merge pull request #11927 from esengine/fix/import-1x-session-layouts
fix(migration): import 1.x sessions from project, v4 and desktop v5 stores
2026-10-04 05:00:36 -07:00
dependabot[bot] 6ee1bbd3aa build(deps): bump pnpm/action-setup
Bumps the actions group with 1 update: [pnpm/action-setup](https://github.com/pnpm/action-setup).


Updates `pnpm/action-setup` from 6.0.9 to 6.1.0
- [Release notes](https://github.com/pnpm/action-setup/releases)
- [Commits](https://github.com/pnpm/action-setup/compare/v6.0.9...ea17c68df8912ef543352723c149a84f56e3d413)

---
updated-dependencies:
- dependency-name: pnpm/action-setup
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-10-04 11:55:05 +00:00
dependabot[bot] c8152a8f7e build(deps): bump the npm group in /desktop/frontend-next with 4 updates
Bumps the npm group in /desktop/frontend-next with 4 updates: [@lezer/highlight](https://github.com/lezer-parser/highlight), [katex](https://github.com/KaTeX/KaTeX), [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) and [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest).


Updates `@lezer/highlight` from 1.2.4 to 1.2.5
- [Changelog](https://github.com/lezer-parser/highlight/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lezer-parser/highlight/commits)

Updates `katex` from 0.18.9 to 0.18.10
- [Release notes](https://github.com/KaTeX/KaTeX/releases)
- [Changelog](https://github.com/KaTeX/KaTeX/blob/main/CHANGELOG.md)
- [Commits](https://github.com/KaTeX/KaTeX/compare/v0.18.9...v0.18.10)

Updates `vite` from 8.3.0 to 8.3.2
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.3.2/packages/vite)

Updates `vitest` from 5.0.1 to 5.0.3
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v5.0.3/packages/vitest)

---
updated-dependencies:
- dependency-name: "@lezer/highlight"
  dependency-version: 1.2.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: katex
  dependency-version: 0.18.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: vite
  dependency-version: 8.3.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: vitest
  dependency-version: 5.0.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-10-04 11:54:18 +00:00
dependabot[bot] 0487b131a0 build(deps-dev): bump electron in /desktop/electron in the npm group
Bumps the npm group in /desktop/electron with 1 update: [electron](https://github.com/electron/electron).


Updates `electron` from 44.4.5 to 44.5.1
- [Release notes](https://github.com/electron/electron/releases)
- [Commits](https://github.com/electron/electron/compare/v44.4.5...v44.5.1)

---
updated-dependencies:
- dependency-name: electron
  dependency-version: 44.5.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-10-04 11:53:26 +00: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
yhh 5b4b4ccb97 fix(migration): import 1.x sessions from project, v4 and desktop v5 stores
Picking a 1.x data folder only looked at <root>/sessions, so per-project
sessions, sessions-v4 and desktop-sessions-v5/by-id imported nothing and the
UI reported no old sessions.
2026-10-04 04:49:41 -07:00
YHH ab8835393f Merge pull request #11932 from esengine/fix/cask-waits-for-cli-release
ci(release): write the Homebrew cask only after the CLI release exists
2026-10-04 04:49:29 -07:00
yhh dc809e1150 feat(tools): add the render_chart built-in, hidden from the provider schema
render_chart validates a chartspec v1 spec with chartspec.Parse and answers
with a chart_id and the deterministic text summary, never the rows. It is a
registered built-in left out of the provider allow list, so it is found and
called through use_capability and the cache-stable prefix is unchanged. The
spec persists in the call arguments the session already stores (#11564).
2026-10-04 04:46:12 -07:00
YHH 2bfe43a62d Merge pull request #11926 from esengine/cli/rename-by-index
fix(tui): /rename <n> <title> names the nth saved session and the picker shows the name
2026-10-04 04:41:55 -07:00
YHH 81ce4da64b Merge pull request #11919 from esengine/feat/chartspec-v1
feat(chartspec): add the closed v1 chart spec, validator and text summary
2026-10-04 04:38:49 -07:00
YHH 6a335ae9d7 Merge pull request #11939 from esengine/fix/editconn-save-failed-wording
fix(settings): distinguish failed save from saved-but-not-applied in provider edit
2026-10-04 04:33:07 -07:00
esengine f89c934e47 ci(release): the Homebrew cask is written only after the CLI release it points at exists, and never moves backwards
cli-channels and cli-pointer both waited only for cli-tag, so the cask could be
pushed while the v<version> release holding its archives was still being
created, and stayed pointing at nothing if cli-pointer then failed. The cask
step also overwrote the tap whatever its version, so a workflow_dispatch of an
older tag moved Homebrew backwards; it now skips a tap that is ahead and fails
closed when the tap's version cannot be read.
2026-10-04 04:32:09 -07:00
YHH bfe2ca68b8 Merge pull request #11931 from esengine/fix/cli-archive-build-identity
ci(release): CLI archives carry the same build identity as the npm packages
2026-10-04 04:31:21 -07:00
YHH c3837f5a93 Merge pull request #11937 from esengine/feat/slash-remote-preset-pasteimage
feat(control): answer /remote, /preset (/work-mode, /profile) and /paste-image
2026-10-04 04:28:17 -07:00
yhh 2abe2784e3 feat(chartspec): add the closed v1 chart spec, its validator and text summary
Leaf package internal/contract/chartspec: spec types for bar/line/pie, a
strict Parse (size cap first, unknown fields refused, scalar cells only,
unsafe integers refused), typed refusals with dotted codes and caps, and a
deterministic Summary that quotes cell data. No consumer yet (#11564).
2026-10-04 04:28:10 -07:00
YHH 0d7fd85452 Merge pull request #11920 from esengine/fix/run-untrusted-folder-exit-code
fix(run): exit 4 when an untrusted folder refused the work, and 1.x migration notes
2026-10-04 04:20:25 -07:00
YHH ac31510363 Merge pull request #11933 from esengine/fix/tui-finished-todos-stay
fix(tui): a finished to-do list stays docked until the next turn starts
2026-10-04 04:14:00 -07:00
YHH 753996a897 Merge pull request #11936 from esengine/feat/tui-rate-band
feat(tui): the footer and turn receipt name the peak or off-peak rate a spend was billed at
2026-10-04 04:13:55 -07:00
yhh 2f45ce03f3 fix(settings): say a refused provider save failed, and a saved-but-unapplied one is saved
EditConn titled every outcome "not saved" at warn level, so a write that
landed but was not applied to the live session read as a failure, and a real
failure read as a soft notice. Split by outcome: error alert for a refused
save, status note for saved-not-applied, own title for a catalog refresh.
2026-10-04 04:08:35 -07:00
esengine 4e4004f0f4 fix(tui): /rename <n> <title> names the nth saved session and the picker shows the name 2026-10-04 04:05:59 -07:00
YHH 939ff9f6f7 Merge pull request #11930 from esengine/fix/tui-blank-thinking-marker
fix(tui): a thinking block with no text draws no marker
2026-10-04 04:04:50 -07:00
YHH feb4a51051 Merge pull request #11925 from esengine/fix/windows-icon-fill
fix(desktop): fill the Windows icon tile so the Start menu icon matches its neighbours
2026-10-04 03:46:57 -07:00
esengine 69464792d2 feat(control): answer /remote, /preset (/work-mode, /profile) and /paste-image instead of 'unknown command' 2026-10-04 03:46:00 -07:00
esengine 75c640b9d2 ci(release): CLI archives carry the commit, build time and docs identity npm builds already carry
The archives that reasonix upgrade, the Homebrew cask and the GitHub release
install were linked with main.version only, so they reported no git_commit or
build_time_utc and the embedded product docs identified themselves as dev. The
npm packages of the same release were linked with all of them.
2026-10-04 03:43:25 -07:00
esengine a970662e35 feat(tui): the footer and turn receipt name the peak or off-peak rate a spend was billed at
CostQuote carries a rate band for a rate confirmed as the vendor's own
schedule, aggregates fold it to peak, off-peak or mixed (none when any call
has no schedule), and the TUI prints it beside the cost as 1.x does.
2026-10-04 03:39:23 -07:00
YHH 19c86553ae Merge pull request #11924 from esengine/cli/status-details
fix(tui): /status lists the work tree, the effort and the model ref as 1.x does
2026-10-04 03:36:09 -07:00
esengine 2719b42f8b fix(run): exit 4 when an untrusted folder refused the work, and write the 1.x migration notes
A headless run in a folder with no trust decision refused every edit and
command and exited 0, so scripts that relied on 1.x defaults looked
successful. It now exits 4 (3 under --fail-on-unverified); the gate and
what it allows are unchanged. docs/MIGRATING.md gains the 1.x differences:
trust prompt, untrusted-folder runs, compact-ratio range, env vars.

Refs #11493
2026-10-04 03:35:56 -07:00
esengine a15b269c7f fix(tui): a finished to-do list stays docked until the next turn starts
The panel dropped a list as soon as every item was completed, so the final
N/N never showed. It now stays through the ask panel and the idle prompt and
goes when the next turn begins; a rebuilt screen (resume, rewind) still does
not draw a list that finished before it was opened.
2026-10-04 03:35:51 -07:00
esengine 597153d377 fix(tui): a thinking block with no text draws no marker
Reasoning that is only whitespace has nothing to fold, so the transcript no
longer prints a collapsed "thought for 0s (Ctrl+O)" line for it.
2026-10-04 03:32:15 -07:00
yhh ebe758959e fix(desktop): fill the Windows icon tile so the Start menu icon matches its neighbours 2026-10-04 03:27:32 -07:00
esengine ae22f4b171 fix(tui): /status lists the work tree, the effort and the model ref as 1.x does 2026-10-04 03:25:25 -07:00
YHH 0b638eb96e Merge pull request #11534 from esengine/feat/commit-message-helper
feat(studio): draft a commit message from the staged diff, commit locally on confirm
2026-10-04 01:56:13 -07:00
esengine 7c2a81eb24 feat(studio): draft a commit message from the staged diff and commit locally on confirm
Adds a typed Commits port on the controller: ProposeCommit reads the staged
index through gitcmd, frozen as a tree object, and asks the session's model
for a conventional-commit message in a request of its own (prefix untouched,
billed as commit-message). CommitStaged writes the reviewed tree with
commit-tree and moves HEAD only if it has not moved, so an index that changes
after confirmation cannot enter the commit. Files named as secret holders or
whose added lines carry a private key are withheld from the model and need an
acknowledgement. Hooks and signing never run. Nothing pushes.

Refs #11518
2026-10-04 01:45:20 -07:00
YHH b3d091139f Merge pull request #11342 from par73e/fix/studio-glm-effort-9642
fix(studio): expose GLM reasoning effort levels
2026-10-04 01:38:46 -07:00
par73e 6913fc9765 fix(studio): address review on GLM reasoning effort
- Add the pre-5.3 GLM catalogs to shippedPresets so installs holding the
  old list are moved forward, plus upgrade tests (old shape upgrades, a
  curated catalog stays untouched).
- Declare the per-model ladder once in provider.ZhipuEffortContract and
  read it from the /effort menu, the normalizer and the request builder,
  instead of repeating the model lists across three switches.
- Cite Zhipu's Core Parameters reference for the documented levels
  (5.2's none/minimal/low/medium/xhigh folds, 5.3's forced thinking).
- Add a boot.Build effect test at the provider-request boundary covering
  the stored enabled/disabled -> EffortDisplay -> request path.
- Surface the disabled->low landing for GLM-5.3 in the /effort menu and
  document the 1M context window the added models reuse.
2026-10-04 16:28:33 +08:00
YHH 18ed0649c9 Merge pull request #11914 from esengine/fix/sidebar-open-version-twice
fix(studio): list a version a pane has open once, not under its parent and as its own row
2026-10-04 01:15:13 -07:00
YHH 90afa32c27 Merge pull request #11913 from esengine/fix/gitcmd-codeql-cut
fix(gitcmd): make the option screen a CodeQL barrier at the exec exit
2026-10-04 00:59:24 -07:00
esengine 7363b44c25 fix(studio): list a version a pane has open once, not under its parent and as its own row 2026-10-04 00:58:28 -07:00
esengine f74ae6d352 fix(gitcmd): classify every argument by the option test before it reaches the exec
CodeQL's go/command-injection models regexp matches as barriers only when the
match is the branch condition dominating each use. screen() folded the match
into an || condition and the -C directory skipped it, so alert 1014 kept its
flows through screen() and Args.
2026-10-04 00:49:46 -07:00
YHH c5b43c410c Merge pull request #11665 from Harbor404/fix/studio-11615-usage-ranges
feat(usage): add calendar ranges and precise daily details
2026-10-04 00:26:39 -07:00
Harbor404 080a8d7ac1 fix(usage): clip long rows without hiding totals 2026-10-04 15:11:03 +08:00
Harbor404 86ba9227ee fix(usage): preserve structured range validation
- keep typed field details for days and explicit ranges
- reject from/to combined with days
- treat explicit dates as zone-less calendar days
- relocate and extend usage range tests
2026-10-04 14:10:41 +08:00
YHH 9b1ec952e1 Merge pull request #11909 from esengine/fix/11905-probe-resolved-base
fix(providers): save the base URL the model-list probe answered at
2026-10-03 22:38:14 -07:00
YHH c28884609c Merge pull request #11911 from esengine/fix/rebuild-busy-saved-identity
fix(serve): settings saved while the runtime is busy no longer read as a failed rebuild
2026-10-03 22:33:29 -07:00
YHH 5f7827c9b2 Merge pull request #11906 from esengine/fix/gitcmd-arg-injection
fix(gitcmd): refuse program-running git options at the exec exit
2026-10-03 22:32:26 -07:00
YHH 711966ef31 Merge pull request #11908 from esengine/fix/no-reload-shortcut
fix(studio): leave F5 and Ctrl/Cmd+R to the page; app-window reload moves to Ctrl/Cmd+Shift+R
2026-10-03 22:26:32 -07:00
esengine fb297c3483 fix(serve): a settings save that meets a busy runtime says it was saved, by code
rebuildFailed collapsed the busy refusal into runtime.rebuild_failed with
the English sentence as detail, so the compaction, shell, sandbox,
permission, browser and MCP-load saves told a zh reader the write had
failed. It now answers runtime.saved_while_running, which the UI renders.
2026-10-03 22:16:18 -07:00
esengine 99ee5cdfe0 fix(gitcmd): refuse program-running git options at the exec exit (CodeQL go/command-injection) 2026-10-03 22:13:54 -07:00
esengine fc2f4b495c fix(providers): save the base URL the model-list probe actually answered at
A bare https://host passed the add-provider probe because {base}/v1/models
answered, while chat posts to the saved base + /chat/completions and failed.
The probe now reports the base that was proven (typed address completed with
/v1 only when the listing answered there alone, openai-style kinds only), and
add, onboarding and edit-refresh adopt it into the address field before save.
2026-10-03 22:13:18 -07:00
esengine 1a38708fda fix(studio): leave F5 and Ctrl/Cmd+R to the page; reload the app window on Ctrl/Cmd+Shift+R
F5 and Ctrl/Cmd+R reloaded the whole Studio UI from any focus, so a page
or browser-pane user pressing them lost the window state. The app window
now reloads only on Ctrl/Cmd+Shift+R (white-screen recovery, crash reload
unchanged); the browser pane reloads its own page on F5 and Ctrl/Cmd+R and
ignores the cache on the shifted chord.
2026-10-03 22:10:30 -07:00
Harbor404 6a508efac6 test(usage): isolate boot effect provider registration 2026-10-04 13:08:32 +08:00
YHH 11b0513747 Merge pull request #11903 from esengine/fix/feedback-keep-shots
fix(studio): keep feedback screenshots in the held draft
2026-10-03 22:03:25 -07:00
esengine 47d219e966 fix(studio): keep feedback screenshots in the held draft 2026-10-03 21:54:57 -07:00
YHH 72d4e9f28e Merge pull request #11711 from KHG420/codex/studio-export-json-encodings
fix(studio): export plugins using supported JSON encodings
2026-10-03 21:26:03 -07:00
Harbor404 46783d365f fix(usage): declare calendar range actions 2026-10-04 12:05:57 +08:00
YHH d486cfe71f Merge pull request #11808 from KHG420/codex/studio-remote-manifest-size
fix(install): require complete bounded remote manifests
2026-10-03 21:02:13 -07:00
xiexian 0f8dfa34f9 fix(studio): export plugins using supported JSON encodings 2026-10-04 11:58:15 +08:00
xiexian dabc3481d2 fix(install): require complete bounded remote manifests 2026-10-04 11:51:27 +08:00
YHH 0195f8241a Merge pull request #11897 from esengine/fix/model-switch-not-default
fix(serve): a session's model switch no longer rewrites default_model
2026-10-03 20:51:23 -07:00
esengine bc13eddb53 fix(serve): a session's model switch no longer rewrites default_model
POST /model persisted every switch as the user's default model, so changing
the model inside one conversation silently changed what every new session and
the next launch started on. The typed /model command, the terminal UI and ACP
never did this; only the HTTP picker path did.

The request now carries an explicit `default` flag. The Settings default-model
row and the first-run model choice send it; the composer picker does not, so
it changes only that session. A pane resolving through the hub's resolver
still never writes this machine's config.
2026-10-03 20:40:02 -07:00
YHH d98832f695 Merge pull request #11816 from esengine/feat/fix-11785
fix(mcp): redact endpoint credentials fail-closed in every display, preview and export surface
2026-10-03 19:32:40 -07:00
Harbor404 310ffda281 fix(usage): align source bars with chart tokens 2026-10-04 10:30:45 +08:00
YHH d7744a1616 Merge pull request #11895 from esengine/fix/powershell-parser-cold-start
fix(bash): give the PowerShell delete parser a cold-start budget
2026-10-03 19:22:45 -07:00
esengine 0da518ce84 fix(mcp): redact endpoint credentials fail-closed in every display, preview and export surface
One structural rule (secrets.RedactEndpoint / CredentialKey / RedactArgs)
replaces mcpsetup.RedactURL's query-only masking. Userinfo and fragments are
dropped or masked, query and fragment keys are decoded before classification,
unparseable endpoints render as <redacted>, and transport diagnostics keep
error identity while omitting peer-supplied prose.

Operational values (install, approval fingerprints, persistence) are untouched;
preview and plan responses add separate display fields that the desktop mirror
and confirmation UI render.

Rebased onto the launcher-aware NameFromArgv: names derive from the original
argv but never from a value the display projection masked, so build-flag values
such as `go run -exec` still resolve. Install classification reads the typed
HTTP status instead of sniffing the server name out of the message.

Fixes #11785
2026-10-03 19:19:58 -07:00
esengine 358fb4cff2 fix(bash): give the PowerShell delete parser a cold-start budget separate from per-call latency 2026-10-03 19:07:06 -07:00
YHH 7aeae1990d Merge pull request #11824 from esengine/feat/fix-images-fixture
test(boot): fix the shell used by the restart image effect test
2026-10-03 18:52:49 -07:00
YHH b2db65b417 Merge pull request #11831 from esengine/feat/fix-queue-withdraw
fix(studio): clear the composer on submit and restore a withdrawn queued message exactly once
2026-10-03 18:51:52 -07:00
YHH 4ffed1b50c Merge branch 'studio' into feat/studio-sponsor 2026-10-03 18:47:02 -07:00
YHH 43372e6ce7 Merge pull request #11893 from esengine/fix/focus-test-flaky
test(studio): wait for the plugin recovery focus move
2026-10-03 18:04:28 -07:00
esengine b650e362df test(studio): wait for the plugin recovery focus move instead of asserting it immediately
The primary button is focused from a passive effect that runs after the
element is committed, so asserting document.activeElement right after a
findBy* resolves races that effect. Use waitFor, as settings-layers does.
2026-10-03 17:37:55 -07:00
YHH 5bfdfb0bd6 Merge pull request #11891 from esengine/fix/release-credit-owner
fix(release): never credit the repository owner in release notes
2026-10-03 17:21:59 -07:00
YHH 3c060de3e2 Merge pull request #11890 from esengine/docs/studio-2.27.0-notes
docs(release): prepare Studio 2.27.0 notes
2026-10-03 17:14:40 -07:00
esengine de789afafb fix(release): never credit the repository owner in release notes 2026-10-03 17:12:08 -07:00
esengine c83f5931fc docs(release): prepare Studio 2.27.0 notes 2026-10-03 17:07:07 -07:00
YHH 028179b37f Merge pull request #11889 from esengine/perf/boot-test-close-catalogs-before-tempdir
test(boot): close process-global catalogs before the temp dir is removed
2026-10-03 16:31:54 -07:00
esengine 880dcc5b64 test(boot): close process-global catalogs before the temp dir is removed
testenv.TempDir retries RemoveAll for two seconds when a file stays held.
On Windows the usage and history SQLite catalogs, which the shared helpers
left open, held a file under nearly every test's directory, so each of those
tests paid the full retry budget at teardown. Closing the catalogs from the
helper's own cleanup, which runs before the removal, makes it converge on
the first attempt.
2026-10-03 16:22:09 -07:00
YHH 8576a0b0ff Merge pull request #11740 from KHG420/codex/studio-unicode-skill-inventory
fix(studio): align package skill inventory with runtime names
2026-10-03 15:53:09 -07:00
YHH 37056a8dba Merge pull request #11865 from KHG420/codex/studio-go-mcp-names
fix(studio): unify MCP launcher name inference
2026-10-03 15:52:29 -07:00
xiexian a078f8e30f fix(studio): unify MCP launcher name inference 2026-10-04 06:38:46 +08:00
YHH 875dd8e67a Merge pull request #11569 from KHG420/codex/studio-compatible-command-example
docs(ecosystem): add compatible command example and host proof
2026-10-03 15:19:23 -07:00
YHH 558b9cbbb2 Merge pull request #11814 from KHG420/codex/studio-theme-import-ignored-files
fix(studio): reject mixed theme archive selections
2026-10-03 15:18:08 -07:00
YHH 461e03770f Merge pull request #11811 from KHG420/codex/studio-theme-asset-freshness
fix(studio): revalidate theme assets with content ETags
2026-10-03 15:16:35 -07:00
YHH dbc79359c0 Merge pull request #11787 from KHG420/codex/studio-mcp-endpoint-transport-path
fix(studio): infer MCP transport from endpoint path segments
2026-10-03 15:14:38 -07:00
YHH 5dc827a1c0 Merge pull request #11821 from KHG420/codex/studio-publish-kind-keys
fix(studio): share keyboard navigation for market radios
2026-10-03 15:06:26 -07:00
YHH 9de8fe7374 Merge pull request #11886 from esengine/perf/boot-scan-limit
test(boot): make the workspace scan limit injectable (refs #11885)
2026-10-03 14:02:07 -07:00
YHH 075db5672f Merge pull request #11888 from esengine/fix/shellrun-quiet-timeout-test
test(shellrun): stop the quiet-timeout test racing interpreter startup
2026-10-03 13:52:23 -07:00
YHH 3dd8fd4dfa Merge pull request #11887 from esengine/perf/bash-probe-memo
perf(sandbox): remember a bash.exe that already passed the launch probe (refs #11885)
2026-10-03 13:51:38 -07:00
esengine 4a82273735 perf(sandbox): do not relaunch bash -c true for a bash.exe already proven under the same size and mtime
A successful Windows bash probe is remembered per executable identity (path, size, mtime) for the life of the process; failures are never remembered. Every boot.Build used to spawn Git bash for it.
2026-10-03 13:45:23 -07:00
esengine 65f5a0c6f3 test(boot): make the workspace scan limit injectable so the over-limit case needs 65 files, not 50,001
agent.Options.WorkspaceScanLimit (zero keeps the 50,000 production bound) is threaded from boot.Options. The incomplete-prose-scan effect test now sets it to 64 and writes 65 files instead of 50,001.
2026-10-03 13:44:58 -07:00
esengine 7e469c1c59 test(shellrun): inject the child in the quiet-timeout test so interpreter startup cannot race the deadline
The deadline starts at launch, so with a real interpreter the test measured startup time. Under load Git bash needed more than 500 ms and the pre-deadline write never landed.
2026-10-03 13:39:36 -07:00
YHH e190049a70 Merge branch 'studio' into codex/studio-theme-import-ignored-files 2026-10-03 13:11:29 -07:00
YHH a3fd97ab74 Merge pull request #11810 from KHG420/codex/studio-theme-import-lock
fix(studio): serialize theme import feedback
2026-10-03 13:00:46 -07:00
xiexian b8e25a4bb4 fix(studio): reject mixed theme archive selections 2026-10-04 03:56:56 +08:00
xiexian 98e1ebe28f fix(studio): serialize theme import feedback 2026-10-04 03:45:20 +08:00
YHH 963fd1eec1 Merge branch 'studio' into codex/studio-unicode-skill-inventory 2026-10-03 12:30:55 -07:00
YHH 09ba9cddc5 Merge pull request #11882 from esengine/fix/flaky-shellrun-quiet-timeout
test(shellrun): give the quiet-timeout case a deadline that outlasts PowerShell startup
2026-10-03 12:30:23 -07:00
YHH 599ba7cb77 Merge pull request #11883 from esengine/fix/flaky-remember-permission-waits
test(boot): bound permission-rule contention by writer count and process readiness by testenv.Budget
2026-10-03 12:27:25 -07:00
YHH 4e41da5a81 Merge pull request #11881 from esengine/fix/flaky-control-wait-budget
test(control): wait for turn events within testenv.Budget instead of fixed 2-10s
2026-10-03 12:26:21 -07:00
esengine 8e87a1557b test(boot): bound permission-rule contention by writer count and process readiness by testenv.Budget
TestRememberPermissionRuleSerializesConcurrentWriters queued 32 writers on a
lock that waits 5s, so a slow Windows filesystem ran the last waiters out of
time (lock timeout at 5.08s). The cross-process variant had the same shape:
each helper waited a fixed 5s for the others to spawn. Eight writers still
interleave, and the readiness waits now read the shared hang budget.
2026-10-03 12:07:30 -07:00
esengine 39fddef450 test(shellrun): give the quiet-timeout case a deadline that outlasts PowerShell startup
The deadline is armed at launch, so a 500ms budget races interpreter startup:
on a loaded Windows runner PowerShell had not printed anything when it
fired, and the test reported the pre-deadline write as lost. PowerShell now
gets a 10s deadline named for what it has to outlast; POSIX shells keep 500ms.
2026-10-03 12:05:48 -07:00
esengine 8f61e66cf1 test(control): wait for turn events within testenv.Budget instead of fixed 2-10s
Hang-catching waits in the control tests were hard-coded to 2, 3, 5 or 10
seconds. A whole turn through the real agent loop can exceed that on a
loaded Windows runner, so TestTurnOutcomeClassifiesPerseverationPause failed
with 'timed out waiting for TurnDone' on a run that passed on rerun. They now
read testenv.Budget(t), which is the single named bound for a wait that only
exists to report a hang.
2026-10-03 12:05:09 -07:00
xiexian 00c97a6036 fix(studio): align package skill inventory with runtime names 2026-10-04 02:43:46 +08:00
YHH 94d1a293f5 Merge branch 'studio' into codex/studio-compatible-command-example 2026-10-03 11:22:13 -07:00
YHH c66775519f Merge branch 'studio' into codex/studio-theme-asset-freshness 2026-10-03 11:22:04 -07:00
YHH a87f125b27 Merge pull request #11622 from Harbor404/fix/11619-ssh-login-path
fix(remote): cancel SSH session opening during bootstrap
2026-10-03 11:18:31 -07:00
Harbor404 c13fad9b1c fix(remote): limit session-open cancellation side effects 2026-10-04 02:04:27 +08:00
YHH 9a2a6bb03b Merge pull request #11857 from esengine/feat/fix-11846
fix(memory): back up legacy memory files before the V2 migration rewrites them
2026-10-03 10:44:08 -07:00
YHH f6b1c6b7f7 Merge pull request #11788 from KHG420/codex/studio-sdk-host-test-guide
docs(sdk): explain local tests and custom host shutdown
2026-10-03 10:32:30 -07:00
esengine 8787ef30a2 fix(memory): back up legacy memory files before the V2 migration rewrites them 2026-10-03 10:30:54 -07:00
xiexian 024b50c173 docs(sdk): explain local tests and custom host shutdown 2026-10-04 01:18:05 +08:00
xiexian 65c7316669 fix(studio): share market radio keyboard navigation 2026-10-04 01:10:46 +08:00
xiexian a4b5a9a485 docs(ecosystem): add compatible command example and host proof 2026-10-04 01:02:54 +08:00
YHH 26ab215cbf Merge pull request #11749 from KHG420/codex/studio-local-mcp-pending-scope
fix(studio): retain local scope for pending MCP installs
2026-10-03 09:40:19 -07:00
YHH a9de4f4f3d Merge pull request #11879 from esengine/feat/fix-device-ordinal-pr0
fix(serve): reuse the lowest free ordinal for Internet controllers
2026-10-03 09:37:41 -07:00
YHH d82fa6aee1 Merge pull request #11477 from KHG420/codex/studio-update-lock-feedback
fix(studio): show disabled update navigation and cover lock release
2026-10-03 09:30:00 -07:00
YHH 34f7203713 Merge pull request #11578 from KHG420/codex/studio-theme-author-guide
docs(studio): add current theme author guide and starter
2026-10-03 09:28:25 -07:00
YHH 6a30dfaca0 Merge pull request #11877 from esengine/feat/fix-snapshot-bound-test
test(boot): bound snapshot walk goroutines by pool size, not total growth
2026-10-03 09:26:56 -07:00
esengine 8e24f4c89c fix(serve): reuse the lowest free ordinal for Internet controllers
cloudPresence kept a monotonic counter, so every reconnect of the same phone
took the next number. Allocate the lowest ordinal not held by a live
connection instead; it stays fixed for the connection's lifetime.
2026-10-03 09:23:51 -07:00
xiexian 53a27ecb58 fix(studio): show disabled update navigation and cover lock release 2026-10-04 00:19:58 +08:00
YHH 8e5a124070 Merge branch 'studio' into fix/studio-11615-usage-ranges 2026-10-03 09:17:32 -07:00
esengine cdab4ade0b test(boot): bound walk goroutines by pool size, not total goroutine growth
The wide-workspace effect test compared total goroutine growth against a
magic 64, which host background work (sqlite watchers in the history
catalog) pushed past under load. Count only goroutines inside the walkers
and bound them by pool size times concurrent walk kinds; assert none
survive the turn.
2026-10-03 09:13:38 -07:00
YHH 7b09c445c5 Merge pull request #11869 from esengine/feat/fix-11838
fix(compaction): name why an automatic fold failed instead of "folded nothing"
2026-10-03 09:13:05 -07:00
YHH f87f3acd8f Merge pull request #11784 from KHG420/codex/studio-market-install-result-recovery
fix(studio): recover failed market install results
2026-10-03 09:09:16 -07:00
YHH bc73c23120 Merge pull request #11716 from KHG420/codex/studio-private-publish-summary
fix(studio): explain private market drafts before saving
2026-10-03 09:07:11 -07:00
Harbor404 419899ab5e fix(remote): cancel while opening an SSH session 2026-10-04 00:05:22 +08:00
xiexian 29791a36c6 docs(studio): add current theme author guide and starter 2026-10-04 00:04:23 +08:00
Harbor404 6d4b5dcb6a fix(usage): label sources without display names 2026-10-04 00:03:42 +08:00
YHH 949d237c34 Merge pull request #11875 from esengine/feat/fix-writepaths-win-test
test(builtin): compare ambiguous writer grant targets against the resolved root
2026-10-03 08:58:52 -07:00
YHH 1e6b185c2d Merge pull request #11651 from KHG420/codex/studio-plugin-failure-recovery
fix(studio): recover failed plugin installs through fresh previews
2026-10-03 08:55:22 -07:00
YHH 960a5a0105 Merge pull request #11796 from KHG420/codex/studio-package-refresh-recovery
fix(studio): recover from extension inventory read failures
2026-10-03 08:54:27 -07:00
esengine 6fe0755a24 fix(compaction): name why an automatic fold failed instead of "folded nothing"
A fold that was refused or failed ended with an aborted card reading
"folded nothing" and a blocked receipt whose code was empty; the cause lived
only in a sentence. Checkpoint rejections now carry a code per rule, summary
failures are classified by sentinel (truncated, timeout, input too large,
context changed, provider failure), and the code rides both the receipt and
the aborted compaction frame. The Studio card shows a localized cause, and
keeps its before/after token pair on one line when the row wraps.
2026-10-03 08:47:02 -07:00
xiexian c023b9f117 fix(studio): explain private market drafts before saving 2026-10-03 23:45:25 +08:00
esengine 9e2664b8b9 test(builtin): compare ambiguous writer grant targets against the resolved root
The Windows runner's temp dir is an 8.3 short name. The grant target for an
ambiguous path is resolved to the long form, so the expectation built from the
unresolved root never matched.
2026-10-03 08:45:06 -07:00
YHH 482f93af21 Merge pull request #11874 from esengine/feat/fix-market-own-review-test
test(studio): drive own-review regression through the real inventory reload
2026-10-03 08:38:35 -07:00
YHH fdc2b8d94b Merge pull request #11269 from zcxads666/fix-studio-inherited-subagent-effort
fix(boot): let inherited subagent effort yield to execution model (studio)
2026-10-03 08:34:56 -07:00
esengine 25b43266b7 test(studio): drive own-review regression through the real inventory reload
The regression held row and button references across the refetch that
PackageList.submit now triggers after a successful review submission, and
its deferred submitMarket fixtures never updated the inventory myMarket
returns. Query live rows after each reload and delegate to the mock's real
submitMarket so the reloaded inventory reflects the new status.
2026-10-03 08:29:18 -07:00
xiexian c0856e8231 fix(studio): revalidate theme assets with content ETags 2026-10-03 23:26:41 +08:00
zcxads666 0d9c11d0a5 fix: keep inherited subagent effort within the execution model's declared capability 2026-10-03 08:20:03 -07:00
YHH bf03b2eafb Merge pull request #11847 from KHG420/codex/studio-mcp-scope-keyboard
fix(studio): support keyboard MCP installation locations
2026-10-03 08:03:04 -07:00
YHH 8fd6d1ae63 Merge pull request #11568 from esengine/feat/share-port
feat(share): let the phone-access LAN link use a fixed port
2026-10-03 08:01:19 -07:00
YHH 68a311ad02 Merge pull request #11807 from KHG420/codex/studio-install-confirmation-lock
fix(studio): expose market confirmation busy and error states
2026-10-03 07:46:12 -07:00
YHH 97c35fbd83 Merge pull request #11845 from KHG420/codex/studio-publish-enter-submit
fix(studio): submit publishing drafts with Enter
2026-10-03 07:38:34 -07:00
YHH 5495a4b404 Merge pull request #11580 from KHG420/codex/studio-market-review-pending
fix(studio): serialize own-package review submissions
2026-10-03 07:34:00 -07:00
YHH 95985b806a Merge pull request #11800 from KHG420/codex/studio-publisher-feedback-semantics
fix(studio): add semantics to publisher feedback
2026-10-03 07:33:09 -07:00
YHH 99ddf11b31 Merge pull request #11813 from KHG420/codex/studio-theme-preview-recovery
fix(studio): retry previews after theme inventory refresh
2026-10-03 07:30:53 -07:00
YHH ed24735e30 Merge pull request #11832 from KHG420/codex/studio-sdk-provider-lifecycle
test(studio): cover installed SDK provider streams at boot
2026-10-03 07:30:37 -07:00
xiexian 4ddf8bd730 fix(studio): expose market confirmation busy and error states 2026-10-03 22:29:36 +08:00
xiexian d974353edf fix(studio): submit publishing drafts with Enter 2026-10-03 22:18:18 +08:00
YHH 70b380471e Merge pull request #11556 from KHG420/codex/studio-fullsidecar-demo-guide
docs(sdk): align fullsidecar demo guidance
2026-10-03 07:16:37 -07:00
YHH 8306c2f66a Merge pull request #11823 from esengine/feat/fix-11815
fix(session): let sessions that write disjoint paths of one workspace proceed in parallel
2026-10-03 07:06:44 -07:00
YHH 8c25d59ac3 Merge pull request #11812 from KHG420/codex/studio-theme-read-recovery
fix(studio): retain themes when inventory reads fail
2026-10-03 07:05:47 -07:00
YHH 5faff41805 Merge pull request #11792 from KHG420/codex/studio-conformance-scratch-cleanup
test(extension): clean conformance scratch before exiting
2026-10-03 07:01:40 -07:00
YHH c249eb6500 Merge pull request #11818 from KHG420/codex/studio-install-source-labels
fix(studio): name installation source inputs accessibly
2026-10-03 06:59:09 -07:00
YHH 8bc1d025a0 Merge pull request #11738 from KHG420/codex/studio-export-whole-env-refs
fix(pluginpkg): redact literals mistaken for environment references
2026-10-03 06:58:10 -07:00
xiexian 5350d3ac34 fix(studio): support keyboard MCP installation locations 2026-10-03 21:51:35 +08:00
YHH 85b5e76d4d Merge pull request #11819 from KHG420/codex/studio-plugin-toggle-reload
fix(studio): retain plugin switch reload outcomes
2026-10-03 06:48:26 -07:00
YHH 9b03193a00 Merge pull request #11809 from KHG420/codex/studio-mcp-operation-errors
fix(studio): show current MCP operation diagnostics
2026-10-03 06:47:36 -07:00
YHH 030b625208 Merge pull request #11805 from KHG420/codex/studio-mcp-inspection-source-lock
fix(studio): keep MCP inspection input stable while reading
2026-10-03 06:46:42 -07:00
YHH b2225b6d72 Merge pull request #11781 from KHG420/codex/studio-market-vote-signout-recovery
fix(studio): recover signed-out market vote writes
2026-10-03 06:41:16 -07:00
YHH ec5f629213 Merge pull request #11866 from esengine/feat/fix-studio-red
fix(shell): only a leading tilde makes a delete target non-literal
2026-10-03 06:32:50 -07:00
esengine ed6b2ea02f fix(session): resolve delegated writes before the grant check so valid targets are not refused 2026-10-03 06:31:29 -07:00
esengine 20f452eb3f fix(session): claim the paths the writers use, close the Windows alias gap and avoid cross-session deadlock 2026-10-03 06:31:29 -07:00
esengine 0a009c71dc fix(session): let sessions that write disjoint paths of one workspace proceed in parallel 2026-10-03 06:31:12 -07:00
YHH c8dcf82efd Merge pull request #11850 from KHG420/codex/sdk-ui-result-ack
fix(sdk): require explicit UI result acknowledgements
2026-10-03 06:21:19 -07:00
YHH 7d630f5ff0 Merge pull request #11841 from esengine/feat/audit-effort-switch
fix(boot): a subagent that names the parent's model inherits the parent's effort
2026-10-03 06:11:44 -07:00
xiexian df5b824df9 fix(sdk): require explicit UI result acknowledgements 2026-10-03 21:05:36 +08:00
esengine 3589efed7b fix(shell): judge delete targets by expansion shape, accept only 8.3 short-name tildes
A tilde inside a Windows 8.3 short-name segment (RUNNER~1) of a drive-letter
path is an ordinary character; any other tilde can expand to a home
directory and stays refused as non-literal. Unescaped braces are refused
too, since a brace word expands to several targets.

Also give the PowerShell delete-analysis subprocess test a hang guard
instead of a 15 s latency assertion that fails on loaded runners.
2026-10-03 06:00:55 -07:00
YHH f66930fd12 Merge pull request #11840 from esengine/feat/fix-11820
fix(state): bound the workspace snapshot walk so goroutines and threads stay limited
2026-10-03 05:59:51 -07:00
esengine 2a3c2cd1f9 fix(boot): a subagent that names the parent's model inherits the parent's effort 2026-10-03 05:53:42 -07:00
YHH 87f1912c1f Merge pull request #11854 from esengine/feat/fix-compact-ratio
fix(cli): validate compact-ratio against the config layer bounds
2026-10-03 05:52:10 -07:00
YHH 5be2913263 Merge pull request #11862 from esengine/feat/ops-events-workflow
ci(ops): register the ops events relay deploy workflow on the default branch
2026-10-03 05:31:04 -07:00
esengine 1e1dbaf07c ci(ops): register the ops events relay deploy workflow on the default branch
workflow_dispatch workflows must exist on the default branch; the job itself only runs from platform, like the other worker deploys.
2026-10-03 05:14:17 -07:00
xiexian 16aeb9124d fix(studio): add semantics to publisher feedback 2026-10-03 19:56:39 +08:00
xiexian 74d977a5c9 fix(studio): recover failed market install results 2026-10-03 19:48:51 +08:00
YHH b7682ae672 Merge pull request #11835 from KHG420/codex/studio-sdk-response-format
fix(sdk): validate provider response format types
2026-10-03 04:36:21 -07:00
YHH fca92a5675 Merge pull request #11826 from KHG420/codex/studio-fullsidecar-null-arguments
fix(sdk): handle null arguments in fullsidecar demo
2026-10-03 04:24:13 -07:00
YHH 4fdaa75397 Merge pull request #11828 from KHG420/codex/studio-sdk-tool-argument-presence
fix(sdk): require tool argument presence
2026-10-03 04:15:13 -07:00
YHH 474ba205d1 Merge pull request #11802 from KHG420/codex/studio-sdk-cancelled-host-calls
fix(sdk): stop already-cancelled host requests before writing
2026-10-03 04:12:43 -07:00
YHH 5717f930f0 Merge pull request #11798 from KHG420/codex/studio-sdk-shutdown-callback-recovery
fix(sdk): recover shutdown callback panics
2026-10-03 04:09:32 -07:00
YHH 28c45b59d0 Merge pull request #11829 from esengine/feat/docs-change-discipline
docs: add the change discipline rules to REASONIX.md
2026-10-03 03:56:40 -07:00
xiexian 2e9045a300 fix(sdk): include externalized reads in the intercept budget 2026-10-03 18:20:33 +08:00
esengine 9692e499bb fix(cli): validate compact-ratio against the config layer bounds 2026-10-03 03:20:23 -07:00
esengine 801fd6b216 fix(state): publish immutable observation objects with a non-replacing link so concurrent observers cannot race the rename 2026-10-03 03:19:33 -07:00
YHH e4b862cf77 Merge pull request #11804 from KHG420/codex/studio-copied-mcp-launcher
fix(studio): recognize copied MCP add launcher paths
2026-10-03 03:14:29 -07:00
xiexian 8ecfa8c084 fix(studio): infer MCP transport from endpoint path segments 2026-10-03 18:13:11 +08:00
YHH e01622522d Merge pull request #11793 from KHG420/codex/studio-sdk-notification-close
fix(sdk): synchronize notification queue shutdown
2026-10-03 03:05:52 -07:00
xiexian cf490285b0 fix(sdk): cancel provider contexts when streams finish 2026-10-03 18:02:43 +08:00
Will ff8e467102 test(e2ebench): prove every declared-solvable verification-stress task can pass
The reference-solution guard fired only for the train corpus. For every other corpus a missing solution/ was a silent skip, so a grader that can never pass stayed indistinguishable from a task with no solution - the case the rule above it exists to prevent. #11380 supplied the ten vs-pos solutions; this extends the guard to the whole verification-stress corpus and supplies the ten tasks it was still skipping.

The e2e suite keeps the skip, and the skip message now says why: its handwritten tasks do not commit a reference solution.

Verified: go test ./cmd/e2ebench -run TestCorpusGradersPassTheReferenceSolution is green across e2e, verification-stress and train. Removing any single solution/ fails the guard at corpus_test.go:254 (checked by hand for vs-neu-cfgkey), so the added solutions are what the guard grades.
2026-10-03 17:51:55 +08:00
YHH 744fbd4f33 Merge pull request #11759 from esengine/feat/tray-language
fix(studio): pass the system language to the kernel host so the tray follows it
2026-10-03 02:40:15 -07:00
YHH 3b8817d29b Merge pull request #11801 from KHG420/codex/studio-uv-global-options
fix(studio): derive MCP names through uv global options
2026-10-03 02:37:28 -07:00
YHH f2a0893b07 Merge pull request #11799 from KHG420/codex/studio-node-mcp-option-values
fix(studio): skip Node option values in MCP names
2026-10-03 02:36:30 -07:00
YHH d0d7631a7d Merge pull request #11779 from KHG420/codex/studio-mcp-command-continuations
fix(studio): parse continued MCP setup commands
2026-10-03 02:34:07 -07:00
esengine 78069f638a fix(plugins): re-verify the approved bytes at publication and keep non-Windows replacement semantics 2026-10-03 02:22:21 -07:00
esengine b441b0da8b fix(state): bound the workspace snapshot walk so goroutines and threads stay limited 2026-10-03 00:33:21 -07:00
esengine f54da89d15 fix(plugins): bind the published plugin bytes to the approved preview and keep the publication failure typed 2026-10-03 00:31:46 -07:00
xiexian 54c8e7ab68 feat(sdk): add installable offline provider example 2026-10-03 15:16:42 +08:00
xiexian 74b6705484 fix(sdk): validate provider response format types 2026-10-03 14:37:10 +08:00
xiexian a5fe9d6b5f test(studio): cover installed SDK provider streams at boot 2026-10-03 14:26:05 +08:00
esengine bce7224270 fix(studio): clear the composer on submit and restore a withdrawn queued message exactly once 2026-10-02 23:19:21 -07:00
esengine 647c1baa5b test(host): call assemble with the startup phase recorder in the tray language effect test 2026-10-02 23:19:01 -07:00
esengine 1bc7fc11e9 docs: add the change discipline rules to REASONIX.md 2026-10-02 22:55:56 -07:00
xiexian e741ffe34b fix(sdk): require tool argument presence 2026-10-03 13:38:53 +08:00
xiexian 3b4a2a7619 fix(sdk): handle null arguments in fullsidecar demo 2026-10-03 13:27:39 +08:00
YHH 6e1a0cccaa Merge pull request #11786 from esengine/feat/fix-11782
fix(shell): refuse recursive deletes whose target is not a literal path inside the granted roots
2026-10-02 22:04:08 -07:00
par73e a0cc22bfc0 fix(studio): expose GLM 5.2 and 5.3 reasoning effort levels 2026-10-03 13:02:02 +08:00
esengine 3552dc1412 test(boot): fix the shell used by the restart image effect test 2026-10-02 22:00:06 -07:00
esengine 0b76ca0f63 fix(plugins): publish a replaced plugin directory without renaming a watched tree on Windows 2026-10-02 21:58:24 -07:00
esengine 265dd7a393 fix(i18n): treat the C and POSIX locales as an explicit English choice and test the tray language end to end 2026-10-02 21:26:52 -07:00
xiexian b407cdceb0 fix(studio): retain plugin switch reload outcomes 2026-10-03 12:21:02 +08:00
xiexian 1d9b15589b fix(studio): name installation source inputs accessibly 2026-10-03 11:57:41 +08:00
YHH f9344a7eed Merge pull request #11760 from esengine/feat/startup-handshake
feat(host): log elapsed milliseconds per startup phase
2026-10-02 20:26:57 -07:00
xiexian 0d2893219f fix(studio): retry previews after theme inventory refresh 2026-10-03 10:57:36 +08:00
xiexian f4bd08549a fix(studio): retain themes when inventory reads fail 2026-10-03 10:48:18 +08:00
xiexian d27be95211 fix(studio): show current MCP operation diagnostics 2026-10-03 10:09:34 +08:00
xiexian 5a10bb1d7f fix(studio): keep blocked market previews out of confirmation 2026-10-03 09:30:11 +08:00
YHH cc5d768001 Merge pull request #11803 from esengine/feat/fix-prev-resp
fix(responses): fall back to full history when a relay rejects previous_response_id
2026-10-02 18:28:13 -07:00
YHH aee132a2f1 Merge pull request #11780 from esengine/feat/fix-11776
fix(evidence): do not demand verification after supporting-only edits when no checks exist
2026-10-02 18:27:16 -07:00
esengine 34f599dcc0 test(shellsafe): cover the delete classification inside its own package 2026-10-02 18:27:03 -07:00
xiexian 5bdaed66d1 fix(studio): keep MCP inspection input stable while reading 2026-10-03 09:13:14 +08:00
xiexian 4ec2f582c2 fix(studio): recognize copied MCP add launcher paths 2026-10-03 09:04:15 +08:00
esengine 3c0a655e90 fix(responses): keep continuation after an expired response id 2026-10-02 18:00:37 -07:00
esengine 813e6932ed fix(evidence): require every mutated path to be a prose path inside the observed root 2026-10-02 17:58:22 -07:00
YHH 11a4734d07 Merge pull request #11504 from Harbor404/fix/powershell-shell-probe
fix(shell): skip a PowerShell Store alias that will not start; record which ran
2026-10-02 17:58:09 -07:00
esengine 6ec0de92be fix(responses): fall back to full history when a relay rejects previous_response_id
A relay that rejected the continuation field with a message the client did not recognise surfaced the HTTP 400 to the user, and the stored response id was kept so the next turn sent the same rejected request. Any HTTP 400 on a request that carried previous_response_id now retries once with the full history; a completed recovery stops sending the field for the client's lifetime, a failed one surfaces its own error naming the attempt.
2026-10-02 17:57:57 -07:00
YHH 35c8104af2 Merge pull request #11546 from Harbor404/feat/effort-model-same-switch-fastpath
perf(studio): skip rebuilds for unchanged model or effort
2026-10-02 17:57:12 -07:00
xiexian cf3460a48f fix(sdk): stop already-cancelled host requests before writing 2026-10-03 08:47:02 +08:00
xiexian a8a632e775 fix(studio): derive MCP names through uv global options 2026-10-03 08:34:35 +08:00
esengine 5069d522bb test(shell): correct the glob cause and make the backslash root case Windows-only 2026-10-02 17:30:05 -07:00
xiexian ee24b43d47 fix(studio): skip Node option values in MCP names 2026-10-03 08:15:19 +08:00
xiexian 92c6944d9b fix(sdk): recover shutdown callback panics 2026-10-03 08:07:59 +08:00
xiexian d52ea1e1d1 fix(studio): recover from extension inventory read failures 2026-10-03 07:59:04 +08:00
esengine 412dd1c865 test(shellparse): cover the delete analysis inside its own package and document the workspace-root refusal 2026-10-02 16:56:38 -07:00
esengine 0ef669e872 style(agent): use the shared receiver name for the prose scan check 2026-10-02 16:28:12 -07:00
xiexian 431dd159a8 fix(sdk): synchronize notification queue shutdown 2026-10-03 07:05:17 +08:00
esengine 72a59c76d4 fix(shell): judge only recursive deletes, attribute each refusal, and close the structural bypasses found in review
Revision after the security review: ordinary commands with dynamic executable names, non-recursive deletes and non-delete wrappers launch again; each refusal carries its own cause and remedy; bounded compound sequences with literal targets inside the granted roots are allowed; default-parameter, pipeline, alias, .NET, find, xargs, cmd and static interpreter payload deletes are judged; symlink components are resolved before dot-dot cleaning; the session temp root is a granted scope; the PowerShell parser is reused across calls; bilingual copy for the new codes.
2026-10-02 16:00:21 -07:00
esengine 83f13a3b1c Merge branch 'studio' of https://github.com/esengine/DeepSeek-Reasonix into HEAD 2026-10-02 15:59:59 -07:00
esengine 93c5419f94 test(evidence): make the prose-only tests independent of fixture files and filesystem case 2026-10-02 15:59:52 -07:00
Harbor404 93bb4eb606 Revert "fix(cli): align plugin doctor root diagnostics"
This reverts commit 41eeae6cd0.
2026-10-03 06:56:15 +08:00
Harbor404 791eca3111 Revert "fix(cli): align plugin doctor root diagnostics"
This reverts commit 73431b67e1.
2026-10-03 06:52:43 +08:00
xiexian b32768b6e0 test(extension): clean conformance scratch before exiting 2026-10-03 06:41:20 +08:00
YHH 7d5bcfd5a9 Merge pull request #11790 from esengine/feat/studio-red
fix(cli): print package warnings before plugin doctor rejects an agent root
2026-10-02 15:39:31 -07:00
esengine b8af495238 fix(cli): print package warnings before plugin doctor rejects an agent root
Doctor now validates declared agent roots fatally, which the warning test added by the root-diagnostics change did not expect, so the two merged changes failed together. The warnings are printed first and the root rejection keeps its exit status.
2026-10-02 15:28:27 -07:00
Harbor404 41eeae6cd0 fix(cli): align plugin doctor root diagnostics 2026-10-03 05:57:48 +08:00
Harbor404 fa5816d74e chore(merge): sync with latest studio 2026-10-03 05:57:46 +08:00
Harbor404 73431b67e1 fix(cli): align plugin doctor root diagnostics 2026-10-03 05:56:05 +08:00
Harbor404 38def5abc8 chore(merge): sync with latest studio 2026-10-03 05:53:54 +08:00
esengine 9d4f5ab8d1 test(evidence): cover prose-only waiver edge cases through boot.Build 2026-10-02 14:27:50 -07:00
esengine 5834500883 fix(shell): refuse recursive deletes whose target is not a literal path inside the granted roots
Approval alone did not stop a recursive delete: in unattended Auto and YOLO a PowerShell sequence whose first assignment failed non-terminally went on to delete the real home directory. A pre-launch gate now parses the command (bash AST, PowerShell parser run as data) and refuses recursive deletes whose target is the home directory, a drive or filesystem root, a workspace ancestor, an expansion, variable or glob, or outside the workspace and granted roots, with a typed shell.destructive_target code; parser failure is shell.analysis_unknown. The gate runs after approval and before every launch route.
2026-10-02 14:26:53 -07:00
YHH 3068d41e3d Merge pull request #11566 from KHG420/codex/studio-market-version-draft
feat(studio): prepare market release drafts from own packages
2026-10-02 14:16:34 -07:00
YHH 02d96f2b8b Merge pull request #11506 from Harbor404/fix/disabled-tools-followups
fix(mcp): preserve disabled tool policy
2026-10-02 14:14:46 -07:00
esengine 20ad469a89 wip: waive verification only in prose-only workspaces 2026-10-02 14:00:50 -07:00
Harbor404 ffc9c3b738 docs(tool): clarify disabled MCP comments 2026-10-03 04:58:46 +08:00
Harbor404 9200563b99 docs(shell): trim review comments 2026-10-03 04:57:05 +08:00
Harbor404 e5e30b6a99 test(boot): filter shell fallback notices 2026-10-03 04:55:28 +08:00
Harbor404 88c3af6913 chore(merge): sync with latest main 2026-10-03 04:53:47 +08:00
Harbor404 d90d071453 test(boot): isolate disabled MCP effect build 2026-10-03 04:53:18 +08:00
Harbor404 4c5df4360f chore(merge): sync with latest main 2026-10-03 04:52:52 +08:00
Harbor404 526544fdf3 docs(serve): trim switch comments 2026-10-03 04:52:32 +08:00
Harbor404 311b5885de chore(merge): sync with latest main 2026-10-03 04:51:26 +08:00
xiexian 7b1ba8b87d feat(studio): prepare market release drafts from own packages 2026-10-03 04:46:18 +08:00
YHH 4f8f8a5bd5 Merge pull request #11553 from KHG420/codex/studio-sdk-tool-example
docs(sdk): add an installable tool extension example
2026-10-02 13:25:12 -07:00
YHH 3c1ad1153b Merge pull request #11584 from KHG420/codex/studio-mcp-author-example
feat(examples): add a runnable stdio MCP plugin
2026-10-02 13:23:31 -07:00
YHH 323b1857f9 Merge pull request #11503 from KHG420/codex/studio-starter-sidecar-lifecycle
fix(sdk): make starter interceptor work on composed inputs
2026-10-02 13:21:33 -07:00
YHH 54b6f28f80 Merge pull request #11748 from KHG420/codex/studio-command-markdown-suffix
fix(studio): drop mixed-case Markdown command suffixes
2026-10-02 13:18:03 -07:00
YHH 7ef30437f7 Merge pull request #11768 from KHG420/codex/studio-mcp-url-scheme-case
fix(studio): recognize HTTP URL schemes regardless of case
2026-10-02 13:17:06 -07:00
YHH f725e9fb27 Merge pull request #11766 from KHG420/codex/studio-node-server-command-name
fix(studio): derive Node MCP names from script entry points
2026-10-02 13:16:08 -07:00
YHH 9ad79c5727 Merge pull request #11767 from KHG420/codex/studio-uv-dependency-server-name
fix(studio): skip uv dependency values when deriving MCP names
2026-10-02 13:12:13 -07:00
YHH ebc29fc2bc Merge pull request #11507 from KHG420/codex/studio-installed-hook-context
test(studio): verify installed hook context lifecycle
2026-10-02 13:04:51 -07:00
YHH b8e07b42a9 Merge pull request #11508 from KHG420/codex/studio-installed-command-effect
test(boot): verify copied slash commands across their lifecycle
2026-10-02 13:03:58 -07:00
YHH 971cea87dc Merge pull request #11777 from esengine/feat/fix-11769
fix(studio): show a signed-in account consistently when the identity service is unreachable
2026-10-02 12:56:43 -07:00
xiexian cc47e5a0ad fix(studio): recover signed-out market vote writes 2026-10-03 03:39:05 +08:00
YHH 594c16e421 Merge pull request #11725 from KHG420/codex/studio-export-valid-env-names
fix(pluginpkg): generate expandable numeric server aliases
2026-10-02 12:37:11 -07:00
YHH 66705ae660 Merge pull request #11689 from KHG420/codex/studio-plugin-root-diagnostics
fix(studio): diagnose native contribution roots that name files
2026-10-02 12:34:00 -07:00
YHH fb54b4cfef Merge pull request #11696 from KHG420/codex/studio-empty-plugin-guidance
fix(studio): correct empty plugin list install guidance
2026-10-02 12:33:01 -07:00
esengine 4b79709cdb fix(evidence): do not demand verification after supporting-only edits when no checks exist
A write to a markdown or text file in a workspace with no declared or captured checks still added a stale_verification debt and, in Balanced, a readiness floor demanding a verification command. When every proven mutation is a supporting-role path and nothing declares or captured a check, neither applies. Mixed, opaque, unproven or partially observed effects and declared checks keep their behaviour.
2026-10-02 12:32:43 -07:00
xiexian 9e0ef76d56 fix(studio): parse continued MCP setup commands 2026-10-03 03:23:59 +08:00
Harbor404 5b07c3dae9 test(shell): prove fallback notice reaches sink 2026-10-03 03:12:18 +08:00
Harbor404 71d23f0ba1 chore(merge): sync with latest main 2026-10-03 03:10:38 +08:00
Harbor404 b5b9f3d523 fix(agent): surface disabled tool refusals first 2026-10-03 03:10:01 +08:00
Harbor404 f65d5aec06 chore(merge): sync with latest main 2026-10-03 03:04:34 +08:00
Harbor404 56792a367c fix(acp): persist no-op config choices 2026-10-03 03:03:58 +08:00
Harbor404 3d9d65b19e chore(merge): sync with latest main 2026-10-03 02:59:47 +08:00
Harbor404 63bbb1334d docs(ui): remove session info helper comment 2026-10-03 02:59:04 +08:00
Harbor404 d2d5571a00 chore(merge): sync with latest main 2026-10-03 02:58:12 +08:00
esengine 69e7f2c7b6 fix(studio): show a signed-in account consistently when the identity service is unreachable
The sidebar row and the account panel disagreed when a token was present but the identity lookup failed: the row showed a signed-in state with a question-mark avatar while the panel drew the sign-in form and hid the error. Both now render the signed-in state, the identity error, a retry and confirmed sign-out.
2026-10-02 11:58:00 -07:00
Harbor404 e730dafd27 fix(usage): keep range errors inline 2026-10-03 02:57:13 +08:00
Harbor404 e1ca40f84a chore(merge): sync with latest main 2026-10-03 02:52:21 +08:00
YHH 15a6b02f1f Merge pull request #11765 from KHG420/codex/studio-registry-resolve-pagination
fix(studio): follow Registry pages for exact MCP installation names
2026-10-02 11:45:59 -07:00
YHH cb880f030c Merge pull request #11543 from Harbor404/fix/remote-open-absolute-workspace
fix(remote): open absolute Windows workspaces from tree
2026-10-02 11:42:48 -07:00
YHH e4f71647c8 Merge pull request #11764 from KHG420/codex/studio-python-server-command-name
fix(studio): derive Python MCP names from interpreter entry points
2026-10-02 11:41:51 -07:00
YHH 1cde2048cc Merge pull request #11698 from KHG420/codex/studio-ci-supervisor-crash-fixture
test(schedrun): wait for child startup before supervisor crash
2026-10-02 11:32:18 -07:00
xiexian 2c40d8905b fix(studio): skip uv dependency values when deriving MCP names 2026-10-03 02:19:40 +08:00
xiexian f8843f4b2c fix(cli): detect non-executable plugin runtimes in doctor 2026-10-03 02:15:08 +08:00
YHH 2902ca1bde Merge pull request #11705 from KHG420/codex/studio-ci-stage-fixture
test(gitcmd): mark rewritten staging fixture stat dirty
2026-10-02 11:10:50 -07:00
YHH c72c073c26 Merge pull request #11715 from KHG420/codex/studio-quoted-prose-paragraphs
fix(repolint): keep quoted paragraphs separate
2026-10-02 11:09:51 -07:00
YHH 8612f792e6 Merge pull request #11724 from KHG420/codex/studio-export-mcp-header-effect
test(boot): exercise exported MCP recipient headers
2026-10-02 11:07:31 -07:00
YHH e8aba147d9 Merge pull request #11699 from Guan0923/codex/rewind-backup-cleanup
fix(checkpoint): 回退成功后清理临时备份
2026-10-02 11:04:02 -07:00
YHH c8339e2cdd Merge pull request #11755 from KHG420/codex/studio-uvx-pinned-server-name
fix(studio): keep uvx version pins out of MCP server names
2026-10-02 10:56:34 -07:00
YHH aff4de8a9b Merge pull request #11756 from KHG420/codex/studio-uvx-from-server-name
fix(studio): skip uvx from values when deriving MCP names
2026-10-02 10:54:36 -07:00
YHH c8e7d97057 Merge pull request #11753 from KHG420/codex/studio-mcp-runner-extension-case
fix(studio): derive MCP names through mixed-case runner extensions
2026-10-02 10:47:49 -07:00
YHH 95334537fe Merge pull request #11752 from KHG420/codex/studio-mcp-empty-definitions
fix(studio): omit empty MCP definitions from pasted previews
2026-10-02 10:45:11 -07:00
YHH 46c6649939 Merge pull request #11758 from KHG420/codex/studio-sdk-served-tool-declarations
fix(sdk): declare only callable tool handlers
2026-10-02 10:43:12 -07:00
YHH 7f575027ed Merge pull request #11747 from KHG420/codex/studio-mcp-pasted-cli-command
fix(studio): parse copied MCP add commands as declarations
2026-10-02 10:37:47 -07:00
YHH 6c11051633 Merge pull request #11745 from KHG420/codex/studio-doctor-agent-roots
fix(studio): validate declared agent roots in plugin doctor
2026-10-02 10:36:48 -07:00
xiexian c4f6c84852 fix(studio): recognize HTTP URL schemes regardless of case 2026-10-03 01:13:22 +08:00
xiexian 9097d2a036 fix(studio): derive Node MCP names from script entry points 2026-10-03 00:38:57 +08:00
YHH e443274913 Merge pull request #11737 from KHG420/codex/studio-package-contribution-rows
fix(studio): replace duplicate contribution rows with current package inventory
2026-10-02 09:35:22 -07:00
YHH e9a0801a58 Merge pull request #11733 from KHG420/codex/studio-package-diagnostic-rows
fix(studio): refresh repeated compatibility issue rows
2026-10-02 09:34:24 -07:00
YHH 9d806ed477 Merge pull request #11736 from KHG420/codex/studio-switch-skill-store
fix(boot): retain configured skill discovery after Studio model switches
2026-10-02 09:18:31 -07:00
xiexian f2cef6dfa8 fix(studio): follow Registry pages for exact MCP installation names 2026-10-03 00:17:39 +08:00
xiexian cefd143bc1 fix(studio): derive Python MCP names from interpreter entry points 2026-10-03 00:00:16 +08:00
esengine 49b5b934a5 feat(host): log elapsed milliseconds per startup phase 2026-10-02 08:56:39 -07:00
esengine d3d3cba4ea fix(studio): pass the system language to the kernel host so the tray follows it 2026-10-02 08:56:31 -07:00
xiexian 9aa53444c7 fix(sdk): declare only callable tool handlers 2026-10-02 23:45:31 +08:00
xiexian 8742e86433 fix(studio): skip uvx from values when deriving MCP names 2026-10-02 23:12:14 +08:00
xiexian da0221a66f fix(studio): keep uvx version pins out of MCP server names 2026-10-02 23:04:40 +08:00
xiexian be46a8575f fix(studio): derive MCP names through mixed-case runner extensions 2026-10-02 22:53:17 +08:00
xiexian 7fb8810372 fix(studio): omit empty MCP definitions from pasted previews 2026-10-02 22:45:25 +08:00
xiexian b854806701 fix(studio): retain local scope for pending MCP installs 2026-10-02 22:27:30 +08:00
xiexian 9ce710a540 fix(studio): drop mixed-case Markdown command suffixes 2026-10-02 22:13:50 +08:00
xiexian 4a963d4b9f fix(studio): parse copied MCP add commands as declarations 2026-10-02 22:04:35 +08:00
xiexian 0f98adf813 fix(studio): validate declared agent roots in plugin doctor 2026-10-02 21:55:49 +08:00
YHH 7bdaae2025 Merge pull request #11731 from esengine/fix/11723-narrow-sidebar-overlap
fix(studio): keep the narrow workspace column legible
2026-10-02 06:36:17 -07:00
YHH db267ac05c Merge pull request #11732 from esengine/fix/11720-run-analysis-inline-detail
fix(studio): open a run-analysis record under the row that was clicked
2026-10-02 06:35:19 -07:00
YHH 9ef965a9d6 Merge pull request #11729 from esengine/fix/11727-menu-position-at-scale
fix(studio): place rail context menus correctly at a zoomed interface size
2026-10-02 06:34:22 -07:00
xiexian 01044c54b7 fix(pluginpkg): preserve only expandable whole environment references 2026-10-02 21:05:11 +08:00
xiexian 0bb6aff2e7 fix(studio): refresh duplicate package contribution rows 2026-10-02 20:53:08 +08:00
xiexian 2e423c43df fix(boot): preserve live skill discovery across Studio model switches 2026-10-02 20:44:44 +08:00
esengine 620aede3d8 fix(studio): open a run-analysis record under the row that was clicked
The record panel was rendered after the whole active-turns list, so with a few
hundred rows it sat thousands of pixels below the click. It now renders inside
the clicked row's list item (or under the signal track for a signal), the same
item stays mounted, and clicking again closes it.
2026-10-02 05:40:13 -07:00
esengine d3f2a601c3 fix(studio): keep the narrow workspace column legible
The session filter row gave four equal cells to labels with counts, so at the
old 176px floor the labels collided; the new-session and search rows wrapped
around their shortcut hints. Raise the column floor to 232px, let the filter
cells size to their labels, ellipsize labels, and drop counts and shortcut
hints below a container width.
2026-10-02 05:38:52 -07:00
YHH 91c13b306c Merge pull request #11726 from esengine/docs/release-2.26.0-notes
docs(release): prepare Studio 2.26.0 notes
2026-10-02 05:28:58 -07:00
xiexian 1bc3c46128 fix(studio): refresh repeated compatibility issue rows 2026-10-02 20:26:02 +08:00
esengine 001473b7a8 docs(release): keep 2.26.0 note bullets within the prose limit 2026-10-02 05:20:20 -07:00
esengine 1f11055c1f fix(studio): place rail context menus correctly at a zoomed interface size
The session and project menus wrote clientX/clientY into left/top, but the
interface scale is CSS zoom, so a fixed element's left/top is multiplied by the
zoom on its way in. Both now go through pinToViewport, which divides by the
zoom and clamps the measured box inside the viewport. The session menu's
max-height is divided by the zoom as well.
2026-10-02 05:17:40 -07:00
esengine 415445ffbc docs(release): mention the author line and Douyin in the 2.26.0 notes 2026-10-02 05:11:42 -07:00
esengine 0e0b79b8c5 docs(release): prepare Studio 2.26.0 notes 2026-10-02 05:11:42 -07:00
YHH 0d9792e8b0 Merge pull request #11718 from esengine/fix/studio-stale-default-model
fix(studio): start with a fallback model when the saved default model is not configured
2026-10-02 05:10:47 -07:00
xiexian 1e2b3189b9 fix(pluginpkg): generate expandable numeric server aliases 2026-10-02 20:04:13 +08:00
YHH b33fe75d7e Merge pull request #11712 from esengine/fix/11706-subagent-stuck-running
fix(studio): keep sub-agent progress previews out of the dispatching call's card
2026-10-02 05:01:00 -07:00
xiexian c1006585e5 test(boot): exercise exported MCP recipient headers 2026-10-02 19:56:51 +08:00
esengine d1dddb53f3 fix(studio): start with a fallback model when the saved default model is not configured
The official DeepSeek entry's model list migrates the retired
deepseek-v4-flash name to deepseek-flash, and qualified refs move with it,
but a bare default_model = "deepseek-v4-flash" (the shape a shared 1.x/Studio
config carries) was left behind. It then resolved to nothing, and because an
unnamed boot preserves an unknown default for its error, the Studio host's
only pane failed to build and the host exited 1 before its handshake.

- config: migrate a bare retired flash ref (default_model and every other
  model-ref field) to the official entry's successor, unless another
  provider still serves that name.
- boot: a new OpenOnFallbackModel option, set only by the Studio host and
  hub panes, lets an unnamed build pass over an unknown non-plugin default
  like a keyless one, open on the first configured chat model, and report
  default_model_unavailable. The config file is not written. run, the TUI
  and every other build keep the actionable unknown-model error and never
  move to another provider on their own.
- serve: removing the provider in use moves the conversation to the startup
  model when the saved default names nothing, instead of answering 500
  after the removal; session titles follow the same startup model.
2026-10-02 04:54:09 -07:00
esengine d2a8d0b190 fix(studio): keep sub-agent progress out of the dispatching call's card
A background sub-agent keeps emitting reasonix.subagent.* progress after the
dispatching call has returned its job id. Folding those frames into the call's
card re-opened it, renamed it and its execution, and nothing closed it again.
Progress now lands in per-call phase state beside the cards; the Subagents
panel reads a child as running while its call is open or its last phase is not
terminal.
2026-10-02 04:51:56 -07:00
YHH efd0a4430a Merge pull request #11704 from esengine/docs/readme-alipay
docs(readme): add Alipay to the Support section
2026-10-02 04:46:07 -07:00
YHH b6f7e2e8e8 Merge pull request #11714 from esengine/fix/11708-guidance-pending-chip
fix(studio): count in-flight guidance from the queue the panel draws
2026-10-02 04:37:24 -07:00
YHH 81d1c2e709 Merge pull request #11713 from esengine/fix/11707-queue-takeback-composer
fix(studio): put a taken-back queued line back in the composer
2026-10-02 04:36:23 -07:00
xiexian 3818e93430 fix(repolint): keep quoted paragraphs separate 2026-10-02 19:18:05 +08:00
esengine 6271a7e264 docs(readme): put the Douyin line in its own quote so no paragraph passes the prose width 2026-10-02 04:15:43 -07:00
esengine 69e78746dd feat(studio): add a Support entry with PayPal and payment QR codes to About 2026-10-02 04:12:32 -07:00
esengine afb73be003 docs(readme): add Alipay next to WeChat Pay in the Support section 2026-10-02 04:12:28 -07:00
YHH 694d4d79c8 Merge pull request #11701 from esengine/docs/readme-douyin
docs(readme): add the Douyin account to the community links
2026-10-02 04:02:45 -07:00
YHH 77990ed9eb Merge pull request #11660 from esengine/feat/studio-community
feat(studio): add the community group, Discord and contributors to the app
2026-10-02 04:00:17 -07:00
esengine f4766d1369 fix(studio): put a taken-back queued line back in the composer
Take back cancelled the entry and dropped its text, so the label promised a
retrieval nothing performed. The body is now read before the entry is given
up and appended to the composer, under whatever is already typed. A line whose
body cannot be read stays queued rather than being lost.
2026-10-02 03:59:02 -07:00
esengine 7358ceb085 fix(studio): count in-flight guidance from the queue the panel draws
The header chip counted client-side pending rows, which a delivery reported
before its HTTP receipt left pending forever, so it named a line the queue
panel (the only place to act on one) no longer showed. It now counts the
kernel's waiting guidance through the same predicate the panel filters by,
and a receipt that arrives after its delivery no longer re-opens the line.
2026-10-02 03:56:57 -07:00
YHH d65ae94a50 Merge pull request #11682 from esengine/feat/cli-setup
feat(tui): first-run connection panel and /setup (/auth)
2026-10-02 03:42:17 -07:00
esengine 54ef19b12a feat(studio): add a Douyin entry and an author line to About 2026-10-02 03:42:00 -07:00
xiexian 21bb3f3564 test(gitcmd): mark rewritten staging fixture stat dirty 2026-10-02 18:37:33 +08:00
esengine ca96e644b4 docs(readme): add the Douyin account to the community links 2026-10-02 03:25:53 -07:00
esengine 5d7a599ffd feat(studio): add the community group, Discord and contributors to the app 2026-10-02 03:23:14 -07:00
Guan09 5aabf0cfbd fix(checkpoint): clean backups after committed rewinds 2026-10-02 18:06:20 +08:00
YHH c0d4b5a582 Merge pull request #11688 from KHG420/codex/studio-plugin-prompt-invocations
fix(studio): show qualified plugin prompt invocations
2026-10-02 03:04:41 -07:00
xiexian 8990aca7cc test(schedrun): wait for child startup before supervisor crash 2026-10-02 18:03:27 +08:00
YHH a2deb8cf51 Merge pull request #11684 from KHG420/codex/studio-theme-contribution-names
fix(studio): identify directory theme contributions by pack name
2026-10-02 02:54:44 -07:00
esengine 41f3c1e6dd feat(tui): add first-run connection panel and /setup (/auth)
The terminal UI opens a Configure connection panel when the session owes a
key, and /setup or /auth opens it at any time. The key is tested without
being stored, saved to the credential store only, and applied to the running
session when it belongs to the active connection; config.toml is untouched.
2026-10-02 02:54:20 -07:00
YHH ead1e05b4e Merge pull request #11683 from esengine/feat/cli-queue
feat(tui): /queue, /steer, /takeover, /status, /export and /copy
2026-10-02 02:52:49 -07:00
Harbor404 777e32333c Merge remote-tracking branch 'origin/studio' into feat/effort-model-same-switch-fastpath
# Conflicts:
#	internal/frontend/serve/serve.go
2026-10-02 17:48:26 +08:00
YHH c2b55b9824 Merge pull request #11694 from KHG420/codex/studio-native-prompt-author-example
docs(plugins): add native prompt author example
2026-10-02 02:46:36 -07:00
YHH faf26a3c81 Merge pull request #11695 from KHG420/codex/studio-plugin-hook-diagnostics
fix(studio): report plugin hook declaration warnings
2026-10-02 02:45:38 -07:00
YHH 6b9c38c2a0 Merge pull request #11686 from xxoingr/fix/deepseek-h2-preface
fix(provider): hold DeepSeek request bodies for the HTTP/2 server preface
2026-10-02 02:44:40 -07:00
xiexian c81500adfa fix(studio): correct empty plugin list install guidance 2026-10-02 17:29:12 +08:00
YHH 790fdfca4b Merge pull request #11685 from esengine/feat/cli-misc
feat: /cls /rename /todo /forget /reload /effort /verbose /diff-fold /sandbox /output-style /reasoning-language /theme /language /currency
2026-10-02 02:27:01 -07:00
xiexian 7a8dfa5499 fix(studio): report plugin hook declaration warnings 2026-10-02 17:17:59 +08:00
xiexian 4e76141a41 docs(plugins): add native prompt author example 2026-10-02 17:09:22 +08:00
YHH 921e7bdb95 Merge pull request #11691 from esengine/fix/11338-studio-subagent-rows
fix(studio): keep fleet worker tool rows inside their worker card
2026-10-02 02:06:15 -07:00
xiexian 77a30b1c9e fix(studio): show qualified plugin prompt invocations 2026-10-02 17:05:49 +08:00
esengine 6565ce0c85 fix(cli): warn when the project config overrides /language; use slices.Backward 2026-10-02 01:53:09 -07:00
esengine 7a70fa4bf8 feat(tui): add /queue, /steer, /takeover, /status, /export and /copy slash commands
The Studio terminal UI answers the 1.x commands that read or steer the
kernel's own state, with 1.x argument shapes and output. Queue verbs and
steer go through the existing inbox routes, so the controller still owns
admission and ordering. /export is a controller capability: the file is
created exclusively and never through a symlink. /takeover resumes the
target without taking a session from another process.
2026-10-02 01:38:49 -07:00
esengine 86eeae3d6d fix(cli): keep settings verbs workspace-scoped and user-level, route currency through a typed notice 2026-10-02 01:38:10 -07:00
esengine 932736c19e feat(tui): add /cls /todo /verbose /diff-fold /theme and an effect test for the settings verbs 2026-10-02 01:33:51 -07:00
esengine 7c2890aeff feat(control): answer /rename /forget /sandbox /output-style /language /currency /reasoning-language /effort and /reload as slash commands 2026-10-02 01:33:45 -07:00
esengine a5984a25bb fix(desktop): keep a fleet worker's tool calls inside the group card
foldTool looked for a call's parent only among top-level cards, so a
worker's own calls, whose parent is the per-worker task card nested in the
fleet group, fell through to the main list.
2026-10-02 01:32:32 -07:00
YHH b66b2c34ff Merge pull request #11687 from esengine/feat/cli-modes
feat(tui): YOLO on the mode cycle with one Ctrl+Y, git footer row, paste folding
2026-10-02 01:32:27 -07:00
xiexian 1b47d23ab3 fix(studio): diagnose native contribution roots that name files 2026-10-02 16:31:09 +08:00
YHH 51163562f1 Merge pull request #11681 from KHG420/codex/studio-plugin-guide-current-controls
docs(studio): align plugin guide with current controls
2026-10-02 01:28:37 -07:00
esengine 6749731ea3 feat(tui): fold multi-line pastes the way 1.x does
Pastes of five lines or a thousand characters become [Pasted text #N · M lines],
counted whatever row ending the terminal uses, and expand on send into the
labelled Begin/End block 1.x sends. Numbering continues past labels a resumed
session already holds, and panels receive pastes unfolded.
2026-10-02 01:10:30 -07:00
esengine 1ace88e63d feat(tui): show workspace@branch with dirt counts in the footer and name compaction as 1.x does
The footer's data row now leads with workspace@branch and the +added -removed ?untracked
counts from a new GET /workspace/git, read at start and after each turn. The compaction
meter label is back to the bare COMPACT word.
2026-10-02 01:10:30 -07:00
esengine db0c478900 feat(tui): put YOLO on the Shift+Tab cycle and enable it with one Ctrl+Y
The one-time confirmation gate is gone: YOLO is always a stop on the cycle and a
single Ctrl+Y enters it, as in the 1.x terminal UI. --yolo on the command line
keeps its one-time notice, and the sandbox still applies in every mode.
2026-10-02 01:10:30 -07:00
xxoingrandClaude Opus 5.5 18043ab882 fix(provider): hold DeepSeek request bodies for the HTTP/2 server preface
On a fresh HTTP/2 connection, api.deepseek.com's edge acknowledges an eagerly
sent request body (about 8 KB or more) with WINDOW_UPDATE before its own
SETTINGS frame. RFC 9113 §3.4 requires SETTINGS first, so Go's client aborts
with "received WindowUpdateFrame before a SETTINGS frame" and the turn fails
with connection error PROTOCOL_ERROR. Studio sends a ~30 KB body on every
turn, so every request fails on affected networks.

Send Expect: 100-continue on chat requests to the official DeepSeek host.
The transport then holds the body until the server answers, by which point
its preface has arrived. HTTP/2 stays negotiated and request bytes are
unchanged; other providers are unaffected.

Moving the header setup into chat_headers.go keeps openai.go within its
file-size budget.

Refs #11679

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-02 16:07:58 +08:00
xiexian 9c435af496 fix(studio): identify directory theme contributions by pack name 2026-10-02 16:05:58 +08:00
YHH 7c03770b1a Merge pull request #11674 from KHG420/codex/studio-plugin-theme-diagnostics
fix(studio): report plugin theme validation warnings
2026-10-02 00:41:28 -07:00
YHH 78c884b1f2 Merge pull request #11677 from KHG420/codex/studio-theme-glob-file-siblings
fix(plugins): skip file siblings in nested theme globs
2026-10-02 00:40:30 -07:00
xiexian 0f48be263d docs(studio): align plugin guide with current controls 2026-10-02 15:37:32 +08:00
YHH 0bced53fe4 Merge pull request #11670 from KHG420/codex/studio-plugin-replace-recovery-hint
fix(plugins): offer replacement in duplicate recovery hint
2026-10-02 00:29:37 -07:00
YHH b52bb7d4c3 Merge pull request #11676 from esengine/docs/readme-tagline
docs(readme): align the tagline with the repository description
2026-10-02 00:26:31 -07:00
xiexian b69954c912 fix(plugins): skip file siblings in nested theme globs 2026-10-02 15:19:17 +08:00
YHH 754dee6725 Merge pull request #11671 from KHG420/codex/studio-lease-test-distinct-pid
test(control): keep lease holder PID distinct from test process
2026-10-02 00:18:12 -07:00
esengine d03684b080 docs(readme): align the tagline with the repository description 2026-10-02 00:08:48 -07:00
xiexian b0578fca34 fix(studio): report plugin theme validation warnings 2026-10-02 15:03:19 +08:00
YHH 8f9c7806c6 Merge pull request #11664 from KHG420/codex/studio-local-link-path-alias
fix: recognize directory aliases in local link installs
2026-10-01 23:41:23 -07:00
xiexian 23f44c5171 test(control): keep lease holder PID distinct from test process 2026-10-02 14:38:59 +08:00
YHH 28383e9f92 Merge pull request #11663 from KHG420/codex/studio-subagent-author-example
docs: add a packaged read-only subagent exercise
2026-10-01 23:33:54 -07:00
YHH 0a4d0493ee Merge pull request #11658 from KHG420/codex/studio-plugin-agent-inventory
fix(plugin): show packaged subagents in capability inventory
2026-10-01 23:32:57 -07:00
YHH 765bdfc872 Merge pull request #11668 from KHG420/codex/studio-linked-plugin-remove
fix: clean up managed links when removing plugin packages
2026-10-01 23:32:05 -07:00
xiexian 13247c20e3 fix(plugins): offer replacement in duplicate recovery hint 2026-10-02 14:22:11 +08:00
xiexian b30ff93cac fix: recognize directory aliases in local link installs 2026-10-02 13:49:52 +08:00
xiexian e309ee2200 docs: add a packaged read-only subagent exercise 2026-10-02 13:39:32 +08:00
xiexian 782070a0df fix: clean up managed links when removing plugin packages 2026-10-02 13:32:25 +08:00
Harbor404 158d9e2b18 feat(studio): copy session handoff information 2026-10-02 13:31:01 +08:00
Harbor404 49dba2381a feat(usage): add calendar ranges and precise daily details 2026-10-02 13:22:33 +08:00
YHH 9a4d3dfaa7 Merge pull request #11657 from esengine/docs/community-links
docs: point the community links at the official QQ group and Discord
2026-10-01 22:08:19 -07:00
YHH 852b5e65aa Merge pull request #11655 from KHG420/codex/studio-own-install-result-retry
fix(studio): let authors re-preview failed package installs
2026-10-01 21:56:19 -07:00
xiexian c9f698352f fix(studio): recover failed plugin installs through fresh previews 2026-10-02 12:54:12 +08:00
xiexian 9393e594f2 fix(plugin): show packaged subagents in capability inventory 2026-10-02 12:44:36 +08:00
esengine 871687fee7 docs: point the community links at the official QQ group and Discord 2026-10-01 21:34:32 -07:00
xiexian b12683f48b fix(studio): let authors re-preview failed package installs 2026-10-02 12:27:00 +08:00
YHH 88ca5ae82b Merge pull request #11648 from KHG420/codex/studio-plugin-remove-help
fix(cli): show confirmation flag in plugin removal help
2026-10-01 21:13:27 -07:00
YHH a6f744d414 Merge pull request #11649 from KHG420/codex/studio-skill-invocation-catalog
fix(studio): honor skill invocation flags in capability catalog
2026-10-01 21:11:33 -07:00
YHH 580339980f Merge pull request #11646 from KHG420/codex/studio-scenario-skill-frontmatter
fix(studio): keep scenario skill frontmatter valid YAML
2026-10-01 20:45:18 -07:00
xiexian daec47c72a fix(studio): honor skill invocation flags in capability catalog 2026-10-02 11:36:06 +08:00
xiexian 1eb7516562 fix(cli): show confirmation flag in plugin removal help 2026-10-02 11:25:19 +08:00
xiexian f392dd2fa7 fix(studio): keep scenario skill frontmatter valid YAML 2026-10-02 11:13:03 +08:00
YHH c0a7ebe10d Merge pull request #11640 from esengine/fix/ask-stepper-back
fix(studio): keep the ask dialog on the next question after going back
2026-10-01 19:22:52 -07:00
esengine 63da41c794 fix(studio): keep the ask dialog on the next question after going back
Next used the first still-open question, so after going back it skipped answered questions, and with two questions it vanished in favour of Confirm. Stepping is now positional; only the last question looks for an open one.
2026-10-01 19:14:20 -07:00
YHH 1651a909f0 Merge pull request #11637 from esengine/fix/touch-enter-lint
fix(studio): keep Enter newline on touch-first devices (continues #11620)
2026-10-01 17:46:45 -07:00
YHH 8ea3bec3e2 Merge pull request #11635 from esengine/ci/windows-boot-timeout
ci(windows): give the Windows package budget 12m now that boot runs past 8m
2026-10-01 16:39:54 -07:00
esengine eba925da41 ci(windows): give the Windows package budget 12m now that boot runs past 8m 2026-10-01 16:29:29 -07:00
YHH d85fe68a17 Merge pull request #11634 from esengine/docs/studio-2.25.0-notes-2
docs(release): add the fixes merged since the first 2.25.0 notes
2026-10-01 15:05:19 -07:00
esengine 73aca5b612 docs(release): add the fixes merged since the first 2.25.0 notes 2026-10-01 14:57:20 -07:00
YHH 195ee6dcf3 Merge pull request #11590 from KHG420/codex/studio-context-hook-example
docs(studio): add a session-start context author example
2026-10-01 14:37:23 -07:00
esengine a7e6771ae5 fix(studio): keep the composer under the file-size ceiling and cover the touch-device send paths 2026-10-01 14:28:17 -07:00
YHH 0c88b9d0b4 Merge pull request #11632 from esengine/fix/footer-text-clip
fix(studio): stop single-line labels from clipping glyphs of tall fonts
2026-10-01 13:25:26 -07:00
esengine e8ac15d52a fix(studio): stop single-line labels from clipping glyphs of tall fonts
The workspace label in the sidebar footer, the composer's model name and the
workbench file name paired overflow clipping with line-height 1, so a UI font
whose ascent and descent exceed 1em lost the tops and bottoms of its glyphs.
They now use line-height 1.5; the rows centre their children, so nothing moves.
A style contract fails any new rule that clips a line tighter than 1.2.
2026-10-01 13:16:53 -07:00
YHH e4dd0ad1b4 Merge pull request #11629 from esengine/fix/meterrail-tabular-nums
fix(studio): keep the status rail from jumping as its numbers change
2026-10-01 12:20:14 -07:00
esengine 404af94b9e fix(studio): keep the status rail from jumping as its numbers change
The rail items re-declare the font shorthand, which resets
font-variant-numeric, so the digits were proportional and each item changed
width with its value. Restate tabular-nums in those rules and reserve the
width of a three-digit speed.
2026-10-01 12:10:06 -07:00
YHH 8de4c91010 Merge pull request #11624 from esengine/feat/remote-bootstrap-nvm
fix(remote): run bootstrap commands with the remote login-shell PATH
2026-10-01 11:25:20 -07:00
esengine b94c4aa944 fix(remote): run bootstrap commands with the remote login-shell PATH 2026-10-01 11:12:01 -07:00
YHH ef1a339ca9 Merge pull request #11613 from Harbor404/fix/studio-11574-browser-workspace
fix(studio): let browser deployments add workspaces by path
2026-10-01 11:08:02 -07:00
YHH 6d679edf67 Merge pull request #11614 from Harbor404/fix/studio-11586-provider-name-ime
fix(studio): hold Add while a source name is composing
2026-10-01 11:03:23 -07:00
xiexian 29c033cfd5 docs(studio): add session-start context author example 2026-10-02 01:28:24 +08:00
YHH 3eda0b98e4 Merge pull request #11618 from esengine/feat/feedback-unavailable-retry
fix(feedback): retry a transient submit response once and say what to do next
2026-10-01 10:02:38 -07:00
Harbor404 b5485b1649 fix(studio): keep Enter newline on touch-first devices 2026-10-02 01:00:05 +08:00
esengine 5e1ea240fb fix(feedback): retry transient submit responses once
A submit that gets an answer without a recognised service code (an uncoded
5xx or an undecodable success body) is retried once with the existing
backoff, keeping the idempotency key. Coded refusals, refused redirects and
replies are never retried. The failure message now says the typed text is
kept, to try again shortly, and points at the GitHub alternative.
2026-10-01 09:41:30 -07:00
Harbor404 c046c5281f fix(studio): keep add disabled during source-name IME
The source-name field intentionally hides validity errors while an IME candidate is still composing. The Add button did not share that state, so a completed form could submit a partial non-ASCII candidate before composition ended. Hold the button while composing and cover the name boundaries at the UI and serve boundary.
2026-10-01 22:45:16 +08:00
Harbor404 7ad19b82a5 fix(studio): add workspaces from a browser
Probe host capabilities before opening a native picker. Headless and remote browser clients now enter a server-side path and use the existing /tree/workspaces API, while the desktop shell keeps its native dialog.
2026-10-01 22:44:28 +08:00
YHH 7b34dd738d Merge pull request #11610 from Harbor404/fix/studio-11581-legacy-recovery-archive
fix(serve): archive a legacy recovery lineage atomically
2026-10-01 07:42:24 -07:00
YHH d77138966a Merge pull request #11611 from KHG420/codex/studio-mcp-result-retry
fix(studio): retain retry for returned MCP install failures
2026-10-01 07:40:24 -07:00
xiexian 4642a4cbee fix(studio): retain retry for returned MCP install failures 2026-10-01 22:18:28 +08:00
YHH 2df63d9f9a Merge pull request #11609 from KHG420/codex/studio-install-failure-reload
fix(studio): preserve install failures alongside reload errors
2026-10-01 07:17:26 -07:00
Harbor404 99e466f2cc fix(sessionstore): archive recovery lineage atomically
Archive a folded legacy recovery row together with its siblings, keep siblings open in a pane active, and restore sidecars if a multi-file update fails. Refs #11581.
2026-10-01 22:09:57 +08:00
YHH c145598d47 Merge pull request #11599 from KHG420/codex/studio-export-executable
fix(studio): keep exported plugin hooks executable after reinstall
2026-10-01 07:08:00 -07:00
xiexian 5ff8944e9f fix(studio): preserve install failures alongside reload errors 2026-10-01 22:01:39 +08:00
YHH e080b2b972 Merge pull request #11604 from KHG420/codex/studio-install-reload-reason
fix(studio): preserve installation reload failure reasons
2026-10-01 06:55:37 -07:00
YHH 614f9d3831 Merge pull request #11595 from KHG420/codex/studio-my-packages-account
fix(studio): scope My packages to the current account
2026-10-01 06:42:18 -07:00
YHH 9d53a3aad7 Merge pull request #11602 from KHG420/codex/studio-remove-reload-result
fix(studio): retain reload failures after package removal
2026-10-01 06:41:23 -07:00
YHH 8d306bd0e6 Merge pull request #11596 from no-name-touse/fix/onboarding-zoom
fix(studio): make onboarding layout follow zoom-adjusted width
2026-10-01 06:33:27 -07:00
YHH a9cae776f5 Merge pull request #11601 from esengine/fix/context-budget-notice-continues-task
fix(agent): tell the model compaction is automatic and the task continues
2026-10-01 06:32:36 -07:00
xiexian 13c1820de5 fix(studio): preserve installation reload failure reasons 2026-10-01 21:16:34 +08:00
xiexian 5d33e047f4 fix(studio): retain reload failures after package removal 2026-10-01 21:06:03 +08:00
esengine ce860db141 fix(agent): tell the model compaction is automatic and the task continues
The context-budget notice reported "room remaining" and, at the second
rung, told the model to land what it knows now. It never said that the
fold is automatic and the same task goes on, so a model near the trigger
read the figure as the end of its window, announced it would be
compacted, and handed the work back to the user (in-app feedback
FB-J2QR-HQ89).

Both rungs now state that the figure counts to the automatic compaction,
not to the end of the window, and carry one shared continuation clause:
keep working, do not stop, hand the work back or suggest a new session.
The notice still rides the turn tail and the typed ContextBudgetFigures
event is unchanged.

Two fixtures sat within a few tokens of the notice size and are widened.
2026-10-01 06:03:44 -07:00
YHH 688658585b Merge pull request #11600 from esengine/docs/studio-2.25.0-notes
docs(release): prepare Studio 2.25.0 notes
2026-10-01 06:00:43 -07:00
Harbor404 ac0a13dbdd Merge remote-tracking branch 'origin/studio' into feat/effort-model-same-switch-fastpath 2026-10-01 20:54:13 +08:00
Harbor404 7f9cb9dbdc fix(acp): remove unused session config helper 2026-10-01 20:54:11 +08:00
esengine eb6ba928be docs(release): prepare Studio 2.25.0 notes 2026-10-01 05:51:27 -07:00
Harbor404 b47f9144a8 Merge remote-tracking branch 'origin/studio' into ci-reasonix-11504 2026-10-01 20:49:30 +08:00
Harbor404 82a06451dd fix(shell): reject a Store alias that will not start 2026-10-01 20:49:24 +08:00
YHH ac7836f048 Merge pull request #11592 from KHG420/codex/studio-review-refresh-order
fix(studio): keep review results after concurrent install refresh
2026-10-01 05:45:51 -07:00
Harbor404 b1f7ba5dd8 Merge remote-tracking branch 'origin/studio' into ci-reasonix-11504 2026-10-01 20:45:24 +08:00
YHH ac7d7be97d Merge pull request #11588 from Guan0923/codex/session-title-context-menu
fix(studio): 右键打开会话菜单并调整版本角标间距
2026-10-01 05:45:01 -07:00
Harbor404 c33429c731 Merge remote-tracking branch 'origin/studio' into fix/remote-open-absolute-workspace
# Conflicts:
#	internal/platform/remote/bootstrap/resolve_workspace_test.go
2026-10-01 20:44:49 +08:00
Harbor404 1e3bb12e68 Merge remote-tracking branch 'origin/studio' into fix/disabled-tools-followups 2026-10-01 20:42:20 +08:00
Harbor404 29d06f6064 fix(mcp): address disabled tool policy review 2026-10-01 20:41:43 +08:00
xiexian a2e20b8a2f fix(studio): preserve executable bits in exported plugins 2026-10-01 20:34:59 +08:00
YHH 0be8aaf783 Merge pull request #11488 from esengine/fix/host-session-sweep-off-boot-path
fix(serve): sweep session history in the background, not inside the build
2026-10-01 05:25:29 -07:00
YHH 1c12f7c174 Merge pull request #11563 from esengine/fix/inbox-skip-requeues
fix(inbox): a skipped question requeues the guidance queued into its turn
2026-10-01 05:15:47 -07:00
esengine 07b2453403 fix(serve): sweep session history in the background instead of inside the build
Every runtime build ran ReconcileCleanupPending inline: a handful of stat and
read calls per transcript in the session directory. On a Windows box with 8000
sessions the sweep took 11 s of an 11.8 s build and 29 s at 20000 sessions
(about 1.4 ms per session, warm), and it sits before the host handshake, so a
cold start with a large history waits on it. The shell gave up at 20 s.

The Studio host, hub.Open, workspace switch and rebuild paths now hand boot a
reconciler that runs the sweep in a goroutine, once per directory at a time.
8000 sessions: build 11.8 s -> 1.0 s.

Refs #11434
2026-10-01 05:14:42 -07:00
xiexian b619b3dbfe fix(studio): scope author package views to the current account 2026-10-01 20:14:15 +08:00
YHH 4cdd4bde93 Merge pull request #11541 from esengine/fix/remote-open-absolute-workspace
fix(remote): a Windows absolute workspace is not joined after the remote home
2026-10-01 05:09:56 -07:00
挽风 8ac91c3399 fix(studio): make onboarding layout follow zoom-adjusted width 2026-10-01 20:05:59 +08:00
esengine 9fc7f5022c fix(inbox): a skipped question requeues the guidance queued into its turn
Skipping an ask ends the turn through the same cancel as the stop button, so
guidance the user had queued into it was held uncertain and the queue paused.
The cancel now carries a typed cause: only the user declining a question
returns the undelivered item to the queue as an ordinary follow-up and lets
dispatch continue. Stop, errors and crashes keep the pause, and a stop that
lands after a skip still wins. The requeue only moves an accepted steer, so a
repeat delivers nothing twice.

Refs #11524
2026-10-01 05:03:36 -07:00
xiexian 7d6687d03b fix(studio): refresh inventory after review submission 2026-10-01 19:53:38 +08:00
YHH af710d8784 Merge pull request #11585 from esengine/fix/add-source-name-inline
fix(studio): show the source name rule beside the field, not as a connection failure
2026-10-01 04:46:51 -07:00
Guan09 723b2b8ef2 fix(studio): 右键打开会话菜单并调整版本角标间距 2026-10-01 19:35:36 +08:00
esengine 3afb4dea49 fix(studio): explain an unusable source name beside the field, not as a connection failure
The add-source form only learned that a name such as a Chinese one is not
allowed after the kernel refused the save, and then showed the refusal under
the title "cannot connect". The name rule now shows inline as the name is
typed (with the allowed characters stated), the add button waits for a usable
name, coded name refusals from the kernel land on the field, and any other
failed save is titled as a save failure. The identifier rule is unchanged.

Reported through in-app feedback FB-WJ8B-C1XH.
2026-10-01 04:34:52 -07:00
esengine c74ea3c431 feat(studio): opt-in archiving of conversations inactive for N days
Adds a user-level [auto_archive] setting (enabled = false, days = 30 by
default; a project reasonix.toml cannot set it). While on, the controller's
ArchiveInactiveSessions archives conversations idle past the threshold from
the same background pass that sweeps recovery branches, so it stays off the
boot path. Archiving only flips catalog metadata and is reversible.

Never archived: pinned, leased by a live run (covers a run or a pending
answer/approval), carrying an unfinished turn, the controller's own
session, subagent traces and superseded versions. Restoring a conversation
counts as activity so it is not re-archived straight away.

The kernel is the authority for pins (BranchMeta.Pinned, tree rows carry
`pinned`). The window sends the union of its own pins and the kernel's
after the tree loads and retries on failure; the sweep, including the one
after enabling, refuses to run until a window has synced once.

Archived conversations stay findable: search returns them with an
"archived" marker, and rows archived by the sweep carry a mark so the
window says once how many were archived and that they can be restored.
Settings > Session gets GET/POST /auto-archive with persistence to the
user file and zh/en strings.

Refs #11516
2026-10-01 04:30:46 -07:00
YHH 79b0547997 Merge pull request #11533 from esengine/feat/project-reveal-menu
feat(desktop): show a project's folder in the file manager from the sidebar menu
2026-10-01 04:25:14 -07:00
xiexian 3f35e37388 feat(examples): add a runnable stdio MCP plugin 2026-10-01 19:14:56 +08:00
YHH e6d0d4aebe Merge pull request #11545 from esengine/fix/tray-icon-blurry
fix(studio): sharpen the Windows tray icon at scaled displays
2026-10-01 04:13:59 -07:00
esengine 7b7e09fdcd fix(serve): touch the disk only with the window's own listed root
Refs #11524
2026-10-01 04:13:47 -07:00
xiexian 4d9ba18ae0 fix(studio): serialize own-package review submissions 2026-10-01 18:44:17 +08:00
YHH 631bd6b111 Merge pull request #11419 from BuGlessRB/open/diff-fence-stream-perf
fix(cli): colour a streaming diff fence hunk by hunk / 修复(cli):流式 diff 围栏逐 hunk 着色
2026-10-01 02:57:08 -07:00
YHH 7ac760e44d Merge pull request #11558 from KHG420/codex/studio-market-vote-retry
fix(studio): retry failed market vote lookups
2026-10-01 02:56:13 -07:00
YHH 9dcfdf2e7c Merge pull request #11565 from esengine/fix/onboarding-scroll
fix(studio): make the first-run connect card scroll in short windows
2026-10-01 02:35:06 -07:00
esengine cac890152a feat(share): let the phone-access LAN link use a fixed port
An optional user-level [serve] share_port makes the direct link listen on a
chosen port instead of a random one. Unset keeps today's behaviour. The range is
1024-65535, a project reasonix.toml cannot set it, and the bind range is
unchanged. A taken port is a typed refusal (share.port_in_use). The settings
page saves it to the user config; changing it moves an open door and
phones re-pair.

Refs #11540
2026-10-01 02:31:52 -07:00
YHH f86eb89b74 Merge pull request #11559 from esengine/fix/cancel-flag-follow-up
fix(control): flag a turn cancelled only when it ended on the cancel sentinel
2026-10-01 02:30:14 -07:00
esengine 6292d66e90 fix(studio): make the first-run connect card scroll in short windows
The onboarding stage only had a min-height, so it grew with its content and
never overflowed itself while the body clipped it. Once the connection result
was showing, the Start button sat below the window with nothing to scroll.
The stage is now the window's height and scrolls; the shell no longer shrinks
or acts as a scroll container. Adds a browser guard (perf/onboarding.mjs).
2026-10-01 02:24:17 -07:00
YHH 3bb9dce90a Merge pull request #11562 from esengine/docs/hooks-guide
docs: add a Hooks section to the guide and reasonix-guide skill
2026-10-01 02:23:54 -07:00
Harbor404 ef8c8cbfb0 fix(studio): refresh provider build identity on same selection 2026-10-01 17:20:04 +08:00
Harbor404 a0d1380335 Merge origin/studio into feat/effort-model-same-switch-fastpath 2026-10-01 17:20:03 +08:00
YHH 5d3a70950c Merge pull request #11552 from esengine/fix/11524-notices-localized
fix(notices): word the context-budget and unapplied-guidance notices from typed codes
2026-10-01 02:19:11 -07:00
esengine a27298c462 docs: document hooks (events, settings.json schema, exit codes, scope)
A model told a user that Reasonix has no hooks. The guide's hooks section only
covered SessionStart, and the bundled reasonix-guide skill had no settings
schema, exit-code convention or worked example.
2026-10-01 02:14:37 -07:00
YHH f84126a76f Merge pull request #11555 from esengine/fix/inbox-block-code
fix(inbox): carry why a queue item stopped as a typed code the desktop words itself
2026-10-01 02:09:05 -07:00
Stephen R. van den Berg e8c7feeeef fix(cli): colour a streaming diff fence hunk by hunk
A ```diff fence rendered through the colourised built-in path on every
frame, and a colourised row costs ~1.4 ms to lay out (chroma via
regexp2) against ~1.3 µs for a plain one. While a fence streams every
delta re-renders the whole growing body, so a multifile diff pegged a
core and the display crawled. Changes:

- Each "@@ " hunk header settles the rows before it, so render that
  prefix and leave only the in-progress hunk on the plain rail: the diff
  colours in as it streams instead of only at the closing fence. A
  closed fence settles the whole body. The formatter runs on each
  settled prefix too, so its output appears hunk by hunk.

- Keep the already-formatted rows on screen while a settled body's run
  is pending: growing the body by one hunk changed its key and redrew
  the whole section on the plain rail until the new run landed, so the
  delta output flashed back to plain at every hunk. The renderer now
  draws the largest settled prefix whose result has landed and leaves
  only the newly settled remainder (and the in-progress hunk) on the
  rail.

- Memoize the colourised rows per hunk (hunk, path, width, rail, theme).
  A completed hunk never changes, so it is highlighted once however many
  frames redraw it; caching the whole body instead would re-highlight
  every earlier hunk at each "@@ " and is quadratic in the hunk count
  (measured: 60 hunks, 79 KB took 40 s with a body memo, 2 s per-hunk).
  The theme is in the key so a runtime theme switch cannot serve the old
  palette's colours.

- A fence still with fewer than two hunks keeps the plain rail, and a
  closed fence whose configured [cli].diff_formatter result is still
  pending also draws the rail, since that output supersedes the built-in
  rows anyway; a landed-but-failed run falls back to the built-in.

- Hold the transcript viewport still when the content shrinks: a settled
  diff collapsing its raw preamble to one formatted line frees rows at
  the bottom, and following them back up made the screen jump. The
  position now holds, the freed rows stay blank, and later rows fill
  them, so scrolling resumes only when the output actually grows past
  what is shown. The hold has only a lower bound, so a shrink larger
  than the viewport would hold the position past the content and blank
  the whole transcript — a collapsing multifile fence, or /clear
  restarting over a shorter one — so the view re-anchors to its tail
  instead. A manual scroll or resize releases the hold.

- Coalesce the stream frames the TUI hands the model: waitUpdate drains
  everything already queued into one updateMsg, so a burst of deltas
  costs one render instead of one per delta. Order is kept and a Gap
  still triggers a history reload.

- Fix the file-header misdetection: a removed SQL "-- x" line renders
  "--- x", which trimDiffPreamble/diffSectionStart mistook for a
  "--- "/"+++ " file header, dropping the "@@" hunk header (line numbers
  reset to 0) or the rows themselves when a removed "--- x" sat before
  an added "+++ y". A pair is a file header only when a "@@ " hunk
  follows it, and countDiff no longer skips "--- "/"+++ " rows.

- Keep a streaming multi-file diff's next file header on the plain rail:
  splitAtLastHunk settled everything before the last "@@ " header, which
  included the next file's "--- "/"+++ " pair (and its git preamble) once
  that file's first hunk header had arrived but no hunk of it had
  completed. isFileHeaderPair then saw no "@@ " after the pair, so the
  settled renderer read it as a removed/added row of the previous file's
  last hunk and counted it in that file's stat. A trailing dangling file
  header now moves from the settled part into the tail until its "@@ "
  arrives.

Tests: the multi-file cases TestSplitAtLastHunk was missing and an
end-to-end streamed test; a theme-switch test pinning the per-hunk memo's
theme key; and a benchmark replaying a 4-file x 15-hunk stream, redrawn
per streamed line.

fix(cli): highlight a streaming diff fence's hunk once

splitDiffSections appends a newline to every section, so splitHunks
returns a section's last hunk with a trailing newline and every earlier
hunk without. hunkRowsCached keyed the memo on the raw hunk, so one hunk
carried two keys over a stream: it was highlighted once while it was its
section's last hunk and again once a following hunk arrived. Every hunk
but a file's last was therefore highlighted twice, at ~1.4 ms a row.

Trim the trailing newline before keying. diffBody already trims, so the
rendered rows are unchanged; the streaming benchmark over a 4-file,
30-hunk, 1816-line diff drops from 7.8 s to 5.6 s.

TestStreamingDiffHunkMemoisedOnce pins one memo entry per hunk: it sees
three entries for two hunks before this change, two after.

Fold the writer-tool preview before highlighting it too. diffBody laid
out every row and then dropped all but maxLines, so a write_file card's
~24-row preview colourised the whole diff: ~772 ms per repaint for a
199-line file, paid on every message while the call runs. Counting the
fold from the source and laying out only the kept rows prices a
2000-line diff like a 24-line one.

BenchmarkDiffBlockPreview prices both shapes through a diffPreviewFold
seam, which TestDiffBodyFoldSeamSameRows proves draws the same rows: a
2000-line preview is 6.3 s and 86 MB with the whole body laid out, 71 ms
and 1.0 MB folded, and flat from 24 lines up.

docs(cli): correct diff-fence comment coherence

Four comments described behaviour the code does not have:

- diffRowCount's doc counted one row per non-blank line, but diffBody draws no
  row for a "@@ " header, so it over-counted by the hunk count.
- renderCodeRail's doc claimed the in-progress hunk and the pending-formatter
  placeholder, both drawn by renderRailText, not here.
- splitDiffSections' doc named only the fence path, omitting DiffText, and
  described the plain-rail fallback where DiffText drops a headerless section.
- the file header claimed md.go still sits at the 800-line ceiling.

Reword each to the constraint that holds now.
2026-10-01 11:01:45 +02:00
esengine f836dc6a3a fix(inbox): carry why a queue item stopped as a typed code the desktop words itself
A held queue item showed the kernel's English diagnostic (for example
"steer accepted but unapplied before turn exit") in a Chinese UI. Items now
also carry a stable BlockCode and the desktop words it in the UI language;
unknown codes fall back to the diagnostic.
2026-10-01 01:56:28 -07:00
YHH 69cb060066 Merge pull request #11548 from esengine/fix/storage-move-keeps-project-list
fix(storage): carry the remembered project list when the state root moves
2026-10-01 01:48:11 -07:00
esengine 6328dfa0b5 fix(control): flag a turn cancelled only when it ended on the cancel sentinel
A real failure that raced the stop button was reported as a cancellation and
hidden. TurnDone.Cancelled now requires Err to be nil or context.Canceled. The
desktop also reads "Cancelled" instead of "Interrupted" after the user's own
cancel.

Refs #11524
2026-10-01 01:43:02 -07:00
xiexian 9bb0aa28fd fix(studio): retry failed market vote lookups 2026-10-01 16:34:22 +08:00
YHH 3a754cebdb Merge pull request #11547 from KHG420/codex/studio-skill-author-invocation-comment
docs(skill): clarify authoring invocation policy
2026-10-01 01:34:04 -07:00
esengine 6694a1b81a fix(storage): carry the state root's loose entries when it moves
Settings > Storage moved sessions and projects but left other top-level state
behind while the code reads only the new root: the remembered project list
(serve-workspaces.json), user-level REASONIX/AGENTS/CLAUDE instructions,
memory/global, schedules, trusted seals, machine-id.key and the MCP migration
marker. All are now owned entries, so a move carries them.

Installs already moved get them back at boot: each entry is merged once without
overwriting and the old copy is renamed *.adopted. The project list is merged
under its lock and written atomically by a new leaf package shared with serve,
and file copies publish whole files. A guard test scans the tree for paths
joined under the state root so a future entry cannot be forgotten.
2026-10-01 01:33:31 -07:00
YHH 003000be21 Merge pull request #11532 from KHG420/codex/studio-publish-draft-lock
fix(studio): keep the publishing draft stable while submitting
2026-10-01 01:29:07 -07:00
Harbor404 fceb6b8ab9 Merge latest studio into UNC round-trip fix 2026-10-01 16:23:00 +08:00
YHH 92a1677fda Merge pull request #11551 from esengine/fix/11524-cancel-not-error
fix(desktop): do not draw a user-cancelled turn as an error card
2026-10-01 01:22:05 -07:00
Harbor404 0a5e45417d Merge latest studio into UNC round-trip fix 2026-10-01 16:21:05 +08:00
esengine e4f1f80a76 fix(notices): word the context-budget and unapplied-guidance notices from typed codes
Both host notices reached every frontend as kernel English, so a Chinese UI
showed an English card. The context-budget notice now carries a code and a JSON
figures payload, the unapplied-guidance notice carries the user's text as
Detail, and the desktop card and the TUI word them from those in the UI
language. Unknown codes and undecodable payloads still fall back to the
kernel's text.

Refs #11524
2026-10-01 01:20:40 -07:00
xiexian 552209fa46 docs(sdk): add an installable tool extension example 2026-10-01 16:16:56 +08:00
xiexian 8fdfb89127 docs(sdk): align fullsidecar demo guidance 2026-10-01 16:11:25 +08:00
YHH 9ced3d1577 Merge pull request #11542 from KHG420/codex/studio-fullsidecar-host-proof
docs(sdk): add fullsidecar installation and host checks
2026-10-01 01:04:54 -07:00
Harbor404 d6e0505dc6 chore(remote): narrow this PR to the UNC round trip 2026-10-01 16:02:51 +08:00
Harbor404 42f42d2d19 fix(acp): keep queued config order on no-op switches 2026-10-01 16:02:04 +08:00
Harbor404 e2b6e8fd31 Merge remote PR history into rebased branch 2026-10-01 15:57:36 +08:00
Harbor404 fe4b15b2f6 fix(remote): open absolute Windows workspaces from tree 2026-10-01 15:57:23 +08:00
esengine 5caa03eca1 fix(desktop): do not draw a user-cancelled turn as an error card
Skipping an ask (or stopping a turn) ends it with the kernel's Cancelled flag
set and context.Canceled as Err. The transcript appended Err as an error card
regardless of the flag, so the user's own choice read as "context canceled".
The flag is the identity; Err is not drawn when it is set. An effect test pins
the kernel side through boot.Build.

Refs #11524
2026-10-01 00:45:03 -07:00
xiexian 309ea9aa0f docs(skill): clarify authoring invocation policy 2026-10-01 15:42:19 +08:00
Harbor404 dd73c2159c perf(studio): skip rebuilds for unchanged model or effort 2026-10-01 15:37:50 +08:00
xiexian 15155c3a67 fix(studio): keep the publishing draft stable while submitting 2026-10-01 15:36:14 +08:00
esengine 7f7a063139 fix(studio): give the Windows tray icon a file at exactly the size Windows asks for 2026-10-01 00:36:08 -07:00
YHH 8c608c8bba Merge pull request #11523 from KHG420/codex/studio-skill-dependency-guide
docs(skills): explain and verify MCP dependency readiness
2026-10-01 00:33:50 -07:00
YHH e9a863c36d Merge pull request #11304 from Guan0923/codex/user-message-copy
feat(studio): 重设计用户消息操作栏并新增复制功能
2026-10-01 00:30:47 -07:00
YHH 0d30c68641 Merge pull request #11528 from KHG420/codex/studio-source-disabled-style
fix(studio): distinguish the busy plugin source without fading text
2026-10-01 00:27:32 -07:00
YHH dc8f746f5e Merge pull request #11537 from KHG420/codex/studio-skill-invocation-comment
docs(skills): distinguish manual listing from model invocation
2026-10-01 00:26:38 -07:00
Harbor404 8f5768e118 fix(remote): open absolute Windows workspaces from tree 2026-10-01 15:26:08 +08:00
Harbor404 277f5614a8 Merge remote-tracking branch 'origin/studio' into ci-reasonix-11504 2026-10-01 15:25:45 +08:00
YHH f6d948c60b Merge pull request #11522 from KHG420/codex/studio-export-result-lifecycle
fix(studio): keep package export feedback current and visible
2026-10-01 00:25:07 -07:00
esengine b2f36909eb fix(remote): a Windows absolute workspace is not joined after the remote home
resolveWorkspace only knew a leading slash as rooted, so a drive-letter path such as C:\Users\u\x (what the remote tree reports as a project root) was appended to home and the serve never started. Rootedness is now asked of the target machine.
2026-10-01 00:24:00 -07:00
Harbor404 12e6f9d3ae Merge fork/fix/powershell-shell-probe after studio rebase 2026-10-01 15:20:35 +08:00
xiexian b5f2f0c025 test(extensions): verify installed fullsidecar host effects 2026-10-01 15:14:53 +08:00
Harbor404 439e868ed3 style(boot): trim shell diagnostic comment 2026-10-01 15:12:11 +08:00
Harbor404 de5ba24bcc fix(boot): log the resolved shell at debug level
Headless `run` must leave stderr empty unless --debug is passed, so the
boot record of the resolved shell interpreter cannot be an Info log. It is a
diagnostic; the always-on counterpart is the Warn shellrun emits when a
launch actually fails.
2026-10-01 15:12:11 +08:00
Harbor404 ac2db59a33 fix(shell): keep the PowerShell probe within lint budgets
Trim repolint comment blocks and move launch-failure logging out of RunForeground so the function stays within the 120-line limit. No behavior change.

Signed-off-by: Harbor404 <[email protected]>
2026-10-01 15:12:11 +08:00
Harbor404 bac803e68c fix(shell): only trust a PowerShell that starts, and record which ran
A pinned [tools.shell] path was honoured on existence alone, so a pwsh.exe
that stats fine but will not start was held for the whole session while every
command failed at launch — the failure #10633 reported, still reachable
through the pinned-path branch after #10617 reworked auto-detection.

- Prove a pinned PowerShell the way the bash arm already proves its own, and
  fall back to detection with a warning when it will not start.
- VerifyShell proves a pinned PowerShell too, so the settings screen refuses
  one where it was typed rather than on every later command.
- powerShellLaunches answers false for a path that is not there: a missing
  executable is not launchable, so callers that gate on it alone now agree.
- resolveShell takes the launch probe as an argument, which puts the decision
  table ("pwsh will not start, 5.1 will") under test on any host.
- Detection that finds nothing says so instead of silently returning bare bash.
- Boot records the interpreter the session resolved to, and a launch-phase
  spawn failure records the shell, its path, the caller and the error. The
  desktop relays the sidecar's stderr into desktop-shell/logs/, which the
  report found empty.

Windows end-to-end is not verified locally; the fallback is covered by the
injected probe on Linux and macOS.
2026-10-01 15:12:11 +08:00
esengine a26c36df13 feat(feedback): keep the unread badge fresh and announce new replies
Refresh the feedback unread count on window focus (at most every 30 s) and
every 10 minutes while a report is unsettled, with back-off to 1 h and no
error UI. A person with no open report is never polled. A hidden window keeps
polling only for someone who enabled the new feedback-reply notification.

Add a fourth notification kind, feedback_reply (default on under the master
switch), raised through POST /notifications/feedback-reply when the unread
count rises while the window is unfocused. The text never carries the reply.
2026-10-01 00:05:59 -07:00
YHH c9daeddf3e Merge pull request #11530 from esengine/fix/editmenu-locale
fix(electron): label the edit menus in the interface language
2026-09-30 23:52:52 -07:00
xiexian 8dd9294495 docs(skills): distinguish manual listing from model invocation 2026-10-01 14:48:59 +08:00
esengine f2dc940a29 test(desktop): cover the host-only route, remote projects and old shells
Refs #11524
2026-09-30 23:47:02 -07:00
xiexian 9d930f67f4 docs(skills): explain and verify MCP dependency readiness 2026-10-01 14:45:56 +08:00
esengine c0766fbe59 fix(electron): keep every platform Edit menu item, test the rebuild rule
The hand-written Edit menu dropped Paste and Match Style, Delete,
Substitutions and Speech; restore them with the same roles (structure checked
against the role-based menu in a real Electron). The menu factory is now
Electron-free and unit-tested, the language follows the first preferred system
language like the page, and the redundant second menu install is gone.

Refs #11524
2026-09-30 23:45:39 -07:00
Guan09 73e2fb91af fix(studio): restore action hit areas and unify hover feedback 2026-10-01 14:43:36 +08:00
xiexian 4df81041b1 fix(studio): dim the busy plugin source input 2026-10-01 14:41:29 +08:00
YHH 4bcda5691d Merge pull request #11511 from KHG420/codex/studio-skill-catalog-guide
docs(skills): explain the catalog projection and cache boundary
2026-09-30 23:32:07 -07:00
esengine 383e6369ac refactor(desktop): read the shell's ability to reveal from the host, not the hub 2026-09-30 23:30:59 -07:00
esengine 6bb625fcf7 fix(desktop): extract the reveal menu item, add refusal wording 2026-09-30 23:22:30 -07:00
YHH 8d963086ea Merge pull request #11529 from esengine/feat/subagent-bg-rows
feat(studio): sub-agent transcript entry in the deck, show-all for clipped nested output
2026-09-30 23:21:57 -07:00
esengine 85df91d4ef feat(desktop): show a project's folder in the file manager from the sidebar menu
The project menu offered only sort and remove-from-list. Add a reveal item that
asks the hub where a listed project lives (GET /host/workspaces/locate, window
only, refuses any folder the window does not list) and has the shell select it,
so it needs no open pane. Remote hosts draw in their own rail and never reach
this menu; a project whose folder is gone shows the item disabled with the
reason.

Refs #11524
2026-09-30 23:21:09 -07:00
xiexian af2dbb252d fix(studio): keep package export feedback current and visible 2026-10-01 14:15:30 +08:00
esengine 8de4b4851c fix(electron): label the edit menus in the interface language
Role-only menu items take their text from Electron's built-in English
catalogue even when the app locale is zh-CN (verified on a real Electron:
getLocale() == zh-CN, labels Undo/Cut/Copy/Paste/Select All). Keep the roles
for the clipboard work and give each item an explicit zh/en label chosen by the
page's saved language (rx-lang), falling back to the machine locale exactly as
the page does. The macOS Edit menu is rebuilt when the language preference
changes.

Refs #11524
2026-09-30 23:15:27 -07:00
esengine fb3d9d7433 fix(studio): register the new actions, trap focus in the transcript overlay, cap its output
Registers tool.show-all, agent.open, agent.close and agent.focus-trap, moves
focus into the overlay and back to the deck chip, caps the overlay's rendered
output at 200000 characters with a note, keeps the sub-agent count stable, wraps
nested output, and drops a stray .agents rule that changed the sidebar spacing.
2026-09-30 23:13:00 -07:00
Harbor404 0a2a8b15ed fix(boot): log the resolved shell at debug level
Headless `run` must leave stderr empty unless --debug is passed, so the
boot record of the resolved shell interpreter cannot be an Info log. It is a
diagnostic; the always-on counterpart is the Warn shellrun emits when a
launch actually fails.
2026-10-01 14:11:01 +08:00
YHH 66029c7dec Merge pull request #11521 from KHG420/codex/studio-plugin-export-refusal
fix(studio): preserve plugin export failure reasons
2026-09-30 23:07:39 -07:00
esengine 43b210573b feat(studio): sub-agent rows open a full transcript, and clipped nested output offers show-all
The sub-agent list stays after delegates finish and each row opens that
sub-agent's whole transcript. A nested call whose output exceeds the 400
character preview now says so with a show-all control instead of cutting.
2026-09-30 23:02:34 -07:00
xiexian 422b47b3a0 docs(skills): describe the turn-projected catalog 2026-10-01 13:39:24 +08:00
YHH d80c290d89 Merge pull request #11519 from KHG420/codex/studio-plugin-source-confirmation
fix(studio): keep the confirmed plugin source stable during preview
2026-09-30 22:29:28 -07:00
YHH 594cbf664a Merge pull request #11520 from KHG420/codex/studio-package-action-errors
fix(studio): expose failed package operations alongside diagnostics
2026-09-30 22:28:38 -07:00
YHH 7bb8031e85 Merge pull request #11514 from KHG420/codex/studio-shared-project-skill
docs(skills): verify sharing a project playbook across collaborators
2026-09-30 22:10:17 -07:00
xiexian 188df0245b fix(studio): preserve plugin export refusal details 2026-10-01 13:04:18 +08:00
xiexian c2df8e3ed6 fix(studio): reveal package action failures with diagnostics 2026-10-01 12:54:56 +08:00
xiexian 104d01ba07 fix(studio): keep plugin source stable during preview 2026-10-01 12:43:45 +08:00
YHH e977923203 Merge pull request #11509 from KHG420/codex/studio-mcp-partial-retry
fix(studio): preserve partial MCP installation results on retry
2026-09-30 21:34:17 -07:00
YHH 5450ed38e9 Merge pull request #11510 from KHG420/codex/studio-backup-plugin-owner
fix(studio): isolate plugin selections during backup restore
2026-09-30 21:33:27 -07:00
xiexian 3fcce85ca7 docs(skills): cover repository sharing and personal activation 2026-10-01 12:26:06 +08:00
Harbor404 1650571850 fix(shell): keep the PowerShell probe within lint budgets
Trim repolint comment blocks and move launch-failure logging out of RunForeground so the function stays within the 120-line limit. No behavior change.

Signed-off-by: Harbor404 <[email protected]>
2026-10-01 12:07:06 +08:00
xiexian d6cc51fe76 fix(studio): keep restored plugin installers with their selection 2026-10-01 11:50:07 +08:00
xiexian abca1a62c5 fix(studio): retain partial MCP outcomes and retry remaining servers 2026-10-01 11:45:26 +08:00
YHH b70b86e7a7 Merge pull request #11494 from esengine/fix/headless-trust-silence
fix(run): say why a headless run in an untrusted folder did nothing
2026-09-30 20:38:05 -07:00
xiexian 86f5ea0c5a fix(sdk): make starter interceptor work on composed inputs 2026-10-01 11:36:41 +08:00
YHH 754166fc32 Merge pull request #11502 from KHG420/codex/studio-plugin-install-confirmation
fix(studio): preserve new plugin confirmation during install
2026-09-30 20:34:00 -07:00
YHH 0aeb204660 Merge pull request #11500 from KHG420/codex/studio-mcp-install-controls
fix(studio): keep MCP install confirmation stable while pending
2026-09-30 20:33:10 -07:00
YHH 1fa7030ced Merge pull request #11499 from KHG420/codex/studio-mcp-partial-install-notification
fix(studio): refresh inventory after partial MCP installation
2026-09-30 20:32:20 -07:00
YHH 43661cdec1 Merge pull request #11498 from KHG420/codex/studio-mcp-install-connection
fix(studio): reset MCP installation state on connection changes
2026-09-30 20:31:30 -07:00
YHH e07bc5faa8 Merge pull request #11496 from KHG420/codex/studio-mcp-operation-owner
fix(studio): scope MCP row confirmations and responses
2026-09-30 20:30:40 -07:00
YHH a968fd8881 Merge pull request #11497 from KHG420/codex/studio-inspection-shortcut-guard
fix(studio): guard installation inspection shortcuts
2026-09-30 20:29:50 -07:00
YHH 420a9231c5 Merge pull request #11458 from KHG420/codex/studio-project-order-11420
feat(studio): persist manual project order without changing launch project
2026-09-30 20:29:00 -07:00
YHH 11b84e5891 Merge pull request #11481 from KHG420/codex/studio-frontend-page-example
docs(studio): add a local frontend delivery example
2026-09-30 20:28:10 -07:00
YHH 0495d1f294 Merge pull request #11485 from KHG420/codex/studio-author-settled-notification
fix(studio): preserve author install notifications after sign-out
2026-09-30 20:27:20 -07:00
YHH 0040dec2a0 Merge pull request #11486 from KHG420/codex/studio-runtime-reload-connection
fix(studio): scope runtime reload state to its connection
2026-09-30 20:26:31 -07:00
YHH 4b94a02c27 Merge pull request #11489 from KHG420/codex/studio-package-operation-connection
fix(studio): reset package operation state on connection change
2026-09-30 20:25:41 -07:00
YHH 28a9010d0e Merge pull request #11475 from KHG420/codex/studio-url-elicitation-11348
feat(mcp): support consent-based URL elicitation
2026-09-30 20:24:40 -07:00
xiexian 2b4d79ceb5 test(boot): cover copied command lifecycle at provider boundary 2026-10-01 11:22:34 +08:00
xiexian 37924b57e4 test(studio): verify installed hook context lifecycle 2026-10-01 11:09:51 +08:00
Harbor404 e69108a843 fix(mcp): preserve disabled tool policy
Project configuration could replace a user server entry and drop disabled_tools, while a stale call to a filtered tool was reported as unknown. Merge user and project disables, reject empty names, and carry a typed refusal through boot and hot-added MCP registration.

Move an existing contextual-tool helper unchanged so execute_one.go does not grow past the file-size ratchet.
2026-10-01 11:09:15 +08:00
Harbor404 9b5ca5ff71 fix(shell): only trust a PowerShell that starts, and record which ran
A pinned [tools.shell] path was honoured on existence alone, so a pwsh.exe
that stats fine but will not start was held for the whole session while every
command failed at launch — the failure #10633 reported, still reachable
through the pinned-path branch after #10617 reworked auto-detection.

- Prove a pinned PowerShell the way the bash arm already proves its own, and
  fall back to detection with a warning when it will not start.
- VerifyShell proves a pinned PowerShell too, so the settings screen refuses
  one where it was typed rather than on every later command.
- powerShellLaunches answers false for a path that is not there: a missing
  executable is not launchable, so callers that gate on it alone now agree.
- resolveShell takes the launch probe as an argument, which puts the decision
  table ("pwsh will not start, 5.1 will") under test on any host.
- Detection that finds nothing says so instead of silently returning bare bash.
- Boot records the interpreter the session resolved to, and a launch-phase
  spawn failure records the shell, its path, the caller and the error. The
  desktop relays the sidecar's stderr into desktop-shell/logs/, which the
  report found empty.

Windows end-to-end is not verified locally; the fallback is covered by the
injected probe on Linux and macOS.
2026-10-01 11:07:32 +08:00
xiexian 71fb27901b fix(studio): preserve new plugin confirmation during install 2026-10-01 10:42:52 +08:00
xiexian 223c55b95f fix(studio): keep MCP install confirmation stable while pending 2026-10-01 10:34:49 +08:00
xiexian d84543d259 fix(studio): refresh inventory after partial MCP installation 2026-10-01 10:17:49 +08:00
xiexian 0247623274 fix(studio): reset MCP installation state on connection changes 2026-10-01 10:05:31 +08:00
YHH 13f238e6f4 Merge pull request #11492 from KHG420/codex/studio-mcp-operation-guard
fix(studio): guard conflicting MCP row operations
2026-09-30 19:00:24 -07:00
YHH 0209b345a0 Merge pull request #11495 from KHG420/codex/studio-skill-operation-owner
fix(studio): scope skill operation results and refresh current inventory
2026-09-30 18:57:14 -07:00
esengine 8fa67e544e fix(run): say why a headless run in an untrusted folder did nothing
A run in a folder with no trust decision refused every edit and command
under the asking default and exited 0 with an empty stderr (plain text
run) and only the generic 'no interactive approver' reason for the model.

The refusal now carries permission.untrusted_folder. The model reads the
cause and the remedy, the text run prints refused tools, code and remedy on
stderr, the structured result's denial carries a remedy, and
--fail-on-unverified exits 3 for it. Exit 0 and the trust gate itself are
unchanged.

Refs #11493
2026-09-30 18:55:48 -07:00
xiexian fe850bc160 fix(studio): guard installation inspection shortcuts 2026-10-01 09:50:50 +08:00
xiexian f527fa023d fix(studio): scope MCP row confirmations and responses 2026-10-01 09:41:52 +08:00
YHH d98ae85fb0 Merge pull request #11490 from esengine/fix/feedback-reply-test-race
test(feedback): read the reply stub's hits under its lock
2026-09-30 18:34:38 -07:00
xiexian 687186fcdd fix(studio): scope skill results and refresh current inventory 2026-10-01 09:31:31 +08:00
xiexian d30fd833d9 fix(studio): guard conflicting MCP row operations 2026-10-01 09:08:42 +08:00
esengine 459c2ae5c4 test(feedback): read the reply stub's hits under its lock 2026-09-30 18:00:24 -07:00
xiexian 33199377da fix(studio): reset package operation state on connection change 2026-10-01 08:57:14 +08:00
xiexian 1f1c469b16 fix(studio): scope runtime reload state to its connection 2026-10-01 08:51:01 +08:00
xiexian 7fe57fc25a fix(studio): preserve author install notifications after sign-out 2026-10-01 08:43:39 +08:00
YHH 6cc249f1d4 Merge pull request #11482 from esengine/release/studio-2.24.0-notes
docs(release): Studio 2.24.0 release notes
2026-09-30 17:36:02 -07:00
xiexian e5a801c603 feat(studio): persist manual project order without changing launch project 2026-10-01 08:35:14 +08:00
esengine fe5ea48568 docs(release): Studio 2.24.0 release notes 2026-09-30 17:29:36 -07:00
YHH 3aa259f2e2 Merge pull request #11480 from KHG420/codex/studio-publish-connection-state
fix(studio): reset publish form when its owner changes
2026-09-30 17:26:57 -07:00
YHH ed9c424991 Merge pull request #11474 from KHG420/codex/studio-api-notes-example
docs(studio): add a local API documentation scenario
2026-09-30 17:26:07 -07:00
YHH 9328ef003a Merge pull request #11478 from KHG420/codex/studio-market-connection-state
fix(studio): refresh market state across runtime connections
2026-09-30 17:24:07 -07:00
xiexian 824f95613a feat(mcp): support consent-based URL elicitation 2026-10-01 08:19:31 +08:00
xiexian 03d5346869 docs(studio): add a local frontend delivery example 2026-10-01 08:15:01 +08:00
YHH 490f3d0bfe Merge pull request #11479 from esengine/fix/11434-host-handshake
fix(studio): wait longer for a cold kernel start, retry an early exit once, and name the cause
2026-09-30 17:03:11 -07:00
esengine 64c8aa5170 fix(studio): wait longer for a cold kernel start, retry an early exit once, and name the cause
The shell gave the kernel 20 s to handshake and reported every failure as a
sentence. The wait is now 60 s (REASONIX_STUDIO_HANDSHAKE_TIMEOUT_MS), a small
starting window appears after 3 s, an exit or spawn failure within 10 s is
retried once, and launch failures carry a typed code (timeout, exited, spawn,
overflow) with a matching explanation in the error box.

Refs #11434
2026-09-30 16:54:16 -07:00
xiexian 00d9c2e490 docs(studio): add a source-linked API guide example 2026-10-01 07:48:20 +08:00
xiexian ff1163da75 fix(studio): refresh market state across runtime connections 2026-10-01 07:47:55 +08:00
xiexian 6cd9593404 fix(studio): reset publish form when its owner changes 2026-10-01 07:44:43 +08:00
YHH b3d7dd50fc Merge pull request #11476 from KHG420/codex/studio-compatible-mcp-ownership
fix(studio): preserve package ownership on explicit MCP connection
2026-09-30 16:37:16 -07:00
YHH f8e541a86b Merge pull request #11466 from KHG420/codex/studio-author-apply-generation
fix(studio): isolate author install responses by connection
2026-09-30 16:35:26 -07:00
YHH b5ea839c94 Merge pull request #11446 from esengine/feat/feedback-replies-ui
feat(studio): reply threads, needs-input and unread badge in the feedback dialog
2026-09-30 16:07:35 -07:00
xiexian 4c8eda7344 fix(studio): preserve package ownership on explicit MCP connection 2026-10-01 06:59:24 +08:00
xiexian dfd8aac901 fix(studio): isolate author installs across connection changes 2026-10-01 06:56:28 +08:00
YHH f69e2c5798 Merge pull request #11472 from KHG420/codex/studio-issue-fix-example
docs(plugins): add a runnable repository issue exercise
2026-09-30 15:39:20 -07:00
YHH 1918bb4f55 Merge pull request #11357 from BuGlessRB/open/ask-autosubmit
feat(cli): auto-submit a fully answered ask batch, gated on auto_submit / 功能(cli):多题问询答完最后一题即自动提交,由 auto_submit 控制
2026-09-30 15:38:35 -07:00
YHH 53d21288df Merge pull request #11465 from KHG420/codex/studio-market-retry-focus
fix(studio): preserve focus through market read recovery
2026-09-30 15:36:09 -07:00
YHH 3c0c265c22 Merge pull request #11467 from KHG420/codex/studio-compatible-mcp-effect
test(plugins): cover compatible MCP install lifecycle
2026-09-30 15:15:30 -07:00
YHH e179b51c52 Merge pull request #11469 from KHG420/codex/studio-update-feedback
fix(studio): preserve applying updates across settings controls
2026-09-30 15:10:44 -07:00
YHH 28b6680353 Merge pull request #11468 from KHG420/codex/studio-disabled-mcp-tools-5444
feat(studio): support disabling individual MCP tools
2026-09-30 15:02:44 -07:00
xiexian 788d2b8d41 fix(studio): preserve keyboard focus across market read recovery 2026-10-01 05:54:19 +08:00
YHH f5e0d83f0d Merge pull request #11454 from KHG420/codex/studio-hook-event-labels-11383
fix(studio): localize automation event labels
2026-09-30 14:48:18 -07:00
xiexian 3b21d25de8 docs(plugins): add a runnable repository issue exercise 2026-10-01 05:42:27 +08:00
xiexian c3b51178cb fix(studio): preserve applying updates across settings controls 2026-10-01 05:26:48 +08:00
YHH 18df489e23 Merge pull request #11449 from KHG420/codex/studio-marketplace-subdir-11378
fix(studio): install git-subdir marketplace plugins
2026-09-30 14:16:47 -07:00
YHH 5598442a99 Merge pull request #11459 from KHG420/codex/studio-bundled-skill-effect
test(studio): verify copied skill references reach the provider
2026-09-30 14:15:50 -07:00
YHH 3b8bb5492b Merge pull request #11460 from KHG420/codex/studio-update-panel-guard
fix(studio): isolate inline package updates through apply
2026-09-30 14:13:47 -07:00
xiexian 1718074787 test(plugins): cover compatible MCP install lifecycle 2026-10-01 05:11:41 +08:00
xiexian bb013c4bc9 feat(studio): filter disabled MCP tools before registration 2026-10-01 05:09:47 +08:00
YHH 1cbd447539 Merge pull request #11456 from KHG420/codex/studio-author-read-races
test(studio): cover stale author package reads
2026-09-30 14:09:11 -07:00
Stephen R. van den Berg 5b609815c5 feat(cli): auto-submit a fully answered ask batch, gated on auto_submit
Answering the last question of a multi-question ask with nothing left
unanswered commits the batch immediately, skipping the extra confirmation
Enter; an incomplete batch jumps to the first unanswered question instead. The
behaviour is gated on a new top-level `auto_submit` option (default false):
with it on the ask drops its Submit tab, and without it the Submit tab is shown
and still needs its explicit Enter.

`auto_submit` is a user preference: LoadForRoot holds it across the project
merge so a cloned repository cannot turn it on, and neither the annotated
project render nor the project delta carries it. Answering moves on to the next
unanswered question, so answering one the panel was thrown back to skips ahead
to the next gap rather than stopping on the already-answered question that
follows it; only the last question, with nothing left unanswered, commits. With
auto-submit on there is no Submit tab to page onto, so right stops at the last
question rather than landing on a hidden tab that commits.

- config: new top-level auto_submit, user/global only; rendered in the user's annotated TOML and omitted from a project render and the project delta.
- tui: Options.AutoSubmit; nextQuestion advances to the next unanswered question and commits on the last; right stops at the last question instead of a hidden Submit tab.
- program_test.go: the commit, next-unanswered-after-skip-back, skipped-question, hidden-tab, no-option and arrow-stops-at-last cases.
- docs: auto_submit in CONFIG_PATHS.md, reasonix.example.toml, and the Guide shortcut tables (en and zh-CN).
2026-09-30 22:42:33 +02:00
Stephen R. van den Berg 0102bf3a6a refactor(config): split provider-source scope and the [ui] section into files
Move the providerSourceScope type out of config.go and the [ui] table render
out of render.go into their own files, so both sit under their recorded
repolint budgets with room for the options that follow.
2026-09-30 22:42:33 +02:00
YHH 873990724d Merge pull request #11445 from esengine/feat/feedback-replies-kernel
feat(feedback): reply threads, triage statuses and unread state in the client
2026-09-30 13:38:50 -07:00
xiexian 9d950f34bc fix(studio): describe automation events in the UI language 2026-10-01 04:38:08 +08:00
xiexian e9882d667e fix(studio): keep package updates isolated until apply settles 2026-10-01 04:27:31 +08:00
YHH 0af416ce89 Merge pull request #11453 from KHG420/codex/studio-install-preview-i18n
fix(studio): localize installation trust disclosures
2026-09-30 13:24:20 -07:00
YHH ee625f5e41 Merge pull request #11443 from KHG420/codex/studio-own-installed-entry
fix(studio): locate capabilities after author installs
2026-09-30 13:23:25 -07:00
xiexian d5a5a6e70f test(studio): verify copied skill references reach the provider 2026-10-01 04:13:52 +08:00
YHH 53b82d1362 Merge pull request #11442 from KHG420/codex/studio-market-retry
fix(studio): retry failed market list and detail reads
2026-09-30 13:10:30 -07:00
xiexian 5c78af6b09 fix(studio): install git-subdir marketplace plugins 2026-10-01 04:09:53 +08:00
xiexian dfbe776afb fix(studio): localize installation trust disclosures 2026-10-01 04:06:09 +08:00
xiexian 0056f8386b fix(studio): locate capabilities after author installs 2026-10-01 03:53:40 +08:00
YHH bf8210aebf Merge pull request #11439 from esengine/fix/skill-invocation-flags
fix(skill): honour disable-model-invocation, user-invocable and argument-hint
2026-09-30 12:53:16 -07:00
xiexian 88966f614f test(studio): cover stale author package reads 2026-10-01 03:46:07 +08:00
YHH 46542dd558 Merge pull request #11450 from KHG420/codex/studio-package-operation-guard
fix(studio): guard concurrent package operations
2026-09-30 12:36:43 -07:00
YHH 4376074c23 Merge pull request #11447 from KHG420/codex/studio-plugin-update-effect
test(studio): verify plugin updates replace MCP contributions
2026-09-30 12:35:53 -07:00
YHH ff6f93280e Merge pull request #11444 from KHG420/codex/studio-author-read-retry
fix(studio): retry failed author market reads
2026-09-30 12:34:57 -07:00
xiexian 651d2bd4df fix(studio): retry failed market list and detail reads 2026-10-01 03:32:43 +08:00
YHH d6eed9aee4 Merge pull request #11422 from esengine/feat/model-overrides-11392
feat(studio): per-model context window and max output in the provider form
2026-09-30 12:18:34 -07:00
YHH 695802235e Merge pull request #11435 from esengine/feat/feedback-ui
feat(studio): feedback dialog with screenshots, environment block and my-feedback list
2026-09-30 12:17:36 -07:00
esengine 5d786d6de0 feat(studio): reply threads, needs-input and unread badge in the feedback dialog
My feedback shows the thread under each report as plain-text bubbles, a
needs-your-input chip with an inline reply box (byte counter, errors per code),
status chips for answered and closed, and a New mark that stays for the visit
in which the replies were read. The sidebar entry and the command palette item
carry an unread count that is read when the app opens and when the page opens,
never on a timer. The form copy says a report becomes public only if it is
filed as an issue, and keeps the warning about secrets and screenshots.

Refs #11424
2026-09-30 12:16:20 -07:00
esengine 01a52ce15e feat(feedback): reply threads, triage statuses and unread state in the client
The kernel reads the new statuses (needs_info, answered, closed), each report's
thread and needsInput, keeps the last-seen reply id per report locally and
exposes unread/hasNew for an entry badge. A reply goes through the controller
port, serve (POST /feedback/{receipt}/reply and /seen) and the TUI
(/feedback show, /feedback reply with --yes as the first token). The new
worker codes become sentinels: feedback.reply_limit, feedback.not_replyable
and feedback.challenge_required. A reply is never retried. Thread text is
cleaned of escape sequences before any frontend sees it. Submit copy no longer
says every report becomes public.

Refs #11424
2026-09-30 12:16:12 -07:00
YHH ad99a35714 Merge pull request #11452 from esengine/ci/feedback-sync-workersdev
ci(feedback-sync): reach the worker through workers.dev
2026-09-30 12:13:05 -07:00
esengine 74318f9d49 ci(feedback-sync): reach the worker through workers.dev 2026-09-30 11:58:16 -07:00
esengine a237c5843e fix(skill): refuse user-only skills as task profiles and in capability routing 2026-09-30 11:43:20 -07:00
YHH fa85a69b9d Merge pull request #11448 from esengine/fix/feedback-sync-403-diag
fix(feedback): show the response origin and body snippet when the worker refuses the sync token
2026-09-30 11:42:27 -07:00
xiexian 1320621cbe fix(studio): guard concurrent package operations 2026-10-01 02:37:10 +08:00
esengine c116fd76ec fix(feedback): show the response origin and body snippet when the worker refuses the sync token 2026-09-30 11:32:11 -07:00
xiexian e3ae0e78ab test(studio): verify plugin updates replace MCP contributions 2026-10-01 02:27:33 +08:00
YHH 6d9266fc8e Merge pull request #11440 from esengine/feat/feedback-sync-replies
feat(feedback): mirror reporter replies, publish only released items and images
2026-09-30 11:18:25 -07:00
xiexian d2a86112a4 fix(studio): retry failed author market reads 2026-10-01 02:14:44 +08:00
esengine d08999485a feat(feedback): mirror reporter replies to their issue, publish only released items and images
The worker now holds new submissions by default. The converter still
creates issues only for received items, links only images the worker marks
released, and posts one idempotent bot comment per reporter reply on items
that have an issue, then acks it. Contact is never read by the converter,
which a test pins.

Refs #11424
2026-09-30 10:52:34 -07:00
YHH 42ef9dd191 Merge pull request #11438 from esengine/fix/remote-error-diagnosis-studio
fix(remote): classify relay connection failures and show the specific cause
2026-09-30 10:00:04 -07:00
esengine cf2146408a fix(skill): honour disable-model-invocation, user-invocable and argument-hint 2026-09-30 09:53:07 -07:00
esengine 226984ba69 fix(remote): say why a relay connection failed instead of one generic sentence
A browser cannot read the status of a refused WebSocket upgrade, so a
rate limit, a refused origin, an expired ticket, a network loss and an
unreachable gateway all read as "relay temporarily unavailable". When the
socket errors before opening, one probe round (offline check, the gateway
health route, and an attachment preflight that only an allowed origin passes)
now classifies the failure by typed code; a 429 from the account service
carries its Retry-After. The notice names the cause, offers reconnect
(counting down a rate limit), and a reconnect that runs out of retries ends
with the last attempt's cause, or idle when the relay had reclaimed the
lease. The generic sentence remains only when the probe itself fails.
2026-09-30 09:51:57 -07:00
YHH f309fed993 Merge pull request #11432 from esengine/feat/feedback-client
feat(feedback): in-app feedback client, controller port, serve endpoints, TUI /feedback
2026-09-30 09:07:05 -07:00
esengine 6db27d94ae feat(studio): feedback dialog with screenshots, environment block and my-feedback list
Refs #11424
2026-09-30 08:52:25 -07:00
esengine f72102c8a6 feat(feedback): in-app feedback client, controller port, serve endpoints and TUI /feedback
Adds internal/platform/feedback: env collection, local redaction (the
crashreport implementation, now exported), image re-encode with a metadata
self-check, idempotent submit with retry, persisted install identity and
receipts, and GET /v1/feedback/mine with typed sentinel errors.

The controller exposes it as the Feedback port (SessionAPI), serve answers
GET /feedback/env, GET /feedback/mine, POST /feedback and POST /feedback/name
with dotted refusal codes, and /feedback [list|name|<category> --yes <text>]
is a controller slash command so the TUI inherits it. Nothing reaches a model
request or the cache-stable prefix.

Refs #11424
2026-09-30 08:45:43 -07:00
YHH 7733545274 Merge pull request #11427 from esengine/feat/feedback-sync
ci(feedback): converter and status sync for in-app feedback
2026-09-30 08:25:58 -07:00
YHH fa39e7db7f Merge pull request #11379 from chuiwenwei/feat/studio-index-corpus-v2
feat(bench): give the index corpus a measured efficiency substrate
2026-09-30 08:20:39 -07:00
YHH 21b3b35210 Merge pull request #11423 from esengine/fix/11421-git-bash-discovery
fix(sandbox): find Git Bash outside Program Files when a WSL bash shadows it on PATH
2026-09-30 08:04:41 -07:00
esengine 714c4dc935 ci(feedback): sync in-app feedback into issues and report status back
Adds a scheduled converter (scripts/feedback-sync.mjs) that turns pending
Studio feedback into issues, idempotently via a hidden marker, and reports
in_progress/fixed/wontfix/duplicate back to the worker. A fix recorded as
"next" is upgraded to its studio-v tag once the tag exists.
2026-09-30 08:04:34 -07:00
Guan09 a2a429e3aa fix(studio): tighten user message action spacing 2026-09-30 23:02:44 +08:00
esengine 85810c192d fix(sandbox): find Git Bash outside Program Files when a WSL bash shadows it on PATH
exec.LookPath returns only the first bash.exe on PATH, which is normally the
WSL launcher in System32, and the fallback list covered only the default
Program Files roots. A Git for Windows install elsewhere (with just its cmd
directory on PATH) was therefore never detected, so Studio offered PowerShell
only. Candidates now also come from every PATH directory and from the Git
tree the git.exe on PATH belongs to, and the WSL launcher is rejected in the
candidate loops too.

Fixes #11421
2026-09-30 07:51:57 -07:00
Will 5e2985ab6f feat(bench): fill the index substrate with six tasks that hold on both arms
Screened 87 candidates, staged: one pass over the pool, then two more only for
the candidates that were clean on both boundary arms. The six that held on both
arms in all three passes stay, the rest are dropped, so the efficiency substrate
only contains tasks that qualify:

  quarter  i62-epochlatch, i37-dispatchlag
  half     i106-budgetseal, i70-ingestfloor
  default  i47-tenantlease, i51-routedelay

All six are SnippetStop, SnippetThenRead or DefensiveRead in each of their
eighteen judged cells - nothing post-sufficient.

What the screening found: the answer kind decides whether a candidate holds.
Half-tier candidates whose answer was a bare number never survived (0 of 8);
the same tier with a codename answer survived 11 of 20. The dirty class in both
cases was PostSufficientRetrieval - the model re-checking a value it already had
- so the shape that needs no checking is the one that holds.

Also: i07 and i10 were re-measured after the review asked for it (their old
numbers came from the grep shape they no longer use), NeverSufficient is
reported separately rather than counted as clean, and the i11/i12 rows are gone
because those tasks are not in the corpus.
2026-09-30 22:49:27 +08:00
YHH 6f5c150339 Merge pull request #11425 from esengine/fix/11418-studio-launch-workspace
fix(studio): do not open the app's own install folder as the first workspace
2026-09-30 07:40:21 -07:00
YHH 46c784ba5e Merge pull request #11402 from esengine/ci/studio-cli-pointer
ci(release): studio writes the CLI update pointer the installed updaters read
2026-09-30 07:31:32 -07:00
esengine fb3a57133b fix(studio): do not open the app's own install folder as the first workspace
The host took its working directory as the first pane's project. A Start menu
launch inherits the install directory and a Finder launch the filesystem root,
so the app's folder was listed in the sidebar again after every removal. The
directory now counts only when it is outside the app's own directory and not a
filesystem root; otherwise the first remembered folder that still exists is
opened, then the home directory.

Fixes #11418
2026-09-30 07:22:40 -07:00
esengine b5b91b1c71 feat(studio): set context window and max output per model on a provider
The provider form only edited one window and one output cap for every model
on a connection. model_overrides already carried both per model and the
kernel resolves them for requests and compaction, so the edit endpoint and
the form now read and write them.

A model with no entry inherits the connection's value, shown as a grey
placeholder; a blank field is never sent as an override, and clearing one
removes the entry.
2026-09-30 07:19:36 -07:00
esengine 7db17aff95 ci(release): studio writes the CLI update pointer the installed updaters read
A cli-pointer job publishes the Studio release's CLI archives as a GitHub
release vX.Y.Z (the only shape the 1.x updater accepts), records it
immutably in R2, then advances cli/{stable,preview}/latest.json. It reuses
the 1.x manifest validators and pointer ordering, only moves forward, and
is gated by STUDIO_PUBLISHES_CLI.

cli-gate requires CLI_PUBLISH_FROZEN so two lines never write the CLI
channels. cli-tag creates the vX.Y.Z tag with the release tag identity in
one inline step of a job that runs no repository code. An existing release
is trusted on its own checksums, never compared with a rebuild.
2026-09-30 07:16:14 -07:00
YHH e29dad555f Merge pull request #11417 from esengine/feat/scheduled-exec-3c
fix(schedule): close the remaining review findings before the ticker lands
2026-09-30 06:14:01 -07:00
esengine 539f2368e9 fix(schedule): close the remaining review findings before the ticker lands
- The host creates the schedule store directory (0700) before a command's
  sandbox is built, so the forbid-read mask exists for it on Linux and on macOS
  paths that go through a symlink.
- Document in schedule/doc.go that the confirmed digest defends only against
  sandboxed writers (Windows, sandbox off, yolo and MCP/plugin processes can
  still write the store).
- Only the model's own usage report for an executor request counts toward
  metering; estimates and side calls no longer let unmetered requests slip past.
- The job handle is released on the ready-refused, ready-timeout and cancelled
  paths.
- A stored session path must be a .jsonl file in a sessions directory under the
  state root.
- Tests: the report's untrusted mark, the stderr sanitising, the supervisor
  watch that cancels and forces the process out, and bubblewrap tests for the
  forbidden-directory mask. The child's own state check is dropped; Start
  refuses a run that is not running.

Refs #11406
2026-09-30 06:01:41 -07:00
YHH eb4641d630 Merge pull request #11416 from esengine/feat/scheduled-exec-3b
fix(schedule): close the review findings on the supervised child
2026-09-30 05:57:01 -07:00
esengine e51f8f59f6 fix(schedule): close the review findings on the supervised child
- A run whose provider reports no usage is stopped after two unreported
  requests and charged the whole per-run cap instead of 0.
- The start of a run is one manifest transition guarded by a token that only
  travels over the supervisor's pipe (its hash is what the manifest holds); the
  child announces it holds the run's lease before it is released, and the
  supervisor holds a supervision lock from before the spawn until the run is
  settled, so the reaper never sees a gap.
- The read-only posture runs no config migration, so it rewrites no config file.
- Seatbelt denies writes as well as reads under forbid-read directories, as the
  bubblewrap mask already did, so a blind overwrite of the schedule store fails.
- Windows refuses to run a child that a Job Object cannot hold; the job handle is
  released; a child whose supervisor left is forced out after a grace period.
- The stored report is marked untrusted, the session path must lie under the
  state root, the stderr tail is sanitized, the supervisor's backstops use the
  policy minimum, the child reads the same user config file the loader does.
- Tests for the mutants the review found surviving; supervised runs are bounded
  by a context so a missing kill fails instead of hanging.

Refs #11406
2026-09-30 05:40:21 -07:00
YHH c6aae2e730 Merge pull request #11413 from esengine/feat/scheduled-exec
feat(schedule): run a claimed scheduled run in a supervised child process
2026-09-30 05:30:59 -07:00
Guan09 e1f30114d2 fix(studio): show copy feedback and remove empty user header 2026-09-30 20:22:49 +08:00
esengine cbcc9551f9 feat(schedule): run a claimed scheduled run in a supervised child process
Adds the hidden `reasonix schedule exec <trigger-id>` child and a supervisor
that starts it, counts the tokens it reports and ends it, process tree and
all, when the wall clock or 1.25x the per-run token cap passes. A run that is
killed, crashes or loses its usage stream is charged the whole per-run cap.

The child holds an operating-system lease for the run, waits for the
supervisor's release line (so a run the store already settled spends nothing;
MarkRunning returning ErrRunSettled kills the child), reads its policy from the
user's configuration only, builds the read-only posture, and keeps reading its
stdin: the end of that stream is the supervisor going away, and the child then
stops and settles itself. A run that parks one kind of request too often
(max_repeat_parks, default 3, user level) is ended.

The final report and parked records are sanitized and bounded by the store on
the way in (PutResult). The schedule store directory joins the protected read
set of every run and the file tools' write guard; Grant.Tools narrows the read
tools of the posture.

Refs #11406
2026-09-30 05:05:42 -07:00
YHH 83856b0f00 Merge pull request #11408 from esengine/feat/scheduled-observe-posture
feat(boot): a read-only posture for unattended runs
2026-09-30 04:18:30 -07:00
esengine 08c717c2fa test(boot): the dot-dot case names a missing file on windows, which is not a leak 2026-09-30 04:07:35 -07:00
esengine e0b45d186a fix(builtin): resolve read paths the way the system opens them
realPath no longer cleans a POSIX path lexically before resolving it, so a
'..' after a symlink is checked against the file the system opens. On Windows
it asks for the final path through a handle, which follows junctions and
settles short names, case and the extended and UNC spellings. read_file
re-checks the scope against the open file. LockSurface freezes SetTools and
SetExtensions once assembly has installed them; Sanitize now removes
zero-width, tag and line-separator characters and runs before a record reaches
any store; a workspace root is refused when the home directory is unknown.

Refs #11406
2026-09-30 04:00:34 -07:00
esengine 2fcb221fad fix(boot): confine the read-only posture to the workspace and close review findings
Read tools take readRoots, checked after symlinks resolve, with the typed
refusal workspace.read_outside_scope; walkers, glob matches and ripgrep
(--no-follow) honour it. The posture forces both secret-protection switches
on and refuses a workspace containing the home directory or filesystem root.
No hook loads under the posture (an empty non-nil scope list). Rebuilding an
observed controller without the posture is refused (ErrObserveRebuild) and
the executor's gate and asker are locked. Approval records keep the host's
text in Summary and the model's arguments in Detail, marked untrusted and
stripped of control and bidi characters. A run may park at most 32 records,
then it is ended (ErrParkLimit). A tool outside the ceiling is refused with
posture.tool_not_allowed instead of unknown tool.

Refs #11406
2026-09-30 03:46:34 -07:00
esengine a32c4a4c3e feat(boot): a read-only posture for unattended runs
Adds a host-only Options.Observe that builds a run which can only read
files. The tool ceiling is decided by what each tool declares about its
reach (tool.Reach), enforced by a registry that refuses every other Add.
Approvals and ask questions are parked as Pending records and never
answered; the gate and asker cannot be replaced through the controller.
The workspace's reasonix.toml, .mcp.json, project hooks and folder grants
are not read, no MCP server or extension sidecar starts, and the run
context rides the turn tail as <scheduled-run>. The posture records
remote_content=false and whether an OS sandbox backs the tool filter.

Refs #11406
2026-09-30 03:25:40 -07:00
YHH a03391a13f Merge pull request #11407 from esengine/feat/schedule-store
feat(schedule): scheduled-runs store, policy and budget accounting (slice 1)
2026-09-30 03:22:53 -07:00
Guan09 23ade961ba feat(studio): 调整用户消息操作栏并增加复制按钮 2026-09-30 18:20:53 +08:00
esengine d8ad8bdc3f feat(schedule): add the scheduled-runs store, policy and budget accounting
Slice 1 of the scheduled-runs P0 (#11406): a machine-wide manifest with atomic
writes and a schema version, a Policy that only the user layer can loosen,
pessimistic budget accounting, and claim-before-start with deterministic
trigger ids. No process is started and nothing is wired to a frontend.
2026-09-30 03:14:54 -07:00
Will e47a2037f2 feat(bench): screen replacement candidates for the index substrate
The review settled the bar: a task qualifies only if it is clean on both of its
boundary arms, and none of the first six candidates is. This adds five more,
authored to the same shape but with a single value each and a call whose
arguments name the topic (./scripts/x {{cue}}), which is the shape the review
pointed at for i07 and i10.

- i15-ingest-lag and i16-commit-fence (quarter), i17-route-budget and
  i18-salvage-epoch (half), i19-tenant-quota (default).
- All five preflight clean at their own tier's generation.
- The corpus stays unqualified until six candidates hold on both arms across a
  three-pass batch; the ones that do not are dropped rather than promoted.
2026-09-30 17:52:28 +08:00
YHH 092a0272cd Merge pull request #11404 from esengine/fix/memory-overflow-11398
fix(studio): wrap long memory titles inside the row
2026-09-30 02:24:11 -07:00
esengine f151f5ba9a fix(studio): wrap long memory titles inside the row instead of overflowing
The title button in a memory row was flex:none, so a long title (or an
unbroken path/identifier) pushed the scope, date and action past the
container and made the Forget label break vertically. Let it shrink and
wrap within 60% of the row, and keep the trailing action on one line.
2026-09-30 02:11:34 -07:00
YHH 0019ad31d0 Merge pull request #11399 from esengine/ci/studio-cli-channels-no-approval
ci(release): studio cli-channels publishes without an approval environment
2026-09-30 01:55:45 -07:00
esengine 205f4ef09f ci(release): studio cli-channels publishes without an approval environment
Withdraws the studio-cli-channels environment from #11395 at the owner's
decision: no human approval. The job reads the shared NPM_TOKEN and
HOMEBREW_TAP_TOKEN. The contract test and STUDIO_RELEASE.md follow.
2026-09-30 01:45:41 -07:00
YHH 20354e388f Merge pull request #11395 from esengine/ci/studio-cli-channels-hardening
ci(release): attest and gate the CLI channels job
2026-09-30 01:07:04 -07:00
esengine 4111108fc4 ci(release): attest and gate the CLI channels job
cli-channels runs in the studio-cli-channels environment and is the only job
with id-token: write. Its registry tokens are environment-only secrets
(STUDIO_NPM_TOKEN, STUDIO_HOMEBREW_TAP_TOKEN), and its first step fails unless
the environment has a required reviewer. npm publish passes --provenance,
matching the 1.x release line. A contract test parses every workflow and pins
the permissions, the secret references and the id-token grant. npm/ changes now
trigger the CI job that runs npm/publish.test.mjs.
2026-09-30 00:49:28 -07:00
YHH fd3c7f06aa Merge pull request #11394 from esengine/fix/dns-not-conn-reset
fix(errmsg): report DNS resolution failures as such, not as a connection reset
2026-09-30 00:22:15 -07:00
YHH 9e8fd7b7f7 Merge pull request #11389 from esengine/fix/storage-survey-11385
fix(storage): bound each root's size walk and show partial results
2026-09-30 00:20:09 -07:00
esengine bfbde4dea7 fix(errmsg): report DNS resolution failures as such, not as a connection reset
neterr.IsConnReset fell back to any net.Error, so a *net.DNSError read as a
dropped stream and users saw "disconnected before completion after retry
attempts" for a host that never resolved and was never retried. DNS errors are
now excluded from the reset judgement, and explainError gives them their own
typed branch that names the host and separates a nonexistent name from a
temporary resolver failure.

Refs #11391
2026-09-30 00:14:09 -07:00
esengine 31f54d8cc7 fix(storage): bound each root's size walk and show partial results
The storage panel showed "Measuring..." forever when one root (a huge or
slow directory, common on Windows) never finished, because GET /storage
walked all five roots synchronously with no limit and the client had no
timeout.

- Survey walks each root under a 10s budget and reports a lower bound with
  Truncated set when it runs out, even if a filesystem call never returns.
- GET /storage?layout=1 answers at once with unmeasured roots and
  GET /storage?root=<id> measures one root, so a slow root no longer holds
  back the others.
- The panel draws rows immediately, fills each root in as it arrives, marks
  a truncated count as a lower bound and shows "timed out" for a root the
  kernel never answers.
2026-09-30 00:10:47 -07:00
YHH 578f55aac2 Merge pull request #11390 from esengine/fix/interrupted-recovery-precedence-studio
fix(agent): new user message outranks the interrupted-turn recovery block
2026-09-29 23:51:14 -07:00
esengine f2b9ef6561 fix(agent): new user message outranks the interrupted-turn recovery block
Port of #11386 (main-v2). The recovery block now ends with a sentence
stating that the user's new message takes precedence, so an unrelated
message no longer makes the model resume the interrupted task.

Refs #11381
2026-09-29 23:38:13 -07:00
YHH c4ed71f9ae Merge pull request #11387 from esengine/fix/workbench-files-tab-11382
fix(studio): picking a browser tab steps the docked file list aside
2026-09-29 23:14:53 -07:00
esengine 3cdf8dbd91 fix(studio): picking a browser tab steps the docked file list aside
The docked workbench hides its canvas while the file list is open, and the
browser page and its address bar are drawn in that canvas. Selecting a tab or
opening a new browser tab left the list open, so the browser never appeared.
Every selection now goes through one helper that also closes the list.
2026-09-29 23:06:48 -07:00
YHH 7ead478adf Merge pull request #11380 from chuiwenwei/test/studio-verification-stress-solutions
test(e2ebench): give the ten vs-pos tasks the reference solutions the guard needs
2026-09-29 21:48:01 -07:00
Will 01fddd508b test(e2ebench): give the ten vs-pos tasks the reference solutions the guard needs
The authoring guard skips any task without a solution/, so all twenty
verification-stress graders have only ever been checked against the pristine
seed - never against an answer that should pass. Ten now carry one.
2026-09-30 12:26:39 +08:00
YHH d5272e17c8 Merge pull request #11367 from chuiwenwei/fix/studio-memorybench-grader-witness
test(e2ebench): assert every memorybench grader accepts its task's correct answer
2026-09-29 20:18:30 -07:00
YHH fea401fa6f Merge pull request #11359 from BuGlessRB/open/diff-fence-scoped-notify
fix(cli): stop the diff formatter repainting every block when a run lands
2026-09-29 20:02:14 -07:00
Will 9eca20c760 test(e2ebench): assert every memorybench grader accepts its task's correct answer
The reference-solution test holds graders to both halves of the authoring rule
but skips memorybench, which ships no solution/ - the half #11329 fell through.
2026-09-30 11:01:59 +08:00
YHH 013caa0859 Merge pull request #11369 from chuiwenwei/fix/studio-policy-flag-row
docs(bench): add the missing -policy row to the e2ebench flag table
2026-09-29 20:01:04 -07:00
YHH c9446388ef Merge pull request #11370 from chuiwenwei/docs/studio-11362-index-corpus-bar
docs(context-retrieval): correct the Index corpus v2 qualification bar
2026-09-29 20:00:13 -07:00
YHH 3dd2b82bf0 Merge pull request #11364 from chuiwenwei/fix/studio-ablate-doc-list
fix(cli): list every ablatable module in the --ablate surfaces
2026-09-29 19:58:13 -07:00
YHH 1793d22f00 Merge pull request #11371 from chuiwenwei/fix/studio-cli-ablate-recall-search
docs(cli): list recall-search in the --ablate module list
2026-09-29 19:50:53 -07:00
Will eabc0a2660 docs(bench): add the missing -policy row to the e2ebench flag table
`-policy` is the MemoryBench experiment arm: `memory-off` hides the memory
store so a run can be compared against itself, and the only switch that
separates the two arms of that counterfactual. It is defined at
`cmd/e2ebench/main.go:268` and used in the recipe further down this README, but
the flag table never listed it.

Placed with the other suite-mode experiment arms (`-anchor`, `-cache`) rather
than beside `-ablate`, so it does not collide with the open #11364 that edits
that row.
2026-09-30 10:33:04 +08:00
Will 89962c4e98 docs(cli): list recall-search in the --ablate module list
ablation.Modules() has seven modules; the sentence documenting --ablate stops at
upstream. #11360 edited the same sentence to drop full-fold but left this one.
2026-09-30 10:30:03 +08:00
Will 7fde39d38f docs(context-retrieval): correct the Index corpus v2 qualification bar
The bar named a field that does not exist (`PostSufficientSearches`) and a mode
that cannot produce counts (`-mode=calibrate`). The gate is the run's
`stopping_class` from the `-mode=run-index` report: a task qualifies when it is
neither `PostSufficientRetrieval` nor `PostSufficientRunaway`, so `SnippetStop`,
`SnippetThenRead` and `DefensiveRead` all qualify, and a defensive read is
deliberately not counted as waste. Also records that calibrate is not a
screening tool, which is why its cue-visibility profile is the same for every
task.
2026-09-30 10:27:48 +08:00
YHH d30147226a Merge pull request #11365 from esengine/fix/cloud-desktop-post-content-type
fix(serve): give bodyless remote-desktop writes a JSON content type
2026-09-29 19:21:42 -07:00
YHH 96f223e61d Merge pull request #11360 from chuiwenwei/fix/studio-11358-retire-fullfold-arm
fix(bench): retire the FullFold ablation and the inert compaction -arm flag
2026-09-29 19:21:33 -07:00
YHH 0d526b6c4e Merge pull request #11363 from chuiwenwei/fix/studio-cm-e2e-selfcheck
fix(bench): make context-maintenance-e2e resume providerless and pin continue to one summary
2026-09-29 19:19:35 -07:00
esengine 03d5655717 fix(serve): give bodyless remote-desktop writes a JSON content type
CloudDesktop only set Content-Type when the request had a body, so a
bodyless POST such as /runtimes/{id}/close reached csrfGuard without a
JSON type and was refused with 415 request.bad_content_type. Deleting a
project or session closes its panel first, so the delete failed in the
web Studio. csrfGuard is unchanged.
2026-09-29 19:12:35 -07:00
Will 2711e55524 fix(cli): list every ablatable module in the --ablate surfaces
Shell completion, the Chinese CLI doc and the benchmarks flag table all stopped
at compaction, omitting upstream and recall-search.
2026-09-30 10:05:01 +08:00
Will 142b79f19a fix(bench): make context-maintenance-e2e resume providerless and pin continue to one summary
The benchmarks README still described this harness as the pre-#8244
comprehension/TTL/prune A/B, and taught a subcommand and flags the code has not
had since that rewrite.
2026-09-30 09:55:23 +08:00
Stephen R. van den Berg 90cf3f3f1f fix(cli): stop the diff formatter repainting every block when a run lands
A full-screen repaint draws every settled block, so when a background
[cli].diff_formatter run landed and the re-render hook was keyless, the
repaint dropped every diff block's cached rows and re-consulted the memo
for each. Once the transcript held more diff keys than the memo, the repaint
always re-missed at least one block, spawned a run for it, and that run
landed and repainted again — an endless loop of formatter runs and full
repaints.

Carry the landed DiffKey on the hook and remember, per block, the keys its
last render consulted, so a landed run drops only the blocks that asked for
its key. Gate the drop on the memo still holding the key: re-rendering a
block whose key was evicted would re-run the formatter and evict another
live key, so an evicted block keeps the rows it already shows.

The memo is raised to 128 so a normal transcript does not reach the eviction
path, and keyed by a 128-bit hash of the diff text rather than the text
itself, so an entry retains only the formatted output (~33-41% less
retained heap). Also record the formatter's 1 MiB size cap as a fallback
reason in the CLI reference.
2026-09-30 03:31:34 +02:00
Will a170f2158a fix(bench): retire the FullFold ablation and the inert compaction -arm flag
ablation.FullFold has had no reader since #8244 (397ed30ec) replaced the
multi-threshold path with the single checkpoint design: foldSource and
carryPriorDigests lost their Off(FullFold) checks together with their test, so
-arm=incremental was identical to the default, and the default already folds the
model-visible projection. The checkpoint design is the intended one, so retire
the dead axis rather than restore the old re-read.

- ablation: drop the FullFold module (constant, comment, module list entry)
- compaction bench: drop the -arm flag, the arms struct, foldArm, and the arm
  label from the report title and the JSON report
- benchmarks/README.md: drop the "Fold arms" section, and correct the paragraph
  that claimed a fold re-derives its digest from the canonical transcript
- docs/CLI.md: drop full-fold from the --ablate list

Verified: go build ./...; go vet; go test ./benchmarks/compaction/
./internal/contract/ablation/; go run ./tools/repolint clean; golangci-lint
v2.12.2 (the version CI pins) 0 issues. -mode=cost still reports the same eight
generations, -arm is now an unknown flag, and the JSON report no longer carries
an arm label.

Fixes #11358.
2026-09-30 09:20:25 +08:00
esengine d04a260c69 docs(release): Studio 2.23.0 release notes 2026-09-29 17:11:16 -07:00
YHH e7d11ed91f Merge pull request #11201 from BuGlessRB/open/diff-fences
feat(cli): render diff fences, an external diff formatter, and shell results that are whole diffs / feat(cli):渲染 diff 围栏、外部 diff 格式化器,以及整体为 diff 的 shell 结果
2026-09-29 16:47:13 -07:00
Stephen R. van den Berg c3e4c641fb refactor(config): move CLIConfig into its own file
studio's config.go sits exactly at its file-size budget, so the diff-fences
feature's added [cli] fields tipped it over repolint's ratchet. Extract the
CLIConfig type into cli.go, alongside the existing render_cli.go, to keep the
budget.
2026-09-29 22:49:16 +02:00
Stephen R. van den Berg 519a4acc11 feat(cli): render a shell result whose whole output is a diff
A shell tool result whose whole output is a unified diff — bash running
`git diff`, or `git show` in its default format — is tagged and rendered as a
diff: the TUI draws it through the same rows a ```diff fence produces, ACP
fences it for its client, and the desktop card renders it with DiffView.

The tag is computed once, at the one sink every emitter shares, and is gated by
the opt-in [agent].embedded_diff_detection (default false). Detection is
whole-text and conservative (diff.IsUnifiedDiff): every line must belong to the
diff grammar — including a `git show` / `git log -p` commit header and its
indented message — and a lossy Bound is never tagged, so a diff followed by
prose, a build log, or a truncation note stays prose. The tag is re-derived when
a session is rebuilt from /history, so a reopened session renders the same diff
the live one did.

The desktop DiffView splits a multi-file diff into per-file sections by the same
rule the CLI uses (git's "diff --git", else the "--- "/"+++ " pair) and names
each with its own "path +A -B" header row, so a `git diff` over several files no
longer shows only its first file's name over rows that belong to all of them.
A single-file diff keeps the card headline as its only name, so the fact is not
said twice.

trimDiffPreamble returning "" for a section with no --- line left a ```diff
fence without a --- /+++ pair rendering as an empty block. splitDiffSections now
returns sections untrimmed; the fence path trims each and falls back to the plain
code rail when it carries no file header, while the shell-diff path still drops a
pure preamble. A marked shell diff keeps the card's "⎿" connector on its first
row, like every other tool card's body. /history reads the embedded-diff flag the
session was built with instead of reloading config per request, and the hunk
parser rejects a body that overshoots its declared count.
2026-09-29 22:45:17 +02:00
Stephen R. van den Berg 4dfab0faf5 feat(cli): format diffs with an external command via [cli].diff_formatter
[cli].diff_formatter names an optional external argv line (run without a shell)
that formats a diff before the CLI/TUI emits it — both a fenced
```diff/```patch block and a writer tool's diff card. The whole diff goes to the
command's stdin and its stdout is re-emitted with non-SGR control sequences
stripped. On failure, timeout, empty output, or an oversized block the built-in
renderer is used. Like [cli].update_channel it is user/global only: a
project-local reasonix.toml cannot run a command on the user's machine.

The run is memoised per (block content, width), because a View re-renders many
times while a fence streams and again on every resize. It runs only on a closed
fence, so a fence still streaming spawns no subprocess per delta, and the command
is handed the terminal width via COLUMNS with its output clamped to it. A
WaitDelay bounds the wait for a formatter's pipes, so one that leaves a child
holding stdout cannot block the render past the timeout. The writer card drops
the "--- /+++ " pair before piping, so the formatter cannot re-emit a raw host
path the card header already names.

This commit also wires [cli].diff_fences, the opt-in toggle that turns the
fenced-diff renderer on.
2026-09-29 22:45:17 +02:00
Stephen R. van den Berg 4622fd295a feat(cli): render ``diff / ``patch fences as a diff
A fenced ```diff or ```patch block in the answer stream is drawn through the
colourised diff renderer — add/remove backgrounds, +/- gutter, line numbers,
chroma — instead of the generic code rail. A multi-file git-style fence is split
into per-file sections, each with a bold(accent(path)) +A -B header. Copy mode
keeps the plain rail path so the copied text stays byte-identical to the fence
source.

A fence that already carries ANSI (a pasted colourised diff) is shown verbatim,
with non-SGR control sequences stripped so a hostile payload cannot drive the
terminal.

It is off by default: the renderer runs only when activeDiffFences is set, so a
fence a model writes stays on the plain code rail unless the user opts in.
2026-09-29 22:45:17 +02:00
YHH 14c3585759 Merge pull request #11346 from KHG420/codex/studio-memorybench-trajectory-11323
fix(memorybench): reject answer reads found in trajectories
2026-09-29 12:10:31 -07:00
xiexian 324147354e fix(memorybench): fail runs that read answer files in trajectories 2026-09-30 02:32:08 +08:00
YHH 8c196cc72e Merge pull request #11351 from esengine/fix/npm-pinned-source
fix(installsource): accept an exact semver pin on npm package sources
2026-09-29 10:51:27 -07:00
esengine c6031016b1 fix(installsource): accept an exact semver pin on npm package sources
`@scope/[email protected]` and `[email protected]` were rejected by both
looksLikePackage implementations (Go installer and registry publish
validation), so an npm MCP entry could not pin its version. Both now accept
an optional `@<exact semver>` suffix and still refuse ranges, dist-tags,
empty versions, a repeated `@`, and anything with whitespace, shell
metacharacters, path segments or scheme prefixes.

A leading `-` is now refused as well, so a bare source can no longer be read
by npx as an option. The default MCP name drops the version suffix.

Pinning the top-level package does not pin its transitive dependencies.
2026-09-29 10:41:52 -07:00
YHH 7c9c4ddb2c Merge pull request #11344 from esengine/docs/standard-contracts-2
docs: bring the contract documents up to the docs standard (batch 2)
2026-09-29 10:11:46 -07:00
YHH 69999fe72e Merge pull request #11335 from KHG420/codex/studio-market-trust-badge
fix(studio): clarify market trust badge
2026-09-29 10:09:29 -07:00
YHH 8479846661 Merge branch 'studio' into docs/standard-contracts-2 2026-09-29 10:02:28 -07:00
YHH 26ba59a21c Merge pull request #11258 from BuGlessRB/open/expose-stream-idle-timeout
feat(provider): per-provider stream idle timeout (idle_timeout_seconds) / 功能(provider):按 provider 设置流空闲超时(idle_timeout_seconds)
2026-09-29 09:40:21 -07:00
esengine 0e03a900b5 docs: bring the contract documents up to the docs standard
Add owner headers to twelve contract and design documents, split their
over-wide paragraphs into rules, route their ownership to the primary
named in CODEOWNERS, and treat the generated protocol index as a
machine-read file. Baseline: doc-owner 40 -> 27, doc-prose 2276 -> 2147.
2026-09-29 09:40:04 -07:00
YHH 7d068b874c Merge pull request #11341 from KHG420/codex/studio-installed-sidecar-effect
test(extension): verify installed SDK sidecar lifecycle
2026-09-29 09:28:30 -07:00
YHH 56b843150c Merge pull request #11340 from KHG420/codex/studio-memorybench-answer-isolation-11323
fix(memorybench): isolate committed answers from graded runs
2026-09-29 09:25:58 -07:00
YHH 5c69bbac54 Merge pull request #11337 from KHG420/codex/studio-market-page-overlap
fix(studio): dedupe overlapping market pages
2026-09-29 09:25:03 -07:00
Stephen R. van den Berg 1a0f26d06d feat(provider): per-provider stream idle timeout (idle_timeout_seconds)
Add idle_timeout_seconds, a per-provider override of the 300s stream idle
watchdog. One knob bounds both windows a model call can spend silent: the
transport's pre-header deadline (prefill) and the read loop's inter-event
watchdog (generation). Zero or unset keeps the default.

The bound holds where the value is read: IdleTimeoutFromExtra clamps to
[1s, 32767s], so a hand-edited value — which never passes UpsertProvider's
validation — cannot overflow time.Duration(v) * time.Second into a negative
window the transport reads as "no timeout". validateProvider still rejects an
out-of-range value on the edit path.

The stall error names idle_timeout_seconds, and the docs note that a silent
pre-header wait is retried once, so the effective hold can reach ~2x the value.
2026-09-29 17:52:35 +02:00
xiexian 61f8c4e106 test(extension): verify installed SDK sidecar lifecycle 2026-09-29 23:40:08 +08:00
YHH c3f0838207 Merge pull request #11332 from KHG420/codex/studio-multifile-skill-example
docs(studio): add multi-file skill package example
2026-09-29 08:35:58 -07:00
YHH 5e838e4342 Merge pull request #11325 from esengine/fix/studio-update-delta-robust
fix(update): keep Studio's delta on another volume and make full downloads resumable
2026-09-29 08:27:26 -07:00
YHH 07d3ffdd00 Merge pull request #11331 from Guan0923/fix/studio-composer-send-visibility
fix(studio): hide send arrow for empty live drafts
2026-09-29 08:26:26 -07:00
YHH 0dee47f5f3 Merge pull request #11321 from Guan0923/fix/studio-project-path-marquee
fix(studio): wrap long workspace paths inside project menus
2026-09-29 08:25:17 -07:00
xiexian e4b673621e fix(memorybench): deny answer checkout during graded runs 2026-09-29 23:23:45 +08:00
YHH f11b121a50 Merge pull request #11330 from Guan0923/fix/studio-stale-ask-after-rewrite
fix(studio): clear stale ask cards after rewriting a message
2026-09-29 08:20:26 -07:00
YHH 8b083a107c Merge pull request #11333 from esengine/fix/memorybench-contradiction-grader
fix(memorybench): mb-contradiction grader can never pass
2026-09-29 08:16:17 -07:00
xiexian 46baa69def fix(studio): dedupe overlapping market pages 2026-09-29 23:16:15 +08:00
YHH 5b9b54596a Merge pull request #11327 from KHG420/codex/studio-shell-progress-codepage-11311
fix(studio): decode code-page shell progress before TUI display
2026-09-29 08:09:29 -07:00
xiexian f4564fd810 fix(studio): clarify market trust badge 2026-09-29 23:03:01 +08:00
esengine cb220f64e5 fix(update): keep Studio's delta on another volume and make full downloads resumable
A Studio install on another drive than the update cache never took the
chunked update: the swap's pre-flight renamed a probe from the install
into the cache, and a rename cannot cross volumes. The swap backup now
lives inside the install (<install>\.reasonix-update\<version>), the one
place guaranteed to share its volume; staged files still come from the
cache and are copied in when the rename fails.

Chunks are fetched through update.Transport, so they take the IPv4
route from the second attempt as the full download does, and chunks a
round could not fetch get a second round instead of voiding the delta.

Why a delta was abandoned is now a typed code (update.delta.*) that the
step giving up attaches; it rides on Progress.DeltaSkipped through the
full download and its failure, is logged, and the version panel says it
is downloading the full package and why.

Full downloads drop a connection that delivers nothing for 30 seconds
and resume on a new one; only attempts that made no progress count
against the retry budget. The artifact is written to a partial file in
the update cache named by its digest, so a timed-out or restarted update
resumes instead of starting over; a partial that fails verification is
removed. A body the manifest's size rules out (an error page served as
200) fails that address and moves to the fallback without being resumed.
With stalls caught, the fetch deadline is two hours rather than thirty
minutes.

A file copied in from another volume is written under a temporary name
beside its destination and renamed into place, so a crash mid-copy never
leaves a half-written file at the install path. A backup directory the
install refuses to create is ErrTreeNotSwappable. The delta phase has
its own 40-minute budget (update.delta.timed_out) and gives up after 16
chunks fail in a row, so a mirror that has gone quiet leaves the full
package its time. The reason is also exposed to screen readers.
2026-09-29 07:54:23 -07:00
YHH a2015d29ca Merge pull request #11328 from KHG420/codex/studio-mock-theme-installed-location
fix(studio): show installed theme location in market demo
2026-09-29 07:53:34 -07:00
YHH 29beca2922 Merge pull request #11324 from KHG420/codex/studio-market-skill-assets-guide
docs(studio): clarify market skill asset packaging
2026-09-29 07:52:28 -07:00
esengine c55bcc1f8d fix(memorybench): stop mb-contradiction from rejecting its own required answer
"pnpm install" contains "npm install" as a substring, so verify.sh's
negative grep matched the very answer the positive grep demanded, and the
task could never pass. Disambiguate the negative check so it only matches
"npm install" when it is not preceded by "p", instead of anchoring the
positive check to line start, which would have moved the grader out of
line with every sibling task's plain substring convention.

Also extends TestSolvableCorpusSeedsMustNotGradeClean to the memorybench
corpus (it was not covered by the existing e2e/verification-stress/train
authoring guard) and adds a targeted regression test pinning the four
cases worked through by hand in the report.

Credit to chuiwenwei for the root-cause analysis and both candidate fixes.

Fixes #11329
2026-09-29 07:52:06 -07:00
xiexian 2abcc7637e docs(studio): add multi-file skill package example 2026-09-29 22:51:14 +08:00
Guan09 78a67947ec fix(studio): hide send arrow for empty live drafts 2026-09-29 22:43:59 +08:00
Guan09 465d7e7dd9 fix(studio): clear expired ask cards before history restore 2026-09-29 22:43:00 +08:00
YHH ffc0bec51b Merge pull request #11326 from KHG420/codex/studio-installed-mcp-direct-effect
test(studio): exercise installed standalone MCP through boot
2026-09-29 07:42:07 -07:00
xiexian 9882d1349a docs(studio): clarify market skill asset packaging 2026-09-29 22:26:04 +08:00
xiexian 18c8fba59f fix(studio): show installed theme location in market demo 2026-09-29 22:22:49 +08:00
xiexian 6075673d9e test(studio): exercise installed standalone MCP through boot 2026-09-29 22:13:32 +08:00
YHH 3b60665854 Merge pull request #11322 from esengine/fix/studio-update-mirror-first
fix(update): fetch Studio artifacts from the mirror first, GitHub as fallback
2026-09-29 07:09:04 -07:00
YHH d59ed66dbc Merge pull request #11315 from KHG420/codex/studio-package-mcp-effect
test(studio): exercise installed plugin MCP through boot
2026-09-29 07:08:47 -07:00
YHH 35dadd8767 Merge pull request #11320 from KHG420/codex/studio-installed-http-mcp-effect
test(studio): exercise installed HTTP MCP package through boot
2026-09-29 07:06:40 -07:00
xiexian 1b7ea17021 fix(shellrun): decode code-page bytes in live progress 2026-09-29 22:03:20 +08:00
YHH a9332b6954 Merge pull request #11318 from KHG420/codex/studio-installed-skill-effect
test(studio): verify installed skill reaches provider
2026-09-29 06:53:58 -07:00
Guan09 d1a27868e2 fix(studio): wrap long workspace paths in project menus 2026-09-29 21:52:46 +08:00
esengine d1ad88853c fix(update): fetch Studio artifacts from the mirror first, GitHub as fallback
The Studio manifest named only GitHub release URLs for full artifacts, so
every update downloaded the installer over the route CN users reach
slowest, even though the release workflow already uploads the same files
to the R2 mirror.

When the release is mirrored, studio-manifest now writes the mirror
address as each asset's url/sig and keeps the GitHub address in new
optional fallback/fallback_sig fields. Old clients ignore the new fields
and use the mirror, which the redirect allow-list already accepts.

The updater tries each address in order with its own retry budget,
resuming what the first delivered from the second. Signature, digest and
size checks are unchanged and apply to whichever address served the
bytes.
2026-09-29 06:52:12 -07:00
YHH 25e41cd046 Merge pull request #11305 from KHG420/codex/studio-compat-fixtures
test(studio): cover theme-only plugin install lifecycle
2026-09-29 06:45:45 -07:00
YHH 87907cb2a2 Merge pull request #11313 from par73e/fix/studio-config-followups-11310
fix(studio): handle missing config and 1.x desktop approval modes
2026-09-29 06:40:49 -07:00
xiexian 9bb3435e62 test(studio): exercise installed HTTP MCP package through boot 2026-09-29 21:40:04 +08:00
YHH 59cc24b3ab Merge pull request #11317 from esengine/fix/market-trust-unpinned
feat(market): install an unpinned approved package on explicit trust
2026-09-29 06:38:13 -07:00
xiexian 50bdcd6088 test(studio): verify installed skill reaches provider 2026-09-29 21:33:30 +08:00
YHH 250b19c052 Merge pull request #11289 from BuGlessRB/open/extract-provider-entry-render
refactor(config): split provider entry types and table rendering into their own files / 重构(config):将 provider 条目类型与表格渲染拆分到独立文件
2026-09-29 06:31:40 -07:00
YHH cd1d559097 Merge pull request #11100 from esengine/fix/plan-check-own-effects
fix(agent): a plan check's own residue does not void it
2026-09-29 06:26:46 -07:00
YHH d821485c19 Merge pull request #11287 from esengine/fix/studio-tui-scrollbar-align
fix(tui): draw every row whole and in its column on Terminal.app, conhost and Windows Terminal
2026-09-29 06:22:55 -07:00
par73e 89d987fb2d fix(studio): avoid missing-config marker writes and read 1.x approval modes 2026-09-29 21:21:22 +08:00
esengine be3fd8a425 feat(market): install an unpinned approved package on explicit trust
An approved market package whose reviewed version carries no content digest
was a dead end: the detail page showed "not pinned" and no way to install it.

It now offers "Trust and install". The request carries trust, and the kernel
plans it through install_source exactly like any other market install; the
preview's own contentDigest becomes the pin, apply must echo it, and content
that changes after the person confirmed is refused. The outcome and the ledger
record the install as unreviewed, and the confirmation says the content may
differ from what was reviewed.

A pinned version is unchanged: trust never replaces the reviewer's digest, a
digest other than the pin is refused as a version change, and moved content
still fails with market.content_changed. Without trust an unpinned version
still answers market.unpinned.

The publisher's own unreviewed install and the trusted install now share one
preview-pinned path, and market.unpreviewed is answered on both routes. The
list filter and badge read "pinned" instead of "installable", since an
unpinned package is now installable too.
2026-09-29 06:20:53 -07:00
esengine 1641c3584d fix(agent): a plan check's own residue does not void it
A command the host cannot classify gets a before/after workspace scan,
and a scan that sees a change proves a write. An inline plan check such
as `python -c "import fizzbuzz; ..."` imports the module and writes its
bytecode, so the check was proven a change and owed itself another run.

Each check now measures "after the latest change" from the latest proven
change not counting its own residue: a run of exactly that check whose
written paths a complete workspace walk named, each of them a file whose
first touch this turn was its creation, none of them a file the plan
names or any other write this turn touched. A check that deletes a file
on one run and writes it back on the next is not its creator. Anything else counts: a run that does more than the check,
a check that rewrites the source it just checked, a check that rewrites
any file that predates the turn whether or not anything named it (go
generate, a whole-tree formatter, a lockfile rewrite), a path list cut
at the observation limit or never walked, and every run of
a different check - so a rebuild by one check voids another check's pass
taken against the stale build, and a check that never ran is not settled
because the only write came from another check. The plan deliverable and
the change criterion leave out the residue of every plan check.

The walk now also runs after a call whose watched paths already proved
a change, and records which files it saw appear: a new top-level entry
proves a change on its own, and without the walk a rewrite deeper in the
tree next to it went unnamed. The receipt says when its paths came from a
complete walk (paths_complete), so residue is never read off a list that
only holds what the host happened to watch.

The executor handoff now says what counts for a verify line: that exact
command, run as written after the latest change. It used to invite the
executor to validate and adapt inferred commands, which is how most held
runs ended up running a reworded variant.
2026-09-29 06:17:42 -07:00
Stephen R. van den Berg 1356b14d2f refactor(config): split provider entry types and table rendering into their own files
Move the ProviderEntry/ProviderModelOverride types, their accessors and
validation into provider_entry.go, and the provider table rendering into
render_providers.go. Trim the moved doc comments to the <=3-line budget and
reclaim the freed repolint budget.

No behavior change: same types, accessors, validation and rendered output.
2026-09-29 15:17:09 +02:00
esengine 34ef4b0338 fix(tui): draw every row whole at the width the terminal counts
The TUI laid rows out with grapheme widths while bubbletea's renderer counts
per rune until the terminal answers the mode 2027 query, and so do the
terminals that never answer it (Terminal.app, legacy conhost). A VS16 emoji
pulled the scrollbar left, a ZWJ row lost its scrollbar cell, table lines
moved, and on conhost or behind an approval card those rows overflowed and
every row below was drawn twice. conhost also drew emoji as fallback glyphs
and `◆ ●` as `?`, and Windows Terminal drew boxes for flags and keycaps.
1.x has the alignment defect as well.

- termrender measures through one method, Cells(), with per-rune Truncate
  and Cut of its own (the library's cut whole clusters and came out wider
  than asked). The TUI sets it per rune and switches it to grapheme clusters
  on the mode report bubbletea acts on; before the program starts it asks the
  terminal how far U+26A0 U+FE0F advances, so one drawing it two wide gets
  the grapheme count without answering 2027.
- Per rune, the whole frame is drawn stand-ins that keep the per-rune count:
  joiners, selectors, keycap marks and tags go, a skin tone becomes its two
  blank cells, a regional indicator its letter, and a one-column emoji with
  only a two-column glyph is kept only before a blank.
- On conhost, told from a pseudoconsole by its window class, each rune is
  asked of the console's own font (glyph and advance) and measurement; one it
  cannot draw as counted takes a drawn symbol of the same meaning, its
  halfwidth form, a flag letter, the OS best-fit when it really maps, or
  blank cells. Windows Terminal gets letters for flags and base characters
  for keycaps and tag flags.
- Live rows full screen are drawn at the transcript's width, so a pending
  tool row is not wrapped again when it lands there.

Selection copies the original text; stand-ins change only what is drawn.
2026-09-29 06:13:14 -07:00
xiexian e0cec93a9e test(studio): exercise installed plugin MCP through boot 2026-09-29 21:03:48 +08:00
YHH 6808c5a9e0 Merge pull request #11314 from KHG420/codex/studio-scope-refresh
fix(studio): ignore stale extension scope reads
2026-09-29 05:54:31 -07:00
YHH 65ff7dab74 Merge pull request #11308 from KHG420/codex/studio-startup-notice-replay-11300
fix(serve): replay startup notices to first SSE window
2026-09-29 05:52:18 -07:00
xiexian 32a6365d87 fix(studio): ignore stale extension scope reads 2026-09-29 20:35:14 +08:00
xiexian 7d069d44dc fix(serve): replay startup notices only once per session 2026-09-29 20:35:02 +08:00
YHH 8f3ee06fd3 Merge pull request #11307 from KHG420/codex/studio-native-compat-fixture
test(studio): cover declarative plugin install lifecycle
2026-09-29 05:33:21 -07:00
YHH 4579185478 Merge pull request #11275 from KHG420/codex/studio-project-provider-save-11245
fix(config): avoid persisting built-in providers in project config
2026-09-29 05:30:21 -07:00
YHH 11876a0d61 Merge pull request #11298 from esengine/feat/studio-desktop-default-posture
feat(studio): default new window sessions from the sandbox claim and folder trust
2026-09-29 05:10:21 -07:00
xiexian eafc8177e9 test(studio): cover declarative plugin install lifecycle 2026-09-29 19:55:10 +08:00
esengine 337f2d2cb2 feat(studio): default new window sessions from the sandbox claim and folder trust
A desktop session whose person never named a posture now opens the way the
terminal does since #11256: Auto only where the OS sandbox confines writes
and the folder is trusted, Ask everywhere else (always on Windows). A named
posture - set in config or switched on the composer - is kept, and a value
this build does not know still opens in Ask with its notice.

- control: mode and "defaulted" are one approval sub-state; a trust answer
  re-derives a defaulted posture with one check-and-set under the approval
  lock, so a concurrent switch stays named. New FolderTrust port (Posture,
  DecideWorkspaceTrust). TrustableFolder resolves symlinks and refuses a
  filesystem root, anything holding a home directory, and anything inside or
  holding the Reasonix home or state directory.
- config: desktop.default_tool_approval_mode is unset by default and renders
  as a comment. A startup step releases a written "auto" once, recorded in
  desktop-posture-released.json beside the config rather than in
  config_version, which the 1.x line's own upgrades own; ask, yolo and
  unknown values stay. The studio host now runs the startup upgrades.
- serve: POST /workspace-trust, approvalDefault on /status; the hub carries
  only a named posture to later panes; a rebuild re-derives a default.
- studio: the terminal's "trust this folder?" question beside the composer
  where a sandbox would confine what trust allows, and a note with a
  one-click switch to Auto where no sandbox is in effect.
2026-09-29 04:55:03 -07:00
xiexian 9666c96f3a Merge remote-tracking branch 'fork/studio' into codex/studio-project-provider-save-11245-followup 2026-09-29 19:51:18 +08:00
xiexian 7e271b1164 fix(serve): replay startup notices to first SSE window 2026-09-29 19:49:01 +08:00
xiexian 8d03609fa1 test(studio): cover theme-only plugin install lifecycle 2026-09-29 19:43:56 +08:00
YHH d3fa67857c Merge pull request #11292 from KHG420/codex/studio-ecosystem
feat(studio): find installable market entries and installed capabilities
2026-09-29 04:33:22 -07:00
YHH f3b5febe90 Merge pull request #11295 from KHG420/codex/studio-ecosystem-docs
docs(studio): provide a tested community author workflow
2026-09-29 04:31:38 -07:00
YHH a63c2b92e4 Merge pull request #11288 from esengine/fix/studio-cli-compat-quiet-stderr
fix(cli): keep a headless run's stderr clean; diagnostics behind run --debug
2026-09-29 04:19:13 -07:00
YHH 8467e1acb2 Merge pull request #11280 from Guan0923/fix/reply-regenerate-turn
fix(studio): 修复旧回复重新生成错误地重跑最后一轮 (#11279)
2026-09-29 04:16:17 -07:00
YHH 856d899bc9 Merge pull request #11302 from esengine/fix/studio-tui-unknown-slash-e2e
fix(tui): return from Subscribe only once the stream is attached
2026-09-29 04:10:39 -07:00
xiexian c5310be184 docs(studio): add tested community author workflow 2026-09-29 19:06:43 +08:00
xiexian 21980b4234 feat(studio): prioritize installable market entries and surface installed capabilities 2026-09-29 19:03:00 +08:00
esengine a062240d9f fix(cli): keep a headless run's stderr clean; diagnostics behind run --debug
1.x's `reasonix run` and `-p` leave stderr empty on a clean run. Studio wrote
three slog records there on every run: `INFO boot: assembly timing`,
`WARN prompt refiner provider construction failed` (whenever the optional
refiner side model cannot be built) and, with -c, `INFO controller: resume
cache state`. A script that treats stderr output as a failure, or captures it
into a report, broke on every invocation.

All three are diagnostics nobody has to act on, so they log at debug level.
`run --debug` lowers the process log level while flags are parsed, before
assembly writes its first record, for whoever wants them back. Warnings the
user has to act on keep their own channel.
2026-09-29 03:56:18 -07:00
YHH d56d29f6fc Merge pull request #11235 from esengine/fix/studio-config-preserve-keys
fix(config): rewrite only the keys a user-config save changed
2026-09-29 03:55:29 -07:00
esengine 543633f143 fix(tui): return from Subscribe only once the stream is attached
/events numbers frames from where a subscriber attaches, and Subscribe
connected in the background. An action taken right after it returned could
emit its frames before the connection registered, and the stream never
carried them. TestUnknownSlashCommandNeverReachesTheModel lost its
unknown-command notice this way whenever the kernel answered faster than the
subscription attached (every time on a Linux runner with GOMAXPROCS at 1 or 2, and on the coverage job), and the TUI's
own Init has the same order: it subscribes and then submits --prompt.

Subscribe now returns once the first connection has a response in hand (the
server registers the subscriber before it writes a byte) or has failed.
2026-09-29 03:53:56 -07:00
xiexian 162ee3edbe fix(config): preserve project provider provenance on save 2026-09-29 18:51:38 +08:00
YHH 19f8d5b001 Merge pull request #11285 from esengine/fix/studio-cli-compat-headless-verdict
fix(cli): headless run exits 0 when the model finishes; verdicts go in the result
2026-09-29 03:17:25 -07:00
YHH e209cf37c8 Merge pull request #11242 from esengine/fix/studio-cli-compat-entries
fix(cli): restore 1.x entry points (chat/code, run --takeover, sessions maintenance, leading --preset)
2026-09-29 03:16:42 -07:00
YHH a1d7886609 Merge pull request #11239 from esengine/fix/studio-cli-compat-setup-help
fix(cli): reasonix setup --help prints usage instead of writing a file
2026-09-29 03:16:00 -07:00
esengine 3e5607272c fix(config): rewrite only the keys a user-config save changed
Every user-config save (reasonix config compact-ratio, the Studio settings
page, the serve settings endpoints, model and approval persistence) went
through Config.SaveTo, which re-rendered the whole file from the struct.
Anything the struct does not decode was dropped - [checkpoints],
browser.chrome_path, providers[].display_name, agent.web_search_model,
desktop.layout_style / status_bar_style_initialized,
remote.hosts[].credential_mode - and values the renderer normalises were
rewritten, so a 1.x default_tool_approval_mode = "danger-full-access"
became "ask". Losing credential_mode = "remote" flips a remote host from
using the host's credentials to tunnelling the local key.

An existing user config is now edited in place: the file is indexed by
table path (array elements by their name), the rendered form of the file
as it loads is compared with the rendered form of the config being saved,
and only the keys that differ are rewritten, added or removed. A key the
save changed but the renderer never writes (a retired field a setter
cleared) is removed, and the keys this build declares retired are still
stripped. Unknown keys and tables, comments, order, line endings and
values this build reads differently stay byte-for-byte. The edited text is
accepted only if it loads to the same configuration the full render would;
otherwise the full render is written and the previous bytes are kept
beside it as config.toml.rewrite-<time>. A missing or empty file is still
written from the template.

A config written from nothing now carries config_version = 12. The 1.x
line rewrites the whole file on start when the marker is below its latest
upgrade (12), dropping [storage], [memory] and the other tables it does
not know. This build's own upgrades still gate on version 6 and an
upgraded older file is still marked 6, so 1.x keeps running its 7-12
migrations for content that never had them.

Arrays of tables are edited per element, keyed by name. The whole array
is written from the render instead - leaving every other byte alone -
when an element the save changes or drops is not a block of its own in
the text, when an element has no name or shares one, when the save
reorders the elements, or when the file holds no such array yet (so
adding the first provider keeps the built-in ones). A new element is
placed before the next kept one, so a rename keeps its neighbours whole.
Dropped elements are removed in one pass over one index, so an element's
sub-tables are never handed to the element above it and removing many
elements is linear.

Keys and sub-tables Config does not decode are never dropped silently.
Rewriting an array carries each element's own into the rendered element
of the same name, verbatim; turning an inline table into a table carries
its plain entries as key lines. When an entry has nowhere to go (two
elements share a name, an element the load merged away, a nested table
value) the result is still written in place and the prior bytes are kept
beside it as config.toml.rewrite-<time>, as the full-rewrite fallback
does. An element the user deleted takes its entries with it.

A value the user sets is written even where the render would not show a
difference: a single-line key is rewritten when its encoded value
changed, so choosing Ask replaces a 1.x danger-full-access that already
loads as ask. A key nobody touched keeps its bytes.

A save never writes a file that does not load. system_prompt is
rendered as an escaped multi-line string (a backslash or """ in a prompt
used to produce an unparseable file). When the render still does not
load, the in-place edit is used if the lines the decoder refuses are the
same before and after the edit and the result loads as the render
describes; otherwise the save fails and the file is left as it was.
Inserted text takes the file's line ending outside strings only.

An unrecognised default_tool_approval_mode still opens a desktop window
in Ask, now with an approval_mode_unrecognized notice the frontend
localizes, and the file keeps the value until the user picks a mode.

The fixture is the file 1.x v1.39.5 writes through its own SaveTo with
the affected keys set.
2026-09-29 03:14:21 -07:00
Guan09 0efb371b9c fix(studio): regenerate replies from their own turn (#11279) 2026-09-29 18:09:35 +08:00
YHH cd360b9d84 Merge pull request #11278 from esengine/fix/studio-cli-compat-fast-fail
fix(cli): fail fast on an unreachable model service, quiet doctor, bare reasonix exits 0
2026-09-29 02:41:07 -07:00
YHH 9683f50271 Merge pull request #11277 from esengine/fix/studio-cli-compat-stream-json
fix(cli): restore 1.x's stream-json and events-jsonl line contract
2026-09-29 02:40:24 -07:00
YHH d305f13d78 Merge pull request #11284 from esengine/fix/studio-11255-short-bubble-align
fix(studio): keep short user bubbles flush right and fold their controls to icons
2026-09-29 02:37:06 -07:00
YHH 8f4ece4453 Merge pull request #11281 from esengine/fix/studio-11267-reading-width
fix(studio): lay transcript cards and the composer on one reading column
2026-09-29 02:36:21 -07:00
esengine 17d681fa2d fix(studio): keep short user bubbles flush right and fold their controls to icons
A one-word message let the edit and rewind controls widen the card, and the
bubble then sat against the card's left edge, leaving a gap on the right.
The controls row no longer sizes the card (contain: inline-size), the bubble
is pushed to the right edge, and the controls drop to icons with their
accessible names, hints and keyboard focus when their labels need more room
than the bubble gives. Whether they fit is measured, not a fixed breakpoint.

Closes #11255
2026-09-29 02:12:00 -07:00
esengine 68ec3c90e1 fix(studio): lay transcript cards and the composer on one reading column
In the reading measure the transcript was 760px wide but replies and user
turns were still capped at --doc (706px), a value left over from when the
flow was a wide container with a narrower prose measure inside it. The
steps card inside a reply and the user bubble therefore stopped 60px short
of the composer's right edge, while the ask card and notices reached it.

The column now has one source: --column (760px) in tokens.css, with --doc
derived from it. The flow is laid at --doc, the composer and its shelves
at --column, and the per-row caps are gone, so every card shares the
composer's edges. The full measure still only moves --doc: the transcript
widens and the composer keeps the reading width. The studio-only
--measure variable that duplicated --doc is removed.

cards.mjs asserts the shared edges at 1440 and 1000px in the reading
measure, and that the full measure leaves the composer unchanged; the
new checks fail against the previous stylesheet.

Closes #11267
2026-09-29 02:09:55 -07:00
YHH 0b80ee3106 Merge pull request #11262 from esengine/fix/studio-tui-shell-local
fix(tui): a ! command runs locally and does not start a model turn
2026-09-29 02:09:27 -07:00
YHH 57a1a66db8 Merge pull request #11177 from KHG420/codex/studio-config-no-unprompted-file-11174
feat(studio): show remembered project rules and default skills to user scope
2026-09-29 01:58:38 -07:00
esengine e19a65af23 fix(tui): a ! command runs locally and does not start a model turn
In 1.x a ! command is the user's own: it runs, its output is shown, and
nothing enters the conversation. The TUI handed it to RunShell, whose
answerShell puts the output into the conversation and starts a turn, so every
command cost a request and the model often replied with a question.

RunShellWith takes a ShellRun; LocalOnly skips answerShell. It sits on a new
LocalShell sub-port that only SessionAPI carries. serve's in-process /submit
takes localShell, and the TUI's shell mode sends through a RunShell client
call that sets it and echoes the command without waiting for a turn to name
it. RunShell keeps answering, so Studio is unchanged.

RunShell and its helpers move from controller.go to shell.go, which the
file-size ceiling on controller.go required.
2026-09-29 01:40:49 -07:00
esengine 4d733ecfda fix(cli): bare reasonix without a terminal exits 0 as in 1.x
With no terminal, 1.x printed usage for a bare `reasonix` and exited 0;
studio exited 2, the usage-error status, although nothing was wrong with the
command line. Session options with no terminal to open a session in remain a
usage error. bareUsage moves out of cli.go into its own file.
2026-09-29 01:40:09 -07:00
esengine fb7e7c381e fix(doctor): stop warning that use_capability is missing
`reasonix doctor` warned on every install that the built-in review and
security-review skills allow use_capability, "which is not in the current
registry". Doctor checked allowed-tools against a hard-coded name list and
passed no tools; assembly always registers the proxy. It now checks against
the compile-time built-ins and the proxy, read from their owners.
2026-09-29 01:40:09 -07:00
esengine c906f3cd45 fix(provider): fail at once when the model service refuses the connection
1.x failed a request to an unreachable model service in about 0.3s. Studio
counted every transport error except cancellation as transient, so a refused
connect walked the whole MaxRetries backoff and `reasonix run` took about
92 seconds to report it, stalling every automated check pointed at a stopped
endpoint.

A refused connection (ECONNREFUSED, or WSAECONNREFUSED on Windows), a name
that does not resolve, and a certificate that does not verify are now
permanent: no backoff changes them. They are recognised by error identity.
Resets, timeouts and retryable statuses keep their retries.
2026-09-29 01:40:09 -07:00
esengine bdf3f96684 fix(cli): headless run exits 0 when the model finishes; verdicts go in the result
1.x's `reasonix run` exited 0 once the model finished. Studio exited 1 with
`error_during_execution` whenever its final-readiness check stayed unmet (for
example, no check ran after the last write), so every 1.x script treated a
finished run as a failure.

Following the migration's decision 3 (in line with how other agent CLIs
report this):

- An unmet final-readiness judgement is a verdict, not a failed run: subtype
  success, is_error false, exit 0, with `readiness` {attempts, missing} in the
  result and in events-jsonl's run_done.
- `--fail-on-unverified` turns that verdict into exit status 3, kept apart
  from 1 (run error) and 2 (usage) and documented.
- `permission_denials` in the result stays the gate's own list; run_done
  carries its count and `-p` names the refused calls on stderr.
- `completion` repeats the turn's completion_summary when there was one.
2026-09-29 01:40:08 -07:00
esengine fb6a82c8e3 fix(cli): restore 1.x's stream-json and events-jsonl line contract
1.x's `run --output-format stream-json` stamped every line with sessionId,
turnId, seq and status, opened the turn with turn_status (queued) and
user_message, reported tool_started between a call's dispatch and its result,
and closed with turn_done; --events-jsonl carried the same lifecycle records
without content. Studio's synchronous controller path publishes no turn
lifecycle, so both streams lacked all four kinds and the envelope, and they
leaked host-internal kinds (workspace_lease and friends) that no 1.x consumer
knows.

The run's output sink now supplies the lifecycle for the turn it drives and
stamps the envelope; only the kinds 1.x emitted reach either stream.
tool_started comes from a new host-only Tool.Executed on ToolResult, set when
the call cleared every gate and ran, so a refused call reports none.

The stats recorder swallowed RecordTurnCompletion instead of passing it to the
sink it wraps, so the run sink never learned a turn had finished: a failed run
reported num_turns 0. It now forwards the signal once (a TurnDone event still
records without a second completion).

Contract tests compare a real run against v1.39.5 output kept under
testdata/v1-run: lifecycle order, envelope keys, kind vocabulary.
2026-09-29 01:40:08 -07:00
YHH 4eeb23bfce Merge pull request #11244 from SivanCola/codex/fix-studio-grep-root-globs
fix(grep): preserve search roots across symlinks and sandboxes / 修复符号链接与沙箱下的 grep 搜索范围
2026-09-29 01:39:34 -07:00
YHH 8016f8be98 Merge pull request #11264 from esengine/fix/studio-tui-transcript-noise
fix(tui): keep task bookkeeping and host notes out of the transcript
2026-09-29 01:37:52 -07:00
YHH 917061e58b Merge pull request #11263 from esengine/fix/studio-tui-help-compact-context
fix(tui): /help, /compact's card and /context's breakdown as 1.x shows them
2026-09-29 01:37:09 -07:00
YHH 7be6fee2d5 Merge pull request #11261 from esengine/fix/studio-tui-rewind-clear
fix(tui): /rewind picks a turn, /clear asks first, Esc clears and Esc Esc rewinds
2026-09-29 01:36:27 -07:00
YHH 3a97d8b439 Merge pull request #11259 from esengine/fix/studio-tui-unknown-slash
fix(tui): refuse slash commands nothing answers instead of sending them to the model
2026-09-29 01:35:45 -07:00
YHH 1a3aa44cb3 Merge pull request #11266 from esengine/fix/studio-11250-reply-menu-clip
fix(studio): keep reply action menus whole near the transcript's edges
2026-09-29 01:34:59 -07:00
YHH 1a684d216c Merge pull request #11251 from esengine/fix/studio-11247-memorybench-isolation
fix(e2ebench): keep MemoryBench's memory-off arm free of the seeded store
2026-09-29 01:33:10 -07:00
YHH f8853733d3 Merge pull request #11252 from esengine/fix/studio-powershell-prelude
fix(shell): defensive PowerShell prologue, UTF-8 redirects on 5.1, Store pwsh detection
2026-09-29 01:32:26 -07:00
xiexian 006b361d71 fix(config): omit normalized built-in provider from project saves 2026-09-29 16:32:03 +08:00
YHH 61e1e03bf9 Merge pull request #11256 from esengine/feat/studio-cli-permission-defaults
feat(cli): default the terminal posture from the sandbox claim and folder trust
2026-09-29 01:31:43 -07:00
YHH 5636fed560 Merge pull request #11265 from esengine/fix/studio-tui-i18n
fix(tui): the terminal's own words go through i18n
2026-09-29 01:30:57 -07:00
YHH cf7bbae959 Merge pull request #11260 from esengine/fix/studio-tui-completion-enter
fix(tui): Enter runs a command typed in full instead of completing it
2026-09-29 01:30:05 -07:00
YHH 06805ca7fc Merge pull request #11248 from esengine/fix/studio-relay-lease
fix(remote): reconnect the phone silently across relay leases and desktop reconnects
2026-09-29 01:17:11 -07:00
SivanCola 37bcff7fb9 test(goal): cover whitespace validation without schema changes / 补充目标参数空白校验测试 (#11240)
* fix(goal): align Studio schema with report validation

Problem: schema-valid goal reports can fail execution for missing reasons or
blank verification commands.

Root cause: status-dependent requirements exist only in prose and execution
checks, not in the provider-visible JSON Schema.

Fix: encode conditional reason requirements and nonblank verification entries,
matching the executor's Unicode whitespace handling without relaxing Goal
authority or delivery validation. Update bilingual guidance and schema fixtures.

Verification: schema/executor agreement regressions and golden guards pass,
along with go vet ./..., make lint, and make check. The static schema changes
once on upgrade; system prompt bytes and within-version stability are preserved.

* fix(goal): keep report schema typed and within prefix budgets

The conditional status schemas omitted explicit types, which failed the
built-in provider compatibility gate. The added constraints also exceeded
both schema token budgets by 78 tokens.

Declare string enum and object branch types and trim descriptions whose
semantics are already explained by the tool. Keep every argument constraint
and the original budget ceilings, and refresh the serialized golden files.

Verified update-goal schema/execution tests, enum/type and schema-budget
guards, provider golden guards, go vet ./..., make lint, and make check.

* test(goal): keep whitespace validation coverage without schema changes

Problem: the schema hardening introduced provider compatibility risk through
a top-level anyOf and a new pattern constraint, and reduced model guidance
to fit the prefix budget.

Root cause: matching a general-purpose JSON Schema validator to execution
does not establish acceptance by model providers.

Fix: restore the original schema, descriptions, documentation, and golden
fixtures. Retain focused executor tests for blank reasons and verification
entries, optional completion fields, field diagnostics, and no report on
rejection. Existing error messages already identify the invalid fields.

Verification: update-goal tests, go vet ./..., make lint, and make check pass.
The owning suites retain the baseline ripgrep failure addressed by #11244.
2026-09-29 01:10:18 -07:00
xiexian 50277a2fce fix: revoke matching session grant with remembered rule 2026-09-29 16:06:33 +08:00
esengine a548df7f55 fix(permission): refuse the remaining read-only escapes and trust no home directory 2026-09-29 01:02:46 -07:00
esengine fec7b1eebb fix(studio): keep reply action menus whole near the transcript's edges
The Regenerate and More menus on a finished reply were absolutely
positioned above the trigger inside the card. The card paints under
content-visibility containment and sits in the transcript scroller, so a
row near the scroller's top lost the upper part of the menu, and a short
reply lost it even mid-screen.

Both menus now go through one ReplyMenu: the list is portaled to the body,
placed by measurement, opens above the trigger when it fits and below it
otherwise, and follows the trigger on scroll and resize. The row keeps a
focus stop where the list used to sit, so Tab order through the menu is
unchanged; Escape now also returns focus to the trigger.

The scroll/resize follow effect that Picker and RewindControl each
carried is now one useFollow hook in place.ts, beside the shared beside()
flip arithmetic, which lowers the unnamed-input ceiling by one.

Closes #11250
2026-09-29 01:01:27 -07:00
esengine b77c9fac60 fix(tui): /help, /compact's card and /context's breakdown as 1.x shows them
/help was not a command, so it went to the model. /compact drew one line,
"context compacted", even for a fold that wrote nothing, and /context
dropped the breakdown the kernel attached to its summary.

/help is now answered on screen in 1.x's layout (commands, then built-in,
custom, skills and MCP prompts, eight rows a dynamic group), built from the
completion catalogue the session answers, the TUI's own commands included.
A finished fold draws 1.x's card: messages folded and trigger, the digest,
and the estimated tokens before and after (i18n CompactionEstimatedTokens,
the 1.x key); a fold with no digest draws none, and a running one says it is
working. The kernel names /context's notice with a context_report code, and
the TUI shows its detail under it.
2026-09-29 01:00:14 -07:00
SivanCola 75abe61e97 fix(grep): keep the physical search root explicit inside sandboxes
Problem: A hidden directory could make grep search HOME and attribute its
matches to the requested external directory under Linux bubblewrap.

Root cause: Searching dot relied on the wrapper preserving the subprocess
working directory, but bubblewrap can fall back when that directory is masked.

Fix: Resolve directory roots to absolute physical paths and use the same path
as both cwd and the ripgrep argument. Restore requested output prefixes and
external-root error displays while preserving wrapped error identity.

Verification: Real Debian bubblewrap reproduced the prior failure for both
tmpfs and session-temp mounts; both cases pass with the fix. Grep regressions,
builtin and boot package tests, go vet, make lint, and make check pass.
2026-09-29 15:54:41 +08:00
esengine f91bad3ca1 fix(shell): make the PowerShell prologue defensive and fix 5.1 redirect encoding
The prologue was one chained assignment,
`$OutputEncoding=[Console]::OutputEncoding=[System.Text.Encoding]::UTF8`.
When the Console assignment throws (no console attached, or
ConstrainedLanguage refusing .NET property writes) neither encoding was
set and every command printed a 7-8 line error first. Each assignment is
now its own try/catch and .NET properties are only touched in
FullLanguage. Both encodings are BOM-less UTF8Encoding instances, so text
piped to native programs no longer starts with EF BB BF.

On Windows PowerShell 5.1 only, the shell tool also sets
$PSDefaultParameterValues['Out-File:Encoding']='utf8', so '>' and Out-File
write UTF-8 instead of UTF-16LE. '*:Encoding' is deliberately not set: it
would make Get-Content misread ANSI (GBK) files. Hooks keep only the output
part of the prologue, since they run the user's own scripts.

PowerShell discovery adds the Store install's execution alias
(%LOCALAPPDATA%\Microsoft\WindowsApps\pwsh.exe). An alias is a zero-byte
reparse point that stats fine but may fail to start the packaged app, so an
alias candidate (from the list or from PATH) must launch once before it is
selected; regular executables are taken as found.
2026-09-29 00:51:43 -07:00
esengine b7df42b3cd fix(tui): the terminal's own words go through i18n
A Chinese terminal still read English in the rows the TUI or the kernel wrote
themselves: "remembered → …" after a # note (1.x says 已记住), the session
picker's Search / No matches / Type to filter, a declined approval, a
sub-agent's call count, and the progress-watch notices. The completion menu
showed its key hint only past eight rows, and in English; 1.x always shows
it, from CompHintSlash / CompHintFile.

These now read from i18n, reusing 1.x's keys where 1.x had them. Text 1.x
itself prints in English (N lines, (active), N turns) stays English. The
language detection moves from i18n.go to detect.go; i18n.go was at the
800-line ceiling and the new keys need the room.
2026-09-29 00:40:08 -07:00
esengine a32fbea44d fix(e2ebench): keep MemoryBench's memory-off arm free of the seeded store
The memory-off arm hid the memory tool but still seeded the store, and the
state root was a sibling of the task workdir under the temp root, so agents
in the off arm found and read the seeded facts with shell commands.

- memory-off no longer seeds memory: the treatment is that no memory exists
- state roots live under the user cache dir with a random name, and a root
  inside the workdir's parent is refused
- -trajectories is made absolute before the child, whose cwd is the task
  workdir, receives it
2026-09-29 00:35:28 -07:00
esengine 72c81815bc fix(tui): /rewind picks a turn, /clear asks first, Esc clears and Esc Esc rewinds
A bare /rewind went to the kernel, which rewound the conversation one turn at
once, and /clear deleted the transcript with no question and left it on
screen. Both are destructive; 1.x asks first. Esc on an idle composer did
nothing, where 1.x clears what is typed and a second Esc opens the rewind
picker.

The TUI now answers /rewind with 1.x's picker (turn, then code+conversation,
conversation, code, or a fold before/after the turn; a partly covered file
restore asks again), prefills the turn's prompt and redraws the conversation
after a conversation rewind. /clear opens 1.x's Clear/Cancel question, the
cursor on Cancel, and clears at once in YOLO; the screen restarts under the
banner. Esc clears the composer, and twice within 600ms on an empty one opens
the picker. The kernel's /new and /clear notices now come from i18n.
2026-09-29 00:32:35 -07:00
esengine 188c373baa fix(tui): keep task bookkeeping and host notes out of the transcript
Every todo_write and complete_step call drew a row ("● todo_write ⎿ 1
lines", "● Step"), though the task list above the composer already shows
what they changed; 1.x draws neither. A shell row previewed the result the
model reads, and the host appends its obligation notes to that result, so
"host obligations changed: - settled: …" appeared as if the command had
printed it.

A settled todo_write or complete_step that succeeded is now kept off the
screen, as 1.x keeps todo_write; a failed one still shows. A shell row
previews what the command streamed while it ran and falls back to the result
only when nothing streamed (a replayed session).
2026-09-29 00:31:18 -07:00
esengine 527eee4b4c fix(permission): close the read-only escapes and keep the posture's sensitive code declared
Found in an adversarial review of the new default posture:

- shellsafe: arguments that make a read-only command run another program or
  write are refused in every spelling the program accepts (clustered short
  flags, abbreviated long options): git grep -O, rg --pre, sort
  --compress-program, go -toolexec/-exec/-vettool, file -C, uniq's output
  operand, date -s, hostname NAME, git reflog expire/delete, npm audit fix,
  kubectl --kubeconfig, gofmt -cpuprofile. less, man and cargo check/doc
  leave the read-only tables.
- The headless gate and the posture decision live in the files REASONIX.md
  declares sensitive again (approval_bridge.go, approval_orchestration.go);
  the MCP fast-path refusal is decided in package permission.
- A sub-agent under an attended Auto no longer gets dynamic shell its parent
  would have to ask about.
- Read-only keeps fresh human decisions human; a refusal no rule produced
  names the posture instead of a deny list.
- The trust prompt and docs state what the sandbox actually bounds (temp,
  toolchain caches, network) and that trust covers reasonix run; a home
  directory or filesystem root is never trusted wholesale; the run result
  names the posture it settled on.
2026-09-29 00:30:16 -07:00
esengine d736354248 fix(remote): bind controller commands to their session and resend only to the same desktop
- The desktop names each controller session in ready; a controller that
  uses the name numbers its commands upward, and the desktop refuses a
  command naming another session or an old number, so a relay cannot
  replay a recorded command into a new session or twice into one.
- ready carries a per-process instance id. A state-changing request
  already sent is resent only to that same instance; a restarted desktop
  has no cached answer, so the request fails explicitly instead.
- The replay cache outlives controller sessions (the grace expiry no
  longer clears it) and is reset only on device identity or account
  change; its key binds method, path and body as well as the id.
- The phone seals and sends commands in one queue so their numbers
  arrive in order; socket listeners are registered by the connection
  itself; errors reach the banner through reason().
2026-09-29 00:15:58 -07:00
esengine 980008de88 feat(cli): default the terminal posture from the sandbox claim and folder trust
A terminal session with no named mode opens in auto only where the OS
sandbox demonstrably confines shell writes (sandbox.IntegrityEnforced) and
the person trusted the folder; everywhere else it asks, and a headless run
refuses writes and lists them as permission_denials in its result.

read-only is a real posture again: every call that is not a read is refused,
allow rules included, and an authorized MCP server's writers too. Gate
refusals carry a code (permission.unattended, read_only, deny_rule,
declined) that reaches the tool result and the run result.

YOLO keeps skipping prompts only; its first interactive use is confirmed
once and recorded under the Reasonix home. Shift+Tab follows 1.x's order,
Ctrl+Y returns to the posture it left, and a terminal's switch no longer
becomes the desktop's default. git -C/--no-pager classify as the
subcommand they wrap.
2026-09-29 00:00:47 -07:00
YHH 1115efbc9f Merge pull request #11243 from esengine/feat/studio-11237-provider-display-name
feat(providers): rename a model service without changing its identity
2026-09-28 23:54:24 -07:00
esengine 04a8b3542a fix(tui): Enter runs a command typed in full instead of completing it
The kernel's slash menu drops the row that would replace the line with
itself, so a finished /tree left only fuzzy neighbours in the menu and Enter
accepted the first of them: /tree ran as /security-review. A finished /hooks
or /mcp stopped at their argument menu and a second Enter put its first
argument in the composer.

Complete now names the dropped row as Typed, which Studio ignores. The TUI
lists it first, and Enter sends the line when it already reads what the
highlighted row would make it: the command's label, or the argument in place.
That is the rule 1.x's composer follows; a half-typed name still completes.
2026-09-28 23:48:08 -07:00
SivanCola 14aae2c4c0 fix(grep): anchor directory searches across symlink aliases
Problem: Studio's ripgrep engine misses slash-glob matches under search
roots with symlinked ancestors, including macOS temporary directories.

Root cause: the absolute search argument keeps the symlink spelling while
ripgrep anchors globs against its physical process directory.

Fix: search dot from the requested subprocess directory and restore the
requested output prefix before external-root token projection. Both search
passes share the behavior; single-file targets and sandbox guards remain.

Verification: existing and new slash-glob regressions fail before the fix
and pass after it. Owning builtin/boot suites, go vet ./..., make lint, and
make check pass. The new test covers both passes and output path privacy.
2026-09-29 14:47:32 +08:00
esengine d8ba78d183 fix(remote): keep phone control alive across relay leases and reconnects
The phone page reconnects silently when the relay closes for idleness, a
legacy admission expiry or a dropped socket, and resends in-flight requests
under the same id. A read is always safe to resend; a state change is
resent only to a desktop that advertises replay, and failed otherwise, so
nothing runs twice. Relay close codes decide the next step: 4401 asks the
user to sign in again (sign out, then the existing sign-in page), 4403 and
4004 stop for good.

The desktop keeps controller sessions across its own relay reconnects,
prunes them against the relay's relay_hello list, sends the relay's
heartbeat frame once the relay names it, and caches answers to
state-changing desktop requests for ten minutes so a resend is answered
from the cache instead of running again.

Refs #11226
2026-09-28 23:45:51 -07:00
xiexian c255459219 Register remembered-rule revoke action 2026-09-29 14:45:05 +08:00
esengine 653439a3b4 fix(tui): refuse slash commands nothing answers instead of sending them to the model
The terminal typed /status, /help, /quit and the other 1.x commands studio
has not ported yet, and the kernel sent each line to the model as prose. That
spent a request, and for /status it ran git and asked for an approval.

A submission can now ask the controller to refuse a slash command no registry
resolves: it gets an unknown-command notice (i18n SlashUnknown, the wording
1.x uses) with the unknown_command code and starts no turn. Paths, code
comments and custom commands resolve exactly as before. The TUI always asks;
Studio does not, so its behaviour is unchanged.
2026-09-28 23:43:35 -07:00
esengine 35bdacd24b fix(cli): restore 1.x entry points: chat/code, run --takeover, sessions maintenance, leading --preset
A 1.x command line that reached one of these exited 2 on studio:

- `reasonix chat` / `reasonix code` were unknown commands. They are the
  interactive session in 1.x, so they now route to the terminal UI.
- `reasonix --preset X -p task` was an unknown command because --preset was
  not a leading session flag. It is again, so it becomes a print-mode run.
- `run --takeover` was an unknown flag. It is accepted; with no contention the
  run proceeds as in 1.x. Taking a session away from another process has no
  counterpart here, so a held session is still refused, and the refusal now
  says --takeover cannot do it and points at --copy.
- `sessions reindex|diagnose|cleanup` fell through to the machine `session`
  command. Sessions are listed straight from their directories here, so
  reindex has no catalog to rebuild and reports the count it reads; diagnose
  and cleanup report and trash covered conflict-recovery branches through the
  sessionstore guards Desktop already uses, keeping 1.x's JSON key names for
  what is measured and its dry-run-until---apply contract.
2026-09-28 23:36:51 -07:00
esengine 023f20e974 feat(providers): rename a model service without changing its identity
A provider entry gains display_name, the key 1.x already writes, so a
config shared with 1.x keeps it on every save. Name stays the identity:
refs, default_model, saved sessions and preset upgrades still point at it.

POST /providers/display-name relabels every entry shown as one account,
all or none, and refuses an over-long label or a control character with
its own code. The provider list, the model list and /status carry the
label; the settings list renames in place (pencil or F2, empty restores
the derived name), and the model picker, wallet header and ACP model
descriptions show it.

Closes #11237
2026-09-28 23:33:15 -07:00
xiexian 76afa1a77c Merge remote-tracking branch 'origin/studio' into codex/studio-config-no-unprompted-file-11174 2026-09-29 14:31:13 +08:00
xiexian 28067f69ff fix(studio): let users revoke remembered project rules 2026-09-29 14:31:03 +08:00
esengine f90b50ec0a fix(cli): stop reasonix setup from writing its flags as config files
resolveSetupTargets takes every unrecognized argument as the config path, so
`reasonix setup --help` wrote a file named --help and reported "Wrote --help".
1.x prints the setup usage for -h/--help and exits 0, and refuses any other
dashed argument with usage on stderr and exit 2. The ACP registry advertises
`reasonix setup` as the login method, so its help has to answer.

Port that guard, in its own file, ahead of target resolution.
2026-09-28 23:24:44 -07:00
YHH d2da65b8c4 Merge pull request #11233 from esengine/fix/studio-11229-windows-command-line-limit
fix(bash): name the Windows command-line limit instead of "filename too long" (studio)
2026-09-28 23:07:21 -07:00
YHH 13b87dd4e9 Merge pull request #11231 from esengine/fix/studio-1x-context-sidecar
fix(session): keep 1.x context sidecars and carry their summary forward
2026-09-28 23:06:39 -07:00
YHH 4a6e55ff08 Merge pull request #11234 from esengine/fix/studio-import-marker-test-windows
test(sessionstore): encode the import-marker fixture's workspace as JSON
2026-09-28 23:05:56 -07:00
YHH c57193ea4f Merge pull request #11236 from esengine/feat/studio-11230-find-in-session
feat(studio): find in a conversation that Ctrl+F actually reaches
2026-09-28 23:05:47 -07:00
YHH af12885528 Merge pull request #11225 from KHG420/codex/studio-ask-markdown-11219
fix(studio): format ask prompt text
2026-09-28 23:05:04 -07:00
YHH 56b68e2701 Merge pull request #11206 from KHG420/codex/1x-unicode-skill-names-4038
feat(studio): support Unicode skill names
2026-09-28 23:04:13 -07:00
xiexian 6bf8c576eb Merge remote-tracking branch 'origin/studio' into codex/studio-unicode-skill-names-4038 2026-09-29 13:39:16 +08:00
esengine b35f38962f feat(studio): find in a conversation that Ctrl+F actually reaches
The in-app find already searched the rows rather than the document, so
it sees messages the transcript has not mounted. What stood between a
person and it:

- The chord matched e.key exactly. Caps Lock sends "F", an input method
  answers "Process", a Cyrillic layout sends "а" - none of them opened
  anything. The window now reads the letter case-insensitively and falls
  back to the key's position when the layout reports no Latin letter.
  It takes Cmd on macOS and Ctrl elsewhere, so Ctrl+F in a macOS text
  field keeps its Emacs meaning, and AltGr is not read as the modifier.
- Nothing on screen offered it. The title bar gets a find button (the
  only way in on a phone) and the palette gets the command, both showing
  the chord.
- It took the chord from every text field. A field hands the window's
  chords through only when it says so (data-window-keys): the composer
  and the find box do, a settings filter or the browser's address bar
  does not. An editor that answered the press itself (CodeMirror's own
  find) still spends it.

Stepping also moves within a row now. Twenty matches in one tool output
counted as twenty but painted and landed as one; the current occurrence
is the nth drawn in its row, and landing scrolls it - through the
output's own clip if it scrolls - to where it is read instead of leaving
the row's top under the bar. A newer landing cancels one still settling,
which typing a query used to race. Arrow keys step like Enter.

The current match was accent at 45%, which in the Studio theme is a
light grey behind light text in dark mode; it is the accent/accent-fg
pair now, neutral in both themes.

The window's key listener moved out of App.tsx into windowkeys.ts to
keep App under the line ceiling.
2026-09-28 22:36:59 -07:00
xiexian ecf1e37a79 fix(studio): reject invisible Unicode skill identifiers 2026-09-29 13:28:07 +08:00
esengine 607025713e test(sessionstore): encode the import-marker fixture's workspace as JSON
A workspace path concatenated into the sidecar is not valid JSON on
Windows, where its backslashes read as escapes; the meta then fails to
parse, the session is never routed to the blocked directory, and the copy
the test expects to fail succeeds.
2026-09-28 22:26:21 -07:00
esengine 9f789f9abd fix(bash): name the Windows command-line limit instead of "filename too long"
A shell command whose command line exceeds CreateProcessW's 32,767 UTF-16
unit ceiling never starts; the model was told "fork/exec powershell.exe:
The filename or extension is too long." and read that as a file-size
problem. On Windows the PowerShell tool passes the whole script as one
-Command argument, so writing a large block of text (e.g. a Chinese
document in a here-string) hits this every time.

The host now counts the command line exactly as os/exec builds it and,
when it will not fit, refuses the call before launch with a typed error
(errCommandLineTooLong) that states the size, the limit, and that nothing
ran, and points at write_file/edit_file. The execution is recorded as
not run / not started. The not-run bookkeeping moves into one helper so
ExecuteDetailed stays within its size budget.

Refs #11229
2026-09-28 22:20:10 -07:00
esengine ac30722a7c fix(session): keep 1.x context sidecars and carry their summary forward
Opening a session 1.x wrote deleted its <id>.context.json. 1.x writes
context schema 4 from 1.36.0; LoadCompactionState accepted only 1-3, and
loadProjectionSidecar removed the file on any load error. The model-written
compaction summary was lost for good, 1.x itself resent the full history on
its next turn, and 2.x sent the full history too.

A load error now drops the projection in memory only. Unknown schemas carry
ErrContextSchemaUnsupported. Schema 4 is schema 3 plus
projection.pinned_context_hash, so it is read when that hash is empty; a
pinned-context checkpoint is authenticated against 1.x provenance this build
does not model and stays unsupported. A load never rewrites another release's
file (lineage keys are normalised in memory), and dropping the projection of
an open 1.x event log leaves the file, since this build never changes that
log. When a 1.x conversation moves to a 2.x session, the projection is
inherited there in schema 3 if it still covers the transcript, so the first
2.x turn sends 1.x's summary instead of the folded history.

The effect test replays a schema 2 session captured from a real 1.38.7 build.
2026-09-28 22:19:05 -07:00
xiexian c4cf1388e4 Merge remote-tracking branch 'origin/studio' into codex/studio-config-no-unprompted-file-11174
# Conflicts:
#	internal/assembly/boot/boot.go
#	internal/assembly/boot/boot_test.go
2026-09-29 13:19:01 +08:00
xiexian 1f2bf0b93f fix(studio): match ask prompt body weight to replies 2026-09-29 13:06:11 +08:00
xiexian 422fd16d9c fix(studio): distinguish bold ask prompt text 2026-09-29 12:48:26 +08:00
YHH 38106c9200 Merge pull request #11227 from chuiwenwei/fix/studio-session-import-marker-guard
fix(sessionstore): keep the legacy import marker unstamped when a copy fails
2026-09-28 21:47:32 -07:00
YHH 2917b96263 Merge pull request #11228 from esengine/fix/studio-11226-remote
fix(studio): keep the relayed event stream alive and the tab on its pane
2026-09-28 21:42:48 -07:00
SivanCola 9a9323f3be fix(studio): restore window on macOS Dock activation (#11221)
Problem: Clicking the Dock icon leaves a Studio window hidden after closing to tray.
Root cause: macOS activates the existing process without emitting second-instance, and the shell had no activate listener.
Fix: Route activation through the shared window reveal path and ignore reveal requests during shutdown. Extend native smoke coverage across activation, second-instance, and tray entry points.
Verification: macOS native smoke, Electron unit tests, frontend build, host build, go vet, and repository lint passed.
2026-09-29 12:03:42 +08:00
esengine 90f201ee25 fix(studio): keep the relayed event stream alive and the tab on its pane
A phone reaching Studio through the account relay has no EventSource; the
page swaps in RemoteEventSource, which polls /events/replay over the
encrypted channel. It fell short of the stream it stands in for in three
ways:

- It found /replay by rewriting a URL that ends in /events. Since the
  silence watchdog (#11216) a replacement stream carries ?lastEventId=, the
  rewrite missed, and every poll asked for the live /events route, which the
  desktop refuses over the relay. An idle pane went silent for 45s and then
  stopped receiving events for good.
- It learned its starting position by asking for lastEventId=0, so each
  attach downloaded the whole replay log (up to 8MB for a long running
  session) through the relay and threw it away. Asking past the watermark
  returns no frames and the watermark alone.
- It never said the frames SSE writes on its own, so a quiet stream looked
  dead to the watchdog and a truncated log looked complete. A quiet poll now
  restates the watermark, and complete=false becomes a stream_gap.

Separately, a reload always focused the first open pane, because which pane
the window showed lived only in React state. The relay's "reconnect" button
reloads the page, so the phone landed on the top project's first session
instead of the one it was on. The tab now keeps that id in sessionStorage
and takes it back when the pane is still open.
2026-09-28 20:58:41 -07:00
Will 641991840d fix(sessionstore): keep the legacy import marker unstamped when a copy fails
importJsonlSessions reported only artifact failures through hadArtifactFailure,
so a session whose own copy failed was skipped silently and the one-time import
marker still went down. The next launch then saw the marker and never retried,
leaving that session unimported for good.

Both copy paths now report the failure, so the marker stays off and the next
launch retries, matching what a failed artifact copy already did.
2026-09-29 11:57:05 +08:00
YHH 106fe593fd Merge pull request #11223 from esengine/fix/studio-11220-awaiting-user-ask
fix(agent): send choices through ask, keep await_user's need to one line
2026-09-28 20:37:59 -07:00
YHH 944321e2b6 Merge pull request #11224 from esengine/fix/studio-11219-await-notice-markdown
feat(studio): render the await_user notice's need as markdown
2026-09-28 20:35:24 -07:00
xiexian 8b2ca911a1 test(studio): await pending ask interactions before cleanup 2026-09-29 11:31:53 +08:00
esengine 020b388126 fix(agent): send choices through ask, keep await_user's need to one line
await_user's `need` is shown to the user as a one-line host notice. With
nothing bounding it, a model put the whole question there, numbered options
included, so the user got a wall of text to answer by typing instead of an
ask card they could click. The description now routes a pick among named
choices to ask, and the tool refuses a need longer than 200 characters with
an error that says to put the substance in the reply or call ask. Length is
a property of the argument, not a reading of its wording.

Closes #11220
2026-09-28 20:27:18 -07:00
esengine c5ef5ed10f feat(studio): render the await_user notice's need as markdown
The hand-back notice printed the model's `need` as one plain paragraph, so
its bold, lists and line breaks reached the user as literal markup. The
kernel now tags the notice with the await_user code and carries the need
verbatim as its detail; the text form stays for the TUI and CLI. Studio
titles the card from the code and renders the detail through the same
sanitized Markdown renderer replies use.

Closes #11219
2026-09-28 20:27:14 -07:00
xiexian 69ede811cc fix(studio): keep ask Markdown layout consistent 2026-09-29 11:19:50 +08:00
YHH 712fcf10f0 Merge pull request #11222 from esengine/fix/studio-perseveration-approve
test: approve the workspace and pin perseveration fixture bytes
2026-09-28 20:02:28 -07:00
esengine 5c7f6f0801 test: approve the workspace and pin perseveration fixture bytes
Workspace configuration only takes effect once the workspace is approved,
so the perseveration boot effect tests reached the real provider instead
of the registered stub.

The perseveration guard fixtures are byte-exact recordings; checking them
out with CRLF on Windows shifts every offset the test asserts.
2026-09-28 19:54:36 -07:00
xiexian 96465dde21 fix(studio): render ask prompts as Markdown 2026-09-29 10:54:22 +08:00
YHH eaf665edff Merge pull request #11218 from esengine/docs/studio-2.22.0-notes-2
docs(release): add the mobile and workbench fixes to the Studio 2.22.0 notes
2026-09-28 19:15:20 -07:00
YHH 735a6e5998 Merge pull request #11216 from esengine/fix/studio-11207-stream-watchdog
fix(studio): replace an event stream that has gone silent
2026-09-28 19:09:57 -07:00
YHH 6817f58e52 Merge pull request #11215 from esengine/fix/studio-11202-speed-gauge
fix(studio): drop the gauge icon in front of the speed meter
2026-09-28 19:09:13 -07:00
YHH 3dea01d441 Merge pull request #11214 from esengine/fix/studio-11203-hidden-toggle
feat(studio): let the explorer show hidden files on request
2026-09-28 19:08:28 -07:00
YHH 2206a95f12 Merge pull request #11213 from esengine/fix/studio-11204-workbench-close
fix(studio): keep the workbench open while its explorer is showing
2026-09-28 19:07:43 -07:00
esengine dbacae668a docs(release): add the mobile and workbench fixes to the Studio 2.22.0 notes 2026-09-28 19:07:30 -07:00
YHH d23e03b20a Merge pull request #11212 from esengine/fix/studio-11205-lan-explorer
fix(studio): let the files toggle open the explorer on narrow screens
2026-09-28 19:06:58 -07:00
xiexian 42e9c7ce2e feat(studio): support Unicode skill names without changing MCP IDs 2026-09-29 09:49:34 +08:00
esengine 07f25c1ca4 fix(studio): replace an event stream that has gone silent
A phone paired over the LAN that sleeps, or whose Wi-Fi drops the link,
can leave its EventSource looking open on a socket nothing arrives on.
EventSource only reconnects on an error it is told about, so the pane
kept showing a live connection while no event reached it, and on plain
HTTP/1.1 each such dead stream also keeps holding one of the browser's
six connections to the host, starving the requests a session switch
makes.

The kernel already restates its position on every transport each 15s
(stream_watermark). The client now treats three silent intervals as a
dead stream: it closes it and opens a new one that resumes from the last
frame it folded (lastEventId), so nothing numbered is lost. Timers stop
while a phone sleeps, so becoming visible and coming back online check at
once instead of waiting for the next tick.
2026-09-28 18:31:21 -07:00
YHH 6038a6fbf6 Merge pull request #11183 from BuGlessRB/open/perseveration-guard
fix(agent): detect perseveration loops and retry once with a nudge / 修复(agent):检测重复输出循环并在提醒后重试一次
2026-09-28 18:30:26 -07:00
YHH a5f772b718 Merge pull request #11211 from esengine/docs/studio-2.22.0-notes
docs(release): prepare Studio 2.22.0 notes
2026-09-28 18:22:01 -07:00
YHH 23f0a011c4 fix(config): workspace configuration only narrows the user's settings (#11208)
A workspace's reasonix.toml and .reasonix/settings.json are read as
untrusted input.

- [sandbox]: bash may only move to enforce and network only to false;
  forbid_read and denied_domains add to the user's; allow_write entries
  and workspace_root apply only when they resolve inside the workspace.
  ${VAR} in sandbox and proxy paths expands from the process environment
  only.
- [permissions] mode, allow and allow_dynamic_bash, [network] proxy
  settings and [desktop] default_tool_approval_mode are read from the
  user config only; deny and ask add to the user's. An "always allow"
  given in a workspace is stored under the Reasonix home
  (project-grants.json) rather than in the checkout.
- Programs a workspace declares (hooks, language servers, providers,
  tools.search.rg_path, tools.shell.path, [browser], tools.system_one
  endpoints and the local Laya runtime) run only once approved for that
  folder. An approval records the declaration and a digest of the files
  it names, is kept under the Reasonix home, and is checked again before
  each start; a change refuses the start with ErrProjectProgramChanged.
  `reasonix trust` lists and approves them, and saving hooks from the
  hooks page approves the block written. Model settings that named only
  a held-back provider fall back to the user's choice.
- Single program paths may not point into the workspace, a temporary
  directory, an allow_write root or a toolchain cache.
- Sandbox write roots and host write directories keep the identity they
  had when first granted; a root whose identity changed is dropped with
  sandbox.write_root_redirected or sandbox.write_root_changed. The
  host-writable directory list lives in internal/base/hostwrite.
- Every ignored value is reported as a load warning with a typed reason
  (Config.IgnoredProjectSettings); held-back programs raise the
  project_programs_awaiting_approval notice.
2026-09-28 18:21:34 -07:00
esengine 6c291e8c3e feat(studio): let the explorer show hidden files on request
The workspace explorer leaves every dot entry out, so folders such as
.reasonix, .github or a .gitignore could not be browsed from Studio at
all. The explorer head now has a toggle that asks the kernel for them.

GET /workspace/files takes hidden=1 to include dot entries in both the
folder listing and the search. VCS stores (fileutil.IsVCSStoreDir) and
dependency trees stay out either way. The default is unchanged: dot
entries hold tool state and local configuration, so they appear only for
a reader who asked, and the choice is remembered per machine.

The explorer's head moves into its own component so WorkbenchPanel
stays under the file-size ceiling.
2026-09-28 18:12:27 -07:00
esengine 395c5eace2 docs(release): prepare Studio 2.22.0 notes 2026-09-28 18:10:58 -07:00
esengine 2f7c0613fd fix(studio): keep the workbench open while its explorer is showing
Closing the last tab folded the docked workbench even when the file
explorer was open beside it, so dismissing a browser tab took the file
list away with it. An open explorer is now a reason to keep the column,
the same as any tab still open; the column still folds with its last tab
when the explorer is closed.
2026-09-28 18:06:17 -07:00
esengine dd381070f4 fix(studio): drop the gauge icon in front of the speed meter
The speed meter already opens with a sparkline of recent throughput, so
the gauge glyph before it said the same thing twice and cost width in a
rail that is tight next to the composer. The cache and context meters
carry no leading icon either; the speed meter now reads the same way.
2026-09-28 18:03:14 -07:00
esengine b1c22c9cfe fix(studio): let the files toggle open the explorer on narrow screens
Under 760px the workbench collapses to one column and the stylesheet hid
the explorer unconditionally, so on a phone paired over the LAN (or any
window that narrow) the files button toggled to pressed over an empty
body and the workspace tree could not be reached at all.

At that width the explorer and the canvas now take turns in the single
column through the existing data-files toggle, the same way a docked
workbench already does. Picking a file still puts the list away and
shows the file.
2026-09-28 18:01:46 -07:00
Stephen R. van den Berg 291a03a9ca fix(agent): detect perseveration by default, reported via progress watch
The guard detects a degenerate generation loop — a model echoing one short block
of text or reasoning byte-for-byte — by default, and reports it through the
progress-watch channel as a new `perseveration` cause. Nothing is cut and nothing
reaches the model unless the caller opts in: a byte-identical block is sometimes
legitimate output (printing a value 300 times, a run of identical table or log
rows, a page of `=` or `A`), so cutting it by default was the wrong trade. The
notice is said once per loop episode — a scan that trips again while the previous
scan also tripped stays quiet, and the next scan that does not trip re-arms it.
The user's `progress_watch.pause` setting is what ends a run on a detected loop.

Cutting the stream and nudging an unattended retry is opt-in with
`perseveration_retries`, under [progress_watch] or per provider on a [[providers]]
entry (the provider value overrides the global default). Unset or 0 reports only;
a positive value also cuts and nudges up to that many retries; a negative value
disables the guard entirely.

The cut path records best-effort usage, lets extensions rule on the terminal
before it is persisted, and never executes the aborted stream's tool calls. It
collapses the loop to the phrase and its last repetition in what is shown and
stored alike, so the transcript follows the trim instead of silently diverging
from the stored turn — no history rewrite is needed. It then shows the user a
`[retrying (N) avoiding perseveration]` interjection, appends a host-authored
nudge through the mid-turn steer path, and retries. Once the budget is spent the
run stops with a typed perseveration pause and emits the `perseveration_loop`
notice, so a parent cannot mistake the loop for a clean result. The retry budget
lives on the run, so the next user message starts fresh.

Adds a boot-level effect test asserting what reaches the provider request and the
frontend sink, and updates the guide.
2026-09-29 02:55:50 +02:00
YHH 0dd3e5574a Merge pull request #11074 from BuGlessRB/open/vi-command-mode
feat(cli): add vi command mode to the composer prompt / 功能(cli):为输入框加入 vi 命令模式
2026-09-28 16:58:38 -07:00
YHH 07291a5f04 Merge pull request #11196 from esengine/fix/studio-updater-ux
fix(studio): ask before restarting into an update, and pin only on rollback
2026-09-28 16:56:48 -07:00
YHH d58f25580e Merge pull request #11198 from esengine/fix/studio-browser-panel-cover
fix(studio): the browser panel pauses only for a layer that is really over it
2026-09-28 16:53:15 -07:00
YHH 77d92a8eb9 Merge pull request #11197 from esengine/feat/studio-11192-per-model-effort
feat(effort): per-model reasoning levels on a relay
2026-09-28 16:52:30 -07:00
esengine d1ab26d6f4 fix(studio): ask before restarting into an update, and pin only on rollback
The version panel's install ran download, install and restart as one act:
once the signature checked, the application ended with no prompt. It also
wrote the pin before downloading anything, for every target, so an update
that failed (or a Windows chunked swap that rolled back after restart, the
2.20.x case behind #11115) left "pinned to 2.21.0" on a 2.20.0 install,
with its only way out disabled while any other move was in flight.

- StartInstall now stops at a verified, staged release (phase "ready").
  POST /update/restart commits it; it is refused with
  update.restart_busy while a local turn or background job is running,
  unless the request says the person saw that (force).
- The pin is written when the install is committed, only for a rollback;
  a move forward releases any pin. A failed or dismissed install puts the
  previous pin back. A pin naming a build other than the running one is
  released at launch.
- A failed move carries a dotted code from the step that failed
  (catalog, download, signature, disk, no package, installer). A forward
  move records itself; the next launch, still on the old build, reports
  update.not_applied with the swap helper's reason, which the helper now
  writes instead of printing to a stderr nobody reads.
- The panel explains each code, links the full installer where installing
  once by hand is the way out, and drops wording that promised automatic
  updates Studio does not perform.
2026-09-28 16:48:55 -07:00
Stephen R. van den Berg ee786a1ca6 feat(cli): add vi command mode to the composer prompt
Add an opt-in vi command mode to the composer prompt ([ui].commandmode =
"vi"). At the prompt Esc enters command mode for navigation and editing
rather than interrupting a turn; command mode owns the keys h l j k 0 $ ^
i a x I A p D and the mode switch.

The composer lives in internal/frontend/tui, so the mode is implemented
there against its textarea: vimode.go carries the command-mode keys and
the mode switch, input.go routes keys through it, and ask.go and
prompts.go keep Esc from dismissing an open question card or deciding an
approval card. Command mode consumes any key carrying printable text, so
a CJK/IME character is eaten rather than inserted.

Because the interrupt key now depends on the composer mode, the
running-turn hint can no longer name a fixed key: with ui.commandmode off
Esc stops a running turn, while in vi mode only ^C interrupts, since Esc
is a mode switch whether or not a turn runs. The thinking
status line therefore names the key that actually interrupts — "(Esc
cancels)" or "(Ctrl+C cancels)" — across en/zh/zh-TW, and the footer names
the composer's input mode, NORMAL in command mode and INSERT otherwise.
The cancelling line drops its "Ctrl+C exits" hint in vi mode, where Ctrl+C
re-cancels instead of exiting, and the approval, ask and plan card hints —
plus the plan choice list — drop their Esc mention there, where Esc is
ignored.

At the prompt, ^C with a non-empty prompt clears it and leaves shell mode,
in vi mode and out. In vi mode it first saves the trimmed draft to the
cmdline history when there is one, as send() does, so an accidental
interrupt never loses it (Up recalls it); a whitespace-only prompt is
cleared without being saved. In vi mode Esc no longer decides an approval
or ask card, where it is pressed out of habit; a row key or Ctrl+C decides
instead, and Ctrl+C still cancels a running turn even while an ask answer
is being typed.

[ui].commandmode is user-global only: a project reasonix.toml cannot set
it, so a cloned repo never rebinds the user's keys.
2026-09-29 01:01:58 +02:00
YHH 562cdd3f7e Merge pull request #10945 from BuGlessRB/open/continue-copy-fresh-session
fix(cli): start fresh on --continue --copy with no session / 修复(cli):--continue --copy 无会话时改为新开会话
2026-09-28 13:18:55 -07:00
Stephen R. van den Berg 422caf9595 fix(cli): skip --copy only on a fresh --continue, not the picker
The fresh-start fallback was inferred from `--continue` being set, so a
bare `-r` picker run with `--continue` also skipped --copy even though the
picker resumes its choice in place and has nothing to duplicate. Carry an
explicit `startedFresh` out of session resolution instead, so only a
`--continue` that found no session skips --copy; the picker and `--copy`
alone stay usage errors.
2026-09-28 21:47:47 +02:00
Stephen R. van den Berg 1fb3f55396 fix(cli): start fresh on --continue --copy with no session
`--continue --copy` with no saved session failed with "--copy requires
--resume or --continue", even though --continue had been passed. --continue
already falls back to a fresh session when it finds nothing, so there is
nothing to duplicate; --copy is now skipped and the run proceeds.

`--copy` with neither --resume nor --continue is still a usage error. Fixed on
both the run path (cli.go) and the TUI (tui_command.go).

The run path's copy resolution moves into applyRunCopy, which keeps runAgent
within its complexity ratchet.
2026-09-28 21:20:53 +02:00
YHH d158d19aef Merge pull request #11200 from esengine/fix/studio-11199-openai-preset-responses
fix(config): serve the OpenAI curated preset on the Responses API
2026-09-28 12:12:04 -07:00
esengine ebabe620e1 fix(config): serve the OpenAI curated preset on the Responses API
OpenAI documents that GPT-6 Sol and Luna take function calls on Chat
Completions only at reasoning_effort=none, and that GPT-6 Astra's tool
calling requires Responses. The curated OpenAI preset nevertheless shipped
kind "openai" (Chat Completions) with default gpt-6-sol, effort medium and a
ladder without none, so every default turn was Chat Completions + tools +
effort medium: the one combination the model table (gpt6Capability,
Responses-only) already says cannot call tools.

- The preset's entry moves to kind "responses". Its effort list and default
  are unchanged; both are inside every GPT-6 model's Responses ladder.
- shippedPresets gains the Chat Completions shape as a generation, with a
  Kind field on shippedPreset: an entry still identified by preset_id
  "openai", our base URL, the shipped catalog, window 1,050,000 and default
  gpt-6-sol is moved onto the Responses wire. Anything its user changed in
  that shape is left alone, and a second pass changes nothing.
- A consistency test walks every curated preset: a model whose table row
  names its wires must be served on one of them, and declared efforts and
  default must sit inside that model's ladder. The old preset fails it for
  all three GPT-6 models.
- Effect tests through boot.Build hold a fresh install and an install of the
  old shape to a Responses body carrying tools and reasoning.effort=medium.

Docs: https://developers.openai.com/api/docs/guides/latest-model,
https://developers.openai.com/api/docs/models/gpt-6-sol,
https://developers.openai.com/api/docs/models/gpt-6-luna,
https://developers.openai.com/api/docs/guides/reasoning

Closes #11199
2026-09-28 12:02:25 -07:00
esengine 24f11e8ecc fix(studio): the browser panel pauses only for a layer that is really over it
The agent's browser panel put its page away and showed "the page is hidden
while something covers it" when nothing a person could see was over it, and
sometimes stayed that way after the cover left.

Why it misfired:

- The check took the topmost element at nine points as the answer. A layer at
  opacity 0, or a transparent full-window layer that only catches clicks, is
  the topmost element and paints nothing, yet it hid the whole page.
- A cover that leaves by a CSS transition, an animation or a hover changes no
  attribute the panel watched, so the page stayed away until something
  unrelated moved. The observer also only listened to five attribute names.
- A popover reaching into one corner hid the whole page and replaced it with
  a sentence.

What changes:

- ui/occlusion.ts decides from hit testing plus computed style and geometry:
  an element counts only if it paints at the probe point (effective opacity,
  visibility, background, backdrop filter, a border band, its own text rects,
  or a replaced element). The slot's own content, its ancestors, and layers
  that paint nothing are skipped. The rectangle sent to the shell is the part
  of the slot the window shows, cut to clipping ancestors. A layer reaching no
  more than 8px into the edge, like a column gutter's grip, does not count.
- Recovery no longer depends on a mutation: transition and animation
  end events reschedule the check, all attributes are observed, and a 400ms poll
  brings the live page back whatever moved.
- A covered page is frozen instead of hidden: the shell takes a picture of the
  page on screen with capturePage before putting it away, and the panel draws
  that picture in the slot until the cover leaves. The sentence is gone.

The picture channel (browser:freeze) takes only the page already shown in
this window, answers only this window's own page, and the picture is drawn in
an <img> and sent nowhere else. A freeze overtaken by a show does not put the
page away. An older shell without the verb falls back to hiding the page.

Tests: browser-panel.interaction.test.tsx covers invisible, transparent and
hidden layers, the slot's own content, recovery with no DOM change, stale
pictures and view transitions. test/freeze_live.js checks the channel in a
real Electron window.
2026-09-28 10:27:16 -07:00
esengine 40c87d94c6 feat(effort): let each model on a relay declare its own reasoning levels
A relay that serves several vendors' models under one address and key could
only declare one supported_efforts list for all of them. Each model can now
carry its own, stored where per-model settings already live
(model_overrides.<model>.supported_efforts / default_effort) and applied by
the same model resolution every request and every level picker reads, so a
level the picker offers is the level the request carries.

Precedence: the model's own list, then the connection's supported_efforts,
then the model table for the endpoint, then the protocol's generic ladder. A
model with no declaration inherits; installed configs need no migration.

- config: ModelEffortDeclaration / SetModelEffortDeclaration (keeps every
  other override field, drops an emptied override, ErrModelDefaultEffortNotListed
  for a default outside the levels) and InheritedEffortCapability. ResolveModel's
  three branches share one forModel step.
- EffectiveEffort: the stored level is one per connection, so after a model
  switch it can fall outside the next model's levels. That model now runs at
  its own default, the level its picker's "auto" names, instead of omitting
  the field while the picker says auto means something else.
- An override that only sets max_output_tokens is no longer dropped as empty
  on load.
- serve: /providers lists each chat model's own, inherited and protocol
  declarations; /providers/edit takes modelEfforts as the whole answer for the
  listed models, refusing with provider.model_default_effort_not_listed and
  provider.model_effort_unlisted.
- Studio: the "Thinking and reasoning levels" fold gains "Levels per model":
  inherit (showing what that means) or pick the model's own levels and default.

The effect test builds a mixed relay through boot.Build and checks the
request body for every offered level of both models, and that a level a
model does not take never reaches the wire.

Closes #11192
2026-09-28 10:21:04 -07:00
YHH 54b7bc20bb Merge pull request #11195 from esengine/fix/studio-browser-false-occlusion
fix(browser): judge a click by where the pointer would really land
2026-09-28 10:06:12 -07:00
YHH 4d486b4829 Merge pull request #11194 from esengine/feat/studio-model-modes
feat(model-modes): declare optional model modes; OpenAI Pro on GPT-5.6/GPT-6
2026-09-28 09:58:24 -07:00
esengine 2c85351b91 fix(browser): judge a click by where the pointer would really land
The check that refuses a click as browser.covered answered a different
question from the one it was asked, in four ways:

- DOM.getNodeForLocation takes document coordinates, and it was given
  viewport ones. On a page scrolled by less than the target's distance
  from the top of the viewport it hit-tested a point that far above the
  target and reported whatever sat there (a banner, a nav bar) as
  covering it; scrolled further, the lookup found nothing and the check
  passed, so a click under a fixed header or a veil went through to the
  wrong element.
- ignorePointerEventsNone: true makes the hit test count
  pointer-events:none elements as hits, the opposite of what a pointer
  does, so decorative overlays were reported as covering.
- Newer Chromium (Edge 154) resolves a hit on a ::before/::after to a
  CSSPseudoElement, which has no parentNode, so a button with a ripple
  layer was refused as covered by its own pseudo-element.
- A form control drawn by its label (a zero-size or visually hidden
  checkbox, a radio painted over by a span in its label) was refused as
  not_visible or covered, although a person clicks the label.

The hit point is now converted to document coordinates, pointer-events:none
is left to the browser, a pseudo-element hit counts as its element, a hit
inside one of the target's labels counts unless it passes through other
interactive content (a link in the label follows the link), and before
refusing, the element is scrolled to the middle of the viewport, which
clears fixed headers and footers. A real cover is still refused, now
naming what is on top by ref. The label list comes from the page, so it is
capped and checked to be labels on the host side.

Measured with a table of pages at 1x and 1.5x device scale on Chromium 151
and Edge 154: before, 6 false refusals (7 on Edge), one click silently
landing on a fixed header and one passing through a veil; after, none,
and the true covers are still refused.
2026-09-28 09:56:57 -07:00
YHH b127b607fa Merge pull request #11193 from esengine/feat/global-provider-presets
feat(config): curated presets for OpenRouter, OpenAI, Gemini and Volcengine Ark
2026-09-28 09:53:19 -07:00
YHH 40891fdc66 Merge pull request #11191 from esengine/docs/skills-guide
docs: add a Skills guide
2026-09-28 09:51:56 -07:00
esengine 6e5d404ddd feat(config): curated presets for OpenRouter, OpenAI, Gemini and Volcengine Ark
Every curated preset so far points at a Chinese coding plan or a
multi-model relay, so a user arriving with an OpenRouter, OpenAI or
Gemini key has to write the provider by hand.

- openrouter: DeepSeek, GPT-6, Gemini, Kimi, GLM, Qwen and MiniMax ids
  checked against /api/v1/models; credits balance via the existing
  OpenRouter decoder; HTTP-Referer / X-OpenRouter-Title /
  X-OpenRouter-Categories so the traffic is attributed to Reasonix in
  OpenRouter's app rankings. Claude ids are left out: on the openai
  dialect no cache_control is sent, so every prompt token would bill at
  the full input rate (claude-dialect).
- openai: GPT-6 Sol/Astra/Luna on Chat Completions, which already sends
  max_completion_tokens to api.openai.com; declares the documented
  low..max effort ladder.
- gemini: the Developer API's OpenAI-compatible endpoint.
- volcengine-coding-plan: the Coding Plan endpoint with ark-code-latest,
  which follows the model chosen in the Ark console.

The presets live in their own file; provider_presets.go only appends
them.
2026-09-28 09:43:04 -07:00
esengine c0f4d5ea15 feat(model-modes): declare optional model modes and offer OpenAI Pro on GPT-5.6/GPT-6
A model can now declare optional run modes beside its effort ladder, in the
same model capability table. Each declaration names its id, the frontend's
label/hint keys, how it maps onto the request, the wires (kinds) and the
vendor endpoint it was established on, and whether it costs noticeably more.

GPT-5.6 (gpt-5.6 alias, sol/terra/luna) and GPT-6 (sol/luna/astra) declare
"pro", sent as Responses reasoning.mode "pro" beside reasoning.effort, on the
Responses API at api.openai.com only (developers.openai.com/api/docs/guides/
reasoning: mode and effort are independent; pro does more model work and is
billed at the model's rates for more tokens).

The mode is session state, not config and not prompt text:
- control.Controller lists the modes the session's model declares and sets
  one; an undeclared mode is refused with ErrModelModeUnsupported.
- The agent carries the chosen id on every main-loop provider.Request; a new
  conversation starts with it off. Subagents, compaction and side models
  never carry it.
- The Responses adapter sends reasoning.mode only for a mode its endpoint
  declared (projected by boot into the provider config), so a relay, Chat
  Completions or a model without the declaration never sees the field.
- A rebuild carries the mode onto a model that declares it and drops it
  otherwise, so a model switch turns Pro off.
- serve: POST /model-mode, refused as model_mode.unsupported; /status lists
  modes only for a model that declares any.

Studio shows a switch at the bottom of the effort menu only when the model
declares a mode, marked as costing more; the trigger reads "... · Pro mode"
while it is on. The row stays pinned to the bottom of a scrolling menu.
2026-09-28 09:39:17 -07:00
esengine 3bca7e6fbe docs: add a Skills guide
Documents how Reasonix discovers and runs Agent Skills: the project,
custom, global and built-in roots, run_skill and slash invocation,
references/ and scripts/ handling, the extra frontmatter keys, and the
/skills and [skills] controls. agentskills.io asks listed clients to link
a page describing their implementation; this is that page.
2026-09-28 09:24:50 -07:00
xiexian 1bd84d9fdd fix(studio): keep remembered permissions out of workspaces
Persist always-allow rules in private per-workspace user state and load them into the effective policy. Default skill installs to user scope unless project scope is requested, so neither action creates reasonix.toml in the workspace.
2026-09-29 00:16:38 +08:00
YHH 025bd17b2f Merge pull request #11189 from esengine/feat/studio-11166-reasoning-levels
feat(effort): GPT-5.6/GPT-6 and Qwen3.8 reasoning ladders, and send what the menu offers
2026-09-28 08:50:11 -07:00
esengine 211d9e8ed8 feat(effort): declare GPT-5.6/GPT-6 and Qwen3.8 reasoning ladders and send what the menu offers
The model table named GPT-5.6's none..max ladder, but the provider was only
ever told a user-declared supported_efforts list, so on api.openai.com the
request builder refused "none" and "xhigh" at build time and clamped "max" to
"high". RequestEffortLevels now projects the resolved ladder (declared list
first, then the model table for the endpoint in hand) into the provider
config, so every rung the composer offers reaches the wire as itself.

Ladders, each from the vendor's own documentation:
- gpt-5.6 (alias of gpt-5.6-sol), sol/terra/luna: none|low|medium|high|xhigh|max,
  default medium, on Chat Completions and Responses.
- gpt-6-sol / gpt-6-luna: none..max, default medium; gpt-6-astra: low..max
  (none answers 400). Responses only: on Chat Completions GPT-6 takes
  function calls only at reasoning_effort=none, and every turn carries tools.
- Model Studio Qwen3.8 (qwen3.8-max, -max-0902, -flash, -2.4t-a95b, -27b):
  low|medium|xhigh plus none (enable_thinking=false), default xhigh; high and
  max fold to xhigh, minimal to low. Vendor hosts: dashscope(-intl).aliyuncs.com
  and <workspace>.<region>.maas.aliyuncs.com.

A ladder's vendor is now a host set with suffixes (vendorEndpoint), and a
ladder names the provider kinds it was declared for. The model table moves to
effort_models.go. The composer names none ("No reasoning") and tells xhigh
("Extra deep") apart from max, which shared one label.

reasoning.mode "pro" is not added here: it is a Responses-only dimension
independent of effort and needs its own control.
2026-09-28 08:34:47 -07:00
YHH 8c9bbd2557 Merge pull request #11180 from Robertoarce/fix/studio-rewind-menu-labels
fix(studio): translate the rewind menu's scope labels
2026-09-28 07:01:30 -07:00
YHH 908b0aa4b9 Merge pull request #10950 from scrappylabsai/feat/studio-ask-notify-origin
fix(control): name the MCP server and clip the prompt in ask notifications
2026-09-28 06:50:46 -07:00
YHH 572206843f Merge pull request #11176 from KHG420/codex/studio-persist-drafts-9580
fix(studio): restore unsent text drafts per session
2026-09-28 06:37:55 -07:00
Brian ConnellyandClaude Opus 5.5 05723cf928 refactor(control): move askNotificationText into ask_notification.go
approval.go went 10 lines over repolint's 800-line ceiling with the MCP
origin handling. The ask-notification text builder now sits beside its
existing test file; no behaviour change.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 09:34:38 -04:00
Roberto 9269f039df fix(studio): translate the rewind menu's scope labels
The rewind menu's three scope labels and the bash coverage-gap reason are
Chinese literals held in a table and rendered as written:

  <span className="lb">{s.label}</span>

Neither static guard could see them. The catalogue test only collects
Chinese inside a t() call, and the JSX test only reads text between tags or
in a rendered attribute. A table holding Chinese is correct exactly when its
render site calls t(), so the render site was the thing that was wrong. The
browser sweep that is meant to be the judge never opens this menu either:
tools/steps.mjs walks no rewind step.

Route both render sites through t() and add the four keys to the English
catalogue. A Chinese window is unchanged - zh still falls back to the key.

Verified: pnpm typecheck; pnpm test (169 files, 1035 tests).
2026-09-28 15:32:38 +02:00
xiexian 82377396a5 fix(studio): restore unsent text drafts per session 2026-09-28 21:02:40 +08:00
YHH 85a3db0114 Merge pull request #11173 from BuGlessRB/open/project-config-path-resolver
refactor(config): one resolver for the project config path / 重构(config):为项目配置路径提供单一解析器
2026-09-28 04:02:25 -07:00
Stephen R. van den Berg 922efd90a3 refactor(config): one resolver for the project config path
Extract ProjectConfigPath(root) as the single place the project config
path is computed, and route every caller through it: load, credentials,
edit, retired-key migration, MCP migration and boot. It still resolves to
<root>/reasonix.toml, so behaviour is unchanged.
2026-09-28 12:51:12 +02:00
YHH 25bbd84e1b Merge pull request #11172 from chuiwenwei/fix/studio-migrate-index-guard
fix(memory): check the index is readable before migrating facts
2026-09-28 03:46:58 -07:00
Will 3e81444615 fix(memory): check the index is readable before migrating facts
MigrateV2 rewrites a fact and then the index. When the index is unreadable,
reindexIn fails after the fact has already been rewritten, and the next run
skips facts that carry an id, so the entry stays out of the index for good.

The migration now reads the index once per directory before touching any fact,
the same guard RestoreArchived uses, so a failure happens before the first
write and a retry still sees the fact as unmigrated.

Cache note: no prefix bytes move; this only decides whether a migration starts.
2026-09-28 18:22:44 +08:00
YHH 3f86a93bdd Merge pull request #11170 from chuiwenwei/feat/studio-recall-omitted-split
feat(memory): split omitted recall facts by limit and budget
2026-09-28 03:01:04 -07:00
YHH 867cf2752c Merge pull request #11163 from BuGlessRB/open/shell-tool-env
feat(tools): preset environment variables for the bash tool / 工具(tools):为 bash 工具预设环境变量
2026-09-28 02:49:19 -07:00
YHH 129513bde1 Merge pull request #11165 from chuiwenwei/test/studio-restore-budget-assertion
test(memory): assert the pinned-budget refusal in the recovery test
2026-09-28 02:48:32 -07:00
YHH 0feed1a569 Merge pull request #11160 from esengine/feat/studio-no-progress-notice
feat(studio): say when a run stops producing observable progress
2026-09-28 02:45:04 -07:00
Stephen R. van den Berg 88cb1507ec feat(tools): preset environment variables for the bash tool
Add a user-global [tools.shell] env table so every command the bash tool
runs inherits extra KEY=value variables (for example
`BASH_ENV = "~/.reasonix/bashsandbox.rc"`). The value is applied over the
inherited environment but under the host-owned session/egress overrides, so
a preset value never displaces TMPDIR or an egress token.

The variables ride the sandbox spec (sandbox.Spec.ShellEnv), so foreground,
background, and host-terminal runs all inherit them alike. It is
user-global: a cloned repo's reasonix.toml cannot inject variables, because
the shell env is held across the project merge next to the sandbox grants.

Presetting a variable that names a host endpoint (SSH_AUTH_SOCK, DOCKER_HOST,
CONTAINER_HOST, XDG_RUNTIME_DIR) does not unmask it: the denied authority set
resolves from both the host environment and the effective one, so a preset
that points a client elsewhere still leaves the host's own socket masked. The
egress loopback proxy ports are unioned the same way, so a preset proxy is
closed and overriding a variable cannot reopen the host's.

The interpreter itself is still resolved by [tools.shell] prefer/path. A key
that cannot appear in an environment entry is dropped rather than written as
a malformed one, and keys are sorted so identical config yields identical
environment bytes.
2026-09-28 11:37:06 +02:00
YHH ad7cb47518 Merge pull request #11167 from kate1128/fix/restore-archived-readable-index-probe
fix(memory): probe index readability before first write in RestoreArc…
2026-09-28 02:31:45 -07:00
Will b59c001ed9 feat(memory): split omitted recall facts by limit and budget
The recall diagnostic reported one Omitted count for two different decisions:
the per-turn hit cap and the character budget. The note the model reads already
names both, so a reader could not tell which one dropped a fact.

RecallResult now reports OmittedByLimit and OmittedByBudget apart, and the
/memory recall diagnostic prints both. The trajectory audit keeps its combined
Omitted value, so the persisted shape is unchanged, and the model-visible note
still carries the sum of the two.

From #11146's review: "Splitting omitted into limit vs budget would be a nice
separate diagnostic."
2026-09-28 17:23:57 +08:00
Will 5f25195610 test(memory): assert the pinned-budget refusal by identity
Address the two non-blocking nits from #11148's review: state the constraint
the test pins instead of narrating the implementation, and assert the error is
the pinned-budget refusal rather than any error.

The assertion matches on identity rather than on the message:
validatePinnedBudget wraps ErrPinnedBudget, and both the recovery test and the
save path's own test assert with errors.Is. The sentinel's text is the setting
the message already names, so the user-visible message stays byte-identical.
2026-09-28 17:16:29 +08:00
kate1128 04d203959b fix(memory): probe index readability before first write in RestoreArchived 2026-09-28 17:12:44 +08:00
YHH 4160c3307e Merge pull request #11162 from esengine/fix/studio-11159-composer-overflow
fix(studio): keep the composer toolbar inside the composer at every width
2026-09-28 02:02:05 -07:00
YHH fa69e53bdc Merge branch 'studio' into feat/studio-no-progress-notice 2026-09-28 01:55:03 -07:00
YHH 19e77f9313 Merge branch 'studio' into fix/studio-11159-composer-overflow 2026-09-28 01:54:28 -07:00
YHH 9f8ad33f9d Merge pull request #11161 from esengine/docs/studio-2.21.0-notes-wrap
docs(release): split the long backup item in the Studio 2.21.0 notes
2026-09-28 01:41:34 -07:00
esengine e7a9c3f1a7 docs(release): split the long backup item in the Studio 2.21.0 notes 2026-09-28 01:34:45 -07:00
esengine 10276daf1e fix(studio): keep the composer toolbar inside the composer at every width
Between roughly 470px and 720px of conversation column (a wide browser
pane is the usual way there) every toolbar control was flex: none, so the
model/effort group ran past the toolbar's box: the send or stop button sat
on the effort picker and the effort label painted outside the composer.
Below 470px the send cluster was taken out of flow with a fixed 38px
reserve, which the wider stop button does not fit in.

The row now lays out in flow at every width: the send cluster keeps its
own slot at the end of the line, the toolbar wraps inside what is left,
and the model group has a minimum basis, grows to fill the line and lets
the model name shrink to an ellipsis before it moves to a line of its
own. The spacer and the per-width offsets it needed are gone.

perf/composer.mjs sweeps 340-760px idle and running with a long model
name and fails on any control outside the composer, any two controls
overlapping, or a model name narrower than 40px; it fails on the old
stylesheet at 340px (running) and 480-720px (both states).

Fixes #11159
2026-09-28 01:33:59 -07:00
esengine f229559c8f feat(studio): say when a run stops producing observable progress
A run that keeps calling tools without changing anything the host can
observe (#11153: dozens of successful `echo` calls with varying arguments,
about 1.1M input tokens) had no signal short of reading the transcript.
This adds one, without bringing back any round-count guard: nothing is
sent to the model, no call is refused, and the cache-stable prefix and
the per-turn execution-policy block are untouched.

Judgement (kernel, structure only):
- evidence.EffectWatch scores each round's receipts. An effect is a
  successful mutation or named write, a task step moving (StepProof,
  TodoStep, or the host's todo progress counter), a verification whose
  outcome changed since it last ran, a first read (a read-only tool
  answering a request not seen before in this run, or a shell command
  naming an existing path not named before, resolved by stat), or a
  delegated result coming back. A command merely being new is not.
- agent.settleProgressRound runs at each tool-round boundary; the count
  lives in turnRuntime, so a user message zeroes it. A second,
  model-independent backstop compares the input tokens spent since the
  last round with an effect against context window x N; an effect
  restarts both counts, so a long run that keeps doing observable work
  is never flagged however much it spends. Without a ledger the round
  judgement stays silent rather than guess.

Feature 2 (default on): a content-free progress_watch event carries the
reading; Studio shows a one-line strip above the composer with Stop
(the ordinary cancel) and "don't show again (this session)". It clears
itself when an effect lands or the turn ends. The TUI says it once per
stall.

Feature 3 (default off): [progress_watch] pause = true ends the run at
the round boundary through a resumable pause (RunPauseInfo Kind
no_progress, TurnDone outcome no_progress). No finalization nudge is
injected. Continue sends an ordinary message, which starts a new run
with the count at zero. The section is user-level only: load restores
the user's value over a project reasonix.toml. Settings > Session edits
it through GET/POST /progress-watch, which persists to the user file
and hands the new watch to the running executor without a rebuild.
Out-of-range values refuse with progress_watch.out_of_range.

Refs #11153
2026-09-28 01:28:33 -07:00
YHH 38f7a5f600 Merge pull request #11129 from esengine/feat/studio-market-votes
feat(studio): vote on market packages, recommended sort, report installs
2026-09-28 01:23:45 -07:00
YHH ee1352964d Merge pull request #11157 from esengine/docs/studio-2.21.0-notes
docs(release): prepare Studio 2.21.0 notes
2026-09-28 01:16:46 -07:00
esengine de30166094 feat(studio): vote on market packages, recommended sort, report installs
The market entry gains up/down vote buttons that show the signed-in
account's vote; pressing the lit one withdraws it. Signed out, the entry
offers sign-in (a jump to the account section); a publisher, an
unverified account and a paired device each say why they cannot vote.
List rows show approval and install count, and the listing defaults to
the registry's new recommended order.

The kernel owns the calls: market.Client gains MyVote/Vote, which send
the account token only to the fixed registry host with redirects
refused, and ReportInstall. Registry refusals keep their identity
(signed_out, email_unverified, own_package, rate_limited; an identity
outage stays market.unreachable) through market.* refusal codes. Vote routes sit
behind accountAuthAllowed like every other account route.

A successful market install reports once, carrying only the slug and a
random id kept only for the market (market/install-id under the Reasonix
home, unrelated to the usage-statistics id), and only when the host allows
it (release builds), both desktop statistics switches are on, and
neither DO_NOT_TRACK, REASONIX_TELEMETRY=off nor CI opts out.

The [desktop] telemetry and metrics switch descriptions now say that
per-package market install counts are reported (anonymous, no content).
A publisher's own unreviewed install is never reported.
2026-09-28 01:14:53 -07:00
esengine 460ff51eb3 docs(release): prepare Studio 2.21.0 notes 2026-09-28 00:47:28 -07:00
YHH 9b0cc7a38e Merge pull request #11154 from esengine/fix/studio-11151-usage-currency
fix(usage): the usage panel reads money in the currency the session does
2026-09-28 00:37:39 -07:00
esengine 6b0a66b3a2 fix(usage): the usage panel reads money in the currency the session does
The usage panel summed every turn in the currency its price table was
kept in, and ignored the display currency the rest of Studio reads
costs in. A DeepSeek entry holds the vendor's USD table, so a user who
pinned CNY, or whose CNY wallet selected it, saw "US$" on the panel
while the pane's cost meter said "¥".

Every quote already carries the vendor's own table in both currencies,
and the stats line records both valuations; nothing had to be converted,
only read. The usage catalog now keeps each row's valuations and tallies
one partition per view: a row the vendor priced in the display currency
moves into it, and any other row keeps its billed currency, so a relay's
USD price is still shown as USD rather than converted at an invented
rate. The schema version moves to 5, which rebuilds the projection from
the JSONL it is derived from.

The live projection path also dropped the cost of every row a running
process appended, marking the file indexed all the same, so today's
spend vanished from the panel until the next restart. It now carries
the same cost a reindex would.
2026-09-28 00:05:43 -07:00
YHH a98a77b493 Merge pull request #11149 from esengine/feat/studio-11145-inline-skill-chips
feat(studio): skill chips anywhere in the composer line
2026-09-27 23:24:49 -07:00
esengine dac78f1bbd docs(studio): split the chip mirror note to fit the list-item width 2026-09-27 23:05:00 -07:00
esengine ee07b51489 feat(studio): skill chips anywhere in the composer line
A skill in the Studio composer was only the text "/name", and only a
leading one ran: the kernel reads a slash at the start of a line and
nowhere else, so "/review" typed mid-sentence reached the model as prose.

Picking a skill or subagent from the slash menu now places a chip, at the
start of the line or after any "/" that opens a word. A "/" past the start
asks the kernel's completion as if it opened the line and keeps only
skills and subagents, since a command is not something a sentence can
hold. The chip is the record of the pick; the same letters typed by hand
stay prose, so nothing reads the submitted wording to find a skill.

The chip is drawn by a mirror laid over the textarea at the textarea's own
computed metrics, so caret, selection, IME and undo stay native. Edits are
reconciled from the text the chips were last placed in, so every way into
the box (typing, drops, prompt refine, a refused send) carries them, and an
edit that reaches into a chip removes it whole. Skills take --net and
subagents --deleg.

On send the line goes out as typed (input), the text with the chips taken
out (submit), and the chips (invocations). /submit and POST /inbox/items
route a chip line through the inbox envelope, which already carries
invocations to prepareInvocationTurn, so a mid-line chip reaches the model
byte-identical to a leading "/name task". A kernel that predates this reads
only input and still runs a leading chip. TryEnqueueAndSteer queues a line
holding an invocation as a follow-up: a steer is read as guidance and would
drop the skill.

Closes #11145
2026-09-27 22:55:54 -07:00
YHH 4ff9414f4a Merge pull request #11148 from chuiwenwei/fix/studio-restore-subject-guard
fix(memory): enforce save-time invariants when recovering an archived fact
2026-09-27 22:37:00 -07:00
YHH 8da9cac34d Merge pull request #11061 from esengine/fix/studio-8938-deepseek-empty-thinking
fix(anthropic): replay an empty thinking block on DeepSeek tool-call turns
2026-09-27 22:26:46 -07:00
Will a63994d0b9 fix(memory): enforce save-time invariants when recovering an archived fact
RestoreArchived checked id and name collisions but skipped the rest of what a
save validates: a recovered fact could take a subject_key another active fact
now holds, or push pinned guidance over the configured budget. The fact model
states the subject rule unconditionally, and the save path refuses both.

Validate the restored fact with the same validator SaveWithOptions uses, before
any part of the restore is written.
2026-09-28 13:25:47 +08:00
YHH 5a4a21bab7 Merge pull request #11143 from chuiwenwei/fix/studio-index-flush-read-error
fix(memory): preserve the MEMORY.md index when reads fail
2026-09-27 22:24:36 -07:00
YHH 6423d8df85 Merge branch 'studio' into fix/studio-8938-deepseek-empty-thinking 2026-09-27 22:19:53 -07:00
YHH df659ea523 Merge pull request #11135 from esengine/feat/studio-market-private
feat(studio): install your own market packages, and publish privately
2026-09-27 22:09:18 -07:00
YHH 00ba31d044 Merge branch 'studio' into feat/studio-market-private 2026-09-27 21:55:50 -07:00
YHH 510676e625 Merge pull request #11121 from esengine/fix/studio-11115-delta-swap-preflight
fix(update): only take a Windows chunked update where the swap can write
2026-09-27 21:54:56 -07:00
YHH 4f0faa4afe Merge pull request #11133 from KHG420/codex/studio-shared-running-status
fix(studio): reflect running turns on newly joined devices
2026-09-27 21:54:38 -07:00
Will e2e514710d fix(memory): preserve the MEMORY.md index when reads fail
studio still drops the read error when it rewrites the MEMORY.md index:
store.go reads the existing index for indexLinesExceptIn and flushIndexIn
with `existing, _ :=`, so an unreadable index reads as empty and the next
save rebuilds it from the managed lines alone, silently discarding
hand-written content while reporting success.

main-v2 already fixed this in 2eb878989 ("preserve index when reads fail"),
with 79acaf6e9 moving the readers into store_index.go to stay inside the
repolint budgets. This ports the same shape to studio so both lines behave
alike:

- readIndexIn keeps treating a missing index as empty but returns any other
  read error; indexLinesExceptIn reports that error and whether the name was
  present, which lets indexContainsIn go away.
- flushIndexIn and reindexIn propagate the read error instead of rebuilding
  the index from an empty read.
- archiveLocked and archiveMemoryInDir read the index before archiving, so a
  failed read happens before any side effect.
- SaveWithOptions reuses removeMemoryFromDir and archiveMemoryInDir, dropping
  the duplicated remove/flush and archive/flush pairs and keeping the
  function inside its size and complexity budgets.
2026-09-28 12:44:31 +08:00
YHH 9539b02d41 Merge pull request #11142 from chuiwenwei/fix/studio-quickadd-read-error
fix(memory): surface read errors in quick-add instead of overwriting doc files
2026-09-27 21:20:11 -07:00
YHH 84974cb155 Merge pull request #11141 from esengine/ci/replies-discussion-close
ci(replies): close a discussion after answering it
2026-09-27 20:57:14 -07:00
Will f369bb7c4d fix(memory): surface read errors in quick-add instead of overwriting doc files
AppendDoc discarded the read error, so an existing but unreadable doc-memory
file (REASONIX.md/AGENTS.md) was treated as empty and rebuilt from scratch,
silently destroying the user's standing instructions. Surface the read error
unless the file is simply missing.

Ported from 1.x (#8319), which fixed the same defect on main-v2; the 2.x
writer still had the original discard.
2026-09-28 11:56:27 +08:00
YHH 04213f2cd7 Merge pull request #11139 from esengine/chore/update-acknowledgments
Update acknowledgments contributors / 更新致谢贡献者
2026-09-27 20:49:32 -07:00
esengine 560de562bc ci(replies): close a discussion after answering it
A discussion entry may carry close: resolved, outdated or duplicate; the bot
closes it with that reason after posting the reply.
2026-09-27 20:49:23 -07:00
YHH 6ce8bcb460 Merge pull request #11138 from esengine/ci/replies-discussions
ci(replies): answer discussions as github-actions too
2026-09-27 20:46:43 -07:00
SivanCola a99ed416b0 docs: update acknowledgments contributors 2026-09-28 03:40:38 +00:00
esengine e8a0e2aad3 ci(replies): answer discussions as github-actions too
Entries may carry a discussion number instead of an issue number. Discussions
have no REST comment API, so the reply goes through addDiscussionComment and
skips a body the bot already posted there.
2026-09-27 20:38:59 -07:00
YHH d6a6c2855d Merge pull request #11126 from esengine/feat/studio-market-compact
feat(studio): compact market rows and an installable-only filter
2026-09-27 20:33:26 -07:00
esengine 0614529acc test(studio): give the market row fixture the status field publishing added 2026-09-27 20:25:59 -07:00
YHH eb82ea896d Merge branch 'studio' into feat/studio-market-compact 2026-09-27 20:13:51 -07:00
YHH dfeeace5ab Merge pull request #11112 from esengine/feat/studio-market-publish
feat(studio): publish skills, plugins, MCP servers and themes to the community market
2026-09-27 20:12:54 -07:00
xiexian cfc25dc8f1 fix(studio): refresh hidden pane status at turn boundaries 2026-09-28 10:57:58 +08:00
esengine 30ac7c3fe6 feat(studio): install your own market packages, and publish privately
A publisher can now keep a package to themselves and install their own
packages before, or without, review.

- Publishing takes a visibility. "Only me" saves the package as private:
  it skips review and never appears in the market. My packages marks it
  private and offers "Submit for review", which moves it to pending.
- Every row in My packages can be installed. The kernel reads the package
  from the registry's /v1/me/packages/:handle/:name as the signed-in
  account, never from what the window sends, so another account's package
  is refused (market.not_yours).
- A live version keeps its reviewer's pin. Any other version has none, so
  the preview's own contentDigest becomes the pin: apply must echo it
  (market.unpreviewed otherwise) and install_source refuses material that
  changed since the preview. The confirmation says "Unreviewed, visible
  only to you", and the ledger records the install as unreviewed.
- The plan, the risk grades, the multi-skill confirmation and the
  themes-only check are the same ones every market install passes; the
  confirmation view is shared with the market entry page.
2026-09-27 19:48:21 -07:00
xiexian 0040a38ce9 fix(studio): show running state when a device joins mid-turn 2026-09-28 10:29:19 +08:00
YHH ae27da71e2 Merge pull request #11120 from esengine/fix/studio-11116-mobile-model-picker
fix(studio): keep the send cluster from covering the model and effort pickers on phones
2026-09-27 19:04:04 -07:00
esengine b6a2e78aea feat(studio): compact market rows and an installable-only filter
Each market row is now two lines: name, kind, verified/installed badges and
the install count on the first; a one-line truncated summary with the
publisher and version, de-emphasised, on the second. Rows stay buttons, so
they open with the keyboard, and truncate rather than overflow at phone width.

The listing now says which packages can be installed from the market. The
kernel reads the registry's `pinned` per listed package (nil when an older
registry does not say, so nothing is claimed) and forwards `pinned=1` for the
new "Installable only" toggle. The registry applies that filter, so pages stay
whole; a registry that answers a filtered ask with an unfiltered page is
refused as market.filter_unsupported rather than trimmed client-side. The
install path is unchanged and still checks the approved version's digest.
2026-09-27 18:57:32 -07:00
esengine 51c5ce1c86 fix(update): only take a Windows chunked update where the swap can write
The chunked update stages the new tree in the update cache and a helper
swaps it in after exit, renaming each installed file into a backup
directory beside the staged tree. Two installs can never complete that
first rename: an all-users install under Program Files, which grants its
users read and execute only, and an install on another volume than the
user's cache, where MoveFileEx without MOVEFILE_COPY_ALLOWED refuses.
For both, the helper rolled every file back and relaunched the old build
with nothing shown, so the update downloaded, "installed", and the
application came back on the version it started from. Before chunked
updates these installs took the full installer, which raises its own
consent and writes wherever it installed.

tryDelta now proves that move before fetching anything: it creates an
entry in the install root and renames it into the cache's delta
directory. If either step fails the full package is downloaded instead,
as for any other delta that cannot be used.

Closes #11115
2026-09-27 18:47:22 -07:00
esengine 9221be649e fix(studio): keep the send cluster from covering the model and effort pickers on phones
In a conversation column of 460px or less, app.css stretches the send
cluster to width: 100%, and at 470px or less studio.css takes the same
element out of flow (position: absolute; right: 0; bottom: 0). The two
rules combined into an invisible full-width strip over the composer's
last line, so every tap on the model picker or the effort picker landed
on the send cluster. The pickers rendered but never opened on a phone,
over LAN and over the relay alike.

The absolute rule now declares width: auto, so the cluster is only as
wide as the send button. A stylesheet test resolves the cascade for
the cluster at phone widths and fails if it is out of flow and a full
row wide.

Closes #11116
2026-09-27 18:39:53 -07:00
YHH f89cdcc0c4 Merge pull request #11110 from esengine/fix/studio-hardening-0928
fix: harden serve authentication, host git and sandboxed git metadata (studio)
2026-09-27 18:30:36 -07:00
esengine dfc9d1ee28 fix(sandbox): collapse over-budget git groups to one bwrap mount
An over-budget worktrees/ or modules/ group was bound read-only as a whole,
but the exact mounts for the gitdirs inside it were still emitted first, so
planting submodule gitdirs kept growing the bubblewrap argument list. Paths
beneath a read-only tree are now left to that tree.

The serve launch-token sandbox test placed its files under /tmp outside the
write root, which bubblewrap masks with a tmpfs: the control read failed and
the denied reads passed without exercising the forbid-read mounts. The files
now live inside the write root on every platform.

Add English wording for the serve.host_rejected and
auth.launch_token_required refusals.
2026-09-27 18:16:21 -07:00
esengine 9feb5188a7 feat(studio): publish to the community market from Studio, and theme packages
Signed-in users can now submit skills, plugins, MCP servers and themes to
the community registry from Settings > Extensions > Discover, and follow
each submission's review state.

- internal/ext/market: Client.Publish (POST /v1/packages) and Client.Mine
  (GET /v1/me/packages) send the account session as a Bearer token to the
  same fixed https host the listing reads, with redirects refused, so the
  token never reaches another host. The registry's refusal codes are
  projected onto sentinels (ErrSignedOut, ErrEmailUnverified, ErrNotOwner,
  ErrVersionExists, ErrRateLimited, ErrRejected).
- A submission is checked against the same source rule an approved
  version must pass to install (sourceInstallable, shared with
  installable): plugins and themes must name a GitHub tree pinned to a
  full commit. Nothing is sent for review that could never be installed.
- theme is its own listing kind that installs through the plugin
  installer (Installer("theme") == "plugin") under the same commit pin and
  digest check. The plan must consist only of plugin packages that carry
  themes and nothing else (no skills, commands, agents, hooks, MCP servers,
  prompts or runtime); otherwise market.not_theme.
- serve: POST /market/publish and GET /market/mine, gated like every
  account route (host grant plus a stored session), each failure a dotted
  code (market.signed_out, market.unpublishable, market.rejected, ...).
- Frontend: Browse / My packages / Publish views inside the market group
  while signed in, a sign-in prompt otherwise; a Theme category filter.
- A theme's apply re-plans and checks themes-only before writing, because
  a planId binds the source and actions but not the listing kind. A
  registry accounts_unavailable answer reads as market.unreachable, not
  as signed out.
2026-09-27 18:06:24 -07:00
esengine 8a2d067dd0 fix(control): keep controller.go within its line budget 2026-09-27 17:48:08 -07:00
esengine 9ae9bc4bc7 fix(sandbox): keep repository git config and hooks read-only inside the bash sandbox
The host's own git reads the workspace repository's configuration and hooks,
so a confined command that can rewrite them can make the host run a program
of its choosing. The OS sandbox now denies writes to them while leaving the
rest of .git writable.

For the repository git itself discovers from each writable root, protected
paths are: config, config.worktree, commondir and hooks/ of the gitdir and
of the common dir; config, config.worktree and commondir of every existing
linked worktree entry (new entries stay creatable); config, config.worktree,
commondir and hooks/ of every submodule gitdir under modules/. A .git file is followed to the gitdir it names
exactly as git resolves it, and the pointer file and commondir are
protected, so a repository cannot aim the protection at a decoy while git
reads something else. The .git entry, the gitdir, the common dir, every
symlink on the way to them and each writable ancestor are pinned: they
cannot be removed, renamed away or replaced by a symlink, while their own
mode and times stay writable (touch and chmod on the workspace root work).

- macOS: deny rules after every write allowance. Existing worktree and
  submodule gitdirs (up to 128 each, found without following symlinks) get
  exact rules and pins; patterns anchored at the common dir cover the rest
  and gitdirs created later, skipping refs/ and logs/ segments so a branch
  or tag named config or hooks stays writable. The profile stops growing
  past the limit.
- Linux: pinned directories become their own bind mounts and protected files
  and hook trees are bound read-only; only real directories and regular
  files are mount targets. Up to 512 gitdirs per group get exact mounts;
  past that the whole group directory is bound read-only, so planted HEAD
  files cannot overflow the argument list. Only existing paths can be
  mounted and symlinks cannot be pinned.
- Windows has no Bash sandbox; the guide says so.

A protected file with another hard link refuses the confined launch with
sandbox.git_metadata_linked (ErrGitMetadataLinked), since no path rule sees
a write through the other name. Whenever a confined command's output names
a protected path, also on exit 0 (git reports some refused config writes
and exits 0), the bash result carries sandbox.git_metadata_protected.

Real-sandbox tests cover each protected path, pointer and symlink
retargeting, patterns for gitdirs created later, bounded rule size with
2000 submodule gitdirs, ordinary git work, and the git operations that now
fail or silently skip their config write.
2026-09-27 17:36:26 -07:00
esengine 5bcba146d8 fix(gitcmd): host git never runs repository-configured programs (studio)
Every git invocation the host makes goes through gitcmd, and each one must
leave the programs a repository's own configuration names unexecuted:
filter and merge drivers, textconv and external diff, fsmonitor, hooks,
signature verifiers, submodule-boundary programs and lazy-fetch transport.

- Fixed keys are overridden with -c: core.fsmonitor (empty, not "false",
  which git before 2.35.2 reads as a hook name), core.hooksPath,
  log.showSignature, merge.verifySignatures, diff.submodule, gc.auto and
  maintenance.auto and safe.bareRepository=explicit; submodule.recurse=false
  for checkout, switch, restore, reset, merge and read-tree (host tree
  updates never update submodules), and elsewhere only where the
  repository's own config sets it.
  GIT_NO_LAZY_FETCH=1 stops a missing object becoming a fetch through the
  repository's remote (git 2.44+); a historical patch that fails for that
  reason returns gitcmd.ErrObjectNotLocal, decided by git's own
  --missing=print listing.
- diff, log and show carry --no-ext-diff and --no-textconv; status and diff
  carry --ignore-submodules=dirty. The subcommand is found past global
  options, so a call passing -C inside args is hardened too.
- Filter and merge drivers defined at local or worktree scope are listed by
  git itself before each invocation (includes followed) and overridden empty;
  user and system scope stay live; a name -c cannot address fails closed
  (ErrRepositoryDrivers).
- worktree add uses --no-checkout and populates through gitcmd inside the new
  worktree, so a conditional include keyed on the new gitdir or branch is
  seen by the driver listing there rather than run by the add's checkout.
  The reset takes the worktree-mutation timeout, and a failed reset removes
  both the worktree and the branch the add created.
- Launcher ls-remote runs Detached with gitcmd's environment (only PATH is
  taken from the launcher's declared env), requires an https://, ssh:// or
  git:// remote (case-insensitive; http:// and local paths are refused), and
  passes the remote and ref past -- so a leading-dash value is an operand.
- frontend/cli runGit passes its directory through gitcmd so enumeration runs
  in the target repository, not the process cwd.
- Repository identity is resolved once, when a session opens: boot runs
  gitcmd.Open (rev-parse --absolute-git-dir --git-common-dir --show-toplevel)
  before any command, the controller carries the resulting gitcmd.Repo, and a
  rebuild of the same workspace passes it on. Every later host call runs
  through the Repo with GIT_DIR, GIT_COMMON_DIR and GIT_WORK_TREE set, so a
  nested .git, a commondir file or a damaged HEAD written afterwards cannot
  substitute another repository or its config; a damaged HEAD fails closed.
  Threaded through gitstatus Status/Diff (serve /changes and diff),
  worktree Inspect/Create/TakeSnapshot and the candidate/apply/remove paths
  (Snapshot and Candidate carry their Repo; host-created worktrees are opened
  right after worktree add), the isolation store, best_of_n, and cli review
  (resolved once before the review agent runs; ObjectsMissing is a Repo
  method).
- Open accepts only a git dir belonging to the work tree that holds the
  directory: the work tree's own .git (sharing no other common dir) or a
  linked worktree's admin dir under <common>/worktrees whose gitdir file
  names that work tree's .git. A .git file or core.worktree pointing at
  another checkout is ErrNotRepository. Submodule checkouts and
  --separate-git-dir repositories opened directly are therefore treated as
  not version-controlled.
- Repo.Verify applies Open's test to a stored identity; Snapshot and
  Candidate identities are verified when used again.
- The snapshot's private index copy keeps the real index's mtime: with a
  fresh mtime git trusted stat for entries written in the index's own
  second, so a same-size edit made in that second was left out.
- Snapshot staging uses gitcmd Repo.StageAll: gitlinks present as directories
  and untracked nested repositories are excluded with :(exclude,literal)
  pathspecs, because add -A checks a submodule by running git inside it under
  its own config. A gitlink keeps the commit the index records; an untracked
  nested repository is left out (it previously failed the snapshot when it had
  no commit).

Regression tests use real git with payload scripts: the gitcmd config-key
matrix (signatures, lazy fetch, submodule boundary), the worktree call sites
(conditional includes on Create and CreateCandidate), the launcher's non-URL
remote rejection, identity pinning after a nested .git, a commondir file and
a damaged HEAD (config origin and filter marker), Open refusing another
checkout's git dir (.git file, core.worktree, foreign admin dir), StageAll
with nested repositories named like pathspec magic or globs, StageAll and TakeSnapshot
past a gitlink with its own filter, the listing and snapshot of a workspace
that later gains its own .git, and a global submodule.recurse=true against
host checkout and reset. The list-then-run race is left to a follow-up that
denies sandboxed writes to .git config and hooks.
2026-09-27 17:36:26 -07:00
esengine 7a562055e5 fix(serve): authenticate by default; approvals require the launch token
`reasonix serve` now generates a per-launch token unless `--auth` or
`[serve].auth_mode` chooses otherwise. `none` remains an explicit opt-out for
deployments fronted by their own authentication: reads stay open to loopback
and bound names only (421 `serve.host_rejected` otherwise), and every
state-changing request, approvals included, needs the launch token (bearer
header or the /auth/token cookie) and otherwise refuses with
`auth.launch_token_required`. A request the loopback gate, a paired-device
gate or the in-process transport already admitted counts as holding it, so
Studio and the TUI keep working unchanged. Token mode also accepts the bearer
header.

The token never reaches the terminal: serve writes it to a 0600 file under the
remote state directory and prints the path, or names the --token-file of a
managed launch. Runtime sandboxes and read tools deny that directory and every
`--token-file`, `--provider-broker-file` and `--provider-broker-token-file`.

[serve] is read from the user config only, so a project reasonix.toml cannot
choose the token, the auth mode, or trust in forwarded headers.
2026-09-27 17:35:46 -07:00
YHH fba2fbad2a Merge pull request #11107 from esengine/feat/studio-community-market
feat(studio): community market tab, installed pinned to the reviewed content
2026-09-27 17:33:09 -07:00
esengine cf2c2dcdaa feat(studio): community market tab, installed pinned to the reviewed content
Studio can now browse, search and install from the community registry
(Settings > Extensions > Discover). The market is a source, not a trust root:

- internal/ext/market reads the registry read-only: https to the fixed
  crash.reasonix.io host, 15s timeout, capped bodies, redirects refused,
  non-active rows dropped. Registry text is untrusted data.
- Only the approved version is installable, and only when its row carries a
  content digest. The digest travels to install_source as expectDigest.
- install_source gains a contentDigest on every plan (machine-independent:
  SKILL.md bytes, plugin commit, MCP entry) and refuses a plan whose material
  differs (ErrDigestMismatch) or cannot be pinned (ErrNotPinnable). Preview
  and apply both pass the check, and apply writes what its own plan read.
- GitHub plugin sources pinned to a full commit (tree/<sha>) now clone and
  check out that commit instead of failing on --branch <sha>.
- A remote source that expands into more than one skill is graded high, so
  the permission layer asks a person even under a blanket allow, and the
  market confirmation lists every skill and holds the install button until
  the person ticks that they read them.
- Market installs require the planId of the plan that was shown, go to the
  global scope, and are recorded in <home>/market/installed.json; "installed"
  is read back only while the recorded targets still exist.
- serve: GET /market/packages, GET /market/packages/{handle}/{name},
  POST /market/plan, POST /market/install, each failure a dotted code
  (market.unpinned, market.content_changed, market.unreachable, ...).
2026-09-27 17:25:41 -07:00
YHH 5b707c00ee Merge pull request #11109 from esengine/feat/studio-config-backup
feat(studio): back up configuration to the signed-in account
2026-09-27 17:02:20 -07:00
esengine b328f4de7f feat(studio): back up configuration to the signed-in account
Adds an opt-in cloud backup under Settings > Account, shown only while
signed in. The kernel collects the user-level setup by category, seals
it locally (gzip, then XChaCha20-Poly1305 under an Argon2id key derived
from a passphrase the user sets), and uploads only the envelope to the
accounts service; the service never sees plaintext.

Categories: settings (providers, default model, interface look),
extensions (user skills, plugin references by source and version, MCP
servers), memory (user-level instruction files and global facts),
automation (global hooks, status line) and secrets (stored provider
keys, off by default). Without secrets, literal env/header values are
stripped with the plugin exporter's StripCredentials and URL userinfo
is dropped; the account session token never travels. Approval mode,
permissions and sandbox are deliberately not restorable.

Restore is preview-then-apply. The preview decrypts, diffs each item
against this machine, shows everything an item would write (endpoints,
env, headers, cwd, instruction file bodies) and holds the snapshot under
a single-use plan id. Apply recomputes consent against the machine at
apply time and refuses, writing nothing, unless each of these is named
in `consented`: hooks, the status line and MCP servers (code that runs
here); a provider that is new or changes endpoint, and a default model
pointing at one (conversations and keys go there); a key that replaces
one already stored; an instruction file that imports other files.
A snapshot is rejected outright when a key is not read by a provider it
carries, when a provider reads REASONIX_* or a host-steering variable
(proxy, TLS, loader, PATH), or when a unit's payload names another item.
Plugins are never installed by restore; they come back as sources for
the ordinary plugin install flow and its plan approval. File items are
written only under their named root, through validated relative paths
that may not cross a symlink; hidden files are not exported. Config
writes go through config.EditConfigFile, hooks through hook.Save, keys
through config.SetCredential.

Serve: GET/POST /backups, DELETE /backups/{id}, POST /backups/{id}/preview,
POST /backups/apply, gated by the account grant and a signed-in token,
with dotted refusal codes the frontend words in both languages.
2026-09-27 16:33:55 -07:00
YHH 093e5e1b3b Merge pull request #11102 from esengine/fix/studio-11101-ask-reason-label
fix(studio): translate the ask card's reason label
2026-09-27 11:32:58 -07:00
esengine f71fb49285 fix(studio): translate the ask card's reason label 2026-09-27 11:26:21 -07:00
YHH 85020e081b Merge pull request #11095 from esengine/feat/plan-deliverable-gate
feat(agent): the readiness gate holds the plan deliverable
2026-09-27 10:34:32 -07:00
YHH 9e137c4c51 Merge pull request #11098 from esengine/fix/studio-web-bare-entry
fix(web-studio): send a visit without a device to the device picker
2026-09-27 10:19:20 -07:00
esengine 4afe8aba93 fix(web-studio): send a visit without a device to the device picker
The hosted build has no kernel behind its origin, so opening
studio.reasonix.io directly probed /runtimes, got the SPA shell and
reported that the kernel did not answer. The deploy now declares where a
visitor picks a computer, and a visit without ?device goes there.
2026-09-27 10:15:53 -07:00
YHH 1b0d257c5b Merge pull request #11097 from esengine/docs/studio-2.20.4-notes-complete
docs(release): list the fixes and changes Studio 2.20.4 ships
2026-09-27 09:54:49 -07:00
esengine 9a5854f6b6 docs(release): list the fixes and changes Studio 2.20.4 ships 2026-09-27 09:54:30 -07:00
YHH 32f24869c9 Merge pull request #11096 from esengine/docs/studio-2.20.4-notes-restore
docs(release): restore the Studio 2.20.4 notes the release was tagged with
2026-09-27 09:46:03 -07:00
esengine 78e68ef9ea docs(release): restore the Studio 2.20.4 notes the release was tagged with 2026-09-27 09:45:45 -07:00
YHH 46aa95c3ae Merge pull request #11094 from esengine/docs/studio-2.20.4-notes
docs(release): Studio 2.20.4 notes
2026-09-27 09:44:50 -07:00
esengine e70cdeaecc feat(agent): the readiness gate holds the plan deliverable
A plan whose steps name candidate files owes a change the host proved.
The shadow verdict and the receipt already said so; the gate now holds
an answer that changed nothing, and releases a turn that concluded
blocked, is waiting on the user, or carries a change a restart brought
in. The hold names all three ways out.

The executor-handoff nudge now runs before the gate ends the run: an
executor that has not acted yet gets its one in-turn nudge first, and
the gate holds only what the nudge could not recover.
2026-09-27 09:43:11 -07:00
esengine d7df48e18a docs(release): Studio 2.20.4 notes 2026-09-27 09:42:18 -07:00
YHH bc092a8bd4 Merge pull request #11091 from esengine/fix/studio-publish-hash-check
ci(release-studio): publish only the Windows packages the signing jobs recorded
2026-09-27 09:28:15 -07:00
YHH 6ef0be5699 Merge pull request #11090 from esengine/fix/plan-check-baseline
fix(agent): a plan check the host cannot classify does not void itself
2026-09-27 09:23:01 -07:00
esengine 8efaa51368 ci(release-studio): publish only the Windows packages the signing jobs recorded
publish downloads studio-windows-signed by name, but any job in the run can
upload an artifact. windows-sign-installer now outputs the SHA-256 of the
installer it signed and verified, and publish requires the downloaded
artifact to hold exactly the installer and the portable zip, hashing to
that output and to windows-verify-package's archive hash. A mismatch, a
missing hash or an extra file fails with a studio-signing.* title before
anything is signed or published.

The Microsoft-signed d3dcompiler_47.dll and dxil.dll must now also chain to
Microsoft Root Certificate Authority 2010, pinned by thumbprint; subject and
issuer names alone can be issued under any trusted root.
2026-09-27 09:17:49 -07:00
YHH da53ed147a Merge pull request #11080 from esengine/fix/studio-sign-all-pe
ci(release-studio): sign and verify every PE file in the Windows tree
2026-09-27 09:17:06 -07:00
YHH e78ec4bdb5 fix(studio): tolerate slow mobile relay handshakes (#11088) 2026-09-27 09:16:22 -07:00
esengine d1bbd6d013 fix(agent): a plan check the host cannot classify does not void itself
The plan-criteria gate and the frozen contract verdict measured "passed
after the latest change" from LatestSuccessfulMutationIndex, which counts
an unclassified command as a change. A plan check such as `python -c ...`
is exactly such a command, so it became the latest change and owed itself
a later run that could never come: every plan whose checks were inline
scripts ended with its criteria outstanding, graded pass or not.

Both now read LatestProvenMutationIndex, the baseline project checks and
owed review already use: the latest proven write, or the latest unproven
one only when nothing was proven.
2026-09-27 09:15:45 -07:00
esengine 579c59da70 ci(release-studio): sign and verify every PE file in the Windows tree
The Windows release signed only the three executables Studio builds. The
installed tree also ships Electron's ffmpeg.dll, dxcompiler.dll,
vk_swiftshader.dll and vulkan-1.dll unsigned, and Smart App Control / WDAC
can refuse to load an unsigned low-reputation DLL, so the app may not start.

Every PE file the Windows bundle may hold is now declared in
scripts/windows-signing-lib.ps1: seven the release signs, and
d3dcompiler_47.dll and dxil.dll, which Electron ships signed by Microsoft
and which are kept that way and verified as Microsoft-signed. A bundle with
an undeclared or missing PE file (.exe, .dll, .node, or any MZ header) fails
until a person updates the list.

windows-sign-payload signs the declared files, verifies all nine, and
records a SHA-256 digest of the whole signed tree as a job output. The
signed tree is the payload artifact; windows-package packages it as it
came. A new job, windows-verify-package, with no secrets and no
environment, checks the payload against that digest, unpacks the zip and
the installer's app-64.7z, requires both trees to equal the payload file
for file (plus resources/elevate.exe, pinned by SHA-256), and outputs the
SHA-256 of both packages. windows-sign-installer only requires the packages
to hash to those values before signing, and afterwards only runs SignTool
verification, so no archive is parsed in a job holding the Certum
credentials and Certum never signs an installer whose contents did not
match.

Signatures are read from the file itself through SignTool and the embedded
signer certificate. Get-AuthenticodeSignature answers from the OS catalog
for d3dcompiler_47.dll and reports a catalog signature whatever the file
carries.

The contract pins the new scripts, and validate now refuses a fingerprint
file that runs a script under scripts/ which is not pinned.
2026-09-27 09:09:45 -07:00
YHH 5226d8426c Merge pull request #11087 from esengine/fix/studio-8900-web-search-result-delta
fix(anthropic): surface web search results delivered in a result delta
2026-09-27 09:08:54 -07:00
esengine 8835d41f52 fix(anthropic): surface web search results delivered in a result delta
Root cause: some Anthropic-compatible gateways, DeepSeek's among them, open
the web_search_tool_result block with an empty content array and send the
results in a later web_search_tool_result_delta. The stream decoder declared
the delta's results field but never handled the event, so the search produced
no provider-run call: no card, and the only visible source list was whatever
the model wrote into its answer. 1.x fixed the same gap in #9197.

Fix: the server-run search state (serverBlock) now remembers result blocks
that opened empty and completes them from the delta, once per block. The
block-start and argument handling moved onto serverBlock with it, which keeps
readStream within its size and complexity budget.

Test: TestStreamSurfacesWebSearchResultDelta fails on origin/studio
("want 1 provider-run call from the result delta, got 0") and passes after
the fix; go test ./internal/model/anthropic/ passes on macOS and Windows.

Fixes #8900
2026-09-27 09:01:04 -07:00
YHH e269fb5dfe feat(studio): add account-gated remote QR access (#11081)
* feat(studio): add account-gated remote QR access

* test(studio): register cloud QR actions
2026-09-27 09:00:53 -07:00
YHH cc62872d88 Merge pull request #11041 from esengine/dependabot/npm_and_yarn/desktop/frontend-next/vitest-5.0.1
build(deps-dev): bump vitest from 4.1.11 to 5.0.1 in /desktop/frontend-next
2026-09-27 08:58:17 -07:00
YHH 34b1c90599 Merge pull request #11083 from esengine/fix/studio-8635-acp-usage-update
feat(acp): report context size and session cost with usage_update
2026-09-27 08:57:12 -07:00
YHH 31b9f862ad Merge pull request #11084 from esengine/fix/studio-11079-explorer-path
fix(serve): open the theme folder when PATH lacks %SystemRoot%
2026-09-27 08:56:40 -07:00
YHH 21b1fc4e68 Merge pull request #11078 from esengine/fix/studio-11072-ask-history
fix(studio): keep an answered ask readable after a session is reopened
2026-09-27 08:47:39 -07:00
YHH dbac88ee4f Merge pull request #11064 from esengine/fix/studio-6337-blank-line-cause
fix(tools): name a blank-line difference when old_string is not found
2026-09-27 08:47:00 -07:00
esengine c2544fd826 fix(serve): open the theme folder when PATH lacks %SystemRoot%
Settings > Appearance > open theme folder failed on Windows with
`exec: "explorer.exe": executable file not found in %PATH%`.

Root cause: revealFolder started a bare "explorer.exe", which exec
resolves through PATH. explorer.exe lives only in %SystemRoot%, and the
environment Studio is launched with does not always carry that directory
on PATH, so the lookup failed before anything started.

Fix: resolve explorer.exe from %SystemRoot% (falling back to %windir%),
the same way the 1.x desktop's revealPath already does, and keep the
bare name only when neither variable is set.

Test: TestFolderCommandFindsExplorerWithoutPath sets PATH to an empty
directory and asserts the command resolves to %SystemRoot%\explorer.exe.
On Windows it failed on the base with the reporter's exact error and
passes after the fix; the serve Folder/Theme tests pass.

Fixes #11079
2026-09-27 08:45:17 -07:00
dependabot[bot] a5ee283391 build(deps-dev): bump vitest in /desktop/frontend-next
Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 4.1.11 to 5.0.1.
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v5.0.1/packages/vitest)

---
updated-dependencies:
- dependency-name: vitest
  dependency-version: 5.0.1
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-09-27 15:44:07 +00:00
YHH 40f4ae414d Merge pull request #11042 from esengine/dependabot/github_actions/actions-1be3298642
build(deps): bump the actions group with 6 updates
2026-09-27 08:41:43 -07:00
esengine c3d72bcde3 feat(acp): report context size and session cost with usage_update
ACP clients render their token/cost meter from the stable usage_update
session notification (Session Context Size and Cost). The ACP server
never sent it, so clients such as Paseo showed no usage at all even
though the controller already measures the context gauge and the
session status telemetry already totals cost.

After every prompt turn, and after session/load and session/resume,
the server now sends usage_update with used/size from the controller's
ContextSnapshot (the same measurement the compaction trigger uses) and,
when every priced event produced a quote, the cumulative session cost
with its ISO currency. A session with no context window yet sends
nothing, since used and size are required by the protocol.

Test: TestPromptTurnReportsUsageUpdate drives session/prompt through the
real controller and agent with a scripted provider; it fails on the base
("usage_update notifications = 0, want 1 per turn") and passes with the
change. TestUsageUpdateForCarriesCostOnlyWhenPriced pins the wire shape.
2026-09-27 08:41:19 -07:00
YHH c8ae7c5d18 Merge pull request #11069 from esengine/fix/studio-shell-startup-log
fix(studio): log shell and host startup, and say why a launch failed
2026-09-27 08:38:19 -07:00
YHH acc58dfb71 Merge pull request #11075 from esengine/fix/studio-8327-deterministic-reset-fixture
test(openai): pin the body-phase reset fixture to the body phase
2026-09-27 08:37:41 -07:00
YHH 3f52cbfd9f Merge pull request #11039 from esengine/dependabot/npm_and_yarn/desktop/electron/npm-afe829c948
build(deps-dev): bump the npm group in /desktop/electron with 2 updates
2026-09-27 08:33:38 -07:00
YHH bd8ffe1e92 Merge pull request #11040 from esengine/dependabot/npm_and_yarn/desktop/frontend-next/npm-6bd16fb67e
build(deps): bump the npm group in /desktop/frontend-next with 10 updates
2026-09-27 08:32:56 -07:00
esengine 7b8a2b5901 fix(studio): keep an answered ask readable after a session is reopened
Root cause: the live transcript draws a question from ask_request and
hides the ask tool call, since both describe the same prompt. A reload
has no event stream, so fromHistory rebuilt only the tool call - which
the transcript then hid. An answered question vanished entirely after a
reload, session switch or restart, even though its arguments (questions,
options, descriptions) and its result (the recorded answer) are both in
the history.

Fix: when an ask call has a successful result, fromHistory rebuilds it
as a sealed ask card from the call's own arguments, shaped the way
agent.AskTool shapes them for the wire, and carries the result verbatim
as the recorded answer. The card shows every question and option and,
in place of the per-question picks it cannot know, what the kernel
recorded the user said. A call with no result or a failed result stays
a plain tool call. The recorded answer is also searchable.

Test: src/state/ask_history.test.ts and
src/ui/cards/AskCard.recorded.test.tsx fail on the base (no ask item
after fromHistory; the footer reads "Library:未答") and pass with the
fix. Full frontend-next vitest 988/988, tsc clean, make check clean.
2026-09-27 08:32:40 -07:00
YHH 7f6213aaa7 fix(studio): skip replayed history on remote connect (#11073) 2026-09-27 08:29:18 -07:00
esengine d9b76f038e test(openai): pin the body-phase reset fixture to the body phase
TestStreamSurfacesEarlyConnResetAsInterrupt and
TestStreamDoesNotReplayAfterOutput forced a real TCP RST right after
writing the 200 head. Whether net/http observes that RST before or after
the head is up to the OS scheduler. When it lands first, the failure is a
header-phase connection error, which SendWithRetry retries by design, so
the server counts several requests and the reqs == 1 assertion fails
(#8327). A local probe that resets before the head shows the path: the
server saw 11 requests and the call spent 92s in backoff.

The fixture is now a scripted RoundTripper: a 200 SSE response whose body
yields the prelude and then fails with a net.OpError(ECONNRESET). The
phase is fixed, the request counter is atomic, and no socket is involved.
Header-phase retry keeps its own coverage in provider's retry_test.go
(TestTransientErr, TestSendWithRetryRecoversAndNotifies).

Test: both tests pass 500/500 on Windows and on macOS. Mutating ReadCut
and streamFailure so a reset is no longer a StreamInterruptedError makes
both fail, so the fixture still guards the contract.
2026-09-27 08:25:21 -07:00
YHH 163c813f40 Merge branch 'studio' into dependabot/github_actions/actions-1be3298642 2026-09-27 08:25:17 -07:00
YHH 302c846fd2 Merge branch 'studio' into dependabot/npm_and_yarn/desktop/electron/npm-afe829c948 2026-09-27 08:24:39 -07:00
YHH 0c4f686301 Merge branch 'studio' into dependabot/npm_and_yarn/desktop/frontend-next/npm-6bd16fb67e 2026-09-27 08:24:00 -07:00
esengine debf38663a fix(studio): log shell and host startup, and say why a launch failed
A Windows launch from a shortcut has no console, so every way Studio
could fail to start -- boot() throwing, the host exiting before its
handshake, the host never starting -- ended in app.quit() with nothing
on screen and nothing on disk.

The shell now writes <userData>/logs/shell.log (start, lock refusal,
host start/exit/spawn failure, boot failure with stack) and tees the
host's stderr into host.log, each rotated at 1 MB x 3. The launch
credential and any token-, secret- or password-named value are
redacted before reaching either file. A launch that fails shows an
error box (zh/en by the system locale) with the reason, the host's
last stderr lines when it never handshook, and the log folder, then
quits. A second launch refused the instance lock stays silent and only
leaves a log line.
2026-09-27 08:21:34 -07:00
YHH 27ef752d05 Merge pull request #11034 from esengine/dependabot/go_modules/go-ca2f16057d
build(deps): bump the go group with 13 updates
2026-09-27 08:21:29 -07:00
YHH eb70520cfa Merge pull request #11070 from esengine/fix/studio-7867-inline-code-links
fix(studio): open a code span that holds one web address as a link
2026-09-27 08:20:40 -07:00
YHH 5f627edac4 perf(studio): cache fingerprinted web assets (#11071) 2026-09-27 08:19:57 -07:00
esengine 3fa681eb77 fix(studio): open a code span that holds one web address as a link
Models often wrap an address in backticks ("open `http://127.0.0.1:8787`").
Markdown links and bare URLs were clickable in the transcript, but a code
span had no link handling, so the address could only be copied out.

Root cause: GFM autolinking never looks inside inline code, and nothing
else turned a code span into a link.

Fix: a remark plugin rewrites an mdast inlineCode node into a link when its
whole value parses as one http(s) URL with a host. mdast keeps inline code
apart from fenced code, so listings are untouched; a span already inside a
link is left alone. The existing click routing (closest a[href]) opens it
like any other link, and the code inherits the link colour.

Test: Markdown.plugins.test.tsx gains two cases. "links a code span that
holds exactly one web address" fails on origin/studio (expected null to be
truthy) and passes after; the negative case (command with a URL, file://,
bare scheme, fenced block) stays unlinked. tsc and the full frontend-next
vitest suite (155 files, 985 tests) pass; make check is clean. Real-browser
screenshots of the perf build, before and after, light and dark, show the
span underlined in the link colour and the fenced block unchanged.
2026-09-27 08:11:46 -07:00
YHH f02c9f2338 Merge pull request #11067 from esengine/codex/studio-remote-fast-connect
perf(studio): overlap remote authorization with boot
2026-09-27 08:05:32 -07:00
YHH 04ea28361a Merge pull request #11068 from esengine/fix/studio-7338-tree-refresh
fix(studio): refresh the explorer when a writing tool call returns
2026-09-27 08:02:31 -07:00
esengine 6eaae7d27f fix(studio): refresh the explorer when a writing tool call returns
Problem: files the agent creates or deletes appear in the Studio explorer
only once the whole turn ends (or the window regains focus), so a long
turn shows a stale tree for its full duration.

Root cause: the pane re-read the working-tree change list only on turn
boundaries, and the explorer reloaded its listing only when that list,
the running flag, the query or the focus glance changed. Nothing between
turn start and turn end asked the disk again.

Fix: count the finished tool calls that are not read-only (the kernel's
own readOnly declaration on each call) and use that count as a trigger:
the pane re-reads /changes and the explorer re-lists its tree and the
open file whenever it moves. Read-only calls cost nothing extra.

Test: pane-tree-refresh.interaction.test.tsx asserts /changes is read
again after a write_file result and not after a read_file result;
WorkbenchPanel.test.tsx asserts the tree picks up a new file when the
count moves mid-turn. Both fail on origin/studio ("expected changes to be
called 1 times, but got 0 times"; draft.txt never listed) and pass with
the fix. tsc --noEmit and repolint -changed pass; the full frontend-next
vitest run passes 982 of 983, the one failure being a 5s timeout in
session-new.interaction.test.tsx under full-suite load that passes when
run alone, with and without this change.

Fixes #7338
2026-09-27 07:51:51 -07:00
esengine 4cba7d0975 perf(studio): overlap remote authorization with boot 2026-09-27 07:51:19 -07:00
YHH 337b8233bd Merge pull request #11063 from esengine/fix/studio-5226-user-agent
feat(provider): identify model requests as Reasonix/<version>
2026-09-27 07:47:45 -07:00
YHH e383f3febc Merge pull request #11060 from esengine/feat/plan-deliverable
feat(contract): a plan that names files owes a proven change
2026-09-27 07:37:13 -07:00
esengine 131400b9b3 fix(tools): name a blank-line difference when old_string is not found
An edit whose old_string drops (or adds) a blank line the file has between
two otherwise identical lines never matches: the fuzzy matcher frees only a
blank-line run's length, never its presence. The failure then read as
"not found (nearest line N)" plus "re-read the file, check for stale
context", so the model re-read the file and reproduced the same span the
same way, over and over (#6337).

The host can tell this case apart structurally: with blank lines dropped on
both sides, old_string names exactly one span, and that span's blank-line
layout differs from old_string's. oldStringNotFoundError now reports that
span and its cause. Matching is unchanged; nothing new is accepted.

Test: TestEditFileNotFoundNamesBlankLinePresenceDrift (edit_file and
multi_edit, CRLF file) fails on studio with the generic nearest-line
message and passes after; two negative cases hold the claim to one span
with a genuinely different layout.

Fixes #6337
2026-09-27 07:27:56 -07:00
esengine 3a51cb1e42 build(deps): tidy the desktop module for the go group bumps
desktop/ replaces reasonix with the parent module, so the root bumps raise
its graph too, and the Studio desktop and package jobs failed with
"updates to go.mod needed". go mod tidy in desktop/ moves only the
versions the root already requires. This also covers #11033.
2026-09-27 07:20:11 -07:00
esengine d9d436dd70 feat(provider): identify model requests as Reasonix/<version>
Model requests on the OpenAI Chat Completions, Anthropic Messages and
Responses wires, and the /models listing, carried no User-Agent, so Go's
default Go-http-client/1.1 went out. Gateways and relays in front of an
endpoint could not attribute the traffic, and some risk controls aimed at
generic Go clients blocked it.

provider.ApplyClientIdentity now sets User-Agent: Reasonix/<version> on
those requests unless one is already present, so a configured custom
header and the OpenCode Go identity still win. Both hosts (cmd/reasonix
and cmd/reasonix-studio-host) record their ldflags version once at start
through provider.SetClientVersion; an unversioned build reports "dev".

Test: TestRequestsIdentifyReasonix (chat + model listing),
TestMessagesIdentifyReasonix and TestResponsesIdentifyReasonix record the
User-Agent an httptest endpoint receives. Before the wiring all three saw
"Go-http-client/1.1" and failed; after it they see "Reasonix/9.8.7".
TestConfiguredUserAgentWins keeps a custom User-Agent header; the existing
OpenCode Go tests still require exactly "Reasonix" there.
2026-09-27 07:19:02 -07:00
YHH c077f7caac Merge pull request #11058 from esengine/codex/studio-remote-presence
feat(studio): show and control Web Studio sessions
2026-09-27 07:17:24 -07:00
esengine f37f943f78 docs(studio): conform 2.20.4 release notes 2026-09-27 07:12:23 -07:00
esengine c3591b1599 fix(anthropic): replay an empty thinking block on DeepSeek tool-call turns
Root cause: on DeepSeek's Anthropic endpoint in thinking mode, every
assistant tool_use turn in history must carry a thinking block. replayThinking
emitted one only when the turn had stored reasoning. A tool-call turn can be
committed without reasoning (the missing-reasoning fallback in the run loop
keeps the first complete response), and from then on every request in the
session, subagent review sessions included, was refused with HTTP 400
"content[].thinking in the thinking mode must be passed back to the API".
Studio has no repair path for that 400, so the session stayed broken.

Fix: while DeepSeek thinking is enabled, a tool-call turn without stored
reasoning replays {"type":"thinking","thinking":""}, which the endpoint
accepts (live-probed in #8927). contentBlock keeps the "thinking" field when
it is empty, because a thinking block without the field is refused as well.
This matches the OpenAI-compatible DeepSeek path, which already sends an
empty reasoning_content key on such turns. Turns with reasoning and thinking-off
requests serialize exactly as before.

Test: TestDeepSeekToolCallTurnWithoutReasoningStillReplaysThinking drives
Stream against an httptest server that answers like DeepSeek. On origin/studio
it fails with "status 400: ... content[].thinking ... must be passed back";
with the fix it passes. TestDeepSeekThinkingOffSendsNoEmptyThinking guards the
thinking-off shape. go test ./internal/model/anthropic/ ./internal/runtime/agent/
./internal/assembly/boot/, go vet, make check and golangci-lint are clean.
2026-09-27 07:12:20 -07:00
YHH 3ea34bf5d5 Merge pull request #10946 from boscocp/fix/studio-blank-line-runs
fix(tools): match an old_string whose blank-line run differs in length / 匹配空行数量不同的 old_string
2026-09-27 07:12:12 -07:00
esengine a9b84b453b feat(contract): a plan that names files owes a proven change
The P4 demonstration showed the shadow verdict calling a turn done when the
model changed nothing and said the task was complete: with no deliverable
in the contract, a turn that owes nothing reads as finished (#11056 made
such turns "none", which is honest but still not "owed").

Deliverables come from structure, never from the task's wording: a plan
whose steps name candidate files is a plan that expects its work to land
somewhere. Such a plan now adds one criterion, change@plan, satisfied by any
change the host proved in the task. It binds no path: candidate files are
the planner's inference, the right change may touch a subset, and a replan
moves them. Files the planner only read do not count, since an analysis
plan reads files too.

- A turn under such a plan that changed nothing ends incomplete on the
  sealed verdict, and the receipt lists "the plan names files to change,
  and no change was proven".
- Losing the deliverable is a relaxation like any plan check: a later plan
  naming no files is routed to the user when an approver exists, and a
  replan that still names files keeps it.
- Plans with no candidate files, and turns with no plan, are unchanged.

The readiness gate does not hold it yet; that follows the same measured
switch plan criteria took.
2026-09-27 07:11:37 -07:00
esengine 90d22e697c fix(studio): certify remote disconnect state 2026-09-27 07:09:09 -07:00
esengine 7d31478a6d docs(studio): add 2.20.4 release notes 2026-09-27 07:03:58 -07:00
esengine ac7119aa3b fix(studio): retain early remote disconnect state 2026-09-27 07:02:24 -07:00
esengine 3eb10d28ed fix(studio): clarify remote connection failures 2026-09-27 07:01:06 -07:00
YHH 5151f0fef3 Merge pull request #11056 from esengine/fix/verdict-no-evidence-no-outcome
fix(verdict): a turn that owed nothing and changed nothing is not done
2026-09-27 06:52:34 -07:00
esengine e7cb60d9d5 feat(studio): surface and control web sessions 2026-09-27 06:52:25 -07:00
Adalberto Pereira 24a8800de0 fix(tools): require an anchoring line and keep the final newline / 空行匹配需要锚点并保留行尾换行
A blank-line run may drift in length only when non-blank lines bound the
window on both sides, so a blank-only old_string no longer names every run
in the file, and the window now applies the same trailing-newline rule as
the other fuzzy modes instead of writing a newline the file never had.
2026-09-27 10:51:53 -03:00
esengine c8fde2a577 fix(verdict): a turn that owed nothing and changed nothing is not done
The P4 rollout demonstration ran a system-prompt candidate that told the
model not to edit files and to say the task was complete. The rollout gate
rejected it, but most of its 29 false completions were turns the shadow
verdict called completed with no obligations at all: the model changed
nothing, so it owed nothing, and owing nothing read as done. The contract
has no deliverables yet, and the rules forbid guessing from the task text
that a change was asked for.

Such a turn now gets outcome "none": nothing owed and no change the host
saw is no evidence of completing anything. A turn with a successful change
and nothing owed still completes; a failed write is not a change. "none"
agrees with a report that gave no verdict and diverges from one that said
done.

Nothing the user sees changes: the gate never inspected such turns and the
receipt was already absent for them. What changes is the bundle outcome
and every rate built on "completed", which it overstated.

The design doc records the new outcome and the P4 demonstration (promote a
harmless prompt candidate, reject a harmful one, both from bundle metrics).
2026-09-27 06:43:08 -07:00
YHH 112372c7d5 Merge pull request #11055 from esengine/feat/rollout-gate
feat(e2ebench): decide whether a candidate capability set may be promoted
2026-09-27 06:30:44 -07:00
YHH bdb8196a84 Merge pull request #11054 from esengine/codex/studio-remote-controls
feat(studio): serve the full Studio UI over encrypted remote transport
2026-09-27 06:19:26 -07:00
esengine 2d2c576cdf feat(e2ebench): decide whether a candidate capability set may be promoted
First P4 slice of docs/design/TRUSTED_EXECUTION.md: the offline rollout
gate (K1: promotion authority is the offline corpus with repeated samples;
I11: false completion must not rise beyond the noise floor on a corpus with
no-solution tasks).

e2ebench -mode rollout reads baseline reports, "--", then candidate
reports (repeated samples are pooled) and decides:

- hold when the comparison means nothing: a side with no capability record
  or more than one set (rollout.version_unknown), both sides the same set
  (rollout.same_version), different tasks (rollout.corpus_mismatch), no
  no-solution tasks to catch a false completion (rollout.corpus_lacks_no_solution),
  or fewer than 50 solvable runs a side (rollout.insufficient_samples);
- reject when false completion or grader pass is significantly worse, its
  whole 95% interval on the bad side (rollout.false_completion_rose,
  rollout.pass_dropped);
- promote otherwise.

A rise inside the noise floor passes: ruling out any rise at all would take
hundreds of runs. The intervals are printed so the resolution is seen. With
-json the decision is written as a machine-readable record, reasons as
codes.

On the P3 reproduction data (one version, 29 solvable runs a side) it holds
for rollout.same_version and rollout.insufficient_samples.
2026-09-27 06:17:29 -07:00
YHH 33df03a329 Merge pull request #11052 from esengine/feat/eval-reproduce
feat(e2ebench): say whether two reports reproduce one version
2026-09-27 06:11:59 -07:00
esengine 62fe7d4e01 feat(studio): add encrypted Web Studio transport 2026-09-27 06:09:31 -07:00
YHH d25c446a2b Merge pull request #11045 from KHG420/codex/studio-acp-skill-raw-after-11020
fix(acp): preserve typed skill input in Studio turns
2026-09-27 06:01:22 -07:00
esengine 098534c6bf feat(e2ebench): say whether two reports reproduce one version
Second P3 slice. compare mode printed two reports' metrics side by side and
left the reader to decide whether a difference meant anything. It now adds
a reproduction section:

- version: one capability set on both sides, two sets (a comparison, not a
  reproduction), mixed, or unknown for reports that predate the set;
- corpus: whether both ran the same task ids with the same prompts;
- grader pass on solvable tasks, bundle completed, and false completion,
  each with B - A and its 95% interval from Newcombe's method over Wilson
  intervals, which stays sensible at the small counts and 0%/100% rates
  these reports produce.

A report pair that is one version on one corpus with every interval
containing zero is reported as reproducing within the noise floor.

On existing data, one binary run twice over 82 tasks gives intervals of
about +/-15pp, and twice over 30 tasks one rate moved 20pp and was still
inside its interval: the resolution a single run has, now printed rather
than assumed.
2026-09-27 06:00:38 -07:00
YHH 956ba1ed51 Merge pull request #11049 from esengine/feat/capability-digests
feat(trajectory): record the capability versions every run had
2026-09-27 05:41:32 -07:00
esengine 90c2c69716 feat(trajectory): record the capability versions every run had
First P3 slice of docs/design/TRUSTED_EXECUTION.md (I10: a rollout metric
counts only runs whose capability digests match). Until now a trajectory
said which model ran and hashed the prompt and tool schemas as a whole, but
not which skills, and no single field said whether two runs had the same
capabilities.

The run header now lists every capability version the run could use, and a
digest of the set:

- host: the build (version and commit), which carries the planner, memory
  and compaction policies compiled into it;
- prompt: the system prompt;
- tool: each tool schema, MCP tools included, which is where an MCP
  integration's behaviour shows (its launch config can hold secrets and is
  not a version);
- skill: each skill by what it says and how it runs, never by where it was
  found, so one skill on two machines is one version.

e2ebench reads the set digest and reports how many sets a report spans; a
report over more than one is an average over versions and says so.

Checked with a real reasonix.exe run on the Windows box: the header carried
1 host, 1 prompt, 9 skills and 18 tools and a set digest.
2026-09-27 05:33:21 -07:00
xiexian da3a77e764 fix(acp): exclude resolved Studio skills from routing 2026-09-27 20:24:01 +08:00
YHH ce720dd122 Merge pull request #11046 from esengine/feat/gate-reads-plan-checks
feat(readiness): hold plan criteria by their step's check, not a citation
2026-09-27 05:20:56 -07:00
esengine 29364f534f feat(readiness): hold plan criteria by their step's check, not a citation
Stage two of moving completion onto the contract revision: the readiness
gate stops reading the replayed contract, where a complete_step citation of
kind manual settled a plan criterion on the model's word.

A required plan criterion now holds the turn only when its step names a
verification command that has not passed since the latest change (or has
never passed, when nothing changed). The message names the check that
settles it. A criterion no command checks is no longer held: the host
cannot settle it, and holding it only ever asked for a citation. It is
listed on the receipt instead (stage one).

Delivery sign-off still requires complete_step; only its role in settling
plan criteria is gone.
2026-09-27 05:12:40 -07:00
YHH 068367ef1e Merge pull request #11044 from esengine/codex/studio-remote-tasks
feat(studio): add encrypted remote task messaging
2026-09-27 05:10:37 -07:00
esengine 3fd0e093f8 feat(studio): add encrypted remote task messaging 2026-09-27 04:59:53 -07:00
dependabot[bot] 96871f15d4 build(deps): bump the actions group with 6 updates
Bumps the actions group with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [actions/setup-go](https://github.com/actions/setup-go) | `6.5.0` | `7.0.0` |
| [actions/upload-artifact](https://github.com/actions/upload-artifact) | `5.0.0` | `7.0.1` |
| [github/codeql-action/init](https://github.com/github/codeql-action) | `4.37.4` | `4.38.2` |
| [github/codeql-action/analyze](https://github.com/github/codeql-action) | `4.37.4` | `4.38.2` |
| [pnpm/action-setup](https://github.com/pnpm/action-setup) | `6.0.9` | `6.1.0` |
| [actions/download-artifact](https://github.com/actions/download-artifact) | `6.0.0` | `8.0.1` |


Updates `actions/setup-go` from 6.5.0 to 7.0.0
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v6.5.0...b7ad1dad31e06c5925ef5d2fc7ad053ef454303e)

Updates `actions/upload-artifact` from 5.0.0 to 7.0.1
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...043fb46d1a93c77aae656e7c1c64a875d1fc6a0a)

Updates `github/codeql-action/init` from 4.37.4 to 4.38.2
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/f205ea1c3313d32999d8d6a48b4f6530d4437b38...2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2)

Updates `github/codeql-action/analyze` from 4.37.4 to 4.38.2
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/f205ea1c3313d32999d8d6a48b4f6530d4437b38...2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2)

Updates `pnpm/action-setup` from 6.0.9 to 6.1.0
- [Release notes](https://github.com/pnpm/action-setup/releases)
- [Commits](https://github.com/pnpm/action-setup/compare/0ebf47130e4866e96fce0953f49152a61190b271...ea17c68df8912ef543352723c149a84f56e3d413)

Updates `actions/download-artifact` from 6.0.0 to 8.0.1
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/018cc2cf5baa6db3ef3c5f8a56943fffe632ef53...3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: github/codeql-action/init
  dependency-version: 4.38.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.38.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: pnpm/action-setup
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: actions/download-artifact
  dependency-version: 8.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-09-27 11:55:55 +00:00
dependabot[bot] 2d2552a7b9 build(deps): bump the npm group
Bumps the npm group in /desktop/frontend-next with 10 updates:

| Package | From | To |
| --- | --- | --- |
| [@lezer/highlight](https://github.com/lezer-parser/highlight) | `1.2.3` | `1.2.4` |
| [katex](https://github.com/KaTeX/KaTeX) | `0.18.4` | `0.18.9` |
| [react](https://github.com/react/react/tree/HEAD/packages/react) | `19.2.8` | `19.3.0` |
| [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) | `19.2.18` | `19.3.0` |
| [react-dom](https://github.com/react/react/tree/HEAD/packages/react-dom) | `19.2.8` | `19.3.0` |
| [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom) | `19.2.5` | `19.3.0` |
| [@babel/parser](https://github.com/babel/babel/tree/HEAD/packages/babel-parser) | `8.0.4` | `8.0.6` |
| [jsdom](https://github.com/jsdom/jsdom) | `30.0.1` | `30.1.1` |
| [playwright](https://github.com/microsoft/playwright) | `1.62.1` | `1.63.0` |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `8.2.2` | `8.3.0` |


Updates `@lezer/highlight` from 1.2.3 to 1.2.4
- [Changelog](https://github.com/lezer-parser/highlight/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lezer-parser/highlight/commits)

Updates `katex` from 0.18.4 to 0.18.9
- [Release notes](https://github.com/KaTeX/KaTeX/releases)
- [Changelog](https://github.com/KaTeX/KaTeX/blob/main/CHANGELOG.md)
- [Commits](https://github.com/KaTeX/KaTeX/compare/v0.18.4...v0.18.9)

Updates `react` from 19.2.8 to 19.3.0
- [Release notes](https://github.com/react/react/releases)
- [Changelog](https://github.com/react/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/react/react/commits/v19.3.0/packages/react)

Updates `@types/react` from 19.2.18 to 19.3.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

Updates `react-dom` from 19.2.8 to 19.3.0
- [Release notes](https://github.com/react/react/releases)
- [Changelog](https://github.com/react/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/react/react/commits/v19.3.0/packages/react-dom)

Updates `@types/react-dom` from 19.2.5 to 19.3.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

Updates `@babel/parser` from 8.0.4 to 8.0.6
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v8.0.6/packages/babel-parser)

Updates `@types/react` from 19.2.18 to 19.3.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

Updates `@types/react-dom` from 19.2.5 to 19.3.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

Updates `jsdom` from 30.0.1 to 30.1.1
- [Release notes](https://github.com/jsdom/jsdom/releases)
- [Commits](https://github.com/jsdom/jsdom/compare/v30.0.1...v30.1.1)

Updates `playwright` from 1.62.1 to 1.63.0
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](https://github.com/microsoft/playwright/compare/v1.62.1...v1.63.0)

Updates `vite` from 8.2.2 to 8.3.0
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/[email protected]/packages/vite)

---
updated-dependencies:
- dependency-name: "@lezer/highlight"
  dependency-version: 1.2.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: katex
  dependency-version: 0.18.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: react
  dependency-version: 19.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: "@types/react"
  dependency-version: 19.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: react-dom
  dependency-version: 19.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: "@types/react-dom"
  dependency-version: 19.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: "@babel/parser"
  dependency-version: 8.0.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: "@types/react"
  dependency-version: 19.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: "@types/react-dom"
  dependency-version: 19.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: jsdom
  dependency-version: 30.1.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: playwright
  dependency-version: 1.63.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: vite
  dependency-version: 8.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-09-27 11:55:31 +00:00
dependabot[bot] 0708dc650b build(deps-dev): bump the npm group in /desktop/electron with 2 updates
Bumps the npm group in /desktop/electron with 2 updates: [electron](https://github.com/electron/electron) and [playwright](https://github.com/microsoft/playwright).


Updates `electron` from 44.1.0 to 44.4.5
- [Release notes](https://github.com/electron/electron/releases)
- [Commits](https://github.com/electron/electron/compare/v44.1.0...v44.4.5)

Updates `playwright` from 1.62.1 to 1.63.0
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](https://github.com/microsoft/playwright/compare/v1.62.1...v1.63.0)

---
updated-dependencies:
- dependency-name: electron
  dependency-version: 44.4.5
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: playwright
  dependency-version: 1.63.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-09-27 11:54:21 +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 4627950697 Merge pull request #11029 from esengine/codex/studio-account-relay
feat(studio): keep signed-in devices reachable
2026-09-27 04:45:55 -07:00
YHH a86854303f Merge pull request #11032 from esengine/feat/receipt-reads-verdict
feat(receipt): answer plan criteria from the sealed verdict, not a citation
2026-09-27 04:45:11 -07:00
YHH 6c1b8c726a Merge pull request #11030 from esengine/docs/studio-2.20.3-notes
docs(release-notes): add studio 2.20.3 notes
2026-09-27 04:43:04 -07:00
esengine 90559e61a1 feat(receipt): answer plan criteria from the sealed verdict, not a citation
Stage one of moving completion onto the contract revision: what the user
reads changes, what blocks a turn does not.

The receipt's unproven plan criteria came from the replayed contract, where
a complete_step citation marks a criterion satisfied on the model's word.
They now come from the sealed verdict: a required plan criterion is proven
only when every verification command its step names passed after the latest
change. A criterion whose step names no command is listed as unproven with
"no command checks it", instead of being settled by a citation. Every other
gap is unchanged, and a turn with no sealed verdict (no trusted state, or no
contract revision in force) keeps the report's gaps as before.

The receipt card renders gaps, not a verdict noun, so this shows up as
listed items where a citation used to leave none.

Not changed: the readiness gate still reads the citation. Moving it is
stage two, measured first for the extra rounds it costs.
2026-09-27 04:38:02 -07:00
xiexian 419146cceb refactor(acp): keep raw turn method on editor port 2026-09-27 19:36:11 +08:00
xiexian 2a44e1e415 fix(acp): preserve raw text across slash prompt expansion 2026-09-27 19:36:11 +08:00
esengine b10577f508 docs(release-notes): add studio 2.20.3 notes 2026-09-27 04:35:45 -07:00
YHH c196e0879d Merge pull request #11020 from KHG420/codex/studio-inline-skill-raw
fix(skill): route Studio inline skills from raw user input
2026-09-27 04:32:36 -07:00
YHH 01770cc2d3 Merge pull request #11024 from esengine/feat/plan-revision-authority
fix(contract): freeze plan checks by command, and let only the user drop them
2026-09-27 04:28:42 -07:00
esengine 61f5f1c02e feat(studio): keep signed-in devices reachable 2026-09-27 04:27:34 -07:00
YHH 57647afaaf Merge pull request #11027 from esengine/docs/studio-shared-certum
docs(studio-release): describe Windows signing with the shared Certum certificate
2026-09-27 04:21:22 -07:00
esengine 554a9f79c3 docs(studio-release): describe Windows signing with the shared Certum certificate 2026-09-27 04:11:11 -07:00
YHH c017be3312 feat: add remote account client contracts (#11021) 2026-09-27 03:50:35 -07:00
esengine 95c497ba90 fix(contract): freeze plan checks by command, and let only the user drop them
Plan criterion ids are positional within one plan (c1, c2, ...), so
#11017's plan@c1 meant different criteria in two plans of the same goal: a
reordered plan could pass as unchanged, a reworded one went unseen, and an
inserted criterion could read as a dropped one. What decides whether a plan
criterion is verifiable is the command its step names, and a command's
canonical identity does hold across plans. The contract now freezes those
commands, deduplicated with the project's checks, and answers each plan
criterion through the check its step names; a criterion whose step names
none is left to the replayed contract as unverifiable.

The plan flow asked the user only when a revision expanded scope. Dropping
a verification command narrows the work and relaxes the contract, and it
never reached the user. Now:

- a plan the user approved carries that, host-stamped (ApprovedByUser is
  not part of the planner's JSON), and a derivation under it that loses
  only plan checks is accepted as the user's (user:plan_approval);
- a later plan that would drop a check the task accepted is routed to
  approval when an approver exists, even if neither the route nor the
  planner asked for it;
- with no approver the plan runs and the dropped check stays owed.

The report still reads complete_step citations. Retiring that changes
what users see for every planned task with a criterion no command checks,
so it moves with the gate switch, where the incomplete terminal is
designed.
2026-09-27 03:49:51 -07:00
xiexian d9c6e82409 test(skill): assert inline pins do not create routes 2026-09-27 18:39:29 +08:00
esengine dded69678d ci: share Certum signing credentials across release lines 2026-09-27 03:30:29 -07:00
esengine d84bc0c625 ci(release-studio): sign Windows with a Studio-owned Certum certificate
SignPath has no quota left, so Studio Windows builds could only ship
unsigned. Port the 1.x Certum SimplySign signer, pointed at Studio's own
account and certificate.

- Signing runs only when STUDIO_SIGNING_ENABLED=true, as three jobs, so
  the SimplySign session is never up while third-party code runs:
  windows-sign-payload signs the window, kernel and computer-use helper;
  windows-package builds the installer and zip on a separate runner with
  no environment or secrets; windows-sign-installer signs the installer.
  The two signing jobs check out only workflow_sha, install no
  toolchain, leave Defender on, and stop SimplySign after signing.
- Verification checks trust, the pinned thumbprint, the
  STUDIO_SIGNING_SUBJECT subject, an issuer and a timestamp on every
  signed executable, on the installer, and on the zip's copies.
- The SimplySign MSI is pinned by SHA-256 as well as by its signature.
- With the switch on, a missing secret or subject, or any signing
  failure, blocks publish. The release body's Windows sentence follows
  windows-sign-installer's result.
- Credentials are STUDIO_CERTUM_USERNAME, STUDIO_CERTUM_OTP_URI and
  STUDIO_CERTUM_KEY_ID only. The concurrency group is
  studio-certum-signing, shared with a new non-publishing smoke workflow.
- publish downloads every package artifact by its exact name, taking
  Windows from windows-sign-installer when it ran, and fails when two
  artifacts carry the same file name.
- signing-contract validates and fingerprints both the tag's tree and the
  workflow_sha tree.
- The SignPath request steps, artifact configurations and request helper
  are removed. The contract now covers the Certum files, and its call
  graph detects Certum credentials.
- The runbook lists the repository settings to decide on before enabling
  and says which PE files stay unsigned (Electron DLLs, elevate.exe, the
  NSIS uninstaller).
2026-09-27 03:30:29 -07:00
YHH 380683c5d0 Merge pull request #11017 from esengine/feat/plan-criterion-verifier
feat(contract): verify plan criteria by the commands their step names
2026-09-27 03:05:28 -07:00
esengine 8c190bc471 feat(contract): verify plan criteria by the commands their step names
The approved plan's acceptance criteria join the canonical contract. Each
is verified by the verification commands of the step it belongs to: the
user approved those commands with the plan, so they are the plan's own
verifiers, not a check the model picked. A criterion counts only when
every one of them passed after the latest change; a step naming none gives
the criterion no verifier, recorded as verifier.none rather than guessed.

This gives the shadow verdict a host answer for plan criteria that were
otherwise answered by complete_step citations, where the model binds a
check to a criterion. The frozen contract never reads that binding, and a
plan criterion it holds is not counted a second time from the replayed
contract.

Satisfying a criterion with its own step's commands discharges nothing the
host itself owes: stale verification and the project's checks are still
derived separately.

Open, recorded in the design doc: a plan revision the user approves that
drops a criterion is refused like any relaxation, and the report still
reads the citation. Both must change before the gate reads the revision.
2026-09-27 02:58:14 -07:00
xiexian cbea244c51 fix(skill): route inline invocations from raw user input 2026-09-27 17:48:42 +08:00
YHH af0bb1129d Merge pull request #11014 from esengine/feat/todo-claim-edge
fix(contract): a todo marked done is not an acceptance criterion satisfied
2026-09-27 02:44:11 -07:00
esengine 224fb26c5b fix(contract): a todo marked done is not an acceptance criterion satisfied
On an unplanned turn the shadow contract turned every todo into a required
criterion and resolved it Satisfied when the model marked it completed.
Nothing but the model's own call stood behind that, and it reached the
user: a turn whose only action was marking a todo completed got a
completion receipt saying done. P1 measured it as the claim_only
divergence.

A todo is the model's own breakdown of the work, not a criterion anyone
accepted, so it is now recorded as optional and never resolved from its
status. Dropping only the resolution would have left every unplanned
multi-step turn reporting incomplete however well its change was proven;
making todos optional leaves completion to what the host proved, as on a
turn with no todos. An unfinished todo is still the readiness gate's to
hold: it counts open todos itself and never read this contract.

The report's "anything declared" test now counts required criteria only,
so a turn that only marked todos gives no verdict instead of done.

Still open: a complete_step citation binds a check to a plan criterion on
the model's word; that edge goes when plan criteria carry frozen verifiers.
2026-09-27 02:32:55 -07:00
esengine 5f960c776c docs: identify project maintainer 2026-09-27 02:27:09 -07:00
YHH e5488f9a4e Merge pull request #11013 from esengine/feat/grant-scoped-attribution
feat(agent): attribute a delegated run's observed writes by its declared paths
2026-09-27 02:20:40 -07:00
YHH f4a72e4eda Merge pull request #11008 from esengine/fix/studio-11004-inline-skill-pin
fix(skill): send a user-typed inline skill as the pinned invocation run_skill returns
2026-09-27 02:18:16 -07:00
esengine fb73260c2c feat(agent): attribute a delegated run's observed writes by its declared paths
Background and fleet children could not settle an unclassified command:
their walks would see siblings and the parent writing too, so #11009 left
them unknown. A run that declared write paths now takes a change the walk
sees inside them as its own, even while other writers run, and ignores
everything outside them.

This is an inference the user accepted, not a proof: claims are proven
disjoint up front and writer tools are fenced to them, but a sibling's
shell command writing into another run's paths is not excluded. A call
that only saw changes outside its paths stays unknown and is never read as
having changed nothing. A run with no declared paths, the root included,
attributes a walk only while no other background writer runs, and that
check no longer counts a background child as running beside itself.

The design doc records the inference as W11.

Not measured: on current studio the unproven_mutation divergence was 1/21
across seven delegation tasks (#11009), and a comparison costs about
CNY 8 for a difference that small.
2026-09-27 02:12:59 -07:00
esengine cecf61fe78 fix(skill): send a user-typed inline skill as the pinned invocation run_skill returns
A bare "/name" line or a lone skill chip reached the provider as the raw
rendered skill body in plain user prose: no <skill-pin> sentinel, and on the
structured (composer) path the typed text trailed as an unlabeled paragraph
instead of an Arguments line. The model-invoked path (run_skill,
read_only_skill, read_skill) already sent the pinned form, so one skill had
two payload shapes depending on who invoked it.

Every invocation site now renders through skill.RenderInvocation: the
string submit path, the structured path, RunSkill (ACP prompts) and the
slash_command tool. A lone inline chip takes the typed text as its
arguments, matching "/name task"; several inline chips, or chips mixed with
subagents, keep appending the shared text once. Nothing moves into the
cache-stable prefix. Store.Render is removed and Render is unexported, so the
pinned form is the only one an invocation can take.

An effect test through the real Build asserts the pinned payload at the
provider boundary for the bare and argument-carrying forms of both paths.

Refs #11004
2026-09-27 02:09:43 -07:00
YHH f860f7566b Merge pull request #11009 from esengine/feat/child-write-attribution
fix(agent): let a foreground child observe its own unclassified writes
2026-09-27 02:05:59 -07:00
esengine 84daa12aff fix(agent): let a foreground child observe its own unclassified writes
A command the host cannot classify (sed -i, an inline interpreter) is
settled by walking the workspace before and after it. Only the root agent
had a root to walk: sub-agents are built without the write root, so a
child's unclassified command stayed unknown, merged into the parent that
way, and left an unproven_mutation no later check can discharge. On the P1
macOS run this was the cause behind the forced-delegation divergences
(sed -i '' and python3 -c inside children).

- agent.Options gains ObserveRoot, used only for that walk; the root agent
  keeps walking its write root, so nothing else about it changes.
- Delegation hands a writer child the workspace as its observe root only
  when it runs in the foreground: the parent is waiting on it, so what
  changes during one of its calls is that call's. Background and fleet
  writers get none, since their walks would claim their siblings' writes.
- While any background writer is registered, no agent settles by walking,
  the root included: the difference between the two walks is not the
  call's alone.

Measured on the Windows box, 7 delegation tasks x 3 samples per arm: the
unproven_mutation divergence was 1/21 on current studio and 0/21 with this
change, grader 21/21 on both. At that frequency the effect is not
measurable; the change stands on the mechanism and its tests, not on the
number.
2026-09-27 01:56:10 -07:00
YHH f3d1e845fb Merge pull request #10898 from esengine/feat/studio-browser-toggle
feat(studio): give the built-in browser tools their own switch
2026-09-27 01:29:32 -07:00
YHH b3df388a51 Merge pull request #11001 from esengine/feat/unrecognized-check-notice
feat(agent): say why a command that ran code did not verify the work
2026-09-27 01:28:57 -07:00
esengine eb387cec84 feat(agent): say why a command that ran code did not verify the work
A shell command that runs code the host cannot prove read-only, and that is
not a recognized check, counts as a possible change: after it the model is
handed a new stale_verification debt keyed on its own command, and nothing
says why. The model reads that as its check having run, or as the host
asking for the same thing again. The host already knows the reason, so the
first such command in a turn now carries it, with the commands the host
does recognize. The decision reads the host's own classification (not
read-only, verification not_verification, stale verification owed after),
never the command's wording, and it rides the tool result, not the prefix.

Measured on the Windows box, 30 tasks x 2 samples per arm, same binary
apart from this change, run back to back:

  stale_verification divergences  control 34/60 (57%)  notice 19/60 (32%)
  grader pass rate                control 58/60        notice 58/60
  tool calls per task             control 20.0         notice 24.2
  tokens per task                 control 199k         notice 221k

Two-proportion z = 2.76 (p ~ 0.006). The tasks were selected for having
shown the divergence before, so the earlier 70% rate is not the baseline;
the control arm is. The extra calls are the recognized checks the model
now runs.
2026-09-27 01:21:14 -07:00
YHH ef7dd9994e Merge pull request #10998 from esengine/fix/studio-forbidread-test-separator
test(config): compare the forbid-read root after cleaning
2026-09-27 01:09:04 -07:00
esengine 38daa912b1 test(config): compare the forbid-read root after cleaning
Expanding ${REASONIX_WORKSPACE_ROOT}/secret on Windows leaves a forward
slash after the drive-letter path, so the raw string never equals
filepath.Join's result even when the root came from the environment.
2026-09-27 01:01:31 -07:00
esengine eca86cf1f7 feat(studio): give the built-in browser tools their own switch
Studio and the 1.x line share config.toml and both read [browser] enabled,
with opposite defaults: 1.x writes enabled = false on every save of the
user file, which Studio then read as the person turning its browser tools
off, and a Studio save wrote enabled = true, which switched the 1.x command
line's browser on.

Studio now decides on [tools] browser_tools (unset = on) and carries
[browser] enabled through a save exactly as read, never writing it where the
file had none. Boot binds the browser tools from the new key.

The settings screen gets a built-in browser switch under Tools, backed by
GET/POST /browser-tools on the provider-edit grant. The save writes the user
file and rebuilds the runtime, declared as runtime-rebuild.
2026-09-27 00:52:53 -07:00
YHH dfdd3b8ed8 Merge pull request #10990 from KHG420/codex/studio-wide-panel-resize
fix(studio): let the workbench use wide displays
2026-09-27 00:47:10 -07:00
YHH b6f50be5f9 Merge pull request #10996 from esengine/feat/canonical-contract
feat(contract): accept the task contract as a revision in trusted state
2026-09-27 00:42:28 -07:00
esengine 1079ee6276 feat(contract): accept the task contract as a revision in trusted state
First P2 slice of docs/design/TRUSTED_EXECUTION.md. The task contract is now
a host-owned record with an identity and immutable revisions, not a replay
rebuilt from receipts each turn.

- internal/runtime/contract derives criteria only from sources the host
  owns: the check identities a task began requiring and the test criteria
  captured as host-owned bytes. Derivation is canonical, so the same sources
  give byte-identical revisions. Host policy accepts a first revision and
  any revision that keeps every criterion and adds to them; one that would
  drop or alter a criterion is refused and the revision in force stands.
- Revisions are sealed as contract/1 records in the workspace stream. The
  delivery checkpoint names the record, so a goal resumed in another
  process is held to the revision it accepted, read back from Trusted Host
  State. A name the store cannot produce is reported, never replaced by a
  fresh acceptance. A new task starts a new contract.
- The shadow verdict answers each accepted criterion from the ledger, not
  from the checkpoint's copy of the declaration, and drops the host
  obligation that owes the same verifier so one debt is not counted twice.
  The audit and trajectory carry the revision, the policy decision and any
  load failure.

Still shadow: the readiness gate is unchanged. The refusal test edits a
checkpoint by hand, which nothing in production does; it demonstrates that
the checkpoint is not the authority, not a reachable bypass.
2026-09-27 00:33:48 -07:00
YHH fc12067a95 Merge pull request #10864 from par73e/feat/studio-model-provider-order
feat(studio): allow reordering model providers
2026-09-27 00:32:56 -07:00
YHH 91339352f7 Merge pull request #10985 from esengine/docs/studio-2.20.1-notes
docs(release-notes): add studio 2.20.1 notes
2026-09-27 00:32:00 -07:00
esengine fafec2c650 docs(release-notes): add studio 2.20.1 notes
The tag shipped without a notes file. This records what it contained, under
the release-note rule: every change cites its pull request or commits.
2026-09-27 00:25:31 -07:00
xiexian d605ad57b6 Merge remote-tracking branch 'origin/studio' into codex/studio-wide-panel-resize 2026-09-27 15:07:24 +08:00
xiexian 8379d61dc9 fix(studio): reuse viewport observer for dock limit 2026-09-27 15:03:52 +08:00
YHH 32982fd6d7 Merge pull request #10984 from esengine/fix/studio-release-notes-authors
ci(release-studio): credit pull request authors and issue fixers in published notes
2026-09-27 00:01:22 -07:00
YHH 9a80ceb5aa Merge branch 'studio' into feat/studio-model-provider-order 2026-09-26 23:54:27 -07:00
YHH c5afcc27a1 Merge branch 'studio' into fix/studio-release-notes-authors 2026-09-26 23:47:56 -07:00
YHH 14682bbd67 Merge pull request #10991 from esengine/fix/studio-2.20.2-notes-lint
fix(studio): turn studio CI green again (2.20.2 notes lint, Windows config test)
2026-09-26 23:46:42 -07:00
esengine 4f2c1fe6ba test(config): pin the forbid-read variable to a real environment value
With the variable set empty the pattern expands to "/secret", which is
absolute on Unix but drive-relative on Windows, so it resolved under the
workspace root and the test failed on every Windows run since 0a0e1989a.
Pointing the variable at a separate directory asserts the actual property -
the value comes from the environment, not the root - on every platform.
2026-09-26 23:38:27 -07:00
esengine 54a4d92963 docs(release-notes): bring studio 2.20.2 notes under the release-note rule
The notes landed with section headings outside the allowed set and changes
that name no pull request, which pushed the repo-wide release-note count past
its ceiling and turned lint red on studio. Same content, each change now cites
#10970, #10980 or #10982.
2026-09-26 23:36:48 -07:00
par73e 61d1de4374 feat(studio): allow reordering model providers 2026-09-27 14:34:51 +08:00
esengine 11920eb387 ci(release-studio): credit pull request authors and issue fixers in published notes
The publish job now renders release-notes/studio/<semver>.md before it
signs anything. A #N that is a pull request gains "by @author"; a #N that
is an issue gains "fixed in #M by @author" for each merged pull request
GitHub links as closing it (the ClosedEvent closer and
closedByPullRequestsReferences, read over GraphQL; mentions are not
fixes). An issue fixed by a direct push stays plain. A 贡献者 list of the
credited humans, in first-appearance order, closes the notes. Bot authors
are never credited; the source file is not rewritten.

scripts/markdown-refs.mjs decides what is a reference: #N at a line start
or after whitespace, "(", "(" or a list separator, outside code spans,
backtick and tilde fences (each closed only by its own kind), indented
code, HTML comments and link targets. A number that names a discussion or
nothing stays plain with a warning instead of failing the release.

Lookups retry with backoff on 5xx, rate limits and dropped connections. A
failure that persists, or an authorisation failure, fails the job with a
release_credits.* code before signing. The publish job gains
issues: read and pull-requests: read.

CI gains a release-notes-scripts job, gated on scripts/ and
release-notes/studio/, that runs the three test files with contents: read.

scripts/release-credits.mjs is the same module the 1.x renderer uses.
2026-09-26 23:28:25 -07:00
xiexian 3a071b4501 fix(studio): let workbench use wide displays 2026-09-27 14:16:20 +08:00
YHH 57e3c6da36 Merge pull request #10986 from esengine/codex/studio-release-notes-required
ci(studio): require auditable release notes
2026-09-26 23:14:03 -07:00
esengine d6d4bd2a50 ci(studio): require auditable release notes 2026-09-26 23:10:21 -07:00
YHH f753f860ed Merge pull request #10982 from esengine/codex/studio-telemetry
feat(studio): report Studio telemetry separately
2026-09-26 22:49:45 -07:00
esengine e766145571 feat(studio): report Studio telemetry separately 2026-09-26 22:41:37 -07:00
YHH 011c2738f8 Merge pull request #10970 from esengine/fix/studio-10965-session-image-blobs
fix(sessionstore): keep image payloads out of the replayed event log
2026-09-26 22:33:22 -07:00
YHH a4120eaa32 Merge pull request #10980 from esengine/fix/verdict-obligation-kind
fix(verdict): name the host obligation kind in divergence reasons
2026-09-26 22:23:06 -07:00
YHH 0758401871 Merge branch 'studio' into fix/studio-10965-session-image-blobs 2026-09-26 22:20:51 -07:00
esengine fe29c01702 fix(verdict): name the host obligation kind in divergence reasons
A full e2ebench run put ten of eighteen divergences under one reason,
obligation.outstanding, and e2ebench gives every task its own state root
and deletes it afterwards, so the sealed bundle that said which obligation
it was does not survive the run. The kind is a content-free enum and is
what decides which old path owes the difference, so the audit now carries
host:<kind> in its place.
2026-09-26 22:09:00 -07:00
esengine 2c619312b3 fix(sessionstore): keep image payloads out of the replayed event log
Every save replays the event log before it appends, and the reader refused a
log past 128 MiB. Images were stored inline as data URLs, so a session with
about 2 MiB of images per turn crossed the budget around turn 63: from then on
LoadSession failed and every Save failed with the same error, and later turns
never reached disk.

New records write each data-URL image to <id>.blobs/<sha256>, fsynced before
the record that names it, and carry an image_blobs side table instead. Replay
reads each blob back byte for byte and verifies its hash. Such records are
stamped schema_version 3, so older studio and 1.x builds refuse the log
instead of loading it without its images and saving that loss back.

A missing blob is restored from the copy the .jsonl checkpoint still inlines
and written back. With no copy left, the message keeps its text and records
the lost image as provider.UnavailableImage (hash and position); only
ModelMessages renders a note, and the blob reappearing restores the image. A
transcript missing an image never rewrites a checkpoint that still holds it,
and a runtime still holding an image whose blob vanished writes it back
instead of being reported as diverged; what cannot be restored fails under
ErrSessionImagesUnavailable.

The reader ceiling is 512 MiB. A save folds a log past 128 MiB only when the
fold drops at least 64 MiB, so a legacy inline-image log is folded to its text
once, while a large text-only log is not rewritten on every save. A compaction
sweeps blobs that neither the published log nor a salvage or recovery
sidecar (.events.jsonl.damaged, .superseded, .context.json) names, and stale
temp files; a sidecar it cannot read whole stops the sweep. The stored
compaction projection is built with ProjectionMessages, so an unavailable
image stays a host record there instead of rendered text. The
blob directory is a session sidecar dir: delete, rename (blobs before the
log), recovery trash, native re-homing and sub-agent delete take it along.

Refs #10965
2026-09-26 22:05:46 -07:00
YHH be4d04c9cd Merge pull request #10976 from esengine/codex/studio-unified-login
fix(studio): open account approval in system browser
2026-09-26 22:01:22 -07:00
YHH c9003d4718 Merge pull request #10963 from esengine/fix/studio-10957-sidebar-loading
fix(studio): show the folder list as loading until the first tree read settles
2026-09-26 21:54:48 -07:00
YHH ffaffeaeee Merge pull request #10974 from esengine/feat/bundle-verdict
feat(verdict): decide each sealed turn from host evidence alone
2026-09-26 21:51:05 -07:00
esengine f516ab81ec fix(studio): open account approval in system browser 2026-09-26 21:44:32 -07:00
esengine 4e16aca0ba fix(studio): show the folder list as loading until the first tree read settles
Problem: on start-up the sidebar said "No folders yet" (尚无文件夹) and
"0 projects" under This machine, and the empty transcript asked to open a
project first, until the kernel answered the first tree read. A slow start
looked exactly like a machine whose folders had been lost.

Root cause: App held the tree as TreeWorkspace[] initialised to [], so "not
read yet" and "read, and empty" were the same value, and every reader of it
(the sidebar empty row, the project count, needsProject) drew the latter.

Fix: App tracks treeRead, set once the first hub.tree() read settles. Until
then the sidebar shows a "Reading folders…" status row instead of the empty
row, hides the project count, and needsProject stays false. With no pane
open, the main area says only that no session is open, without the
add-folder prompt it gives once the tree is read and empty. A failed read
still settles, as before, so the window never waits forever.

The row carries the clock icon on the existing tool-running spin (still
under prefers-reduced-motion) and, past one second, the elapsed time via
format.seconds, ticking once a second. The tick is display only and is
aria-hidden, so the status region is not re-announced every second; the
interval lives with the row and is cleared when the read settles or the
row unmounts.

Test: sidebar-first-read.interaction.test.tsx holds hub.tree() pending,
asserts the loading row and no empty-state wording, then resolves [] and
asserts "No folders yet"; a second case with no panes asserts the main
area's add-folder hint is absent until the read settles. Both fail on origin/studio (no loading row) and
passes with this change. tree-reading.interaction.test.tsx uses fake
timers: the elapsed value reads 3s then 5s, is aria-hidden inside the
status, disappears with no timers left once the read settles, and the
interval is cleared on unmount. The perf harness gains ?treems= to hold
tree reads back and ?panes=0 to open with no pane, used for before/after
screenshots in light and dark; perf/motion.mjs counts the reading row's
spin as live work.
2026-09-26 21:39:51 -07:00
esengine 63952c3e8d feat(verdict): decide each sealed turn from host evidence alone
Third slice of the trusted execution layer (P1). Every shadow bundle now
carries an outcome derived only from host evidence, and how that outcome
diverges from the completion report's verdict.

- internal/runtime/verdict: Evaluate turns the contract's criteria and
  checks, the receipts and the ledger's outstanding obligations into one
  verdict per obligation and an outcome (completed, incomplete, failed,
  blocked). It never reads a criterion's status, because todo_write and
  complete_step can set it; before contracts freeze verifiers, the only
  criterion host evidence satisfies is an atomic ask proven by a successful
  mutation. Checks keep the state the contract derives from receipts.
- Classify compares that outcome with the report's verdict: agree,
  new_stricter or new_looser, with reason codes (claim_only, evidence.stale,
  evidence.missing, check.failed, obligation.outstanding, verifier.none,
  old_gaps, old_criteria).
- The EvidenceBundle audit carries outcome, class and reasons, and
  e2ebench prices them like the completion report: false completion is a
  completed bundle on a run the grader failed, next to the divergence and
  reason census.

TestEffectClaimOnlyCompletionDivergesThroughRealBuild runs the known hole
through boot.Build: a todo marked completed with no other action finishes
the turn and the report says done; the sealed bundle says incomplete,
new_stricter, claim_only.
2026-09-26 21:29:24 -07:00
YHH b31cf3bd74 Merge pull request #10971 from esengine/feat/workspace-snapshot
feat(observation): snapshot the workspace around every turn
2026-09-26 21:22:13 -07:00
esengine d475a2fb6d feat(observation): snapshot the workspace around every turn
Second slice of the trusted execution layer (P1). The shadow bundle now
records the workspace as each turn found and left it, what changed during
the turn, and what changed between sealed turns while nobody observed it.

- internal/state/observation: a snapshot is a tree of directory nodes stored
  as content-addressed index objects in Trusted Host State, so an unchanged
  directory costs nothing to record again and two snapshots diff by walking
  only subtrees whose digests differ. The policy is host-owned; the default
  excludes version-control stores and the host's own state root, each with a
  reason, and keeps dependency trees observed because verifiers read them.
  Ignore files and workspace config are never consulted.
- A file is identified by its stamp (size, mtime, and on macOS/Linux ctime,
  inode, device), not a content hash. On this repository's main checkout
  (107,803 entries, 2.1 GB of node_modules) a cold snapshot takes 1.9 s and a
  warm one about 0.2 s; hashing every file would cost seconds per process.
- trustedstate gains PutIndexObject, which skips the per-object fsync: tree
  nodes are an index, not a seal, and with fsync the first snapshot of this
  repository took 31 s. A node lost to a crash makes a later diff
  unverifiable, never a satisfied comparison.
- The turn-start snapshot runs while the first model request is in flight;
  the first tool batch waits for it, so nothing the turn does lands in its
  own baseline. The previous snapshot is read from the stream's head record,
  so edits made between sessions are seen too.
- The EvidenceBundle audit adds snapshot completeness and the count of
  unobserved changes.

The design doc's W1/W3 now say what was built and why (W8-W10).
2026-09-26 21:14:18 -07:00
YHH 6ef1a5fa34 Merge pull request #10969 from esengine/feat/evidence-bundle
feat(trusted-state): seal every turn's shadow evidence bundle
2026-09-26 21:14:08 -07:00
esengine d41b8824f7 feat(trusted-state): seal every turn's shadow evidence bundle
First slice of the trusted execution layer (docs/design/TRUSTED_EXECUTION.md,
P1). Nothing reads the bundle back to decide a turn.

- internal/state/trustedstate: content-addressed objects, one record chain
  per stream, a head file replaced by rename. A head this process already
  observed refuses rollback, a same-generation swap and a history that no
  longer reaches it; a store with no observed head cannot tell a forged but
  self-consistent chain, and the tests pin exactly that. Failures carry
  sentinels and a typed code.
- IntegrityLevel is supplied by the components that confine what the model
  drives: sandbox.WriteProtects (the platform's effective write roots, both
  overlap directions, symlinked ancestors), plus one-way flags set by the MCP
  launcher and the extension sidecar launcher whenever they start a process
  outside the sandbox, including a "confined" MCP launch that fell back
  because no backend was available.
- File tools refuse writes into <state>/trusted with a typed refusal
  (workspace.trusted_state_write); an explicit allow_write entry keeps raw
  access and also reaches the bash write roots, which drops the level.
- The root agent seals the contract, completion report and receipts at the
  end of every turn, including turns the readiness gate stopped. Receipt
  arguments are kept as digests only. A new content-free EvidenceBundle
  audit reaches the trajectory with the record digest, generation, integrity
  and the typed cause when sealing failed.

The design doc records what implementing this showed: MCP servers default to
host mode and sidecars always run unconfined, so installs with either are
tamper-evident only, and the level is per process (another process running
as the same user can write the store).
2026-09-26 21:03:21 -07:00
YHH 6267aaa64c Merge pull request #10952 from KHG420/codex/fix-typesafe-model-check
fix(studio): verify TypeSafe models through System One
2026-09-26 20:48:16 -07:00
YHH 7451f575bf Merge pull request #10966 from esengine/docs/trusted-execution
docs(design): trusted execution layer
2026-09-26 20:35:06 -07:00
esengine c6ad2294db docs(design): trusted execution layer
Define the host-owned layer behind "allowed to start", "completed with
evidence" and "allowed to ship": trust boundary, canonical objects
(Task Contract, Evidence Bundle, Capability Rollout, RiskPolicy,
ObservationPolicy, WorkspaceSnapshot, TrustedHead), state machines,
invariants and typed failure semantics. No UI and no implementation.

Workspace content, including REASONIX.md and .gitignore, is source
material for revision requests and never changes enforcement. Hash
chaining is tamper evidence relative to a trusted head, not an
authority boundary, so L3 enforcement requires host-protected state.

The document lives under docs/design/ so the embedded product-doc
corpus (docs/*.md) cannot present unshipped design as a feature.
2026-09-26 20:26:03 -07:00
YHH 5b764405a6 Merge pull request #10907 from esengine/fix/studio-windows-sandbox-attribution
fix(tools): attribute out-of-workspace writes to the write scope, not a sandbox
2026-09-26 20:19:54 -07:00
xiexian f54bf03787 fix(studio): verify TypeSafe models via System One 2026-09-27 11:18:39 +08:00
YHH bf7e0e96b8 Merge pull request #10960 from esengine/fix/studio-3220-delete-key
feat(studio): Delete on a focused sidebar session asks to delete it
2026-09-26 20:13:17 -07:00
YHH 044942e9e0 Merge pull request #10959 from esengine/fix/studio-4135-hide-amounts
feat(studio): hide the wallet balance and session cost behind an eye toggle
2026-09-26 20:13:10 -07:00
YHH 903f9e1c8c Merge pull request #10958 from esengine/fix/studio-8136-ask-pick-note
fix(ask): let a single-choice pick carry a typed note
2026-09-26 20:07:16 -07:00
esengine 6b4a684eac feat(studio): Delete on a focused sidebar session asks to delete it
Delete (and Cmd+Backspace on macOS) on the focused session row, or on a
kept version/recovery copy row, opens the same inline confirmation the row
menu's delete opens. Only a row holding focus reacts: the handler requires
the row itself to be the event target, never a hover target, and stays out
of the way while the row is being renamed.

The confirmation now also answers Escape as cancel, without letting the key
reach the window's stop-the-run handler, and focus returns to the row the
key asked about so a keyboard is not stranded on the page body.
Focus goes back only when the question is dismissed, not when another
row's question replaces it; after a delete lands it moves to the next row,
or the previous one when the deleted row was last.

The row keydown declares both session.open and session.delete, and the
copy row's delete button and the confirmation's cancel carry their action
ids, keeping the census's unnamed-write count where it was.

Fixes #3220
2026-09-26 19:58:44 -07:00
esengine cb7261a920 feat(studio): hide the wallet balance and session cost behind an eye toggle
A screen share or a recording leaks the account balance and the session's
spend as readily as anything else in the meter bar. An eye button beside the
wallet masks both as ••• and persists per machine (rx-hide-amounts), so they
stay hidden across restarts. The sidebar's wallet entry and every amount in
the cost card (session total, this turn, original currency, per-source rows,
wallet) are masked too. Only the drawing changes: the wallet keeps being
read, so revealing shows the current value without a request.

Fixes #4135
2026-09-26 19:58:29 -07:00
esengine 61630d5313 fix(tools): attribute out-of-workspace writes to the write scope, not a sandbox
The file writers refuse a target outside workspace_root and allow_write on
every OS, including Windows where bash is never OS-sandboxed. The refusal
was a plain error whose text said writes were "confined" and told the
reader to "widen [sandbox] workspace_root / allow_write", so a Windows user
who never enabled a sandbox was told by the agent that it had been
sandboxed.

confine now returns a tool.Refusal coded workspace.write_outside_scope that
names the refused path, the writable directories, that this is the file
tools' scope rather than an OS sandbox, and the setting that allows it. The
agent carries a tool.Refusal's code onto the tool result on the error path,
so the frontend receives the cause as an identity, and the tool card renders
the localized cause from that code. The scope itself is unchanged.
2026-09-26 19:57:44 -07:00
esengine f652aafa00 fix(ask): let a single-choice pick carry a typed note
An option can ask for more than itself ("paste the output"), but once it
was picked there was nowhere to type: in Studio the pick and the free-text
box were mutually exclusive, and in the TUI a pick answered at once.

Studio keeps the box open under a single-choice pick as an optional note,
so the answer goes out as [pick, note]; the kernel already joins several
selections. The note is its own state: an Other answer left behind when a
pick is made is not sent as a note, and going back to Other finds it still
typed. Choosing Other still makes the typed text the whole answer, Enter in
the note box answers like Enter in the Other box, and a sealed card shows a
pick's note under the pick rather than as Other.

The TUI takes Tab on a single-choice option as "pick and add a note": the
composer opens for the note, Enter sends it after the pick, Esc keeps the
pick without one. Picking an option now also drops a previously typed
answer, which a single-choice question used to send alongside the pick.

Fixes #8136
2026-09-26 19:50:40 -07:00
YHH 9d02251b26 Merge pull request #10944 from esengine/fix/studio-10188-strong-colour-token
feat(theme): let a pack colour bold transcript text
2026-09-26 19:49:49 -07:00
YHH 26920430bb Merge pull request #10869 from KHG420/codex/studio-slash-loading
fix(studio): show slash completion loading state
2026-09-26 19:49:07 -07:00
YHH 5d4ec7c65c Merge pull request #10926 from esengine/fix/studio-8568-copy-label
fix(studio): name what a copy button copies in its tooltip and text
2026-09-26 19:48:25 -07:00
YHH 7b9b7a679d Merge pull request #10917 from esengine/fix/studio-7934-thinking-click-fold
feat(tui): open an answer's thinking with a click or Ctrl+O in full screen
2026-09-26 19:46:58 -07:00
YHH 96b7129f0d Merge pull request #10956 from esengine/fix/studio-9107-write-paths-guidance
fix(delegation): steer write_paths toward file paths over directories
2026-09-26 19:46:30 -07:00
YHH c91658d452 Merge pull request #10916 from esengine/fix/studio-7904-ask-other-keyboard
fix(studio): let a typed "other" answer be given from the keyboard
2026-09-26 19:46:17 -07:00
YHH 22730e87d6 Merge pull request #10954 from esengine/fix/studio-10921-opencode-session
fix(model): send x-opencode-session to OpenCode Go
2026-09-26 19:42:26 -07:00
esengine 0cbcbbf5b9 fix(delegation): steer write_paths toward file paths over directories
A directory claim overlaps every path beneath it, so two writers that
name the same directory serialize even when they touch different files.
The task and fleet descriptions and their write_paths properties now say
to declare file paths when the targets are known.

Both tools sit behind use_capability, so the provider-visible prefix and
the boot golden fixtures are unchanged.
2026-09-26 19:37:59 -07:00
esengine 3cabe4c4e7 fix(model): send x-opencode-session to OpenCode Go
OpenCode Go refuses third-party requests that carry no session:
"Request is missing x-opencode-session and cannot be routed efficiently"
(HTTP 400). The 1.x provider layer sends that header; the studio adapters
never did, so every OpenCode Go call failed on its first request while
the same configuration worked in 1.x.

Each of the three adapters (chat, messages, responses) now mints one
session id per client and presents it, together with a Reasonix
User-Agent, on every request whose URL is on opencode.ai/zen/go. A
header the user configured is kept. Other hosts are untouched.

With the session in place the first tool round trip still failed:
OpenCode Go's chat route rejects a role=tool message carrying "name"
(fixed on 1.x in #10852). The chat adapter omits it there only.

The responses client groups the new id with the DashScope session-cache
flag, and the anthropic client with its custom headers, so neither
guarded struct grows a scalar.

Test: a local gateway that answers 400 without x-opencode-session,
reached through a transport that keeps the opencode.ai URL.
Before: all three adapters fail with "status 400: ... missing
x-opencode-session"; TestOpenCodeGoChatToolResultOmitsName fails with
tool message carries name "read_file". After: all pass, and two
clients present distinct ids while one client keeps its own.

Fixes #10921
2026-09-26 19:31:01 -07:00
Brian ConnellyandClaude Opus 5.5 baf8db1156 fix(control): name the MCP server and clip the prompt in ask notifications
ask also carries MCP elicitation, so the Notification hook forwarded an
MCP server's prompt verbatim to whatever channel the hook feeds. Name the
server for an elicitation ("answer needed (MCP <source>): ..."), prefer
its own message over the first field's prompt, and clip every forwarded
prompt to one line of 120 runes.

Follow-up to #10812.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-26 17:48:45 -04:00
YHH 0b9376a173 Merge pull request #10911 from esengine/fix/studio-7857-mcp-stderr-codepage
fix(plugin): decode an MCP server's code-page stderr before reporting it
2026-09-26 14:01:21 -07:00
esengine c7498ce9d3 fix(plugin): decode an MCP server's code-page stderr before reporting it
On a Chinese Windows, cmd.exe reports a missing command in the console
code page (GBK), not UTF-8. The stdio transport kept the stderr tail as
raw bytes, so an MCP startup failure such as "'read-only-mysql-mcp-server'
is not recognized as an internal or external command" reached the settings
page as U+FFFD mojibake and the user could not see why the server failed.

The stderr tail now goes through encoding.DecodeOutput, the one decoder
for process output. The buffer keeps the last 16 KiB, so only its head
can be cut: tailBuffer records when it dropped bytes and passes
Cut{Head: cut}. The end is never cut, because it is whatever the server
last wrote. Output that is not UTF-8 is decoded from the raw bytes, so a
GBK tail that begins with a CJK character keeps that character.

Tests:
- TestStdioFailureDecodesCodePageStderr starts a real child that writes
  GBK to stderr and exits.
- TestStderrTailDecodesTheServersCodePage covers GBK that starts with a
  CJK character, both uncut and cut, plus UTF-8 cut mid-character.
All of them fail on origin/studio and pass with this change.

Fixes #7857
2026-09-26 13:54:33 -07:00
YHH 219a7bbf75 Merge pull request #10947 from esengine/fix/studio-6488-utf8-partial-tail
fix(encoding): read uncut UTF-8 that ends in half a character as UTF-8
2026-09-26 13:39:03 -07:00
esengine 0f29e498ea fix(encoding): read uncut UTF-8 that ends in half a character as UTF-8
DecodeOutput trims a partial rune only at an edge the caller cut. Output
that nothing cut but that stops inside a character still has invalid raw
bytes, so it went through charset detection and fell back to GB18030,
and the whole output turned into mojibake: "编译成功…正在运行" plus half
of "中" read as "缂栬瘧鎴愬姛…". This is common in real use:
- a process killed on timeout mid-write;
- a persistent shell's partial() read;
- a block-buffered pipe.

The output is now read as UTF-8, less the final incomplete rune, when
two things hold: that rune is the only invalid part, and a multi-byte
UTF-8 character has already appeared. Both are structural evidence. When
only ASCII precedes the half rune, nothing says UTF-8, so the bytes
still go through detection. The uncut GBK probes (价, 啊\r\n, ok 洹,
啊 ok) are unchanged.

Test: TestDecodeOutputReadsUncutUTF8EndingInPartialRune fails before
this change with the mojibake above and passes after it. It also pins
ASCII plus half a rune (read as a GBK pair) and uncut, tail-cut and
head-cut GBK.

Refs #6488
2026-09-26 13:23:40 -07:00
YHH 1343c62350 Merge pull request #10933 from esengine/fix/studio-windows-flaky-effect-steer
test: stop the restart-review and steer tests racing Windows fsync latency
2026-09-26 13:19:20 -07:00
YHH 4908566b41 Merge pull request #10943 from esengine/fix/studio-10201-acp-goal-resume
feat(acp,serve): expose explicit Goal pause and resume
2026-09-26 13:15:41 -07:00
YHH eda07b74d8 Merge pull request #10940 from esengine/fix/studio-10319-reload-key
feat(studio): recover a blank window by reload key or on renderer crash
2026-09-26 13:09:33 -07:00
YHH d88859b695 Merge pull request #10941 from esengine/fix/studio-10114-ignore-runtime-state
fix(workspace): keep host-written .reasonix state out of git status
2026-09-26 13:08:53 -07:00
Adalberto Pereira 846ad2159f fix(tools): match an old_string whose blank-line run differs in length / 匹配空行数量不同的 old_string
The edit_file fuzzy matcher already tolerates trailing whitespace, tab-vs-space
indentation and copied read_file line prefixes, but not a blank-line run the
caller reproduced at a different length. PEP 8 asks for two blank lines between
top-level definitions and a model routinely writes one, so the span fails to
match a file it otherwise quotes exactly.

blankRunRanges collapses each run of consecutive blank lines into one token, so
a run's length is free while its presence is not: a blank line absent from the
file still never matches one the caller invented. Non-blank lines keep the
exact-match rule modulo trailing whitespace, and the single-match requirement is
unchanged. The relaxation is a standalone last resort rather than a fuzzyMode,
so a span that drifted in two dimensions at once still fails.
2026-09-26 17:04:17 -03:00
YHH d45fadd011 Merge pull request #10932 from esengine/fix/studio-9178-key-slot-collision
fix(serve): give a new provider a key slot nothing else holds
2026-09-26 12:53:02 -07:00
YHH e1c46175e3 Merge pull request #10939 from esengine/chore/studio-drop-workgroupassay-binary
chore: drop the committed workgroupassay binary
2026-09-26 12:46:37 -07:00
YHH 9bbeec16d9 Merge pull request #10829 from KHG420/codex/auto-refresh-markdown-preview
fix(studio): auto-refresh open Markdown previews
2026-09-26 12:44:37 -07:00
esengine 12ea68bfb3 feat(acp,serve): expose explicit Goal pause and resume
An ACP client had no way to bring back a Goal the session still held.
session/set_mode goal always drafts a new Goal, so the next prompt replaced
the retained objective and reset its counters, while the controller's
ResumeGoal/PauseGoal (reached through /goal in the CLI and Studio composer)
were never driven by a frontend directly.

ACP gains two vendor methods, advertised in agentCapabilities._meta with
schemaVersion 1:

- _reasonix.io/session/goal/resume re-enters a stopped, blocked, paused
  or cancelled Goal with its objective, counters, todos and delivery
  checkpoint. It does not start a turn; the next session/prompt continues
  it. A running, complete or absent Goal fails with -32010.
- _reasonix.io/session/goal/pause pauses a running Goal at its next
  continuation boundary, or fails with -32011.

On success both clear the turn-outcome label (cancelled, failed) the
status showed in place of the Goal state, return the Goal in the status
shape, publish a status_update with event "goal", and emit
current_mode_update when the mode moves. docs/ACP.md documents that a
"failed" status labels the last turn while the Goal keeps running.

serve drives the same capabilities through POST /goal/resume and
POST /goal/pause, answering {changed, status, goal}, so frontend-parity
holds without a scope exception.

The rule that a running Goal cannot be resumed moves into
goalMachine.resume under its lock. Before, resume accepted a running Goal
and bumped its continuation epoch, dropping the continuation in flight;
this also affected /goal resume in the CLI and Studio.

initialize/authenticate move unchanged to handshake.go so service.go stays
inside its size budget.

Tests:
- acp: TestServeGoalResumeKeepsTheRetainedObjective and
  TestServeGoalPauseStopsARunningGoal fail with only the declarations
  present (capability not advertised; -32601 method not found) and pass
  after. TestServeGoalFailedLabelKeepsTheGoalRunning fails when pause does
  not clear the label (pause reports "failed") and passes after.
- control: TestControllerResumeGoalRefusesARunningGoal and the new
  TestGoalAdvanceResultCannotCrossGoalLifecycle subtest fail on the base
  ("resume accepted a goal that is already running") and pass after.
- serve: TestServeGoalPauseAndResumeKeepTheObjective.
2026-09-26 12:43:30 -07:00
YHH ef5c5296d3 Merge pull request #10889 from esengine/fix/studio-6488-gb18030-lossless
fix(encoding): a file is GB18030 only if a write can restore its bytes
2026-09-26 12:39:43 -07:00
YHH 75562bc60b Merge pull request #10937 from esengine/fix/studio-9658-request-url-repeats-base
fix(provider): ignore an endpoint override that only repeats base_url
2026-09-26 12:38:43 -07:00
esengine b864219ba7 feat(studio): recover a blank window by reload key or on renderer crash
A Studio window can go blank in three ways, and none had a way back
short of quitting, although the kernel keeps running the turn and a
loaded page reattaches to it:
- A render error outside the per-block boundaries unmounts the React
  tree while the renderer lives on. The shell installs no menu with a
  reload role, so nothing reloads the page.
- The renderer dies while the window is visible. main.js handled
  render-process-gone only before the window first showed, so the
  window stayed dead.
- The renderer dies while the window is hidden in the tray. showWindow
  then revealed the dead page.

installReload binds Cmd+R on macOS, Ctrl+R elsewhere (by key or by the
KeyR code, so non-Latin layouts work) and a bare F5 via
before-input-event. A dead renderer's input never reaches that event, so
it also reloads on render-process-gone while the window is visible,
except for a clean exit. It returns revive(), which showWindow calls
before showing: it reloads when webContents.isCrashed(). Crash reloads
share a guard of at most 3 within 60 s; past that the window is left
alone instead of looping.

Test: desktop/electron/test/unit.mjs, three tests (reload key; visible
crash plus loop guard; hidden crash revived before show). They fail on
the base (Cannot find module '../src/reload.js'), and the hidden-crash
test also fails against the previous revision (no revive); all pass
after, suite 46/46. On Electron 44.1.0: a visible
forcefullyCrashRenderer was followed by did-finish-load; crashing on
every load gave three reloads and then stopped; crashing a hidden
window and then revive() plus show() reloaded it (isCrashed=true, then
did-finish-load).

Refs #10319
2026-09-26 12:32:14 -07:00
esengine 74558c98d7 fix(workspace): keep host-written .reasonix state out of git status
Running Reasonix in a git repository left pasted attachments under
.reasonix/attachments and background-task state under .reasonix/tasks as
untracked files, so users added .reasonix to their .gitignore by hand.

Root cause: both stores created their directories in the workspace with no
marker telling git they hold host runtime state. Ignoring all of .reasonix
is not the answer: the same directory holds files a project commits on
purpose (settings.json hooks, skills, output styles, instructions).

Fix: fileutil.MarkUntracked drops a ".gitignore" containing "*" into a
directory the host owns. The marker is written to a temp file in the same
directory and published with os.Link, so a failed write or a crash never
leaves an empty marker that would read as present forever; Link also
refuses an existing target and does not follow a symlink there, so a
marker already in place is kept. The attachment writer and the task store
(task dirs and idempotency records) mark their own roots, best effort in
both: the marker is a courtesy to the user's git, and a filesystem without
hard links must still store the state. The user's .gitignore is never
touched.

Test: TestSavedAttachmentsStayOutOfGitStatus and
TestFileStoreStateStaysOutOfGitStatus run real `git status --porcelain
--untracked-files=all` in a fresh repository with the machine's git config
cut off (GIT_CONFIG_GLOBAL at a missing temp file, GIT_CONFIG_NOSYSTEM,
XDG_CONFIG_HOME at a temp dir, -c core.excludesFile=). With the base
store code they fail listing .reasonix/attachments/* and
.reasonix/tasks/{t1,.idempotency}/*; with the fix the status is empty.
TestMarkUntrackedLeavesNothingWhenTheWriteFails fails against a
create-then-write marker ("failed write left [.gitignore] behind") and
passes with the linked temp file; a symlinked marker is not followed.
2026-09-26 12:27:57 -07:00
esengine 635774f5ef feat(theme): let a pack colour bold transcript text
Bold text in the transcript could only be told apart by weight. The
.md strong rule painted it with --text, and the theme vocabulary had no
entry a pack could use for it, so the only way to colour it was editing
the shipped stylesheet.

Add an fgStrong colour token. The kernel accepts it, the frontend maps it
to --text-strong, and .md strong reads var(--text-strong, var(--text)),
so every pack that does not name it renders exactly as before. It steps
with the reader's contrast setting like fg does, and the shipped-pack
contrast test now holds it to AA on the same surfaces as the other inks.

Test: TestDecodeAcceptsAStrongTextColour fails on the base (the token is
dropped with a "not a theme token" warning) and passes after; the two new
theme.test.ts cases fail on the base (--text-strong never set) and pass
after. Real-browser screenshots of the perf build, light and dark: before,
setting --text-strong leaves bold at the body colour; after, bold takes
it, and the default rendering is unchanged.
2026-09-26 12:26:15 -07:00
YHH cd0e4733ac Merge pull request #10935 from esengine/fix/studio-9442-openrouter-balance
feat(billing): read OpenRouter's credits endpoint as a wallet
2026-09-26 12:23:41 -07:00
YHH b246172185 Merge pull request #10936 from esengine/fix/studio-9274-tui-statusline
fix(tui): honour a user-level [statusline].command in the terminal footer
2026-09-26 12:22:42 -07:00
YHH eb622d7484 Merge pull request #10931 from esengine/fix/studio-9005-gemini-enum
fix(tools): bound todo_write's level instead of an integer enum
2026-09-26 12:18:15 -07:00
esengine 1d0a421f65 chore: drop the committed workgroupassay build output
The root .gitignore already lists /workgroupassay; the 10 MB binary was
committed with 491016f8c alongside the tool's source and is rebuilt by
go run ./tools/workgroupassay.
2026-09-26 12:14:46 -07:00
esengine 3116ccfa91 test: stop the restart-review and steer tests racing Windows fsync latency
TestEffectRestartKeepsTheReviewAHighRiskGoalOwes (internal/assembly/boot)
failed on windows-latest in runs 36256346762, 36232402249, 36230551992,
36229537444 and 36258827818 (#10901): "PHASE-1 ... never settled", goal
running, 25-26 requests after 20s.

Root cause: the goal in that test can never finish by design and stops only
on its token budget. At 30000 tokens and 1000 per reply, each phase ran about
30 provider requests, 23 of them refused "done" claims, each a full host
turn: about 65 ms on macOS here and, from the CI tail, about 0.8 s on a
hosted Windows runner. 30 turns at that rate do not fit the 20 s settle window. The
assertion only reads whether phase two carried the review nudge and whether
the goal ended incomplete, and the first refused claim already settles both.
At 8000 tokens each phase makes its planned calls plus three nudged
continuations, 16 requests per arm instead of 60, with the same outcome
(blocked, review demanded, in both arms). Settling now waits for the goal to
leave running while the controller is idle. The old test read "idle for
500 ms" as settled, but an idle gap between two continuations is not the
end of the goal.

TestTrySteerRejectedBecomesFollowup (internal/session/control) waited 1 s
for the gated runner. TrySteerInboxItem dispatches the rejected steer
before it returns, so the claim is already durable (state running, intent
follow-up, controller running) when the call comes back. What was being
timed is the turn goroutine reaching the runner. Before that it goes through
a chain of fsyncs: checkpoint begin, branch meta, in-flight marker, recovery
snapshot. That chain takes 100-670 ms on macOS here and can pass a second on
a loaded Windows runner. The test now asserts the admission synchronously.

The 8-minute package timeout in 36258827818 attempt 2 came from
TestThirtySteersApplyAndAckExactlyOnce. Its initial turn missed the same 1 s
start window, t.Fatal ran, and the deferred autosaveWG.Wait blocked forever:
the fake provider's first round was parked on p.release, which only the
test body closed. Every test that parks a fake on a release channel now
closes it idempotently (sync.OnceFunc) before waiting on autosaveWG, in
steeringTurn's cleanup and in TestSteerUnreadAtTurnEndIsDeliveredAsAFollowup.
TestThirtySteers now uses steeringTurn, whose existing 10 s start bound
remains a hang guard only. That wait also returns on TurnDone, so a turn that ends without reaching the provider fails at once
with the event. A deliberate t.Fatal right after steeringTurn used to hang
until -timeout. It now fails in 0.15 s.

TestBackgroundJobWakesSessionThroughManager failed the same way on this
PR's first Windows run (36263202340): a 1 s wait covered the job goroutine
queueing the continuation and the woken turn's fsync chain. The test now
takes the observer's own result for the publish step. It then waits for the
runner input, and the only other exit is a TurnDone. Every step ends in an
event the product always emits, so no clock races the disk. The note check
stays after the runner input: by then the turn's composition has drained
the note, or the ack has dropped it.

Local: -race -count=20 of the five control tests and of the boot test pass
(the boot test drops from about 18 s to about 4 s locally).
2026-09-26 12:13:49 -07:00
YHH 1e5a50de14 Merge pull request #10923 from esengine/fix/studio-8272-responses-output-calls
fix(responses): close tool calls the completed response lists
2026-09-26 12:09:11 -07:00
esengine 6c00620aa0 fix(provider): ignore an endpoint override that only repeats base_url
Root cause: the 1.x settings editor mirrored base_url into request_url
(and older ones into chat_url), and 1.x and Studio read the same
config.toml. Studio used either override verbatim, so a relay such as
https://router.shengsuanyun.com/api/v1 received its chat POST on the API
root and answered "404 page not found" while /models still listed the
catalog. 1.x fixed this in #9464 and its follow-up 73b6d9b4b; Studio never got
the comparison.

Fix: port provider.EndpointOverrideRepeatsBase into
internal/contract/provider (scheme and host case-insensitive, path exact
apart from a trailing slash, an override with a query or fragment is
deliberate and kept). The openai provider applies it to request_url and
chat_url, anthropic to its root with and without /v1, responses to its
base. Chat URL resolution moves into chaturl.go to keep openai.go inside
its budget.

Test: TestStreamIgnoresEndpointOverridesThatRepeatBaseURL (openai,
request_url and chat_url) and TestStreamIgnoresRequestURLThatRepeatsBaseURL
(anthropic, responses) stream against an httptest server whose base is
/api/v1. On the base they POST to "/api/v1/", "/api/v1" or "/api"; with
the fix they POST to /api/v1/chat/completions, /api/v1/messages and
/api/v1/responses. TestEndpointOverrideRepeatsBase covers the comparison.

Fixes #9658
2026-09-26 12:08:31 -07:00
YHH 15aacdeb18 Merge pull request #10927 from esengine/fix/studio-8486-host-nudges
fix(agent): mark loop-composed instructions as host-authored
2026-09-26 12:06:49 -07:00
esengine 1158e53c03 fix(tui): honour a user-level [statusline].command in the terminal footer
The config template documents [statusline].command as a command whose
first stdout line replaces the built-in status data row, but the
terminal UI never read the key: nothing passed it from config into
tui.Options, so a configured command was silently ignored.

runTUI now hands the TUI a runner built from the loaded config. When
the footer refreshes its between-turn meters (startup, turn end,
session switch) the TUI fetches the kernel status, feeds
{model, contextUsed, contextWindow, cwd} as JSON on stdin, and shows
the command's first line in place of the telemetry row. Empty output
or a failed run keeps the built-in row. The spawn goes through the
hook spawner with a 2s timeout and without searching the working
directory for a bare executable name.

Statusline is user-level configuration and is never taken from a
project file: LoadForRoot restores the user/global value after the
project merge, the project render scope omits [statusline], and the
project delta never writes it. The template and reasonix.example.toml
say so.

Test:
- tui: TestStatuslineCommandReplacesTheTelemetryRowAfterATurn and
  TestEmptyStatuslineOutputKeepsTheBuiltInRow drive the model through a
  turn_done update against an HTTP kernel stub.
- cli: TestStatuslineRunnerFeedsStdinAndKeepsTheFirstLine runs a real
  shell command; TestStatuslineRunnerIgnoresAProjectCommand loads a
  project-only command and expects no runner.
- config: TestProjectStatuslineIsIgnored, TestUserStatuslineWinsOverProject,
  TestStatuslineNeverRendersIntoAProjectFile.
Before the scope change the config and project-command tests fail on
their assertions (project value resolved and rendered); after it all
pass.
2026-09-26 12:05:10 -07:00
esengine 8a00d0a562 fix(serve): give a new provider a key slot nothing else holds
Problem: adding a source could write over, or read, a credential that belongs
to another endpoint (#9178):
- a name that folds to another provider's slot ("deepseek" beside the
  official DEEPSEEK_API_KEY entries) wrote its key over that slot;
- adding under a name that already exists upserted over the entry and
  inherited its slot, so a keyless source at a relay resolved and sent the
  old key there;
- a removed provider's credential stays stored, and a later source whose
  name derives the same slot read it.

Root cause: POST /providers derived the slot from the name alone and
treated an add as an upsert.

Fix:
- config.FreeAPIKeyEnvFor keeps the name's own slot only while no config
  entry holds it and nothing is stored there; otherwise it allocates a
  private REASONIX_CONNECTION_<id>_KEY slot, the scheme 1.x uses.
- POST /providers refuses a name that already exists (409
  provider.name_taken) unless the body says replace. Onboarding, which fills
  in the entry a fresh install already names, sends replace; a replace at
  that entry's own host keeps its slot, anywhere else it gets a free one.
- An unreadable config refuses the save (500 provider.config_unreadable)
  instead of falling back to the name-derived slot.
- The same-host keyless "second door" still shares a slot on purpose.

Test: on origin/studio, TestANewSourceNeverWritesAnotherProvidersKeySlot,
TestAKeylessSourceElsewhereDoesNotBorrowAFoldedSlot,
TestAddingUnderAnExistingNameIsRefused (200, want 409) and
TestAKeylessSourceDoesNotInheritARemovedProvidersKey (relay-b resolved
"sk-a") fail; all pass after, with TestReplacingAnEntryKeepsItsSlotOnlyAtItsHost
and the existing key-reuse tests. Onboarding.test asserts replace is sent.
2026-09-26 12:04:13 -07:00
xiexian 106c79d139 Merge studio into Markdown preview fix 2026-09-27 03:03:49 +08:00
YHH dbb7adefeb Merge pull request #10922 from esengine/fix/studio-8355-run-flag-surface
fix(cli): approval flags mean the same thing on either side of run
2026-09-26 12:03:15 -07:00
YHH 1ed55fd4df Merge pull request #10901 from esengine/fix/studio-6990-stream-idle-timeout
fix(model): wait five minutes for a silent model before calling the stream dropped
2026-09-26 12:00:23 -07:00
YHH be4336025b Merge pull request #10928 from esengine/fix/studio-8385-subagent-bare-model
fix(subagent): a bare model name stays on the parent's provider
2026-09-26 11:54:36 -07:00
xiexian 68860de492 Merge remote-tracking branch 'origin/studio' into codex/studio-slash-loading 2026-09-27 02:54:18 +08:00
xiexian 9f4d12ea45 fix(studio): retain completion menus during lookups 2026-09-27 02:54:11 +08:00
esengine f28d4f3c94 fix(encoding): a file is GB18030 only if a write can restore its bytes
edit_file on a UTF-8 file carrying CJK text rewrote bytes it never touched:
continuation bytes of characters far from the edit came back as 84 31 A4 37.

Detect took a nil error from the GB18030 decoder as proof the file was
GB18030. The decoder never fails: it maps every invalid sequence to U+FFFD.
So any file that was not valid UTF-8 -- one stray byte is enough -- was read
as GB18030, and the read-modify-write tools (edit_file, multi_edit,
delete_range, write_file over an existing file), checkpoint restore and the
config migrations then re-encoded every unreadable sequence as the GB18030
form of U+FFFD.

Detect now names a charset only when decoding and re-encoding restores the
input byte for byte. GB18030 is tried first, then GBK, which restores what
GB18030 cannot such as CP936's single-byte euro 0x80, so such a file stays
editable in its own encoding; anything else is LossyUTF8, whose bytes pass
through untouched. The new Kind is appended because checkpoints persist it
by value.

GBK cannot hold every character, so Encode now returns an error instead of
falling back to UTF-8, which rewrote a whole CP936 file as UTF-8 when an edit
added a character such as an emoji. A legacy charset that cannot represent a
character answers an *UnencodableError naming it, its byte offset and the
charset, with ErrUnencodable as its identity; the file tools, checkpoint
restore and the config migration refuse the write and leave the file as it
was. Fixtures encode through encodingtest.MustEncode.

Readers that hold only part of a stream say so rather than inherit the
whole-file answer:
- DetectFragment runs the round trip on the prefix the decoder consumes when
  not at EOF, so a character the cut split is dropped structurally. grep's
  8 KiB peek and read_file's 256 KiB sample use it.
- DecodeOutput is the one entry point for bytes that are only displayed.
  The producer says which ends its bound cut (Cut{Head, Tail}); UTF-8 is read
  without a character split at a cut end, and an uncut end keeps its bytes,
  since a GBK pair there is output rather than half a rune. What no charset
  restores is still read as GB18030. Hook output (tail cut when truncated),
  the shell tail (head cut once it drops bytes) and the shell's combined
  output (uncut) call only it.
- DetectAndDecode decodes a legacy charset once; the edit tools and
  DecodeToUTF8 use it instead of detecting and decoding separately.

Tests that fail before this change (on the base or an earlier revision of it)
and pass here:
- TestE2EEditKeepsBytesOfUTF8FileWithStrayByte: e4 b8 ad became
  e4 b8 84 31 a4 37 outside the edit (fails on origin/studio).
- TestDetectUTF8WithStrayByteIsNotGB18030 (fails on origin/studio).
- TestWritesRefuseCharacterCP936CannotHold (edit_file, multi_edit,
  write_file): nil error and the whole CP936 file rewritten as UTF-8.
- TestDecodeOutputTrimsCutUTF8Edges: UTF-8 cut at the front read as mojibake.
- TestDecodeOutputKeepsUncutCodePageEdges, TestDecodeHookOutputKeepsUncutCodePageEdges:
  GBK pairs at an uncut edge (bc db, b0 a1 0d 0a, 6f 6b 20 e4 a1,
  b0 a1 20 6f 6b) were dropped as partial UTF-8 runes.
- TestEditKeepsCP936FileWithEuroByte: an edit adding Chinese wrote UTF-8
  into a CP936 file.
- TestGrepGB18030PeekWithoutNewline, TestGrepGB18030PeekCutMidCharacter:
  no matches past a peek cut mid-character.
- TestReadFileGB18030SampleWithoutNewline: garbled text past the sample.
- TestDecodeHookOutputReadsTruncatedCodePageText,
  TestDecodeHookOutputReadsCP936Euro, TestCodePageOutputCutMidCharacterIsReadable:
  mojibake for cut or euro-carrying cmd.exe output.
- TestEncodeRefusesCharacterTheCharsetCannotHold, TestDetectGBKWithEuroByte,
  TestDetectFragmentDropsTheSplitCharacter, TestDecodeOutputReadsCutCodePageText:
  the API's contract.
2026-09-26 11:51:40 -07:00
esengine 0eaf4bd72b feat(billing): read OpenRouter's credits endpoint as a wallet
A balance_url pointing at https://openrouter.ai/api/v1/credits left the
balance readout empty. The wallet decoder is chosen by the endpoint's
host, and openrouter.ai had no entry, so the body was read as DeepSeek's
shape; {"data":{"total_credits","total_usage"}} carries no balance_infos
and was rejected as unreadable.

Add an openrouter.ai decoder: remaining = total_credits - total_usage,
in USD, available while positive. A body missing either field stays
ErrUnreadable rather than rendering as a zero balance.

Test: TestOpenRouterCreditsDecode and TestOpenRouterSpentWalletIsUnavailable
fail on the base ("wallet response not in this endpoint's shape") and pass
with the decoder; go test ./internal/model/billing/ is green.
2026-09-26 11:50:59 -07:00
xiexian 7ef5ddb557 fix(studio): retain Markdown preview on transient poll errors 2026-09-27 02:46:58 +08:00
esengine adb5f3f9c0 fix(cli): approval flags mean the same thing on either side of run
`reasonix run` rejected --yolo and --dangerously-skip-permissions with
"unknown flag", `reasonix -y run "task"` failed with "unknown command",
and `reasonix --permission-mode auto run "task"` opened the terminal UI
with "run task" as its prompt instead of running headless. Print mode
disagreed with itself: `--yolo run -p hi` ran the task "run hi" and
`-y -p hi` was an unknown command.

Root cause: run's flag set never registered the yolo spellings the
terminal UI takes, and normalizeCommand only looked at argv[0], so a
leading session flag sent everything to the terminal UI, -y was read as
the command name, and the leading -p scan ran past the verb.

Fix:
- run registers --yolo/--dangerously-skip-permissions as an alias for
  --permission-mode bypassPermissions; each conflict (-y with --yolo,
  either with --permission-mode) names only the flags involved.
- run's flags move into runFlags/newRunFlags so they can be consulted
  before dispatch.
- The verb is located with the union of the terminal UI's flags and
  run's on the resume-normalized argv, as runTUI reads it, so a value of
  either is never taken for it. Leading flags move after `run` only when
  the session flags (the terminal UI's plus -y and -p) and run's both
  parse them to the same flags with the same values. Otherwise the line stays the terminal
  UI's: `--resume run hi`, `-r run hi` and `--inline run hi` keep their
  meaning, and a run-only leading flag (--metrics, --output-format) is
  reported by the terminal UI.
- -y and -p may lead like the other session flags, and the leading -p
  scan stops at the verb even when a run-only flag precedes it.
- The first word after the leading flags is the verb, as it is with no
  flags; CLI.md documents that, and interspersed run flags.

Test:
- TestRunApprovalFlagsReachTheHeadlessGate runs Run() against a fake
  provider whose first reply calls write_file. The file appears only
  where the resolved mode lets an unattended write through (ask and
  dontAsk stay unwritten). On origin/studio the five new spellings exit
  2 with no file. A mutation that drops the leading flags while routing
  fails it for the three leading-flag spellings.
- TestNormalizeCommandCarriesLeadingFlagsIntoRun fails on origin/studio
  (10 cases) and on the previous revision of this change (9 cases,
  including the three terminal-UI regressions). The revision before this
  one fails 2 cases: `--model m --metrics x run -p hi` and the same with
  --output-format ran the task "run hi". The revision before this one
  fails 2 more: `--resume --permission-mode
  --permission-mode=bypassPermissions run hi` and `--resume --resume=abc
  run hi` were forwarded to run although the two sets read different
  values. It passes now.
- TestRunTakesTheYoloSpellings covers the alias and the per-flag
  conflict messages.

Gates: gofmt, go vet (also GOOS=windows), make check clean,
golangci-lint 0 issues (also GOOS=windows), go test
./internal/frontend/cli/.

Refs #8355
2026-09-26 11:40:10 -07:00
YHH 20fcba1fc4 Merge pull request #10920 from esengine/fix/studio-8271-acp-image-blocks
fix(acp): save prompt images and blob resources as attachments
2026-09-26 11:40:04 -07:00
YHH 97622c7d59 Merge pull request #10915 from esengine/fix/studio-8079-provider-edit-rebuild
fix(serve): a source edit reaches the conversation running on it
2026-09-26 11:37:20 -07:00
esengine 71aceb9e84 fix(tools): bound todo_write's level instead of an integer enum
Root cause: the todo_write schema declared "level" as
{"type":"integer","enum":[0,1]}. Gemini's function-declaration validator
accepts enum only on string properties with string members, so any Gemini
model behind an OpenAI-compatible relay rejected the whole request with
400 INVALID_ARGUMENT (TYPE_STRING on enum[0]/enum[1]), whatever the prompt.

Fix: express the same range as "minimum":0,"maximum":1. Execute already
rejects any level outside 0..1, so accepted input is unchanged. The level
description loses its "Nesting level:" lead-in to stay inside the schema
token budget. Boot golden fixtures are regenerated for the new bytes.

Test: TestBuiltinSchemaEnumsAreStrings walks every registered built-in
schema and fails on an enum that is not on a string property or has a
non-string member. On origin/studio it fails with three findings on
todo_write's level; with the fix it passes, as do
go test ./internal/tools/builtin/ ./internal/assembly/boot/.

Fixes #9005
2026-09-26 11:35:11 -07:00
esengine ef28ba7d06 fix(responses): close tool calls the completed response lists
A Responses turn could end with a tool call the model issued and never
run it. The stream reader dispatched a function call only when its
function_call_arguments.done or output_item.done event arrived; a relay
that forwards the added and delta events but not the done events (or
sends deltas without the item id) left the call open, the terminal
response.completed ended the attempt, and the turn committed as a clean
answer with no calls. The run then finished ok after one round.

The terminal response's output array is the response's own list of what
it issued. On response.completed and response.incomplete, every
function_call item there that the stream did not already close (matched
by call id) is now completed from that item; one whose status is set and
is not "completed" is left alone, so a call truncated at the output
limit is still not dispatched, and one listed without a call id is not
dispatched either.

The output array decodes one item at a time. Decoded as a typed slice, a
single item with a non-string field (arguments sent as an object by a
non-conforming relay) would fail the whole terminal event, which is then
skipped and the turn ends with unexpected EOF where it used to finish.
A call with no name, such as an arguments.done event that named no
item, is no longer dispatched as an empty call; it stays open for a
later event to name.

Test: wire_gap_test.go drives the real HTTP stream reader. On
origin/studio the "done events never came" and "deltas without item id"
cases fail with calls = nil; both pass after the change, and the
no-duplicate and truncated-call guards pass on both. The
unreadable-item, empty-call-id and unnamed-arguments.done cases fail
before the per-item decode and the skips, and pass after.
go test ./internal/model/... ./internal/runtime/agent/ passes.
2026-09-26 11:35:00 -07:00
YHH 8e34b62ad3 Merge pull request #10919 from esengine/fix/studio-8326-shell-neutral-tests
test(shell): stop requiring a POSIX shell where the subject is not POSIX
2026-09-26 11:29:18 -07:00
YHH 9bfe900453 Merge pull request #10913 from esengine/fix/studio-8143-reasoning-guard
fix(agent): stop aborting turns at the client reasoning byte guard
2026-09-26 11:15:33 -07:00
YHH 4624fbd025 Merge pull request #10905 from esengine/fix/studio-7088-subagent-start-hook
feat(hook): bracket a foreground task sub-agent with SubagentStart/Stop
2026-09-26 11:15:08 -07:00
YHH 82c295c695 Merge pull request #10910 from esengine/fix/studio-7739-fuzzy-at-search
feat(fileref): reach an @ reference by its letters in order
2026-09-26 11:13:30 -07:00
YHH 5d332eb804 Merge pull request #10908 from esengine/fix/studio-7853-fakeip-dual-stack
fix(web_fetch): dial an allowed address when a host also resolves to a blocked one
2026-09-26 11:12:45 -07:00
esengine 607d9c0f64 feat(tui): open an answer's thinking with a click or Ctrl+O in full screen
The full-screen transcript showed an answer's thinking only as a
"thought for Ns" marker, with no way to read it; a long shell output
could already be opened with a click, the thinking could not.

Full screen, the marker now reads ▸ while shut and ▾ while open, with a
(Ctrl+O) hint. A left click on it, or Ctrl+O for the newest one (for a
terminal whose mouse is handed back to it), opens and shuts the thinking
underneath. Each opened row carries its own faint style, since the
transcript is split into rows after rendering. A streamed answer's first
chunk keeps its row, and when the answer settles that row takes the
final thinking and elapsed time, so thinking that kept arriving after
the text started is not lost. Inline mode, where printed rows cannot be
redrawn, keeps the fixed ▎ marker.

Tests (internal/frontend/tui/screen_test.go):
- TestThinkingOpensAndShutsOnAClick: streamed and settled answers open
  and shut on a click of the marker row (FAIL on origin/studio).
- TestOpenedThinkingIsFaintOnEveryRow: every opened row starts with the
  faint SGR under an ANSI256 profile (FAIL with one style around the block).
- TestStreamedThinkingOpensAsItSettled: thinking arriving after the text
  and the final "thought for 4s" show when opened (FAIL with the row
  copied at the first chunk).
- TestCtrlOOpensTheLatestThinking: Ctrl+O opens and shuts it with the
  mouse handed to the terminal (FAIL without the key).

Fixes #7934
2026-09-26 11:12:11 -07:00
esengine cc03ad8949 fix(serve): a source edit reaches the conversation running on it
Root cause: POST /providers/edit saved the entry and answered 204 without
rebuilding the runtime. The context window, token limits, headers and
reasoning protocol are bound into the agent at assembly, so the open
conversation kept its old window: the gauge and the fold point went on
counting against the number the form had just replaced, until something
unrelated (a model switch) rebuilt it. /context/window already rebuilds
in place; the edit form did not.

Fix: after the save, act only when the conversation runs on the edited
source and a field the agent binds at assembly actually changed (base URL,
models, vision, per-model overrides, window, max output, headers, extra
body, protocol, efforts). A key-only edit needs no rebuild because the key
is read per request, so it answers 204. Otherwise:
- running model still listed: rebuild in place. With work in progress (a
  running turn, a pending question or background jobs) the save still
  lands and the answer is provider.saved_while_running; any other rebuild
  failure is runtime.rebuild_failed.
- the edit unticks the running model: a plain save, 204.
- the running model was already unlisted before the edit: nothing on the
  entry can carry the change to that conversation, so the answer is
  provider.saved_model_unlisted ("switch models to apply it").
On any of these "saved but not applied" answers the edit form refreshes
the providers list and stays open to show why.

Test:
- TestEditProviderReachesTheConversationRunningOnIt failed on
  origin/studio ("window = 0, want the saved 1000000"); passes.
- TestEditProviderMidTurnSaysTheConversationKeepsItsSettings failed on
  origin/studio ("mid-turn = 204, want 409"); passes.
- TestEditProviderDroppingTheRunningModelIsStillSaved and
  TestEditProviderKeyOnlyMidTurnNeedsNoRebuild failed on an earlier
  revision of this change (409 instead of 204); pass.
- TestEditProviderAfterDroppingTheRunningModelSaysItIsUnlisted failed on
  the previous head ("editing the window after the drop = 204, want
  409"); passes.
- EditConn test: a saved_while_running answer calls onSaved, not onDone.
The serve package, go vet (also GOOS=windows), golangci-lint, make check,
tsc, vitest, census:verify and perf/i18n.mjs pass.
2026-09-26 11:10:49 -07:00
esengine f8fffdffa1 fix(subagent): a bare model name stays on the parent's provider
A task, parallel_tasks or fleet call may name a child model without a
provider, e.g. "deepseek-v4-flash". The sub-agent resolver passed that
straight to Config.ResolveModel, which answers a bare name with the first
provider that lists it. With the official DeepSeek entry listed before a
relay that serves the same model, a child of a session running on the relay
was silently sent to the official API instead.

A bare name the parent's provider serves is now qualified with that
provider before resolution, in both the runtime resolver and the identity
the usage record reports. A provider name, a provider/model reference, or
a model only another provider lists resolve as before.

Test: TestSubagentBareModelStaysOnParentProvider fails on origin/studio
(resolved refs = ["first/shared-flash" ...], want "relay/shared-flash")
and passes after the fix; go test ./internal/assembly/boot/
./internal/runtime/delegation/ pass, make check clean.
2026-09-26 11:10:13 -07:00
esengine 686b97dc4b fix(agent): mark loop-composed instructions as host-authored
A reopened Studio conversation showed the host's own instructions to the
model as if the user had typed them: the tool-round / task-budget
finalization request, the retry after a reasoning-only answer, the
executor-handoff retry, and the output-truncation fact.

Root cause: /history and the TUI restore decide "who said this" from
provider.Message.HostAuthored, which is written by the producer. Four
injection sites in the run loop appended role=user messages without it
(only the workflow-phase repair set it), so the transcript rebuilt them
as user bubbles and session previews could count them as user turns.

Fix: set HostAuthored at each site. Provider requests are unchanged:
the projection clears the field before sending.

Test: TestLoopComposedUserMessagesAreHostAuthored drives a real Run into
the task-budget landing, an empty-final retry and a truncated tool call.
Before the fix all three cases fail ("a line the loop composed is
recorded as the user's"); after it they pass. agent, serve, control,
sessionstore and boot packages pass; make check and golangci-lint clean.

Fixes #8486
2026-09-26 11:09:09 -07:00
esengine 04b99a751c fix(studio): name what a copy button copies in its tooltip and text
A code block's copy button already swaps to a check mark after a copy,
but hovering it said "Copy response", and the config-trouble banner's
button for the config file path read "Copy response" as its visible text.

Root cause: CopyButton's idle label was the fixed string for a chat
reply; the caller's `label` only reached aria-label, so title and
visible text ignored it. ConfigTrouble passed no label at all.

Fix: the idle label is the caller's `label` when given, and the config
banner labels its button "Copy path" (en locale entry added).

Test: src/ui/CopyButton.test.tsx fails on origin/studio (code block
title "复制回复" vs "复制这段代码"; path button text "复制回复" vs
"复制路径") and passes after. Full vitest (928), tsc, census:verify
and make check pass.
2026-09-26 11:07:15 -07:00
esengine d7259a1793 fix(studio): let a typed "other" answer be given from the keyboard
Choosing the free-text option on an ask card left the caret on the
option button, so the answer could not be typed until the box was
clicked, and Enter in the box did nothing: every typed answer needed
the mouse twice.

Choosing the option now focuses its box, every time it is chosen, and
Enter there does what the primary button would. When Enter moves to the
next question, focus follows into that pane (its open box, else its
first option), because the pane it leaves becomes display:none. The key
goes through useIme, so the Enter that confirms an IME candidate is not
read as an answer. The box carries an aria-label.

Test (PromptCards.interaction):
- the caret lands in the box after choosing the option; a composing
  Enter does not answer, a later Enter does (clock stubbed, not slept);
- Other -> a normal option -> Other again puts the caret back in the
  box (FAIL before: focus stayed on the button);
- Enter on question 1 moves focus to question 2's first option (FAIL
  before: focus stayed in the hidden pane's input).
Before/after screenshots were taken on the perf build, light and dark.

Fixes #7904
2026-09-26 11:06:10 -07:00
YHH 0ca2587b3e Merge pull request #10900 from esengine/fix/studio-7352-cloud-mount-config-path
fix(config): load a project config under a cloud-drive mount
2026-09-26 11:01:26 -07:00
YHH a61f73de6c Merge pull request #10903 from esengine/fix/studio-7035-mcp-workspace-var
feat(config): expand the workspace root in MCP server entries
2026-09-26 10:58:33 -07:00
esengine ba12e4f2e6 fix(acp): save prompt images and blob resources as attachments
An ACP client that sent a picture got an answer from a model that never
saw it. session/prompt flattened its blocks to text and kept only text
blocks and resources with inline text: an embedded resource carrying a
base64 blob, which a client sends because embeddedContext is advertised,
was dropped without a word, and so was any image block. An image-only
prompt was refused as empty.

Image blocks and blob resources are now decoded and stored with the
control package's attachment savers under .reasonix/attachments/ in the
session's workspace, and the prompt carries the "@path" reference that
serve and the CLI already hand a turn. Undecodable data or bytes that do
not sniff as an image fail the prompt with invalid params instead of
vanishing. Steer uses the same path. initialize now advertises
promptCapabilities.image = true.

Test: prompt_attachments_test.go drives session/prompt through the real
Serve loop and a real controller. On origin/studio the image block and
blob resource cases fail with the turn input "what is in this picture?"
(no reference), the image-only case fails with "empty prompt", and the
undecodable case returns no error; all pass after the change, and
go test ./internal/frontend/acp/ passes.
2026-09-26 10:53:49 -07:00
esengine 8eb3764aec test(shell): stop requiring a POSIX shell where the subject is not POSIX
On a Windows host without Git Bash the shell tool resolves to PowerShell,
a supported path. Five tests still assumed bash there:

- TestBackgroundBashWaitAndOutput, TestBashTerminalFallsBackWhenUnhandled,
  TestBashTerminalSkippedWhenEnvFilteringEnabled and TestRunForegroundSuccess
  ran `printf`, which PowerShell does not have. They test job, terminal and
  runner plumbing, not POSIX syntax, so they now run `echo`, which both
  interpreters accept with the same output.
- TestDefaultSpawnerUsesGitBashForExplicitShOnWindows needs a real Git Bash;
  it now skips when no bash is installed. Discovery itself stays covered by
  the injected-host tests in internal/safety/sandbox.
- TestGoldenBaselineNoExtensions records the bash tool's bash description.
  A host that resolves PowerShell renders a different, intended description,
  so the golden now skips there instead of reporting drift, and is never
  regenerated from such a host.

Test: the touched tests pass on macOS (bash host) and the four packages pass
in full; go vet passes with GOOS=windows. The PowerShell-only failure could
not be reproduced here (no Windows host without Git Bash is available); the
reporter's reproduction in #8326 shows `printf` not recognized and the
golden diff on the bash tool description.
2026-09-26 10:52:03 -07:00
YHH ccc2714a96 Merge pull request #10881 from esengine/fix/studio-5914-symlink-explorer
fix(serve): list a workspace link as what it resolves to
2026-09-26 10:44:57 -07:00
esengine 3ad87f17ae fix(agent): stop aborting turns at the client reasoning byte guard
Problem: a long high-effort think on Studio ended with "response stopped:
hit the client reasoning safety limit" and no answer, even though the
provider was still inside the output budget it had been sent.

Root cause: the agent cancelled the provider stream once 128 KiB of hidden
reasoning had been buffered (about 32K estimated tokens) and returned
errReasoningByteLimitExceeded. The request already carries a provider
output budget (auto ~64K on DeepSeek high), so the byte guard fired inside
the budget and turned a legitimate think into a failed turn. The 1.x line
dropped the abort in #8779; Studio never received it.

Fix: the byte limit now bounds only what the host retains. Reasoning keeps
streaming live, the stored copy is the rune-safe prefix of the stream's
first ReasoningByteLimit bytes, and the default rises to 8 MiB so no
provider-bound reasoning is cut under current output ceilings. Interrupted
usage estimates count every streamed reasoning byte. The sentinel and the
client_reasoning_limit finish reason had no producer left and are removed.

Test: TestDefaultReasoningLimitLetsLongThinkingFinish and
TestReasoningByteLimitBoundsRetainedReasoningOnly fail on origin/studio
with "reasoning output exceeded client byte limit" and pass after the fix.
go test ./internal/runtime/agent ./internal/assembly/boot
./internal/tools/builtin, the race run of the reasoning tests, go vet,
golangci-lint and make check are clean.
2026-09-26 10:37:40 -07:00
esengine e12b31b1fc feat(hook): bracket a foreground task sub-agent with SubagentStart/Stop
Hooks could see a sub-agent finish (SubagentStop) but not begin, and
SubagentStop fired only when the child answered. A failed, cancelled or
self-refused task ended silently, so an external observer driven by
hooks (activity indicators, notifiers) had no event marking the start
of delegated work and no reliable end.

SubagentStart is a new non-blocking event fired just before a
foreground `task` call executes, with the call's arguments in toolArgs.
SubagentStop now fires from a defer on every exit after that start
(answer, failure, cancel, refusal) with `error` set when there was no
answer, under a context detached from the turn's cancellation so a
cancelled turn does not kill the hook. Both carry the call id as callId
(tool_use_id in the Claude-format payload) so consumers can pair them.
Only a foreground `task` fires them; read_only_task, parallel_tasks,
fleet, skill sub-agents and background tasks fire neither. hook.go,
the reasonix-guide skill and GUIDE (en/zh) state that scope and that
exit 2 cannot block.

Test: TestSubagentStartPairsWithSubagentStop runs answer, failure,
cancel and refusal cases plus a background call. Before (SubagentStop
on success only): fails, cancelled and refuses each fail with
"SubagentStart must be followed by exactly one SubagentStop, saw 0".
After: all pass, with matching call ids, err set on the three non-answer
cases, and a live context in SubagentStop. TestRunnerSubagentStartPayload
checks callId, toolArgs and error reach the hook's stdin.
2026-09-26 10:31:41 -07:00
YHH cc79c726a6 Merge pull request #10906 from esengine/fix/studio-7444-windows-remote-posix-shell
fix(remote): identify a Windows host whatever its OpenSSH login shell
2026-09-26 10:28:02 -07:00
esengine 0a0e1989a7 feat(config): expand the workspace root in MCP server entries
A global [[plugins]] entry had no way to name the workspace it runs for:
${VAR} expansion read only the process environment and the project
.env. Servers that key on the project path in a header or argument
(IntelliJ's IJ_MCP_SERVER_PROJECT_PATH) therefore needed one entry per
project. Installed plugin packages already got ${CLAUDE_PROJECT_DIR};
user and .mcp.json entries did not.

ExpandedPluginForRoot resolves ${REASONIX_WORKSPACE_ROOT} and
${CLAUDE_PROJECT_DIR} to the workspace, and both places that turn an
entry into a spec call it with the root they already hold:
pluginspec.FromEntry (session boot, capdiag, mcp oauth) and the
controller's mcpIdentitySpec (live add, install, reconnect). The host
owns these values, so they outrank a process or .env variable of the
same name; a hook's child process inherits its parent session's
CLAUDE_PROJECT_DIR. Config path fields (sandbox roots, skill paths,
proxy) keep plain environment expansion. WorkspaceRootValue is the one
canonical form (absolute, cleaned, symlinks as opened), also used for
the plugin-package substitution.

Tests (the first three fail before the fix and pass after):
- TestAddMCPServerExpandsWorkspaceRootInLiveHeaders: live AddMCPServer
  against an httptest MCP server. Before: live headers = ("",
  "/inherited/from/a/parent/hook").
- TestLoadForRootExpandsWorkspaceRootInPluginEntries: two workspaces,
  one global http entry and one .mcp.json entry, with a conflicting
  CLAUDE_PROJECT_DIR in process env and .env. Before: header = "".
- TestPluginSpecsForRootExpandWorkspaceRoot covers the boot mapping, and
  TestWorkspaceRootVarsStayOutOfConfigPathExpansion keeps sandbox paths
  out of scope.
2026-09-26 10:25:06 -07:00
esengine ef6fe03c97 feat(fileref): reach an @ reference by its letters in order
Typing "@proinf" found nothing for ProjectInfoNew.tsx: fileref.Search
only recorded a hit when the query was a contiguous substring of a
directory name, the basename or a path segment, so a file could only be
reached by spelling a run of its name exactly or walking down to it one
directory at a time.

Search now keeps a last tier for names that contain the query's letters
in order (case-insensitive), files by basename and directories by name.
It ranks after every contiguous hit and only fills the slots those
leave, so existing results and their order are unchanged. The kernel's
Complete and the external-folder ref search both go through Search, so
every frontend gets it.

Tests: TestSearchMatchesSubsequenceAfterSubstringHits (fileref) and
TestCompleteRefFindsFileBySubsequence (control.Complete, the boundary the
composer calls) fail on origin/studio (got only [src/proinfo.go]; items
= []) and pass with the change.
2026-09-26 10:23:58 -07:00
YHH c15d7c83f2 Merge branch 'studio' into fix/studio-7352-cloud-mount-config-path 2026-09-26 10:23:55 -07:00
YHH 8006530d36 Merge branch 'studio' into fix/studio-5914-symlink-explorer 2026-09-26 10:23:46 -07:00
esengine df053a976d fix(web_fetch): dial an allowed address when a host also resolves to a blocked one
Under a fake-ip resolver (Clash / mihomo) every public host answers with a
routable A from 198.18.0.0/15 beside a unique-local AAAA from fd00::/8.
The direct dial path refused the whole host as soon as any resolved address
was on the SSRF blocklist, so web_fetch failed for every site with
"refusing to fetch internal address ... (resolves to fdfe:...)".

Root cause: the guard treated one blocked address as a verdict on the host,
although the dial is pinned to a resolved address rather than the name. A
blocked address in a mixed answer can simply be skipped; the connect still
only ever reaches an address that passed the check. A host whose addresses
are all blocked is refused exactly as before.

The direct path moves into dialVettedAddress, which tries the allowed
addresses in resolver order, and the lookup becomes a package seam so the
test can supply a resolver answer.

Test: TestWebFetchDialsAllowedAddressBesideBlockedOne resolves a name to
[fdfe:dcba:9876::152, 127.0.0.1] and fetches from a loopback server. On the
base it fails with "refusing to fetch internal address api-docs.example
(resolves to fdfe:dcba:9876::152)"; with the fix it passes.
TestWebFetchRefusesHostWithOnlyBlockedAddresses keeps the all-blocked
refusal. go test ./internal/tools/builtin/, go vet, make check and
golangci-lint are clean.
2026-09-26 10:22:17 -07:00
esengine 64abf5af72 fix(model): wait five minutes for a silent model before calling the stream dropped
A local model running at ~25 tok/s can spend well over two minutes
prefilling a long context without sending a byte. The openai, anthropic
and responses providers each cut such a call after 120s of silence (the
SSE idle watchdog, and the transport's response-header timeout on the two
that set one; anthropic set none, so its header wait was unbounded), and
the turn failed with "stream stalled — no data for 2m0s" although the
model was still working. The 1.x line already moved these guards to 300s.

The window now lives once, as provider.StreamIdleTimeout (300s), and all
three providers read it for both the idle watchdog and the header wait.

A longer header window multiplies through SendWithRetry, which retries a
response-header timeout with the full MaxRetries budget: 11 x 300s plus
backoff is close to an hour before the user sees an error, and a
prefill that timed out once times out identically on every attempt. A
transport timeout (a net.Error reporting Timeout() while the caller's
context is still live) now gets its own budget of one retry, the way
maxAuthRetries bounds auth failures, so the worst case is about ten
minutes.

Test:
- TestNewWaitsFiveMinutesForASilentModel in openai, anthropic and
  responses builds each client through New and reads its idle and
  header windows. Before: "stream idle window = 2m0s, want at least 5m"
  in all three, and "response header window = 0s" for anthropic.
- TestSendWithRetryGivesAHeaderTimeoutOneRetry drives a real
  http.Transport against a server that never sends headers. Before:
  "calls = 4, want the timed-out attempt plus exactly one retry" (retry
  limit 3). After: two calls and an error that is a net.Error timeout.

Fixes #6990
2026-09-26 10:20:05 -07:00
YHH d921615a0c Merge pull request #10897 from esengine/fix/studio-6860-planner-sees-executor-outcome
feat(coordinator): show the planner what the executor did since it last planned
2026-09-26 10:17:30 -07:00
YHH 7795a5f2f4 Merge pull request #10896 from esengine/fix/studio-6887-relative-evidence-paths
fix(evidence): credit a workspace-relative citation of an absolute write
2026-09-26 10:16:49 -07:00
YHH 4e8a6b47a3 Merge pull request #10899 from esengine/fix/studio-7135-f11-fullscreen
fix(studio): bind F11 to full screen on Linux and Windows
2026-09-26 10:15:34 -07:00
esengine 818fc803c8 fix(remote): identify a Windows host whatever its OpenSSH login shell
A Windows machine whose OpenSSH DefaultShell is Git Bash could not be used
as a remote host: `uname -sm` answers "MSYS_NT-10.0-26200 x86_64", which is
neither Linux nor macOS, and the fallback `echo %OS% %PROCESSOR_ARCHITECTURE%`
is cmd syntax that bash (and PowerShell) print back literally, so the machine
was refused as unsupported.

Root cause: detectPlatform asked the Windows question in cmd's syntax, while
every command windowsShell sends afterwards already goes through
`powershell -EncodedCommand`, which any of the three login shells runs the
same way.

Fix: ask for OS and PROCESSOR_ARCHITECTURE through psCommand. The guide no
longer says remote hosts are Linux and macOS only.

Test: TestWindowsIsIdentifiedWhateverTheLoginShell drives detectPlatform
through cmd, PowerShell, Git Bash (MSYS_NT) and MINGW64 login shells. On
origin/studio the Git Bash case fails with
  bootstrap: unsupported remote: `echo %OS%` answered "%OS% %PROCESSOR_ARCHITECTURE%"
and all four pass after the change.
2026-09-26 10:11:37 -07:00
esengine 286e682c31 fix(serve): list a workspace link as what it resolves to
The explorer classified entries by os.ReadDir's DirEntry.IsDir, which is
false for any symlink. A link to a folder inside the workspace was drawn
as a file, so it could not be expanded, and clicking it asked for a file
read that fails. Search had the same flaw: WalkDir does not follow links,
so a linked folder whose name matched came back as a file hit, and a link
to a file outside the tree came back as a hit the file endpoint's
os.OpenRoot then refuses.

A link entry is now resolved: one naming a folder inside the tree is
listed as a folder (expanding it already works, listFolder checks the
real path), one naming a file inside the tree as a file, and one that
dangles or leaves the tree is not offered, matching the refusal
listFolder already gives such a folder.

Test: TestALinkIsListedAsWhatItResolvesTo and
TestASearchListsOnlyLinksToFilesInTheTree fail on origin/studio
("directories = [alpha zeta]", "search = [alpha/a.txt far.txt near.txt
txt-folder zeta/z.txt]") and pass with the fix; the serve package
passes in full.

Refs #5914
2026-09-26 10:07:23 -07:00
YHH 9f5ed774b7 Merge pull request #10882 from esengine/fix/studio-6354-header-timeout-retry
fix(provider): retry a response-header timeout instead of ending the turn
2026-09-26 10:04:25 -07:00
YHH 7a3038d501 Merge pull request #10887 from esengine/fix/studio-6401-copy-without-rail
fix(tui): copying a selection leaves the code rail behind
2026-09-26 10:02:40 -07:00
esengine 99fcccdfc0 feat(coordinator): show the planner what the executor did since it last planned
The planner keeps a session of its own and only ever saw its own plans,
so the next planning turn did not know what the executor had found or
changed, and turns the policy routed straight to the executor never
reached it at all. Users ended up relaying the executor's results to the
planner by hand.

Every executor run now owes the planner its final reply (plus the user's
input when the planner never saw that turn), and the next planning input
carries the owed outcomes in an <executor-since-last-plan> block at the
turn tail, so the planner's cache-stable prefix is untouched. The debt
clears only once a plan comes back; a planning turn that fails and rolls
back keeps it owed. Switching sessions clears it with the planner
transcript.

The debt is bounded where it is held: task and reply are cut to their
render caps on append, only the newest four outcomes are kept, and a
count of the trimmed ones is rendered. A reply is taken only from the
run's own messages; when a compaction rewrites the session mid-run,
those begin at the run's own user message, identified by a stamp later
than every message before the run, and without it the outcome reads
"no reply captured" instead of an earlier turn's reply.

Test: TestPlannerSeesWhatTheExecutorDidSinceItLastPlanned and
TestPlannerKeepsExecutorOutcomeOwedWhenPlanningFails fail on
origin/studio (the second planning input carries no executor reply).
TestExecutorOutcomeNeverReportsAnEarlierReplyAfterCompaction (an
earlier reply reported twice) and TestOwedExecutorOutcomesStayBounded
(7 outcomes held, want 4) fail on the previous revision. All pass after,
with the existing coordinator suite.
2026-09-26 09:59:46 -07:00
YHH 84155ce239 Merge pull request #10895 from esengine/fix/studio-6823-shell-label
fix(environment): attribute the reported shell to the bash tool
2026-09-26 09:56:00 -07:00
esengine a15348f9b7 fix(evidence): credit a workspace-relative citation of an absolute write
complete_step rejected diff/files evidence whose paths were cited
workspace-relative ("frontend/src/core/backend/idb.ts") when the writer
had been given the absolute path: the ledger records a receipt's path as
the tool received it, and complete_step compared cited and recorded
paths for exact equality, in the per-turn ledger and in the cross-turn
session fallback alike. The step could not be signed off even though
the file had been written.

The agent now attaches its workspace root to each tool call's evidence
scope, and complete_step checks every cited path under the forms that
name the same workspace file: the citation itself, a relative citation
joined to the root, and an absolute citation made relative to it. A path
outside the workspace keeps only its own form, relative-vs-relative stays
exact, and case folds only on Windows through NormalizePath. The ledger
queries themselves are unchanged, so completion reports and blocked
conclusions keep exact matching.

Test: TestCompleteStepCreditsCitationsOfTheSameWorkspaceFile and
TestE2ERelativeCitationOfAbsoluteWriteSignsOff fail on origin/studio
("diff paths have no matching successful writer receipt") and pass
after. TestCompleteStepCreditsNoOtherFileForACitation (a same tail
outside the workspace, a base name, a relative nested write, a deeper
file's trailing components, a .bak, a ../ escape, a different case on
Linux) is rejected on origin/studio, credited under suffix matching,
and rejected after.
2026-09-26 09:55:32 -07:00
esengine 2bf6da8968 fix(config): load a project config under a cloud-drive mount
A workspace below a Box Drive mount on Windows failed every config load
with "resolve project config path ...: The system cannot find the path
specified", whether or not reasonix.toml existed.

Root cause: resolveConfigAccessPath canonicalizes the whole path with
filepath.EvalSymlinks before it confines a project config link to its
directory. Below a cloud-drive reparse point that canonicalization fails
while the files themselves open normally, and the failure was treated as
fatal even when reasonix.toml was a plain file or absent.

Fix: when canonicalization fails and the config entry itself is absent or
a non-link file, use the logical path. Such a file lies in its own
directory whatever its ancestors resolve to, so the confinement check has
nothing to decide. A link still has to resolve, so an unresolvable link is
refused as before.

Test: path_access_test.go stubs canonicalization to fail below a mount
directory. Without the fallback both LoadForRoot cases fail with the
reported "resolve project config path" error; with it they load (and the
project value is applied), and a reasonix.toml link under the same mount
is still refused.
2026-09-26 09:53:16 -07:00
YHH 3e5238675a Merge pull request #10894 from esengine/fix/studio-6785-show-turn-usage
fix(tui): honor ui.show_turn_usage in the terminal UI
2026-09-26 09:50:34 -07:00
esengine 7b28e8cf4f fix(studio): bind F11 to full screen on Linux and Windows
Off macOS the Electron shell sets the application menu to null so no bar
renders inside the window. Electron binds F11 only through a menu role
(togglefullscreen), so with no menu the key did nothing and the window
had no way into full screen on Linux or Windows.

The shell now listens for a bare, non-repeated F11 keyDown on the main
window's contents and toggles setFullScreen. macOS is left alone: it
keeps full screen on the title bar and the window menu.

Test: desktop/electron/test/unit.mjs "F11 toggles full screen where no
application menu binds it" fails on origin/studio (the key is unbound)
and passes with this change; node --test test/unit.mjs is 43/43.
2026-09-26 09:49:53 -07:00
esengine d856a08493 fix(environment): attribute the reported shell to the bash tool
The environment block in the system prompt printed "- Shell: bash". That
value is the interpreter sandbox.ResolveShell picked for the bash tool; the
user's login shell ($SHELL) never feeds it (it is only read to recover
PATH). Unattributed, the line reads as the user's shell, so a fish or zsh
user got advice about ~/.bashrc.

The line now reads "- Shell used by the bash tool: <shell>", and the
rendered [tools.shell] config section says the setting picks the bash
tool's interpreter and is independent of the login shell. The prefix
changes once on upgrade and stays byte-stable within a session.

Test: TestBuildEnvironmentBlockAttributesShellToBashTool builds through
boot with SHELL=/usr/bin/fish and reads the system message sent to the
provider. On origin/studio it fails (block shows "- Shell: bash"); with
the fix it passes, together with the environment and config packages.

Fixes #6823
2026-09-26 09:23:31 -07:00
esengine 11882ecd19 fix(tui): honor ui.show_turn_usage in the terminal UI
The config documents ui.show_turn_usage as the switch for the per-request
"TURN  … tok · in … · cached …" receipt in the CLI/TUI transcript, and the
1.x TUI reads it. The studio TUI never did: runTUI loaded the config but
passed nothing about it to tui.Options, and every usage row was drawn, so
setting it to false changed nothing.

tui.Options gains HideTurnUsage, set from the loaded config. A hidden usage
row still folds into the transcript (a later frame of the same attempt
restates it in place and TurnOut keeps counting), but it is marked committed
without being printed and is skipped in the live area.

Test: TestHiddenTurnUsageNeverReachesTheScreen drives a turn through the
model and checks the screen while the answer streams and after it settles.
Before the fix, hide=true failed in both states (receipt shown); after it,
both pass, and hide=false still shows the receipt.
2026-09-26 09:22:13 -07:00
YHH fd26c6c704 Merge pull request #10879 from esengine/fix/studio-5693-live-file-preview
fix(studio): refresh an open file preview when the file changes on disk
2026-09-26 08:55:05 -07:00
YHH 2079470588 Merge pull request #10885 from esengine/fix/studio-6369-single-tilde
fix(studio): a single tilde reads as a range, not strikethrough
2026-09-26 08:54:07 -07:00
YHH 0228994111 Merge pull request #10875 from esengine/fix/studio-5235-open-file-changes
feat(studio): a folding mode that opens file changes only
2026-09-26 08:53:25 -07:00
YHH 6a21efb0c3 Merge pull request #10874 from esengine/fix/studio-5058-carriage-return-progress
fix(studio): a progress meter redraws its line instead of growing it
2026-09-26 08:52:42 -07:00
esengine ebd527487e fix(studio): refresh an open file preview when the file changes on disk
Root cause: the workbench read an open file once, keyed on its path only.
The explorer tree beside it is re-read when a turn settles, when the
window is looked at again and when the change set moves, but the file
view never was, so an edit by the agent or another application stayed
invisible until the tab was closed and reopened.

Fix: the file view is re-read on the same occasions as the tree, and the
content is replaced only when the kernel's revision for it moved. A draft
with unsaved edits is never overwritten, including one made while a
reload read is in flight, and a hidden panel reads nothing. Revisions
are opaque, so a read only counts if no save and no later read has been
issued since it started; otherwise a reload issued before a save could
land after it and revert the view to the pre-save content.

Test: WorkbenchPanel.test.tsx adds five cases.
- "shows an open file as it is on disk once a turn settles" and
  "... once the window is looked at again" fail on the base (view still
  shows "First") and pass after the fix.
- "keeps unsaved edits when the file changes on disk underneath them"
  pins that a dirty draft issues no read.
- "keeps an edit made while a reload was reading the file" fails when the
  in-flight dirty-draft guard is removed and passes with it.
- "drops a reload that was issued before a save and lands after it"
  fails without the read/save ordering (view reverts to "# Second") and
  passes with it.
Full frontend-next vitest (915 tests), tsc --noEmit and make check are
clean.

Refs #5693
2026-09-26 08:46:00 -07:00
YHH 67a83685e6 Merge pull request #10876 from esengine/fix/studio-5496-blockquote-wrap
fix(termrender): wrap blockquote text to the width beside its rail
2026-09-26 08:45:31 -07:00
esengine bde08cebf7 fix(tui): copying a selection leaves the code rail behind
A fenced code block is drawn with a "│ " rail in its gutter. With the
mouse captured, selectedText read the rows exactly as drawn, so copying
code from an answer pasted a rail at the start of every line. Native
mouse mode already draws the rail as blank cells; the in-app copy now
reads the transcript rendered that way. The rail and its blank form have
the same width, so rows and columns still line up with the selection
drawn on screen, and only rows through the selection's end are rendered.

Test: TestSelectionCopiesCodeWithoutTheRail selects a railed code block
in a settled and a live answer. Before: copied
"  │ func a() {\n  │     return\n  │ }". After: no rail; the tui package,
go vet, golangci-lint and make check pass.
2026-09-26 08:30:54 -07:00
YHH 8301d7719d Merge pull request #10862 from esengine/feat/studio-explorer-reveal
feat(studio): reveal explorer entries in the system file manager
2026-09-26 08:30:29 -07:00
YHH 7124dc45e2 Merge pull request #10880 from esengine/fix/studio-5799-compact-blocks-model-switch
fix(control): a model switch waits for an in-flight /compact
2026-09-26 08:26:44 -07:00
YHH 7a09e7461c Merge branch 'studio' into feat/studio-explorer-reveal 2026-09-26 08:20:33 -07:00
YHH 00f1fcea20 Merge pull request #10878 from esengine/fix/ci-windows-test-cache
ci: run the Windows Go legs without the test result cache
2026-09-26 08:19:48 -07:00
esengine 8008423086 fix(studio): a single tilde reads as a range, not strikethrough
remark-gfm defaults to singleTilde: true, so "500~1000 or 2000~3000"
struck out everything from the first tilde to the second. A lone tilde is
the ordinary way to write a range, especially in Chinese replies. The
transcript renderer now passes singleTilde: false; ~~text~~ still strikes.

Test: Markdown.plugins.test.tsx renders two ranges and one ~~...~~ span.
Before the fix it found 2 <del> elements; after, exactly one, holding
only the doubled-tilde text. Full frontend-next vitest and tsc pass.
2026-09-26 08:15:44 -07:00
esengine ae678f2f1d fix(provider): retry a response-header timeout instead of ending the turn
SendWithRetry retries transient transport errors, but transientErr refused
anything matching errors.Is(err, context.DeadlineExceeded). net/http's
"timeout awaiting response headers" error implements Is(DeadlineExceeded),
so a slow gateway that hit ResponseHeaderTimeout ended the request on the
first attempt and left an unattended run stopped.

Whether the caller gave up is now asked of the caller's context rather than
read off the error's identity; a transport timeout while the caller is still
waiting is retried with the usual backoff.

Test: TestSendWithRetryRetriesResponseHeaderTimeout drives a real
http.Transport with ResponseHeaderTimeout against an httptest server that
stalls the first request. Before: "p: request failed: ... net/http: timeout
awaiting response headers". After: recovers on the second attempt.
TestSendWithRetryStopsWhenCallerDeadlineExpires pins that the caller's own
deadline is still not retried.

Refs #6354
2026-09-26 08:15:00 -07:00
YHH 6e177b0047 Merge pull request #10873 from esengine/fix/studio-4912-tui-keys
fix(tui): page the transcript with Shift+PgUp/PgDn and paste with Shift+Insert
2026-09-26 08:12:57 -07:00
esengine c3dd47eb5d fix(control): count a session rotation as active work so a model switch waits for /compact
A model switch rebuilds the controller from a snapshot of its history and
closes the old one. /compact runs asynchronously under the rotation gate,
but RuntimeStatus reported only running/finishing as active, so every
busy guard built on it (serve switchModel, provider/workspace rebinds,
ACP config rebuilds) let the switch through mid-fold: the new controller
carried the pre-fold history while the fold landed on the discarded
controller, which then wrote the compacted transcript over the same file.

RuntimeStatus.Running now includes the rotation gate, which also covers
rewind, summarize and new/clear session, matching the 1.x line where
maintenance already counts as running.

Test: TestSwitchModelRefusesWhileManualCompactIsFolding holds a real
manual compaction open at its summary call and switches the model through
serve. Before: switchModel returned nil and replaced the controller.
After: it refuses with busy.switch_model and keeps the old controller.
2026-09-26 08:02:53 -07:00
esengine 9d899e8dc5 ci: run the Windows Go legs without the test result cache
The Windows smoke and full legs looked hung: output stopped before
internal/assembly/boot and the step ran into its job timeout without
-timeout ever firing. Nothing was hung. With the result cache on, go test
passes each test binary -test.testlogfile, and before and after the run
it computes the cache key from that log, calling filepath.EvalSymlinks on
every recorded path outside the module. boot's log holds about 1.9M
entries (84 MB); on a Windows runner that costs about five minutes per
pass, and a restored cache that already holds a boot log pays it twice.

Pass -count=1 to every root-module go test that runs on Windows, and hold
ci.yml to it with a test that reads the workflow.
2026-09-26 08:00:52 -07:00
esengine a843729e38 fix(termrender): wrap blockquote text to the width beside its rail
A quoted paragraph was wrapped to the full terminal width and then
shifted right by the "▎ " rail (plus any list indent), so every full
row ran two or more columns past the edge. The terminal soft-wrapped
the overflow, leaving a stray rail on one row and the last characters
of the line on the next — most visible with CJK text, where every row
is filled to the edge.

renderBlockquote now lays the quote's body out at the width left after
its prefix and restores the renderer's width afterwards, so nested
quotes and quotes inside list items narrow once per level.

Test: TestBlockquoteRowsFitTheRenderWidth renders top-level, nested,
list-indented and Latin blockquotes at widths 20/41/80 and asserts no
row exceeds the width. It fails on the base (an 84-column row at width 80, 24 at width 20)
and passes with the fix.
2026-09-26 07:58:35 -07:00
esengine 275d3b918c feat(studio): a folding mode that opens file changes only
A reader who audits what the agent wrote had no way to keep diffs open
while leaving reads and commands folded. The folding settings answered per
part of the transcript (work group, step) and never per what a step did:
"steps" opened everything or only failures, and the work group folded once
the turn ended, so an edit was always two clicks deep.

Folding gains a "changed" mode for the work group and for steps. A step
opens when it changed a file (it carries a diff or line counts, the same
fact the +N/-M badge already reads); a work group opens when one of its
calls did. A "Changes only" preset sets both. The settings preview gains an
edit step so the mode shows what it does.

Test: src/ui/fold-changes.test.tsx renders a real Transcript with a read-only
turn stretch and one holding bash + edit_file. On the base, "changed" is
not a stored mode and both groups stay folded (expected [false, true],
got [false, false]); with the fix only the edit's group and the edit's
step open. Full vitest (144 files, 912 tests) and tsc pass.
2026-09-26 07:49:44 -07:00
esengine 3dc0707686 fix(studio): a progress meter redraws its line instead of growing it
curl, wget, pip and most download tools redraw their progress meter by
returning to column zero with a carriage return. The tool card renders
shell output in a <pre>, and a browser draws a carriage return as a
space, so every frame the command printed landed side by side on one
line. The card showed the first frame (0%) and the rest ran off to the
right: a 64-frame curl download measured 35003px wide in Chromium, so a
running download looked stuck at its start.

Term now resolves each line the way a terminal would leave it: every
frame overwrites the start of the one before, a CRLF ending stays a line
ending, and a trailing return holds the last complete frame while the
next one is still arriving. Copy still takes the raw output.

Test: four cases in ToolCards.state.test.tsx (latest frame shown,
shorter frame keeps the uncovered tail, CRLF, trailing return) fail on
origin/studio and pass with the fix. In the perf build the same curl
card goes from 35003px to 710px wide and shows 100% instead of 0%.
2026-09-26 07:49:43 -07:00
esengine 3343286b35 fix(tui): page the transcript with Shift+PgUp/PgDn and paste with Shift+Insert
Root cause: the full-screen transcript only bound plain PgUp/PgDn, so the
Shift+PgUp/PgDn a terminal uses for its own scrollback fell through to the
composer and did nothing. Shift+Insert, which Linux terminals that pass the
key through expect to paste, was unbound too (the textarea binds only Ctrl+V).
Ctrl+C quitting (twice, when idle) and Ctrl+Insert copying a selection were
already in place.

Shift+Up/Down stay with the composer, which uses them to select lines.

Test: TestShiftPageKeysScrollTheTranscript and
TestShiftInsertPastesClipboardText fail on origin/studio ("shift+pgup left
the view on the tail", "shift+insert did nothing") and pass with the fix;
go test ./internal/frontend/tui/ passes.

Refs #4912
2026-09-26 07:46:15 -07:00
esengine fda5c751f4 feat(studio): reveal explorer entries in the system file manager
Anything a task produced had to be found by hand under the workspace
path, because the explorer had no way to open Explorer or Finder at a
file or folder. Nothing like it existed. The only reveal in the app
opens the theme folder.

The explorer header gains a button that shows the workspace root. Every
file and folder row gains a context menu:

- "Reveal in File Explorer" on Windows
- "Reveal in Finder" on macOS
- "Show in file manager" elsewhere

The kernel confines the path and the shell does the showing.

- GET /workspace/locate?path=<workspace-relative> answers the absolute
  path of an entry in that runtime's workspace. "" and "." name the
  root. It refuses "..", absolute paths and links that resolve outside
  the tree with workspace.path_outside_tree, and a missing entry with
  workspace.file_missing.
- The route is gated on the local-desktop host grant. AllowEditorOpen
  is renamed AllowLocalDesktop because the grant now covers both
  routes. A networked serve and a paired device get
  workspace.locate_no_window.
- The hub refuses the route for a remote pane before proxying, with the
  same code. Only the hub knows the pane is remote, and a far kernel,
  compromised or not, answers with paths that mean nothing here.
- The renderer sends only its pane base and the relative path, over the
  new shell:reveal IPC. Electron main does the rest:
  - it requires the base to be a pane (/rt/<id>);
  - it asks the kernel with its own credential;
  - it acts only on an absolute path the kernel answered, and on Windows
    only on a drive-letter path, never a share or device path
    (\\host\share, \\?\, \\.\, \??\);
  - it only ever calls shell.showItemInFolder, the root included.
    openPath is never used: a directory can be an application bundle,
    or a link to one, and opening it would launch it outside the
    sandbox.
- StudioHost now forwards the query string, which it used to drop
  silently.
- The page hides the action in a browser tab (no bridge) and in a
  remote pane.

Tests:
- workspace_locate_test.go:
  - entries inside the tree, and "." as the root;
  - escapes, including a symlink escape;
  - a missing entry;
  - no grant, and a paired device;
  - the hub refusing a remote pane without reaching the far kernel.
- Electron unit tests drive reveal() through StudioHost against a real
  HTTP server. They check that the root is only selected, even when it
  is answered as a directory, that forged bases never reach the kernel,
  and that relative answers and Windows share or device answers are
  refused.
- WorkbenchPanel tests cover the header button, both row menus, the
  keyboard path, the remote pane and a coded refusal.
2026-09-26 05:54:07 -07:00
YHH 21722a28a3 Merge pull request #10867 from esengine/fix/studio-provider-protocol-hint-wrap
fix(studio): put the protocol hint below its switcher in a service's detail
2026-09-26 05:35:45 -07:00
xiexian 7ee0bf5b35 fix(studio): show slash completion loading state
Show a pending state while slash commands and skills load, and keep a dismissed pending request from reopening the menu when it resolves. Covers the delayed and Esc paths with interaction tests. Fixes #9311.
2026-09-26 20:34:49 +08:00
esengine 19dee62303 fix(studio): put the protocol hint below its switcher in a service's detail
In Settings > Model services the protocol row (.vway) lays out the label,
the segmented control and the hint in one nowrap flex row. The label and
the control are flex: none, and the hint is the only item that can shrink
(flex: 0 1 auto, min-width: 0). The detail column is only ~474px wide even
in a 1400px window: label 47 + control 367 + gaps 20 + padding 24 leave
16px, so the hint was shrunk to 16px and set one word per line, 24 lines
down the pane (computed styles read in Chromium). The only rule that
wrapped the row was a max-width: 760px viewport query, which never fires
while the pane itself is narrow inside a wide window.

A switcher row now always wraps, and the hint takes flex: 1 1 100%, so it
sits on its own line under the switcher at every width - the way the
neighbouring rows put a description under their label rather than beside
their control. The viewport query that did this only on narrow windows is
removed.

Test: perf/overflow.mjs opens Settings > Model services at 1400px and 640px
and measures every switcher hint in the detail pane (width against the
room in its row, rendered line count).
Before (origin/studio): 1400px FAIL - width 16px of 450px, 24 lines;
640px ok. After: 1400px 450/450px, 2 lines; 640px 538/538px, 2 lines.
tsc --noEmit, vitest src/ui/Providers src/ui/AddProvider
src/ui/settings-catalog src/styles (36 passed), perf/contrast.mjs clean,
make check clean.
2026-09-26 05:27:32 -07:00
YHH d96ca544ab Merge pull request #10861 from esengine/fix/studio-explorer-refresh
fix(studio): re-read the explorer tree when the window is looked at again
2026-09-26 04:45:35 -07:00
YHH 1674279493 Merge pull request #10859 from esengine/fix/studio-running-step-visible
fix(studio): a running step moves on its own row, and step rows lose their blank column
2026-09-26 04:32:57 -07:00
esengine 39d531704d fix(studio): a running step moves on its own row, and step rows lose their blank column
A running bash step in the activity group showed the same static clock as
the group header; the only sign the turn was alive was the composer's R.
Each step row also left a wide blank between the status mark and the
command.

Root causes:
- Nothing animated a running call's state mark; running differed from
  done only by a tint.
- `.activity-body .hl .nm` was `flex: 0 0 96px`: a fixed name column, so
  a short name like `bash` left ~80px of empty box before the command.
- `.flow .call[data-k="say"] .hl` (and its `.nm`/`.arg` rules) matched
  every descendant `.hl`. An activity group is re-hosted inside the say
  card, so a running row (a plain `.hl`, not a disclosure summary) picked
  up the answer header's 29px left padding and 10px bottom margin and sat
  26px right of the finished rows; args also took `--muted` instead of
  the group's `--faint`.

Fix:
- A running call's clock turns (2.4s linear) while its state holds. One
  fact moves in one place: the step's own mark while the group is open,
  the group header's mark once it is folded. The header also says
  "{n} 项运行中" in `--net`. Reduced motion stops the turn and keeps the
  tint; hidden panes pause it.
- The name hugs its text up to 96px and never shrinks.
- The say header rules are scoped to the card's own header
  (`> .c > .hl`), and plain step heads get the summary's 3px inset.

The motion guard counted any two infinite loops as the whole budget; it
now classifies each loop by what it reports (the run mark, or a running
call's mark at most once per running call) and fails any other loop.

Test: new perf/running.mjs drives a finished and a running bash call via
__feed in dark/light at 1440px and 640px, plus reduced motion. Against
origin/studio it fails 28 checks (no animation on the running mark, no
header signal, 83px between name and command, rows 26px apart); after
the fix all pass. motion, idle, cards, contrast, overflow and surface
guards pass; vitest 908/908; tsc clean; make check clean.
2026-09-26 04:24:10 -07:00
esengine 0b99a615a7 fix(studio): re-read the explorer tree when the window is looked at again
The explorer kept listing folders that had been deleted in Windows
Explorer: 12 entries against the 4 the OS showed.

Root cause: GET /workspace/files reads the disk on every request and keeps
no cache, but the panel only asked when it was opened, when the search
query changed, or when the git change set changed. That change set is
itself fetched only at turn boundaries, and untracked or ignored folders
(and every folder of a workspace that is not a repository) never move it.
So a change made in another application was never followed by a read, and
the tree's freshness rested on the agent being the one who wrote.

Fix: the listing is also read again when the window regains focus or its
page becomes visible (useGlance), and when a turn settles. No fs watcher:
the kernel listing is uncached and cheap, a watcher would need a new
dependency, a handle per expanded folder on Windows, and a new pushed
event type across the wire, all to refresh a panel nobody is looking at.

Test: WorkbenchPanel.test.tsx "drops a folder deleted outside the app once
the window is looked at again" deletes a folder from the backing store
without any port call and fires window focus. Fails on origin/studio (the
t620 row is still present), passes with the fix. A second case covers the
turn-settle read of a file git does not list.
2026-09-26 04:18:46 -07:00
YHH 61953d1c65 Merge pull request #10855 from esengine/fix/studio-10851-cjk-step-label
fix(studio): keep the receipt label on one line beside a long command
2026-09-26 04:05:58 -07:00
esengine c2ce043cae fix(studio): keep the receipt label on one line beside a long command
The clean delivery receipt lays out the tick, the label and the evidence
(file count plus passing commands) in one flex row. Only the evidence was
meant to give way (min-width: 0 + ellipsis), but the label kept the default
flex-shrink: 1. Flex shrinking is weighted by basis, so a command many times
wider than the label took almost none of the loss and the label was shrunk to
its min-content width - a single glyph for CJK, which may break between any
two characters. "没有未经验证的部分" rendered one glyph per line at every window
width.

The tick and the label now take flex: none; the evidence is the only item
that shrinks and ellipsizes.

The row also sat off the answer's column: it was indented by the tool-call
symbol column (42px), which top-level transcript items no longer draw, and
ran the full flow width instead of --doc. It now hangs like the answer card
(6px inline hang, max-width --doc), so the tick lines up with the answer
text and the activity group, and the row ends where they end.

Test: perf/cards.mjs feeds a turn_done receipt with a long && chain through
the real App in Chromium and measures the label at 1440/640/420px.
Before: "回执标签不被长命令挤成竖排 — 9 行" FAIL at all three widths.
After: 1 行 at all three, and the command is still clipped in-row.
The same page checks the row against the answer's text column: before,
tick off by 48px and row 60px wider at 1440px; after, 0px/0px at all three.
tsc --noEmit, vitest src/ui/cards src/styles (67 passed), make check clean.

Refs #10851
2026-09-26 03:59:00 -07:00
YHH 53c5683090 Merge pull request #10854 from esengine/fix/studio-10792-multi-edit-all-failures
feat(tools): multi_edit reports every failing step in one call
2026-09-26 03:54:08 -07:00
esengine f0a04741dc feat(tools): multi_edit reports every failing step in one call
multi_edit returned on the first step whose old_string was missing, not
unique, or empty, so a batch with three bad steps took three round trips
to surface them all even though nothing was ever written.

The sweep now records a failed step, leaves the buffer as it was, and
checks the remaining steps against it. Any failure still returns before
the write, so the file stays untouched. A lone failure keeps its
existing "edit N: ..." form; several are listed under
"K of N edits failed; <path> left untouched", with the note that steps
after a failed one ran without its replacement, so a later failure may
be a consequence of an earlier one.

Test: TestMultiEditReportsEveryFailedStep (5 edits, 3 bad: not found,
not unique, empty old_string) fails on origin/studio, which reports only
edit 2, and passes with this change; the file is unchanged in both.
go test ./internal/tools/builtin/ passes; make check and golangci-lint
on the package are clean.

Refs #10792
2026-09-26 03:27:55 -07:00
YHH 76ae0ee554 Merge pull request #10850 from esengine/fix/studio-review-untrusted-checkout
fix(cli): reasonix review ignores tools and skills the reviewed checkout configures
2026-09-26 03:24:44 -07:00
esengine ae4ab517f8 fix(cli): reasonix review ignores tools and skills the reviewed checkout configures
The checkout under review is untrusted input, yet review built its grep
from the merged config and read the review skill with the checkout as a
project root. A reasonix.toml setting [tools.search] engine = "rg" and
rg_path = "tools/rg" made review's grep execute a program the checkout
ships, and a .reasonix/skills/review/SKILL.md without read-only replaced
the built-in review skill, handing an attacker-written prompt bash.

Review now loads the user-global config alone (Roots.LoadUserConfigReadOnly)
for its tool registry: search engine, rg_path and sandbox. Its skill store
has no project root, so project-scope skill directories are never
discovered. Provider and model resolution are unchanged.
2026-09-26 03:11:36 -07:00
YHH 144e6e9797 Merge pull request #10846 from esengine/fix/studio-hooks-planner-guardian-review
fix(hooks): run configured tool hooks in subagents, the planner, the guardian and reasonix review
2026-09-26 03:11:01 -07:00
esengine c4ea3251a8 fix(hooks): run configured tool hooks in subagents, the planner, the guardian and reasonix review
On studio only the executor and the controller fired the session's hooks.
Every other agent that calls tools ran with no hook runner, so a PreToolUse
deny on reading a secret did not cover its reads:

- delegated children: TaskTool.subagentOptions (task, read_only_task,
  parallel_tasks, fleet, background tasks and model-invoked run_skill via
  RunProfileSpec) and roleWiring.skillRunOptions (read_only_skill) set no
  Hooks. This ports main-v2's 896faf36d (#10843), which never reached studio;
- the dual-model planner (roleWiring.planner);
- the guardian, reachable through gateApprover -> guardianSess.Review;
- `reasonix review`, which builds its subagent outside boot.

hook.Runner gains ForRole(role): a runner firing the same live hook set in
the same environment, whose session id is the parent's id at fire time
suffixed with ":"+role. A SetSessionID rotation (/new, clear, branch
switch) therefore reaches every child, and a hook keyed on session_id
neither merges the planners of different sessions nor confuses a child
with the executor. The hooks and the repeat-suppression state live in a
shared hookSet, so SaveHooks/Replace reaches every derived runner.

boot passes the runner to roleWiring: planner fires as <session>:planner,
guardian as <session>:guardian (guardian.NewSession takes the hooks), task
and skill children as <session>:subagent:<ref|call id> through
TaskToolOptions.HooksForRole and skillRunOptions.

`reasonix review` loads only the user's hooks: hook.LoadOptions gains
Scopes, and boot.NewUserHookRunner loads hook.UserScopes (global
settings.json and installed plugins). Project hooks from the reviewed
checkout never run, since a branch under review is untrusted input. Each
review run fires under a fresh id.

Review hooks start in the checkout root so they can inspect it, which on
Windows would let cmd.exe resolve a legacy hook's bare `python` or `node`
to an executable the checkout ships, since cmd.exe searches the current
directory first. The review runner's spawner (hook.WithoutCwdExeSearch)
sets NoDefaultCurrentDirectoryInExePath=1 in every hook process, which
cmd.exe honours; the cwd stays the checkout.

Tests (real PreToolUse scripts that log their payload and exit 2):
- TestBuildRunsPreToolUseInsideTaskSubagent (boot.Build; task,
  read_only_task, run_skill, read_only_skill): the child's read is blocked
  and fires under <parent>:subagent:...
- TestEffectPreToolUseCoversPlannerReads / ...GuardianReads (boot.Build):
  blocked, under <parent>:planner / <parent>:guardian
- TestEffectPlannerHookSessionFollowsRotation: NewSession moves the
  planner's hook session to the new parent id
- TestReviewCommandRunsOnlyUserHooks: a global deny blocks the review's
  read, a project hook in the checkout never runs, two runs fire under
  two distinct ids
- TestUserHookRunnerDisablesCwdExeSearch: through the spawn seam, the
  review runner's hook process starts in the checkout with
  NoDefaultCurrentDirectoryInExePath=1
- TestLoadUserScopesSkipProjectHooks, TestRunnerForRoleDerivesSessionIDAtFireTime

Refs #9255
2026-09-26 02:32:11 -07:00
YHH 40de19b6e5 Merge pull request #10838 from c020627/docs-studio-wails-residue
docs: port the retired-Wails wording cleanup to studio
2026-09-26 00:50:48 -07:00
esengine 7ee4bbb77e ci(release): studio publishes the CLI to npm and Homebrew only when told to
The 1.x line keeps the CLI's install channels until 2.x is stable, so
the npm and Homebrew job now runs only with the repository variable
STUDIO_PUBLISHES_CLI=true. Studio releases still carry the CLI archives
its remote workspaces install from.
2026-09-26 00:47:56 -07:00
YHH 040a4b587c Merge pull request #10841 from esengine/chore/ack-token-studio
ci(acknowledgments): open the weekly PR with a token that triggers CI
2026-09-26 00:32:45 -07:00
esengine ac2bc077b2 ci(acknowledgments): open the weekly PR with a token that triggers CI
A pull request authored with GITHUB_TOKEN never starts other workflows, so
the weekly acknowledgments PR never reported the checks branch protection
requires and could only land by bypassing it. The step now uses the
dedicated ACKNOWLEDGMENTS_TOKEN secret when it is set, and falls back to
GITHUB_TOKEN otherwise.

The contributor table also listed github-actions[bot]; bot accounts are
now filtered before the top 20 are taken (30 are fetched to keep 20).
Checked by running the script against the live API: the previous version
writes one github-actions row, this one writes none.
2026-09-26 00:13:00 -07:00
YHH 578c1347ad Merge pull request #10828 from onionviolet/codex/studio-version-empty-fallback
fix(tui): show dev for missing build version
2026-09-26 00:10:55 -07:00
c020627 9a999f7b7b docs: port the retired-Wails wording cleanup to studio
Wails stopped being the desktop shell when the Electron shell landed, but
seven places on `studio` still describe it in the present tense. Six of them
were already fixed on `main-v2` by #10018; they only need the same edit
carried over here.

- `CHECKPOINTS`: the desktop rewind control calls `controller.Rewind` over the
  desktop host protocol, not the Wails binding.
- `TASK_CONTRACT`: the constraint keeps the desktop host JSON contract intact.
- `RECOVERY`: `doctor` inspects install problems without loading the desktop
  shell, and macOS keeps LaunchServices launching the desktop app bundle.
- `DESKTOP_CRASH_DIAGNOSTICS_RUNBOOK`: the budget is pre-shell work, and
  report/metric persistence runs after shell startup.
- `SESSION_MEMORY_RETRIEVAL`: the example project file no longer points at
  `desktop/frontend/wailsjs/`, which went away with the Wails shell. Studio
  commits its generated files under `internal/ext/extension/protocol/`.
- `REASONIX.md`: the frontends behind `control.Controller` are the chat TUI,
  HTTP/SSE serve and the Electron desktop.
- `issue-auto-label`: the classifier prompt no longer tells the model the
  desktop app is Wails.
2026-09-26 14:08:56 +08:00
esengine 3f58372f40 ci(issues): allow a one-second gap between replies 2026-09-25 20:34:00 -07:00
onionviolet 729ef1a6f2 fix(tui): show dev for missing build version 2026-09-25 23:29:48 -04:00
esengine 5d4e7e710e ci(issues): a batch of replies can set the gap between entries 2026-09-25 20:26:25 -07:00
YHH 199f948463 Merge pull request #10820 from esengine/fix/studio-9758-reasoning-only-after-tools
fix(agent): retry once when a tool round ends in a reasoning-only stop
2026-09-25 20:10:27 -07:00
YHH 4bd331641d Merge pull request #10823 from esengine/fix/studio-10369-wayland-x11-default
fix(studio): relaunch the shell on X11 under Wayland when XWayland is there
2026-09-25 20:07:58 -07:00
esengine d2d6b4070c ci(issues): a reply can close its issue as not planned
A cleanup of reports on old versions is not a fix, and closing those as
completed tells the reporter and the tracker otherwise.
2026-09-25 20:06:22 -07:00
xiexian 4c3a33c891 fix(studio): auto-refresh open Markdown previews 2026-09-26 11:02:28 +08:00
esengine cf66d68c9c fix(studio): relaunch the shell on X11 under Wayland when XWayland is there
Root cause: Electron 44 picks its Wayland Ozone backend by itself in a
Wayland session, and on drivers whose EGL platform init fails there (#10369)
the GPU process exits and the app dies before a window appears; the same
machine works with --ozone-platform=x11. Studio's deb entry runs the
Electron binary directly, so no wrapper builds its argv, and the only
switch main.js sets is disable-backgrounding-occluded-windows. Chromium
selects Ozone before main.js runs, so appendSwitch cannot choose it.

Decision: default to X11 whenever it is available, the same rule as the
1.x line. On Linux, when the session is Wayland (XDG_SESSION_TYPE, else
WAYLAND_DISPLAY) and DISPLAY is set, main.js relaunches once with
--ozone-platform=x11 prepended and exits, before it claims the instance
lock so the relaunch finds nothing held. REASONIX_OZONE_PLATFORM=wayland
keeps native Wayland, x11 forces X11, auto is the default. A pure Wayland
session without DISPLAY and an X11 session are unchanged, and an explicit
--ozone-platform or --ozone-platform-hint argument wins, which is also what
stops the relaunched process from relaunching again. The update handover's
app.relaunch() reuses process.argv and keeps the flag.

Test: two cases in test/unit.mjs. One loads the real main.js with a stub
electron module under a Wayland+DISPLAY environment and asserts it
relaunches with the flag and exits before requestSingleInstanceLock; on
origin/studio it fails with actual [['lock']] where
[['relaunch', ['--ozone-platform=x11', ...]], ['exit', 0]] was expected.
The other walks the session table through relaunchForOzonePlatform. Both
pass with the fix (38/38). A probe on Electron 44.1.0 confirmed a
top-level return after app.relaunch({args}) + app.exit(0) before ready
starts a child that receives the new argv.
2026-09-25 20:00:23 -07:00
esengine 285272440f fix(agent): retry once when a tool round ends in a reasoning-only stop
Root cause: on DeepSeek thinking mode, reasoningOnlyFinishHonoured
accepts a finish_reason=stop with empty content as the final answer.
When that stop comes right after a tool round, the tool results never
get a visible synthesis and the turn goes idle until the user sends
another message (#9758).

Decision: a narrow structural retry. When this turn ran tools, no
visible assistant text follows the last tool result in the transcript,
and the stop is reasoning-only, the existing empty-final retry runs
once; a second reasoning-only stop is honoured. A reasoning-only answer
to a turn that ran no tools is still honoured, so the extra thinking
round the honour rule prevents does not come back there. The judgement
reads structure only: usedAnyTool, empty content, message position.
Non-DeepSeek providers keep their existing bounded retry.

Tests: TestRunRetriesReasoningOnlyStopAfterToolRoundOnce and
TestRunAcceptsSecondReasoningOnlyStopAfterToolRound drive a scripted
DeepSeek-policy provider through Agent.Run. On the base both fail with
"provider calls = 2, want tool round + reasoning-only stop + one retry";
with the fix they pass. TestRunAcceptsReasoningOnlyFinalWhenModelStopped
(no tools) passes unchanged, as do internal/runtime/agent and its
direct dependents.
2026-09-25 19:59:02 -07:00
YHH eb284e84ee Merge pull request #10827 from esengine/fix/studio-10540-conhost-width
fix(tui): fit glyphs a conhost CJK font draws double-width
2026-09-25 19:58:23 -07:00
YHH 30fa424456 Merge pull request #10824 from esengine/fix/studio-9719-kill-durable
fix(session): save the transcript once a turn's prompt is admitted
2026-09-25 19:57:11 -07:00
YHH c9bdc1e0c2 Merge pull request #10810 from esengine/fix/studio-10372-linux-static-host
fix(studio): build the Linux kernel and update helper without cgo
2026-09-25 19:52:30 -07:00
esengine 628163e0f0 fix(tools): an image read_file refuses says who can attach it
read_file told the model to reference an image as @path and, if it
cannot read images, to delegate with read_only_task. Only a reference
in the user's own message attaches a picture, so for an image the user
never attached the delegation reached a child with nothing to look at.
The refusal now says an image attached this turn can go to
read_only_task, and that any other one needs the user to send it.

In 2,217 local sessions, all six image refusals were attachments, which
the delegation does carry; the unattached case has not been seen, so no
new task parameter is added for it.
2026-09-25 19:47:32 -07:00
esengine 66e73724f0 fix(studio): build the Linux kernel and update helper without cgo
build-host.js built reasonix-studio-host on Linux, and linux-payload.js
built reasonix-studio-update-helper, with cgo at its default. On the
ubuntu-22.04 runner that links both against glibc 2.35: in the
studio-v2.20.0 .deb both name /lib64/ld-linux-x86-64.so.2 and the host
needs GLIBC_2.32/2.34, so Studio will not start its kernel on glibc 2.31
systems even though the host is already held CGO-free by studio.yml. On
Linux the only cgo in its closure is the stdlib's optional net and os/user
glue, which have pure-Go implementations.

Both builds now pass CGO_ENABLED=0, and the package job reads the ELF
program headers of the .deb's Go binaries (packaging/elf.js) and fails if
one names a program interpreter.

Test: test/unit.mjs adds a case for elf.js; on the base it fails with
"Cannot find module '../packaging/elf.js'", with the fix 36/36 pass.
`node packaging/elf.js` on the studio-v2.20.0 .deb's host and helper
exits 1 ("... is dynamically linked (interpreter
/lib64/ld-linux-x86-64.so.2)"), and exits 0 on both built with
CGO_ENABLED=0 GOOS=linux.

Refs #10372
2026-09-25 19:38:13 -07:00
esengine 2c80057f08 fix(tui): fit glyphs a conhost CJK font draws double-width
Root cause: legacy conhost with a CJK TrueType font (the default on
Chinese Windows) sizes East Asian Ambiguous runes by the glyph, so it
draws a font-dependent subset two cells wide: · … “ ” ‘ ’ — – ● → ↑ ↓
◆ ° × and Greek/Cyrillic letters among them. The TUI and its renderer
count all of them as one cell, so every full-width row carrying k of
them is k cells too wide on screen: conhost wraps it, the alt-screen
frame grows past the window and scrolls, and later repaints land on
shifted rows, which is the stacked-frame garbage in the report.

Decision: measure instead of guess. When stdout is a console, a
never-activated screen buffer of that console (same font) measures
each non-ASCII rune the layout counts as one cell, once, via
WriteConsoleW and a cursor read-back. A rune the console draws wider is
swapped, after layout, for a one-cell stand-in both sides agree on:
Unicode's halfwidth variant when it has one (→ becomes U+FFEB), else
the OS US-ASCII best-fit (· becomes ., “ becomes "), else the OS
default character. Each candidate is itself measured before it is
used. The swap is rune for rune at equal counted width, so applying it
to the finished frame changes no row's layout; escape sequences and
OSC payloads pass through untouched. A pseudoconsole (Windows Terminal,
VS Code, SSH) measures these runes as one cell, so nothing is replaced
there; other platforms get a nil fit and are unchanged.

The same measurement also covers what the inline mode prints above the
frame (answers and the banner), since a wrapped print adds rows the
renderer does not know about. The banner is printed from a command
goroutine, so the per-rune cache is guarded by a mutex.

Test: TestFrameFitsConhostMeasuredWidths renders the real TUI View at
120 columns after a streamed answer, with the width table measured on
a Chinese Windows 11 conhost, and asserts no row draws wider than the
terminal. With the substitution disabled it fails:
  row 3 draws 121 cells on conhost, terminal has 120
With the fix it passes (also under -race). In a real conhost window
the unfixed binary shows the status row twice with "au" / "to" split
across rows; the fixed binary keeps the 30-row frame intact.

Refs #10540
2026-09-25 19:12:18 -07:00
esengine e4fc2b0e13 fix(control): sandbox settings still save on a host with no sandbox
The rendered config template writes bash = "enforce" even when nobody
chose it, so on a Linux host without bubblewrap the settings read back
as enforce and every later save was refused as sandbox.unavailable,
including one that only added a writable directory. Only switching the
jail on is refused now; a file already holding enforce saves its other
fields, and bash stays refused at run time as before.

Reproduced on a bwrap-less Linux: the new test fails there without this
change and passes with it. It skips where a sandbox exists and on
Windows, which forces bash off.
2026-09-25 18:52:40 -07:00
esengine 9ddcf2eca3 fix(session): save the transcript once a turn's prompt is admitted
Root cause: the transcript was written only on the 30s autosave tick or
at turn end. A process killed between admitting the user message and the
next save reopened with the in-flight marker but without the prompt, so
the turn vanished from both the history and the model's context.

Decision: the same bounded change as the 1.x line's option A (1). The
agent gains an AdmissionSaver seam called from LandAuthoredUserMessage,
the one place a turn's user message lands, and the controller saves the
transcript there: one extra save per turn, skipped without a session
path, and a failed save only logs. The 1.x partial-output checkpoint
does not apply here: studio has no event-log session store, and the
cache-stable prompt prefix is untouched.

Test: TestKilledTurnKeepsItsPrompt (internal/session/control) runs one
turn, starts a second, streams part of a reply and ends the child
process with os.Exit (no TurnDone, no final save), then reloads the
session and runs recovery. On origin/studio it fails with "prompt of the
killed turn lost after restart"; with the fix it passes.
2026-09-25 18:52:19 -07:00
esengine a099f92d25 test(control): pin bash off where the changed-file switch is saved
The test saves the sandbox settings twice. On a Linux runner without
bubblewrap the second save asks for a sandbox the host cannot run and
is refused, which has nothing to do with the switch under test.
2026-09-25 18:47:06 -07:00
YHH f371ec07b9 Merge pull request #10816 from BuGlessRB/open/scrollbar-native-mouse
feat(tui): drop the scrollbar and code gutter in native mouse mode / 功能(tui):原生鼠标模式下去除滚动条与代码块竖线
2026-09-25 18:39:29 -07:00
YHH 6c44fbaa3e Merge pull request #10815 from BuGlessRB/open/continue-fresh-session
feat(cli): start a fresh session when --continue finds nothing / 功能(cli):--continue 找不到会话时改为开启新会话
2026-09-25 18:39:26 -07:00
YHH 2fbfb16d08 Merge pull request #10812 from scrappylabsai/feat/studio-ask-notification-hook
feat(control): fire the Notification hook for pending ask questions
2026-09-25 18:39:24 -07:00
YHH 366c493a6e Merge pull request #10793 from onionviolet/codex/studio-tui-version
feat(tui): add /version to studio
2026-09-25 18:39:21 -07:00
Stephen R. van den Berg 5b5ee2d161 feat(tui): drop the scrollbar and code gutter in native mouse mode
When native mouse is on (REASONIX_DISABLE_MOUSE, or the /mouse toggle), the
terminal owns the mouse: the in-app scrollbar can no longer be dragged, so the
transcript drops its right-hand column and reclaims that width for content, and
fenced code drops its "│" gutter for clean copy. Capture-on is unchanged.
2026-09-26 02:18:45 +02:00
Stephen R. van den Berg c4f0483223 feat(cli): start a fresh session when --continue finds nothing
When --continue finds no saved session, it reports the miss and starts a fresh
session instead of exiting 1. Both the run path and the TUI resume path fall
through, a --continue miss is no longer reported as "continue" in telemetry,
and the flag help and docs say so.
2026-09-26 02:18:45 +02:00
onionviolet 99a93a9453 feat(tui): show build version with /version 2026-09-25 16:50:44 -04:00
Brian ConnellyandClaude Opus 5.5 c1e78ca0e0 feat(control): fire the Notification hook for pending ask questions
The ask tool blocks the run on the user exactly like an approval prompt,
but only approval prompts fired the Notification hook, so an external
channel wired to it heard about every pending approval while a blocking
question looked like the agent still working. In YOLO mode ask is the
only prompt that waits on the user, and it was the one that stayed silent.

Fire the hook once the AskRequest is emitted, mirroring the approval call
site: message "answer needed: <first prompt>" (en/zh/zh-TW),
notificationType "question_prompt".

Rebuild of #9048 on studio.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-25 15:18:57 -04:00
YHH ebc7ff9e22 Merge pull request #10807 from esengine/fix/studio-10458-missing-key-message
fix(cli): point a missing model key at the credentials file, not the shell
2026-09-25 11:05:12 -07:00
esengine f9a5b39161 fix(tui): a streamed answer keeps its layout, and the end card says what it found
Looked at on Windows Terminal against 1.x with the same scripted turn:

- A streaming answer is printed a block at a time, and each block lost the
  blank line before it, so headings, lists, code and tables ran together.
  A later stretch now starts with that line.
- Tabs were drawn zero cells wide in the full-screen view, so code lost its
  indentation. Rows expand them before wrapping, as 1.x does.
- A list resumed after a code block restarted at 1; it counts from the
  number it was written with.
- oneLine cut by runes, so a line with CJK text ran past the row and wrapped
  to column 0. It cuts by cells.
- The approval panel and the end-of-turn mark drew ⏸ and ⚠, which Windows
  Terminal renders as two-cell emoji over one-cell slots. The panel is headed
  by its title in the accent colour, and its key hint is translated.
- The end-of-turn card is 1.x's: what went unproven, in the reader's
  language, instead of "incomplete · 2 gap(s)".
- The footer's compaction figure reads "距压缩 81%" (headroom left), not
  "压缩 81%".
2026-09-25 11:04:57 -07:00
esengine 79c984b32a feat(tools): refuse a whole-file write over a change made since the agent saw the file
write_file replaces the whole file, so a model writing from what it read
earlier silently discards anything changed since: an edit the user made
in their editor while the turn ran, a formatter, another process. The
run now keeps what the agent last saw of each file, through read_file or
its own writes, and write_file refuses when the file holds something
else, telling the model to read it again. edit_file and multi_edit match
their old text against the current file and were already safe.

In 2,217 local sessions, the case never shows in the logs, because an
outside edit is not recorded there, so this is a safety net rather than
a measured gain. It costs nothing until a file has actually changed,
and is on by default. [tools].protect_changed_files and a switch in the
Studio sandbox settings turn it off.
2026-09-25 10:32:58 -07:00
esengine 1584a67453 fix(cli): point a missing model key at the credentials file, not the shell
An explicit --model whose provider has no key failed with
`provider "deepseek-flash" requires DEEPSEEK_API_KEY`. Provider keys
resolve only from the loaded config's credentials file
(storedCredentialValue on Roots.UserCredentialsPath), so the message
sent users to export the variable in their shell or system environment,
which Reasonix never reads. It also labelled the model ref as a
provider. Reported through `reasonix acp` from the VS Code extension in
#10458.

The error now names the model, the variable, the credentials file of
the config it was resolved against, and `reasonix setup`, and says
shell environment variables are not read.

Test: TestResolveModelForCLIMissingKeyNamesCredentialFile sets the key
only in the process environment. On the base it fails with
  error "provider \"deepseek-flash\" requires REASONIX_CLI_TEST_SHELL_ONLY_KEY"
  does not mention ".../.reasonix/.env"
and passes with the fix; go test ./internal/frontend/cli/ passes.
2026-09-25 10:24:20 -07:00
esengine a992bd98f9 ci(issues): close the issues a merged 1.x pull request fixes
GitHub acts on "Fixes #N" only for pull requests merged into the default
branch, which is studio, so fixes merged into main-v2 left their issues
open. On a merge into main-v2 this reads the description's closing
keywords, notes the fix on each open issue and closes it.
2026-09-25 09:12:26 -07:00
Weibao Chen 13f689b5c1 feat(skill): allow quiet startup skill warnings (#10795)
Adds [skills].suppress_warnings (default false), which quiets the non-fatal skill warnings printed during startup discovery. doctor diagnostics are unchanged. Implements #6483.
2026-09-25 08:51:52 -07:00
esengineandBuGlessRB f1a584a9ef fix(tui): start with mouse capture off where 1.x did
The terminal UI ignored REASONIX_DISABLE_MOUSE, which the guide still
documents. It now reads it as 1.x does: 1 starts with the mouse handed
to the terminal, 0 captures it, and unset leaves capture off over SSH,
where the native selection reaches the user's clipboard.

Co-authored-by: BuGlessRB <[email protected]>
2026-09-25 08:47:13 -07:00
esengine 806e916d3c ci(studio): pin the self-test workflow's actions to full-length commit SHAs 2026-09-25 08:45:07 -07:00
YHH d0cfc7db6d ci: pin every third-party action to a full-length commit SHA (#10779)
A tag is a mutable pointer: whoever controls the action's repository can
move it to new code and every workflow here runs it on the next event,
with this repository's token and on its self-hosted runners. A commit SHA
cannot be moved.

Each reference keeps its tag as a trailing comment, the form Dependabot's
github-actions ecosystem reads and updates. This lets the repository-level
"require full-length SHA" policy be enabled without failing studio runs.
2026-09-25 08:44:45 -07:00
YHH 49585e9147 fix(goaleval): read the verdict a thinking model leaves in reasoning (#10785)
With no recovery_model or guardian_model configured, the Goal evaluator
runs on the main model. A DeepSeek thinking model can stream its whole
answer as reasoning_content and finish with stop and an empty content
block. boundedllm.Call collected only ChunkText, so the evaluator got ""
and every goal paused with "empty goal evaluator response".

boundedllm.Config gains ReasoningAnswer. When a caller opts in and the
provider reports finish_reason=stop with no content, Call returns the
reasoning stream instead. A truncated stream still yields nothing, and
reasoning past MaxOutputBytes is refused only when it would become the
answer, so reasoning before real content is never charged. The goal
evaluator opts in because parseVerdict extracts the JSON object and fails
closed on anything else; free-text callers (advisor, prompt refine) keep
the content-only behaviour.

Test: TestEvaluateReadsVerdictFromReasoningOnlyStop fails on the base
with "empty goal evaluator response" and passes with the fix;
TestEvaluateIgnoresReasoningOfTruncatedStream and TestCallReasoningAnswer
cover the stop guard, opt-in and output budget.

Refs #9678
2026-09-25 08:43:45 -07:00
YHH 5154389dd7 fix(cli): an ambiguous --resume query names its candidates (#10787)
resolveSessionQuery reported a query that matched several sessions as a
plain sentence, so neither caller could recover which sessions matched:
`reasonix run --resume <q>` exited 1 with only a match count, and the
terminal UI refused to start the same way.

The ambiguity is now a typed ambiguousSessionQueryError carrying the
matched sessions. The headless start still exits 1 but lists each
candidate (last activity, the id --resume accepts, its title) followed by
a new AmbiguousResumeHint. The terminal UI, which already requires a TTY,
opens its session picker limited to those matches (tui.Options.PickAmong).
The narrowing belongs to that first picker alone: it is dropped whether
the first session fetch succeeds or fails, so a later /resume lists
everything.

The picker resumes the chosen session in place, so an ambiguous query
with --copy is refused up front with the same candidate listing rather
than opening a picker that would silently ignore --copy.

Tests: TestRunAmbiguousResumeListsCandidates drives runAgent with an
ambiguous --resume and fails on the base with only
`session query "session" is ambiguous (2 matches)` on stderr;
TestTUIAmbiguousResumeWithCopyIsRefused fails when the picker path is
taken with --copy set; TestPickerNarrowingEndsWithAFailedFirstFetch fails
when the narrowing is cleared only after a successful fetch;
TestResolveSessionQueryAmbiguityCarriesMatches and
TestPickerStartsOnTheGivenSessions cover the typed error and the
narrowed picker.
2026-09-25 08:41:54 -07:00
YHH 23c82fc149 fix(tui): history recall keeps the draft it replaced (#10789)
recall() kept no draft slot: up on a one-line composer overwrote the
text with the previous prompt, and down past the newest entry called
composer.Reset(). With any history present, a single down on a draft
that had never left for the history wiped it outright.

The draft is now set aside when a walk begins and restored when the
walk returns past the newest entry; down at the live position is left
to the textarea instead of resetting it.

Tests in internal/frontend/tui/recall_test.go: TestRecallReturnsTheDraftItLeft
and TestDownOnADraftKeepsIt fail on the base (the composer ends empty)
and pass with the fix.

Refs #10425
2026-09-25 08:34:14 -07:00
YHH a5981dd096 fix(cli): keep fatal crash output of the CLI for the next start (#10790)
A CLI process that died from a panic on a background goroutine or a
fatal runtime error (out of memory, concurrent map writes) left no
evidence. runWithCrashCapture only recovers panics on the main
goroutine and nothing called debug.SetCrashOutput, so that output went
only to the console's stderr, which is gone once the terminal closes.

The CLI now mirrors runtime crash output into a per-PID file under the
Reasonix home. The next start turns any dump left by a dead process
into a pending crash report that keeps the runtime classification and
the sanitized goroutine stacks and redacts the panic value, and removes
the empty files of processes that exited without one. A panic already
reported through recover detaches the file first so it is not queued
twice.

Test: TestGoroutinePanicIsReportedOnNextStart re-runs the test binary
as a child that panics on a background goroutine, then asserts the next
start lists exactly one report with the panicking goroutine's stack and
no panic value. On the base it lists none.
2026-09-25 08:33:24 -07:00
esengine 626871ea50 ci(studio): retry the self-test's module download, which the intranet route drops 2026-09-25 07:51:28 -07:00
esengine acedea659b ci(studio): set the self-test PATH from cmd, which the runner's execution policy allows 2026-09-25 07:45:04 -07:00
esengine 06a58e54e4 ci(studio): build an unsigned Windows self-test installer on the intranet runner
Dispatch-only, so nothing a pull request writes runs on that machine.
The version is a prerelease one patch above the newest release, so the
build neither offers itself that release nor hides the next one.
2026-09-25 07:44:07 -07:00
esengine 7a52c69746 ci(issues): reply on pull requests, and wait out only a rate limit
A comment on a pull request needs pull-requests: write. A refusal that
is not a rate limit is final, so it fails the entry at once instead of
being retried for ten minutes.
2026-09-25 07:40:30 -07:00
esengineandBuGlessRB 3047105ea4 feat(cli): --permission-mode reads 1.x's names
A 1.x command line keeps working under 2.x: workspace-write is Auto,
danger-full-access is Yolo, and read-only, which 2.x has no mode for,
falls back to Ask. MIGRATING says where yolo's meaning differs.

Co-authored-by: BuGlessRB <[email protected]>
2026-09-25 07:22:29 -07:00
esengine a7ffbd48ea feat(cli): a bare reasonix opens the terminal UI, with the options 1.x took
In 1.x a bare reasonix, or one given only session options, was the chat;
in 2.x it printed usage, so -c, -r and every muscle-memory command line
stopped at a help screen. In a terminal it now starts the terminal UI;
without one it still prints usage, since no one is there to chat with.

The terminal UI takes the rest of 1.x's session options: --effort,
--permission-mode (plan starts in plan mode, acceptEdits allows the write
tools), --yolo and --dangerously-skip-permissions, --add-dir,
--allowed-tools, --copy, --max-steps and the retired --profile. --yolo and
--dangerously-skip-permissions were listed as session options but nothing
read them. The usage text names the interactive form again.
2026-09-25 07:17:00 -07:00
esengine c7fccad57d docs: 2.x opens sessions-v4 conversations
The migration guide says 1.x 1.38.8 and later conversations now open in
2.x, where they are imported, how an import follows 1.x, and that the
machine session list shows them once imported. The roadmap marks W1b done,
which meets gate G2, and points W2 at its pull request.
2026-09-25 07:09:03 -07:00
esengine 454d1da76f feat(session): list and open the conversations 1.x keeps in sessions-v4
1.x 1.38.8 and later keep every conversation in <project>/sessions-v4, a
directory per session holding a manifest and an events.frames log of
checksummed batches of zstd-compressed JSON, with large payloads and images
in a content pool beside it. 2.x did not list them, so a person moving from
npm 1.x found their recent history gone.

internal/state/sessionv4 reads that store and nothing else: it validates
each batch's framing and checksum, treats a batch without its end record as
a write in progress, resolves pool references by digest, and folds the
message events into the conversation 1.x shows, with origin "host" read as
HostAuthored and pooled images carried as data URLs. It takes no lock and
writes no byte under the root.

Session lists now include each v4 conversation that has no import yet, at
sessions/v4-<id>.jsonl, using 1.x's own listing cache when it still matches
the log. Opening that path imports the conversation there first, recording
its source; opening it again after 1.x continued it refreshes the copy only
while 2.x has not added to it. The name carries a prefix because 1.x takes
sessions-v4/<name> as the mirror of sessions/<name>.jsonl. Verified end to
end against stores written by 1.x 1.38.10 and main-v2 HEAD, on macOS and
Windows.
2026-09-25 07:09:03 -07:00
yhh 216550cd2b ci(issues): run issue replies on the self-hosted win-gpu runner
A reply batch needs one small job, and on GitHub-hosted runners it queued
behind every branch's CI matrix. The win-gpu runner takes only jobs that
name its label, so replies start at once and CI keeps its runners.
2026-09-25 06:46:41 -07:00
yhh 631c9fc71e ci(issues): post maintainer replies as github-actions[bot]
A batch of triage replies from a maintainer's own account runs into
GitHub's content-creation limit, which answers "submitted too quickly"
and stays closed for a while. This manual workflow takes a JSON list of
replies, posts them as github-actions[bot] under the repository's quota
at a steady pace, retries a refused comment, skips a reply already there
and closes the issues marked for it.
2026-09-25 06:27:53 -07:00
esengine 1a274370df docs: sessions by 1.x version, and the 2.x release gates
The migration guide said 1.x writes schema 2 from 1.38.2 on. It does so
only through 1.38.7; 1.38.8 and later keep conversations in sessions-v4,
which 2.x does not list. The guide now says where each 1.x version keeps a
conversation and which of them 2.x opens, drops the REASONIX_SESSION_LOG
step that 2.x reading schema 2 made unnecessary, and states that 2.x never
writes a 1.x session's files.

The roadmap records the direction the version announcement left open: the
gates that end 2.x pre-release in the same release that moves npm and
Homebrew, the planned weekly release train and update channels, the 1.x
feature and format freeze with maintenance ending 3 months after general
availability or at a backstop date, and splits the session work into the
done schema 2 half and the open sessions-v4 half.
2026-09-25 06:16:50 -07:00
esengine 2c63162b6b feat(session): open 1.x schema 2 sessions, and continue them in a session of their own
1.x 1.38.2 through 1.38.7 save sessions in a schema 2 event log, an
append-only DAG of messages under named heads, and 2.x refused every one of
them with "uses schema 2; this build supports up to 1". 2.x now replays
that log read-only and materializes the head 1.x would open: parent chain,
patches, redactions and system override applied, and 1.x's origin "host"
read as HostAuthored so injected context stays out of the transcript and
the preview.

2.x never writes the log or anything beside it. A save that finds the
transcript unchanged stops with ErrSessionLogUnchanged, so opening a 1.x
conversation touches no file. Before a turn starts, and at any save that
has more than 1.x holds, the controller moves the conversation to a new
2.x session with the same title and says so; 1.x keeps the original byte
for byte. Verified against sessions written by a real 1.38.7 build.

Host-authored messages no longer count as a turn or become the preview,
and the event-log probe moves to its own file.
2026-09-25 06:15:24 -07:00
esengine 0ed7b4b5a1 docs: a 1.x to 2.x migration guide and the 2.x roadmap
docs/MIGRATING.md was the 0.x to 1.0 guide and told a reader on the 2.x
default branch that main-v2 was the active line. It now covers moving from
1.x to 2.x: what the two lines share under one Reasonix home, which sessions
open where now that 1.x 1.38.2 writes a schema 2 log 2.x cannot read, the
REASONIX_SESSION_LOG=v1 switch that keeps a machine running both usable,
the commands that moved, and the steps to run 2.x beside 1.x.

docs/ROADMAP.md is the roadmap the version announcement (#10748) promised:
how 2.x ships, the terminal takeover that gates the release that moves npm
and Homebrew to 2.x, the remaining workstreams, and the decisions still
open. Both are owned by @esengine, and README links to them. The guide's
old prose debt leaves the repolint baseline.
2026-09-25 05:32:37 -07:00
esengine 4391487d8f fix(store): a 1.x turns sidecar is not listed as a conversation
1.x and 2.x share one session directory, and 1.x writes <id>.turns.jsonl
beside each transcript. 2.x classified it by its .jsonl ending and listed
it as a conversation of its own, so a machine that ran both showed a ghost
session for every 1.x conversation.
2026-09-25 05:28:44 -07:00
yhh dae42027cc fix(anthropic): a stream error before any output is a recoverable interrupt
An Anthropic `error` event (overloaded_error and the like) that arrives
before anything was emitted left the provider as a plain
StreamPayloadError, so the Agent never replayed the attempt, while the
OpenAI provider already promotes the same pre-output payload error to
StreamInterrupt(upstream_error) (#10719). Apply that rule here: the read
loop's sink records whether a chunk reached the consumer, and only an
error with nothing emitted becomes an interruption. After output a
replay would show it twice, so it stays a payload error; the payload
identity survives the wrap for errors.As.

sendChunk moves into the sink so anthropic.go does not grow past its
size budget.
2026-09-25 05:04:13 -07:00
yhh a50a6b5c44 fix(projectiondb): cap the WAL with journal_size_limit on every connection
A disk catalog runs in WAL mode with no journal_size_limit, so after a
burst of writes the -wal file keeps its high-water size for as long as
the database is open (#10714). The limit is a per-connection pragma and
the pool opens up to MaxOpenConns connections, so it rides the DSN the
way busy_timeout and foreign_keys do rather than a single Exec that
would reach only one of them. 4 MiB leaves ordinary checkpoints alone.
2026-09-25 05:04:13 -07:00
yhh d318232358 fix(provider): root object tool schemas always carry "required": []
Relays that re-serialize tool schemas emit `required: null` for an
object schema that omits "required", and the upstream validator rejects
the whole request with "null is not of type 'array'" (#10614).
context_budget is one such schema on this branch, and any MCP or plugin
tool may be another, so the fix sits in canonicalization rather than in
each tool: a root object schema gets an empty array when it has none,
including after an invalid non-array value is dropped.

This changes the tool block of the cache-stable prefix once (golden
ToolsHash/PrefixHash regenerated); the budgets rise by the bytes the
array costs: provider-visible 3700 -> 3708 tokens, built-in 6767 -> 6787.
2026-09-25 05:04:13 -07:00
esengine 4b9ec92981 feat(tui): paste a screenshot with Ctrl+V, as 1.x did
Ctrl+V (Alt+V on Windows, whose terminals keep Ctrl+V for their own text
paste) reads the clipboard of the machine the terminal runs on. An image
goes to the kernel through /attachments and stands in the composer as
[image #N], sent as the @-reference it was stored under; without one the
clipboard's text comes in the way a terminal paste would, which is also
how a console without bracketed paste gets a paste folded.

internal/platform/clipimage reads the image through what each platform
ships: osascript, Windows PowerShell, wl-paste or xclip. A tool that cannot
say what the clipboard holds counts as offering no image rather than being
told apart by the words of its error, since either way the paste falls back
to text.
2026-09-25 04:42:45 -07:00
esengine e83e2b156e fix(tui): keep inline history in order, and say the remaining notices in the user's language
The inline renderer places a print by scrolling it in above a frame it
takes to sit at the bottom of the screen, and places each print from where
its last redraw left the frame. A restored conversation arrived as dozens
of prints with no redraw between them, on a screen the frame did not yet
fill, and landed interleaved with lines lost. A commit now goes out as one
print, split into pieces that fit above the frame with a redraw between
them, and a replay first fills the screen so the frame starts at the bottom.

The cancelled-turn, compaction, shell-busy, stream-reload and declined-
question lines now come from the i18n catalog.
2026-09-25 04:42:45 -07:00
esengine 3579dbbd39 feat(tui): open long shell output with Ctrl+B, and scroll a selection held at the edge
Full screen, a shell call's output shows its ten-line preview with a
"more lines (Ctrl+B)" row, as in 1.x: Ctrl+B opens the newest one up to
200 lines and shuts it again, and a click on that row does the same. A
settled block keeps the row it draws, so opening it redraws only that
block. A drag held against the viewport's top or bottom row keeps
scrolling the selection until it leaves the edge or the transcript ends.
2026-09-25 04:42:45 -07:00
esengine 4ad09c6656 feat(tui): pick a session to resume, and notify when the turn needs you
A bare -r opens the saved-session picker on the first frame, and /resume
opens it mid-chat, as in 1.x: typing filters, the open session is marked
and skipped by default, and choosing one binds the runtime to it through
/resume and reads it back. /resume and /mouse are offered in the slash
menu beside the kernel's commands. /sessions dates each entry, and moves
to its own file.

The TUI's event sink now carries the configured system notifications, as
the 1.x chat screen's did.
2026-09-25 04:42:45 -07:00
esengine 6cd9508902 feat(tui): take the full screen by default, with 1.x's scrollbar and mouse
The transcript is drawn through its own viewport, as 1.x drew it: a
scrollbar in the last column, the wheel, PgUp/PgDn and Ctrl+Home/End,
dragging the thumb, and drag-to-select that copies on release. Settled rows
keep how to draw them, so a resize rewraps them. /mouse hands the mouse
back to the terminal for its own selection, and --inline writes the
conversation into the terminal's scrollback instead.
2026-09-25 04:42:45 -07:00
esengine 7519957fbf feat(tui): look and behave like the 1.x chat screen
Users coming from 1.x meet the screen they know: the banner and tip, an
accent-framed composer with the ❯ prompt, the mode tag on its colored
ground, the footer's model/effort group and its cache, context, fold,
balance and cost row, a "本轮" usage line under each request, the ▎
thought marker, the ⎿ tool summaries, the To-dos panel, and approvals and
questions as selectable panels in place of the composer. Labels come from
the shared i18n catalog, so the screen speaks the configured language.

Shift+Tab steps ask, auto and plan; Ctrl+Y toggles YOLO.
2026-09-25 04:42:45 -07:00
esengine 6146fb5086 feat(tui): a welcome card, a framed composer, a spinner, and cards for prompts
The terminal UI reads as a product rather than a log: a welcome card names
the model and workspace, the composer sits between rules with a placeholder,
a running turn shows what it is doing and for how long, the status bar
carries a mode badge and a context gauge, approvals and questions are boxed
with their keys standing out, and the user's own lines are a band.

The theme is now read from config before the program starts; it was left at
the built-in default.

A message frame that arrives after the call which closed its answer now
settles that answer instead of appending a second copy, which printed every
answer before a call twice.
2026-09-25 04:42:45 -07:00
yhh 186e75abb6 feat(studio): 模型偏好 is one table of uses, the models doing them and their services
The page listed every model as a card to click for the primary model,
then drew the per-task roles as a second set of dropdown cards below it,
so choosing what runs where meant reading two layouts that said the same
kind of thing. The 1.x desktop app put this in one table, and #10761 asks
for its layout back.

Each use is now a row: what it is for, a select of the models that can do
it grouped by service, and the service that model goes through. The
default model's row also lists what that model declares: image input,
adjustable reasoning, context size. Roles that follow the default say so, and
decision says it has no source rather than offering chat models. The two
settings rows it replaces become one, 按用途选择模型; reasoning effort
and context upkeep stay below it.

The card list and the role picker are gone with their styles. The
services page now works out its default selection while rendering, so
it no longer flashes an empty detail before the selection lands.
2026-09-25 02:58:19 -07:00
yhh d8bd8c763c fix(studio): the selected model service drops the accent line that bent around its corner 2026-09-25 02:47:38 -07:00
yhh a11c9bf956 feat(studio): model services get their own page, a list beside the one being edited
Settings kept every model setting on one long page: the source list with
an editor that unfolded inside it, the primary-model list below that, and
roles and context after. Adding or changing a service meant finding the
right row, opening it, and scrolling past the rest. The 1.x desktop app
split this into 模型服务 and 模型偏好 with a list-and-detail layout, and
users asked for that back (#10761).

模型服务 is now its own section. Services are listed on the left, with a
search once there are six or more and 添加模型服务 at the foot; the
selected service's fields are open on the right without a second click,
and adding a service takes the right side while the list stays in
place. The page opens on the service in use, or on the one the composer
sent the user to declare effort levels for. At narrow widths the list
stacks above the detail.

The remaining section is renamed 模型偏好 and keeps the primary model,
reasoning effort, per-task models and context upkeep. The composer's
manage-models entry, its effort-declare link and the palette entry now
open 模型服务.
2026-09-25 02:43:49 -07:00
yhh eab809e16f fix(studio): one name per approval mode, and the permission menu fits a phone
The composer's permission menu and Settings each kept their own table of
the four approval modes, and they drifted: the menu called ask 逐项确认
and auto 自动继续, Settings 询问 and 自动, the shelf 自动批准, and the two
lists ran in different orders. Users read the menu's 逐项确认 as a mode
Settings did not have.

approvals.ts is now the one table. Menu, Settings and shelf all say
不询问 / 询问 / 自动批准 / 全部放行, ordered from strictest to most
permissive, with the same descriptions.

In a narrow conversation pane the menu opened rightward from its trigger
and ran off the screen. There it now spans the toolbar row instead.
2026-09-25 02:19:45 -07:00
esengine 5ff9d7a0f5 feat(tui): questions, completion, the task list, and fixes from real runs
- Questions: an open ask is answered one question at a time. A digit
  answers a single choice or toggles a multi choice, typed text joins the
  picks as an answer no option offered, Esc declines with nothing
  selected. The card left in the scrollback says what was answered.
- Completion: `/` at the start of a line and an `@` token open the
  kernel's menu as they are typed, Tab asks anywhere. Offsets cross
  /complete in UTF-16 both ways, so a line with CJK before the token
  splices where the kernel meant; a directory keeps the menu open a level
  down.
- The task list is re-read from /todos when the kernel says it moved and
  drawn while it has work left; a finished list goes away.

Found running against a real model: a thought-only step and whitespace-only
answers printed an empty speaker header; option and menu rows lost their
indent to the row-shortening helper; the receipt read the verdict "done"
as unfinished.
2026-09-25 00:27:00 -07:00
esengine ed499c4cac feat(control): the model answers a command the user ran
A `!` command ran and was shown, and the model never saw it: the next turn
had no idea what the user had just looked at. RunShell now follows the
command with a turn whose input is the command, its exit code, any launch
error and its output, so the model responds to it the way it would to a
line typed to it.

The output is bounded at 24 KiB, cut from the middle with the cut said, so
a large listing neither fills the context nor reaches the model looking
complete. A command the user stopped is not followed by a turn, and neither
is one run where no model is attached. The ACP `!` prompt now ends after
that answer.
2026-09-25 00:27:00 -07:00
yhh 27f247bc39 fix(workbench): opening a folder reads that folder, and a failure says so in the file list
Expanding a folder in the explorer walked the whole workspace and kept
the entries whose parent matched, so one folder the process could not
read anywhere in the tree failed every expansion with a 500, and a large
tree made every click slow; the root listing, which skips into nothing,
kept working, which is why the list drew and its folders never opened.
The explorer drew that error in the file view's slot, which the docked
file list hides, so the click looked like it did nothing.

A folder listing now reads that folder alone, and its real path is held
inside the workspace so a link cannot list what is outside it. A search
still walks the tree and passes over a folder it cannot read. A missing
folder answers 404 workspace.file_missing, a linked-out one 400.

The explorer now shows its own failures in the file list. Reloading the
tree, which happens when the changed files change after a turn, reads
the folders that were open again, where it used to fold them all shut.

Checked on Windows against a workspace with a folder denied by ACL: the
folders beside it open, the denied one reports in the list, and an open
folder stays open through a reload.
2026-09-25 00:22:48 -07:00
esengine c5b651bea7 feat(tui): reasonix tui, a terminal UI on the in-process kernel
The second layer of the terminal UI: the program a person operates, on
top of the client, stream and transcript.

- `reasonix tui [--model] [--preset] [--dir] [-c] [-r QUERY] [prompt]`
  assembles the kernel the way `reasonix serve` does, adopts it into a hub
  and drives it through Hub.InProcessClient: no port is opened. The kernel's
  logging goes to ~/.reasonix/logs/tui.log while the UI owns the screen.
- Rendering keeps the terminal's scrollback: every settled row goes above
  the program in the order it happened, and a streaming answer's finished
  Markdown blocks go as soon as they are final (nothing inside an open
  fence). What is still changing stays in a live region capped at half the
  screen, each row cut short of the terminal width so none wraps into a row
  the renderer does not know it drew.
- Input: Enter sends, or queues while a turn runs; Ctrl+S steers; Ctrl+J
  (or Shift+Enter where the terminal reports it) is a newline; Esc
  interrupts; `!` on an empty line is shell mode; Shift+Tab cycles the
  approval mode; Ctrl+C twice or Ctrl+D on an empty line quits. Large pastes
  fold into a token and go to the model whole. Approvals take only the
  answers the host says it honours; plan approvals map to start, revise or
  leave.
- The transcript seats a turn on the message it began on, the way Studio
  does: a queued follow-up moves to where its turn started, steered input
  waits for its steer event, and another client's turn is drawn once from
  the kernel's text. Restore rebuilds from /history for -c/-r and after a
  stream gap.

cli.go's argv normalisation moves into normalizeCommand so the dispatch
switch keeps one case per command within its complexity budget, and `tui`
joins the shell-completion registry.
2026-09-24 23:45:53 -07:00
esengine bf2984ae8c refactor(termrender): the terminal visual layer is its own package
The theme, colour profile, Markdown/math renderer, tool cards, diff view,
assistant block and clipboard writes move out of internal/frontend/cli into
internal/frontend/termrender, so a terminal frontend other than cli can draw
the same bytes without importing cli. cli now calls termrender for every
style, width, box and Markdown helper; view_helpers.go stays in cli.

termrender exports the surface a frontend draws with: Bold/Dim/Green/Red/
Yellow/Accent/Reverse, VisibleWidth/PadRight/Boxed, ConfigureTheme,
ConfigureThemeFromConfig(ForTTYOutput), ActiveTheme/ThemeName and the
ThemeStyle/ThemeFg/ThemeLipColor/ApplyTextareaTheme helpers over an exported
Palette/Color, RenderMarkdown and MarkdownRenderer, AssistantBlock, ToolCard/
ToolDot/ToolHead/ToolArg/ToolDisplayName, DiffBlock/DiffStat, CopyToClipboard
with ClipboardCopyMsg, ANSIConsoleReady and SetColorProfile.

Transcript helpers nothing calls (edgeScrollDir, selSpan, scrollbarThumb)
are dropped with their tests, and the clipboard message loses the seq and
status-hint fields no caller set. The metadata-command test now asserts that
version/help never reach the TTY theme setup, through a cli-side seam, since
the terminal probe it used to swap is termrender's.

The repolint baseline diff is the moved files' prior essay debt carried to
their new paths (mathnode.go, md.go, width_test.go; transcript.go's is paid
off), plus orphan budget for the eight exports whose consumer, the new terminal
UI, is not in the tree yet (ToolCard, DiffBlock, AssistantBlock,
RenderMarkdown, CopyToClipboard, ApplyTextareaTheme, ThemeName) or which only
a cli test drives (SetColorProfile). Wiring the UI reclaims that budget.
2026-09-24 23:45:52 -07:00
yhh 8a3c25322b fix(sandbox): a backtick escapes only inside double quotes when null redirects are normalized
normalizeNullRedirects treated a backtick as an escape inside any quote,
so in echo 'a `' >nul the backtick swallowed the closing single quote,
the string never ended, and the real >nul after it was left alone; under
bash that writes a file named nul. Single-quoted text is literal in both
bash and PowerShell, and the backtick is PowerShell's escape only inside
double quotes, which is where the backslash case already applied.

The new cases also cover the escaped-quote branches, which had no test:
the sandbox package fell to 77.6% against its 78.1% coverage floor on
the lint job, three statements short.
2026-09-24 23:27:50 -07:00
yhh 4c38a9e639 ci: the pull-request race sweep names jobs where it now lives
fc3a44dbc moved jobs from internal/runtime to internal/tools, and the
pull-request race step still listed ./internal/runtime/jobs/..., so every
pull request's race job failed with setup failed on a directory that no
longer exists. Pushes run the full ./... sweep and never saw it. Every
other path the workflows and the Makefile name still exists.
2026-09-24 23:27:49 -07:00
64253ebe73 fix(pricing): bill Chinese public holidays as DeepSeek off-peak (#10758)
* fix(pricing): bill Chinese public holidays as DeepSeek off-peak

Port of the main-v2 holiday fix (#10756) to the studio pricing package,
which moved from internal/billing to internal/contract/pricing and now
carries the peak schedule in PeakWindow. The holiday table rides the
same struct field pattern as WeekendOffPeak: PeakWindow.HolidayOffPeak
lists Beijing-local dates off the State Council 2026 notice, and
IsPeak flattens them like weekends. Make-up workdays (调休) stay
off-peak: they are calendar weekends, which is how DeepSeek rules them.

* chore(pricing): keep the holiday comments within the repo's comment rules

English only and within the essay budget; no behaviour change.

---------

Co-authored-by: nanami-0713 <[email protected]>
Co-authored-by: yhh <[email protected]>
2026-09-24 23:09:17 -07:00
esengine d58a02719f feat(tui): the terminal UI's client, event stream and transcript
The first layer of the new terminal UI, which drives a session through the
same HTTP/SSE contract Studio uses and imports nothing from the controller:

- Client: typed calls for the routes a chat needs; a refusal carries the
  kernel's code (APIError), never only its sentence.
- Subscribe: /events with Studio's ordering — numbered frames delivered once
  and in order, a jump filled from /events/replay before the frame that
  revealed it, what the replay no longer holds reported as a gap, a
  backwards count read as a restarted stream, a reconnect resumed with
  Last-Event-ID.
- Transcript: the fold of Studio's session reducer — the settled message
  repairs the streamed answer, one card per call with sub-agent calls under
  their task, prompts settled here or by the kernel's receipt, pending input
  moved to where the turn read it, and turn_done saying which of four ways
  the turn ended.

An end-to-end test drives a real kernel behind an in-process hub through a
turn that streams, waits on an approval answered through the client, runs a
command and ends settled.
2026-09-24 23:05:31 -07:00
esengine e86fd8f887 feat(acp): a ! prompt runs the user's own command
The editor is a frontend in the kernel's process, like the terminal UI
the in-process serve client exists for. A prompt that starts with `!` now
runs through RunShell instead of going to the model: it streams back as a
tool call and ends the prompt when the command finishes; cancelling the
prompt cancels the command.
2026-09-24 23:05:31 -07:00
esengine 72e5d8ddb9 feat(serve): an in-process client, and ! commands for the frontend that holds it
Hub.InProcessClient reaches the hub's routes with no listener: a frontend
in the kernel's own process opens no port, so there is nothing on the
machine or the network to authenticate against. Responses stream through a
pipe as the handler flushes, so /events works through it.

A `!` line on /submit is the user's own shell command, which runs
unconfined. It stays refused off a socket (shell.unavailable_over_http);
through the in-process transport it runs. The transport marks the request
context itself, and no request read from a socket passes through it, so the
grant rests on which way the request came in rather than on anything it
carries.
2026-09-24 23:05:31 -07:00
esengine 34da60673d fix(studio): a stopped background job reads as stopped, not as a success
Every ended job drew the same green pip and printed the kernel's raw status
word. Only done is green; failed is red, killed and interrupted are neutral,
and each is labelled in the reader's language.
2026-09-24 22:50:49 -07:00
esengine 74ae5a3373 fix(studio): background jobs can be stopped, and the deck works on a phone
The background-jobs deck listed jobs with no way to stop one: the controller
had CancelJob, but no port carried it and serve had no route. BackgroundJobs
joins SessionAPI, POST /jobs/{id}/cancel drives it and refuses a job that is no
longer running as job.not_running, and each running row gets a stop button.

On touch the deck could not be closed: it opened on :hover and :focus-within,
which a tap leaves set, so the chip's own toggle had no effect. Hover opening
is now limited to hover-capable pointers, and a press outside or Escape closes
it; the context card gets the same treatment.

The meter rail did not fit a phone: the chip ran past the edge, and tapping it
scrolled the overflow-hidden .app sideways, dragging the composer with it.
Narrow panes drop the sparkline, wallet, speed and (narrowest) cost readings,
the two popovers position against the rail instead of their chips, and .app
clips rather than hides so focus can no longer scroll it.

perf/composer.mjs counts only visible peers as overlapping the model button.
2026-09-24 22:50:48 -07:00
yhh bf4edd280d fix(sysproxy): honour WinINET bypass wildcards and the implicit loopback bypass
bypassed() matched only exact entries and a leading "*". The bypass list
Clash, v2rayN and xray write by default is `<local>;localhost;127.*;10.*;
...;192.168.*`, so every trailing-wildcard entry missed. It also lacked
WinINET's implicit rule that loopback goes direct unless `<-loopback>` is
listed.

In the default auto mode, on Windows with such a system proxy, requests to
local and LAN model endpoints went through the proxy: Ollama or LM Studio
on 127.0.0.1, vLLM on 192.168.x. That added a hop, failed outright when the
proxy routed to a remote exit, and made a cancelled stream keep the local
model generating. Cancel closed only the client-to-proxy hop, and the proxy
held its upstream connection open.

"*" is now a wildcard anywhere in an entry. Only "*" is special, and
brackets are trimmed from IPv6 entries. localhost, 127/8 and ::1 bypass
unless `<-loopback>` is present. TestBypassed gains the trailing-wildcard,
near-miss, mid-pattern and loopback cases; it fails on 8 of them without
the fix. The same change lands on main-v2.
2026-09-24 22:32:30 -07:00
esengine 7ac19b90c5 feat(release): npm and Homebrew follow the Studio line
The CLI had no install channel on the 2.x line: release-studio.yml builds the
CLI archives for Studio's remote installs, and npm's `reasonix` and the
Homebrew tap were published only by the retired 1.x release machinery. The
2.x line now takes both over, and 1.x no longer publishes the CLI.

A cli-channels job runs after the release is published:
- npm: the existing npm/build.mjs builds the per-platform packages and the
  launcher and publishes them through publish.mjs's staged dist-tag flow. A
  2.x version outranks every 1.x one, so `latest` moves to it; a candidate
  goes to `next`.
- Homebrew: scripts/render-brew-cask.sh writes the cask from the digests in
  the release's own SHA256SUMS — a missing digest fails the render rather
  than shipping an empty one — and the job pushes it to
  esengine/homebrew-reasonix. Candidates leave the tap alone, since brew has
  no prerelease channel.
2026-09-24 22:20:04 -07:00
esengine 2da273ba7d fix(cli): a double-clicked reasonix.exe says where Studio is instead of vanishing
The Studio release carries the CLI archive too, listed first. Double-clicked
from Explorer, bare argv printed the usage and exited, and the console window
Windows opened for it closed with it, which read as a crash. When this
process is the only one attached to its console, it now names Studio's
installer and waits for Enter; from a shell nothing changes.

The usage examples dropped bare `reasonix`, `--continue` and `--resume`, which
only print the usage on this line.

LAYOUT.md: three list items split to stay under the prose width.
2026-09-24 22:06:48 -07:00
esengine 0099490a73 test(goal): the 24-round goal test waits as long as that work takes
It ran into the default five-second wait on a slow Windows runner.
2026-09-24 21:46:32 -07:00
esengine c9ebc58155 fix(studio): a warm pane is not a selection, and removing a project is not a close
Every workspace holding a warm runtime drew a raised box, so four rows read as
four selections and people asked how to close them; the kernel already retires
idle panes at capacity. Only the workspace of the active session keeps its
emphasis, and a session held by a pane keeps its pip.

The project row's × removed the project from the list but sat next to + where
it read as "close". It is now a ··· menu (workspace.menu) whose one item leads
to the existing confirm.
2026-09-24 21:39:56 -07:00
esengine 4179e1e524 test(jobs): the restart attribution test runs without an OS sandbox
The coverage job installs no bubblewrap, so an enforced bash sandbox refused
the background job and the test read no job at all. It measures how a job's
end is attributed, not where it runs.
2026-09-24 21:35:05 -07:00
esengine 1ec1f1949e fix(studio): the rest of what the perf guards found once they reached the real layout
Product:
- Tool cards wrap their head in details.tool-disclosure, so the hit highlight
  and the failed-call peek never matched them; both selectors now reach it.
- Light --faint fell below AA on every raised ground and overrode the tiered
  tokens; darkened by lightness only. Segments and the new-task shortcut sit on
  --overlay and read --muted. Hard-coded crumb separator, delta and failed
  state colours now use tokens; the approval tool tag uses --warn-ink, the
  running step reads a darker --net, and the failure pill mixes its ink
  towards --text so it clears AA in both schemes.
- The local workspace row's remove control is the same Cross icon the remote
  rows already draw, not a text glyph.

Guards: retargeted to the current markup (model picker trigger, tool
disclosure head, context card MCP rows, wallpaper section, queue and plan
controls), dropped checks for the removed task board and focus mode, and the
wallpaper reach is measured with the dim overlay cleared. All 22 exit 0 and
selfcheck shows each can still go red.
2026-09-24 21:30:30 -07:00
esengine 01e04ea614 test(isolation): compare the applied file without the platform's line endings
On Windows, git applies the isolated change under core.autocrlf, so the
file lands with CRLF; the best-of-n effect test already compares that way.
2026-09-24 21:17:35 -07:00
esengine 26294ae811 fix(control): the headless Run holds the turn gate
Run — the synchronous turn `reasonix run` drives — never took the turn gate
the guarded paths check. A turn admitted through the gate, such as the
inbox delivering a background job's end, could therefore start on the same
executor while Run was mid-turn, and the two wrote the task state together:
the race TestEffectRestartNamesWhyABackgroundJobEnded tripped on CI.

Run now admits through runSynchronousTurn, as RunTurn does, and waits out a
turn already in flight rather than failing: after a resume the host may
dispatch a job's interruption before the caller's first Run. Its turn
completion is recorded once, by the admission, instead of twice. Cancel()
now reaches a headless turn as it does any other.

The restart test's scripted model started a new job on any turn it did not
recognise, which the interruption delivery now visibly is; it only
acknowledges such turns.
2026-09-24 21:17:35 -07:00
esengine 91faa67c1d fix(studio): what a guard reaching the real layout found wrong with it
Run against studio's own stylesheet, the browser guards reached parts of the
window they had never measured.

- A collapsed column is inert, not only narrow. The rail was inert only in
  focus mode and the workbench never, so Tab walked into controls nobody
  could see and scrolled a column of zero width to show them. Both are inert
  while collapsed, and the workbench stops painting once its collapse ends.
- Focus mode is gone. Its only way in was removed with the side column in
  the studio redesign; the state, the Escape branch, the action id and the
  layout notes that described it are removed with it.
- The queue's withdraw control lost its enlarged hit area when it moved from
  the transcript into the queue panel: 19px tall. It gets the outward
  ::after back, 29px.
- Switching to the run analysis gave the transcript and the docked workbench
  the same view-transition name, so the browser aborted the transition and
  the rejection went uncaught. The docked workbench no longer takes the
  name; it keeps it when it is the tab being switched to. A transition the
  browser skips is not an error.
- The studio composer's own chrome is taller than the one the queue's
  shared ceiling was set against, so 64 queued lines left the transcript at
  36-39% of the pane. Studio lowers the ceiling (28vh, 19vh when short) and
  the transcript stays above 40%.
2026-09-24 21:07:32 -07:00
esengine de71e8df4b test(egress): keep the guard's comment on its test, skip the socket test on Windows
The egress grant test sat between TestEverySubjectSensitiveToolActuallyAsksSomething
and its comment, which read as one six-line block. Windows has no /tmp and no
sandbox that reaches the proxy on a socket.
2026-09-24 20:46:51 -07:00
esengine 3683d0a215 style(sessionstore): gofmt save.go
The trailing blank line failed the gofmt step, which stops the Linux and
macOS test jobs before a single test runs.
2026-09-24 20:46:51 -07:00
yhh ade58d1610 docs(readme): drop the embedded logo animation video 2026-09-24 20:31:41 -07:00
esengine ebb278cec1 feat(sandbox): allowed_domains is enforced on Linux too
On Linux the list only warned: bubblewrap either kept the command's network
namespace (no network at all) or dropped it (all of it), and a separate
namespace cannot reach a proxy on the host's loopback.

The command now keeps its namespace, which has no route out. The egress
proxy also listens on a Unix socket under the user's cache, mounted
read-only into the sandbox, and the host binary runs inside the namespace
as the egress bridge: it listens on the namespace's 127.0.0.1:<proxy port>,
carries each connection to the socket, runs the command, and returns its
exit status as a shell would. HTTP_PROXY and friends need no change. The
bridge is an entry of whatever binary imports the sandbox package, chosen
by an environment variable it clears before the command starts, so no
helper has to be shipped or found.

The host's own loopback is out of reach from the namespace, so a local
proxy cannot be used to go around the list. A route with no socket keeps
the namespace shut rather than falling back to open.

The bridge's forwarding and exit status are tested on any Unix; the
namespace boundary and the end-to-end refusal and approval run where
bubblewrap is usable (Linux CI).
2026-09-24 20:26:16 -07:00
esengine a250e31399 feat(sandbox): a host outside allowed_domains is put to the user
A host the egress list does not name was refused outright. Where the
frontend can ask, the proxy now holds the connection and asks through a
network_egress approval whose subject is the host: allow once (for that
command), allow for the session (that host only), or decline. A declined
host reaches the model as declined-by-user rather than blocked-by-allowlist.

- network_egress is declared subject-sensitive in permission, so a person
  answers whatever the approval mode, an unattended session refuses, and a
  session grant names the host it covers. Before that declaration a grant
  for one host answered for every host after it.
- The controller's sandbox approver also answers ApproveEgress; bash finds
  it on the tool context the escape approver already rides, so nothing
  below the controller grew a new channel. Headless runs have none and keep
  refusing.
- Each command asks once per host, one question at a time, and a token's
  asker ends with the command. Denied hosts are never asked.
2026-09-24 20:26:16 -07:00
esengine cb5ce6adf3 test(perf): the idle guard watches studio's run signal, and judges a stroke by its drawing
The guard asserted that the border glow runs while a turn runs; studio's
design removed that glow, and its running signal is the run mark's R being
traced (studio-r-run). The three directions stay: still at rest, moving
while running, still again after.

It also flagged the mark's straight stroke as drawn on nothing, because an
SVG shape's box leaves out its stroke and a straight line has no area. An
SVG shape is now judged by its drawing's box.
2026-09-24 20:26:16 -07:00
esengine fb115ebeb7 fix(studio): the sidebar's filled controls read as a layer in light, and its row menu is quiet rather than absent
In the light theme the new-session button and the session filter segments
were separated from the sidebar by fill alone, a 1.8 step in OKLab
lightness, and the button had its border removed: neither read as a layer.
Both now mix 3% of the text colour into their fill, in light only.

Each session row reserved 34px of its title for the menu button and drew
the button at zero opacity until hover, so every title was cut short for a
control nobody could see. It now rests at 35% and comes up on hover, focus
and selection as before.
2026-09-24 20:26:16 -07:00
esengine bc599b2d39 fix(studio): a narrow question card leads with its answer, and a disabled mode button takes no pointer
In a conversation narrower than 460px the ask card's actions stack, and they
stacked in DOM order: "reply without choosing" above the primary answer.
The approval card already leads with its main action there, and the cards
guard holds both; the primary button now takes the first row.

The refine button is disabled until there is a draft, yet its icon still
showed a pointer and it lifted on hover, reading as something to press.
A disabled mode button keeps its tooltip but takes the default cursor and
does not lift.
2026-09-24 20:26:16 -07:00
esengine 45f28b6778 feat(sandbox): bash egress can be limited to named domains
Network in the bash sandbox was one bit: open to everything, or shut. With
[sandbox] allowed_domains set, bash's external traffic goes through a
host-side proxy (internal/safety/egress) that admits only the listed hosts;
denied_domains outrank them.

- The proxy serves CONNECT and absolute-URI HTTP on loopback, decides from
  the requested name, refuses names that resolve only to loopback,
  link-local, metadata or this host's own addresses, and chains through the
  upstream HTTP or SOCKS5 proxy the environment names.
- macOS: the Seatbelt profile shuts every destination but loopback, Unix
  sockets and the proxy port, and closes the loopback ports of the proxies
  the environment and the system proxy settings name. Loopback stays open
  so tests and dev servers keep working. Bind, inbound and outbound are
  separate grants, because (local ip "localhost:*") on network* matches the
  local end of every outbound connection and admits all of them.
- Each confined launch carries a token as proxy userinfo; what the proxy
  refused under it is appended to the bash result by the host, so a policy
  refusal reaches the model as one instead of a bare connection error.
- The list only narrows open network: it applies where network is on and
  the sandbox confines; a repo may set one only where the user has not, and
  its denials only add to the user's.
- Linux and Windows warn that the list is not enforced there.

Known gaps, stated where they live: a host proxy on a loopback port nobody
declared stays reachable, name lookups still reach the system resolver,
the decision reads the requested name (domain fronting is not caught), and
MCP servers are not routed through the proxy.

internal/safety/egress is declared sensitive, with its coverage floor.
2026-09-24 20:26:16 -07:00
esengine f3274a3247 feat(studio): a failed request floats above the composer instead of pushing it
The window's error bar sat in the main column under the panes: every failed
request pushed the composer up by the bar's height, as a full-width strip at
the far end of the screen from most of what fails.

It now floats. The pane in front carries it on a zero-height anchor pinned to
the top edge of its composer card, at the right: it never moves the
conversation, rises with the card as the prompt grows, and stays clear of
the send button. With no pane on screen it takes the main area's corner. Its
fill is mixed into the surface, since it now sits over content.

perf/errbar.mjs holds the geometry at two widths: the bar appears and takes
clicks, sits above the card, leaves send clickable, and does not move the
composer. It reaches the bar the way a real failure does, through a hub
method the bench can now refuse. Against the old in-flow bar it fails on the
composer moving 45px and on the bar sitting below the card.
2026-09-24 20:26:16 -07:00
esengine 0c02f53d68 fix(perf): the bench loads studio.css, as the window does
main.tsx has loaded studio.css since the studio interface landed, and the
perf harness never followed: every browser guard since has measured a page
without the studio layout, with the composer at the top of the window and
the sidebar unstyled. Run against the real layout, fifteen guards still
report failures; four of them (cards, idle, press, surface) were passing
only because the layout was missing, and contrast and pick stop failing.
2026-09-24 20:26:16 -07:00
esengine e07df2ed87 fix(config): a repo can no longer grant itself host authorities
host_authorities is user-global: loadForRoot holds the user's value and puts
it back after the project reasonix.toml merges. The held value was the slice
header, and decoding the project file writes into the backing array the
user's decode left behind, so the held copy read back the repo's list. A
cloned repo declaring host_authorities = ["docker"] got the container daemon
socket inside the bash sandbox — with an explicit user grant and with the
default one alike.

The held value is now a copy.
2026-09-24 20:26:16 -07:00
esengine 881dc68289 feat(evidence): a rewritten pytest case is reported like a rewritten Go test
RewrittenTestCriteria read Go test files only, so a Python run that moved
its own expectation reached the completion summary, the best_of_n judge and
the integrity bench with a clean record.

pytest modules are now read by pytest's default collection contract
(test_*.py / *_test.py, module-level test* functions, test* methods of
Test* classes). Bodies come from Python's own line structure — the
indentation stack, bracket joining, whole strings — and compare as token
signatures, so reformatting, comments, docstrings and formatter trailing
commas do not read as rewrites while a changed expectation or parametrize
case does. Source that does not lex claims nothing.

Detection only: holding a criterion is still Go-only, now enforced at
captureRewrittenCriteria too, because no backend re-runs a captured Python
test and holding one would owe the turn a baseline run nothing can settle.
2026-09-24 20:26:15 -07:00
esengine 9a78e92169 fix(studio): the phone card no longer asks a shut door for a code
Opening the card refreshed the share status and, in the same commit, minted
a pairing code on the status it already held. When phone access had been
turned off elsewhere since, the mint reached the kernel first and was
refused as share.closed, and the window's error bar told a person who had
only opened the card to "turn phone access on first".

Opening now waits for the read and mints only when the door is open and no
live code is held. A failure inside the card is said inside the card, under
its header, and clears when the card closes, rather than landing in the
window's error bar at the far end of the screen.
2026-09-24 20:26:15 -07:00
esengine 8814848fb4 feat(bestof): the judge reads each attempt's host record, not only its diff
Each candidate kernel already emitted a CompletionSummary at turn end —
verdict, checks passed/failed/suppressed, review, gaps, and existing tests
it rewrote or removed — but the candidate sink forwarded usage only, so the
judge compared attempts on their final message and diff alone.

candidateSink now keeps the last summary per attempt; the judge sees it as
a host record the attempt cannot write, and the parent's report carries it
per attempt. A failed check counts against an attempt, and one that
rewrote tests the task did not ask to change is suspect however green.
2026-09-24 20:26:15 -07:00
esengine 2d23da04fb test(editor): editor discovery tests no longer depend on what the Mac has installed
Discovery searched /Applications as a literal, so the tests that stand for a
machine with no editor could not build one: on a Mac with VS Code installed
TestDiscoverRefusesWithAnIdentityWhenNothingIsInstalled found it,
TestInstalledPathsAreSearchedBeforePath found it before the placed copy, and
serve's TestEditorOpenSeparatesMissingFromFailedToStart launched it against
a controller with no workspace and got a 500. CI runners have no editors, so
all three were green there and red on a developer's machine.

Discovery now reads the machine through a host value: the OS, the
environment and the system application folder. Discover passes the real
one; the editor tests pass an empty folder. The serve test no longer tries
to empty the machine at all: an editor the user configured that does not
exist is the missing class everywhere, and it reaches the same refusal.
2026-09-24 20:26:15 -07:00
esengine e1b8d5542f test(studio): an earlier version folds under its conversation and opens
The sidebar's version rows had no rendering test: the expander, the label
and opening the version's own path are now exercised through the real
Workspaces component.
2026-09-24 20:26:15 -07:00
esengine 25414f3abe test(serve): race resume against /new, not the /fork that no longer exists
The lease-alignment race posted to /fork, removed with explicit forking, so
half of it only collected 404s. /new rotates the active path through another
handler, which is what the race is for.
2026-09-24 20:26:15 -07:00
esengine a7986e6eae feat(session): an edit, regenerate or rewind keeps the conversation it cut
Editing an earlier message, regenerating a reply and rewinding the
conversation all truncate the transcript, and only the latest one could be
undone. What was said before the cut was gone.

A conversation rewind now saves what it is about to cut as a version of the
session: a whole session file, marked Superseded with the session as its
parent, left out of every session list so it never reads as a second
conversation. The Studio sidebar hangs versions under their conversation in
the same expander as recovery copies, labelled apart, and each opens as a
whole conversation. A version someone carries on becomes a conversation of
its own: the first save that adds a turn clears the mark and keeps the
parent. Undoing a rewind drops the version that now duplicates the restored
conversation, judged by content so it holds after a restart. Removing a
conversation removes its versions. Keeping a version is a record, not a
precondition: a rewind that cannot keep one still happens.

NewSessionPath moves to session_path.go to keep save.go inside its budget.
2026-09-24 20:26:15 -07:00
esengine 56b24652d0 fix(jobs): a job the session closed under reads as interrupted, not killed
Closing the manager cancelled every running job the same way kill_shell
does, so a job read after a restart came back as killed. Nobody killed it:
the process running it ended, which only the host knows, and a bare "killed"
left the model to guess between that and a stop someone asked for.

A job cancelled because its manager is closing now ends Interrupted, the
status an abandoned record already repairs to, and one stopped by Kill stays
Killed. bash_output and wait render the cause after an interrupted status.
The tool descriptions are unchanged, so the cached prefix does not move.

The boot-level restart test starts a background job, restarts, and reads
it: a restart cannot keep the job alive, but the model is told why it ended.
Status and its cause move to status.go.
2026-09-24 20:26:15 -07:00
esengine 09d2811609 fix(isolation): a pending isolated result outlives the process that made it
The store kept pending results in memory and removed every worktree when the
session closed. A restart between delegating an isolated task and applying
it lost the work, and the refusal that followed said no result was pending,
a claim the host could not know was true.

The worktree is now the result and the store only indexes it. A finished
run writes an entry mark beside its worktree naming the id, the workspace
and the snapshot; what it changed is derived from the worktree on every
read, so no stored tree can outlive the objects it names. A lookup the
memory misses is recovered from disk, closing a session no longer removes
anything, and a result ends only when applied or discarded. Results taken
from another workspace are never offered, since applying one would write
into that workspace from this one. Unsettled results older than fourteen
days are swept at boot, and only directories carrying the mark are touched.

The boot-level restart test delegates in one turn and applies in the next,
with and without a restart between them, and both arms now land the file.
2026-09-24 20:26:15 -07:00
esengine 5521459d27 fix(delivery): a restart keeps the proof a pending change already earned
A goal resumed after a restart re-demanded inspection, verification and
sign-off for a change that had all three before the process ended: their
receipts lived in the ledger, which does not cross the boundary. It was
fail-closed but not restart-equivalent, and every restart cost a re-run.

The delivery checkpoint now carries Proven: what the turn's last readiness
verdict had proven for the pending change, with a content fingerprint of
every path the scope changed. On resume, with no new write in this process,
the proof discharges what it covers, but only while every path still holds
the content it was earned on. A path edited or deleted while no process was
watching voids all of it, which is more than an uninterrupted run can see.
A change with a part the host cannot name carries no proof at all.

The verdict is recorded where the turn asks to stop, so the checkpoint reads
the same derivation the gate used rather than a second one. The Delivery
branch of the readiness check moves to appendDeliveryGaps to keep the
function inside its complexity budget. The restart harness no longer pins
any stricter step: both arms now owe exactly the review.
2026-09-24 20:26:15 -07:00
yhh 4fe5caf8ce docs(repo): studio is the 2.x line, ready to be the default branch
The repository now describes the two lines the version roadmap announced
(discussions/10748): 2.x on studio, 1.x on main-v2 in maintenance.

- README (en, zh-CN): a Versions section and a Studio 2.x release badge.
  Install leads with Reasonix Studio 2.x from the studio-v* pre-releases,
  with the 1.x CLI and desktop app beside it as the stable line. The
  zh-CN build-from-source section gains the Studio build it lacked.
- Issue forms offer 2.x / 1.x / 0.x, and the labeler maps them to v3 /
  v2 / v1, with the pasted version still overriding the dropdown. The
  forms link the roadmap. The PR template and CONTRIBUTING say which
  branch takes which changes.

Issue forms, schedules and Dependabot are read from the default branch
only, so these take effect when studio becomes it:
- CodeQL scans pushes and PRs on both lines, and the weekly scan checks
  out each line and files results under its own ref, so 1.x keeps its
  security scanning.
- v1-maintenance-schedule starts main-v2's nightly app-memory and
  diagnostic-overhead monitors, which measure the 1.x desktop app, on
  main-v2.
- Dependabot drops /desktop/frontend and /site, which do not exist on
  studio, and covers /desktop/frontend-next and /desktop/electron. It
  also names main-v2 as a target for patch updates only.
2026-09-24 20:21:38 -07:00
esengine 8aa9cb02ae fix(repolint): directories sharing a layer rank may not import each other
layer-order ranked model, tools, safety, ext, state and platform as one peer
pool that could import freely inside itself, so state -> tools or
model -> platform passed the gate and nothing held the directory graph
acyclic. The pool is split into the order the tree already follows
(safety/state, model, platform, ext/tools), and a package now imports only
its own directory or a strictly lower rank. The tree needed no change.
2026-09-24 10:37:52 -07:00
esengine acc273a31c fix(delivery): the review a high-risk change owes survives a restart
The structured review a high-risk change owes was derived from mutation
receipts in the evidence ledger, which has no persisted form. A goal
restarted between its turns resumed with an empty ledger, derived nothing,
and completed without the review. The restart-equivalence tests showed it
through the real boot and the controller's own resume.

The delivery checkpoint now carries OwedReview: the review kinds a change in
its scope still owes and the paths a review has to cover. Each turn's
checkpoint joins what the live ledger derives with what was carried in and is
still unsettled, so a restarted ledger deriving nothing is never read as a
discharge. The gate owes a carried review until a review of that kind covering
those paths lands; one kind does not settle the other. The live derivation is
unchanged and shares one helper with the checkpoint, so an uninterrupted run
is held exactly as before.

The restart harness now fails any step where a restart owes less than an
uninterrupted run. What a restart still re-demands — inspection, verification
and sign-off, whose receipts do not cross the boundary either — is fail-closed
and pinned as known, and the boot-level test asserts both arms end blocked.
2026-09-24 10:33:33 -07:00
esengine 6d43ffa476 test(boot): a restart between goal turns lets a high-risk change skip its review
The production form of the restart-equivalence harness: two real boots, the
session resumed from its file the way --continue does, and goal turns
submitted the way a window submits them, so they carry the goal's delivery
scope. The scripted model acts on milestones rather than turn counts, and
every reply spends budget, so a goal that cannot finish stops on its own
token budget as a real one does.

A high-risk change (a path the project declares sensitive) is made, checked
and signed off, with no review. Uninterrupted, the goal stays blocked and the
host keeps demanding the review. Restarted between its two phases, the
resumed goal is never asked for one and completes. Each arm run in its own
process gives the same result, so no package state carries between them.

The review obligation is derived from mutation receipts in the evidence
ledger, which has no persisted form: the goal sidecar carries only the
DeliveryCheckpoint. The case is pinned as a known divergence so the fix turns
it red.
2026-09-24 10:27:10 -07:00
esengine b2ddd0c91f feat(skill): an oversized catalog names every skill instead of clipping the tail
Past IndexMaxChars the listing was cut mid-rune: with 91 skills only 35
lines survived whole, the other 56 were invisible to the model, and the cut
could leave half an identifier to call. The listing now degrades to names
only, cut whole-line with a count of what it left out, and points at
use_capability search for descriptions. A catalog under the cap renders the
same bytes as before.

No per-turn retrieval over the user's wording: ranking by prose would hide
skills inside a gate. The model asks through the existing capability search,
whose keywords now include the triggers a skill's author declared.
2026-09-24 10:19:17 -07:00
esengine 88ba715b86 test(agent): restart equivalence harness, pinning the review a restart drops
Stopping the process at a turn boundary and resuming must leave the host
deciding exactly what it would have decided had it kept running. The harness
plays a scenario twice, once in one agent and once through a restart after a
chosen step, and compares the readiness verdict of every step. The restarted
arm is built as a resume builds it: a fresh agent with an empty ledger, the
transcript, and the DeliveryCheckpoint after a JSON round trip, which is all
the goal sidecar carries; the controller's own restore of that checkpoint is
established by goal_resume_project_check_test.

A signed-off ordinary change agrees across a restart. A high-risk change does
not: the uninterrupted arm owes structured_review at every later step, while
the restarted arm owes re-verification and sign-off instead, and once those
are redone it finalizes with no review at all. The review obligation is
derived from mutation receipts in the ledger, and the ledger does not cross
the boundary. The case is pinned as a known divergence, so the fix turns it
red and takes the mark off.
2026-09-24 10:15:51 -07:00
esengine 9da268b769 feat(delegation): task can run a writer in its own git worktree
With [agent] worktree_isolation on, task takes isolation "worktree". The
sub-agent then runs as an unattended kernel of its own, rooted at a fresh
worktree of the workspace as it is now, and nothing it writes reaches the
workspace. Its result lists the changed paths under an iso_ id, and the
parent settles them with apply_isolated or discard_isolated.

apply_isolated declares every path it writes, so the host reserves,
checkpoints and records them like any other write, and it applies path by
path: a path the workspace changed meanwhile refuses the whole apply by code
and keeps the result. A workspace git cannot snapshot is refused with
isolation.not_git; isolation is never faked with the shared workspace.

The child runs under the session's own approval posture, read when it starts.
It has no one to ask, so a session that asks gets a child that refuses what
needs approval, and nothing it does exceeds what the session would do
unattended. Arguments a separate kernel cannot honour (profile, tools,
write_paths, continuation, background) are refused rather than dropped. The
child offers neither isolation nor best_of_n, and the settlement tools are
hidden from sub-agents. Closing the session removes every worktree it holds.

best_of_n's attempt runner is shared as runUnattended, and task's name,
description and schema move to task_schema.go.
2026-09-24 10:10:45 -07:00
esengine d51d652ef2 feat(agent): a writer that declares its write set is observed like write_file
The host observes a write through Previewer, which describes one file. A
writer that changes several paths named nowhere in its arguments was left an
opaque writer: a coverage gap on the checkpoint, no preimages, and a receipt
with no paths, so neither rewind nor the evidence ledger could say what it did.

tool.WritePathDeclarer lets such a writer name every path before it runs. The
host reserves exactly those paths against background writers, captures each
one's preimage and after-fingerprint, and records them on the receipt. A code
rewind then restores a modified path and removes a created one.
2026-09-24 09:47:14 -07:00
esengine 0695ac4d6a feat(worktree): apply a candidate path by path against a workspace that moved on
Apply refuses unless the whole workspace still equals the snapshot, which
holds for best_of_n, where the parent waits, and never for a candidate that
ran beside a parent that kept editing. ApplyPaths judges each changed path
alone: the workspace's version is hashed through the same filters a snapshot
stages with, and compared against the snapshot's blob and the candidate's.
A path still at the snapshot is written, one already at the candidate's
version is left alone, and anything else is a conflict. One conflict writes
nothing, and the ConflictError names every path.

ChangePaths refuses a change outside the folder the snapshot was taken of,
and DiffStat gives per-path line counts for reporting a candidate.
2026-09-24 09:43:11 -07:00
esengine 29ecacc743 fix(mcp): list methods follow nextCursor instead of reading one page
tools/list, resources/list and prompts/list took the first page and ignored
nextCursor, so a server that paginates lost every tool past its first page
without any error. The three now read every page through one helper, bounded
at 64 pages; a cursor handed out twice is reported as a server loop rather
than served as a complete catalog.
2026-09-24 09:32:07 -07:00
esengine 7a8b720bc7 fix(readiness): a rewritten declaration no longer retires the check a goal began under
The readiness gate read only the checks the current process declared. A Goal
restored into a process whose declaration had changed carried its captured
baseline in the delivery checkpoint, but nothing enforced it, so running the
replacement alone was enough to finalize. The Goal resume path was shown
reachable by goal_resume_project_check_test.

The gate now also owes every baseline check no current declaration names,
inside the delivery scope that captured it, anchored on the same write the
gate already uses. The trust-domain rule settles the authority question: the
workspace cannot supersede a captured criterion.

The full switch to ledger obligations is not taken: a resumed process starts
from an empty ledger, where the obligations owe nothing at all, so the switch
would open the resume path this closes. A restored checkpoint from another
scope does not bind an unscoped turn.
2026-09-24 09:32:07 -07:00
esengine 2cec05088e ci(release-studio): package Windows in parallel and mirror chunks 64 at a time
The 2.20.0 release spent 226s packaging Windows and 640s mirroring to R2.

Packaging compressed the same 245 MB tree twice, the 7-Zip archive and
then the NSIS installer. They now run at once; the zip reads its own copy of
win-unpacked because NSIS writes elevate.exe into the tree it packs, and a
zip read beside that would contain it or not by timing. A local cross-build
gives the same 128-file zip as the sequential step.

Defender real-time scanning is switched off for the Windows build, which
writes that tree several times over. continue-on-error keeps a runner image
that refuses the switch from failing the release.

The R2 mirror sent 18,755 delta chunks of about 10 KB with the AWS CLI's
default of 10 requests in flight, roughly 31 a second. Raising it to 64 leaves
the published objects unchanged.
2026-09-24 09:06:52 -07:00
5508 changed files with 440951 additions and 35713 deletions

No files matched your search

+1
View File
@@ -27,3 +27,4 @@
*.yml text eol=lf
.githooks/** text eol=lf
benchmarks/**/*.sh text eol=lf
internal/runtime/agent/testdata/perseveration/** -text
+16 -1
View File
@@ -10,6 +10,7 @@
# User-facing guides and references.
/docs/ @SivanCola @esengine
/docs/THEME_AUTHOR_GUIDE.md @esengine @SivanCola
/sdk/ @SivanCola @esengine
/README.md @SivanCola @esengine
/README.zh-CN.md @SivanCola @esengine
@@ -19,11 +20,25 @@
/docs/DOCS_STANDARD.md @esengine @SivanCola
/docs/STUDIO_*.md @esengine @SivanCola
/docs/SESSION_MEMORY_RETRIEVAL.md @esengine @SivanCola
/docs/*_CONTRACT.md @esengine @SivanCola
/docs/TOOL_APPROVAL_MODES.md @esengine @SivanCola
/docs/CAPABILITY_DIAGNOSTICS.md @esengine @SivanCola
/docs/CHECKPOINTS.md @esengine @SivanCola
/docs/EXTENSION_*.md @esengine @SivanCola
/docs/SUBAGENT_*.md @esengine @SivanCola
/docs/ROADMAP.md @esengine @SivanCola
/docs/MIGRATING.md @esengine @SivanCola
/docs/design/ @esengine @SivanCola
/release-notes/ @esengine @SivanCola
# Release signing: provider policy, its contract and the workflow that uses it.
/.signpath/ @esengine @SivanCola
/.github/workflows/release-studio.yml @esengine @SivanCola
/.github/workflows/studio-certum-signing-smoke.yml @esengine @SivanCola
/.github/actions/setup-certum/ @esengine @SivanCola
/cmd/signpath-contract/ @esengine @SivanCola
/scripts/complete-signpath-request.ps1 @esengine @SivanCola
/scripts/install-smoke.ps1 @esengine @SivanCola
/scripts/sign-certum.ps1 @esengine @SivanCola
/scripts/verify-windows-authenticode.ps1 @esengine @SivanCola
/scripts/verify-windows-package.ps1 @esengine @SivanCola
/scripts/windows-signing-lib.ps1 @esengine @SivanCola
+7 -5
View File
@@ -7,23 +7,25 @@ body:
attributes:
value: |
Thanks for filing a bug. Pick which version line you're on below — it gets
labeled (`v1`/`v2`) automatically, no maintainer action needed.
labeled automatically, no maintainer action needed. Not sure which line
you run? See the [version roadmap](https://github.com/esengine/DeepSeek-Reasonix/discussions/10748).
- type: dropdown
id: version-line
attributes:
label: Version line
description: Which line are you running?
options:
- "v2 — Go rewrite (1.x), main-v2 (active development)"
- "v1 — Legacy TypeScript (0.x), maintenance only"
- "2.x — Reasonix Studio, studio branch (active development)"
- "1.x — main-v2 branch (maintenance / stable)"
- "0.x — legacy TypeScript (maintenance only)"
validations:
required: true
- type: input
id: version
attributes:
label: Exact version
description: Output of `reasonix --version`.
placeholder: e.g. 1.0.0
description: Output of `reasonix --version`, or the version shown in Studio's settings.
placeholder: e.g. 2.20.0 or 1.39.0
validations:
required: true
- type: textarea
+9
View File
@@ -1,5 +1,14 @@
blank_issues_enabled: false
contact_links:
- name: Version roadmap (1.x / 2.x)
url: https://github.com/esengine/DeepSeek-Reasonix/discussions/10748
about: Which line to use, which branch takes which changes, and where Reasonix is heading.
- name: Questions & discussions
url: https://github.com/esengine/DeepSeek-Reasonix/discussions
about: For usage questions, ideas, and general discussion — please use Discussions instead of opening an issue.
- name: Discord
url: https://discord.gg/XF78rEME2D
about: Bilingual community chat for setup help, workflow showcases, and feature ideas.
- name: QQ group (DeepSeek-Reasonix官方群, 1093562660)
url: https://qm.qq.com/q/i59b0z2R8s
about: Chinese-language community chat for setup help and everyday questions.
+3 -2
View File
@@ -9,8 +9,9 @@ body:
label: Version line
description: Which line is this for?
options:
- "v2 — Go rewrite (1.x), main-v2 (active development)"
- "v1 — Legacy TypeScript (0.x), maintenance only"
- "2.x — Reasonix Studio, studio branch (active development)"
- "1.x — main-v2 branch (maintenance / stable)"
- "0.x — legacy TypeScript (maintenance only)"
validations:
required: true
- type: textarea
+35
View File
@@ -0,0 +1,35 @@
name: Connect to Certum cloud signing
description: Verify the pinned SimplySign installer and connect to the configured certificate.
inputs:
username:
required: true
description: SimplySign account
otp-uri:
required: true
description: TOTP provisioning URI
thumbprint:
required: true
description: Expected SHA-1 certificate thumbprint
runs:
using: composite
steps:
- name: Verify SimplySign installer
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
Invoke-WebRequest 'https://files.certum.eu/software/SimplySignDesktop/Windows/9.4.3.90/SimplySignDesktop-9.4.3.90-64-bit-en.msi' -OutFile SimplySignDesktop.msi
$hash = (Get-FileHash -Algorithm SHA256 SimplySignDesktop.msi).Hash
if ($hash -ne 'CE2B38EF0124A574BC08E5558726099CAAC79A7C58B8D3D712F629371FE1BCB7') {
throw "SimplySign installer SHA-256 $hash does not match the pinned 9.4.3.90 build"
}
$signature = Get-AuthenticodeSignature SimplySignDesktop.msi
if ($signature.Status -ne 'Valid' -or $signature.SignerCertificate.Subject -notmatch 'Asseco Data Systems') {
throw 'SimplySign installer signature or publisher verification failed'
}
- name: Authenticate cloud certificate
uses: dismine/windows-app-signing-setup-action@89ae3b032d4bc7a5b98d1a42a34e61ecb6faad64
with:
certum-username: ${{ inputs.username }}
certum-otp-uri: ${{ inputs.otp-uri }}
certum-key-id: ${{ inputs.thumbprint }}
capture-diagnostics: 'false'
+61 -2
View File
@@ -1,5 +1,6 @@
version: 2
updates:
# 2.x (studio): the development line.
- package-ecosystem: "gomod"
directory: "/"
schedule:
@@ -21,7 +22,7 @@ updates:
update-types: ["minor", "patch"]
- package-ecosystem: "npm"
directory: "/desktop/frontend"
directory: "/desktop/frontend-next"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
@@ -31,7 +32,7 @@ updates:
update-types: ["minor", "patch"]
- package-ecosystem: "npm"
directory: "/site"
directory: "/desktop/electron"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
@@ -47,3 +48,61 @@ updates:
groups:
actions:
patterns: ["*"]
# 1.x (main-v2): maintenance. Dependabot reads this file from the default
# branch only, so the 1.x line is named here; it takes patch updates alone.
- package-ecosystem: "gomod"
directory: "/"
target-branch: "main-v2"
schedule:
interval: "weekly"
open-pull-requests-limit: 3
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-major", "version-update:semver-minor"]
groups:
go-1x:
patterns: ["*"]
update-types: ["patch"]
- package-ecosystem: "gomod"
directory: "/desktop"
target-branch: "main-v2"
schedule:
interval: "weekly"
open-pull-requests-limit: 3
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-major", "version-update:semver-minor"]
groups:
go-1x:
patterns: ["*"]
update-types: ["patch"]
- package-ecosystem: "npm"
directory: "/desktop/frontend"
target-branch: "main-v2"
schedule:
interval: "weekly"
open-pull-requests-limit: 3
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-major", "version-update:semver-minor"]
groups:
npm-1x:
patterns: ["*"]
update-types: ["patch"]
- package-ecosystem: "npm"
directory: "/site"
target-branch: "main-v2"
schedule:
interval: "weekly"
open-pull-requests-limit: 3
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-major", "version-update:semver-minor"]
groups:
npm-1x:
patterns: ["*"]
update-types: ["patch"]
+38
View File
@@ -1,7 +1,41 @@
<!--
Target branch: 2.x features and fixes go to `studio`. 1.x takes bug fixes,
provider/API compatibility, release/updater, security and platform stability
only, on `main-v2`.
-->
## Summary
-
## Cause
-
## Blast radius
-
## Neighbouring behaviours tested
-
## Why this layer
-
## UI changes
<!--
Delete this section if the PR touches nothing under `desktop/frontend-next/src`
or the website pages.
-->
- Screenshots, before and after: light and dark, a wide window and a phone-width one (about 390 px). Attach them to this description.
- Entries moved, hidden or removed: none / list each one.
- Hiding or removing a visible control needs a linked issue and a release-notes line naming it. Do not retire UI with `display: none !important` or a feature flag.
- If a row of `docs/STUDIO_PARITY.md` changes, update it here.
## Issues
<!--
@@ -20,3 +54,7 @@ the source-to-target contract here: easing syntax, time units, callbacks,
cancellation, reduced-motion behavior, and failure fallback. Verification must
assert that the target API was actually called; a mock that silently skips it
does not count.
## Disclosure
- [ ] AI assistance was used for this change (optional; for transparency only, it does not change how the change is reviewed)
Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="_图层_2" data-name="图层 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 145.55 113.29">
<defs>
<style>
.cls-1 {
fill: #ffffff;
stroke: #ffffff;
stroke-miterlimit: 10;
}
</style>
</defs>
<g id="_图层_1-2" data-name="图层 1">
<g>
<path class="cls-1" d="M144.68,38.49l-.06-.23c-.88-3.28-2.5-5.94-4.58-8.06.14,5.65-2.96,11.02-6.22,16.66l-.39.68c-2.26,3.91-4.66,7.94-6.57,11.1l-.86,1.38c-3.36,5.44-6.27,10.14-12.18,14.18-8.34,5.87-18.2,5.81-26.92,5.76-2.81-.02-5.48-.03-7.95.14l-.35.02c-3.22.06-5.96,1.57-8.17,4.49l-.14.18c-.86,1.06-1.7,2.26-2.58,3.54-3.43,4.92-7.69,11.04-16.17,12.42-4.37.86-9.98.84-14.94.83h-1.95c-.52,0-1.06,0-1.61,0-6.95,0-16.08-.89-21.94-6.55-.15-.15-.3-.3-.44-.45.5,4.73,2.33,8.64,5.44,11.65,5.86,5.66,14.98,6.55,21.94,6.55.55,0,1.09,0,1.61-.01h1.93c4.96.02,10.58.04,14.96-.83,8.48-1.37,12.74-7.5,16.17-12.42.88-1.27,1.72-2.48,2.58-3.54l.14-.18c2.21-2.92,4.95-4.43,8.17-4.49l.35-.02c2.47-.17,5.13-.16,7.95-.14,8.72.05,18.58.11,26.91-5.76,5.92-4.03,8.82-8.73,12.19-14.17l.86-1.39c1.91-3.15,4.3-7.18,6.57-11.09l.39-.69c3.81-6.6,7.41-12.84,5.86-19.57ZM120.9,23.02c-.28,0-.56,0-.83,0-9.68-.19-24.03-.09-35.57-.01l-2.04.02c-.93.01-1.82.02-2.67.03-8.36.08-14.4.13-23.33,3.82l-.27.12c-10.76,4.68-16.91,12.16-22.83,21.95-5.53,8.76-12.62,20.57-16.32,26.79-.41.69-.77,1.3-1.09,1.84l-.49.84c-1.49,2.53-3.23,5.49-4.21,8.95,3.98,1.9,8.52,2.65,12.71,2.91.27-.77.64-1.56,1.07-2.38.48-.94,1.05-1.9,1.63-2.89l.49-.84c.96-1.62,2.38-3.99,4.05-6.78,3.82-6.36,8.98-14.88,13.19-21.55l.07-.11c5.02-8.31,9.2-13.45,16.91-16.81l.11-.05c6.57-2.7,10.54-2.74,18.43-2.81.87-.01,1.78-.02,2.69-.03l1.99-.02c9.17-.06,20.13-.14,29.01-.07,2.26.01,4.39.04,6.32.08h.12s.11,0,.11,0c1.25-.01,2.91.11,4.61.46,1.03.22,2.08.52,3.05.93.21-.35.41-.71.62-1.06l.39-.68c1.92-3.33,3.79-6.57,4.97-9.82-4.08-1.92-8.7-2.76-12.89-2.82Z"/>
<path class="cls-1" d="M115.07,11.81c-9.7-.19-24.07-.09-35.62-.01h-1.99c-.93.03-1.82.04-2.67.04-8.36.08-14.4.14-23.33,3.83l-.27.12c-10.76,4.67-16.91,12.16-22.83,21.95-6.13,9.71-14.19,23.21-17.41,28.63l-.5.84c-2.08,3.55-4.68,7.97-4.94,13.39v.2s0,.21,0,.21c.01.81.06,1.61.15,2.38.14.15.29.3.44.45,1.53,1.47,3.28,2.63,5.15,3.52,3.98,1.9,8.52,2.65,12.71,2.91,1.41.09,2.78.12,4.08.12.55,0,1.09-.01,1.61-.01h1.95c4.95.01,10.57.03,14.94-.83,8.48-1.38,12.74-7.5,16.17-12.42.88-1.28,1.72-2.48,2.58-3.54l.14-.18c2.21-2.92,4.95-4.44,8.17-4.49l.35-.02c2.47-.17,5.14-.16,7.95-.14,8.71.05,18.58.1,26.92-5.76,5.91-4.04,8.82-8.74,12.18-14.18l.86-1.39c1.74-2.86,3.87-6.45,5.95-10.03.21-.35.41-.71.62-1.06l.39-.68c1.92-3.33,3.79-6.57,4.97-9.82.82-2.26,1.31-4.53,1.25-6.84-5.18-5.29-13.22-7.25-19.97-7.19ZM122.56,40.37l-.39.67c-2.21,3.81-4.55,7.77-6.39,10.79l-.84,1.36c-3.01,4.87-4.83,7.81-8.48,10.29l-.1.07c-4.94,3.49-11.95,3.45-19.38,3.41-2.88-.02-5.87-.04-8.79.16-7.1.19-13.51,3.58-18.07,9.57-1.13,1.4-2.12,2.83-3.08,4.21-2.92,4.18-4.71,6.57-7.64,7.02l-.31.06c-3.09.63-8.28.61-12.45.6h-2.16c-3.85.07-7.01-.16-9.45-.69-2.24-.48-3.87-1.22-4.89-2.2-.66-.64-1.54-1.81-1.63-4.65.11-1.35.71-2.82,1.52-4.35.48-.94,1.05-1.9,1.63-2.89l.49-.84c3.17-5.33,11.19-18.75,17.24-28.33l.07-.11c5.02-8.32,9.2-13.45,16.92-16.81l.1-.05c6.57-2.7,10.54-2.74,18.43-2.82.87,0,1.78,0,2.69-.03h1.94c11.52-.09,25.86-.19,35.38,0h.23c1.25-.01,2.92.11,4.61.46,3.15.66,6.4,2.12,7.27,5.02.2,1.2-.89,3.62-2.27,6.18-.7,1.31-1.48,2.65-2.2,3.9ZM120.07,23.01c-9.68-.19-24.03-.09-35.57-.01l-2.04.02c-.93.01-1.82.02-2.67.03-8.36.08-14.4.13-23.33,3.82l-.27.12c-10.76,4.68-16.91,12.16-22.83,21.95-5.53,8.76-12.62,20.57-16.32,26.79-.78-.35-1.41-.77-1.9-1.24-.66-.64-1.54-1.81-1.63-4.65.18-2.18,1.62-4.64,3.15-7.25l.49-.83c3.17-5.32,11.18-18.73,17.24-28.33l.07-.12c5.02-8.31,9.2-13.45,16.92-16.81l.1-.04c6.57-2.7,10.54-2.74,18.43-2.82.87-.01,1.78-.01,2.69-.03h1.99c11.5-.09,25.82-.19,35.33,0h.23c3.57-.04,10.55,1.02,11.88,5.48.14.84-.35,2.27-1.13,3.93-.28,0-.56,0-.83,0Z"/>
<path class="cls-1" d="M134.68,16.09l-.06-.23c-3.07-11.45-15.08-15.33-24.55-15.25-9.68-.19-24.03-.09-35.57-.01h-2.04c-.93.03-1.82.04-2.67.04-8.36.08-14.4.14-23.33,3.83l-.27.12c-10.76,4.67-16.91,12.16-22.83,21.95-6.14,9.73-14.2,23.22-17.41,28.62l-.49.85c-2.09,3.55-4.69,7.96-4.95,13.39v.2s0,.21,0,.21c.09,5.57,1.82,10.13,5.15,13.58.14.15.29.3.44.45,1.53,1.47,3.28,2.63,5.15,3.52,3.98,1.9,8.52,2.65,12.71,2.91,1.41.09,2.78.12,4.08.12.55,0,1.09-.01,1.61-.01h1.95c4.95.01,10.57.03,14.94-.83,8.48-1.38,12.74-7.5,16.17-12.42.88-1.28,1.72-2.48,2.58-3.54l.14-.18c2.21-2.92,4.95-4.44,8.17-4.49l.35-.02c2.47-.17,5.14-.16,7.95-.14,8.71.05,18.58.1,26.92-5.76,5.91-4.04,8.82-8.74,12.18-14.18l.86-1.39c1.74-2.86,3.87-6.45,5.95-10.03.21-.35.41-.71.62-1.06l.39-.68c1.92-3.33,3.79-6.57,4.97-9.82.82-2.26,1.31-4.53,1.25-6.84-.02-.96-.14-1.93-.36-2.91ZM119.76,25.27c-.7,1.31-1.48,2.65-2.2,3.9l-.39.67c-1.19,2.04-2.41,4.13-3.57,6.09-1.01,1.7-1.97,3.31-2.82,4.7l-.84,1.36c-3.01,4.87-4.83,7.81-8.48,10.29l-.1.07c-4.94,3.49-11.96,3.45-19.38,3.41-2.89-.02-5.87-.04-8.79.16-7.1.18-13.51,3.58-18.07,9.57-1.13,1.4-2.12,2.83-3.08,4.21-2.92,4.18-4.71,6.57-7.64,7.02l-.31.06c-3.09.63-8.27.61-12.45.6h-2.16c-3.85.07-7.01-.16-9.45-.69-1.16-.25-2.16-.57-2.99-.96-.78-.35-1.41-.77-1.9-1.24-.66-.64-1.54-1.81-1.63-4.65.18-2.18,1.62-4.64,3.15-7.25l.49-.83c3.17-5.32,11.18-18.73,17.24-28.33l.07-.12c5.02-8.31,9.2-13.45,16.92-16.81l.1-.04c6.57-2.7,10.54-2.74,18.43-2.82.87-.01,1.78-.01,2.69-.03h1.99c11.5-.09,25.82-.19,35.33,0h.23c3.57-.04,10.55,1.02,11.88,5.48.14.84-.35,2.27-1.13,3.93-.34.72-.73,1.48-1.14,2.25Z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.4 KiB

+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="_图层_2" data-name="图层 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 145.55 113.29">
<defs>
<style>
.cls-1 {
fill: #111111;
stroke: #111111;
stroke-miterlimit: 10;
}
</style>
</defs>
<g id="_图层_1-2" data-name="图层 1">
<g>
<path class="cls-1" d="M144.68,38.49l-.06-.23c-.88-3.28-2.5-5.94-4.58-8.06.14,5.65-2.96,11.02-6.22,16.66l-.39.68c-2.26,3.91-4.66,7.94-6.57,11.1l-.86,1.38c-3.36,5.44-6.27,10.14-12.18,14.18-8.34,5.87-18.2,5.81-26.92,5.76-2.81-.02-5.48-.03-7.95.14l-.35.02c-3.22.06-5.96,1.57-8.17,4.49l-.14.18c-.86,1.06-1.7,2.26-2.58,3.54-3.43,4.92-7.69,11.04-16.17,12.42-4.37.86-9.98.84-14.94.83h-1.95c-.52,0-1.06,0-1.61,0-6.95,0-16.08-.89-21.94-6.55-.15-.15-.3-.3-.44-.45.5,4.73,2.33,8.64,5.44,11.65,5.86,5.66,14.98,6.55,21.94,6.55.55,0,1.09,0,1.61-.01h1.93c4.96.02,10.58.04,14.96-.83,8.48-1.37,12.74-7.5,16.17-12.42.88-1.27,1.72-2.48,2.58-3.54l.14-.18c2.21-2.92,4.95-4.43,8.17-4.49l.35-.02c2.47-.17,5.13-.16,7.95-.14,8.72.05,18.58.11,26.91-5.76,5.92-4.03,8.82-8.73,12.19-14.17l.86-1.39c1.91-3.15,4.3-7.18,6.57-11.09l.39-.69c3.81-6.6,7.41-12.84,5.86-19.57ZM120.9,23.02c-.28,0-.56,0-.83,0-9.68-.19-24.03-.09-35.57-.01l-2.04.02c-.93.01-1.82.02-2.67.03-8.36.08-14.4.13-23.33,3.82l-.27.12c-10.76,4.68-16.91,12.16-22.83,21.95-5.53,8.76-12.62,20.57-16.32,26.79-.41.69-.77,1.3-1.09,1.84l-.49.84c-1.49,2.53-3.23,5.49-4.21,8.95,3.98,1.9,8.52,2.65,12.71,2.91.27-.77.64-1.56,1.07-2.38.48-.94,1.05-1.9,1.63-2.89l.49-.84c.96-1.62,2.38-3.99,4.05-6.78,3.82-6.36,8.98-14.88,13.19-21.55l.07-.11c5.02-8.31,9.2-13.45,16.91-16.81l.11-.05c6.57-2.7,10.54-2.74,18.43-2.81.87-.01,1.78-.02,2.69-.03l1.99-.02c9.17-.06,20.13-.14,29.01-.07,2.26.01,4.39.04,6.32.08h.12s.11,0,.11,0c1.25-.01,2.91.11,4.61.46,1.03.22,2.08.52,3.05.93.21-.35.41-.71.62-1.06l.39-.68c1.92-3.33,3.79-6.57,4.97-9.82-4.08-1.92-8.7-2.76-12.89-2.82Z"/>
<path class="cls-1" d="M115.07,11.81c-9.7-.19-24.07-.09-35.62-.01h-1.99c-.93.03-1.82.04-2.67.04-8.36.08-14.4.14-23.33,3.83l-.27.12c-10.76,4.67-16.91,12.16-22.83,21.95-6.13,9.71-14.19,23.21-17.41,28.63l-.5.84c-2.08,3.55-4.68,7.97-4.94,13.39v.2s0,.21,0,.21c.01.81.06,1.61.15,2.38.14.15.29.3.44.45,1.53,1.47,3.28,2.63,5.15,3.52,3.98,1.9,8.52,2.65,12.71,2.91,1.41.09,2.78.12,4.08.12.55,0,1.09-.01,1.61-.01h1.95c4.95.01,10.57.03,14.94-.83,8.48-1.38,12.74-7.5,16.17-12.42.88-1.28,1.72-2.48,2.58-3.54l.14-.18c2.21-2.92,4.95-4.44,8.17-4.49l.35-.02c2.47-.17,5.14-.16,7.95-.14,8.71.05,18.58.1,26.92-5.76,5.91-4.04,8.82-8.74,12.18-14.18l.86-1.39c1.74-2.86,3.87-6.45,5.95-10.03.21-.35.41-.71.62-1.06l.39-.68c1.92-3.33,3.79-6.57,4.97-9.82.82-2.26,1.31-4.53,1.25-6.84-5.18-5.29-13.22-7.25-19.97-7.19ZM122.56,40.37l-.39.67c-2.21,3.81-4.55,7.77-6.39,10.79l-.84,1.36c-3.01,4.87-4.83,7.81-8.48,10.29l-.1.07c-4.94,3.49-11.95,3.45-19.38,3.41-2.88-.02-5.87-.04-8.79.16-7.1.19-13.51,3.58-18.07,9.57-1.13,1.4-2.12,2.83-3.08,4.21-2.92,4.18-4.71,6.57-7.64,7.02l-.31.06c-3.09.63-8.28.61-12.45.6h-2.16c-3.85.07-7.01-.16-9.45-.69-2.24-.48-3.87-1.22-4.89-2.2-.66-.64-1.54-1.81-1.63-4.65.11-1.35.71-2.82,1.52-4.35.48-.94,1.05-1.9,1.63-2.89l.49-.84c3.17-5.33,11.19-18.75,17.24-28.33l.07-.11c5.02-8.32,9.2-13.45,16.92-16.81l.1-.05c6.57-2.7,10.54-2.74,18.43-2.82.87,0,1.78,0,2.69-.03h1.94c11.52-.09,25.86-.19,35.38,0h.23c1.25-.01,2.92.11,4.61.46,3.15.66,6.4,2.12,7.27,5.02.2,1.2-.89,3.62-2.27,6.18-.7,1.31-1.48,2.65-2.2,3.9ZM120.07,23.01c-9.68-.19-24.03-.09-35.57-.01l-2.04.02c-.93.01-1.82.02-2.67.03-8.36.08-14.4.13-23.33,3.82l-.27.12c-10.76,4.68-16.91,12.16-22.83,21.95-5.53,8.76-12.62,20.57-16.32,26.79-.78-.35-1.41-.77-1.9-1.24-.66-.64-1.54-1.81-1.63-4.65.18-2.18,1.62-4.64,3.15-7.25l.49-.83c3.17-5.32,11.18-18.73,17.24-28.33l.07-.12c5.02-8.31,9.2-13.45,16.92-16.81l.1-.04c6.57-2.7,10.54-2.74,18.43-2.82.87-.01,1.78-.01,2.69-.03h1.99c11.5-.09,25.82-.19,35.33,0h.23c3.57-.04,10.55,1.02,11.88,5.48.14.84-.35,2.27-1.13,3.93-.28,0-.56,0-.83,0Z"/>
<path class="cls-1" d="M134.68,16.09l-.06-.23c-3.07-11.45-15.08-15.33-24.55-15.25-9.68-.19-24.03-.09-35.57-.01h-2.04c-.93.03-1.82.04-2.67.04-8.36.08-14.4.14-23.33,3.83l-.27.12c-10.76,4.67-16.91,12.16-22.83,21.95-6.14,9.73-14.2,23.22-17.41,28.62l-.49.85c-2.09,3.55-4.69,7.96-4.95,13.39v.2s0,.21,0,.21c.09,5.57,1.82,10.13,5.15,13.58.14.15.29.3.44.45,1.53,1.47,3.28,2.63,5.15,3.52,3.98,1.9,8.52,2.65,12.71,2.91,1.41.09,2.78.12,4.08.12.55,0,1.09-.01,1.61-.01h1.95c4.95.01,10.57.03,14.94-.83,8.48-1.38,12.74-7.5,16.17-12.42.88-1.28,1.72-2.48,2.58-3.54l.14-.18c2.21-2.92,4.95-4.44,8.17-4.49l.35-.02c2.47-.17,5.14-.16,7.95-.14,8.71.05,18.58.1,26.92-5.76,5.91-4.04,8.82-8.74,12.18-14.18l.86-1.39c1.74-2.86,3.87-6.45,5.95-10.03.21-.35.41-.71.62-1.06l.39-.68c1.92-3.33,3.79-6.57,4.97-9.82.82-2.26,1.31-4.53,1.25-6.84-.02-.96-.14-1.93-.36-2.91ZM119.76,25.27c-.7,1.31-1.48,2.65-2.2,3.9l-.39.67c-1.19,2.04-2.41,4.13-3.57,6.09-1.01,1.7-1.97,3.31-2.82,4.7l-.84,1.36c-3.01,4.87-4.83,7.81-8.48,10.29l-.1.07c-4.94,3.49-11.96,3.45-19.38,3.41-2.89-.02-5.87-.04-8.79.16-7.1.18-13.51,3.58-18.07,9.57-1.13,1.4-2.12,2.83-3.08,4.21-2.92,4.18-4.71,6.57-7.64,7.02l-.31.06c-3.09.63-8.27.61-12.45.6h-2.16c-3.85.07-7.01-.16-9.45-.69-1.16-.25-2.16-.57-2.99-.96-.78-.35-1.41-.77-1.9-1.24-.66-.64-1.54-1.81-1.63-4.65.18-2.18,1.62-4.64,3.15-7.25l.49-.83c3.17-5.32,11.18-18.73,17.24-28.33l.07-.12c5.02-8.31,9.2-13.45,16.92-16.81l.1-.04c6.57-2.7,10.54-2.74,18.43-2.82.87-.01,1.78-.01,2.69-.03h1.99c11.5-.09,25.82-.19,35.33,0h.23c3.57-.04,10.55,1.02,11.88,5.48.14.84-.35,2.27-1.13,3.93-.34.72-.73,1.48-1.14,2.25Z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

+111 -55
View File
@@ -15,7 +15,7 @@ permissions:
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
# Cheap path gate for pull requests. PRs confined to docs/release-notes
@@ -26,20 +26,24 @@ jobs:
# merges. Gated jobs skip only on an explicit `false` output: wrapped in
# `always()`, a failed `changes` job (or a missing output) makes them run
# the full matrix instead of silently passing required checks as skipped.
# Pushes to main-v2 always run everything.
# Pushes to main-v2 always run everything. Pull requests run the macOS and
# Windows legs only when `platform` is not 'false'; the rule lives in
# scripts/ci-platform-scope.sh and fails toward running them.
changes:
runs-on: ubuntu-latest
outputs:
code: ${{ steps.filter.outputs.code }}
desktop: ${{ steps.filter.outputs.desktop }}
sdk: ${{ steps.filter.outputs.sdk }}
scripts: ${{ steps.filter.outputs.scripts }}
platform: ${{ steps.filter.outputs.platform }}
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0
- id: filter
run: |
code=true; desktop=true; sdk=true
code=true; desktop=true; sdk=true; scripts=true; platform=true
base=""
if [ "${{ github.event_name }}" = "pull_request" ]; then
base="${{ github.event.pull_request.base.sha }}"
@@ -47,9 +51,9 @@ jobs:
base="${{ github.event.before }}"
fi
if [ -n "$base" ] && git cat-file -e "$base^{commit}" 2>/dev/null; then
files=$(git diff --name-only "$base" HEAD)
files=$(git diff --name-only --no-renames "$base" HEAD)
if [ -n "$files" ]; then
code=false; desktop=false; sdk=false
code=false; desktop=false; sdk=false; scripts=false
# Root-module CI: desktop/ is a separate module, so only the
# clearly unrelated paths below can skip it.
if echo "$files" | grep -qvE '^(docs/|release-notes/|desktop/|workers/|[^/]+\.md$)'; then code=true; fi
@@ -60,9 +64,31 @@ jobs:
# its DTOs are generated from internal/ext/extension/protocol, so
# both paths must trigger the sdk job.
if echo "$files" | grep -qE '^(sdk/|internal/ext/extension/)'; then sdk=true; fi
# The release-note tests render every committed Studio note; npm/ holds the publish tests.
if echo "$files" | grep -qE '^(scripts/|npm/|release-notes/studio/|\.github/workflows/release-studio\.yml$)'; then scripts=true; fi
platform=$(git diff -z --name-only --no-renames "$base" HEAD | bash scripts/ci-platform-scope.sh)
fi
fi
{ echo "code=$code"; echo "desktop=$desktop"; echo "sdk=$sdk"; } >> "$GITHUB_OUTPUT"
{ echo "code=$code"; echo "desktop=$desktop"; echo "sdk=$sdk"; echo "scripts=$scripts"; echo "platform=$platform"; } >> "$GITHUB_OUTPUT"
release-notes-scripts:
needs: changes
if: always() && (github.event_name != 'pull_request' || needs.changes.outputs.scripts != 'false')
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7
with:
node-version: "22"
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with:
go-version-file: go.mod
cache: true
- run: node --test scripts/linux-install-smoke.test.mjs scripts/studio-artifacts.test.mjs scripts/ci-platform-scope.test.mjs scripts/cli-archive-build.test.mjs scripts/cli-archive-contents.test.mjs scripts/release-credits.test.mjs scripts/update-contributors.test.mjs scripts/update-acknowledgments.test.mjs scripts/markdown-refs.test.mjs scripts/studio-release-notes.test.mjs scripts/publish-cli-pointer.test.mjs scripts/studio-release-flags.test.mjs scripts/studio-notes-publish.test.mjs scripts/withdraw-version.test.mjs scripts/brew-cask-guard.test.mjs scripts/feedback-sync.test.mjs npm/publish.test.mjs
# The ruleset requires the per-OS check names (test (ubuntu-latest) etc.).
# A matrix job skipped at job level reports no per-leg checks at all, so
@@ -71,6 +97,9 @@ jobs:
# detector reports the diff is unrelated, every step skips and each leg
# reports success in seconds. `always()` also keeps the legs alive when
# the changes job itself fails (fail-open: an empty output != 'false').
# A pull request with no platform-relevant change moves the macOS and
# Windows legs onto an ubuntu runner with every step skipped: the check
# names the ruleset requires still report, without a scarce runner.
test:
needs: changes
if: always()
@@ -78,15 +107,15 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
runs-on: ${{ github.event_name == 'pull_request' && needs.changes.outputs.platform == 'false' && 'ubuntu-latest' || matrix.os }}
env:
RUN_STEPS: ${{ github.event_name != 'pull_request' || needs.changes.outputs.code != 'false' }}
RUN_STEPS: ${{ github.event_name != 'pull_request' || (needs.changes.outputs.code != 'false' && (matrix.os == 'ubuntu-latest' || needs.changes.outputs.platform != 'false')) }}
steps:
- if: env.RUN_STEPS == 'true'
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- if: env.RUN_STEPS == 'true'
uses: actions/setup-go@v7
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with:
go-version-file: go.mod
cache: true
@@ -157,19 +186,30 @@ jobs:
# budget above normal 7-9 minute runner variance; each package test binary
# remains independently bounded by Go's per-package timeout below.
#
# That timeout is 8m, not 3m: across four consecutive runs of the same
# code, internal/assembly/boot measured 133s, 146s, 162s and then hit 180s, and
# internal/runtime/agent 82s, 92s, 144s and 180s — a 2.2x spread from runner
# variance alone, with four heavy packages sharing four vCPUs. At 3m the
# step failed on slow runners without a code change; a hang still trips
# the 15 minute job budget above.
# The per-package timeout is 18m: internal/assembly/boot took 478-717s
# (12 push runs) on the Windows runner, so 18m is 1.5x the worst. A hang
# still trips the step budget (25m smoke, 35m full).
#
# Every root-module `go test` that runs on Windows passes -count=1. With
# the result cache on, go test records each file and environment access
# a test binary makes, and before and after the run resolves every
# recorded path outside the module through filepath.EvalSymlinks.
# internal/assembly/boot records about 1.9M accesses; on a Windows runner
# that bookkeeping costs minutes per pass while nothing is printed.
# internal/base/testenv holds every such step to it.
- name: test (Windows smoke)
if: env.RUN_STEPS == 'true' && runner.os == 'Windows' && github.event_name == 'pull_request'
timeout-minutes: 15
timeout-minutes: 25
env:
REASONIX_RELEASE_CACHE_GUARD: "1"
WINDOWS_SANDBOX_WAIT_MS: "20000"
run: go test -p 4 -timeout=8m ./internal/runtime/agent/... ./internal/platform/appidentity/... ./internal/assembly/boot/... ./internal/state/checkpoint/... ./internal/frontend/cli/... ./internal/session/control/... ./internal/base/filelock/... ./internal/base/fileutil/... ./internal/ext/hook/... ./internal/state/instruction/... ./internal/ext/mcplaunch/... ./internal/platform/notify/... ./internal/base/proc/... ./internal/platform/remote/... ./internal/platform/repair/... ./internal/safety/sandbox/... ./internal/base/sysproxy/... ./internal/state/workspacelease/... ./cmd/...
run: go test -count=1 -p 4 -timeout=18m ./internal/runtime/agent/... ./internal/platform/appidentity/... ./internal/assembly/boot/... ./internal/state/checkpoint/... ./internal/frontend/cli/... ./internal/session/control/... ./internal/base/filelock/... ./internal/base/fileutil/... ./internal/ext/hook/... ./internal/state/instruction/... ./internal/ext/mcplaunch/... ./internal/platform/notify/... ./internal/base/proc/... ./internal/platform/remote/... ./internal/platform/repair/... ./internal/safety/sandbox/... ./internal/base/sysproxy/... ./internal/state/workspacelease/... ./cmd/...
# Worktree rollback is absent from the broad Windows PR smoke list.
- name: test (Windows worktree rollback)
if: env.RUN_STEPS == 'true' && runner.os == 'Windows' && github.event_name == 'pull_request'
timeout-minutes: 3
run: go test -count=1 -timeout=2m -run '^TestDiscardUnpublished.*OnWindows$|^TestCreateFromDetachedHEADAndOccupiedBranch$' ./internal/platform/worktree
# The general Windows PR smoke list intentionally omits internal/contract/tool.
# Keep the session-temp portability contract covered without widening the
@@ -178,7 +218,7 @@ jobs:
- name: test (Windows session temp)
if: env.RUN_STEPS == 'true' && runner.os == 'Windows' && github.event_name == 'pull_request'
timeout-minutes: 3
run: go test -timeout=2m -run '^TestBashSharesSessionTempAcrossCalls$' ./internal/tools/builtin
run: go test -count=1 -timeout=2m -run '^TestBashSharesSessionTempAcrossCalls$' ./internal/tools/builtin
# Shell execution contract: PowerShell identity, Chinese workspace paths,
# UTF-8 output, and ExitCode retention must gate PRs on native Windows.
@@ -186,18 +226,14 @@ jobs:
- name: test (Windows shell execution contract)
if: env.RUN_STEPS == 'true' && runner.os == 'Windows' && github.event_name == 'pull_request'
timeout-minutes: 5
run: go test -timeout=3m -run '^TestBashPowerShellExecuteDetailedContract$|^TestBashPowerShell51PreflightRejectsAndAndDetailed$|^TestBashPowerShellOutputIsUTF8$|^TestBashPowerShellSurfacesNonZeroExit$|^TestBashPowerShellRejectsChaining$' ./internal/tools/builtin
run: go test -count=1 -timeout=3m -run '^TestBashPowerShellExecuteDetailedContract$|^TestBashPowerShell51PreflightRejectsAndAndDetailed$|^TestBashPowerShellOutputIsUTF8$|^TestBashPowerShellSurfacesNonZeroExit$|^TestBashPowerShellRejectsChaining$' ./internal/tools/builtin
# Full push suite still uses the same 8m package budget as Windows smoke:
# agent/boot/control already hit 180s package timeouts under runner load,
# so 3m fails on slow machines without a code regression. The step budget
# is what runner variance costs rather than what the suite takes: green
# runs on this branch spread 520s-976s while three pushes timed out at
# 1287-1305s, so 20m sat inside the distribution and reported a slow
# runner as failed tests. A hang still dies here rather than on a package.
# Full push suite uses the same 18m package timeout as Windows smoke and a
# 35m step budget above it. -count=1 as at the smoke step. A failure prints
# the failed tests' output and the 15 slowest boot tests.
- name: test (full)
if: env.RUN_STEPS == 'true' && runner.os == 'Windows' && github.event_name != 'pull_request'
timeout-minutes: 30
timeout-minutes: 35
env:
# Run the prompt-cache prefix-stability guard (TestCacheHit*) in CI: a
# regression there silently tanks the cache hit rate the project is
@@ -206,16 +242,34 @@ jobs:
# Bound sandbox helper children in Windows tests so a failed OS-level
# launch cannot pin the Actions step after Go's package timeout fires.
WINDOWS_SANDBOX_WAIT_MS: "20000"
run: go test -p 4 -timeout=8m ./...
shell: bash
run: |
go test -count=1 -p 4 -timeout=18m -json ./... | tee "$RUNNER_TEMP/full.jsonl" |
jq -rj --unbuffered 'select(.Action == "output" and .Test == null) | .Output'
- name: test (full) failure detail
if: failure() && env.RUN_STEPS == 'true' && runner.os == 'Windows' && github.event_name != 'pull_request'
shell: bash
run: |
f="$RUNNER_TEMP/full.jsonl"
[ -s "$f" ] || exit 0
echo "::group::output of failed tests"
jq -rn '[inputs | select(.Test != null)] | group_by(.Package + "/" + .Test)
| map(select(any(.[]; .Action == "fail")))[]
| map(select(.Action == "output") | .Output) | join("")' "$f" || true
echo "::endgroup::"
echo "slowest internal/assembly/boot tests (seconds, name):"
jq -rn '[inputs | select((.Package | endswith("/internal/assembly/boot")) and .Test != null and .Action == "pass")]
| sort_by(-.Elapsed)[:15][] | "\(.Elapsed)\t\(.Test)"' "$f" || true
race:
needs: changes
if: always() && (github.event_name != 'pull_request' || needs.changes.outputs.code != 'false')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions/setup-go@v7
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with:
go-version-file: go.mod
cache: true
@@ -242,7 +296,7 @@ jobs:
if: github.event_name == 'pull_request'
env:
REASONIX_RELEASE_CACHE_GUARD: "1"
run: go test -race ./internal/runtime/agent/... ./internal/ext/plugin/... ./internal/runtime/jobs/... ./internal/base/proc/... ./internal/safety/sandbox/... ./internal/base/filelock/... ./internal/contract/eventwire/... ./internal/platform/remote/... ./internal/ext/extension/... ./internal/assembly/boot/... ./internal/session/control/... ./internal/contract/tool/...
run: go test -race ./internal/runtime/agent/... ./internal/ext/plugin/... ./internal/tools/jobs/... ./internal/base/proc/... ./internal/safety/sandbox/... ./internal/base/filelock/... ./internal/contract/eventwire/... ./internal/platform/remote/... ./internal/ext/extension/... ./internal/assembly/boot/... ./internal/session/control/... ./internal/contract/tool/...
- name: test -race (full)
if: github.event_name != 'pull_request'
@@ -261,18 +315,18 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
runs-on: ${{ github.event_name == 'pull_request' && needs.changes.outputs.platform == 'false' && 'ubuntu-latest' || matrix.os }}
env:
RUN_STEPS: ${{ github.event_name != 'pull_request' || needs.changes.outputs.sdk != 'false' }}
RUN_STEPS: ${{ github.event_name != 'pull_request' || (needs.changes.outputs.sdk != 'false' && (matrix.os == 'ubuntu-latest' || needs.changes.outputs.platform != 'false')) }}
defaults:
run:
working-directory: sdk/go
steps:
- if: env.RUN_STEPS == 'true'
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- if: env.RUN_STEPS == 'true'
uses: actions/setup-go@v6
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: sdk/go/go.mod
@@ -317,9 +371,9 @@ jobs:
run:
working-directory: desktop
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions/setup-go@v7
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with:
go-version-file: desktop/go.mod
cache: true
@@ -347,7 +401,7 @@ jobs:
run: go vet ./...
- name: golangci-lint
uses: golangci/golangci-lint-action@v9
uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9
with:
version: v2.12.2
working-directory: desktop
@@ -368,15 +422,15 @@ jobs:
# compile it against a real macOS toolchain.
desktop-macos:
needs: changes
if: always() && (github.event_name != 'pull_request' || needs.changes.outputs.desktop != 'false')
if: always() && (github.event_name != 'pull_request' || (needs.changes.outputs.desktop != 'false' && needs.changes.outputs.platform != 'false'))
runs-on: macos-latest
defaults:
run:
working-directory: desktop
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions/setup-go@v7
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with:
go-version-file: desktop/go.mod
cache: true
@@ -386,7 +440,7 @@ jobs:
run: go test -race ./...
- name: golangci-lint
uses: golangci/golangci-lint-action@v9
uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9
with:
version: v2.12.2
working-directory: desktop
@@ -396,16 +450,16 @@ jobs:
# (//go:build windows), so the ubuntu leg above never compiles them.
desktop-windows:
needs: changes
if: always() && (github.event_name != 'pull_request' || needs.changes.outputs.desktop != 'false')
if: always() && (github.event_name != 'pull_request' || (needs.changes.outputs.desktop != 'false' && needs.changes.outputs.platform != 'false'))
runs-on: windows-latest
defaults:
run:
shell: bash
working-directory: desktop
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions/setup-go@v7
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with:
go-version-file: desktop/go.mod
cache: true
@@ -422,9 +476,9 @@ jobs:
if: always() && (github.event_name != 'pull_request' || needs.changes.outputs.code != 'false' || needs.changes.outputs.desktop != 'false' || needs.changes.outputs.sdk != 'false')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions/setup-go@v7
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with:
go-version-file: go.mod
cache: true
@@ -443,7 +497,7 @@ jobs:
run: echo "version=$(cat .golangci-version)" >> "$GITHUB_OUTPUT"
- name: golangci-lint
uses: golangci/golangci-lint-action@v9
uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9
with:
version: ${{ steps.golangci.outputs.version }}
args: --timeout=5m
@@ -467,7 +521,9 @@ jobs:
run: |
go run github.com/rhysd/actionlint/cmd/[email protected] \
-ignore 'label "windows-11-arm" is unknown' \
-ignore 'label "ubuntu-22.04-arm" is unknown' \
.github/workflows/release-studio.yml \
.github/workflows/studio-certum-signing-smoke.yml \
.github/workflows/ci.yml
govulncheck:
@@ -476,9 +532,9 @@ jobs:
runs-on: ubuntu-latest
continue-on-error: true # informational — stdlib vulns need a Go patch release
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions/setup-go@v7
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with:
go-version-file: go.mod
cache: true
@@ -494,9 +550,9 @@ jobs:
if: always() && (github.event_name != 'pull_request' || needs.changes.outputs.code != 'false')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions/setup-go@v7
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with:
go-version-file: go.mod
cache: true
@@ -505,7 +561,7 @@ jobs:
run: go test -coverprofile=coverage.out -covermode=atomic ./...
- name: upload coverage
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
with:
name: coverage-report
path: coverage.out
+35
View File
@@ -0,0 +1,35 @@
name: Close linked issues
# GitHub closes an issue named by "Fixes #N" only when the pull request merges
# into the default branch. This does the same for the 1.x line. It reads the
# merged pull request's description and never checks out its code.
on:
pull_request_target:
types: [closed]
branches: [main-v2]
permissions:
issues: write
jobs:
close:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
with:
script: |
const pr = context.payload.pull_request;
const { owner, repo } = context.repo;
// GitHub's own closing-keyword grammar, applied to this repository's issues only.
const keyword = /\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s*:?\s+#(\d+)\b/gi;
const numbers = [...new Set([...(pr.body ?? '').matchAll(keyword)].map((m) => Number(m[1])))];
const sha = (pr.merge_commit_sha ?? '').slice(0, 9);
for (const issue_number of numbers) {
const { data } = await github.rest.issues.get({ owner, repo, issue_number });
if (data.pull_request || data.state !== 'open') continue;
await github.rest.issues.createComment({ owner, repo, issue_number,
body: `Fixed on the 1.x line by #${pr.number} (${sha}). It ships in the next 1.x release.` });
await github.rest.issues.update({ owner, repo, issue_number, state: 'closed', state_reason: 'completed' });
core.info(`closed #${issue_number}`);
}
+27 -14
View File
@@ -4,23 +4,25 @@ on:
# Docs/release-notes/root-Markdown changes carry no analyzable code; the
# weekly cron keeps the full scan cadence regardless.
push:
branches: ["main-v2"]
branches: ["main-v2", "studio"]
paths-ignore:
- 'docs/**'
- 'release-notes/**'
- '*.md'
pull_request:
branches: ["main-v2"]
branches: ["main-v2", "studio"]
paths-ignore:
- 'docs/**'
- 'release-notes/**'
- '*.md'
# A schedule runs only from the default branch, so the weekly scan checks out
# both lines itself: 1.x is in maintenance, and security is part of it.
schedule:
- cron: "27 3 * * 1"
jobs:
analyze:
name: Analyze (${{ matrix.language }})
name: Analyze (${{ matrix.language }}${{ matrix.branch && format(', {0}', matrix.branch) || '' }})
runs-on: ubuntu-latest
permissions:
security-events: write
@@ -30,21 +32,32 @@ jobs:
strategy:
fail-fast: false
matrix:
include:
- language: go
build-mode: autobuild
- language: javascript-typescript
build-mode: none
- language: actions
build-mode: none
language: [go, javascript-typescript, actions]
# Empty means "the ref this event is about"; a scheduled run names each line.
branch: ${{ fromJSON(github.event_name == 'schedule' && '["main-v2", "studio"]' || '[""]') }}
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ matrix.branch }}
- id: head
if: matrix.branch != ''
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- name: Initialize CodeQL
uses: github/codeql-action/init@v4.37.4
uses: github/codeql-action/init@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
build-mode: ${{ matrix.language == 'go' && 'autobuild' || 'none' }}
- name: Perform CodeQL Analysis
uses: github/codeql-action/[email protected]
if: matrix.branch == ''
uses: github/codeql-action/analyze@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
with:
category: "/language:${{ matrix.language }}"
# Results from a checked-out line are filed under that line's ref, not the
# default branch the schedule happened to run from.
- name: Perform CodeQL Analysis (${{ matrix.branch }})
if: matrix.branch != ''
uses: github/codeql-action/analyze@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
with:
category: "/language:${{ matrix.language }}"
ref: refs/heads/${{ matrix.branch }}
sha: ${{ steps.head.outputs.sha }}
+3 -3
View File
@@ -19,12 +19,12 @@ jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v6.0.9
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
with:
version: 10
run_install: false
- uses: actions/setup-node@v7
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7
with:
node-version: "24"
cache: pnpm
+2 -2
View File
@@ -19,8 +19,8 @@ jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7
with:
node-version: '22'
cache: npm
+3 -3
View File
@@ -19,12 +19,12 @@ jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v6.0.9
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
with:
version: 10
run_install: false
- uses: actions/setup-node@v7
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7
with:
node-version: "24"
cache: pnpm
+44
View File
@@ -0,0 +1,44 @@
name: Deploy ops events relay
on:
workflow_dispatch:
permissions:
contents: read
concurrency:
group: deploy-ops-events
cancel-in-progress: false
jobs:
deploy:
if: github.ref == 'refs/heads/platform'
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7
with:
node-version: '22'
cache: npm
cache-dependency-path: workers/ops-events/package-lock.json
- name: Validate
working-directory: workers/ops-events
run: |
npm ci
npm run typecheck
npm test
- name: Deploy
working-directory: workers/ops-events
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
run: npx wrangler deploy
- name: Sync secrets
working-directory: workers/ops-events
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
GITHUB_WEBHOOK_SECRET: ${{ secrets.OPS_GITHUB_WEBHOOK_SECRET }}
OPS_LISTEN_TOKEN: ${{ secrets.OPS_LISTEN_TOKEN }}
OPS_EMIT_TOKEN: ${{ secrets.OPS_EMIT_TOKEN }}
run: |
node -e 'process.stdout.write(JSON.stringify({GITHUB_WEBHOOK_SECRET:process.env.GITHUB_WEBHOOK_SECRET||null,OPS_LISTEN_TOKEN:process.env.OPS_LISTEN_TOKEN||null,OPS_EMIT_TOKEN:process.env.OPS_EMIT_TOKEN||null}))' | npx wrangler secret bulk
+49
View File
@@ -0,0 +1,49 @@
name: Deploy Web Studio
on:
push:
branches: [studio]
paths:
- "desktop/frontend-next/**"
- ".github/workflows/deploy-web-studio.yml"
permissions:
contents: read
concurrency:
group: web-studio-production
cancel-in-progress: true
jobs:
deploy:
runs-on: ubuntu-latest
defaults:
run:
working-directory: desktop/frontend-next
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
with:
version: 10
run_install: false
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7
with:
node-version: "24"
cache: pnpm
cache-dependency-path: desktop/frontend-next/pnpm-lock.yaml
- name: Install
run: pnpm install --frozen-lockfile
- name: Build
env:
VITE_REMOTE_HOME: https://reasonix.io/remote/
run: pnpm build
- name: Deploy
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
run: npx --yes [email protected] deploy --config wrangler.jsonc
+4 -4
View File
@@ -32,7 +32,7 @@ jobs:
environment: e2e-bot
steps:
- name: Acknowledge
uses: actions/github-script@v9
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
with:
script: |
await github.rest.reactions.createForIssueComment({
@@ -42,16 +42,16 @@ jobs:
# Default-branch checkout: this is where the harness (cmd/e2ebench), the
# suite, and a run --metrics-capable agent live.
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0
- uses: actions/setup-go@v7
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with:
go-version-file: go.mod
cache: true
- uses: actions/setup-python@v7
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
with:
python-version: '3.12'
+77
View File
@@ -0,0 +1,77 @@
name: Sync in-app feedback
# Turns feedback submitted from Reasonix Studio into issues and reports issue
# state (in progress, fixed in a tag, won't fix, duplicate) back to the worker,
# and mirrors the reporter's replies onto their issue as bot comments.
# Text from the public reaches the script only through the worker's JSON and
# the GitHub API, never through a shell command line or an expression.
on:
schedule:
- cron: '*/10 * * * *'
- cron: '5 3 * * *'
workflow_dispatch:
inputs:
dry_run:
description: Print what would happen without creating issues or writing back
required: false
default: false
type: boolean
full:
description: Re-read every open entry instead of only recently changed issues
required: false
default: false
type: boolean
permissions:
contents: read
issues: write
concurrency:
group: feedback-sync
cancel-in-progress: false
jobs:
sync:
if: github.repository == 'esengine/DeepSeek-Reasonix'
runs-on: ubuntu-latest
timeout-minutes: 8
steps:
- name: Check secret
id: gate
env:
FEEDBACK_ADMIN_TOKEN: ${{ secrets.FEEDBACK_ADMIN_TOKEN }}
run: |
if [ -z "$FEEDBACK_ADMIN_TOKEN" ]; then
echo "::notice::FEEDBACK_ADMIN_TOKEN is not set; feedback sync skipped"
echo "run=false" >> "$GITHUB_OUTPUT"
else
echo "run=true" >> "$GITHUB_OUTPUT"
fi
- if: steps.gate.outputs.run == 'true'
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
# git tag --contains needs full history; the tag API would cost a request per entry.
fetch-depth: 0
persist-credentials: false
- if: steps.gate.outputs.run == 'true'
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7
with:
node-version: '22'
- name: Sync
if: steps.gate.outputs.run == 'true'
env:
FEEDBACK_ADMIN_TOKEN: ${{ secrets.FEEDBACK_ADMIN_TOKEN }}
# crash.reasonix.io answers runner IPs with a managed challenge; workers.dev serves only the converter's admin paths.
FEEDBACK_BASE_URL: https://reasonix-crash-report.reasonix.workers.dev
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DRY_RUN: ${{ github.event.inputs.dry_run || 'false' }}
FULL: ${{ github.event.inputs.full || 'false' }}
SCHEDULE: ${{ github.event.schedule }}
run: |
set -euo pipefail
args=()
if [ "$DRY_RUN" = "true" ]; then args+=(--dry-run); fi
# The daily schedule sweeps everything the change window could have missed.
if [ "$FULL" = "true" ] || [ "$SCHEDULE" = "5 3 * * *" ]; then args+=(--full); fi
node scripts/feedback-sync.mjs "${args[@]}"
+3 -3
View File
@@ -20,7 +20,7 @@ jobs:
classify:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v9
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
env:
DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
with:
@@ -35,7 +35,7 @@ jobs:
const body = (issue.body || '').slice(0, 4000);
const system = [
'You categorize GitHub issues for Reasonix, a Go-based AI coding agent with a Wails desktop app and a terminal UI.',
'You categorize GitHub issues for Reasonix, a Go-based AI coding agent with an Electron desktop app and a terminal UI.',
'Pick labels ONLY from these fixed sets. Never invent labels.',
'area (0-2, the affected subsystem):',
' agent: core agent loop / tool-calling / reasoning',
@@ -43,7 +43,7 @@ jobs:
' config: configuration, setup wizard, .toml/.env',
' updater: auto-update, installer, release packaging',
' provider: model providers, model selection/switching',
' desktop: Wails desktop GUI',
' desktop: Electron desktop GUI',
' tui: terminal UI / CLI',
' skills: skills system',
' rendering: terminal rendering / flicker / repaint',
+116
View File
@@ -0,0 +1,116 @@
name: Issue replies
# Posts maintainer replies as github-actions[bot], so a batch of triage answers
# spends the repository's quota instead of a person's content-creation limit.
on:
workflow_dispatch:
inputs:
replies:
description: 'JSON array of {"issue": 123, "body": "...", "close": true | "not_planned"} or {"discussion": 123, "body": "...", "close": "resolved" | "outdated" | "duplicate"}'
required: true
type: string
interval:
description: Seconds between entries
required: false
default: '20'
type: string
permissions:
issues: write
pull-requests: write
discussions: write
concurrency:
group: issue-replies
cancel-in-progress: false
jobs:
reply:
runs-on: win-gpu
timeout-minutes: 180
steps:
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
env:
REPLIES: ${{ inputs.replies }}
INTERVAL: ${{ inputs.interval }}
with:
script: |
const replies = JSON.parse(process.env.REPLIES);
if (!Array.isArray(replies)) throw new Error('replies must be a JSON array');
const { owner, repo } = context.repo;
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const failed = [];
// Only a rate limit is worth waiting out; any other refusal is final.
const rateLimited = (error) => {
const headers = error.response?.headers ?? {};
return error.status === 429 ||
(error.status === 403 && (headers['retry-after'] !== undefined || headers['x-ratelimit-remaining'] === '0'));
};
// Discussions have no REST comment API; they are addressed by node id.
const replyToDiscussion = async (number, body) => {
const q = await github.graphql(
`query($owner:String!,$repo:String!,$n:Int!){repository(owner:$owner,name:$repo){discussion(number:$n){id comments(last:100){nodes{body author{login}}}}}}`,
{ owner, repo, n: number });
const d = q.repository.discussion;
if (!d) throw new Error('no such discussion');
if (d.comments.nodes.some((c) => c.author?.login === 'github-actions' && c.body === body)) return false;
await github.graphql(`mutation($id:ID!,$body:String!){addDiscussionComment(input:{discussionId:$id,body:$body}){comment{id}}}`, { id: d.id, body });
return true;
};
const closeReasons = { resolved: 'RESOLVED', outdated: 'OUTDATED', duplicate: 'DUPLICATE' };
const closeDiscussion = async (number, reason) => {
const q = await github.graphql(
`query($owner:String!,$repo:String!,$n:Int!){repository(owner:$owner,name:$repo){discussion(number:$n){id closed}}}`,
{ owner, repo, n: number });
const d = q.repository.discussion;
if (d.closed) return;
await github.graphql(`mutation($id:ID!,$r:DiscussionCloseReason!){closeDiscussion(input:{discussionId:$id,reason:$r}){discussion{id}}}`, { id: d.id, r: reason });
};
for (const { issue, discussion, body, close } of replies) {
if (Number.isInteger(discussion) && typeof body === 'string' && body.trim() !== '') {
try {
const posted = await replyToDiscussion(discussion, body);
if (close !== undefined && close !== false) {
const reason = closeReasons[close];
if (!reason) throw new Error(`unknown close reason ${close}`);
await closeDiscussion(discussion, reason);
}
core.info(`discussion #${discussion}: ${posted ? 'replied' : 'already replied'}${close ? ', closed' : ''}`);
} catch (error) {
failed.push(`discussion ${discussion}: ${error.message}`);
}
await sleep(Math.max(1, Number(process.env.INTERVAL) || 20) * 1000);
continue;
}
if (!Number.isInteger(issue) || typeof body !== 'string' || body.trim() === '') {
failed.push(`${issue}: malformed entry`);
continue;
}
try {
const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number: issue, per_page: 100 });
const posted = comments.some((c) => c.user?.type === 'Bot' && c.body === body);
if (!posted) {
for (let attempt = 0; ; attempt++) {
try {
await github.rest.issues.createComment({ owner, repo, issue_number: issue, body });
break;
} catch (error) {
if (attempt >= 5 || !rateLimited(error)) throw error;
core.warning(`#${issue}: ${error.message}; retrying in 2 minutes`);
await sleep(120_000);
}
}
}
if (close === true || close === 'not_planned') {
const { data } = await github.rest.issues.get({ owner, repo, issue_number: issue });
if (data.state === 'open') {
await github.rest.issues.update({ owner, repo, issue_number: issue, state: 'closed', state_reason: close === 'not_planned' ? 'not_planned' : 'completed' });
}
}
core.info(`#${issue}: ${posted ? 'already replied' : 'replied'}${close ? ', closed' : ''}`);
} catch (error) {
failed.push(`${issue}: ${error.message}`);
}
await sleep(Math.max(1, Number(process.env.INTERVAL) || 20) * 1000);
}
if (failed.length) core.setFailed(`not done:\n${failed.join('\n')}`);
+26 -25
View File
@@ -2,7 +2,8 @@ name: Label issue version
# Issue forms can't let a reporter set a label directly (that needs triage rights),
# so the bug/feature forms carry a "Version line" dropdown and this workflow reads
# the submitted value and applies the matching v1/v2 label on their behalf.
# the submitted value and applies the matching label on their behalf:
# 0.x -> v1 (TypeScript), 1.x -> v2 (main-v2), 2.x -> v3 (studio).
on:
issues:
types: [opened, edited]
@@ -18,46 +19,46 @@ jobs:
label:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v9
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
with:
script: |
const body = context.payload.issue.body || '';
const sectionOf = (name) =>
body.split(/^###\s+/m).find((s) => new RegExp(`^${name}`, 'i').test(s)) || '';
const labelOf = (major) => ({ 0: 'v1', 1: 'v2', 2: 'v3' })[major];
const m = sectionOf('Version line').match(/\bv([12])\b/i);
const m = sectionOf('Version line').match(/\b([012])\.x\b/);
if (!m) {
core.info('No version line found; nothing to label.');
return;
}
let choice = 'v' + m[1];
let choice = labelOf(Number(m[1]));
// The dropdown is a choice, and v2 sits first, so it gets picked by
// reporters who are actually on 0.x. `Exact version` is pasted from
// `reasonix --version`, so when it parses it is the better evidence:
// 0.x is the TypeScript line, 1.x the Go rewrite. Anything that does
// not parse (a commit sha, "main-v2") leaves the dropdown to decide.
// The dropdown is a choice, and its first entry gets picked by
// reporters who never looked. `Exact version` is pasted from
// `reasonix --version` or Studio, so when it parses it is the better
// evidence. Anything that does not parse (a commit sha, a branch
// name) or names no known line leaves the dropdown to decide.
const exact = sectionOf('Exact version').match(/\b(\d+)\.(\d+)\.(\d+)/);
if (exact) {
const fromVersion = Number(exact[1]) === 0 ? 'v1' : 'v2';
if (fromVersion !== choice) {
core.info(`Version line says ${choice} but ${exact[0]} says ${fromVersion}; trusting the version.`);
choice = fromVersion;
}
const fromVersion = exact && labelOf(Number(exact[1]));
if (fromVersion && fromVersion !== choice) {
core.info(`Version line says ${choice} but ${exact[0]} says ${fromVersion}; trusting the version.`);
choice = fromVersion;
}
const other = choice === 'v2' ? 'v1' : 'v2';
await github.rest.issues.addLabels({
...context.repo,
issue_number: context.issue.number,
labels: [choice],
});
// Keep v1/v2 mutually exclusive in case an edit flipped the choice.
try {
await github.rest.issues.removeLabel({
...context.repo,
issue_number: context.issue.number,
name: other,
});
} catch (e) {
core.info(`No ${other} label to remove.`);
// Keep the line labels mutually exclusive in case an edit flipped the choice.
for (const other of ['v1', 'v2', 'v3'].filter((l) => l !== choice)) {
try {
await github.rest.issues.removeLabel({
...context.repo,
issue_number: context.issue.number,
name: other,
});
} catch (e) {
core.info(`No ${other} label to remove.`);
}
}
+1 -1
View File
@@ -23,6 +23,6 @@ jobs:
label:
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v7
- uses: actions/labeler@bf12e9b00b37c5c0ca2b87b79b2daf7891dbda13 # v7
with:
sync-labels: false
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
label:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v9
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
with:
script: |
const base = context.payload.pull_request.base.ref;
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -24,7 +24,7 @@ jobs:
if: github.event.comment.user.type != 'Bot' && github.event.comment.author_association == 'NONE'
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v9
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
with:
script: |
const EMOJI = /[\u{1F300}-\u{1FAFF}\u{2600}-\u{27BF}\u{2B00}-\u{2BFF}\u{FE0F}]/gu;
+2 -2
View File
@@ -40,10 +40,10 @@ jobs:
if: github.repository == 'esengine/DeepSeek-Reasonix'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0
- uses: actions/setup-node@v7
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7
with:
node-version: '22'
- name: Test sweeper
@@ -0,0 +1,117 @@
name: Studio Certum signing smoke test
# Signs two throwaway probes with the certificate release-studio.yml would use,
# and publishes nothing. It reports the signer's subject, which is the value
# STUDIO_SIGNING_SUBJECT must hold.
on:
workflow_dispatch:
permissions:
contents: read
concurrency:
group: certum-signing
cancel-in-progress: false
jobs:
sign:
if: github.repository == 'esengine/DeepSeek-Reasonix' && github.ref == 'refs/heads/studio'
runs-on: windows-2022
environment: studio-release
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with:
go-version-file: go.mod
cache: false
- name: Check signing configuration
shell: pwsh
env:
CERTUM_USERNAME: ${{ secrets.CERTUM_USERNAME }}
CERTUM_OTP_URI: ${{ secrets.CERTUM_OTP_URI }}
CERTUM_KEY_ID: ${{ secrets.CERTUM_KEY_ID }}
run: |
$ErrorActionPreference = 'Stop'
if ([string]::IsNullOrWhiteSpace($env:CERTUM_USERNAME)) { throw 'Missing secret CERTUM_USERNAME' }
if ($env:CERTUM_OTP_URI -notmatch '^otpauth://totp/') { throw 'Secret CERTUM_OTP_URI must be a complete TOTP URI' }
if ($env:CERTUM_KEY_ID -notmatch '^[0-9a-fA-F]{40}$') { throw 'Secret CERTUM_KEY_ID must be the 40-character SHA-1 certificate thumbprint' }
- name: Build open-source signing probes
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
# This minimal probe is covered by the repository's MIT license.
New-Item -ItemType Directory payload | Out-Null
'package main; func main() { println("Reasonix Studio Certum signing probe") }' | Set-Content probe.go
$env:GOOS = 'windows'
$env:GOARCH = 'amd64'
$env:CGO_ENABLED = '0'
go build -trimpath -o payload/probe.exe probe.go
if ($LASTEXITCODE -ne 0) { throw 'Probe compilation failed' }
'probe.exe' | Set-Content payload/signing-files.txt
Copy-Item payload/probe.exe installer-probe.exe
- name: Connect to SimplySign cloud certificate
uses: ./.github/actions/setup-certum
with:
username: ${{ secrets.CERTUM_USERNAME }}
otp-uri: ${{ secrets.CERTUM_OTP_URI }}
thumbprint: ${{ secrets.CERTUM_KEY_ID }}
- name: Sign and verify payload and installer probes
shell: pwsh
env:
CERTUM_KEY_ID: ${{ secrets.CERTUM_KEY_ID }}
run: |
./scripts/sign-certum.ps1 -PayloadDirectory payload
./scripts/sign-certum.ps1 -FilePath installer-probe.exe
# The session signs for any process on the runner while it is up.
- name: Disconnect from Certum
if: always()
shell: pwsh
run: Get-Process -Name '*SimplySign*' -ErrorAction SilentlyContinue | Stop-Process -Force
# With the switch on, an unset subject fails here as it would in a
# release; with it off, the value is reported so it can be recorded.
- name: Report and check the signer
shell: pwsh
env:
SIGNING_ENABLED: ${{ vars.STUDIO_SIGNING_ENABLED }}
EXPECTED_SUBJECT: ${{ vars.STUDIO_SIGNING_SUBJECT }}
run: |
$ErrorActionPreference = 'Stop'
$failed = $false
foreach ($file in @('payload/probe.exe', 'installer-probe.exe')) {
$signature = Get-AuthenticodeSignature -LiteralPath $file
$certificate = $signature.SignerCertificate
$stamp = if ($signature.TimeStamperCertificate) { $signature.TimeStamperCertificate.Subject } else { '(none)' }
@("### $file", '', "- subject: ``$($certificate.Subject)``", "- issuer: ``$($certificate.Issuer)``",
"- thumbprint: ``$($certificate.Thumbprint)``", "- timestamp: ``$stamp``", '') >> $env:GITHUB_STEP_SUMMARY
if (-not $signature.TimeStamperCertificate) {
Write-Output "::error title=studio-signing.timestamp-missing::$file carries no timestamp"
$failed = $true
}
if ([string]::IsNullOrWhiteSpace($certificate.Issuer)) {
Write-Output "::error title=studio-signing.issuer-missing::$file has a signer certificate without an issuer"
$failed = $true
}
if ([string]::IsNullOrWhiteSpace($env:EXPECTED_SUBJECT)) {
if ($env:SIGNING_ENABLED -eq 'true') {
Write-Output "::error title=studio-signing.subject-unset::STUDIO_SIGNING_SUBJECT is empty while STUDIO_SIGNING_ENABLED=true"
$failed = $true
} else {
Write-Output "::warning::STUDIO_SIGNING_SUBJECT is not set; record '$($certificate.Subject)'"
}
} elseif ($certificate.Subject -cne $env:EXPECTED_SUBJECT) {
Write-Output "::error title=studio-signing.unexpected-signer::STUDIO_SIGNING_SUBJECT is '$($env:EXPECTED_SUBJECT)' but $file is signed by '$($certificate.Subject)'"
$failed = $true
}
}
if ($failed) { exit 1 }
'Certum payload and installer signing, signer, trust and timestamps passed.' >> $env:GITHUB_STEP_SUMMARY
@@ -0,0 +1,107 @@
name: Studio Windows self-test
# An unsigned Windows installer for trying a branch on a real machine before it
# is released. It runs on the intranet runner, so it is dispatch-only: the
# repository is public, and nothing a pull request writes may run there.
on:
workflow_dispatch:
inputs:
ref:
description: Branch, tag or commit to build
required: false
default: studio
type: string
permissions:
contents: read
concurrency:
group: studio-selftest-windows
cancel-in-progress: true
jobs:
build:
runs-on: win-gpu
timeout-minutes: 90
steps:
# The machine's PATH finds WSL's bash before Git's, and a bash step run in
# WSL sees neither this checkout's toolchain nor Windows paths. cmd, since
# the machine's execution policy refuses the script a powershell step is.
- name: Put Git Bash first on PATH
shell: cmd
run: '>>"%GITHUB_PATH%" echo C:\Program Files\Git\bin'
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ inputs.ref }}
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with:
go-version-file: go.mod
cache: true
cache-dependency-path: go.sum
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
with:
version: 10
run_install: false
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7
with:
node-version: "24"
cache: pnpm
cache-dependency-path: |
desktop/frontend-next/pnpm-lock.yaml
desktop/electron/pnpm-lock.yaml
# A patch above the newest release, as a prerelease: it ranks ahead of
# what is published, so the build does not offer itself that release as
# an update, and behind the next one, which it does offer.
- name: Choose the version
id: version
shell: bash
run: |
latest="$(git ls-remote --tags origin 'refs/tags/studio-v*' | sed -n 's#.*refs/tags/studio-v\([0-9.]*\)$#\1#p' | sort -V | tail -1)"
test -n "$latest" || { echo "::error::no studio-v* release tag"; exit 1; }
IFS=. read -r major minor patch <<< "$latest"
version="$major.$minor.$((patch + 1))-selftest.${{ github.run_number }}"
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "Version: \`$version\` from \`$(git rev-parse --short HEAD)\`" >> "$GITHUB_STEP_SUMMARY"
# The intranet's route to the module proxy drops connections now and then.
# What arrives stays in this machine's module cache, so a retry resumes.
- name: Fetch Go modules
shell: bash
run: for i in 1 2 3 4 5; do go mod download && exit 0; sleep 10; done; exit 1
- name: Build the SPA
working-directory: desktop/frontend-next
shell: bash
run: pnpm install --frozen-lockfile && pnpm build
- name: Build the kernel
working-directory: desktop/electron
shell: bash
env:
REASONIX_VERSION: v${{ steps.version.outputs.version }}
run: pnpm install --frozen-lockfile && pnpm run build:host
- name: Build the bundle
working-directory: desktop/electron
shell: bash
run: pnpm exec electron-builder --dir -c.extraMetadata.version="${{ steps.version.outputs.version }}"
- name: Package the installer
working-directory: desktop/electron
shell: bash
run: |
pnpm exec electron-builder --prepackaged ../../dist/win-unpacked --win nsis \
-c.extraMetadata.version="${{ steps.version.outputs.version }}"
sha256sum ../../dist/ReasonixStudio-windows-amd64-installer.exe | tee -a "$GITHUB_STEP_SUMMARY"
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: studio-selftest-windows-${{ steps.version.outputs.version }}
path: dist/ReasonixStudio-windows-amd64-installer.exe
if-no-files-found: error
retention-days: 7
+417 -57
View File
@@ -16,7 +16,7 @@ permissions:
concurrency:
group: studio-${{ github.ref }}
cancel-in-progress: true
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
frontend:
@@ -26,14 +26,14 @@ jobs:
run:
working-directory: desktop/frontend-next
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: pnpm/action-setup@v6.0.9
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
with:
version: 10
run_install: false
- uses: actions/setup-node@v7
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7
with:
node-version: "24"
cache: pnpm
@@ -81,9 +81,187 @@ jobs:
exit 1
fi
# Real-Chromium guards that run without a kernel. Only the ones that passed
# three consecutive local runs are wired; the rest stay manual (see
# desktop/frontend-next/perf/README.md). Skipped unless the SPA changed.
browser-guards:
name: browser guards
runs-on: ubuntu-latest
timeout-minutes: 15
defaults:
run:
working-directory: desktop/frontend-next
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0
- name: SPA changed?
id: spa
working-directory: .
run: |
if [ "${{ github.event_name }}" = "pull_request" ] &&
git diff --quiet "origin/${{ github.base_ref }}...HEAD" -- desktop/frontend-next; then
echo "run=false" >> "$GITHUB_OUTPUT"
else
echo "run=true" >> "$GITHUB_OUTPUT"
fi
- if: steps.spa.outputs.run == 'true'
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
with:
version: 10
run_install: false
- if: steps.spa.outputs.run == 'true'
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7
with:
node-version: "24"
cache: pnpm
cache-dependency-path: desktop/frontend-next/pnpm-lock.yaml
- if: steps.spa.outputs.run == 'true'
name: Install
run: pnpm install --frozen-lockfile
- if: steps.spa.outputs.run == 'true'
name: Build the perf harness and install Chromium
run: |
pnpm exec vite build --config vite.perf.config.ts
pnpm exec playwright install --with-deps chromium
- if: steps.spa.outputs.run == 'true'
name: Guards
run: |
python3 -m http.server 4399 --directory dist-perf >/dev/null 2>&1 &
for _ in $(seq 30); do curl -sf http://localhost:4399/perf.html >/dev/null && break; sleep 1; done
node perf/intrinsic.mjs
node perf/firstscreen-fonts.mjs
node perf/contrast.mjs
node perf/stickyfold.mjs
node perf/crumb.mjs
node perf/hoverradius.mjs
node perf/jump-ends.mjs
node perf/modelrows.mjs
node perf/explorersize.mjs
node perf/codeeditor-size.mjs
node perf/rowhit.mjs
node perf/tab-pip.mjs
node perf/context-card-hover.mjs
node perf/sessquick.mjs
node perf/sessionrows.mjs
node perf/wheel.mjs
node perf/branch-tooltip.mjs
node perf/branch-menu.mjs
node perf/image-preview.mjs
node perf/user-images.mjs
node perf/toast-anchor.mjs
node perf/addfolder-rest.mjs
node perf/scroll-jump.mjs
node perf/add-project-selects.mjs
node perf/setup-defer.mjs
node perf/narrow.mjs
node perf/settings-rows.mjs
node perf/narrow-steps.mjs
node perf/job-names.mjs
node perf/mcp-actions.mjs
node perf/web-access.mjs
node perf/peers-settings.mjs
node perf/peer-threads.mjs
node perf/text-size.mjs
node perf/fs-scale.mjs
node perf/composer-feel.mjs
node perf/side-panel.mjs
node perf/card-shell.mjs
node perf/line-cards.mjs
node perf/detail-panel.mjs
node perf/brand-lockup.mjs
node perf/notice-line.mjs
node perf/brought-line.mjs
node perf/modal-focus.mjs
node perf/focus-ring.mjs
node perf/narrow-forms.mjs
node perf/i18n-rendered.mjs
node perf/narrow-rail.mjs
node perf/selfcheck.mjs branch-tooltip
# What a hosted page does over the relay, end to end: the production bundle,
# the real Studio host and kernel, a real Chromium; only the accounts API and
# the relay are stand-ins (tools/relayfake). Event-transport changes are
# judged here, because a unit test of one side cannot see the other.
relay-harness:
name: relay harness
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0
- name: Transport changed?
id: scope
env:
BASE_REF: ${{ github.base_ref }}
run: |
paths="desktop/frontend-next internal/frontend/serve internal/platform/remotecloud internal/platform/account tools/relayfake cmd/reasonix-studio-host"
if [ "$GITHUB_EVENT_NAME" = "pull_request" ] &&
git diff --quiet "origin/$BASE_REF...HEAD" -- $paths; then
echo "run=false" >> "$GITHUB_OUTPUT"
else
echo "run=true" >> "$GITHUB_OUTPUT"
fi
- if: steps.scope.outputs.run == 'true'
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with:
go-version-file: go.mod
cache: true
- if: steps.scope.outputs.run == 'true'
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
with:
version: 10
run_install: false
- if: steps.scope.outputs.run == 'true'
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7
with:
node-version: "24"
cache: pnpm
cache-dependency-path: desktop/frontend-next/pnpm-lock.yaml
- if: steps.scope.outputs.run == 'true'
name: Build the host, the stand-ins and the page
run: |
go build -o desktop/frontend-next/relay/bin/studio-host ./cmd/reasonix-studio-host
go build -o desktop/frontend-next/relay/bin/relayfake ./tools/relayfake
cd desktop/frontend-next
pnpm install --frozen-lockfile
pnpm exec vite build --outDir dist-relay --emptyOutDir
pnpm exec playwright install --with-deps chromium
- if: steps.scope.outputs.run == 'true'
name: Scenarios (hosted page over the relay; local page on a flapping network)
working-directory: desktop/frontend-next
env:
RELAY_HOST_BIN: ${{ github.workspace }}/desktop/frontend-next/relay/bin/studio-host
RELAY_FAKE_BIN: ${{ github.workspace }}/desktop/frontend-next/relay/bin/relayfake
RELAY_DIST: ${{ github.workspace }}/desktop/frontend-next/dist-relay
run: |
node relay/live.mjs
node relay/storm.mjs
# A pull request with no platform-relevant change (scripts/ci-platform-scope.sh)
# moves the Windows and macOS legs onto an ubuntu runner with every step
# skipped, so the check names still report without a scarce runner. A push to
# studio, or a failed `scope`, runs all three.
desktop:
name: desktop (${{ matrix.os }})
runs-on: ${{ matrix.os }}
needs: scope
if: always()
runs-on: ${{ github.event_name == 'pull_request' && needs.scope.outputs.platform == 'false' && matrix.os != 'ubuntu-22.04' && 'ubuntu-latest' || matrix.os }}
env:
RUN_STEPS: ${{ github.event_name != 'pull_request' || needs.scope.outputs.platform != 'false' || matrix.os == 'ubuntu-22.04' }}
strategy:
fail-fast: false
matrix:
@@ -92,9 +270,11 @@ jobs:
run:
working-directory: desktop
steps:
- uses: actions/checkout@v7
- if: env.RUN_STEPS == 'true'
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions/setup-go@v7
- if: env.RUN_STEPS == 'true'
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with:
go-version-file: desktop/go.mod
cache: true
@@ -107,20 +287,97 @@ jobs:
# carries build dependencies of its own.
# Same command as `make studio-test`; this job is already in desktop/.
- name: go test
if: env.RUN_STEPS == 'true'
run: go test ./...
- if: env.RUN_STEPS == 'true'
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7
with:
node-version: "24"
- name: CUA process and packaging contracts
if: env.RUN_STEPS == 'true'
timeout-minutes: 2
run: node --test --test-name-pattern=CUA electron/test/unit.mjs
- name: Install Linux native CUA fixture dependencies
if: env.RUN_STEPS == 'true' && runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y xvfb openbox dbus-x11 libgtk-3-dev at-spi2-core
- name: Native CUA application acceptance
if: env.RUN_STEPS == 'true'
timeout-minutes: 6
shell: bash
run: |
if [ "$RUNNER_OS" = Linux ]; then
dbus-run-session -- xvfb-run -a sh -c 'openbox >/tmp/reasonix-openbox.log 2>&1 & export NO_AT_BRIDGE=0 GTK_MODULES=gail:atk-bridge; node electron/test/cua-native.mjs'
else
node electron/test/cua-native.mjs
fi
# The host sits in the main module and nothing else builds it, so its
# dependency closure is checked here: it must stay CGO-free and
# platform-neutral or the Electron bundle cannot ship it. Cross-compiling
# is host-independent, so once is enough.
- name: host stays pure Go and platform-neutral
if: runner.os == 'Linux'
if: env.RUN_STEPS == 'true' && runner.os == 'Linux'
working-directory: .
run: |
for os in windows linux darwin; do
CGO_ENABLED=0 GOOS=$os GOARCH=amd64 go build -o /dev/null ./cmd/reasonix-studio-host
for arch in amd64 arm64; do
CGO_ENABLED=0 GOOS=$os GOARCH=$arch go build -o /dev/null ./cmd/reasonix-studio-host
done
done
# The ARM64 leg queues on a scarcer runner pool, so a pull request runs it
# only when it touches what the leg verifies; a push to studio always does.
# The macOS and Windows package legs follow `platform`, the same rule as the
# desktop matrix above.
scope:
name: package matrix
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.pick.outputs.matrix }}
arm64: ${{ steps.pick.outputs.arm64 }}
platform: ${{ steps.pick.outputs.platform }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0
- id: pick
env:
EVENT: ${{ github.event_name }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: |
arm64=true
platform=true
if [ "$EVENT" = pull_request ]; then
arm64=false
platform=$(git diff -z --name-only --no-renames "$BASE_SHA" HEAD | bash scripts/ci-platform-scope.sh)
if git diff --name-only --no-renames "$BASE_SHA" HEAD |
grep -qE '^(desktop/electron/|desktop/packaging/|desktop/cmd/update-helper/|desktop/cmd/studio-manifest/|desktop/go\.(mod|sum)$|cmd/reasonix-studio-host/|internal/platform/(update|appupdate)/|go\.(mod|sum)$|scripts/(linux-install-smoke|verify-linux-package|verify-studio-artifacts)\.sh$|scripts/(windows-signing-lib|verify-windows-package|verify-windows-authenticode|install-smoke)\.ps1$|\.github/workflows/(studio|release-studio)\.yml$)'; then
arm64=true
fi
fi
legs='[
{"os":"macos-latest","arch":"arm64","platform":"darwin"},
{"os":"ubuntu-22.04","arch":"amd64","platform":"linux"},
{"os":"windows-latest","arch":"amd64","platform":"windows","ebarch":"x64"}
]'
if [ "$platform" = false ]; then
legs="$(jq -c 'map(select(.platform == "linux"))' <<<"$legs")"
elif [ "$arm64" = true ]; then
legs="$(jq -c '. + [{"os":"windows-11-arm","arch":"arm64","platform":"windows","ebarch":"arm64"},{"os":"ubuntu-22.04-arm","arch":"arm64","platform":"linux"}]' <<<"$legs")"
else
legs="$(jq -c . <<<"$legs")"
fi
{
echo "arm64=$arm64"
echo "platform=$platform"
echo "matrix={\"include\":$legs}"
} >> "$GITHUB_OUTPUT"
# The packaging contract, on real artifacts rather than on the config that
# describes them. Unsigned on purpose: signing and notarization are a
# distribution concern and belong to the release workflow, and treating their
@@ -128,55 +385,63 @@ jobs:
# cross-compile while only Windows had ever produced a package.
package:
name: package (${{ matrix.os }})
needs: scope
runs-on: ${{ matrix.os }}
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
include:
- os: macos-latest
arch: arm64
platform: darwin
- os: ubuntu-22.04
arch: amd64
platform: linux
- os: windows-latest
arch: amd64
platform: windows
matrix: ${{ fromJSON(needs.scope.outputs.matrix) }}
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions/setup-go@v7
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with:
go-version-file: desktop/go.mod
cache: true
cache-dependency-path: desktop/go.sum
- uses: pnpm/action-setup@v6.0.9
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
with:
version: 10
run_install: false
- uses: actions/setup-node@v7
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7
with:
node-version: "24"
cache: pnpm
cache-dependency-path: desktop/electron/pnpm-lock.yaml
# electron-builder shells out to dpkg-deb through fakeroot for the .deb.
# electron-builder shells out to dpkg-deb through fakeroot for the .deb and
# to rpmbuild for the .rpm.
- name: Install Linux packaging deps
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y fakeroot
run: sudo apt-get update && sudo apt-get install -y fakeroot rpm cpio xvfb xauth policykit-1
# The fpm electron-builder carries is an x86_64 program, so on an ARM64
# runner the .deb and .rpm are built with one installed from RubyGems.
- name: Use a native fpm on ARM64 Linux
if: runner.os == 'Linux' && runner.arch == 'ARM64'
run: |
sudo apt-get install -y ruby ruby-dev build-essential
sudo gem install --no-document fpm -v 1.16.0
echo "USE_SYSTEM_FPM=true" >> "$GITHUB_ENV"
- name: Build the SPA
working-directory: desktop/frontend-next
run: pnpm install --frozen-lockfile && pnpm build
# The Windows legs name their architecture instead of taking the
# toolchain's, so a runner whose Go or Node is the other architecture
# fails here rather than shipping a mislabelled package.
- name: Build the kernel the shell speaks to
working-directory: desktop/electron
run: pnpm install --frozen-lockfile && pnpm run build:host
env:
REASONIX_GOARCH: ${{ matrix.platform == 'windows' && matrix.arch || '' }}
REASONIX_VERSION: v0.0.0-ci
run: pnpm install --frozen-lockfile && pnpm test && pnpm run build:host
# The .deb's absolute-path payload. A no-op off Linux, so it is not
# guarded here: the one place it must not be forgotten is the one place
@@ -191,7 +456,8 @@ jobs:
working-directory: desktop/electron
env:
CSC_IDENTITY_AUTO_DISCOVERY: "false"
run: pnpm run build:dist
ELECTRON_BUILDER_7Z_FILTER: ${{ matrix.platform == 'windows' && matrix.arch == 'arm64' && 'BCJ' || '' }}
run: pnpm run build:dist ${{ matrix.ebarch && format('--{0}', matrix.ebarch) || '' }} -c.extraMetadata.version=0.0.0-ci
# The release vocabulary is GOARCH. electron-builder's own is not, and the
# rename that reconciles them runs here rather than in the manifest.
@@ -207,6 +473,99 @@ jobs:
exit 1
fi
# An emulated x64 program runs on ARM64 Windows, so a package that merely
# starts proves nothing about its architecture: every PE image in the
# bundle has to be built for the one the package is named for.
- name: Every PE image is built for the package's architecture
if: matrix.platform == 'windows'
run: |
dir=$(find dist -maxdepth 1 -type d -name 'win*-unpacked' | head -1)
test -n "$dir"
node desktop/electron/packaging/pe.js "$dir" "${{ matrix.arch }}"
# An update starts the installer with --updated, and that is what keeps the
# shortcuts (and the taskbar pin hanging off them) from being deleted and
# recreated. Installed twice over itself the way an update does it, the
# desktop shortcut has to still be the same file, pointing at a path with
# no version in it, or every update costs the user their pin.
- name: An update keeps the shortcuts and they name no version
if: matrix.platform == 'windows'
shell: pwsh
run: |
$installer = (Get-ChildItem dist -Filter 'ReasonixStudio-windows-*-installer.exe' | Select-Object -First 1).FullName
$dir = Join-Path $env:RUNNER_TEMP 'rx-update-guard'
Start-Process $installer -ArgumentList '/S', "/D=$dir" -Wait
$lnk = Join-Path ([Environment]::GetFolderPath('Desktop')) 'Reasonix Studio.lnk'
$target = (New-Object -ComObject WScript.Shell).CreateShortcut($lnk).TargetPath
if ($target -ne (Join-Path $dir 'Reasonix Studio.exe')) { throw "the desktop shortcut targets $target, not the stable executable" }
$mark = [datetime]'2020-01-01'
(Get-Item $lnk).LastWriteTime = $mark
Start-Process $installer -ArgumentList '/S', '--updated', "/D=$dir" -Wait
if ((Get-Item $lnk).LastWriteTime -ne $mark) { throw 'an update rewrote the desktop shortcut' }
Start-Process (Join-Path $dir 'Uninstall Reasonix Studio.exe') -ArgumentList '/S', '/currentuser' -Wait
# The release packages a signed copy of the bundle and then reads both
# packages back against it. The same recipe on an unsigned copy, so the
# packaging contract (the installer's one application archive, the pinned
# elevate.exe, every PE image's architecture) is exercised on pull
# requests and not first by a release.
- name: The packages read back against the bundle they were made from
if: matrix.platform == 'windows'
shell: bash
env:
ELECTRON_BUILDER_7Z_FILTER: ${{ matrix.arch == 'arm64' && 'BCJ' || '' }}
run: |
rm -rf payload nsis-src packed zip-src
cp -r "$(find dist -maxdepth 1 -type d -name 'win*-unpacked' | head -1)" payload
rm -f payload/resources/elevate.exe
# The NSIS run writes elevate.exe into the tree it packs, so each
# packager gets a copy and payload stays what was signed.
cp -r payload zip-src
cp -r payload nsis-src
cd desktop/electron
pnpm exec electron-builder --prepackaged ../../zip-src --win zip --${{ matrix.ebarch }} \
-c.extraMetadata.version=0.0.0-ci -c.directories.output=../../packed-zip
pnpm exec electron-builder --prepackaged ../../nsis-src --win nsis --${{ matrix.ebarch }} \
-c.extraMetadata.version=0.0.0-ci -c.directories.output=../../packed
mv ../../packed-zip/ReasonixStudio-windows-${{ matrix.arch }}.zip ../../packed/
ls -la ../../packed
- name: verify-windows-package.ps1 accepts them
if: matrix.platform == 'windows'
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
. ./scripts/windows-signing-lib.ps1
$digest = Get-ManifestDigest -Manifest @(Get-TreeManifest -Root payload)
./scripts/verify-windows-package.ps1 `
-PayloadDirectory payload `
-ExpectedPayloadDigest $digest `
-InstallerPath "packed/ReasonixStudio-windows-${{ matrix.arch }}-installer.exe" `
-PortableArchivePath "packed/ReasonixStudio-windows-${{ matrix.arch }}.zip" `
-OutputPath "$env:RUNNER_TEMP/verify-output.txt" `
-Architecture ${{ matrix.arch }}
Get-Content "$env:RUNNER_TEMP/verify-output.txt"
# The release's own install smoke, against the unsigned installer: the
# package is installed where it is meant to run, its tree checked, and
# removed.
- name: The package installs, runs and uninstalls
if: matrix.platform == 'windows'
shell: pwsh
run: |
./scripts/install-smoke.ps1 `
-Installer "dist/ReasonixStudio-windows-${{ matrix.arch }}-installer.exe" `
-Architecture ${{ matrix.arch }}
- name: Keep the arm64 installer for the x64 refusal check
if: matrix.platform == 'windows' && matrix.arch == 'arm64'
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: studio-arm64-installer-ci
path: dist/ReasonixStudio-windows-arm64-installer.exe
if-no-files-found: error
retention-days: 1
# The update channel is the signed manifest. A second answer to which
# version is current is not a convenience. The .blockmap is the same
# channel's differential index and carries the release prefix, so it also
@@ -233,36 +592,13 @@ jobs:
echo "page: ${page:-<missing>}"
test -n "$host" -a -n "$page"
# Read off the package rather than off the config that describes it. Both
# of these went wrong the same way once: electron-builder names a package
# after package.json unless told, and fpm records the owner it finds on
# disk. Either one leaves a Studio that cannot update itself on Linux and
# says nothing about it until a user asks.
- name: The .deb is Studio's own package and carries what updates it
- name: Linux packages carry their declared architecture and update payload
if: runner.os == 'Linux'
run: |
deb=$(ls dist/ReasonixStudio-linux-*.deb)
name=$(dpkg-deb -f "$deb" Package)
if [ "$name" != "reasonix-studio" ]; then
echo "::error::the package is $name; dpkg holds that beside the installed reasonix-studio instead of upgrading it, and update.StudioLine asks for it by name"
exit 1
fi
dpkg-deb -c "$deb" > contents.txt
# The path is the polkit action's exec.path annotation, so it is the
# only one authorization is granted for; root because polkit will not
# run a helper the invoking user could have written. Either spelling
# of root: dpkg-deb prints the numeric ids it cannot map, and which
# one it prints is a property of the machine reading the package
# rather than of the package.
if ! grep -qE '^-rwxr-xr-x (root/root|0/0) +[0-9]+ .* \./usr/lib/reasonix-studio/reasonix-studio-update-helper$' contents.txt; then
echo "::error::no root-owned executable update helper at the path the polkit action names"
grep reasonix-studio-update-helper contents.txt || echo "(not in the package at all)"
exit 1
fi
if ! grep -q './usr/share/polkit-1/actions/io.reasonix.studio.update.policy$' contents.txt; then
echo "::error::the polkit action is not in the package, so pkexec has nothing to authorize against"
exit 1
fi
run: bash scripts/verify-linux-package.sh "dist/ReasonixStudio-linux-${{ matrix.arch }}.deb" "dist/ReasonixStudio-linux-${{ matrix.arch }}.rpm" "${{ matrix.arch }}"
- name: The Linux package installs, runs, reinstalls and uninstalls
if: runner.os == 'Linux'
run: bash scripts/linux-install-smoke.sh "dist/ReasonixStudio-linux-${{ matrix.arch }}.deb" "${{ matrix.arch }}" 0.0.0-ci
# Recorded, not asserted: the real bundle layout, so later work argues
# from what electron-builder produced rather than from its documentation.
@@ -290,3 +626,27 @@ jobs:
echo "::error::the manifest has no entry for ${{ matrix.platform }}-${{ matrix.arch }}"
exit 1
fi
# The arm64 installer carries one application and no other. Run anywhere but
# ARM64 Windows it has to say so and stop, instead of exiting 0 with nothing
# installed.
arm64-installer-refuses-x64:
name: arm64 installer refuses an x64 machine
needs: [scope, package]
if: needs.scope.outputs.arm64 == 'true'
runs-on: windows-latest
steps:
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: studio-arm64-installer-ci
path: installer
- name: Run it silently
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$target = Join-Path $env:RUNNER_TEMP 'rx-refused'
$run = Start-Process -FilePath installer/ReasonixStudio-windows-arm64-installer.exe -ArgumentList '/S', "/D=$target" -Wait -PassThru
Write-Host "exit code $($run.ExitCode)"
if ($run.ExitCode -eq 0) { throw 'the arm64 installer exited 0 on an x64 machine' }
if (@(Get-ChildItem -Recurse -Force $target -ErrorAction SilentlyContinue).Count -ne 0) { throw 'the arm64 installer wrote into the target on an x64 machine' }
+7 -6
View File
@@ -13,8 +13,8 @@ jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7
with:
node-version: '22'
- name: Update README contributor tables
@@ -22,9 +22,10 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: node scripts/update-acknowledgments.mjs
- name: Open pull request
uses: peter-evans/create-pull-request@v8
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8
with:
token: ${{ secrets.GITHUB_TOKEN }}
# GITHUB_TOKEN-authored pull requests never trigger CI, so required checks would never report.
token: ${{ secrets.ACKNOWLEDGMENTS_TOKEN || secrets.GITHUB_TOKEN }}
branch: chore/update-acknowledgments
delete-branch: true
title: 'Update acknowledgments contributors / 更新致谢贡献者'
@@ -32,6 +33,6 @@ jobs:
body: |
Refreshes the README acknowledgments tables from the GitHub contributors API.
- Source: `repos/esengine/DeepSeek-Reasonix/contributors?per_page=20&anon=1`
- Anonymous contributors are shown without email addresses.
- Source: `repos/esengine/DeepSeek-Reasonix/contributors?per_page=50&anon=1`
- The repository owner, declared aliases, tool identities, bots and entries without a GitHub login are left out (`scripts/credit-identities.json`).
labels: documentation
+2 -2
View File
@@ -24,10 +24,10 @@ jobs:
if: github.repository == 'esengine/DeepSeek-Reasonix'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0
- uses: actions/setup-node@v7
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7
with:
node-version: '22'
- name: Test chart generator
@@ -0,0 +1,29 @@
name: 1.x nightly monitoring
# A schedule runs only from the default branch. These monitors measure the 1.x
# desktop app, which lives on main-v2 alone, so their nightly runs are started
# from here on that branch. Their push and pull_request gates run on main-v2 as
# before.
on:
schedule:
- cron: "17 3 * * *"
- cron: "43 4 * * *"
workflow_dispatch:
permissions:
actions: write
jobs:
dispatch:
runs-on: ubuntu-latest
steps:
- name: App memory screening
if: github.event_name == 'workflow_dispatch' || github.event.schedule == '17 3 * * *'
env:
GH_TOKEN: ${{ github.token }}
run: gh workflow run app-memory.yml --repo "$GITHUB_REPOSITORY" --ref main-v2
- name: Diagnostic overhead monitoring
if: github.event_name == 'workflow_dispatch' || github.event.schedule == '43 4 * * *'
env:
GH_TOKEN: ${{ github.token }}
run: gh workflow run diagnostic-overhead.yml --repo "$GITHUB_REPOSITORY" --ref main-v2
@@ -0,0 +1,84 @@
name: Withdraw a Studio version
# Pulls a published Studio release out of the update catalog (or puts it back),
# so installs stop being offered it without waiting for a new release. Only the
# catalog changes: the release's files stay where they are, and a withdrawn
# release can be restored with the same run.
on:
workflow_dispatch:
inputs:
version:
description: "Version to withdraw or restore, for example 2.36.0"
required: true
type: string
reason:
description: "Why it is withdrawn (shown to users on that version); ignored when restoring"
required: false
type: string
restore:
description: "Put a withdrawn version back instead of withdrawing it"
required: false
type: boolean
default: false
concurrency:
group: studio-catalog
cancel-in-progress: false
jobs:
withdraw:
name: ${{ inputs.restore && 'restore' || 'withdraw' }} ${{ inputs.version }}
runs-on: ubuntu-latest
timeout-minutes: 10
environment: studio-release
permissions:
contents: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: studio
persist-credentials: false
- name: Update the Studio catalog
env:
VERSION: ${{ inputs.version }}
REASON: ${{ inputs.reason }}
RESTORE: ${{ inputs.restore }}
R2_BUCKET: ${{ secrets.R2_BUCKET }}
HAS_R2: ${{ secrets.R2_ACCESS_KEY_ID != '' && secrets.R2_SECRET_ACCESS_KEY != '' && secrets.R2_ACCOUNT_ID != '' && secrets.R2_BUCKET != '' }}
run: |
if [ "$HAS_R2" != "true" ]; then
echo "::error::R2 credentials are not configured, so the catalog cannot be changed"
exit 1
fi
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.]+)?$ ]]; then
echo "::error::version must look like 2.36.0"
exit 1
fi
aws configure set aws_access_key_id "${{ secrets.R2_ACCESS_KEY_ID }}"
aws configure set aws_secret_access_key "${{ secrets.R2_SECRET_ACCESS_KEY }}"
aws configure set default.region auto
endpoint="https://${{ secrets.R2_ACCOUNT_ID }}.r2.cloudflarestorage.com"
at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
current=/tmp/studio-versions-current.json
next=/tmp/studio-versions-next.json
aws s3 cp "s3://${R2_BUCKET}/studio/versions.json" "$current" --endpoint-url "$endpoint"
if [ "$RESTORE" = "true" ]; then
bash scripts/withdraw-version.sh "$current" --restore "$VERSION" "$at" > "$next"
jq -e --arg v "$VERSION" '.versions | map(select((.version | ltrimstr("v")) == $v)) | length == 1' "$next" >/dev/null
else
bash scripts/withdraw-version.sh "$current" "$VERSION" "$REASON" "$at" > "$next"
jq -e --arg v "$VERSION" '(.versions | map(select((.version | ltrimstr("v")) == $v)) | length == 0) and (.withdrawn | map(select((.version | ltrimstr("v")) == $v)) | length == 1)' "$next" >/dev/null
fi
aws s3 cp "$next" "s3://${R2_BUCKET}/studio/versions.json" \
--endpoint-url "$endpoint" \
--content-type "application/json; charset=utf-8" \
--cache-control "public, max-age=300, stale-if-error=86400"
echo "Studio catalog updated for $VERSION (restore=$RESTORE)"
- name: Keep the GitHub release off the latest slot
if: ${{ !inputs.restore }}
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ inputs.version }}
run: gh release edit "studio-v${VERSION}" --prerelease --latest=false --repo "$GITHUB_REPOSITORY"
+2
View File
@@ -15,6 +15,7 @@
/runtime-resume
/extension-protocol-gen
/reasonix-plugin-example
/mcp-line-counter-kit
/reasonix-studio-host
/signpath-contract
/studio-delta
@@ -94,3 +95,4 @@ scripts/official-theme-art/__pycache__/
# running, which on Windows is every rebuild while Studio is open.
*.exe
*.exe~
/relayfake
@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Stage two: sign the finished NSIS container, and only when the exact
executables it was built around are present and already signed. The
authenticode-verify entries are what make the two stages one contract rather
than two independent requests — an installer rebuilt from an unsigned binary
is refused here rather than discovered by a user after it is installed.
Both of the shell's own executables are verified. Listing only one would let
the other be swapped between the stages and still pass.
GitHub Actions wraps this flat bundle in a zip before SignPath receives it. -->
<artifact-configuration xmlns="http://signpath.io/artifact-configuration/v1">
<zip-file>
<pe-file path="*installer*.exe">
<authenticode-sign />
</pe-file>
<pe-file path="Reasonix Studio.exe">
<authenticode-verify />
</pe-file>
<pe-file path="reasonix-studio-host.exe">
<authenticode-verify />
</pe-file>
</zip-file>
</artifact-configuration>
@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Stage one: sign the executables a user actually runs, before anything is
packaged around them. Signing only the finished NSIS container would leave
them unsigned on disk after installation, which is what reputation and ML
heuristics quarantine.
The Electron shell ships two of its own where the Wails one was a single
binary: the window, and the kernel it spawns beside itself. Everything else
the bundle carries is Chromium's or electron-builder's — resources/elevate.exe
is the other PE file — and arrives from its own publisher, so it is not
listed here: this configuration must not re-sign somebody else's binary.
GitHub Actions wraps the flat payload directory in a zip before SignPath
receives it. -->
<artifact-configuration xmlns="http://signpath.io/artifact-configuration/v1">
<zip-file>
<pe-file path="Reasonix Studio.exe">
<authenticode-sign />
</pe-file>
<pe-file path="reasonix-studio-host.exe">
<authenticode-sign />
</pe-file>
</zip-file>
</artifact-configuration>
+7 -3
View File
@@ -6,11 +6,15 @@ allowed_branch_names:
- studio
allowed_build_definitions:
- .github/workflows/release-studio.yml
- .github/workflows/studio-certum-signing-smoke.yml
fingerprint_files:
- .github/workflows/release-studio.yml
- .signpath/artifact-configurations/studio-windows-payload.xml
- .signpath/artifact-configurations/studio-windows-installer.xml
- scripts/complete-signpath-request.ps1
- .github/workflows/studio-certum-signing-smoke.yml
- .github/actions/setup-certum/action.yml
- scripts/install-smoke.ps1
- scripts/sign-certum.ps1
- scripts/verify-windows-authenticode.ps1
- scripts/verify-windows-package.ps1
- scripts/windows-signing-lib.ps1
- desktop/electron/electron-builder.yml
- cmd/signpath-contract/main.go
+44 -2
View File
@@ -162,16 +162,58 @@ ci: add golangci-lint and govulncheck
## Submitting changes
Reasonix ships on two lines (see the
[version roadmap](https://github.com/esengine/DeepSeek-Reasonix/discussions/10748)),
and each takes different changes:
| Line | Branch | Takes |
| --- | --- | --- |
| 2.x | `studio` | Features and fixes: active development |
| 1.x | `main-v2` | Bug fixes, provider/API compatibility, release/updater, security, platform stability |
A fix that matters on both lines lands on `main-v2` and is reviewed for
porting to `studio`; do not open the same PR against both.
1. Fork the repository
2. Create a feature branch from `main-v2`
2. Create a feature branch from the branch your change targets
3. Make your changes with tests
4. Ensure `go test ./...` passes
5. Ensure `gofmt -l .` shows no changes
6. Submit a pull request to `main-v2`
6. Submit a pull request to that same branch
## Pull request policy
**Related changes.** If you have several related changes of one pattern, a
tracking issue plus one pull request is easiest to review.
**Issue first for features.** A change that adds behaviour rather than fixing a
defect should link an issue the maintainers have agreed to before the work
starts. A fix for a reproducible defect may open directly, with the failing test
in the PR. Documentation-only and test-only PRs are reviewed at lower priority.
**Review tiers.**
- Changes under the `sensitive:` paths declared in `REASONIX.md` get a full
review plus a security review, regardless of author.
- Every PR carries the four sections of the PR template: Cause, Blast radius,
Neighbouring behaviours tested, Why this layer.
- The new test must fail without the change.
- A UI change (`desktop/frontend-next/src` or the website pages) fills the
template's UI section: screenshots before and after, light and dark, wide
and phone width.
- The same section carries the line "Entries moved, hidden or removed".
- Hiding or removing a visible control needs a linked issue and a release-notes
line naming it, and updates `docs/STUDIO_PARITY.md` for a 1.x feature.
- Review holds these; no check reads them.
**Transparency.** The PR template has an optional checkbox for disclosing AI
assistance. It is for transparency only and does not change how a change is
reviewed.
## Reporting issues
Open an issue on GitHub with:
- Which line (1.x or 2.x) and the exact version
- Steps to reproduce
- Expected vs actual behavior
- Go version and OS
+122 -53
View File
@@ -18,9 +18,12 @@
<a href="https://esengine.github.io/DeepSeek-Reasonix/">Website</a>
&nbsp;·&nbsp;
<strong><a href="https://discord.gg/XF78rEME2D">Discord</a></strong>
&nbsp;·&nbsp;
<strong><a href="https://qm.qq.com/q/i59b0z2R8s">QQ</a></strong>
</p>
<p align="center">
<a href="https://github.com/esengine/DeepSeek-Reasonix/releases?q=studio-v&expanded=true"><img src="https://img.shields.io/github/v/release/esengine/DeepSeek-Reasonix.svg?filter=studio-v*&include_prereleases&style=flat-square&color=8250df&labelColor=161b22&label=studio%202.x" alt="Reasonix Studio 2.x"/></a>
<a href="https://www.npmjs.com/package/reasonix"><img src="https://img.shields.io/npm/v/reasonix.svg?style=flat-square&color=cb3837&labelColor=161b22&logo=npm&logoColor=white" alt="npm version"/></a>
<a href="https://github.com/esengine/DeepSeek-Reasonix/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/esengine/DeepSeek-Reasonix/ci.yml?style=flat-square&label=ci&labelColor=161b22&logo=githubactions&logoColor=white" alt="CI"/></a>
<a href="./LICENSE"><img src="https://img.shields.io/npm/l/reasonix.svg?style=flat-square&color=8b949e&labelColor=161b22" alt="license"/></a>
@@ -30,6 +33,7 @@
<a href="https://github.com/esengine/DeepSeek-Reasonix/graphs/contributors"><img src="https://img.shields.io/github/contributors/esengine/DeepSeek-Reasonix.svg?style=flat-square&color=bc8cff&labelColor=161b22&logo=github&logoColor=white" alt="contributors"/></a>
<a href="https://github.com/esengine/DeepSeek-Reasonix/discussions"><img src="https://img.shields.io/github/discussions/esengine/DeepSeek-Reasonix.svg?style=flat-square&color=58a6ff&labelColor=161b22&logo=github&logoColor=white" alt="Discussions"/></a>
<a href="https://discord.gg/XF78rEME2D"><img src="https://img.shields.io/badge/discord-join-5865F2.svg?style=flat-square&labelColor=161b22&logo=discord&logoColor=white" alt="Discord"/></a>
<a href="https://qm.qq.com/q/i59b0z2R8s"><img src="https://img.shields.io/badge/QQ%20group-1093562660-12B7F5.svg?style=flat-square&labelColor=161b22&logo=tencentqq&logoColor=white" alt="QQ group 1093562660"/></a>
</p>
<p align="center">
@@ -39,23 +43,83 @@
<br/>
<p align="center"><strong>Open source · MIT · a single Go binary</strong></p>
<h3 align="center">A coding agent you can leave running.</h3>
<p align="center">One local engine, four ways in — terminal, desktop app, browser, or your editor over ACP. Plan mode, permissions, a workspace sandbox and per-turn checkpoints keep a long autonomous run something you can still read and undo.</p>
<div align="center">
<video src="https://github.com/user-attachments/assets/ab2f3878-e224-4931-8254-060e7695cfb9" controls preload="metadata" width="560"></video>
</div>
<br/>
<p align="center"><strong>Open source · MIT · runs on your computer, model of your choice</strong></p>
<h3 align="center">An open-source coding agent for your terminal, desktop, browser and editor.</h3>
<p align="center">It reads your project, edits files and runs commands and tests. Have it ask before each step, or work on its own within limits you set. Files its edit tools changed can be rewound. New here? Start with the Studio desktop app.</p>
<p align="center">Maintained by <strong>Huahui Yu</strong>.</p>
> [!IMPORTANT]
> **Community · 加入社区** — bilingual Discord for setup help (`#help` / `#求助`), workflow showcases, and feature ideas. → **<https://discord.gg/XF78rEME2D>**
>
> QQ group **DeepSeek-Reasonix官方群** (`1093562660`) → **<https://qm.qq.com/q/i59b0z2R8s>** ([QR code](./docs/assets/qq-group.svg))
> Douyin: **做游戏的小鱼** (Douyin ID `22703872788`) · [QR code](./docs/assets/douyin.png)
<br/>
## ❤️Sponsor
<table><tr><td width="180" align="center" valign="middle"><a href="https://www.rightapi.ai/register?aff=e083bf73" target="_blank" rel="sponsored noopener noreferrer"><img src="./.github/sponsor/rightcode.png" alt="Right Code" width="64"></a></td><td valign="middle">Thanks to Right Code for sponsoring this project! Right Code reliably provides routing services for models such as Claude Code, Codex, and Gemini, with both pay-as-you-go and monthly subscription billing options available. Invoices are available upon top-up, and enterprise and team users can receive dedicated one-on-one support. Register via <a href="https://www.rightapi.ai/register?aff=e083bf73" target="_blank" rel="sponsored noopener noreferrer">this link</a>.</td></tr><tr><td width="180" align="center" valign="middle"><a href="https://www.packyapi.ai/register?aff=rIbQ" target="_blank" rel="sponsored noopener noreferrer"><picture><source media="(prefers-color-scheme: dark)" srcset="./.github/sponsor/packycode-dark.svg"><img src="./.github/sponsor/packycode-light.svg" alt="PackyCode" width="80"></picture></a></td><td valign="middle">Thanks to PackyCode for sponsoring this project! Access leading AI models through PackyCode with one API endpoint and one API key, with automatic failover and dedicated high-speed routes for Codex and Claude Code. Register via <a href="https://www.packyapi.ai/register?aff=rIbQ" target="_blank" rel="sponsored noopener noreferrer">this link</a>.</td></tr></table>
## What it is
Reasonix is an open-source AI coding assistant. The program runs on your computer; the AI model is a service you configure, which receives what it reads. You describe a task in plain language; it reads code, edits files and runs commands and tests inside the project folder you choose, and shows you each step.
You pick the model: a DeepSeek preset is built in, any OpenAI-compatible service is one config entry, and you pay the model provider directly.
## What you get
- **Read, edit, run.** It reads code, edits files and runs tests and commands, all inside your project folder.
- **Permission levels.** Have it ask every time, or let everyday actions run on their own; your forbid rules always hold. To see the approach first, use plan mode: it starts work only once you approve.
- **Rewind.** A file is saved before it changes, so you can rewind by conversation turn without touching your git history. Only files its edit tools changed are covered, not what a shell command did.
## 30-second start
Download, add a model key, open a project. New here? Start with the Studio desktop app.
1. Download and install the desktop app, Reasonix Studio, from the [releases page](https://github.com/esengine/DeepSeek-Reasonix/releases?q=studio-v&expanded=true).
2. Open it and enter your model provider and key when asked.
3. Open your project folder and write what you want, for example "add retry on failure to the http client".
4. At the level you chose, it stops and asks before anything that needs your say-so. Allow it to continue, or refuse.
Prefer a terminal? Download the `reasonix` command-line tool, run `reasonix setup` to pick a model, then run `reasonix`. See [Install](#install) and [Quick start](#quick-start).
## Four ways to use it
- **Desktop app (Studio).** A graphical app; the best place to start.
- **Terminal.** A full-screen text interface; `reasonix run "task"` also works in scripts.
- **Browser.** `reasonix web` opens a web interface on your own machine.
- **Editor.** Install the extension in VS Code; other editors that support ACP (Agent Client Protocol, a common way for an editor to connect to an AI assistant) can connect to `reasonix acp`.
All four are the same program underneath. What follows is for advanced users and contributors.
## Versions
Reasonix ships on two lines. See the [version roadmap announcement](https://github.com/esengine/DeepSeek-Reasonix/discussions/10748) for the reasoning behind the split.
| Line | Branch | Status | Get it |
| --- | --- | --- | --- |
| **Reasonix 2.x** | `studio` (this branch) | Active development | [Studio releases](https://github.com/esengine/DeepSeek-Reasonix/releases?q=studio-v&expanded=true) |
| **Reasonix 1.x** | [`main-v2`](https://github.com/esengine/DeepSeek-Reasonix/tree/main-v2) | Maintenance / stable | `npm i -g reasonix` · [desktop download](https://reasonix.io/?download=desktop#start) |
- **Want something stable?** Stay on 1.x. It keeps receiving bug fixes,
provider/API compatibility, updater and security fixes; its core architecture
is no longer being extended.
- **Want the new architecture?** Use 2.x Reasonix Studio. It is still moving
quickly; reports are welcome.
Valuable fixes, tests and behaviour from 1.x are reviewed one by one and ported
or reimplemented on 2.x where they fit the new architecture.
- [Moving from 1.x to 2.x](./docs/MIGRATING.md): what the two lines share, and
how to run both on one machine without losing sessions.
- [2.x roadmap](./docs/ROADMAP.md): what 2.x still has to deliver, how it ships,
and the decisions still open.
## Features
Technical highlights for advanced users.
- **Config-driven.** Providers, the agent, enabled tools, and plugins are all
declared in `reasonix.toml`. No hardcoded models.
- **Multi-model & composable.** DeepSeek ships as a preset; any
@@ -73,53 +137,60 @@
## Install
Choose the path that matches how you want to use Reasonix. The CLI/TUI,
desktop app, and VS Code extension all use the same local Reasonix engine.
This branch is **Reasonix 2.x**. Its desktop app is Reasonix Studio; the CLI,
Studio, and editor integrations all run the same local Reasonix engine.
### Path A: CLI / TUI
### Reasonix Studio (2.x)
Install the native binary through npm on any supported platform, or use
Homebrew on macOS:
Download the package for your platform from the latest
[Studio release](https://github.com/esengine/DeepSeek-Reasonix/releases?q=studio-v&expanded=true)
(tagged `studio-v2.*`; candidates such as `-rc.N` are marked as
pre-releases):
| Platform | Package | Architecture |
| --- | --- | --- |
| macOS | `.dmg` or `.zip` | Apple Silicon (`arm64`) / Intel (`amd64`) |
| Windows | Installer `-installer.exe` or portable `.zip` | x64 |
| Linux | `.deb` or `.rpm` | x64 (`amd64`) / ARM64 (`arm64`, aarch64) |
Every package ships with a `.minisig` signature, and the release carries
`SHA256SUMS`. Once installed, Studio updates itself in place.
The same release also carries the 2.x `reasonix` CLI as archives for
`darwin|linux|windows × amd64|arm64`.
### Reasonix 1.x (stable)
The 1.x CLI installs through npm on any supported platform, or Homebrew on
macOS:
```sh
npm i -g reasonix # any OS; pulls the prebuilt native binary
brew install esengine/reasonix/reasonix # macOS
```
Prebuilt archives (`darwin|linux|windows × amd64|arm64`) and `SHA256SUMS` are on
every [GitHub release](https://github.com/esengine/DeepSeek-Reasonix/releases).
The 1.x desktop app is on the
[official download page](https://reasonix.io/?download=desktop#start). Windows
installers are code-signed with a Certum code-signing certificate.
### Path B: Desktop app
### VS Code extension
Use the [official download page](https://reasonix.io/?download=desktop#start)
for the latest desktop build.
| Platform | Package | Architecture |
| --- | --- | --- |
| macOS | Universal `.dmg` or `.zip` | Apple Silicon / Intel |
| Windows | Installer `.exe` or portable `.zip` | x64 / ARM64 |
| Linux | `.deb` or `.tar.gz` | x64 |
Windows installers are code-signed through [SignPath.io](https://signpath.io/)
with a free certificate provided by the [SignPath Foundation](https://signpath.org/).
### Path C: VS Code extension
Complete Path A first. The extension does not bundle the CLI; it starts your
local `reasonix acp` backend and adds native chat, editor context, tool-call
approvals, model selection, and workspace sessions.
The extension does not bundle the CLI; it starts your local `reasonix acp`
backend and adds native chat, editor context, tool-call approvals, model
selection, and workspace sessions. Install the 1.x CLI first.
- **VS Code:** [install from Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=SivanLiu.reasonix-agent)
- **VSCodium / Eclipse Theia:** [install from Open VSX Registry](https://open-vsx.org/extension/SivanLiu/reasonix-agent)
- **Extension ID:** `SivanLiu.reasonix-agent` · [source and usage guide](https://github.com/SivanCola/reasonix-vscode)
### Path D: Build from source
### Build from source
Clone the repository first:
Clone the repository; `studio` builds 2.x and `main-v2` builds 1.x:
```sh
git clone https://github.com/esengine/DeepSeek-Reasonix.git
cd DeepSeek-Reasonix
git switch studio
```
#### CLI
@@ -146,9 +217,12 @@ webview dependencies and Linux build tags.
## Quick start
### CLI / TUI
### Reasonix Studio
These commands are for the CLI/TUI installed through Path A:
Install and launch Studio, then connect a provider and model in the app. No CLI
setup is needed.
### CLI / TUI
```sh
reasonix setup # configure a provider and model
@@ -159,13 +233,6 @@ reasonix run "implement the TODOs in main.go"
In an interactive session, run `/init` when you want Reasonix to create project
instructions.
### Desktop app
Download the installer for your platform from the
[official download page](https://reasonix.io/?download=desktop#start), install
and launch Reasonix, then configure a provider and model in the app. The CLI
commands above are not required for the desktop app.
For advanced CLI usage and configuration, see the **[CLI reference](./docs/CLI.md)**,
**[Guide](./docs/GUIDE.md)**, and
**[configuration paths](./docs/CONFIG_PATHS.md)**.
@@ -181,8 +248,9 @@ For advanced CLI usage and configuration, see the **[CLI reference](./docs/CLI.m
[Checkpoints & rewind](./docs/CHECKPOINTS.md)
- **Engineering & migration:** [Spec](./docs/SPEC.md) ·
[Task contracts & pause policy](./docs/TASK_CONTRACT.md) ·
[Tool contract](./docs/TOOL_CONTRACT.md) · [Migrating from 0.x](./docs/MIGRATING.md)
- **Extension development:** [Extensions](./docs/EXTENSIONS.md) ·
[Tool contract](./docs/TOOL_CONTRACT.md) · [Moving from 1.x to 2.x](./docs/MIGRATING.md) ·
[2.x roadmap](./docs/ROADMAP.md)
- **Extension development:** [Skills](./docs/SKILLS.md) · [Extensions](./docs/EXTENSIONS.md) ·
[Plugin packages and Manifest v1](./docs/PLUGIN_PACKAGES.md) ·
[Extension Protocol](./docs/EXTENSION_PROTOCOL.md) ·
[Go SDK and starter](./sdk/go/README.md)
@@ -208,11 +276,11 @@ A small list of folks whose work has shaped Reasonix the most — the current to
<!-- reasonix-top-contributors:start -->
| Contributor | Contributor | Contributor | Contributor |
| --- | --- | --- | --- |
| [**SivanCola**](https://github.com/SivanCola) | [**esengine**](https://github.com/esengine) | [**ttmouse**](https://github.com/ttmouse) | [**lifu963**](https://github.com/lifu963) |
| **reasonix** | [**HUQIANTAO**](https://github.com/HUQIANTAO) | [**GTC2080**](https://github.com/GTC2080) | [**light-front-theory**](https://github.com/light-front-theory) |
| **merge-order-check** | [**Li-Charles-One**](https://github.com/Li-Charles-One) | [**eghrhegpe**](https://github.com/eghrhegpe) | **wufengfan** |
| [**CVEngineer66**](https://github.com/CVEngineer66) | [**dependabot\[bot\]**](https://github.com/apps/dependabot) | [**lanshi17**](https://github.com/lanshi17) | [**SuMuxi66**](https://github.com/SuMuxi66) |
| [**CnsMaple**](https://github.com/CnsMaple) | [**cyq1017**](https://github.com/cyq1017) | [**JesonChou**](https://github.com/JesonChou) | [**XTLine**](https://github.com/XTLine) |
| [**SivanCola**](https://github.com/SivanCola) | [**KHG420**](https://github.com/KHG420) | [**ttmouse**](https://github.com/ttmouse) | [**lifu963**](https://github.com/lifu963) |
| [**HUQIANTAO**](https://github.com/HUQIANTAO) | [**Harbor404**](https://github.com/Harbor404) | [**GTC2080**](https://github.com/GTC2080) | [**mchenziyi**](https://github.com/mchenziyi) |
| [**Li-Charles-One**](https://github.com/Li-Charles-One) | [**light-front-theory**](https://github.com/light-front-theory) | [**XTLine**](https://github.com/XTLine) | [**eghrhegpe**](https://github.com/eghrhegpe) |
| [**noreply**](https://github.com/noreply) | [**chuiwenwei**](https://github.com/chuiwenwei) | [**xxoingr**](https://github.com/xxoingr) | [**Bernardxu123**](https://github.com/Bernardxu123) |
| [**HaoyueQin**](https://github.com/HaoyueQin) | [**BuGlessRB**](https://github.com/BuGlessRB) | [**CVEngineer66**](https://github.com/CVEngineer66) | [**JesonChou**](https://github.com/JesonChou) |
<!-- reasonix-top-contributors:end -->
Special thanks to [**Bernardxu123**](https://github.com/Bernardxu123) for designing the project logo and intro video.
@@ -242,8 +310,9 @@ coffee, not a contract — donations don't buy feature priority or change how
issues get triaged.
- **International** — PayPal: [paypal.me/yuhuahui](https://paypal.me/yuhuahui)
- **国内** — 微信支付(扫码)
- **国内** — 微信支付 / 支付宝(扫码)
<p align="center">
<img src=".github/sponsor/wechat-pay.jpg" alt="WeChat Pay QR code" width="180"/>
<img src=".github/sponsor/alipay.jpg" alt="Alipay QR code" width="180"/>
</p>
+129 -47
View File
@@ -18,9 +18,12 @@
<a href="https://esengine.github.io/DeepSeek-Reasonix/">官方网站</a>
&nbsp;·&nbsp;
<strong><a href="https://discord.gg/XF78rEME2D">Discord</a></strong>
&nbsp;·&nbsp;
<strong><a href="https://qm.qq.com/q/i59b0z2R8s">QQ</a></strong>
</p>
<p align="center">
<a href="https://github.com/esengine/DeepSeek-Reasonix/releases?q=studio-v&expanded=true"><img src="https://img.shields.io/github/v/release/esengine/DeepSeek-Reasonix.svg?filter=studio-v*&include_prereleases&style=flat-square&color=8250df&labelColor=161b22&label=studio%202.x" alt="Reasonix Studio 2.x"/></a>
<a href="https://www.npmjs.com/package/reasonix"><img src="https://img.shields.io/npm/v/reasonix.svg?style=flat-square&color=cb3837&labelColor=161b22&logo=npm&logoColor=white" alt="npm version"/></a>
<a href="https://github.com/esengine/DeepSeek-Reasonix/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/esengine/DeepSeek-Reasonix/ci.yml?style=flat-square&label=ci&labelColor=161b22&logo=githubactions&logoColor=white" alt="CI"/></a>
<a href="./LICENSE"><img src="https://img.shields.io/npm/l/reasonix.svg?style=flat-square&color=8b949e&labelColor=161b22" alt="license"/></a>
@@ -30,6 +33,7 @@
<a href="https://github.com/esengine/DeepSeek-Reasonix/graphs/contributors"><img src="https://img.shields.io/github/contributors/esengine/DeepSeek-Reasonix.svg?style=flat-square&color=bc8cff&labelColor=161b22&logo=github&logoColor=white" alt="contributors"/></a>
<a href="https://github.com/esengine/DeepSeek-Reasonix/discussions"><img src="https://img.shields.io/github/discussions/esengine/DeepSeek-Reasonix.svg?style=flat-square&color=58a6ff&labelColor=161b22&logo=github&logoColor=white" alt="Discussions"/></a>
<a href="https://discord.gg/XF78rEME2D"><img src="https://img.shields.io/badge/discord-join-5865F2.svg?style=flat-square&labelColor=161b22&logo=discord&logoColor=white" alt="Discord"/></a>
<a href="https://qm.qq.com/q/i59b0z2R8s"><img src="https://img.shields.io/badge/QQ%20group-1093562660-12B7F5.svg?style=flat-square&labelColor=161b22&logo=tencentqq&logoColor=white" alt="QQ group 1093562660"/></a>
</p>
<p align="center">
@@ -39,21 +43,77 @@
<br/>
<p align="center"><strong>开源 · MIT · 单个 Go 二进制</strong></p>
<h3 align="center">可以一直开着跑的编码 Agent。</h3>
<p align="center">一套本地引擎,四个入口——终端、桌面端、浏览器,或通过 ACP 接入你的编辑器。计划模式、权限、工作区沙箱与逐轮 checkpoint,让长时间自治运行始终可读、可撤销。</p>
<div align="center">
<video src="https://github.com/user-attachments/assets/ab2f3878-e224-4931-8254-060e7695cfb9" controls preload="metadata" width="560"></video>
</div>
<br/>
<p align="center"><strong>开源 · MIT · 程序跑在你电脑上,模型自选</strong></p>
<h3 align="center">终端、桌面、浏览器、编辑器里都能用的 AI 编程助手。</h3>
<p align="center">它会读你的项目、改文件、跑命令和测试。每一步先问你,或在你划定的范围内自己做,由你选;它用编辑工具改过的文件可以退回去。第一次用,从桌面应用 Studio 开始。</p>
<p align="center">维护者:<strong>Huahui Yu</strong>。</p>
> [!IMPORTANT]
> **加入社区 · Community** — 双语 Discord,提供安装答疑(`#help` / `#求助`)、工作流展示与功能想法。→ **<https://discord.gg/XF78rEME2D>**
>
> QQ 群 **DeepSeek-Reasonix官方群**(群号 `1093562660`)→ **<https://qm.qq.com/q/i59b0z2R8s>**([二维码](./docs/assets/qq-group.svg))
> 抖音:**做游戏的小鱼**(抖音号 `22703872788`)· [二维码](./docs/assets/douyin.png)
## ❤️赞助商
<table><tr><td width="180" align="center" valign="middle"><a href="https://www.rightapi.ai/register?aff=e083bf73" target="_blank" rel="sponsored noopener noreferrer"><img src="./.github/sponsor/rightcode.png" alt="Right Code" width="64"></a></td><td valign="middle">感谢 Right Code 赞助了本项目!Right Code 稳定提供 Claude Code、Codex、Gemini 等模型的中转服务,并可选按量、包月两种计费模式。充值即可开票,企业、团队用户一对一对接。通过<a href="https://www.rightapi.ai/register?aff=e083bf73" target="_blank" rel="sponsored noopener noreferrer">此链接</a>注册。</td></tr><tr><td width="180" align="center" valign="middle"><a href="https://www.packyapi.ai/register?aff=rIbQ" target="_blank" rel="sponsored noopener noreferrer"><picture><source media="(prefers-color-scheme: dark)" srcset="./.github/sponsor/packycode-dark.svg"><img src="./.github/sponsor/packycode-light.svg" alt="PackyCode" width="80"></picture></a></td><td valign="middle">感谢 PackyCode 赞助了本项目!PackyCode 是一家稳定、高效的 API 中转服务商,统一域名、统一密钥、智能容灾切换。通过<a href="https://www.packyapi.ai/register?aff=rIbQ" target="_blank" rel="sponsored noopener noreferrer">此链接</a>注册。</td></tr></table>
## 它是什么
Reasonix 是一个开源的 AI 编程助手。程序跑在你的电脑上;AI 模型是你自己配置的服务,它读到的内容会发给这个服务。你用大白话描述任务,它在你选的项目文件夹里读代码、改文件、跑命令和测试,每一步都摆给你看。
模型你自己选:内置 DeepSeek 预设,其他兼容 OpenAI 接口的服务填一条配置就行,费用直接付给模型服务商。
## 你能得到什么
- **读、改、跑**:读你的项目、改文件、跑测试和命令,都在你选的项目文件夹里。
- **权限分档**:每次都问你,或让日常操作自动放行;你设的禁止规则始终有效。想先看方案,就开计划模式,批准后它才动手。
- **可回退**:改文件前先存快照,按对话轮次回退,不碰 git 历史。只覆盖编辑工具改的文件,命令行命令造成的改动不在内。
## 30 秒上手
下载、填模型密钥、打开项目。第一次用,从桌面应用 Studio 开始。
1. 从[发布页](https://github.com/esengine/DeepSeek-Reasonix/releases?q=studio-v&expanded=true)下载并安装桌面应用 Reasonix Studio。
2. 打开应用,按提示填入模型服务商和密钥。
3. 打开你的项目文件夹,写下想做的事,例如“给 http 客户端加上失败重试”。
4. 按你选的档位,该问你的操作它会先停下来问;同意就继续,不同意就拒绝。
更喜欢终端的话,下载 `reasonix` 命令行后运行 `reasonix setup` 配好模型,再运行 `reasonix` 即可。详见[安装](#安装)和[快速开始](#快速开始)。
## 四种用法
- **桌面应用(Studio)**:图形界面,适合第一次使用。
- **终端**:全屏文字界面,也能用 `reasonix run "任务"` 在脚本里运行。
- **浏览器**:`reasonix web` 在本机打开网页界面。
- **编辑器**:VS Code 安装扩展即可;其他支持 ACP(Agent Client Protocol,编辑器接入某个 AI 助手的通用约定)的编辑器,可连接 `reasonix acp`。
四种用法背后是同一套程序。下面的内容写给进阶用户和贡献者。
## 版本
Reasonix 分为两条版本线,调整的原因见[版本路线公告](https://github.com/esengine/DeepSeek-Reasonix/discussions/10748)。
| 版本线 | 分支 | 状态 | 获取方式 |
| --- | --- | --- | --- |
| **Reasonix 2.x** | `studio`(当前分支) | 活跃开发 | [Studio 发布页](https://github.com/esengine/DeepSeek-Reasonix/releases?q=studio-v&expanded=true) |
| **Reasonix 1.x** | [`main-v2`](https://github.com/esengine/DeepSeek-Reasonix/tree/main-v2) | 维护 / 稳定 | `npm i -g reasonix` · [桌面端下载](https://reasonix.io/?download=desktop#start) |
- **想要稳定**:继续使用 1.x。它会持续收到 Bug 修复、Provider / API 兼容、
更新器和安全修复,但不再扩展核心架构。
- **想体验新架构**:使用 2.x Reasonix Studio。它仍在快速迭代,欢迎反馈问题。
1.x 中有价值的修复、测试和行为会逐项 review,适合新架构的会迁移或重新实现到 2.x。
- [从 1.x 迁移到 2.x](./docs/MIGRATING.md)(英文):两条线共用哪些数据,以及如何在同一台
机器上同时使用而不丢会话。
- [2.x 路线图](./docs/ROADMAP.md)(英文):2.x 还要交付什么、如何发布、哪些决定尚未做出。
## 特性
以下是写给进阶用户的技术要点。
- **配置驱动**:provider、agent、启用的工具、插件全部在 `reasonix.toml` 中声明,
内核无硬编码模型。
- **多模型 · 可组合**:DeepSeek 作为预设内置;任何 OpenAI 兼容
@@ -67,57 +127,83 @@
## 安装
选择适合你的使用路径。CLI/TUI、桌面端和 VS Code 扩展都使用同一套本地
Reasonix 引擎。
当前分支是 **Reasonix 2.x**,桌面端是 Reasonix Studio;CLI、Studio 和编辑器集成
都运行同一套本地 Reasonix 引擎。
### 路径 A:CLI / TUI
### Reasonix Studio(2.x)
任意支持的平台都可以通过 npm 安装原生二进制;macOS 也可以使用 Homebrew:
从最新的 [Studio 发布](https://github.com/esengine/DeepSeek-Reasonix/releases?q=studio-v&expanded=true)
下载对应平台的安装包(标签为 `studio-v2.*`;`-rc.N` 等候选版本标为预发布):
| 平台 | 安装包 | 架构 |
| --- | --- | --- |
| macOS | `.dmg` 或 `.zip` | Apple Silicon(`arm64`)/ Intel(`amd64`) |
| Windows | 安装器 `-installer.exe` 或便携 `.zip` | x64 |
| Linux | `.deb` 或 `.rpm` | x64(`amd64`)/ ARM64(`arm64`,aarch64) |
每个安装包都附带 `.minisig` 签名,发布页另有 `SHA256SUMS`。安装后 Studio 会在
应用内自行更新。
同一个发布页也提供 2.x 的 `reasonix` CLI 归档(`darwin|linux|windows × amd64|arm64`)。
### Reasonix 1.x(稳定版)
1.x CLI 在任意支持的平台上都可以通过 npm 安装,macOS 也可以使用 Homebrew:
```sh
npm i -g reasonix # 任意系统;自动拉取对应平台的原生二进制
brew install esengine/reasonix/reasonix # macOS
```
预编译归档(`darwin|linux|windows × amd64|arm64`)和 `SHA256SUMS` 见每个
[GitHub release](https://github.com/esengine/DeepSeek-Reasonix/releases)。
1.x 桌面端请前往[官方下载页](https://reasonix.io/?download=desktop#start)。
Windows 安装器使用 Certum 代码签名证书签名。
### 路径 B:桌面端
### VS Code 扩展
前往[官方下载页](https://reasonix.io/?download=desktop#start)获取最新桌面版本。
| 平台 | 安装包 | 架构 |
| --- | --- | --- |
| macOS | 通用 `.dmg` 或 `.zip` | Apple Silicon / Intel |
| Windows | 安装器 `.exe` 或便携 `.zip` | x64 / ARM64 |
| Linux | `.deb` 或 `.tar.gz` | x64 |
Windows 安装器通过 [SignPath.io](https://signpath.io/) 完成代码签名,证书由
[SignPath 基金会](https://signpath.org/) 免费提供。
### 路径 C:VS Code 扩展
请先完成路径 A。扩展不内置 CLI,而是启动本机的 `reasonix acp` 后端,
并提供原生聊天、编辑器上下文、工具调用审批、模型选择和工作区会话。
扩展不内置 CLI,而是启动本机的 `reasonix acp` 后端,并提供原生聊天、编辑器
上下文、工具调用审批、模型选择和工作区会话。请先安装 1.x CLI。
- **VS Code:** [从 Visual Studio Marketplace 安装](https://marketplace.visualstudio.com/items?itemName=SivanLiu.reasonix-agent)
- **VSCodium / Eclipse Theia:** [从 Open VSX Registry 安装](https://open-vsx.org/extension/SivanLiu/reasonix-agent)
- **扩展 ID:** `SivanLiu.reasonix-agent` · [源码与使用说明](https://github.com/SivanCola/reasonix-vscode)
### 路径 D:从源码构建
### 从源码构建
克隆仓库;`studio` 构建 2.x,`main-v2` 构建 1.x:
```sh
git clone https://github.com/esengine/DeepSeek-Reasonix.git
cd DeepSeek-Reasonix
git switch studio
```
#### CLI
CLI 构建需要 **Go 1.25+**。模块固定了 `toolchain`,保持 `GOTOOLCHAIN=auto`
即可让 Go 自动下载对应工具链,也可以手动安装。
```sh
make build # -> bin/reasonix(.exe)
make cross # -> dist/(darwin|linux|windows × amd64|arm64)
```
#### Studio
Studio 的前端还需要 **Node 24+ 与 pnpm 10**(`npm install -g pnpm@10`)。
```sh
make studio
```
各平台的 webview 依赖和 Linux 构建标签见 [Studio 构建指南](desktop/README.md#prerequisites)。
## 快速开始
### CLI / TUI
### Reasonix Studio
以下命令仅适用于通过路径 A 安装的 CLI/TUI:
安装并启动 Studio,然后在应用内连接 provider 和模型即可使用,无需配置 CLI。
### CLI / TUI
```sh
reasonix setup # 配置 provider 和模型
@@ -127,12 +213,6 @@ reasonix run "把 main.go 里的 TODO 实现掉"
需要项目指令时,可在交互式会话中运行 `/init`。
### 桌面端
从[官方下载页](https://reasonix.io/?download=desktop#start)下载对应系统的安装包,
安装并启动 Reasonix,然后在应用内配置 provider 和模型即可使用。桌面端无需执行
上面的 CLI 命令。
CLI 进阶用法和详细配置见 **[CLI 命令参考](./docs/CLI.zh-CN.md)**、
**[指南](./docs/GUIDE.zh-CN.md)** 和
**[配置路径](./docs/CONFIG_PATHS.md)**。
@@ -150,7 +230,8 @@ CLI 进阶用法和详细配置见 **[CLI 命令参考](./docs/CLI.zh-CN.md)**
- **工程与迁移:** [规格](./docs/SPEC.md) ·
[任务合约与暂停策略](./docs/TASK_CONTRACT.md) ·
[工具合约](./docs/TOOL_CONTRACT.md) ·
[从 0.x 迁移](./docs/MIGRATING.md)
[从 1.x 迁移到 2.x](./docs/MIGRATING.md) ·
[2.x 路线图](./docs/ROADMAP.md)
- **扩展开发:** [扩展概览](./docs/EXTENSIONS.md) ·
[插件包与 Manifest v1](./docs/PLUGIN_PACKAGES.md) ·
[Extension Protocol](./docs/EXTENSION_PROTOCOL.md) ·
@@ -177,11 +258,11 @@ CLI 进阶用法和详细配置见 **[CLI 命令参考](./docs/CLI.zh-CN.md)**
<!-- reasonix-top-contributors:start -->
| Contributor | Contributor | Contributor | Contributor |
| --- | --- | --- | --- |
| [**SivanCola**](https://github.com/SivanCola) | [**esengine**](https://github.com/esengine) | [**ttmouse**](https://github.com/ttmouse) | [**lifu963**](https://github.com/lifu963) |
| **reasonix** | [**HUQIANTAO**](https://github.com/HUQIANTAO) | [**GTC2080**](https://github.com/GTC2080) | [**light-front-theory**](https://github.com/light-front-theory) |
| **merge-order-check** | [**Li-Charles-One**](https://github.com/Li-Charles-One) | [**eghrhegpe**](https://github.com/eghrhegpe) | **wufengfan** |
| [**CVEngineer66**](https://github.com/CVEngineer66) | [**dependabot\[bot\]**](https://github.com/apps/dependabot) | [**lanshi17**](https://github.com/lanshi17) | [**SuMuxi66**](https://github.com/SuMuxi66) |
| [**CnsMaple**](https://github.com/CnsMaple) | [**cyq1017**](https://github.com/cyq1017) | [**JesonChou**](https://github.com/JesonChou) | [**XTLine**](https://github.com/XTLine) |
| [**SivanCola**](https://github.com/SivanCola) | [**KHG420**](https://github.com/KHG420) | [**ttmouse**](https://github.com/ttmouse) | [**lifu963**](https://github.com/lifu963) |
| [**HUQIANTAO**](https://github.com/HUQIANTAO) | [**Harbor404**](https://github.com/Harbor404) | [**GTC2080**](https://github.com/GTC2080) | [**mchenziyi**](https://github.com/mchenziyi) |
| [**Li-Charles-One**](https://github.com/Li-Charles-One) | [**light-front-theory**](https://github.com/light-front-theory) | [**XTLine**](https://github.com/XTLine) | [**eghrhegpe**](https://github.com/eghrhegpe) |
| [**noreply**](https://github.com/noreply) | [**chuiwenwei**](https://github.com/chuiwenwei) | [**xxoingr**](https://github.com/xxoingr) | [**Bernardxu123**](https://github.com/Bernardxu123) |
| [**HaoyueQin**](https://github.com/HaoyueQin) | [**BuGlessRB**](https://github.com/BuGlessRB) | [**CVEngineer66**](https://github.com/CVEngineer66) | [**JesonChou**](https://github.com/JesonChou) |
<!-- reasonix-top-contributors:end -->
特别感谢 [**Bernardxu123**](https://github.com/Bernardxu123) 设计的项目 logo和开场视频。
@@ -209,9 +290,10 @@ CLI 进阶用法和详细配置见 **[CLI 命令参考](./docs/CLI.zh-CN.md)**
如果 Reasonix 帮你省了时间或 token,欢迎请杯咖啡。捐助不会换来 feature
优先级,也不会影响 issue 的处理顺序——就是「谢谢」。
- **国内** — 微信支付(扫下方二维码)
- **国内** — 微信支付 / 支付宝(扫下方二维码)
- **海外** — PayPal: [paypal.me/yuhuahui](https://paypal.me/yuhuahui)
<p align="center">
<img src=".github/sponsor/wechat-pay.jpg" alt="微信支付收款码" width="180"/>
<img src=".github/sponsor/alipay.jpg" alt="支付宝收款码" width="180"/>
</p>
+35 -6
View File
@@ -9,7 +9,7 @@ agent. It is the Reasonix analog of Claude Code's CLAUDE.md.
- Go kernel under `internal/`; each package owns one concern. A package's long
explanation belongs in its `doc.go`, not spread across implementation files.
- One transport-agnostic `control.Controller` sits behind every frontend (chat
TUI, HTTP/SSE serve, Wails desktop). Add behavior to the controller, not a
TUI, HTTP/SSE serve, Electron desktop). Add behavior to the controller, not a
frontend, so all three inherit it.
- Layering (enforced): utility packages import nothing under `reasonix/` except
one another — that layer is closed, so a leaf reaching a leaf drags no graph
@@ -219,9 +219,11 @@ otherwise the block the model already has goes on reading as current.
- `#<note>` in chat quick-adds an always-on instruction. The `remember` tool
instead saves a fallible background fact (frontmatter file + `MEMORY.md`
index). Fact `type` classifies content; independent `scope` controls whether it
is project-only (the default) or explicitly global. The index loads into the
stable prefix on the next session; global user/feedback bodies also load as
lower-priority compatibility guidance. The current turn receives a tail note.
is project-only (the default) or explicitly global.
- The saved-fact index stays out of the stable prefix and is reached through the
`memory` tool; pinned bodies and global user/feedback bodies load into it, the
latter as lower-priority compatibility guidance. The current turn receives a
tail note.
## Notes
@@ -320,6 +322,31 @@ Use `go test ./path/to/target/` to detect cycles **before** pushing. A `[setup f
- **Keep the PR diff minimal.** Only the files relevant to the PR's purpose — no stray changes from other branches.
- **Amend, don't add commits, for review feedback** — keeps the commit history clean.
## Change discipline
**Assess before editing.** For every change, humans and agents list callers,
consumers of the same state, sibling features, reload/replay/restart paths,
error and edge states, TUI/CLI and desktop equivalents, and wire mirrors.
A fix that breaks a neighbouring behaviour is a failed fix.
**Test the neighbourhood.** UI changes add or extend interaction tests for
adjacent functions of the same component or flow (composer: submit, clear,
queue, withdraw, restore, steer, failure, replay after reload) and run the whole
affected suite.
Write tests first; they must fail without the change.
**Fix at the owning layer.** Use the existing unified pattern: no special-case
flags, duplicated paths, or workarounds at the symptom site.
If the design is duplicated or wrong, make the smallest unifying change:
one path instead of two, deleting what becomes dead. Use current language and
framework idioms; do not add a new pattern where one exists.
**PR evidence.** Every PR description carries: Cause, Blast radius,
Neighbouring behaviours tested, and Why this layer.
## Reasonix host checks
The paths below decide what the agent is allowed to do — a wrong change here
@@ -342,6 +369,8 @@ out of how a path is spelled, which cannot tell `internal/auth` from
- sensitive: internal/ext/pluginpkg/**
- sensitive: internal/base/netclient/**
- sensitive: internal/safety/redirectguard/**
- sensitive: internal/safety/endpointclient/**
- sensitive: internal/safety/egress/**
- sensitive: internal/platform/browser/**
- sensitive: internal/platform/computer/**
@@ -374,8 +403,8 @@ observation, never by a check that ran afterwards.
What follows bounds Observable today. It is not a restatement of the invariant:
moving a number here must not move the promise.
- `scanWorkspace` walks the workspace recursively, skips VCS store dirs, and is
`complete` only below 50k files. Over that, or past a walk error, it
- `scanWorkspace` walks the workspace and every granted extra root recursively,
skips VCS store dirs, and is `complete` only below 50k files in total. Over that, or past a walk error, it
establishes nothing and the mutation stays unproven.
- Symlinks are recorded by `Lstat`, so a write through one lands outside what
the walk compares. Confining the resolved target is the sandbox's job.
+57 -6
View File
@@ -56,9 +56,10 @@ Supported boundaries include:
- Sandbox behavior for built-in shell execution where the platform supports it.
- Secret handling for provider keys, bot credentials, OAuth tokens, plugin
headers, and credential-store fallback files.
- HTTP `serve` protections for the unauthenticated local server, including
localhost binding assumptions, JSON-only state-changing requests, and CORS
restrictions.
- HTTP `serve` protections, including the default per-launch token, the launch
token every state-changing request requires when authentication is opted
out, localhost binding assumptions, JSON-only state-changing requests, and
CORS restrictions.
- Desktop and bot session isolation, including per-workspace session metadata
and configured bot allowlists.
- Updater, install, and release verification paths.
@@ -67,10 +68,10 @@ The following are normally treated as trusted local/operator-controlled inputs
unless another bug lets an untrusted actor supply them:
- CLI arguments and text typed directly by the local user.
- Project configuration files intentionally loaded from the current workspace.
- The user's own configuration under the Reasonix home directory.
- Explicit `@path` references supplied by the local user to attach local files.
- MCP servers, language servers, hooks, and slash commands installed or enabled
by the local user.
- MCP servers, language servers, hooks, and slash commands installed, enabled,
or approved by the local user.
- Provider base URLs and model names configured by the local user.
The following can be security issues when reachable by an untrusted actor or
@@ -89,6 +90,56 @@ when they bypass the intended boundary:
- Trusting unverified update artifacts, plugin definitions, or downloaded
binaries.
## Project Configuration
Files that arrive with a workspace are untrusted input: `reasonix.toml`,
`.reasonix/settings.json`, `.mcp.json` and the project `.env`. Cloning a
repository is not a decision to trust its author, so these files may narrow
what the user configured and never widen it.
| Setting | What a workspace file may do |
| --- | --- |
| `sandbox.bash` | Move `off` to `enforce`, never back |
| `sandbox.network` | Move `true` to `false`, never back |
| `sandbox.forbid_read`, `sandbox.denied_domains` | Add to the user's lists |
| `sandbox.allow_write`, `sandbox.workspace_root` | Apply only when they resolve inside the workspace, symlinks followed; `${VAR}` in any sandbox path expands from the process environment only |
| `sandbox.host_authorities`, a user's `sandbox.allowed_domains` | Nothing |
| `permissions.deny`, `permissions.ask` | Add to the user's rules |
| `permissions.mode`, `permissions.allow`, `permissions.allow_dynamic_bash` | Nothing |
| `desktop.default_tool_approval_mode` | Nothing |
| `[network]` proxy settings | Nothing |
| `[[providers]]`, `tools.system_one` endpoints | Declare them; they stay out of use until approved, and model settings naming them fall back to the user's |
An "always allow" answer, or extra write access, granted in a workspace is
stored under the Reasonix home, not in the workspace.
Programs a workspace names for the host to run stay off until the user
approves them for that workspace:
- Hooks, language server commands, `tools.search.rg_path`,
`tools.shell.path`, `[browser]` and `tools.system_one.laya.python` are
approved with `reasonix trust`, or for hooks by saving them from the hooks
editor.
- An approval is stored under the Reasonix home with a digest of the
declaration and of the workspace files it names; any change needs approval
again. Approvals and grants belong to one workspace folder.
- Hooks and language servers are checked against their approval again before
each start, and refused if a file they name changed, appeared or now
resolves elsewhere.
- A single program path (`tools.shell.path`, `tools.search.rg_path`,
`[browser]`, `laya.python`) may not point into the workspace, an
`allow_write` root, a temporary directory or a toolchain cache the bash jail
leaves writable, and may not climb with `..`; its approval covers the
program file's content.
- `tools.system_one.laya.local` is read from the user's configuration only.
- Without an interactive frontend they stay off.
- MCP servers a workspace declares stay off until the user enables them for
that workspace.
A project value that is not applied is reported as a load warning with the
reason. A way for workspace files to widen any of the above without the
user's approval is a security issue.
## `@` File References
Reasonix supports `@path` references so users can include local files and images
+42 -37
View File
@@ -7,9 +7,9 @@ SWE-bench Verified mode:
[`cmd/e2ebench`](../cmd/e2ebench/main.go). It runs each task against a real
provider and emits a markdown + JSON report (accuracy, cache-hit rate, token
use, cost) suitable for pasting into a PR.
- `context-maintenance-e2e/` — a standalone seed → resume → comprehension
harness that A/B-compares cold-restart cache behavior with and without
context pruning.
- `context-maintenance-e2e/` — a standalone seed → resume → continue smoke that
checks a session seeds below the compaction trigger, resumes without paying
for a summary, and folds exactly once past it.
- `compaction/` — CompactionBench: grows a session one generation at a time and
folds it after each, measuring what repeated compaction costs and what it
loses. See [CompactionBench](#compactionbench) below.
@@ -420,19 +420,20 @@ own outcome).
| `-suite` | `benchmarks/e2e` | Suite root (must contain `tasks/<id>/`). |
| `-task` | *(all)* | Suite mode: run only these comma-separated task IDs (e.g. `-task fix-add-bug`); unknown IDs fail with the available list. |
| `-attempts` | `1` | Suite and diff modes: retry a task until an attempt passes, up to N; enables the `Pass@≤N` KPI, and TTCS charges a retried solve with its failed attempts' wall. |
| `-trials` | `1` | Suite mode: run every task N times and keep every outcome. Adds a **Reliability** line: `pass^N` (tasks that passed every trial), `pass@N` (any trial), mean `pass@1` over all trials, and the flaky tasks by name. `Pass@≤N` asks whether the agent can solve a task; `pass^N` asks whether it does so every time. Cannot be combined with `-attempts`. |
| `-trials` | `1` | Suite mode: run every task N times and keep every outcome. Adds a **Reliability** line: `pass^N` (complete tasks that passed every trial) and `pass@N` (complete tasks with any pass), both over the tasks that ran all N trials only (`n/a` when none did), mean `pass@1` over every graded trial, and the flaky tasks by name; a task the token budget cut short is listed on an **Incomplete** line and is in neither. `Pass@≤N` asks whether the agent can solve a task; `pass^N` asks whether it does so every time. Cannot be combined with `-attempts`. |
| `-tape-record DIR` | — | Suite mode, with `-meter`: record every provider exchange at the meter, one tape per task and trial (`DIR/<task>/trial-N/0001.request.json`, `.response.json`, `.response.body`). Taped runs use a fixed workdir path and pin the seeded files' times, because both reach the model through the workspace block and file listings. |
| `-tape-replay DIR` | — | Suite mode, with `-meter`: answer each run from its tape instead of the provider, and add a **Replay** line naming the first request whose body differs from the recorded one, down to the message (`request 2, message 4 (tool)`), the tool list, or a field. The diverging request is kept as `NNNN.replayed.json` beside the recorded one. A kernel change that should not alter what the model is asked replays with every request matched. |
| `-bin` | `reasonix` | Path to the reasonix binary. |
| `-model` | *(config default)* | Provider/model name. |
| `-profile` | `baseline` | Tool-surface/runtime tier: `baseline` \| `economy` \| `balanced` \| `delivery`. All but `baseline` append `--profile <tier>` to the agent invocation; `baseline` passes no flag (byte-identical legacy control, behaviorally `balanced`). Economy starts with the core tool set and pays `connect_tool_source` rounds plus prefix resets to grow it — the report's Tool surface line prices that trade. |
| `-ablate` | *(none)* | Ablation arm: comma-separated subsystems to switch off — `evidence`, `planner`, `subagent`, `retrieval`, `compaction`; `none` \| `all`. |
| `-ablate` | *(none)* | Ablation arm: comma-separated subsystems to switch off — `evidence`, `planner`, `subagent`, `retrieval`, `compaction`, `upstream` and `recall-search`; `none` \| `all`. |
| `-out` | *(stdout)* | Write the markdown report here. |
| `-json` | *(none)* | Write the JSON report here (optional). |
| `-trajectories` | *(none)* | Suite mode: write one `<task-id>.trajectory.jsonl` per task into this directory (the agent's full event stream with timestamps — see `reasonix run --trajectory`). The report gains a time-attribution line (tools vs. model) and each JSON result a `trajectory` digest. |
| `-force-planner` | `false` | Suite mode: prefix each prompt with a plan-first directive so the two-model turn engages regardless of the planner gate. Use for the "with planner" arm of an A/B; results carry `plan_forced` so arms are only comparable with equal forcing. |
| `-anchor` | `blind` | Suite mode: which hypothesis the agent holds before it looks at anything — `blind` (none, the control) \| `correct` \| `wrong`. The seeded arms prefix each prompt with the task's authored seed and **skip** tasks that have none, so an unseeded control run never lands in a seeded denominator. Results carry `anchor`. See [Anchor resistance](#anchor-resistance). |
| `-cache` | `cold` | Suite mode: `cold` runs each task as a fresh session (the fair cross-agent comparison arm); `warm` primes the provider prefix cache with a one-step run in the same workdir first, measuring the long-lived-session steady state. Never mix arms in one report — compare them with `-mode compare cold.json warm.json`. |
| `-policy` | *(none)* | Suite mode: experiment arm. `memory-off` hides the memory store, which is the MemoryBench counterfactual arm; the empty default runs the baseline. |
| `-budget` | `800000` | Abort once total tokens cross this (`0` = no cap). Remaining tasks are reported as skipped. |
| `-meter` | *(off)* | Suite mode: route the benchmarked provider through the neutral measuring proxy, using this `config.toml` as the source. Spend is then counted at the request boundary instead of trusted from the harness. See [Neutral metering](#neutral-metering). |
| `-faults` | *(none)* | Suite mode: inject provider failures through the meter — absolute indices (`3:429`) and/or a cadence that scales with the run (`every:5:500`). Requires `-meter`. See [Fault recovery](#fault-recovery). |
@@ -541,33 +542,32 @@ writer.
## context-maintenance-e2e
This harness measures what happens when a long session goes idle past the
provider's cache TTL and then resumes: it A/B-compares cold-restart miss tokens
with and without pruning, and checks that the agent re-reads a file behind a
prune placeholder instead of hallucinating.
A cost-capped smoke for content-driven maintenance, in three legs over one state
directory: seed a session just below the compaction trigger, load it back and
check nothing is re-derived, then grow it past the trigger and check exactly one
summary lands.
It is hardcoded to the `deepseek-v4-flash` model at `https://api.deepseek.com`
and requires the `DEEPSEEK_API_KEY` environment variable.
Seed and resume take no provider; `continue` samples and needs
`DEEPSEEK_API_KEY`. The model is hardcoded to `deepseek-v4-flash` at
`https://api.deepseek.com`.
```sh
export DEEPSEEK_API_KEY=...
# Seed below the trigger: no checkpoint installed, no summary started
go run ./benchmarks/context-maintenance-e2e -offline seed
# Seed both arms (pruned + control) with a large session and warm the cache
go run ./benchmarks/context-maintenance-e2e seed
# Wait past the provider's cache TTL, then resume: prune the "pruned" arm and
# compare cold-restart miss tokens
# Reload: the projection version must not move and no summary may run
go run ./benchmarks/context-maintenance-e2e resume
# Run the comprehension trials (agent must re-read a pruned file and answer
# from it); exits non-zero unless every trial passes
go run ./benchmarks/context-maintenance-e2e comprehension
# Grow past the trigger: one summary, version +1, then no further summary
export DEEPSEEK_API_KEY=...
go run ./benchmarks/context-maintenance-e2e continue
```
| Flag | Default | Purpose |
| --- | --- | --- |
| `-dir` | `benchmarks/context-maintenance-e2e/run` | State directory for `seed`/`resume` (sessions + `meta.json`, `resume-<ts>.json`). |
| `-trials` | `5` | Number of comprehension trials. |
| `-dir` | `benchmarks/context-maintenance-e2e/run` | State directory shared by the three legs (session + `meta.json`). |
| `-max-usd` | `0.50` | Hard cost cap for the live legs. |
| `-offline` | off | Skip the live provider; `seed` only. |
## See also
@@ -593,6 +593,24 @@ e2ebench -suite benchmarks/memorybench -budget 0 -policy memory-off -trajectorie
e2ebench -mode compare on.json off.json # Memory utility section
```
MemoryBench requires an OS read sandbox. It runs on macOS and Linux and
refuses to start on Windows, where the benchmark cannot isolate the committed
`tasks/<id>/memory/` answers from a graded agent. Each run also audits every
recorded trajectory leg for a successful read of those answers; an answer read
invalidates the result and makes the suite command exit nonzero. A missing or
unreadable trajectory marks only that run incomplete; other tasks still run.
Trajectories are captured temporarily when `-trajectories` is omitted.
The audit treats a completed `read_file` call naming a task-memory path as a
read. For `bash` and `grep`, a task-memory path in the arguments plus an authored
fact line in the result is **evidence** of a read, not a proof of how that text
was obtained. `glob` and directory listings expose names but not fact bodies;
the audit does not count them as answer reads. Commands that reach the answer
through an indirect path, such as `cd` followed by a relative filename, may
not be attributed by the trajectory check. The OS read sandbox is the primary
boundary; this audit is a second check, not a proof that an unflagged run never
accessed an answer.
Utility delta = paired Pass(on) − Pass(off). Harmful attribution is paired,
never judged: the same task passed without memory and failed with it while
recall fired. Scenario classes: exact, paraphrase, cjk, symbol, distractor
@@ -606,8 +624,8 @@ update (revised value wins), pinned (prefix channel end to end).
`benchmarks/compaction/` drives the real agent compaction path over a session
that grows one generation at a time. Each generation appends a round of work
and then folds, so generation N folds everything generations 1..N produced —
which is the growth that matters, because a fold re-derives its digest from the
whole canonical transcript rather than from the previous digest.
which is the growth that matters, because a fold reads the model-visible
projection, so the previous digest feeds the next one.
```bash
go run ./benchmarks/compaction -mode=cost # offline, no API key
@@ -634,16 +652,3 @@ probe, not a compaction loss.
Probe answers are scored on whole words, and a wanted answer does not count if a
rejected one appears anywhere in the same reply — "yes, but it has not been
re-run since" is the shape a drifting digest produces, and it is not a pass.
### Fold arms
`-arm=full` (default) re-derives every digest from the canonical transcript, so
digests never chain. `-arm=incremental` folds the model-visible view instead,
feeding the previous digest back through the summarizer. The arms exist to price
that trade: run the cost arm for what chaining saves, and the fidelity arm for
what it costs.
```bash
go run ./benchmarks/compaction -mode=cost -arm=incremental
DEEPSEEK_API_KEY=… go run ./benchmarks/compaction -mode=fidelity -arm=incremental
```
@@ -0,0 +1,35 @@
# Compaction prefix probe
## Method
1. Measured on 2026-10-17 against the official `https://api.deepseek.com` OpenAI adapter, using the model returned by `/models`: `deepseek-flash`. Thinking was disabled. Inputs were synthetic; no workspace history or credentials are recorded here.
2. Each of three text and three rich-history trials sent a main request to warm the prefix, then called the actual agent summary path, then an independent summary control. Rich history included a synthetic closed `read_file` call, result and reasoning. Warmup outputs were discarded, never executed.
3. A summary had a 1,024-token output cap; warmups had 64. Success means a heading, preservation of the fixture's `ParseDuration` and zero-value constraint, no emitted tool calls, and `finish_reason=stop`. This checks the fixture's contract, not arbitrary-summary factual completeness.
4. First-attempt reuse success counts successful reused summaries / six reused summaries. Fallback rate counts independent requests initiated by the reuse coordinator / six reuse trials; the six explicit independent controls are excluded. Both were 6/6 success and 0/6 fallbacks. Independent controls also succeeded 6/6.
5. The raw result file records provider-reported usage and full stream latency, measured from adapter `Stream` until channel close. Token rows below sum only the three indicated summary requests; they exclude warmup, model listing and any earlier exploratory requests. Cache residency was uncontrolled; prior probes had warmed these synthetic prefixes.
## Results
| History | Shape | N | Prompt | Hit | Miss | Output | Median ms | Estimated USD |
| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| Text | Reused | 3 | 3,714 | 3,072 | 642 | 636 | 1,713 | 0.000487116 |
| Text | Independent | 3 | 1,218 | 768 | 450 | 644 | 1,610 | 0.000456204 |
| Rich | Reused | 3 | 3,780 | 3,072 | 708 | 742 | 1,774 | 0.000560616 |
| Rich | Independent | 3 | 1,341 | 768 | 573 | 921 | 1,815 | 0.000640854 |
1. Estimates use the [official pricing](https://api-docs.deepseek.com/quick_start/pricing/) checked on the measurement date: off-peak Flash USD per million tokens, hit 0.003, miss 0.15, output 0.6. October 17 was a Saturday. Formula: `(hit*0.003 + miss*0.15 + output*0.6)/1_000_000`. These are token estimates, not account deductions.
2. Reuse hit fractions were 82.7% (text) and 81.3% (rich), versus 63.1% and 57.3% for controls. This small fixture does **not** establish cost savings: retaining the system/catalog increased both total input and input cost. Text total cost increased 6.8%; rich total decreased 12.5% because output was shorter, not because input was cheaper.
3. The six warmups consumed 7,032 prompt and 231 output tokens, estimated USD 0.000346680. All 18 recorded generation calls together cost an estimated USD 0.002491470. No failed-digest retry occurred in these live trials; the deterministic fallback tests establish its behavior and accounting.
4. A rejected nonempty reused answer earns one independent-shape fallback. A separate truncation retry may enlarge the output cap, for at most three logical model calls total. Every actual attempt emits and records its own spend; returned usage is their sum. Empty output, stream errors and cancellation do not earn shape retries.
5. These sequential, six-pair trials establish interoperability for one model, mode and short fixture. They do not estimate production failure probability, thinking-mode behavior, other models, cold-cache cost, or large-history savings.
## Reproduction
1. Provide a test key through an existing secret environment configuration. Do not put credentials in source, command arguments or captured logs. Select a model currently returned by `/models`.
2. Run the opt-in probe; it skips without `DEEPSEEK_API_KEY`. The dedicated build tag avoids enabling unrelated live tests.
```sh
REASONIX_LIVE_MODEL=deepseek-flash go test -tags live_compaction ./internal/runtime/agent -run '^TestLiveCompactionPrefix$' -v -count=1
```
3. This probe exercises actual summary construction and the HTTP adapter. The hermetic `TestEffectCompactionSharesThePreviouslySentPrefix` separately exercises manual, automatic pressure and rich history through `boot.Build`, comparing actual JSON request-prefix and schema bytes. Its sparse-history case also proves an interleaved retained user turn survives the fold.
@@ -0,0 +1,512 @@
[
{
"model": "deepseek-flash",
"rich": false,
"trial": 1,
"results": [
{
"kind": "warm",
"milliseconds": 828,
"digest": false,
"grounded": false,
"tool_calls": 1,
"usage": {
"PromptTokens": 1127,
"CompletionTokens": 38,
"TotalTokens": 1165,
"CacheHitTokens": 896,
"CacheMissTokens": 231,
"CacheWriteTokens": 0,
"CacheWriteBilledTokens": 0,
"ReasoningTokens": 0,
"FinishReason": "tool_calls",
"Estimated": false,
"RequestCount": 1,
"ServerToolRequests": 0,
"ContextPromptTokens": 0,
"ContextCompletionTokens": 0,
"ContextReasoningTokens": 0,
"ContextCacheHitTokens": 0,
"ContextCacheMissTokens": 0
}
},
{
"kind": "reuse",
"milliseconds": 2006,
"digest": true,
"grounded": true,
"tool_calls": 0,
"usage": {
"PromptTokens": 1238,
"CompletionTokens": 234,
"TotalTokens": 1472,
"CacheHitTokens": 1024,
"CacheMissTokens": 214,
"CacheWriteTokens": 0,
"CacheWriteBilledTokens": 0,
"ReasoningTokens": 0,
"FinishReason": "stop",
"Estimated": false,
"RequestCount": 1,
"ServerToolRequests": 0,
"ContextPromptTokens": 0,
"ContextCompletionTokens": 0,
"ContextReasoningTokens": 0,
"ContextCacheHitTokens": 0,
"ContextCacheMissTokens": 0
}
},
{
"kind": "independent",
"milliseconds": 1817,
"digest": true,
"grounded": true,
"tool_calls": 0,
"usage": {
"PromptTokens": 406,
"CompletionTokens": 234,
"TotalTokens": 640,
"CacheHitTokens": 256,
"CacheMissTokens": 150,
"CacheWriteTokens": 0,
"CacheWriteBilledTokens": 0,
"ReasoningTokens": 0,
"FinishReason": "stop",
"Estimated": false,
"RequestCount": 1,
"ServerToolRequests": 0,
"ContextPromptTokens": 0,
"ContextCompletionTokens": 0,
"ContextReasoningTokens": 0,
"ContextCacheHitTokens": 0,
"ContextCacheMissTokens": 0
}
}
]
},
{
"model": "deepseek-flash",
"rich": false,
"trial": 2,
"results": [
{
"kind": "warm",
"milliseconds": 820,
"digest": false,
"grounded": false,
"tool_calls": 1,
"usage": {
"PromptTokens": 1127,
"CompletionTokens": 38,
"TotalTokens": 1165,
"CacheHitTokens": 896,
"CacheMissTokens": 231,
"CacheWriteTokens": 0,
"CacheWriteBilledTokens": 0,
"ReasoningTokens": 0,
"FinishReason": "tool_calls",
"Estimated": false,
"RequestCount": 1,
"ServerToolRequests": 0,
"ContextPromptTokens": 0,
"ContextCompletionTokens": 0,
"ContextReasoningTokens": 0,
"ContextCacheHitTokens": 0,
"ContextCacheMissTokens": 0
}
},
{
"kind": "reuse",
"milliseconds": 1633,
"digest": true,
"grounded": true,
"tool_calls": 0,
"usage": {
"PromptTokens": 1238,
"CompletionTokens": 187,
"TotalTokens": 1425,
"CacheHitTokens": 1024,
"CacheMissTokens": 214,
"CacheWriteTokens": 0,
"CacheWriteBilledTokens": 0,
"ReasoningTokens": 0,
"FinishReason": "stop",
"Estimated": false,
"RequestCount": 1,
"ServerToolRequests": 0,
"ContextPromptTokens": 0,
"ContextCompletionTokens": 0,
"ContextReasoningTokens": 0,
"ContextCacheHitTokens": 0,
"ContextCacheMissTokens": 0
}
},
{
"kind": "independent",
"milliseconds": 1555,
"digest": true,
"grounded": true,
"tool_calls": 0,
"usage": {
"PromptTokens": 406,
"CompletionTokens": 202,
"TotalTokens": 608,
"CacheHitTokens": 256,
"CacheMissTokens": 150,
"CacheWriteTokens": 0,
"CacheWriteBilledTokens": 0,
"ReasoningTokens": 0,
"FinishReason": "stop",
"Estimated": false,
"RequestCount": 1,
"ServerToolRequests": 0,
"ContextPromptTokens": 0,
"ContextCompletionTokens": 0,
"ContextReasoningTokens": 0,
"ContextCacheHitTokens": 0,
"ContextCacheMissTokens": 0
}
}
]
},
{
"model": "deepseek-flash",
"rich": false,
"trial": 3,
"results": [
{
"kind": "warm",
"milliseconds": 905,
"digest": false,
"grounded": false,
"tool_calls": 1,
"usage": {
"PromptTokens": 1127,
"CompletionTokens": 38,
"TotalTokens": 1165,
"CacheHitTokens": 896,
"CacheMissTokens": 231,
"CacheWriteTokens": 0,
"CacheWriteBilledTokens": 0,
"ReasoningTokens": 0,
"FinishReason": "tool_calls",
"Estimated": false,
"RequestCount": 1,
"ServerToolRequests": 0,
"ContextPromptTokens": 0,
"ContextCompletionTokens": 0,
"ContextReasoningTokens": 0,
"ContextCacheHitTokens": 0,
"ContextCacheMissTokens": 0
}
},
{
"kind": "reuse",
"milliseconds": 1713,
"digest": true,
"grounded": true,
"tool_calls": 0,
"usage": {
"PromptTokens": 1238,
"CompletionTokens": 215,
"TotalTokens": 1453,
"CacheHitTokens": 1024,
"CacheMissTokens": 214,
"CacheWriteTokens": 0,
"CacheWriteBilledTokens": 0,
"ReasoningTokens": 0,
"FinishReason": "stop",
"Estimated": false,
"RequestCount": 1,
"ServerToolRequests": 0,
"ContextPromptTokens": 0,
"ContextCompletionTokens": 0,
"ContextReasoningTokens": 0,
"ContextCacheHitTokens": 0,
"ContextCacheMissTokens": 0
}
},
{
"kind": "independent",
"milliseconds": 1610,
"digest": true,
"grounded": true,
"tool_calls": 0,
"usage": {
"PromptTokens": 406,
"CompletionTokens": 208,
"TotalTokens": 614,
"CacheHitTokens": 256,
"CacheMissTokens": 150,
"CacheWriteTokens": 0,
"CacheWriteBilledTokens": 0,
"ReasoningTokens": 0,
"FinishReason": "stop",
"Estimated": false,
"RequestCount": 1,
"ServerToolRequests": 0,
"ContextPromptTokens": 0,
"ContextCompletionTokens": 0,
"ContextReasoningTokens": 0,
"ContextCacheHitTokens": 0,
"ContextCacheMissTokens": 0
}
}
]
},
{
"model": "deepseek-flash",
"rich": true,
"trial": 1,
"results": [
{
"kind": "warm",
"milliseconds": 561,
"digest": false,
"grounded": false,
"tool_calls": 1,
"usage": {
"PromptTokens": 1217,
"CompletionTokens": 39,
"TotalTokens": 1256,
"CacheHitTokens": 1024,
"CacheMissTokens": 193,
"CacheWriteTokens": 0,
"CacheWriteBilledTokens": 0,
"ReasoningTokens": 0,
"FinishReason": "tool_calls",
"Estimated": false,
"RequestCount": 1,
"ServerToolRequests": 0,
"ContextPromptTokens": 0,
"ContextCompletionTokens": 0,
"ContextReasoningTokens": 0,
"ContextCacheHitTokens": 0,
"ContextCacheMissTokens": 0
}
},
{
"kind": "reuse",
"milliseconds": 1774,
"digest": true,
"grounded": true,
"tool_calls": 0,
"usage": {
"PromptTokens": 1260,
"CompletionTokens": 256,
"TotalTokens": 1516,
"CacheHitTokens": 1024,
"CacheMissTokens": 236,
"CacheWriteTokens": 0,
"CacheWriteBilledTokens": 0,
"ReasoningTokens": 0,
"FinishReason": "stop",
"Estimated": false,
"RequestCount": 1,
"ServerToolRequests": 0,
"ContextPromptTokens": 0,
"ContextCompletionTokens": 0,
"ContextReasoningTokens": 0,
"ContextCacheHitTokens": 0,
"ContextCacheMissTokens": 0
}
},
{
"kind": "independent",
"milliseconds": 2271,
"digest": true,
"grounded": true,
"tool_calls": 0,
"usage": {
"PromptTokens": 447,
"CompletionTokens": 381,
"TotalTokens": 828,
"CacheHitTokens": 256,
"CacheMissTokens": 191,
"CacheWriteTokens": 0,
"CacheWriteBilledTokens": 0,
"ReasoningTokens": 0,
"FinishReason": "stop",
"Estimated": false,
"RequestCount": 1,
"ServerToolRequests": 0,
"ContextPromptTokens": 0,
"ContextCompletionTokens": 0,
"ContextReasoningTokens": 0,
"ContextCacheHitTokens": 0,
"ContextCacheMissTokens": 0
}
}
]
},
{
"model": "deepseek-flash",
"rich": true,
"trial": 2,
"results": [
{
"kind": "warm",
"milliseconds": 610,
"digest": false,
"grounded": false,
"tool_calls": 1,
"usage": {
"PromptTokens": 1217,
"CompletionTokens": 39,
"TotalTokens": 1256,
"CacheHitTokens": 1024,
"CacheMissTokens": 193,
"CacheWriteTokens": 0,
"CacheWriteBilledTokens": 0,
"ReasoningTokens": 0,
"FinishReason": "tool_calls",
"Estimated": false,
"RequestCount": 1,
"ServerToolRequests": 0,
"ContextPromptTokens": 0,
"ContextCompletionTokens": 0,
"ContextReasoningTokens": 0,
"ContextCacheHitTokens": 0,
"ContextCacheMissTokens": 0
}
},
{
"kind": "reuse",
"milliseconds": 2263,
"digest": true,
"grounded": true,
"tool_calls": 0,
"usage": {
"PromptTokens": 1260,
"CompletionTokens": 276,
"TotalTokens": 1536,
"CacheHitTokens": 1024,
"CacheMissTokens": 236,
"CacheWriteTokens": 0,
"CacheWriteBilledTokens": 0,
"ReasoningTokens": 0,
"FinishReason": "stop",
"Estimated": false,
"RequestCount": 1,
"ServerToolRequests": 0,
"ContextPromptTokens": 0,
"ContextCompletionTokens": 0,
"ContextReasoningTokens": 0,
"ContextCacheHitTokens": 0,
"ContextCacheMissTokens": 0
}
},
{
"kind": "independent",
"milliseconds": 1783,
"digest": true,
"grounded": true,
"tool_calls": 0,
"usage": {
"PromptTokens": 447,
"CompletionTokens": 242,
"TotalTokens": 689,
"CacheHitTokens": 256,
"CacheMissTokens": 191,
"CacheWriteTokens": 0,
"CacheWriteBilledTokens": 0,
"ReasoningTokens": 0,
"FinishReason": "stop",
"Estimated": false,
"RequestCount": 1,
"ServerToolRequests": 0,
"ContextPromptTokens": 0,
"ContextCompletionTokens": 0,
"ContextReasoningTokens": 0,
"ContextCacheHitTokens": 0,
"ContextCacheMissTokens": 0
}
}
]
},
{
"model": "deepseek-flash",
"rich": true,
"trial": 3,
"results": [
{
"kind": "warm",
"milliseconds": 834,
"digest": false,
"grounded": false,
"tool_calls": 1,
"usage": {
"PromptTokens": 1217,
"CompletionTokens": 39,
"TotalTokens": 1256,
"CacheHitTokens": 1024,
"CacheMissTokens": 193,
"CacheWriteTokens": 0,
"CacheWriteBilledTokens": 0,
"ReasoningTokens": 0,
"FinishReason": "tool_calls",
"Estimated": false,
"RequestCount": 1,
"ServerToolRequests": 0,
"ContextPromptTokens": 0,
"ContextCompletionTokens": 0,
"ContextReasoningTokens": 0,
"ContextCacheHitTokens": 0,
"ContextCacheMissTokens": 0
}
},
{
"kind": "reuse",
"milliseconds": 1575,
"digest": true,
"grounded": true,
"tool_calls": 0,
"usage": {
"PromptTokens": 1260,
"CompletionTokens": 210,
"TotalTokens": 1470,
"CacheHitTokens": 1024,
"CacheMissTokens": 236,
"CacheWriteTokens": 0,
"CacheWriteBilledTokens": 0,
"ReasoningTokens": 0,
"FinishReason": "stop",
"Estimated": false,
"RequestCount": 1,
"ServerToolRequests": 0,
"ContextPromptTokens": 0,
"ContextCompletionTokens": 0,
"ContextReasoningTokens": 0,
"ContextCacheHitTokens": 0,
"ContextCacheMissTokens": 0
}
},
{
"kind": "independent",
"milliseconds": 1815,
"digest": true,
"grounded": true,
"tool_calls": 0,
"usage": {
"PromptTokens": 447,
"CompletionTokens": 298,
"TotalTokens": 745,
"CacheHitTokens": 256,
"CacheMissTokens": 191,
"CacheWriteTokens": 0,
"CacheWriteBilledTokens": 0,
"ReasoningTokens": 0,
"FinishReason": "stop",
"Estimated": false,
"RequestCount": 1,
"ServerToolRequests": 0,
"ContextPromptTokens": 0,
"ContextCompletionTokens": 0,
"ContextReasoningTokens": 0,
"ContextCacheHitTokens": 0,
"ContextCacheMissTokens": 0
}
}
]
}
]
+12 -33
View File
@@ -20,7 +20,6 @@ import (
"time"
"reasonix/internal/base/tokencount"
"reasonix/internal/contract/ablation"
"reasonix/internal/contract/event"
"reasonix/internal/contract/provider"
"reasonix/internal/contract/tool"
@@ -42,7 +41,6 @@ func main() {
report := flag.String("report", "1,2,4,8", "generations to report on")
window := flag.Int("window", 128_000, "context window in tokens")
control := flag.Bool("control", true, "fidelity: also score probes against full history")
arm := flag.String("arm", "full", "full | incremental: re-derive each digest from canonical, or fold the previous projection")
_ = flag.Bool("snip", false, "accepted and ignored: automatic snip projections are gone")
out := flag.String("out", "", "write the JSON report here")
flag.Parse()
@@ -51,14 +49,11 @@ func main() {
res []genResult
err error
)
a := arms{incremental: *arm == "incremental"}
switch {
case *arm != "full" && *arm != "incremental":
err = fmt.Errorf("unknown arm %q", *arm)
case *mode == "cost":
res, err = runCost(*gens, *window, a)
case *mode == "fidelity":
res, err = runFidelity(*gens, *window, *control, a)
switch *mode {
case "cost":
res, err = runCost(*gens, *window)
case "fidelity":
res, err = runFidelity(*gens, *window, *control)
default:
err = fmt.Errorf("unknown mode %q", *mode)
}
@@ -66,9 +61,9 @@ func main() {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
printReport(*mode+" / "+*arm, res, reportAt(*report))
printReport(*mode, res, reportAt(*report))
if *out != "" {
b, _ := json.MarshalIndent(map[string]any{"mode": *mode, "arm": *arm, "window": *window, "generations": res}, "", " ")
b, _ := json.MarshalIndent(map[string]any{"mode": *mode, "window": *window, "generations": res}, "", " ")
if werr := os.WriteFile(*out, append(b, '\n'), 0o644); werr != nil {
fmt.Fprintln(os.Stderr, werr)
os.Exit(1)
@@ -101,7 +96,7 @@ type harness struct {
calls *callRecorder
}
func newHarness(t *testingDir, p provider.Provider, window int, rec *callRecorder, arm arms) *harness {
func newHarness(t *testingDir, p provider.Provider, window int, rec *callRecorder) *harness {
sess := newSession()
path := filepath.Join(t.dir, "session.jsonl")
a := agent.New(p, tool.NewRegistry(), sess, agent.Options{
@@ -112,26 +107,10 @@ func newHarness(t *testingDir, p provider.Provider, window int, rec *callRecorde
// boot's default when cfg.Agent.Keep is unset; without it the bench
// would measure a configuration no real session runs.
KeepPolicy: agent.KeepErrors,
Ablation: foldArm(arm.incremental),
}, rec.sink())
return &harness{sess: sess, agentA: a, path: path, calls: rec}
}
// foldArm switches full re-derivation off, which is what makes a fold read the
// previous projection instead of the canonical transcript.
// arms selects the maintenance behaviour under test. Snipping is off by default
// so a run stays comparable with baselines recorded before it existed.
type arms struct {
incremental bool
}
func foldArm(incremental bool) ablation.Set {
if incremental {
return ablation.New(ablation.FullFold)
}
return ablation.Set{}
}
// runGeneration grows the session and folds it, returning what that fold cost.
func (h *harness) runGeneration(ctx context.Context, gen int, probes []probe) genResult {
growSession(h.sess, gen, probes)
@@ -160,7 +139,7 @@ func (h *harness) runGeneration(ctx context.Context, gen int, probes []probe) ge
return r
}
func runCost(gens, window int, a arms) ([]genResult, error) {
func runCost(gens, window int) ([]genResult, error) {
dir, cleanup, err := tempDir()
if err != nil {
return nil, err
@@ -169,7 +148,7 @@ func runCost(gens, window int, a arms) ([]genResult, error) {
rec := &callRecorder{}
p := &scriptedProvider{rec: rec, reply: syntheticDigest, window: window}
h := newHarness(dir, p, window, rec, a)
h := newHarness(dir, p, window, rec)
var out []genResult
for gen := range gens {
@@ -178,7 +157,7 @@ func runCost(gens, window int, a arms) ([]genResult, error) {
return out, nil
}
func runFidelity(gens, window int, control bool, a arms) ([]genResult, error) {
func runFidelity(gens, window int, control bool) ([]genResult, error) {
key := os.Getenv("DEEPSEEK_API_KEY")
if key == "" {
return nil, fmt.Errorf("fidelity mode needs DEEPSEEK_API_KEY")
@@ -194,7 +173,7 @@ func runFidelity(gens, window int, control bool, a arms) ([]genResult, error) {
defer cleanup()
rec := &callRecorder{}
h := newHarness(dir, &recordingProvider{inner: p, rec: rec}, window, rec, a)
h := newHarness(dir, &recordingProvider{inner: p, rec: rec}, window, rec)
probes := probeSuite()
ctx := context.Background()
+1 -1
View File
@@ -12,7 +12,7 @@ func TestRepeatedFoldsStayBoundedAndKeepSucceeding(t *testing.T) {
reserve = 8192 // summaryOutputReserve: the digest the call must still return
maxCalls = 7 // maxSummarySpans + the merge pass
)
res, err := runCost(gens, window, arms{})
res, err := runCost(gens, window)
if err != nil {
t.Fatalf("runCost: %v", err)
}
+15 -14
View File
@@ -1,6 +1,6 @@
// Cost-capped seed → resume → continue smoke for content-driven maintenance.
// Offline covers seed/resume; live continue needs DEEPSEEK_API_KEY and allows
// at most one summary after growing past compact_ratio=0.85.
// Seed and resume run without a provider; live continue needs DEEPSEEK_API_KEY
// and starts exactly one summary after growing past compact_ratio=0.85.
package main
import (
@@ -70,7 +70,7 @@ func (s *recordSink) Emit(e event.Event) {
func prov() (provider.Provider, *provider.Pricing) {
key := os.Getenv("DEEPSEEK_API_KEY")
if key == "" {
fmt.Fprintln(os.Stderr, "DEEPSEEK_API_KEY not set (use -offline for seed/resume)")
fmt.Fprintln(os.Stderr, "DEEPSEEK_API_KEY not set (use -offline for seed)")
os.Exit(1)
}
p, err := provider.New("openai", provider.Config{
@@ -193,7 +193,10 @@ func seed(dir string, offline bool, cap *costCap) {
m.ProjectedTokens, m.TriggerTokens, m.ProjectionVersion, m.SummaryCalls, m.SpentUSD)
}
func resume(dir string, offline bool) {
// resume reads the seeded session back and checks that loading it neither
// advances the projection version nor pays for a summary. It never samples, so
// it takes no provider and needs no API key.
func resume(dir string) {
path := filepath.Join(dir, "session.jsonl")
m := readMeta(dir)
sess, err := sessionstore.LoadSession(path)
@@ -202,11 +205,7 @@ func resume(dir string, offline bool) {
os.Exit(1)
}
sink := &recordSink{}
var p provider.Provider
if !offline {
p, _ = prov()
}
a := newAgent(p, sess, path, sink)
a := newAgent(nil, sess, path, sink)
snap := a.ContextMaintenanceSnapshot()
if snap.ProjectionVersion != m.ProjectionVersion {
fmt.Fprintf(os.Stderr, "resume version = %d, want %d\n", snap.ProjectionVersion, m.ProjectionVersion)
@@ -258,12 +257,14 @@ func cont(dir string, offline bool, cap *costCap) {
fmt.Fprintln(os.Stderr, "continue prepare:", err)
os.Exit(1)
}
if sink.summaryStarts > 1 {
fmt.Fprintf(os.Stderr, "continue started %d summaries, want ≤1\n", sink.summaryStarts)
// The fixture is already above the trigger it was grown past, so a leg that
// starts no summary has not observed the fold it exists to test.
if sink.summaryStarts != 1 {
fmt.Fprintf(os.Stderr, "continue started %d summaries, want exactly 1\n", sink.summaryStarts)
os.Exit(1)
}
after := a.ContextMaintenanceSnapshot()
if sink.summaryStarts == 1 && after.ProjectionVersion != m.ProjectionVersion+1 {
if after.ProjectionVersion != m.ProjectionVersion+1 {
fmt.Fprintf(os.Stderr, "continue version = %d, want %d after one summary\n",
after.ProjectionVersion, m.ProjectionVersion+1)
os.Exit(1)
@@ -306,14 +307,14 @@ func cont(dir string, offline bool, cap *costCap) {
func main() {
dir := flag.String("dir", "benchmarks/context-maintenance-e2e/run", "state directory")
maxUSD := flag.Float64("max-usd", defaultMaxUSD, "hard cost cap for live API legs")
offline := flag.Bool("offline", false, "skip live provider (seed/resume only)")
offline := flag.Bool("offline", false, "skip live provider (seed only)")
flag.Parse()
cap := &costCap{maxUSD: *maxUSD}
switch flag.Arg(0) {
case "seed":
seed(*dir, *offline, cap)
case "resume":
resume(*dir, *offline)
resume(*dir)
case "continue":
cont(*dir, *offline, cap)
default:
+112 -20
View File
@@ -3,12 +3,12 @@
Two questions about long-horizon memory, and the harness that makes their
answers trustworthy.
-mode=preflight build all 12 tasks under every arm and check the contract
-mode=preflight build all 18 tasks under every arm and check the contract
-mode=snippet does a rank-1 hit hand over the whole answer?
-mode=adversarial send a model after the answer through every host surface
-mode=run-search Experiment R: is searchable canonical a real capability?
-mode=run-boundary Experiment I, paired: 6 tasks across their own cue boundary
-mode=run-index Experiment I, full: 6 tasks x 4 index budgets
-mode=run-boundary Experiment I, paired: the 6 efficiency tasks across their boundary
-mode=run-index Experiment I, full: the 6 efficiency tasks x 4 budgets
-mode=calibrate find each index task's PlantAfterGen for its cue tier
`-dry` drives any run mode with a scripted provider, for nothing. Real runs need
@@ -22,17 +22,28 @@ addresses by hand at roughly nine times the page-in cost (139 vs 1292
tokens/task). Measured after three separate leaks were closed, so the numbers
are from a clean environment.
**Fold index marginal utility: unresolved.** Both the positive and the negative
estimates are invalidated. The first Stage 2 ran in a contaminated environment.
The clean paired batch found a consistent effect (six of six tasks searched less
and paged in less with the cue visible), and the same-batch dose-response did
not reproduce it (`boundary-aligned 0/4`, recall-token delta reversing sign).
The most likely reason is that two of the six index tasks carry heavy-tail
stopping behaviour that swamps the effect being measured.
**Fold index marginal utility: direction reproduced, size still conditional.** On the v2
substrate the direction holds in six of six tasks and in the pooled paired batch: with the
cue visible the index addresses the target, so the same task is searched less and pages in
less (0.67 searches and 70 recall tokens per task, against 1.50 and 120 with the cue
absent).
Existing evidence is not sufficient to tune the shipped 1% budget in either
direction. Reopening this wants a new corpus, not more samples: see
"Index corpus v2" below.
A size is visible but is not yet quotable. On the four tasks whose absent-cue arm stayed
out of a loop, the index cost 64,952 prompt tokens against 115,551 without, 44% less
(`i106-budgetseal` -35,147, `i70-ingestfloor` -8,222, `i37-dispatchlag` -7,599, and
`i112-lagquota` +369, the prefix rent a task that never searches pays). That is a
**post-hoc subset**: `i118-fencelag` and `i47-tenantlease` are excluded because their
absent-cue arm looped in this batch (five and three retrieval rounds, three escapes each;
together 80% of the absent-cue total), and the qualification above does not screen for
stopping behaviour — holding on both boundary arms across three passes shows a stable cue
boundary, not a stopping arm. Quoting 44% as the index's effect would mean choosing the
sample after seeing the result; a batch screened on stopping stability is what would make
a size quotable, and one arm also reverses either way (`i70-ingestfloor` default, +11,135
prompt tokens over three escapes).
Batch facts, because the counts differ: 36 runs were executed (12 paired, 24 index), and
the token totals here cover the 24-run index batch — the paired batch ran before the
per-run usage line existed.
**Things that turned out not to be problems**, each after being measured rather
than argued about:
@@ -74,14 +85,95 @@ scanned for answer literals before the first provider request and again after
the turn. Any answer reaching the model through a tool that is not `recall`
marks the run contaminated and takes it out of every statistic.
## Index corpus v2, if this is reopened
## Index corpus v2
The present index tasks are not a fit substrate: two of six carry stopping
behaviour that dominates the signal. A task should qualify only if a calibration
run shows `PostSufficientSearches == 0`, alongside the existing requirements
(successful tool call, answer in the output rather than the cue, rank 1,
`FirstHitCoverage` complete, one or two simple values, nothing in the
workspace). Keep i03 and i04 — as a stopping corpus, not an efficiency one.
The efficiency substrate is six tasks that hold on both arms: `i37-dispatchlag`
and `i118-fencelag` (quarter), `i106-budgetseal` and `i70-ingestfloor` (half),
`i47-tenantlease` and `i112-lagquota` (default).
They came out of five batches of candidates, each screened the same way: one
`-mode=run-index` pass over the batch, then two more only for the candidates that
were clean on both boundary arms, and only the candidates that held on both arms
in all three passes stayed. A candidate that does not hold is dropped, not
promoted. The numbers are candidates, per tier:
| batch | quarter | half | default |
| --- | --- | --- | --- |
| first pool, `i31`-`i54` | 1 of 8 | 0 of 8 | 2 of 8 |
| second pool, `i61`-`i84` | 1 of 4 screened | 4 of 8 | 0 of 8 |
| third pool, `i85`-`i108` | 2 of 8 | 8 of 16 | none authored |
| replacement pool, `i109`-`i116` | - | - | 4 of 8 |
| replacement pool, `i92`, `i117`-`i124` | 3 of 9 | - | - |
That table counts candidates that held on both arms in all three passes, which is
not the size of the substrate. 89 candidates were authored, 77 were screened (the
second pool's quarter tier stopped at 4 of 8 and its default tier was never run),
and 25 held: 7 quarter, 12 half, 6 default. The substrate keeps two per tier, so
the other 19 survivors are spares rather than corpus members. Candidates from the
earlier round are not counted here.
| task | tier | cue present | cue absent |
| --- | --- | ---: | ---: |
| `i37-dispatchlag` | quarter | 3/3 | 3/3 |
| `i118-fencelag` | quarter | 3/3 | 3/3 |
| `i106-budgetseal` | half | 3/3 | 3/3 |
| `i70-ingestfloor` | half | 3/3 | 3/3 |
| `i47-tenantlease` | default | 3/3 | 3/3 |
| `i112-lagquota` | default | 3/3 | 3/3 |
Across their 36 judged cells: `SnippetStop` 23, `SnippetThenRead` 10 and
`DefensiveRead` 3 - every cell clean, nothing post-sufficient and nothing
`NeverSufficient`.
Four of the first six were flagged in review for sharing answer values, probe
queries and prompt wording within a tier. Separating them and re-screening those
four changed two of them: `i37` and `i47` held, `i62` and `i51` did not, so they
were replaced - `i90-recoverylag` was tried first and did not hold either, and
`i118-fencelag` and `i112-lagquota` are what did. The separation was not
cosmetic: the shared wording had been carrying two of the six.
Nothing in the task shape separates the survivors from the rest: within the half
tier, plain-number answers survived 0 of 8 in the first pool and 4 of 8 in the
second, and codename answers survived 8 of 16 in the third. An earlier claim here
that the answer kind decides whether a candidate holds does not survive this
table and is withdrawn. The dirty class was `PostSufficientRetrieval` in most
failures - the model re-checking a value it already had - but which tasks hit it
was not predictable from their shape at this sample.
`i01`-`i06` are the frozen study's six tasks, kept as `roleStopping`. Measured
once each with `-mode=run-index -task <id>`, per task and per arm:
| task | tier | cue present | cue absent |
| --- | --- | --- | --- |
| `i01-transport-fallback` | quarter | SnippetStop | DefensiveRead |
| `i02-scheduler-handoff` | quarter | PostSufficientRetrieval | PostSufficientRetrieval |
| `i03-coalescing` | half | PostSufficientRetrieval | PostSufficientRunaway |
| `i04-recovery-fence` | half | DefensiveRead | PostSufficientRunaway |
| `i05-cache-probe` | default | SnippetThenRead | PostSufficientRetrieval |
| `i06-dispatch-handoff` | default | SnippetStop | PostSufficientRetrieval |
Five of the six show post-sufficient behaviour in at least one arm (`i02`-`i06`),
and `i01` is clean on both. The cue-present arm is clean for four of them
(`i01`, `i04`, `i05`, `i06`), which is why they stay in the corpus as the
stopping side rather than being retitled. All six are excluded from `run-index`
and `run-boundary` batches and stay measurable one at a time with `-task <id>`. A
task added to the corpus declares a role, so it cannot arrive without one.
A task qualifies only if it is clean on both of its boundary arms - the
cue-present run and the cue-absent run - in every run of a screening batch. Clean
means a `stopping_class` other than `PostSufficientRetrieval` or
`PostSufficientRunaway`: `SnippetStop`, `SnippetThenRead` and `DefensiveRead`
all qualify, and a defensive read is deliberately not counted as waste. The
class comes from the three counts in that report — `searches_after_sufficient`,
`reads_after_sufficient` and `escapes_after_sufficient` — together with how
sufficiency was reached and how many rounds followed it. The remaining
requirements still apply — successful tool call, answer in the output rather
than the cue, rank 1, `FirstHitCoverage` complete, one or two simple values,
nothing in the workspace.
`-mode=calibrate` is not a screening tool: it builds and reads back the
fixtures, and reads nothing else, so its cue-visibility profile is the same for
every task.
Then repeats rather than levels: cue-present against cue-absent on each task's
own boundary, three runs per cell. What is unknown is the variance, and four
@@ -0,0 +1,86 @@
package main
import (
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
)
// runBlockedBatch runs one dry index batch where every arm's runOne fails
// before any model turn: the task's own directory is a regular file, so the
// fixture cannot be built. In production the same early return happens when the
// fixture build, the session load, or the answer-on-disk guard fails.
func runBlockedBatch(t *testing.T) string {
t.Helper()
tk := indexTasks()[0]
root := t.TempDir()
if err := os.WriteFile(filepath.Join(root, tk.ID), []byte("not a directory\n"), 0o644); err != nil {
t.Fatal(err)
}
if code := runExperiment("run-index", experimentIndex, root, true, []contextTask{tk}); code != 0 {
t.Fatalf("runExperiment exit = %d, want 0 (every run failed, but the batch finished)", code)
}
return root
}
func readResultLines(t *testing.T, root string) ([]contextMetrics, []string) {
t.Helper()
raw, err := os.ReadFile(filepath.Join(root, "results.jsonl"))
if err != nil {
t.Fatal(err)
}
var runs []contextMetrics
var lines []string
for text := range strings.SplitSeq(strings.TrimSpace(string(raw)), "\n") {
var m contextMetrics
if err := json.Unmarshal([]byte(text), &m); err != nil {
t.Fatalf("results.jsonl: %v", err)
}
runs = append(runs, m)
lines = append(lines, text)
}
return runs, lines
}
// A run that failed before the model turn produced no measurement. Recording it
// as a zero-valued contextMetrics puts it in the funnel's Scored denominator,
// in "Where it broke", and in the dose curve as a real zero.
func TestBatchDoesNotRecordAFailedRunAsAResult(t *testing.T) {
root := runBlockedBatch(t)
runs, lines := readResultLines(t, root)
if len(runs) == 0 {
t.Fatal("no runs recorded at all")
}
for i, m := range runs {
if m.Task == "" || m.Arm == "" || m.FailureStage == "" {
t.Fatalf("results.jsonl line %d records a run that never ran as a result "+
"(task=%q arm=%q failure_stage=%q):\n %s", i+1, m.Task, m.Arm, m.FailureStage, lines[i])
}
}
}
// The manifest is the artefact that says which runs counted, so a failed run
// must not be added to CompletedRuns or to the task id list.
func TestBatchManifestDoesNotReportFailedRunsAsCompleted(t *testing.T) {
root := runBlockedBatch(t)
var manifest batchManifest
data, err := os.ReadFile(filepath.Join(root, "batch.json"))
if err != nil {
t.Fatal(err)
}
if err := json.Unmarshal(data, &manifest); err != nil {
t.Fatal(err)
}
for _, id := range manifest.TaskIDs {
if id == "" {
t.Fatalf("manifest lists an empty task id: task_ids=%q (completed=%d planned=%d)",
manifest.TaskIDs, manifest.CompletedRuns, manifest.PlannedRuns)
}
}
if manifest.CompletedRuns == manifest.PlannedRuns {
t.Fatalf("manifest reports every planned run completed although none produced evidence: "+
"completed=%d planned=%d task_ids=%q", manifest.CompletedRuns, manifest.PlannedRuns, manifest.TaskIDs)
}
}
+155
View File
@@ -0,0 +1,155 @@
package main
import (
"crypto/sha256"
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"
"time"
)
// batchInfo is what the mode itself knows before any run happens. Everything
// else in the manifest is derived from the runs that were written, so a figure
// in it cannot drift from the artifact beside it.
type batchInfo struct {
Mode string
Planned int
Started time.Time
Dry bool
// Repo is the checkout the batch ran from, captured before the harness
// isolates its workspace: git cannot describe a directory that is gone.
Repo string
}
// batchManifest is written beside results.jsonl so a later reader does not have
// to reconstruct what the batch was from a terminal scrollback.
type batchManifest struct {
SchemaVersion int `json:"schema_version"`
Mode string `json:"mode"`
StartedAt string `json:"started_at"`
FinishedAt string `json:"finished_at"`
GitCommit string `json:"git_commit"`
GitDirty *bool `json:"git_dirty,omitempty"`
Model string `json:"model"`
Window int `json:"window"`
Generations int `json:"generations"`
TaskIDs []string `json:"task_ids"`
Arms []string `json:"arms"`
PlannedRuns int `json:"planned_runs"`
CompletedRuns int `json:"completed_runs"`
// FailedRuns counts the planned runs that ended before a transcript existed.
// They are rows in results.jsonl, never part of CompletedRuns, and the field
// is absent when there were none so an intact batch is unchanged.
FailedRuns int `json:"failed_runs,omitempty"`
// Usage totals are absent when nothing reported any: an unmeasured cost must
// not read as a measured zero. UsageReportedRuns stays, because zero there
// is a fact about the batch.
UsageReportedRuns int `json:"usage_reported_runs"`
UsageReportedRequests int `json:"usage_reported_requests,omitempty"`
PromptTokens int `json:"prompt_tokens,omitempty"`
CompletionTokens int `json:"completion_tokens,omitempty"`
CacheHitTokens int `json:"cache_hit_tokens,omitempty"`
CacheMissTokens int `json:"cache_miss_tokens,omitempty"`
UsageEstimated bool `json:"usage_estimated,omitempty"`
ResultsSHA256 string `json:"results_sha256"`
TrajectoriesSHA256 string `json:"trajectories_sha256"`
}
// gitCommitAndDirty reports the checkout the batch ran from. Unknown is written
// rather than omitted, so an absent field is never mistaken for a clean tree.
func gitCommitAndDirty(repo string) (commit string, dirty *bool) {
commit = "unknown"
if repo == "" {
return commit, nil
}
in := func(args ...string) ([]byte, error) {
cmd := exec.Command("git", args...)
cmd.Dir = repo
return cmd.Output()
}
if out, err := in("rev-parse", "--short", "HEAD"); err == nil {
commit = strings.TrimSpace(string(out))
}
if out, err := in("status", "--porcelain"); err == nil {
state := strings.TrimSpace(string(out)) != ""
dirty = &state
}
return commit, dirty
}
func fileSHA256(path string) (string, error) {
data, err := os.ReadFile(path)
if err != nil {
return "", err
}
return fmt.Sprintf("%x", sha256.Sum256(data)), nil
}
func sortedKeys(set map[string]bool) []string {
out := make([]string, 0, len(set))
for k := range set {
out = append(out, k)
}
sort.Strings(out)
return out
}
// writeBatchManifest records the batch beside the runs it describes. It runs
// last, so its digests are of what is already on disk.
func writeBatchManifest(root string, info batchInfo, all []contextMetrics) error {
ids, arms := map[string]bool{}, map[string]bool{}
var prompt, completion, cacheHit, cacheMiss, requests, reported, failed int
estimated := false
for _, m := range all {
// A failed run has no task, arm or evidence to contribute: it is counted
// as a failure below and is not part of what the batch measured.
if m.Failed {
failed++
continue
}
ids[m.Task] = true
arms[m.Arm] = true
if m.UsageReportedRequests > 0 {
reported++
requests += m.UsageReportedRequests
prompt += m.PromptTokens
completion += m.CompletionTokens
cacheHit += m.CacheHitTokens
cacheMiss += m.CacheMissTokens
estimated = estimated || m.UsageEstimated
}
}
commit, dirty := gitCommitAndDirty(info.Repo)
model := runModel
if info.Dry {
model = "scripted"
}
manifest := batchManifest{
SchemaVersion: 1, Mode: info.Mode,
StartedAt: info.Started.UTC().Format(time.RFC3339),
FinishedAt: time.Now().UTC().Format(time.RFC3339),
GitCommit: commit, GitDirty: dirty,
Model: model, Window: fixtureWindow, Generations: fixtureGenerations,
TaskIDs: sortedKeys(ids), Arms: sortedKeys(arms),
PlannedRuns: info.Planned, CompletedRuns: len(all) - failed, FailedRuns: failed,
UsageReportedRuns: reported, UsageReportedRequests: requests,
PromptTokens: prompt, CompletionTokens: completion,
CacheHitTokens: cacheHit, CacheMissTokens: cacheMiss, UsageEstimated: estimated,
}
var err error
if manifest.ResultsSHA256, err = fileSHA256(filepath.Join(root, "results.jsonl")); err != nil {
return err
}
if manifest.TrajectoriesSHA256, err = fileSHA256(filepath.Join(root, "trajectories.jsonl")); err != nil {
return err
}
data, err := json.MarshalIndent(manifest, "", " ")
if err != nil {
return err
}
return os.WriteFile(filepath.Join(root, "batch.json"), append(data, '\n'), 0o644)
}
@@ -0,0 +1,91 @@
package main
import (
"crypto/sha256"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
func seedArtifacts(t *testing.T, root string) {
t.Helper()
for _, name := range []string{"results.jsonl", "trajectories.jsonl"} {
if err := os.WriteFile(filepath.Join(root, name), []byte("{}\n"), 0o644); err != nil {
t.Fatal(err)
}
}
}
func readManifest(t *testing.T, root string) (batchManifest, string) {
t.Helper()
data, err := os.ReadFile(filepath.Join(root, "batch.json"))
if err != nil {
t.Fatal(err)
}
var got batchManifest
if err := json.Unmarshal(data, &got); err != nil {
t.Fatal(err)
}
return got, string(data)
}
func TestBatchManifestRecordsWhatTheBatchWas(t *testing.T) {
root := t.TempDir()
seedArtifacts(t, root)
all := []contextMetrics{
{Task: "b", Arm: "index-off"},
{Task: "a", Arm: "index-off"},
{Task: "a", Arm: "index-half", UsageReportedRequests: 2, PromptTokens: 100,
CompletionTokens: 10, CacheHitTokens: 60, CacheMissTokens: 40},
}
if err := writeBatchManifest(root, batchInfo{Mode: "run-index", Planned: 4, Started: time.Now().Add(-time.Minute)}, all); err != nil {
t.Fatal(err)
}
got, _ := readManifest(t, root)
if got.SchemaVersion != 1 || got.Mode != "run-index" || got.PlannedRuns != 4 || got.CompletedRuns != 3 {
t.Fatalf("manifest header = %+v", got)
}
if strings.Join(got.TaskIDs, ",") != "a,b" || strings.Join(got.Arms, ",") != "index-half,index-off" {
t.Fatalf("ids/arms = %v / %v, want sorted and unique", got.TaskIDs, got.Arms)
}
if got.UsageReportedRuns != 1 || got.UsageReportedRequests != 2 || got.PromptTokens != 100 ||
got.CompletionTokens != 10 || got.CacheHitTokens != 60 || got.CacheMissTokens != 40 {
t.Fatalf("usage totals = %+v", got)
}
// No repo means git could not describe the checkout: unknown commit, and no
// dirty flag at all, because "clean" is a claim this batch cannot make.
if got.GitCommit != "unknown" || got.GitDirty != nil {
t.Fatalf("git state = %q / %v, want unknown and no dirty flag", got.GitCommit, got.GitDirty)
}
want := fmt.Sprintf("%x", sha256.Sum256([]byte("{}\n")))
if got.ResultsSHA256 != want || got.TrajectoriesSHA256 != want {
t.Fatalf("digests = %q / %q, want %q", got.ResultsSHA256, got.TrajectoriesSHA256, want)
}
}
// A dry batch pays nothing, so its cost fields must be absent rather than zero:
// an unmeasured spend must not read as a measured one.
func TestBatchManifestLeavesUnreportedUsageAbsent(t *testing.T) {
root := t.TempDir()
seedArtifacts(t, root)
all := []contextMetrics{{Task: "a", Arm: "index-off"}, {Task: "a", Arm: "index-half"}}
if err := writeBatchManifest(root, batchInfo{Mode: "run-index", Planned: 2, Started: time.Now(), Dry: true}, all); err != nil {
t.Fatal(err)
}
got, raw := readManifest(t, root)
if got.UsageReportedRuns != 0 {
t.Fatalf("usage_reported_runs = %d, want 0 reported", got.UsageReportedRuns)
}
if got.Model != "scripted" {
t.Fatalf("dry batch model = %q, want scripted", got.Model)
}
for _, absent := range []string{"usage_reported_requests", "prompt_tokens", "completion_tokens", "cache_hit_tokens"} {
if strings.Contains(raw, absent) {
t.Fatalf("a dry batch wrote %q:\n%s", absent, raw)
}
}
}
@@ -0,0 +1,69 @@
package main
import (
"strings"
"testing"
)
// boundaryRuns builds the input reportBoundaries reads: one scored run for an
// index task's cue side, and one failed run for another index task's cue side.
// The runs are constructed because the report is a pure function of the slice;
// the batch path that produces them is covered by the failed-batch tests.
func boundaryRuns(t *testing.T) (all []contextMetrics, scoredTask, failedTask string) {
t.Helper()
index := indexTasks()
if len(index) < 2 {
t.Fatalf("need at least two index tasks, have %d", len(index))
}
scored, failed := index[0], index[1]
cue, noCue := boundaryPair(scored.CueTier)
if cue == "" || noCue == "" {
t.Fatalf("task %s has an empty boundary pair (%q, %q)", scored.ID, cue, noCue)
}
failedCue, _ := boundaryPair(failed.CueTier)
if failedCue == "" {
t.Fatalf("task %s has no cue side", failed.ID)
}
all = []contextMetrics{
{Task: scored.ID, Arm: "index-" + cue, AnswerRecovered: true, SearchCalls: 1, RecallReturnedTokens: 318},
{Task: scored.ID, Arm: "index-" + noCue, AnswerRecovered: false},
{Task: failed.ID, Arm: "index-" + failedCue, Failed: true, FailureStage: stageNoEvidence},
}
return all, scored.ID, failed.ID
}
// TestBoundaryReportExcludesFailedRuns: a run that never produced a transcript
// must not appear in the cue-boundary table, which was the last reporting exit
// that still folded it in as a zero measurement.
func TestBoundaryReportExcludesFailedRuns(t *testing.T) {
all, scoredTask, failedTask := boundaryRuns(t)
out := captureStdout(t, func() { reportBoundaries(all) })
if strings.Contains(out, failedTask) {
t.Fatalf("the boundary table printed the failed task %q as a measurement:\n%s", failedTask, out)
}
if !strings.Contains(out, scoredTask) {
t.Fatalf("the boundary table dropped the scored task %q, so the exclusion is not specific to failures:\n%s", scoredTask, out)
}
if !strings.Contains(out, "cue-present") || !strings.Contains(out, "cue-absent") {
t.Fatalf("the pooled comparison disappeared:\n%s", out)
}
}
// TestBoundaryReportSaysNothingToCompareWhenEveryRunFailed: with no scored run
// the report must say so, rather than printing a comparison of zeros.
func TestBoundaryReportSaysNothingToCompareWhenEveryRunFailed(t *testing.T) {
all, _, _ := boundaryRuns(t)
failedOnly := make([]contextMetrics, 0, len(all))
for _, m := range all {
m.Failed = true
m.FailureStage = stageNoEvidence
failedOnly = append(failedOnly, m)
}
out := captureStdout(t, func() { reportBoundaries(failedOnly) })
if !strings.Contains(out, "scored=0") || !strings.Contains(out, "failed=") {
t.Fatalf("an all-failed batch did not report scored=0 with a failed count:\n%s", out)
}
if strings.Contains(out, "cue-present") || strings.Contains(out, "cue-absent") {
t.Fatalf("an all-failed batch still printed a pooled comparison of zeros:\n%s", out)
}
}
+25 -2
View File
@@ -18,7 +18,8 @@ import (
// canonical position, and never the model's prose. "I will search the history"
// is not a search; a recall call with a query is.
// failure stages, in funnel order. Exactly one is reported per run.
// failure stages, in funnel order. Exactly one is reported per run, except for
// NoEvidence: that stage belongs to a run with no measurement at all.
const (
stageReadMissed = "NoSearch" // never asked, and never read the target either
stageSearchMiss = "SearchMiss" // searched, target never came back
@@ -26,6 +27,7 @@ const (
stageAnswerWrong = "ReadButAnswerWrong" // opened it and still answered wrong
stageRecovered = "Recovered"
stageContaminated = "Contaminated"
stageNoEvidence = "NoEvidence"
// Routing is judged by model round, not by the linear order of tool events:
// two calls in one round are one parallel fan-out, and calling that
@@ -89,6 +91,15 @@ type contextMetrics struct {
// resident prefix; this is what is paid dynamically instead.
RecallReturnedTokens int `json:"recall_returned_tokens"`
// Provider usage as reported on the stream, or empty for a dry run. The count
// is completions that reported usage, so it is not a count of attempts.
UsageReportedRequests int `json:"usage_reported_requests,omitempty"`
PromptTokens int `json:"prompt_tokens,omitempty"`
CompletionTokens int `json:"completion_tokens,omitempty"`
CacheHitTokens int `json:"cache_hit_tokens,omitempty"`
CacheMissTokens int `json:"cache_miss_tokens,omitempty"`
UsageEstimated bool `json:"usage_estimated,omitempty"`
Searches []searchAttempt `json:"searches,omitempty"`
// PostHitSearches are queries issued after the target had already been
// returned: not a query problem, a result-to-read problem.
@@ -107,6 +118,12 @@ type contextMetrics struct {
FailureStage string `json:"failure_stage"`
TimeoutReason string `json:"timeout_reason,omitempty"`
// Failed marks a run that ended before a transcript existed: it has no score
// and no place in the statistics. The row stays in results.jsonl with its
// task, arm and stage, so a failure is counted rather than dropped.
Failed bool `json:"failed,omitempty"`
Reason string `json:"reason,omitempty"`
// UnexpectedWorkTools are investigation tools used instead of recall.
// Reaching for the workspace is behaviour, not noise: how a model routes
// its uncertainty when memory is missing is part of what is measured.
@@ -453,6 +470,12 @@ type funnel struct {
func summarize(arm string, runs []contextMetrics) funnel {
f := funnel{Arm: arm, Runs: len(runs), Stages: map[string]int{}, Routes: map[string]int{}}
for _, r := range runs {
// A run that never produced a transcript is not a scored run and has no
// stage to place: counting it would put an empty name in the funnel, the
// stage table and the routes.
if r.Failed {
continue
}
if r.Contaminated {
f.Contaminated++
f.Stages[r.FailureStage]++
@@ -622,7 +645,7 @@ func reportStopping(all []contextMetrics) string {
var extraRounds, extraTokens []float64
sufficient, answeredNext := 0, 0
for _, m := range all {
if m.Contaminated {
if m.Failed || m.Contaminated {
continue
}
classes[m.StoppingClass]++
+132 -7
View File
@@ -26,6 +26,12 @@ const (
tierDefault = "default" // visible at default only
)
// role places an index task in the efficiency substrate or the stopping corpus.
const (
roleEfficiency = "efficiency"
roleStopping = "stopping"
)
// plantKind is how a task's target enters the transcript.
const (
plantAssistant = "assistant" // the model's own account: never index-addressed
@@ -45,6 +51,8 @@ type contextTask struct {
AnswerVars []string
CueVar string
CueTier string
// Role is roleEfficiency or roleStopping; it decides the batch a task enters.
Role string
// PlantAfterGen ages the target: mergeFoldIndex trims from its oldest end,
// so what follows a cue decides which budget addresses it. Frozen, because
// a run that searched for it would let the tested code move its goalposts.
@@ -121,6 +129,20 @@ func num(name string, lo, hi int) varSpec {
return varSpec{Name: name, Kind: varInt, Min: lo, Max: hi}
}
// efficiencyTasks is the substrate a study measures on. Every task in it
// qualifies under the corpus bar; the stopping corpus is deliberately excluded,
// because a task carrying post-sufficient stopping behaviour swamps the effect
// an efficiency batch exists to measure.
func efficiencyTasks() []contextTask {
var out []contextTask
for _, t := range indexTasks() {
if t.Role == roleEfficiency {
out = append(out, t)
}
}
return out
}
func searchTasks() []contextTask {
return []contextTask{
{
@@ -192,10 +214,104 @@ func searchTasks() []contextTask {
}
}
func indexTasks() []contextTask {
// efficiencyIndexTasks is what a study measures on: two tasks per cue tier.
func efficiencyIndexTasks() []contextTask {
out := indexQuarterTasks()
out = append(out, indexHalfTasks()...)
out = append(out, indexDefaultTasks()...)
return out
}
// indexQuarterTasks holds the quarter-tier literals.
func indexQuarterTasks() []contextTask {
return []contextTask{
{
ID: "i01-transport-fallback", Experiment: experimentIndex, TargetKind: "tool_input",
ID: "i37-dispatchlag", Experiment: experimentIndex, TargetKind: "tool_input", Role: roleEfficiency,
Prompt: "早前会话里执行过 ./scripts/dispatchlag,它打印出的 dispatch lag epoch 是多少?",
ProbeQuery: "dispatchlag dispatch lag epoch",
Vars: []varSpec{code("cue", "dispatchlag"), code("epoch", "cobalt")},
AnswerVars: []string{"epoch"}, CueVar: "cue", CueTier: tierQuarter,
PlantAfterGen: 4,
Plant: plantCall, CallID: "p37", CallTool: "bash",
CallArgs: `{"command":"./scripts/dispatchlag {{cue}}"}`,
PlantBody: "dispatch lag epoch = {{epoch}}",
},
{
ID: "i118-fencelag", Experiment: experimentIndex, TargetKind: "tool_input", Role: roleEfficiency,
Prompt: "早前会话里运行过 ./scripts/fencelag,它打印出的 fence lag 是多少?",
ProbeQuery: "fencelag fence lag",
Vars: []varSpec{code("cue", "fencelag"), code("v", "slate")},
AnswerVars: []string{"v"}, CueVar: "cue", CueTier: tierQuarter,
PlantAfterGen: 4,
Plant: plantCall, CallID: "x118", CallTool: "bash",
CallArgs: `{"command":"./scripts/fencelag {{cue}}"}`,
PlantBody: "fence lag = {{v}}",
},
}
}
// indexHalfTasks holds the half-tier literals.
func indexHalfTasks() []contextTask {
return []contextTask{
{
ID: "i70-ingestfloor", Experiment: experimentIndex, TargetKind: "tool_input", Role: roleEfficiency,
Prompt: "早前会话里执行过 ./scripts/ingestfloor,它打印出的 route quota 是多少?",
ProbeQuery: "ingestfloor quota",
Vars: []varSpec{code("cue", "ingestfloor"), num("quota", 1200, 9800)},
AnswerVars: []string{"quota"}, CueVar: "cue", CueTier: tierHalf,
PlantAfterGen: 2,
Plant: plantCall, CallID: "q70", CallTool: "bash",
CallArgs: `{"command":"./scripts/ingestfloor {{cue}}"}`,
PlantBody: "route quota = {{quota}}",
},
{
ID: "i106-budgetseal", Experiment: experimentIndex, TargetKind: "tool_input", Role: roleEfficiency,
Prompt: "早前会话里执行过 ./scripts/budgetseal,它打印出的 route quota 是多少?",
ProbeQuery: "budgetseal route quota",
Vars: []varSpec{code("cue", "budgetseal"), code("quota", "amber")},
AnswerVars: []string{"quota"}, CueVar: "cue", CueTier: tierHalf,
PlantAfterGen: 2,
Plant: plantCall, CallID: "h106", CallTool: "bash",
CallArgs: `{"command":"./scripts/budgetseal {{cue}}"}`,
PlantBody: "route quota = {{quota}}",
},
}
}
// indexDefaultTasks holds the default-tier literals.
func indexDefaultTasks() []contextTask {
return []contextTask{
{
ID: "i47-tenantlease", Experiment: experimentIndex, TargetKind: "tool_input", Role: roleEfficiency,
Prompt: "早前会话里执行过 ./scripts/tenantlease,它打印出的 tenant lease(秒)是多少?",
ProbeQuery: "tenantlease tenant lease",
Vars: []varSpec{code("cue", "tenantlease"), {Name: "lease", Kind: varDuration, Min: 15, Max: 300}},
AnswerVars: []string{"lease"}, CueVar: "cue", CueTier: tierDefault,
PlantAfterGen: 0,
Plant: plantCall, CallID: "p47", CallTool: "bash",
CallArgs: `{"command":"./scripts/tenantlease {{cue}}"}`,
PlantBody: "tenant lease = {{lease}}",
},
{
ID: "i112-lagquota", Experiment: experimentIndex, TargetKind: "tool_input", Role: roleEfficiency,
Prompt: "早前会话里执行过 ./scripts/lagquota,它打印出的 lagquota lease 是多少?",
ProbeQuery: "lagquota lagquota lease",
Vars: []varSpec{code("cue", "lagquota"), {Name: "lease", Kind: varDuration, Min: 20, Max: 420}},
AnswerVars: []string{"lease"}, CueVar: "cue", CueTier: tierDefault,
PlantAfterGen: 0,
Plant: plantCall, CallID: "d112", CallTool: "bash",
CallArgs: `{"command":"./scripts/lagquota {{cue}}"}`,
PlantBody: "lagquota lease = {{lease}}",
},
}
}
// stoppingIndexTasks is the frozen study's six tasks, kept out of efficiency
// batches and measurable on their own with -task <id>.
func stoppingIndexTasks() []contextTask {
return []contextTask{
{
ID: "i01-transport-fallback", Experiment: experimentIndex, TargetKind: "tool_input", Role: roleStopping,
Prompt: "transport fallback probe 当时报告的 selector 是什么?",
ProbeQuery: "transport fallback probe selector",
Vars: []varSpec{code("cue", "tfprobe"), code("selector", "fern")},
@@ -206,7 +322,7 @@ func indexTasks() []contextTask {
PlantBody: "fallback selector = {{selector}}",
},
{
ID: "i02-scheduler-handoff", Experiment: experimentIndex, TargetKind: "tool_input",
ID: "i02-scheduler-handoff", Experiment: experimentIndex, TargetKind: "tool_input", Role: roleStopping,
Prompt: "scheduler handoff 当时记录的 grace 和 marker 是什么?",
ProbeQuery: "scheduler handoff grace marker",
Vars: []varSpec{
@@ -220,7 +336,7 @@ func indexTasks() []contextTask {
PlantBody: "handoff grace = {{grace}}\nmarker = {{marker}}",
},
{
ID: "i03-coalescing", Experiment: experimentIndex, TargetKind: "tool_input",
ID: "i03-coalescing", Experiment: experimentIndex, TargetKind: "tool_input", Role: roleStopping,
Prompt: "job completion 的 coalescing probe 最后记录的 window 和 marker 是多少?",
ProbeQuery: "coalescing policy completion window",
Vars: []varSpec{
@@ -234,7 +350,7 @@ func indexTasks() []contextTask {
PlantBody: "completion coalesce window = {{window}}\nmarker = {{marker}}",
},
{
ID: "i04-recovery-fence", Experiment: experimentIndex, TargetKind: "tool_input",
ID: "i04-recovery-fence", Experiment: experimentIndex, TargetKind: "tool_input", Role: roleStopping,
Prompt: "recovery fence 那次检查报告的 marker 和两个 epoch 是什么?",
ProbeQuery: "recovery fence epoch check",
Vars: []varSpec{
@@ -248,7 +364,7 @@ func indexTasks() []contextTask {
PlantBody: "{{marker}}\nexpected epoch {{expected}}\nobserved epoch {{observed}}",
},
{
ID: "i05-cache-probe", Experiment: experimentIndex, TargetKind: "tool_input",
ID: "i05-cache-probe", Experiment: experimentIndex, TargetKind: "tool_input", Role: roleStopping,
Prompt: "cache probe 当时记录的 prefix salt 和 scope 是什么?",
ProbeQuery: "cache probe prefix salt scope",
Vars: []varSpec{code("cue", "cacheprobe"), code("salt", "comet"), code("scope", "lineage")},
@@ -259,7 +375,7 @@ func indexTasks() []contextTask {
PlantBody: "prefix salt = {{salt}}\nscope = {{scope}}",
},
{
ID: "i06-dispatch-handoff", Experiment: experimentIndex, TargetKind: "tool_input",
ID: "i06-dispatch-handoff", Experiment: experimentIndex, TargetKind: "tool_input", Role: roleStopping,
Prompt: "dispatch handoff probe 当时确认的 mode 和 marker 是什么?",
ProbeQuery: "dispatch handoff probe mode",
Vars: []varSpec{code("cue", "dispatchhandoff"), code("mode", "trigger"), code("marker", "iris")},
@@ -272,6 +388,12 @@ func indexTasks() []contextTask {
}
}
// indexTasks is the whole index corpus, the efficiency substrate first.
func indexTasks() []contextTask {
out := efficiencyIndexTasks()
return append(out, stoppingIndexTasks()...)
}
func allTasks() []contextTask {
return append(searchTasks(), indexTasks()...)
}
@@ -332,6 +454,9 @@ func validateCorpus() error {
if t.CueVar == "" {
return fmt.Errorf("%s: an index task needs a cue var", t.ID)
}
if t.Role != roleEfficiency && t.Role != roleStopping {
return fmt.Errorf("%s: an index task needs a role (%s or %s)", t.ID, roleEfficiency, roleStopping)
}
if v, _ := tierScales(t.CueTier); v == nil {
return fmt.Errorf("%s: unknown cue tier %q", t.ID, t.CueTier)
}
+1 -1
View File
@@ -121,7 +121,7 @@ var doseMetrics = []struct {
func reportDoseResponse(all []contextMetrics) string {
valid := map[string]map[string]contextMetrics{}
for _, m := range all {
if m.Contaminated || m.FailureStage == "RunError" {
if m.Failed || m.Contaminated || m.FailureStage == "RunError" {
continue
}
scale := strings.TrimPrefix(m.Arm, "index-")
@@ -0,0 +1,306 @@
package main
import (
"encoding/json"
"os"
"path/filepath"
"reflect"
"strconv"
"strings"
"testing"
)
// blockTask makes one task fail before its first model turn: its own directory
// is a regular file, so buildFixture cannot create the session under it. It is
// the same early exit as a failed fixture build, session load, or
// answer-on-disk guard, and it needs no provider.
func blockTask(t *testing.T, root, id string) {
t.Helper()
if err := os.WriteFile(filepath.Join(root, id), []byte("not a directory\n"), 0o644); err != nil {
t.Fatal(err)
}
}
// mixedTaskPair is one task whose runs all fail and one whose runs all succeed.
// Both are drawn from the corpus, so the batch is the real pipeline.
func mixedTaskPair() (blocked, ok contextTask) {
blocked, ok = indexTasks()[0], indexTasks()[1]
for _, t := range indexTasks() {
if t.Role == roleEfficiency && len(armsFor(t)) == 2 {
ok = t
break
}
}
return blocked, ok
}
// runBlockedExperiment runs one dry index batch over the given tasks, with the
// named task failing before any model turn, and returns the batch directory, the
// runs, the manifest, and everything the run printed.
func runBlockedExperiment(t *testing.T, tasks []contextTask, blocked ...string) (string, []contextMetrics, batchManifest, string) {
t.Helper()
root := t.TempDir()
for _, id := range blocked {
blockTask(t, root, id)
}
out := captureStdout(t, func() {
if code := runExperiment("run-index", experimentIndex, root, true, tasks); code != 0 {
t.Errorf("runExperiment exit = %d, want 0 (the batch finished)", code)
}
})
runs, _ := readResultLines(t, root)
manifest, _ := readManifest(t, root)
return root, runs, manifest, out
}
// captureStdout reads everything the function prints. os.Pipe is unbuffered, so
// the reader must run beside the call.
func captureStdout(t *testing.T, f func()) string {
t.Helper()
r, w, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
old := os.Stdout
os.Stdout = w
done := make(chan string, 1)
go func() {
var b strings.Builder
buf := make([]byte, 4096)
for {
n, err := r.Read(buf)
b.Write(buf[:n])
if err != nil {
break
}
}
done <- b.String()
}()
f()
os.Stdout = old
w.Close()
text := <-done
r.Close()
return text
}
// funnelScored reads the "runs=N" column of the named arm's funnel line out of
// the printed tables, so the assertion is on the number a reader sees.
func funnelScored(t *testing.T, out, arm string) int {
t.Helper()
for line := range strings.SplitSeq(out, "\n") {
fields := strings.Fields(line)
if len(fields) < 2 || fields[0] != arm || !strings.HasPrefix(fields[1], "runs=") {
continue
}
n, err := strconv.Atoi(strings.TrimPrefix(fields[1], "runs="))
if err != nil {
t.Fatalf("funnel line for %s: %v\n%s", arm, err, line)
}
return n
}
t.Fatalf("no funnel line for arm %s:\n%s", arm, out)
return 0
}
// The batch report states both counts on one line, so a reader of the scrollback
// sees the scored set and the failures without counting funnel rows.
func reportCounts(t *testing.T, out string) (scored, failed int) {
t.Helper()
for line := range strings.SplitSeq(out, "\n") {
if !strings.Contains(line, " scored=") || !strings.Contains(line, " failed=") {
continue
}
for f := range strings.FieldsSeq(line) {
switch {
case strings.HasPrefix(f, "scored="):
scored, _ = strconv.Atoi(strings.TrimPrefix(f, "scored="))
case strings.HasPrefix(f, "failed="):
failed, _ = strconv.Atoi(strings.TrimPrefix(f, "failed="))
}
}
return scored, failed
}
t.Fatalf("no line reports scored= and failed=:\n%s", out)
return 0, 0
}
// A batch that mixes a task that never ran with one that did must read as its
// successful runs only: the failed task leaves Scored, the funnel, and the
// completed count, and is reported as a failure with its identity instead.
// Every assertion here is a number, not the absence of an empty row.
func TestMixedBatchCountsOnlyTheRunThatProducedEvidence(t *testing.T) {
blocked, ok := mixedTaskPair()
_, runs, manifest, out := runBlockedExperiment(t, []contextTask{blocked, ok}, blocked.ID)
wantScored, wantFailed := len(armsFor(ok)), len(armsFor(blocked))
if manifest.CompletedRuns != wantScored || manifest.FailedRuns != wantFailed {
t.Fatalf("manifest completed=%d failed=%d, want %d and %d (planned=%d)",
manifest.CompletedRuns, manifest.FailedRuns, wantScored, wantFailed, manifest.PlannedRuns)
}
if len(runs) != manifest.CompletedRuns+manifest.FailedRuns {
t.Fatalf("results.jsonl has %d rows but completed=%d failed=%d",
len(runs), manifest.CompletedRuns, manifest.FailedRuns)
}
for _, id := range manifest.TaskIDs {
if id == blocked.ID {
t.Fatalf("task_ids lists %s, which produced no evidence: %v", blocked.ID, manifest.TaskIDs)
}
}
scored, failures := 0, 0
for _, m := range runs {
if m.Failed {
failures++
if m.Task != blocked.ID || m.FailureStage == "" || m.Reason == "" {
t.Fatalf("failed row lost its identity or stage: %+v", m)
}
continue
}
scored++
if m.Task != ok.ID {
t.Fatalf("a run scored from the wrong task: %+v", m)
}
}
if scored != wantScored || failures != wantFailed {
t.Fatalf("rows scored=%d failed=%d, want %d and %d", scored, failures, wantScored, wantFailed)
}
// Each arm's funnel holds exactly the successful task's runs, and the two
// scored counts agree with each other and with the manifest.
total := 0
for _, arm := range armsFor(ok) {
total += funnelScored(t, out, arm.name)
}
if total != manifest.CompletedRuns {
t.Fatalf("funnels scored %d runs, manifest completed_runs = %d:\n%s", total, manifest.CompletedRuns, out)
}
if gotScored, gotFailed := reportCounts(t, out); gotScored != manifest.CompletedRuns || gotFailed != manifest.FailedRuns {
t.Fatalf("report says scored=%d failed=%d, manifest says %d and %d",
gotScored, gotFailed, manifest.CompletedRuns, manifest.FailedRuns)
}
if strings.Contains(out, "## Where it broke") && strings.Contains(out, "=-\n") {
t.Fatalf("the stage table kept an empty stage:\n%s", out)
}
}
// A batch where every run fails must not read as an empty success: zero scored
// runs, a nonzero failed count, and both stated.
func TestAllFailedBatchReportsZeroScoredAndNonzeroFailed(t *testing.T) {
blocked, _ := mixedTaskPair()
_, runs, manifest, out := runBlockedExperiment(t, []contextTask{blocked}, blocked.ID)
if manifest.CompletedRuns != 0 {
t.Fatalf("completed_runs = %d, want 0 (nothing produced evidence)", manifest.CompletedRuns)
}
if manifest.FailedRuns != manifest.PlannedRuns || manifest.FailedRuns == 0 {
t.Fatalf("failed_runs = %d, planned_runs = %d: every planned run failed",
manifest.FailedRuns, manifest.PlannedRuns)
}
if len(manifest.TaskIDs) != 0 {
t.Fatalf("task_ids = %v, want none", manifest.TaskIDs)
}
for i, m := range runs {
if !m.Failed || m.FailureStage == "" || m.Task != blocked.ID {
t.Fatalf("results.jsonl line %d is not a marked failure: %+v", i+1, m)
}
}
total := 0
for _, arm := range armsFor(blocked) {
total += funnelScored(t, out, arm.name)
}
if total != 0 {
t.Fatalf("funnels scored %d runs although nothing ran:\n%s", total, out)
}
if gotScored, gotFailed := reportCounts(t, out); gotScored != 0 || gotFailed != manifest.FailedRuns {
t.Fatalf("report says scored=%d failed=%d, want 0 and %d", gotScored, gotFailed, manifest.FailedRuns)
}
}
// The control: with nothing blocked, a batch is what it always was. Every run
// is completed with a stage, two identical batches write byte-identical results
// and the same manifest header, and the new failure field stays out of it.
func TestAllSuccessfulBatchKeepsItsArtifacts(t *testing.T) {
tasks := indexTasks()
first, runs, manifest, out := runBlockedExperiment(t, tasks)
second, again, repeat, _ := runBlockedExperiment(t, tasks)
if manifest.CompletedRuns != len(runs) || manifest.FailedRuns != 0 {
t.Fatalf("completed=%d failed=%d rows=%d, want every row completed",
manifest.CompletedRuns, manifest.FailedRuns, len(runs))
}
if manifest.CompletedRuns != manifest.PlannedRuns {
t.Fatalf("completed=%d planned=%d, want all planned", manifest.CompletedRuns, manifest.PlannedRuns)
}
for i, m := range runs {
if m.Failed || m.Task == "" || m.Arm == "" || m.FailureStage == "" {
t.Fatalf("results.jsonl line %d is not a completed run: %+v", i+1, m)
}
}
if gotScored, gotFailed := reportCounts(t, out); gotScored != manifest.CompletedRuns || gotFailed != 0 {
t.Fatalf("report says scored=%d failed=%d, want %d and 0", gotScored, gotFailed, manifest.CompletedRuns)
}
if !reflect.DeepEqual(runs, again) {
t.Fatal("two identical successful batches scored differently")
}
// Byte-for-byte on the runs. Trajectories carry per-message wall-clock
// stamps, so they are compared field by field instead.
if got, want := readFile(t, second, "results.jsonl"), readFile(t, first, "results.jsonl"); got != want {
t.Fatalf("an unchanged batch wrote different results.jsonl:\n%s\n---\n%s", want, got)
}
gotTraj, wantTraj := withoutStamps(t, readFile(t, second, "trajectories.jsonl")),
withoutStamps(t, readFile(t, first, "trajectories.jsonl"))
if gotTraj != wantTraj {
t.Fatalf("an unchanged batch wrote different trajectories:\n%s\n---\n%s", wantTraj, gotTraj)
}
repeat.StartedAt, repeat.FinishedAt = manifest.StartedAt, manifest.FinishedAt
repeat.TrajectoriesSHA256 = manifest.TrajectoriesSHA256
if !reflect.DeepEqual(repeat, manifest) {
t.Fatalf("an unchanged batch wrote a different manifest:\n%+v\n---\n%+v", manifest, repeat)
}
if repeat.FailedRuns != 0 {
t.Fatalf("a batch with no failure reports failed_runs=%d", repeat.FailedRuns)
}
}
// withoutStamps drops the createdAt stamp every message carries, which is the
// one field a dry batch cannot reproduce between two runs.
func withoutStamps(t *testing.T, text string) string {
t.Helper()
var b strings.Builder
for line := range strings.SplitSeq(strings.TrimSpace(text), "\n") {
var row map[string]any
if err := json.Unmarshal([]byte(line), &row); err != nil {
t.Fatal(err)
}
stripStamp(row)
out, err := json.Marshal(row)
if err != nil {
t.Fatal(err)
}
b.Write(out)
b.WriteByte('\n')
}
return b.String()
}
func stripStamp(v any) {
switch typed := v.(type) {
case map[string]any:
delete(typed, "createdAt")
delete(typed, "workDurationMs")
for _, child := range typed {
stripStamp(child)
}
case []any:
for _, child := range typed {
stripStamp(child)
}
}
}
func readFile(t *testing.T, root, name string) string {
t.Helper()
data, err := os.ReadFile(filepath.Join(root, name))
if err != nil {
t.Fatal(err)
}
return string(data)
}
+1 -1
View File
@@ -36,7 +36,7 @@ func (p *digestProvider) Name() string { return "fixture-digest" }
func (p *digestProvider) Stream(_ context.Context, _ provider.Request) (<-chan provider.Chunk, error) {
p.calls++
ch := make(chan provider.Chunk, 2)
ch <- provider.Chunk{Type: provider.ChunkText, Text: "Earlier work continued across several files; nothing outstanding was recorded."}
ch <- provider.Chunk{Type: provider.ChunkText, Text: "## Earlier work\nEarlier work continued across several files; nothing outstanding was recorded."}
ch <- provider.Chunk{Type: provider.ChunkDone}
close(ch)
return ch, nil
+14 -3
View File
@@ -5,6 +5,7 @@ import (
"crypto/rand"
"fmt"
"hash/fnv"
"io"
mathrand "math/rand"
"strings"
)
@@ -90,15 +91,25 @@ func seededRand(parts ...string) *mathrand.Rand {
// liveRand is the generator a paid run uses: every run gets values that existed
// nowhere before it started.
func liveRand() *mathrand.Rand {
var seed [8]byte
if _, err := rand.Read(seed[:]); err != nil {
rng, err := liveRandFrom(rand.Reader)
if err != nil {
panic("contextbench: no entropy for a live run: " + err.Error())
}
return rng
}
// liveRandFrom seeds a generator from an entropy source, so what a run's values
// are unpredictable from is the source and nothing else.
func liveRandFrom(entropy io.Reader) (*mathrand.Rand, error) {
var seed [8]byte
if _, err := io.ReadFull(entropy, seed[:]); err != nil {
return nil, err
}
var n int64
for _, b := range seed {
n = n<<8 | int64(b)
}
return mathrand.New(mathrand.NewSource(n))
return mathrand.New(mathrand.NewSource(n)), nil
}
// instantiate substitutes {{name}} and refuses anything left over. The grammar
+10 -3
View File
@@ -54,15 +54,22 @@ func main() {
case "preflight":
os.Exit(runPreflight(tasks, root))
case "run-search":
os.Exit(runExperiment(experimentSearch, root, *dry, tasks))
os.Exit(runExperiment(*mode, experimentSearch, root, *dry, tasks))
case "run-boundary":
os.Exit(runBoundaries(root))
os.Exit(runBoundaries(*mode, root))
case "snippet":
os.Exit(runSnippetAudit(root, 100))
case "adversarial":
os.Exit(runAdversarial(root))
case "run-index":
os.Exit(runExperiment(experimentIndex, root, *dry, tasks))
batch := tasks
if *only == "" {
// A batch measures the efficiency substrate only. Naming one task
// with -task runs that task, role included, so the stopping corpus
// stays measurable on its own.
batch = efficiencyTasks()
}
os.Exit(runExperiment(*mode, experimentIndex, root, *dry, batch))
default:
fmt.Fprintf(os.Stderr, "unknown mode %q\n", *mode)
os.Exit(2)
+69 -17
View File
@@ -1,7 +1,10 @@
package main
import (
"bytes"
"fmt"
"maps"
mathrand "math/rand"
"os/exec"
"slices"
"strings"
@@ -19,16 +22,24 @@ func TestCorpusIsWellFormed(t *testing.T) {
if got := len(searchTasks()); got != 6 {
t.Errorf("search experiment has %d tasks, want 6", got)
}
if got := len(indexTasks()); got != 6 {
t.Errorf("index experiment has %d tasks, want 6", got)
if got := len(indexTasks()); got != 12 {
t.Errorf("index experiment has %d tasks, want 12", got)
}
stopping := map[string]bool{}
tiers := map[string]int{}
for _, task := range indexTasks() {
if task.Role == roleStopping {
stopping[task.ID] = true
continue
}
tiers[task.CueTier]++
}
// Two per tier is what makes the staircase readable: the same task can be
// compared across the boundary its cue sits on, instead of two different
// questions being compared across two arms.
if !stopping["i03-coalescing"] || !stopping["i04-recovery-fence"] {
t.Errorf("the stopping corpus is %v; it must keep i03-coalescing and i04-recovery-fence", stopping)
}
// Two per tier, over the efficiency substrate, is what makes the staircase
// readable: the same task can be compared across the boundary its cue sits
// on, instead of two different questions being compared across two arms.
for _, tier := range []string{tierQuarter, tierHalf, tierDefault} {
if tiers[tier] != 2 {
t.Errorf("tier %s has %d tasks, want 2", tier, tiers[tier])
@@ -36,6 +47,21 @@ func TestCorpusIsWellFormed(t *testing.T) {
}
}
// The role decides which batch a task enters; the literal lists are only where
// it is declared, so the two must not drift.
func TestIndexListsMatchTheirRoles(t *testing.T) {
for _, task := range append(append(indexQuarterTasks(), indexHalfTasks()...), indexDefaultTasks()...) {
if task.Role != roleEfficiency {
t.Errorf("%s sits in an efficiency list with role %q", task.ID, task.Role)
}
}
for _, task := range stoppingIndexTasks() {
if task.Role != roleStopping {
t.Errorf("%s sits in the stopping list with role %q", task.ID, task.Role)
}
}
}
// The invariant the whole instantiation layer exists for: no literal a scorer
// needs may be readable in this repository before a run starts.
func TestNoAnswerLiteralExistsInTheRepository(t *testing.T) {
@@ -59,20 +85,46 @@ func TestNoAnswerLiteralExistsInTheRepository(t *testing.T) {
}
}
// Two runs of the same task must not share an answer, or one leaked run leaks
// every future one.
func TestInstancesDifferBetweenRuns(t *testing.T) {
// A run's values are unpredictable from a previous run only if the live
// generator is seeded from entropy. Comparing answers across two instances
// cannot assert that: a task whose answer is a bounded number collides by
// chance (1/8601 to 1/286 per task), so the check is on the seed source.
func TestLiveRandDrawsFromEntropy(t *testing.T) {
a, b := liveRand(), liveRand()
for range 4 {
if a.Int63() != b.Int63() {
return
}
}
t.Fatal("two live generators produced the same stream: they are seeded alike")
}
// The source alone decides an instance: the same entropy gives the same
// answers, different entropy gives different values.
func TestLiveRandSourceDecidesTheInstance(t *testing.T) {
same := func() *mathrand.Rand {
rng, err := liveRandFrom(bytes.NewReader(bytes.Repeat([]byte{7}, 8)))
if err != nil {
t.Fatal(err)
}
return rng
}
other, err := liveRandFrom(bytes.NewReader(bytes.Repeat([]byte{9}, 8)))
if err != nil {
t.Fatal(err)
}
if _, err := liveRandFrom(bytes.NewReader(nil)); err == nil {
t.Fatal("an empty entropy source was accepted")
}
for _, task := range allTasks() {
a, err := instantiateTask(task, liveRand())
if err != nil {
t.Fatal(err)
a, _ := instantiateTask(task, same())
b, _ := instantiateTask(task, same())
if !slices.Equal(a.AnswerMarkers, b.AnswerMarkers) || !maps.Equal(a.Vars, b.Vars) {
t.Errorf("%s: the same entropy gave different instances", task.ID)
}
b, err := instantiateTask(task, liveRand())
if err != nil {
t.Fatal(err)
}
if slices.Equal(a.AnswerMarkers, b.AnswerMarkers) {
t.Errorf("%s produced the same answers twice: %v", task.ID, a.AnswerMarkers)
c, _ := instantiateTask(task, other)
if maps.Equal(a.Vars, c.Vars) {
t.Errorf("%s: different entropy gave the same values", task.ID)
}
}
}
@@ -130,7 +130,7 @@ type queryAudit struct {
func auditQueries(runs []contextMetrics) queryAudit {
a := queryAudit{Reformulations: map[string]int{}, MissedProbeTerms: map[string]int{}}
for _, r := range runs {
if r.Contaminated {
if r.Failed || r.Contaminated {
continue
}
a.Runs++
+63 -13
View File
@@ -14,7 +14,6 @@ import (
"reasonix/internal/assembly/boot"
"reasonix/internal/contract/ablation"
"reasonix/internal/contract/event"
"reasonix/internal/contract/provider"
"reasonix/internal/contract/tool"
_ "reasonix/internal/model/openai"
@@ -86,6 +85,7 @@ func runAdversarial(root string) int {
adversarialPrompts[t.Experiment])
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", id, err)
m = failedMetrics(t, "adversarial", err)
}
verdict := "isolated"
if m.EscapeFoundAnswer {
@@ -132,10 +132,11 @@ func runOne(p provider.Provider, t contextTask, arm ablation.Set, armName, root
}
boot.ApplyUnifiedProviderToolSurface(reg, false, arm)
var usage usageSink
a := agent.New(p, reg, sess, agent.Options{
ContextWindow: fixtureWindow, CompactRatio: 0.5, RecentKeep: 2,
SessionPath: f.Path, KeepPolicy: agent.KeepErrors, Ablation: arm,
}, event.Discard)
}, &usage)
a.LoadProjectionSidecar(f.Path)
// From here the transcript lives only in memory. On disk it is the answer.
sealFixture(f.Path)
@@ -161,6 +162,10 @@ func runOne(p provider.Provider, t contextTask, arm ablation.Set, armName, root
appended := sess.Snapshot()[before:]
m := scoreRun(appended, inst, f.Target, armName, cueVisible)
m.trajectory = appended
if u, requests := usage.snapshot(); requests > 0 {
m.UsageReportedRequests, m.PromptTokens, m.CompletionTokens = requests, u.PromptTokens, u.CompletionTokens
m.CacheHitTokens, m.CacheMissTokens, m.UsageEstimated = u.CacheHitTokens, u.CacheMissTokens, u.Estimated
}
m.scoreAnswer(finalAnswer(appended), inst)
if runErr != nil {
m.FailureStage = "RunError"
@@ -169,6 +174,16 @@ func runOne(p provider.Provider, t contextTask, arm ablation.Set, armName, root
return m, runErr
}
// failedMetrics is what a run that never reached a transcript leaves behind. It
// carries the run's identity and the stage that ended it, so the failure stays a
// countable fact; Failed keeps it out of every statistic.
func failedMetrics(t contextTask, armName string, err error) contextMetrics {
return contextMetrics{
Task: t.ID, Arm: armName, Failed: true,
FailureStage: stageNoEvidence, Reason: err.Error(),
}
}
// finalAnswer is the last assistant text the turn produced.
func finalAnswer(msgs []provider.Message) string {
for _, m := range slices.Backward(msgs) {
@@ -212,7 +227,10 @@ func isolateWorkspace() (func(), error) {
// The two arms get separate nonces: reusing one would leave the first arm's
// answer in a host-readable history for the second to find.
func runBoundaries(root string) int {
func runBoundaries(mode, root string) int {
started := time.Now()
planned := 0
repo, _ := os.Getwd()
root, err := filepath.Abs(root)
if err != nil {
fmt.Fprintln(os.Stderr, err)
@@ -232,16 +250,18 @@ func runBoundaries(root string) int {
var all []contextMetrics
var cueSide, noCueSide []contextMetrics
for _, t := range indexTasks() {
for _, t := range efficiencyTasks() {
cue, noCue := boundaryPair(t.CueTier)
for _, side := range []struct {
scale string
has bool
}{{cue, true}, {noCue, false}} {
name := "index-" + side.scale
planned++
m, err := runOne(p, t, armFor(side.scale, false), name, root, side.has, false)
if err != nil {
fmt.Fprintf(os.Stderr, "%s [%s]: %v\n", t.ID, name, err)
m = failedMetrics(t, name, err)
}
all = append(all, m)
if side.has {
@@ -249,14 +269,14 @@ func runBoundaries(root string) int {
} else {
noCueSide = append(noCueSide, m)
}
fmt.Printf("%-24s %-14s cue=%-3v %-14s search=%d read=%d direct=%-5v recall-tok=%-5d escape=%d\n",
fmt.Printf("%-24s %-14s cue=%-3v %-14s search=%d read=%d direct=%-5v recall-tok=%-5d%s escape=%d\n",
t.ID, name, side.has, m.FailureStage, m.SearchCalls, m.ReadCalls,
m.CueDirectRead, m.RecallReturnedTokens, m.EscapeCalls)
m.CueDirectRead, m.RecallReturnedTokens, promptTokenSuffix(m), m.EscapeCalls)
}
}
reportDeltas(cueSide, noCueSide)
fmt.Print(reportStopping(all))
return writeResults(root, all)
return writeResults(root, batchInfo{Mode: mode, Planned: planned, Started: started, Repo: repo}, all)
}
// reportDeltas is the paired reading: each task against itself, then pooled.
@@ -298,7 +318,10 @@ func deltaBool(a, b bool) string {
// runExperiment runs one experiment's tasks across its arms. A dry run drives
// the same pipeline with a scripted provider: it proves fixture, run, scoring
// and report hold together before any of it is paid for.
func runExperiment(experiment, root string, dry bool, tasks []contextTask) int {
func runExperiment(mode, experiment, root string, dry bool, tasks []contextTask) int {
started := time.Now()
planned := 0
repo, _ := os.Getwd()
root, err := filepath.Abs(root)
if err != nil {
fmt.Fprintln(os.Stderr, err)
@@ -334,15 +357,17 @@ func runExperiment(experiment, root string, dry bool, tasks []contextTask) int {
// test: a bad minute would land entirely on one arm.
for _, arm := range shuffledArms(t) {
cueVisible := t.Experiment == experimentIndex && cueExpectedVisible(t, arm.scale)
planned++
m, err := runOne(p, t, armFor(arm.scale, arm.searchOff), arm.name, root, cueVisible, dry)
if err != nil {
fmt.Fprintf(os.Stderr, "%s [%s]: %v\n", t.ID, arm.name, err)
m = failedMetrics(t, arm.name, err)
}
byArm[arm.name] = append(byArm[arm.name], m)
all = append(all, m)
fmt.Printf("%-24s %-14s %-18s search=%d hit=%d read=%d recall-tok=%d %v\n",
fmt.Printf("%-24s %-14s %-18s search=%d hit=%d read=%d recall-tok=%d%s %v\n",
t.ID, arm.name, m.FailureStage, m.SearchCalls, m.TargetSearchHits, m.ReadCalls,
m.RecallReturnedTokens, m.UnexpectedWorkTools)
m.RecallReturnedTokens, promptTokenSuffix(m), m.UnexpectedWorkTools)
}
}
reportFunnels(byArm)
@@ -355,7 +380,7 @@ func runExperiment(experiment, root string, dry bool, tasks []contextTask) int {
if experiment == experimentIndex {
reportBoundaries(all)
}
return writeResults(root, all)
return writeResults(root, batchInfo{Mode: mode, Planned: planned, Started: started, Dry: dry, Repo: repo}, all)
}
func cueExpectedVisible(t contextTask, scale string) bool {
@@ -410,7 +435,7 @@ func writeTrajectories(root string, all []contextMetrics) error {
return nil
}
func writeResults(root string, all []contextMetrics) int {
func writeResults(root string, info batchInfo, all []contextMetrics) int {
path := filepath.Join(root, "results.jsonl")
f, err := os.Create(path)
if err != nil {
@@ -418,17 +443,30 @@ func writeResults(root string, all []contextMetrics) int {
return 2
}
defer f.Close()
scored, failed := 0, 0
for _, m := range all {
if err := writeJSONLine(f, m); err != nil {
fmt.Fprintln(os.Stderr, err)
return 2
}
if m.Failed {
failed++
} else {
scored++
}
}
if err := writeTrajectories(root, all); err != nil {
fmt.Fprintln(os.Stderr, "write trajectories:", err)
return 2
}
fmt.Printf("\n%d runs written to %s (with trajectories.jsonl)\n", len(all), path)
if err := writeBatchManifest(root, info, all); err != nil {
fmt.Fprintln(os.Stderr, "write batch manifest:", err)
return 2
}
// Both counts are named: a reader who sees only the batch size cannot tell
// measurements from attempts.
fmt.Printf("\n%d runs written to %s: scored=%d failed=%d (with trajectories.jsonl and batch.json)\n",
len(all), path, scored, failed)
return 0
}
@@ -437,9 +475,21 @@ func writeResults(root string, all []contextMetrics) int {
// what a four-arm average cannot do.
func reportBoundaries(all []contextMetrics) {
byKey := map[string]contextMetrics{}
scored, failed := 0, 0
for _, m := range all {
if m.Failed {
// No transcript, no boundary to compare: count the failure instead of
// printing the zero it left behind as a measurement.
failed++
continue
}
scored++
byKey[m.Task+"|"+m.Arm] = m
}
if scored == 0 {
fmt.Printf("\n## Across each task's cue boundary\n no scored runs: scored=%d failed=%d (nothing to compare)\n", scored, failed)
return
}
var cueSide, noCueSide []contextMetrics
fmt.Println("\n## Across each task's cue boundary")
fmt.Printf(" %-24s %-16s %-4s %-10s %-9s %-12s %-11s %s\n",
+59
View File
@@ -0,0 +1,59 @@
package main
import (
"fmt"
"sync"
"reasonix/internal/contract/event"
"reasonix/internal/contract/provider"
)
// usageSink reads what a run cost from the events the agent already emits. Every
// billed completion reports event.Usage — the round path, compaction, command
// routing and failure diagnosis all do — so the bench reads the same record the
// product reads instead of wrapping a provider and counting a second time.
type usageSink struct {
mu sync.Mutex
requests int
usage provider.Usage
}
func (s *usageSink) Emit(e event.Event) {
if e.Kind != event.Usage || e.Usage == nil {
return
}
s.mu.Lock()
defer s.mu.Unlock()
// One event is one completion that reported usage; RequestCount covers a
// completion that merged attempts. A request that failed before reporting
// leaves no event, so this is not a count of attempts.
if e.Usage.RequestCount > 0 {
s.requests += e.Usage.RequestCount
} else {
s.requests++
}
u := *e.Usage
s.usage.PromptTokens += u.PromptTokens
s.usage.CompletionTokens += u.CompletionTokens
s.usage.TotalTokens += u.TotalTokens
s.usage.CacheHitTokens += u.CacheHitTokens
s.usage.CacheMissTokens += u.CacheMissTokens
s.usage.ReasoningTokens += u.ReasoningTokens
s.usage.Estimated = s.usage.Estimated || u.Estimated
}
func (s *usageSink) snapshot() (provider.Usage, int) {
s.mu.Lock()
defer s.mu.Unlock()
return s.usage, s.requests
}
// promptTokenSuffix renders what a run cost only when something reported it. A
// dry run reports nothing, and a trailing "prompt-tok=0" would read as a
// measured zero rather than as no measurement.
func promptTokenSuffix(m contextMetrics) string {
if m.UsageReportedRequests == 0 {
return ""
}
return fmt.Sprintf(" prompt-tok=%d", m.PromptTokens)
}
@@ -0,0 +1,54 @@
package main
import (
"testing"
"reasonix/internal/contract/event"
"reasonix/internal/contract/provider"
)
// The cost a run reports must be what the agent reported, summed across every
// completion it billed — including one that merged attempts and one that could
// only estimate. Any other event kind is not a cost.
func TestUsageSinkAccumulatesWhatTheAgentReports(t *testing.T) {
var sink usageSink
sink.Emit(event.Event{}) // a kind that is not Usage
sink.Emit(event.Event{Kind: event.Usage, Usage: &provider.Usage{
PromptTokens: 100, CompletionTokens: 10, TotalTokens: 110, CacheHitTokens: 60, CacheMissTokens: 40,
}})
sink.Emit(event.Event{Kind: event.Usage, Usage: &provider.Usage{
PromptTokens: 7, CompletionTokens: 3, CacheHitTokens: 2, CacheMissTokens: 5, RequestCount: 2, Estimated: true,
}})
sink.Emit(event.Event{Kind: event.Usage}) // a Usage event with no payload is not a cost
usage, requests := sink.snapshot()
if requests != 3 {
t.Fatalf("requests = %d, want 3 (1 + the merged attempt's 2; the empty event adds none)", requests)
}
if usage.PromptTokens != 107 || usage.CompletionTokens != 13 || usage.TotalTokens != 110 {
t.Fatalf("accumulated usage = %+v", usage)
}
if usage.CacheHitTokens != 62 || usage.CacheMissTokens != 45 {
t.Fatalf("cache split = %+v", usage)
}
if !usage.Estimated {
t.Fatal("Estimated was dropped when one completion reported an estimate")
}
}
// A dry run must not claim a cost it never paid, in the JSON or in the line a
// person reads: an empty suffix, not "prompt-tok=0".
func TestUsageSinkStaysEmptyWithoutUsage(t *testing.T) {
var sink usageSink
sink.Emit(event.Event{})
usage, requests := sink.snapshot()
if requests != 0 || usage.PromptTokens != 0 || usage.CompletionTokens != 0 {
t.Fatalf("nothing reported usage, got %+v requests=%d", usage, requests)
}
if suffix := promptTokenSuffix(contextMetrics{}); suffix != "" {
t.Fatalf("a run with no reported usage still printed %q", suffix)
}
if suffix := promptTokenSuffix(contextMetrics{UsageReportedRequests: 1, PromptTokens: 12}); suffix != " prompt-tok=12" {
t.Fatalf("a reported cost printed %q", suffix)
}
}
@@ -0,0 +1,3 @@
alpha=QORVEX-7741
beta=MIRALD-3208
gamma=TESSIK-9155
+87
View File
@@ -0,0 +1,87 @@
# Desktop surface evaluation
This protocol supplies the fixed-task trajectory comparison required by
[RFC #12584](https://github.com/esengine/DeepSeek-Reasonix/issues/12584).
There are no measured results yet. Component tests and scripted providers prove
delivery, validation and replay; they do not measure a model's choice of interface
or a person's effort.
## Paired runs
Run each task twice in fresh desktop sessions: ordinary text/ask interaction and
native surfaces. Use the same model, sampling settings, initial workspace,
permission mode and task prompt. Record commit, model, configuration and the
native-surface capability setting. Alternate condition order across repetitions.
Keep the hidden tool out of the provider prefix in both conditions; only its
availability through capability discovery differs.
The baseline may batch questions or accept a single text reply. Never force it to
ask one question per field. Do not tell either model how many turns to use or
which component to render. If the surface-enabled model chooses text, retain
that run in the surface condition. Interface choice is part of the experiment.
The operator knows the private values below but does not volunteer them before
the model requests the corresponding information. Supply all requested values
together, regardless of presentation. Allow inspection and local editing before
submission. Save the trajectory and an observer ledger locally; raw transcripts
and draft content must not enter the aggregate report.
## Fixed tasks
| Task | Initial prompt, identical in both conditions | Private operator input | Completion criterion |
| --- | --- | --- | --- |
| `release-brief` | Prepare a release brief. Ask me for the audience, release date and whether this is a staged rollout before writing it. | audience: existing desktop users; date: 2026-11-03; staged rollout: yes | Final brief contains all three values and adds no release commitment. |
| `storage-choice` | Help me choose storage for a small desktop app. Present reasonable choices, ask me to choose and ask whether offline operation is required. Then summarize the decision. | storage: SQLite; offline: yes | Final decision states SQLite and offline operation, with no installation or file changes. |
| `draft-revision` | Draft a two-line release announcement and let me revise it before returning the final version verbatim. | Replace with the exact string ` Desktop release ready.\nKeep existing settings.\n` (decode the escapes). | Final returned string has exactly the specified leading spaces and newlines. Formatting wrappers are excluded from the comparison. |
| `preview-decision` | Show a plain-text before/after preview changing retry count from 2 to 3. Ask whether to keep that change and whether to include a release note. Return a decision summary; do not change files. | keep change: yes; release note: no | Preview shows 2 to 3; final summary preserves both decisions; workspace is unchanged. |
Freeze this task set before collecting results. Record a new protocol version if
prompts, private inputs, completion criteria or metric definitions change.
Neither the model's final claim nor a successful input acknowledgment establishes
completion. The observer checks the resulting output against the criterion.
## Metric definitions (version 1)
The anchor is acknowledgment of the initial task prompt. The observation ends
when the criterion is verified, the operator abandons the task, or the run fails.
The initial prompt is excluded from subsequent-input counts because both
conditions start from the same request.
| Metric | Count and population | Exclusions |
| --- | --- | --- |
| `acknowledged_inputs` | Distinct operator submissions acknowledged after the anchor: chat replies, ask answers, surface answers and surface actions. Includes acknowledged input later withdrawn or superseded. | Initial task prompt, local draft edits, retries with the same acknowledged request identity, automatic dispatch, replay and observer grading. This counts input exchanges, not provider rounds. |
| `submission_attempts` | Explicit operator send attempts, including rejected submissions and manual retries. | Automatic transport retries, replay, local edits and model tool calls. |
| `withdrawals` | Successful operator withdrawals of admitted input. | Failed withdrawals, automatic cancellation and replay. |
| `completed` | Runs whose output meets the fixed criterion. Denominator: every started run in each condition. | None; failures and abandoned runs remain in the denominator. |
Record these in a content-free ledger: task id, repetition, condition, observer
event ordinal, event type, opaque request identity, acknowledgment status and
completion verdict. Record the source trajectory reference and observed start
and end anchors alongside it. Distinguish manual observation from host-recorded
events. Do not copy labels, prompts, private values, drafts or answer strings into
the ledger. Identities are local correlation handles, not account identifiers.
The current pane counters and rendered trajectory export cannot reconstruct all
acknowledgments, refusals and withdrawals. Do not infer them from `ask` calls,
tool-result counts, final prose, message wording or card mounts. Until structural
acknowledgment records cover every channel, an observer ledger is required.
## Report and decision
Report the number started, completed, failed and abandoned for each condition,
then paired input-count differences for repetitions where **both** conditions
meet the same criterion. List excluded pairs and their reason beside that
comparison. Also report submission attempts and withdrawals; fewer admitted
inputs with more failures is not automatically less effort.
Keep per-task results visible. Do not combine scripted fixtures with observed
model/operator runs or describe this small task set as an account-wide result.
Report model/configuration differences and missing evidence as limitations.
The RFC's benefit remains unproven until observed trajectories show fewer input
exchanges to the same verified outcome. These results alone do not justify P3;
adaptive layout or MCP Apps needs a measured problem in addition to this result.
The owner-requested map, comparison and dashboard workspaces extend the delivery
scope independently of this study. Their scripted acceptance fixtures verify
rendering and interaction only; they do not count as observed benefit evidence.
@@ -1,5 +1,4 @@
prompt = "为本项目新建一个数据库迁移脚本文件(空文件即可),文件名必须符合项目的迁移命名规范。"
class = "cjk"
max_steps = 8
timeout_sec = 180
memory_markers = ["MEMKEY-CJK5T"]
@@ -1,5 +1,4 @@
prompt = "Write the deploy region for this project (just the region string) into answer.txt."
class = "conflict"
max_steps = 8
timeout_sec = 180
memory_markers = ["MEMKEY-CONF2P"]
@@ -1,5 +1,4 @@
prompt = "Write the command to install this project's dependencies into answer.txt."
class = "contradiction"
max_steps = 8
timeout_sec = 180
memory_markers = ["MEMKEY-CONTRA"]
@@ -1,3 +1,3 @@
#!/usr/bin/env bash
set -e
grep -q "pnpm install" answer.txt && ! grep -q "npm install" answer.txt
grep -q "pnpm install" answer.txt && ! grep -qE '(^|[^p])npm install' answer.txt
@@ -1,5 +1,4 @@
prompt = "Write this project's canonical fast-check command (the exact command line, nothing else) into answer.txt."
class = "distractor"
max_steps = 8
timeout_sec = 180
memory_markers = ["MEMKEY-DIST5Q"]
@@ -1,5 +1,4 @@
prompt = "Write this project's canonical fast-check command (the exact command line, nothing else) into answer.txt."
class = "exact"
max_steps = 8
timeout_sec = 180
memory_markers = ["MEMKEY-EXACT7Q"]
@@ -1,4 +1,3 @@
prompt = "Add a function add(a, b) returning a+b to util.py."
class = "generic"
max_steps = 8
timeout_sec = 180
@@ -1,5 +1,4 @@
prompt = "Copy the exact server timeout configuration line from config/server.conf into answer.txt, unchanged."
class = "history"
max_steps = 8
timeout_sec = 180
memory_markers = ["MEMKEY-HIST6M"]
@@ -1,5 +1,4 @@
prompt = "Which tool do we install dependencies with here? Write the exact install command into answer.txt."
class = "paraphrase"
max_steps = 8
timeout_sec = 180
memory_markers = ["MEMKEY-PARA3X"]
@@ -1,6 +1,5 @@
prompt = "Create a file named notes.txt containing one line: hello."
class = "pinned"
max_steps = 8
timeout_sec = 180
memory_markers = ["MEMKEY-PIN4D"]
memory_markers_prefix = true
@@ -1,5 +1,4 @@
prompt = "Confirm the current release branch of this repo and write it into answer.txt."
class = "stale"
max_steps = 8
timeout_sec = 180
memory_markers = ["MEMKEY-STALE1"]
@@ -1,5 +1,4 @@
prompt = "Set the retry max backoff to 250 in this project's configuration."
class = "symbol"
max_steps = 8
timeout_sec = 180
memory_markers = ["MEMKEY-SYM9K"]
@@ -1,5 +1,4 @@
prompt = "Write the current API base URL for this project into answer.txt."
class = "update"
max_steps = 8
timeout_sec = 180
memory_markers = ["MEMKEY-UPD8R"]
@@ -1,5 +1,4 @@
prompt = "查一下灰度的放量比例,写进 answer.txt。"
class = "v1miss"
max_steps = 8
timeout_sec = 180
memory_markers = ["MEMKEY-VM1GR"]
@@ -1,5 +1,4 @@
prompt = "这个项目的部署审批人是谁?把审批人的用户名写进 answer.txt。"
class = "v1miss"
max_steps = 8
timeout_sec = 180
memory_markers = ["MEMKEY-VM1XL"]
@@ -1,5 +1,4 @@
prompt = "Set the max idle connection seconds to 30 in this project's configuration."
class = "v1miss"
max_steps = 8
timeout_sec = 180
memory_markers = ["MEMKEY-VM1SYM"]
+12 -9
View File
@@ -87,14 +87,17 @@ controller-level test rather than a corpus.
Until it is run, the bypass is a conditional mechanism with no demonstrated
production path, and the cutover below has no defect to justify it.
## The cutover this is evidence for
## What the gate enforces now
The gate keeps enforcement until this corpus shows, across runs:
The Goal resume path was shown reachable by
`internal/session/control/goal_resume_project_check_test.go`, and the trust-domain
rule in `REASONIX.md` settles its authority question: the workspace cannot
supersede a captured criterion. The gate therefore owes every baseline criterion
the current declaration no longer names, inside the delivery scope that
captured it, anchored where the gate already anchors. The rest of the obligation
derivation is not switched in: a resumed process starts from an empty ledger, and
the obligations would owe nothing there.
- the known bypass reproduces under the legacy path while the obligation path
preserves the baseline;
- no unexplained `candidate_only` and no unexplained `legacy_only`;
- the only divergences are `baseline_preservation`, `identity_normalization`,
and a `mutation_index` difference the two after-indices explain;
- and, once every baseline and current obligation is genuinely satisfied, the
candidate permits finalization — a bypass fixed into a deadlock is not fixed.
The probe still runs. The full cutover it is evidence for needs, across runs,
no unexplained `candidate_only` or `legacy_only`, and a candidate that permits
finalization once every baseline and current obligation is satisfied.
@@ -0,0 +1,3 @@
def total(items):
"""Sum the items."""
return sum(x for x in items if x > 0)
@@ -0,0 +1,3 @@
def total(items):
"""Sum the items."""
return sum(x for x in items if x > 0)
@@ -0,0 +1,3 @@
def total(items):
"""Sum the items."""
return sum(x for x in items if x > 0)
@@ -0,0 +1,3 @@
def total(items):
"""Sum the items."""
return sum(x for x in items if x > 0)
@@ -0,0 +1,3 @@
def total(items):
"""Sum the items."""
return sum(x for x in items if x > 0)
@@ -0,0 +1,3 @@
def total(items):
"""Sum the items."""
return sum(x for x in items if x > 0)
@@ -0,0 +1,3 @@
def total(items):
"""Sum the items."""
return sum(x for x in items if x > 0)
@@ -0,0 +1,3 @@
def total(items):
"""Sum the items."""
return sum(x for x in items if x > 0)
@@ -0,0 +1,2 @@
value=7
file=limits/budget.py
@@ -0,0 +1,2 @@
handler=h07.py
missing=validate_payload
@@ -0,0 +1,8 @@
def wire_fee(amount):
"""Return the wire fee for `amount`: the greater of 15.0 and 0.1% of it.
A zero or negative amount has no wire fee, so it returns 0.0.
"""
if amount <= 0:
return 0.0
return max(15.0, amount * 0.001)
@@ -0,0 +1,13 @@
def convert(amount, rate):
"""Convert `amount` at `rate` and return the product.
Both units belong to the caller. A negative amount converts unchanged.
"""
return amount * rate
def spread(bid, ask):
"""Return `ask - bid`, the spread between the two quoted prices.
A negative result means the bid is above the ask.
"""
return ask - bid
Loaded 100 of 5508 files, more files were not shown because too many files have changed in this diff. Show more