Compare commits

...
Author SHA1 Message Date
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 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 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 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
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 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 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 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
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
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
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
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
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
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
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
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
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
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 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
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
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 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
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 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
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 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
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 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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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 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 b441b0da8b fix(state): bound the workspace snapshot walk so goroutines and threads stay limited 2026-10-03 00:33:21 -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 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 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
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
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
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
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
3024 changed files with 229446 additions and 15278 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
+13 -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,6 +20,12 @@
/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
@@ -27,6 +34,11 @@
# 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
+6
View File
@@ -6,3 +6,9 @@ contact_links:
- 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.
+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'
+32
View File
@@ -8,6 +8,34 @@ only, on `main-v2`.
-
## 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
<!--
@@ -26,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

+55 -19
View File
@@ -33,13 +33,14 @@ jobs:
code: ${{ steps.filter.outputs.code }}
desktop: ${{ steps.filter.outputs.desktop }}
sdk: ${{ steps.filter.outputs.sdk }}
scripts: ${{ steps.filter.outputs.scripts }}
steps:
- 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
base=""
if [ "${{ github.event_name }}" = "pull_request" ]; then
base="${{ github.event.pull_request.base.sha }}"
@@ -49,7 +50,7 @@ jobs:
if [ -n "$base" ] && git cat-file -e "$base^{commit}" 2>/dev/null; then
files=$(git diff --name-only "$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 +61,30 @@ 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
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"; } >> "$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@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: "22"
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with:
go-version-file: go.mod
cache: true
- run: node --test scripts/cli-archive-build.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/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
@@ -157,12 +179,9 @@ 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
@@ -173,11 +192,11 @@ jobs:
# 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 -count=1 -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/...
# The general Windows PR smoke list intentionally omits internal/contract/tool.
# Keep the session-temp portability contract covered without widening the
@@ -196,14 +215,12 @@ jobs:
timeout-minutes: 5
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. It runs with
# -count=1 for the reason given at the smoke step. 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
@@ -212,7 +229,25 @@ 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 -count=1 -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
@@ -278,7 +313,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- if: env.RUN_STEPS == 'true'
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: sdk/go/go.mod
@@ -474,6 +509,7 @@ jobs:
go run github.com/rhysd/actionlint/cmd/[email protected] \
-ignore 'label "windows-11-arm" is unknown' \
.github/workflows/release-studio.yml \
.github/workflows/studio-certum-signing-smoke.yml \
.github/workflows/ci.yml
govulncheck:
+3 -3
View File
@@ -43,20 +43,20 @@ jobs:
if: matrix.branch != ''
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- name: Initialize CodeQL
uses: github/codeql-action/init@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.language == 'go' && 'autobuild' || 'none' }}
- name: Perform CodeQL Analysis
if: matrix.branch == ''
uses: github/codeql-action/analyze@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
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@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
with:
category: "/language:${{ matrix.language }}"
ref: refs/heads/${{ matrix.branch }}
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
with:
version: 10
run_install: false
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
with:
version: 10
run_install: false
+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@820762786026740c76f36085b0efc47a31fe5020 # 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@820762786026740c76f36085b0efc47a31fe5020 # 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
+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@820762786026740c76f36085b0efc47a31fe5020 # 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[@]}"
+38 -2
View File
@@ -6,7 +6,7 @@ on:
workflow_dispatch:
inputs:
replies:
description: 'JSON array of {"issue": 123, "body": "...", "close": true | "not_planned"}'
description: 'JSON array of {"issue": 123, "body": "...", "close": true | "not_planned"} or {"discussion": 123, "body": "...", "close": "resolved" | "outdated" | "duplicate"}'
required: true
type: string
interval:
@@ -18,6 +18,7 @@ on:
permissions:
issues: write
pull-requests: write
discussions: write
concurrency:
group: issue-replies
@@ -45,7 +46,42 @@ jobs:
return error.status === 429 ||
(error.status === 403 && (headers['retry-after'] !== undefined || headers['x-ratelimit-remaining'] === '0'));
};
for (const { issue, body, close } of replies) {
// 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;
File diff suppressed because it is too large. Load diff
@@ -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
@@ -41,7 +41,7 @@ jobs:
cache: true
cache-dependency-path: go.sum
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
with:
version: 10
run_install: false
@@ -99,7 +99,7 @@ jobs:
-c.extraMetadata.version="${{ steps.version.outputs.version }}"
sha256sum ../../dist/ReasonixStudio-windows-amd64-installer.exe | tee -a "$GITHUB_STEP_SUMMARY"
- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: studio-selftest-windows-${{ steps.version.outputs.version }}
path: dist/ReasonixStudio-windows-amd64-installer.exe
+207 -14
View File
@@ -28,7 +28,7 @@ jobs:
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
with:
version: 10
run_install: false
@@ -81,6 +81,66 @@ 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@820762786026740c76f36085b0efc47a31fe5020 # 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/contrast.mjs
node perf/wheel.mjs
node perf/branch-tooltip.mjs
node perf/selfcheck.mjs branch-tooltip
desktop:
name: desktop (${{ matrix.os }})
runs-on: ${{ matrix.os }}
@@ -121,6 +181,45 @@ jobs:
CGO_ENABLED=0 GOOS=$os GOARCH=amd64 go build -o /dev/null ./cmd/reasonix-studio-host
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.
scope:
name: package matrix
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.pick.outputs.matrix }}
arm64: ${{ steps.pick.outputs.arm64 }}
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
if [ "$EVENT" = pull_request ]; then
arm64=false
if git diff --name-only "$BASE_SHA" HEAD |
grep -qE '^(desktop/electron/|desktop/cmd/studio-manifest/|desktop/go\.(mod|sum)$|cmd/reasonix-studio-host/|internal/platform/(update|appupdate)/|go\.(mod|sum)$|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 [ "$arm64" = true ]; then
legs="$(jq -c '. + [{"os":"windows-11-arm","arch":"arm64","platform":"windows","ebarch":"arm64"}]' <<<"$legs")"
else
legs="$(jq -c . <<<"$legs")"
fi
echo "arm64=$arm64" >> "$GITHUB_OUTPUT"
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,20 +227,12 @@ 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
@@ -154,7 +245,7 @@ jobs:
cache: true
cache-dependency-path: desktop/go.sum
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
with:
version: 10
run_install: false
@@ -174,8 +265,13 @@ jobs:
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
env:
REASONIX_GOARCH: ${{ matrix.platform == 'windows' && matrix.arch || '' }}
run: pnpm install --frozen-lockfile && pnpm run build:host
# The .deb's absolute-path payload. A no-op off Linux, so it is not
@@ -191,7 +287,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) || '' }}
# 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 +304,78 @@ 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 }}"
# 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@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
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
@@ -301,3 +470,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@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
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' }
+2 -2
View File
@@ -33,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
+1
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
@@ -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
+29
View File
@@ -181,6 +181,35 @@ porting to `studio`; do not open the same PR against both.
5. Ensure `gofmt -l .` shows no changes
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:
+61 -15
View File
@@ -18,6 +18,8 @@
<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">
@@ -31,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">
@@ -40,22 +43,63 @@
<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>
<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"><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="150"></picture></a></td><td>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.</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, pre-release | [Studio releases](https://github.com/esengine/DeepSeek-Reasonix/releases?q=studio-v&expanded=true) |
| **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,
@@ -74,6 +118,8 @@ or reimplemented on 2.x where they fit the new architecture.
## 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
@@ -98,8 +144,8 @@ Studio, and editor integrations all run the same local Reasonix engine.
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.*`, published as pre-releases while 2.x is in active
development):
(tagged `studio-v2.*`; candidates such as `-rc.N` are marked as
pre-releases):
| Platform | Package | Architecture |
| --- | --- | --- |
@@ -125,8 +171,7 @@ brew install esengine/reasonix/reasonix # macOS
The 1.x desktop app is on the
[official download page](https://reasonix.io/?download=desktop#start). Windows
installers are code-signed through [SignPath.io](https://signpath.io/) with a
free certificate provided by the [SignPath Foundation](https://signpath.org/).
installers are code-signed with a Certum code-signing certificate.
### VS Code extension
@@ -205,7 +250,7 @@ For advanced CLI usage and configuration, see the **[CLI reference](./docs/CLI.m
[Task contracts & pause policy](./docs/TASK_CONTRACT.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:** [Extensions](./docs/EXTENSIONS.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)
@@ -231,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) | [**eghrhegpe**](https://github.com/eghrhegpe) | [**Bernardxu123**](https://github.com/Bernardxu123) |
| [**HaoyueQin**](https://github.com/HaoyueQin) | [**chuiwenwei**](https://github.com/chuiwenwei) | [**BuGlessRB**](https://github.com/BuGlessRB) | [**CVEngineer66**](https://github.com/CVEngineer66) |
| [**JesonChou**](https://github.com/JesonChou) | [**lanshi17**](https://github.com/lanshi17) | [**par73e**](https://github.com/par73e) | [**SuMuxi66**](https://github.com/SuMuxi66) |
<!-- reasonix-top-contributors:end -->
Special thanks to [**Bernardxu123**](https://github.com/Bernardxu123) for designing the project logo and intro video.
@@ -265,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>
+59 -13
View File
@@ -18,6 +18,8 @@
<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">
@@ -31,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">
@@ -40,12 +43,53 @@
<br/>
<p align="center"><strong>开源 · MIT · 单个 Go 二进制</strong></p>
<h3 align="center">可以一直开着跑的编码 Agent。</h3>
<p align="center">一套本地引擎,四个入口——终端、桌面端、浏览器,或通过 ACP 接入你的编辑器。计划模式、权限、工作区沙箱与逐轮 checkpoint,让长时间自治运行始终可读、可撤销。</p>
<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"><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="150"></picture></a></td><td>感谢 PackyCode 赞助了本项目!PackyCode 是一家稳定、高效的 API 中转服务商,统一域名、统一密钥、智能容灾切换。</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`。
四种用法背后是同一套程序。下面的内容写给进阶用户和贡献者。
## 版本
@@ -53,7 +97,7 @@ Reasonix 分为两条版本线,调整的原因见[版本路线公告](https://
| 版本线 | 分支 | 状态 | 获取方式 |
| --- | --- | --- | --- |
| **Reasonix 2.x** | `studio`(当前分支) | 活跃开发,预发布 | [Studio 发布页](https://github.com/esengine/DeepSeek-Reasonix/releases?q=studio-v&expanded=true) |
| **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 兼容、
@@ -68,6 +112,8 @@ Reasonix 分为两条版本线,调整的原因见[版本路线公告](https://
## 特性
以下是写给进阶用户的技术要点。
- **配置驱动**:provider、agent、启用的工具、插件全部在 `reasonix.toml` 中声明,
内核无硬编码模型。
- **多模型 · 可组合**:DeepSeek 作为预设内置;任何 OpenAI 兼容
@@ -87,7 +133,7 @@ Reasonix 分为两条版本线,调整的原因见[版本路线公告](https://
### Reasonix Studio(2.x)
从最新的 [Studio 发布](https://github.com/esengine/DeepSeek-Reasonix/releases?q=studio-v&expanded=true)
下载对应平台的安装包(标签为 `studio-v2.*`,2.x 活跃开发期间以预发布形式发布):
下载对应平台的安装包(标签为 `studio-v2.*`;`-rc.N` 等候选版本标为预发布):
| 平台 | 安装包 | 架构 |
| --- | --- | --- |
@@ -110,8 +156,7 @@ brew install esengine/reasonix/reasonix # macOS
```
1.x 桌面端请前往[官方下载页](https://reasonix.io/?download=desktop#start)。
Windows 安装器通过 [SignPath.io](https://signpath.io/) 完成代码签名,证书由
[SignPath 基金会](https://signpath.org/) 免费提供。
Windows 安装器使用 Certum 代码签名证书签名。
### VS Code 扩展
@@ -213,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) | [**eghrhegpe**](https://github.com/eghrhegpe) | [**Bernardxu123**](https://github.com/Bernardxu123) |
| [**HaoyueQin**](https://github.com/HaoyueQin) | [**chuiwenwei**](https://github.com/chuiwenwei) | [**BuGlessRB**](https://github.com/BuGlessRB) | [**CVEngineer66**](https://github.com/CVEngineer66) |
| [**JesonChou**](https://github.com/JesonChou) | [**lanshi17**](https://github.com/lanshi17) | [**par73e**](https://github.com/par73e) | [**SuMuxi66**](https://github.com/SuMuxi66) |
<!-- reasonix-top-contributors:end -->
特别感谢 [**Bernardxu123**](https://github.com/Bernardxu123) 设计的项目 logo和开场视频。
@@ -245,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>
+30 -3
View File
@@ -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
+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
+41 -36
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.
@@ -426,13 +426,14 @@ own outcome).
| `-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
```
+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:
+93 -12
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
@@ -31,8 +31,8 @@ The most likely reason is that two of the six index tasks carry heavy-tail
stopping behaviour that swamps the effect being measured.
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.
direction. Reopening this wants a new corpus, not more samples: that substrate
now exists, two qualifying tasks per cue tier — see "Index corpus v2" below.
**Things that turned out not to be problems**, each after being measured rather
than argued about:
@@ -74,14 +74,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
+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
@@ -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
+8 -1
View File
@@ -62,7 +62,14 @@ func main() {
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(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)
}
}
}
+1 -1
View File
@@ -232,7 +232,7 @@ 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
@@ -0,0 +1,3 @@
alpha=QORVEX-7741
beta=MIRALD-3208
gamma=TESSIK-9155
@@ -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"]
@@ -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
@@ -0,0 +1,4 @@
EARTH_RADIUS_KM = 6371.0
def dist(left, right):
return abs(left - right) * EARTH_RADIUS_KM
@@ -0,0 +1,4 @@
import geo
def near_by(point, other):
return geo.dist(point, other) < 10
@@ -0,0 +1,16 @@
import json
import sys
def upgrade(row):
parts = row["fullname"].rsplit(" ", 1)
out = {k: v for k, v in row.items() if k != "fullname"}
out["first"] = parts[0]
out["last"] = parts[1] if len(parts) > 1 else ""
return out
def main():
for line in sys.stdin:
line = line.strip()
if not line:
continue
print(json.dumps(upgrade(json.loads(line))))
@@ -0,0 +1,7 @@
import logging
log = logging.getLogger("orders")
def place(order, request_id):
log.info("placing order %s", order["id"], extra={"request_id": request_id})
return {"ok": True}
@@ -0,0 +1,7 @@
import logging
log = logging.getLogger("shipping")
def dispatch(order, request_id):
log.info("dispatching %s", order["id"], extra={"request_id": request_id})
return {"ok": True}
@@ -0,0 +1,11 @@
import math
def p95(sorted_values):
if not sorted_values:
return 0
return sorted_values[math.ceil(len(sorted_values) * 0.95) - 1]
def rate(events, seconds):
if seconds <= 0:
return 0.0
return len(events) / seconds
@@ -0,0 +1,9 @@
import core
_cache = None
def list_records_cached():
global _cache
if _cache is None:
_cache = core.list_records()
return _cache
@@ -0,0 +1,5 @@
import cache
def main():
for k, v in sorted(cache.list_records_cached().items()):
print(f"{k}={v}")
@@ -0,0 +1,4 @@
DATA = {"a": 1, "b": 2}
def list_records():
return dict(DATA)
@@ -0,0 +1 @@
DEFAULTS = {"host": "localhost", "port": 8080}
@@ -0,0 +1,6 @@
import json
import defaults
def load(text):
data = json.loads(text) if text.strip() else {}
return dict(defaults.DEFAULTS, **data)
@@ -0,0 +1,4 @@
import json
def dump(cfg):
return json.dumps(cfg, sort_keys=True)
@@ -0,0 +1,2 @@
STATES = ("new", "paid", "delivered", "cancelled")
TERMINAL = ("delivered", "cancelled")
@@ -0,0 +1,11 @@
import states
LEGAL = {
"new": ("paid", "cancelled"),
"paid": ("delivered", "cancelled"),
"delivered": (),
"cancelled": (),
}
def can_move(a, b):
return b in LEGAL[a]
@@ -0,0 +1,5 @@
import schema, serializer
def display_name(line):
rec = serializer.load(line)
return rec["handle"] or rec["email"]
@@ -0,0 +1,4 @@
FIELDS = ("handle", "email")
def blank():
return {f: "" for f in FIELDS}
@@ -0,0 +1,4 @@
import timings
def is_slow(entry):
return entry["elapsed_s"] > timings.SLOW_SECONDS
@@ -0,0 +1,4 @@
import timings
def within_budget(entries):
return sum(e["elapsed_s"] for e in entries) <= timings.BUDGET_SECONDS
@@ -0,0 +1,5 @@
SLOW_SECONDS = 2.0
BUDGET_SECONDS = 60.0
def record(seconds):
return {"elapsed_s": seconds}
@@ -0,0 +1,13 @@
import normalize
def merge_files(paths):
rows = []
header = None
for p in paths:
lines = [l.rstrip("\n") for l in open(p)]
if not lines:
continue
if header is None:
header = normalize.header(lines[0])
rows.extend(normalize.row(l) for l in lines[1:])
return [header] + rows
@@ -0,0 +1,5 @@
def header(line):
return ",".join(part.strip().lower() for part in line.split(","))
def row(line):
return ",".join(part.strip() for part in line.split(","))
@@ -0,0 +1,5 @@
import datetime
def parse(s):
y, m, d = s.split("-")
return datetime.date(int(y), int(m), int(d))
@@ -0,0 +1,5 @@
import parse_date
def in_window(day, start, end):
d = parse_date.parse(day)
return parse_date.parse(start) <= d <= parse_date.parse(end)
@@ -0,0 +1,11 @@
import tokenizer
def build(docs):
idx = {}
for doc_id, text in docs.items():
for tok in tokenizer.tokens(text):
idx.setdefault(tok, []).append(doc_id)
return idx
def lookup(idx, word):
return sorted(idx.get(word.lower(), []))
@@ -0,0 +1,5 @@
import re
def tokens(text):
return re.findall(r"[a-z0-9]+", text.lower())
@@ -0,0 +1,11 @@
import parse
def balance(lines):
total = 0.0
for line in lines:
kind, amount = parse.parse_line(line)
if kind == "deposit":
total += amount
elif kind == "withdraw":
total -= amount
return total
@@ -0,0 +1,3 @@
def parse_line(line):
kind, amount = line.strip().split()
return kind, float(amount.replace(",", ""))
@@ -0,0 +1,4 @@
import paginate
def next_link(items, size, page):
return f"?page={page + 1}" if page < len(paginate.pages(items, size)) else None
@@ -0,0 +1,2 @@
def pages(items, size):
return [items[i:i + size] for i in range(0, len(items), size)]
@@ -0,0 +1,5 @@
def apply_pct(price, pct):
return price * (1 - pct / 100.0)
def apply_coupon(price, coupon):
return price - coupon
Loaded 100 of 3024 files, more files were not shown because too many files have changed in this diff. Show more