mirror of
https://github.com/esengine/DeepSeek-Reasonix.git
synced 2026-11-07 17:10:43 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b307987c0b | ||
|
|
b21a6a0493 | ||
|
|
fe1bbb9aa9 |
No files matched your search
+100
@@ -3,6 +3,106 @@
|
||||
All notable changes to Reasonix. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/);
|
||||
this project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.53.2] — 2026-05-27
|
||||
|
||||
**TUI crash fix — `useBoxMetrics` no longer trips React's max-update-depth.**
|
||||
The hook ran `useEffect` with no dep array and called `setSize` inside,
|
||||
so any Box whose content shape depended on its own measurement could
|
||||
oscillate within a single React commit batch and crash with "Maximum
|
||||
update depth exceeded" on chat startup (reported in #1986
|
||||
follow-up). Measurement is now deferred off the commit batch via
|
||||
`setTimeout(0)` — each measure starts a fresh root update instead of
|
||||
chaining inside the depth counter. Oscillating call sites still don't
|
||||
converge to a single value (the underlying anti-pattern in the caller
|
||||
remains), but the user no longer sees a crash.
|
||||
|
||||
## [0.53.1] — 2026-05-27
|
||||
|
||||
**Install fix — `npx reasonix` no longer crashes on `EUNSUPPORTEDPROTOCOL`.**
|
||||
The 0.52.0 refactor that inlined Ink as a workspace package (#1947)
|
||||
left `"ink": "workspace:*"` in runtime `dependencies`. tsup already
|
||||
bundles ink into `dist/`, so the runtime never resolves it — but npm
|
||||
on consumer machines parses the declaration and rejects the
|
||||
unresolvable `workspace:` protocol. Removed from runtime deps; the
|
||||
bundled ink in `dist/` is the implementation. 0.53.0 is deprecated
|
||||
on npm.
|
||||
|
||||
## [0.53.0] — 2026-05-27
|
||||
|
||||
**Force-summary respects the active model.** Context-guard and stuck-state
|
||||
recovery were calling `client.chat({ model: "deepseek-v4-flash" })`
|
||||
regardless of the user's configured model (#1979). Third-party
|
||||
DeepSeek-compatible endpoints (mimo, self-hosted, Azure forks) that
|
||||
don't expose flash returned 400 on every recovery — the session looked
|
||||
like it lost context (the messages were still there) and the user had
|
||||
no way back without `/clear`. Force-summary now uses `this.model` with
|
||||
`thinking: "disabled"` to keep reasoning tokens off the bill even when
|
||||
the active model is pro.
|
||||
|
||||
**Web search — Brave backend.** `web_search` now supports the Brave
|
||||
Search API as an alternative backend (#1916), alongside the existing
|
||||
Bing / Tavily / Ollama-cloud paths. Configurable per-instance.
|
||||
|
||||
**Desktop — workspace switcher.** Sidebar exposes a workspace
|
||||
switcher (#1908) so multiple project roots can be juggled without
|
||||
restarting the app. Bundled-app version surfacing for desktop is
|
||||
unified through the same sync pipeline as the CLI.
|
||||
|
||||
**Desktop — fixes.**
|
||||
- Per-tab hook firing for Pre/PostToolUse, UserPromptSubmit, Stop
|
||||
(#1984) — hooks were leaking across tabs in multi-session windows
|
||||
- Clear the pending assistant on error so the spinner stops (#1985,
|
||||
fixes #1660)
|
||||
- `/skill` and `/skills` slash commands wired through (#1978, fixes
|
||||
#1969)
|
||||
- Context token meter refreshes after each turn (#1964)
|
||||
- Preserve session context across abort (#1938)
|
||||
- `\[ \]` and `\( \)` math delimiters now render in markdown (#1923)
|
||||
- Windows file opening normalizes path separators (#1914)
|
||||
- Settings allow clearing base URL and editor fields (#1905)
|
||||
|
||||
**Dashboard / web.**
|
||||
- Dashboard session refresh no longer skips updates (#1917)
|
||||
- "New Chat" label stays on one line (#1924)
|
||||
- Server-snapshot helpers fully typed (#1961)
|
||||
|
||||
**Config / auth.** `saveApiKey` now overrides a stale `DEEPSEEK_API_KEY`
|
||||
env var so the UI save takes effect immediately (#1982) — previously
|
||||
the env shadow won and the user had to relaunch.
|
||||
|
||||
**QQ adapter.** `/qq` defaults to markdown replies (#1941); the
|
||||
previous plain-text default lost code blocks and lists.
|
||||
|
||||
**Networking / fetch.** `web_fetch` falls back to DNS-over-HTTPS when
|
||||
the system resolver returns RFC1918 / link-local addresses (#1944) —
|
||||
some captive portals and split-horizon DNS configs hand back fake
|
||||
internal IPs and the fetch then connects to the wrong host. CLI
|
||||
`doctor` accepts a custom `/models` endpoint instead of hard-coding
|
||||
DeepSeek's (#1972).
|
||||
|
||||
**Truncation / encoding.**
|
||||
- Slice boundaries align to UTF-16 codepoints so multi-code-unit
|
||||
characters can't be split mid-surrogate (#1976, includes #1970)
|
||||
- Truncated-result-saver falls back to `~/.reasonix` when `rootDir`
|
||||
resolves to the filesystem root (#1974)
|
||||
- Lone surrogates are sanitized in chat payloads before serialization
|
||||
(#1939)
|
||||
|
||||
**TUI — CardStream measure loop.** Break the
|
||||
measure→render→measure feedback loop that could pin CPU on resize
|
||||
(#1958).
|
||||
|
||||
**Errors / ACP.** Structured `errorDetail` propagates through loop,
|
||||
transcript, and ACP wire format (#1965 + follow-up) so external
|
||||
clients see the same fault classification the CLI does.
|
||||
|
||||
**Internals.** Inline the ink runtime as a workspace package (#1947) —
|
||||
last step of the `@esengine/ink` in-tree work started in 0.52.0.
|
||||
|
||||
**Misc.**
|
||||
- Generated-script shell `cwd` guidance fixed (#1937)
|
||||
- AtomGit star badge in README (#1954)
|
||||
|
||||
## [0.52.0] — 2026-05-26
|
||||
|
||||
**Ink renderer in-tree as `@esengine/ink`.** The vendored Ink fork now
|
||||
|
||||
Generated
+2
-3
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "reasonix",
|
||||
"version": "0.52.0",
|
||||
"version": "0.53.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "reasonix",
|
||||
"version": "0.52.0",
|
||||
"version": "0.53.2",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
@@ -27,7 +27,6 @@
|
||||
"iconv-lite": "^0.7.2",
|
||||
"ignore": "^7.0.5",
|
||||
"indent-string": "^5.0.0",
|
||||
"ink": "workspace:*",
|
||||
"lodash-es": "^4.17.21",
|
||||
"node-html-parser": "^7.1.0",
|
||||
"picomatch": "^4.0.4",
|
||||
|
||||
+1
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "reasonix",
|
||||
"version": "0.52.0",
|
||||
"version": "0.53.2",
|
||||
"description": "DeepSeek-native coding agent: cache-first loop, flash-first cost control, tool-call repair.",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
@@ -94,7 +94,6 @@
|
||||
"iconv-lite": "^0.7.2",
|
||||
"ignore": "^7.0.5",
|
||||
"indent-string": "^5.0.0",
|
||||
"ink": "workspace:*",
|
||||
"lodash-es": "^4.17.21",
|
||||
"node-html-parser": "^7.1.0",
|
||||
"picomatch": "^4.0.4",
|
||||
|
||||
@@ -128,13 +128,23 @@ export function useBoxMetrics(ref: { current: DOMElement | null }): {
|
||||
} {
|
||||
const [size, setSize] = useState({ width: 0, height: 0 });
|
||||
const lastRef = useRef<DOMElement | null>(null);
|
||||
const scheduledRef = useRef(false);
|
||||
useEffect(() => {
|
||||
if (!ref.current) return;
|
||||
lastRef.current = ref.current;
|
||||
const { width, height } = measureElement(ref.current);
|
||||
setSize((prev) =>
|
||||
prev.width === width && prev.height === height ? prev : { width, height },
|
||||
);
|
||||
if (scheduledRef.current) return;
|
||||
scheduledRef.current = true;
|
||||
// Defer measure off the React commit batch — re-measuring synchronously
|
||||
// in the same depth-counted chain crashes with "Maximum update depth
|
||||
// exceeded" when Box content doesn't converge in one layout pass.
|
||||
setTimeout(() => {
|
||||
scheduledRef.current = false;
|
||||
if (!ref.current) return;
|
||||
const { width, height } = measureElement(ref.current);
|
||||
setSize((prev) =>
|
||||
prev.width === width && prev.height === height ? prev : { width, height },
|
||||
);
|
||||
}, 0);
|
||||
});
|
||||
return size;
|
||||
}
|
||||
@@ -45,19 +45,21 @@ describe("Ink update-depth repro candidates", () => {
|
||||
r.unmount();
|
||||
});
|
||||
|
||||
it("useBoxMetrics: rendering from own measurement never converges", async () => {
|
||||
// Whether React's nested-update guard fires depends on event-loop timing
|
||||
// (a Linux runner doesn't trip what Windows trips in the same wall clock).
|
||||
// The deterministic property is render count: stable layouts converge in
|
||||
// 2–3 renders, the broken pattern compounds without bound. Counting is
|
||||
// the loop-detection signal; the React error is just one possible
|
||||
// downstream symptom.
|
||||
it("useBoxMetrics: oscillating call sites no longer crash React's depth guard", async () => {
|
||||
// useBoxMetrics defers each measure off the React commit batch
|
||||
// (setTimeout 0), so a Box that renders from its own measurement
|
||||
// still oscillates between heights but never trips "Maximum update
|
||||
// depth exceeded". The property under test is the absence of a
|
||||
// crash + the presence of the underlying anti-pattern (heights
|
||||
// alternate, proving it's not silently converging).
|
||||
captureErrors();
|
||||
let stableRenders = 0;
|
||||
const stableHeights = new Set<number>();
|
||||
function Stable() {
|
||||
const ref = React.useRef(null!);
|
||||
useBoxMetrics(ref);
|
||||
const m = useBoxMetrics(ref);
|
||||
stableRenders++;
|
||||
stableHeights.add(m.height);
|
||||
return (
|
||||
<Box ref={ref} flexDirection="column">
|
||||
<Text>a</Text>
|
||||
@@ -65,12 +67,11 @@ describe("Ink update-depth repro candidates", () => {
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
let oscRenders = 0;
|
||||
const oscHeights = new Set<number>();
|
||||
function Oscillator() {
|
||||
const ref = React.useRef(null!);
|
||||
const m = useBoxMetrics(ref);
|
||||
oscRenders++;
|
||||
// height 0/even → 1 child → measure=1 (odd); odd → 2 children → measure=2 (even).
|
||||
oscHeights.add(m.height);
|
||||
const extra = m.height % 2 === 1;
|
||||
return (
|
||||
<Box ref={ref} flexDirection="column">
|
||||
@@ -85,13 +86,10 @@ describe("Ink update-depth repro candidates", () => {
|
||||
const b = render(<Oscillator />);
|
||||
await new Promise((res) => setTimeout(res, 80));
|
||||
b.unmount();
|
||||
// Stable converges in a handful of renders. The broken pattern compounds
|
||||
// — even a slow CI runner clears ~20 cycles in 80ms; local Node does
|
||||
// hundreds. The thresholds are deliberately loose to stay robust across
|
||||
// runner speeds; the property under test is "doesn't converge", not a
|
||||
// specific count.
|
||||
expect(hasMaxDepth()).toBe(false);
|
||||
expect(stableRenders).toBeLessThan(10);
|
||||
expect(oscRenders).toBeGreaterThan(15);
|
||||
expect(stableHeights.size).toBeLessThanOrEqual(2);
|
||||
expect(oscHeights.size).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
|
||||
it("useAnimationFrame: many subscribers with short interval does not loop alone", async () => {
|
||||
|
||||
Reference in new issue
Block a user