文件最后提交记录最后更新时间
feat(code-query): tree-sitter get_symbols + find_in_code (TS/JS/Python/Go/Rust/Java) (#1387) * feat(code-query): tree-sitter get_symbols + find_in_code tools for TS/TSX/JS Two grammar-aware tools for single-file code structure queries, scoped per the #556 RFC's "narrowly-scoped one-tier pilot" constraint. - get_symbols: outlines top-level + nested symbols (function, class, method, interface, type, enum, namespace) with 1-based positions - find_in_code: identifier search with role classification (call / definition / reference), AST-filtered so comments + string literals do not match; handles new X() and obj.X() as calls on X Bundles tree-sitter-typescript + tree-sitter-javascript wasm from the official npm packages (~3.4 MB in dist/grammars/). Non-TS/JS files return an "unsupported; use search_content" hint. Within-file only — cross-file resolution stays out of scope per the RFC. Closes #556 * feat(code-query): extend to Python / Go / Rust / Java Adds 4 grammars beyond the TS/TSX/JS/JSX pilot: tree-sitter-python, tree-sitter-go, tree-sitter-rust, tree-sitter-java. Total bundled grammar wasm now 5.5 MB. Per-language symbol queries land function / class / method / interface / type / enum / namespace / property kinds with consistent shape. Methods detected via post-process when a function_definition / function_item sits inside a class, impl, or trait container; Rust impl blocks set the parent name from impl's type field so impl Foo and `impl Trait for Foo` both attribute methods to Foo. find_in_code's classifier learns each language's call shape: - Python call, Java method_invocation, object_creation_expression - Go selector_expression, Rust field_expression, Python attribute, JS member_expression — all handled uniformly via field-name lookups so obj.X() is a call on X across languages --------- Co-authored-by: reasonix <reasonix@deepseek.com>9 天前
refactor(ink): extract vendored renderer to @esengine/ink + tui polish (#1847) Stage 1 of #1829: ship the renderer as a separate private package instead of vendoring at src/ink/. Picks up the tui-polish work that landed alongside it (visuals, pulse animations, ghosting fix). ## Renderer extraction - src/ink/ removed; renderer lives in esengine/reasonix-ink (private) - package.json: ink dep points at @esengine/ink (file: tarball for local dev; swap to npm: alias post-publish); react-reconciler and yoga-layout added as direct deps so they hoist to a single top-level instance (single React dispatcher, single reconciler instance); 17 transitive runtime deps and 3 @types removed - tsconfig: ink path-map removed - vitest.config: ink alias removed - tests/helpers/ink-stdio.ts: text() uses strip-ansi + restores CSI N C cursor-forward back to literal spaces - tests/helpers/ink-test.ts (new): replaces ink-testing-library; lastFrame() joins all chunks instead of returning only the most recent write() — required because the diff-based renderer emits multi-chunk frames per commit - 15 test files migrated; 13 obsolete UI snapshot tests removed (5 files) ## Visual alignment - UserCard: single full-row backgroundColor=MESSAGE_BG.user fill - ApprovalCard: round border with borderLeft/borderRight=false - PromptInput: round border (top + bottom only) + borderText with plan / history / mode / model - ReasoningCard: italic + dim header; collapsed state appends (ctrl-o to expand) hint - theme/tokens: new MESSAGE_BG section (user/bash/selected) for all 5 themes - CardHeader: glyph prop accepts string | ReactElement ## Pulse animation system - Pulse primitive: generic frame cycler with DIAMOND/SQUARE/ TRIANGLE/CIRCLE/HEX presets aligned to existing brand glyphs - Wired into 7 cards + 4 LiveRows components - ThinkingRow + OngoingToolRow: live elapsed time + output token count + t/s rate - ticker.tsx: useTick / useSlowTick switched from useAnimationTimer (keepAlive=false) to useAnimationFrame (keepAlive=true) so the shared clock actually starts ## Bug fixes - PromptInput: removed systemCursorSync — the out-of-band CSI CUP write desynced the renderer's screen model under sync-output mode, causing residual glyphs starting at the 3rd typed character - Wizard: TextInput now sourced from ink (drop-in TextInput exposed by the package); ink-text-input dep removed ## Tests 279 files, 3722 passed, 11 skipped, 0 failed. Refs #1829 (Stage 1). Co-authored-by: reasonix <reasonix@deepseek.com>3 天前
feat(desktop): add approval and completion notifications (#1519) Adds OS-level notifications to the Tauri desktop app so a user away from the window finds out when: - a new approval request appears (shell command / path access / choice / plan / checkpoint / revision) - a long-running turn finishes (only after at least 15 s of busy → idle, to skip quick replies) Design: - Pure deriveDesktopNotifications(...) derives the list from previous-vs-current snapshots; no Tauri calls inside, so the unit tests run without a window. - dispatchDesktopNotifications(...) takes injected deps (isFocused / isPermissionGranted / requestPermission / sendNotification) so the App.tsx wiring is the only place that talks to the Tauri plugin. Focus check is repeated at dispatch time as a second guard. - Notification permission is requested at most once per session; a denial sticks. - All copy goes through t() against the active desktop UI language; both EN and zh-CN added. Wiring lives in TabRuntime via a useEffect over the pending-state slices + busy flag. tauri-plugin-notification is added on both the JS and Rust sides (Cargo.toml + capabilities/default.json). A vitest alias maps the plugin to a mock so the desktop test suite stays hermetic.7 天前
feat(storm): add stormExempt flag to exempt obvious reads from the loop guard (#388) Co-authored-by: Charles H <contact@ctharvey.me>22 天前
test(lifecycle): rename lifecycle harness fixtures (#1532) Tiny follow-up to #1499. The harness helper used to register only mutation tools (delete_file, move_file, etc), but #1499 extended it to register read_file too so the cancel/stop-recovery tests could verify read-only operations still work. The old name registerMutationTools doesn't fit anymore — renaming to registerLifecycleToolFixtures. Test-only, behavior-neutral. Refs #1285. Follow-up to #1499.7 天前
feat(acp): --mcp + --mcp-prefix for headless callers (#780) reasonix acp is missing the MCP-server bridging that reasonix run and reasonix chat already have. Headless ACP clients (Paperclip's reasonix-local adapter, etc.) need to plumb domain-specific tool servers to the agent without forking config — same shape as #766/#767. Lifts the MCP load loop from run.ts:81-142 into a loadMcpServers helper and calls it inside buildSession so the tools are bridged into the toolset registry BEFORE the prefix is computed (matters for the cache key). Failures stay non-fatal — a broken side-concern server logs to stderr and the session keeps running. Disposes spawned MCP children when the ACP server's stdin closes. Closes #779.16 天前
feat(acp): --transcript flag for headless cost capture (#766) * feat(acp): --transcript flag for headless cost capture Tees raw LoopEvents through writeRecord/recordFromLoopEvent during session/prompt so headless ACP callers can parse usage, cost, and prefixHash without driving the TUI. Reuses the JSONL shape chat and code already emit; zero changes to transcript helpers. Single-session-per-process — mirrors chat/code behavior; the JSONL stays cleanly seekable. Adds tests/fixtures/acp-driver.ndjson as scaffolding for future end-to-end ACP smoke tests, per maintainer ack on #765. Refs: #765 * test(acp): assert fixture sessionId is a placeholder Adversarial review noted that piping acp-driver.ndjson as-is into the binary produces ERR_INVALID_PARAMS "unknown session" with no hint about the substitution requirement. Lock in the placeholder contract so anyone editing the fixture sees the test fail with a clear comment instead of discovering the trap at runtime.16 天前
fix(yolo): auto-resolve run_command + run_background gates in YOLO mode (#1489) Reported in #1448. With YOLO mode enabled, file edits skip confirmation (editModeRef path) but shell commands still prompt — defeating the "fully unattended" promise. Two layers were supposed to bypass shell prompts in YOLO: 1. shell.ts's isAllowAll() check — should skip gate.ask entirely. 2. pause-policy.ts:autoResolveVerdict — should auto-resolve any gate.ask that does slip through. Layer 2 only handled plan_checkpoint and path_access. run_command and run_background fell through to the UI for a manual prompt. That gap matters because shell.ts's allowAll closure reads on-disk config via loadEditMode(), but: - reasonix acp --yolo sets opts.yolo = true in-memory without calling saveEditMode, so shell.ts can't see it. - Anywhere a desync between the in-UI editMode and the persisted config can occur (e.g. mid-toggle races), shell.ts will let the gate.ask through. Extend autoResolveVerdict to return { type: "run_once" } for run_command / run_background when editMode === "yolo" — same pattern as path_access. run_once (not always_allow) so a YOLO session doesn't pollute the persisted allowlist with every transient command. Matches the documented intent ("yolo mirrors shell.ts's allowAll bypass"). Tests in tests/acp-yolo.test.ts: - Replaced the previous assertion that run_command falls through to the bridge under --yolo (that was the old design that's now the bug) with one that asserts auto-resolve to run_once. - Added the same coverage for run_background. - Added a regression that run_command in auto mode still bridges to the client (only yolo bypasses). Closes #1448 Co-authored-by: reasonix <reasonix@deepseek.com>8 天前
chore(errors): tighten errorDetail propagation (#1965) Follow-up on #1918 — strip the four loose joints that surfaced in review: - Replace (err as any).code / (err as any).phase casts with a small errorMeta(err) narrowing helper in loop/errors.ts. Same payload, no escape hatch. - Extract is4xxError alongside is5xxError so the loop's "client error → not retryable" branch reads like the existing 5xx code path instead of an inline regex. - Decouple recoverable from retryable in the loop's API-failure yield. recoverable is the historical "agent can keep going" flag (consumed by event listing), retryable is the new external retry hint; conflating them changed event-listing output silently. - Carry name through ErrorEvent + eventize so the ACP dispatcher surfaces the real error name instead of ev.message.split(":")[0]. Co-authored-by: reasonix <reasonix@deepseek.com>2 天前
feat(tui): phase-aware label for the busy-fallback activity row (#546) Refs #36720 天前
fix(ui): remove mojibake from scroll history hints (#1094) Move the scroll-history hint into an i18n-backed InputAreaWithHistoryHint component (app.historyScrollHint), and replace remaining GBK-as-UTF-8 mojibake literals (闂?, 濠电姷…) in App.tsx with the intended ASCII separators / wording. Adds tests/app-visible-mojibake.test.ts to scan source for the mojibake byte sequences so the corruption can't recur. Closes #1082.12 天前
chore(quality): expand comment-policy scope + architecture invariants test (#92) The comment-policy gate previously only walked src/. Tests, benchmarks, scripts, and dashboard/src were left to convention — which meant that multi-paragraph module-essay headers and stale phase-narrative comments silently accumulated. Expand the test's roots to cover every code dir, clean the 146 violations that surfaced, and gate them at pre-push. The new tests/architecture-invariants.test.ts promotes two properties from spike #87 (Exp 1) into permanent regression: - reducer projection determinism (identical events → byte-identical messages, regardless of timestamp / id / counter state) - message-level append-only across turn boundaries (every cut is a strict prefix of any later cut) These are the load-bearing properties for the v0.14 event kernel and the forkable-sessions RFC. Encoding them as tests means any reducer / event schema change that violates them fails CI, not a future spike. No behavior change in src/. Comment-policy expansion + invariants test add about 18 new test assertions; full verify is unchanged at ~23s.27 天前
refactor(ink): extract vendored renderer to @esengine/ink + tui polish (#1847) Stage 1 of #1829: ship the renderer as a separate private package instead of vendoring at src/ink/. Picks up the tui-polish work that landed alongside it (visuals, pulse animations, ghosting fix). ## Renderer extraction - src/ink/ removed; renderer lives in esengine/reasonix-ink (private) - package.json: ink dep points at @esengine/ink (file: tarball for local dev; swap to npm: alias post-publish); react-reconciler and yoga-layout added as direct deps so they hoist to a single top-level instance (single React dispatcher, single reconciler instance); 17 transitive runtime deps and 3 @types removed - tsconfig: ink path-map removed - vitest.config: ink alias removed - tests/helpers/ink-stdio.ts: text() uses strip-ansi + restores CSI N C cursor-forward back to literal spaces - tests/helpers/ink-test.ts (new): replaces ink-testing-library; lastFrame() joins all chunks instead of returning only the most recent write() — required because the diff-based renderer emits multi-chunk frames per commit - 15 test files migrated; 13 obsolete UI snapshot tests removed (5 files) ## Visual alignment - UserCard: single full-row backgroundColor=MESSAGE_BG.user fill - ApprovalCard: round border with borderLeft/borderRight=false - PromptInput: round border (top + bottom only) + borderText with plan / history / mode / model - ReasoningCard: italic + dim header; collapsed state appends (ctrl-o to expand) hint - theme/tokens: new MESSAGE_BG section (user/bash/selected) for all 5 themes - CardHeader: glyph prop accepts string | ReactElement ## Pulse animation system - Pulse primitive: generic frame cycler with DIAMOND/SQUARE/ TRIANGLE/CIRCLE/HEX presets aligned to existing brand glyphs - Wired into 7 cards + 4 LiveRows components - ThinkingRow + OngoingToolRow: live elapsed time + output token count + t/s rate - ticker.tsx: useTick / useSlowTick switched from useAnimationTimer (keepAlive=false) to useAnimationFrame (keepAlive=true) so the shared clock actually starts ## Bug fixes - PromptInput: removed systemCursorSync — the out-of-band CSI CUP write desynced the renderer's screen model under sync-output mode, causing residual glyphs starting at the 3rd typed character - Wizard: TextInput now sourced from ink (drop-in TextInput exposed by the package); ink-text-input dep removed ## Tests 279 files, 3722 passed, 11 skipped, 0 failed. Refs #1829 (Stage 1). Co-authored-by: reasonix <reasonix@deepseek.com>3 天前
fix(at-mention): allow CJK and other Unicode letters in @-mention paths (#764) The picker's prefix regex was ASCII-only: /(?:^|\s)@([a-zA-Z0-9_./\-]*)$/ So @中文/ failed the $ anchor on the first non-ASCII byte and detectAtPicker returned null. The picker disappeared, Tab had nothing to drill into, and the user couldn't enter a Chinese-named folder via @. Swap the inner char class for [\p{L}\p{N}_./\-] with the u flag. Same change on AT_MENTION_PATTERN so the expander recognises the completed mention too. Closes #749.16 天前
fix(config): EXDEV fallback for atomic write on OneDrive / NTFS reparse points (#1778) writeConfig and rewriteSession used renameSync for the final atomic swap. On Windows, when ~/.reasonix sits inside a OneDrive-managed folder or crosses a junction / reparse-point boundary, rename refuses with EXDEV (-4037) even though the tmp file and destination are siblings. The CLI then crashed on every launch trying to mint the dashboard token (#1738). Extract a shared atomicWriteSync helper that catches EXDEV from rename and falls back to copyFileSync + unlink. Atomicity is lost on that path (no other safe option on Windows), but a working write beats the launch-blocking error. Fixes #1738. Co-authored-by: reasonix <reasonix@deepseek.com>4 天前
fix(tests): sync assertions broken by #2173 and #2168 (#2200) - auto-git-rollback: write_file output changed from 'wrote N chars' to 'edited ... (M→N chars)' unified-diff format (#2173); update regex. - slash + ui-slash-suggestions: Telegram /telegram command added by #2168 brings the release command count from 43 to 44; update both assertions.19 小时前
feat(ui): !cmd bash-mode prefix — user-typed shell passthrough (0.5.8) Claude-Code-style bash mode. User types !ls src/ or !git status; Reasonix runs the command in the sandbox root, shows both the command and its output, and appends the combined text to the conversation log so the next model turn has the context automatically. Faster than round-tripping through the model just to cat a file or check git state, and closes a common UX gap vs. Claude Code. ### Mechanism - src/cli/ui/bang.tsdetectBangCommand(text) returns the command body (whitespace-trimmed) or null. Only a LEADING ! counts; ! alone / ! returns null; echo !hi doesn't trigger (the ! isn't at position 0). - formatBangUserMessage(cmd, output) — prepends a [!cmd]\n header so the model can distinguish user-typed shell output from its own run_command tool results (which are role: tool, structurally different). - App.tsx handleSubmit intercept — fires AFTER the y/n fast-path (so pending edits still resolve cleanly) and BEFORE slash parsing: - Push role: "user" Historical row with the raw !cmd text. - `await runCommand(cmd, { cwd: codeMode?.rootDir ?? cwd, timeoutSec: 60, maxOutputChars: 32_000 })`. - Push role: "info" Historical row with formatCommandResult(cmd, result). - `loop.log.append({ role: "user", content: formatBangUserMessage (cmd, formatted) })` so the next step() includes it in buildMessages. - setBusy true/false around the run so PromptInput reflects the in-flight state. - Catch errors → warning row, don't log. ### No allowlist gate User-typed ! is explicit consent (consistent with Claude Code; the allowlist exists to bound what the MODEL can do with run_command, not what the user chooses by hand). If someone wants stricter gating later a REASONIX_BANG_CONFIRM=1 toggle could layer on — out of scope here. ### Known limitation: session durability loop.log.append updates in-memory only. On session resume, the bang output is gone (the session file didn't see it). The next actual step() does appendAndPersist for the next user turn, and the model sees this bang output live within the same session either way. Treating bang as ephemeral keeps the fast-path simple; durable capture could land later if real usage surfaces demand. ### /help + discovery /help gains a "Shell shortcut" section documenting !cmd with example commands (!git status !ls src/ !npm test), plus a "File references" section covering the @ picker from 0.5.5 / 0.5.7 that was undocumented there. ### Tests (+9, suite 897 → 906) tests/bang.test.ts — detectBangCommand covers: body extraction, whitespace trim, non-bang pass-through, !-alone / blank body, mid-string ! rejection, embedded-trailing-! preservation, multi-word with flags/quotes. formatBangUserMessage covers header prepending and verbatim body preservation. Lint + typecheck + build clean. 906 tests, 8.6s. 1 个月前
chore(quality): expand comment-policy scope + architecture invariants test (#92) The comment-policy gate previously only walked src/. Tests, benchmarks, scripts, and dashboard/src were left to convention — which meant that multi-paragraph module-essay headers and stale phase-narrative comments silently accumulated. Expand the test's roots to cover every code dir, clean the 146 violations that surfaced, and gate them at pre-push. The new tests/architecture-invariants.test.ts promotes two properties from spike #87 (Exp 1) into permanent regression: - reducer projection determinism (identical events → byte-identical messages, regardless of timestamp / id / counter state) - message-level append-only across turn boundaries (every cut is a strict prefix of any later cut) These are the load-bearing properties for the v0.14 event kernel and the forkable-sessions RFC. Encoding them as tests means any reducer / event schema change that violates them fails CI, not a future spike. No behavior change in src/. Comment-policy expansion + invariants test add about 18 new test assertions; full verify is unchanged at ~23s.27 天前
fix(desktop): bundled CLI loads on installed targets; drop CDN fonts (#806) * fix(desktop): bundled CLI loads on installed targets; drop CDN fonts Two unrelated issues both surfaced as a black-screen on the v0.42.0-2 release. CLI sidecar: tsup was emitting dist/cli/index.js as an ESM bundle with commander, ink, undici, etc. left external. That's fine for npm i -g reasonix (npm brings the deps), but the desktop ships dist/cli/ as a static resource with no node_modules next to it, so the spawned Node process died on ERR_MODULE_NOT_FOUND for the first import and the IPC channel never came up → empty UI. Switch the CLI entry to a self-contained bundle: noExternal: [/.*/] plus platform: "node", with react-devtools-core whitelisted as external (it's an optional peer dep that ink dynamically pulls only when devtools are enabled, which we never do). undici's dispatcher uses require() at runtime; in ESM output the esbuild shim refuses dynamic require. Banner-inject a createRequire into globalThis.require so the shim picks it up. Drop a one-line dist/cli/package.json with {"type":"module"} so Node skips the CJS-first parse attempt and the MODULE_TYPELESS_PACKAGE_JSON warning. Fonts: index.html loaded IBM Plex from googleapis.com, which the bundled CSP (`style-src 'self' 'unsafe-inline'; font-src 'self' data:) blocks. Switch to @fontsource/ibm-plex-{sans,mono,serif}` so the woff2s are bundled into dist/assets/ at build time — identical typeface, zero runtime network. The previously-imported @fontsource-variable/inter + jetbrains-mono packages were dead weight (CSS never referenced those families); removed. Enable tauri's devtools feature while the desktop is prerelease — F12 is what surfaced both bugs, and it stays useful while the desktop is being shaken out. Revisit before stable. * test(bundle-smoke): assert runtime deps are inlined, not external Companion to the CLI bundling change: the smoke guardrail was the inverse of what we now want — it asserted ink was kept external so npm could resolve it from the user's tree. The desktop sidecar runs from a static dist/cli/ with no node_modules, so external imports are exactly the failure mode we're guarding against now. Flips the assertion: no dist/cli/*.js should `import from "commander" | "ink" | "undici"` — those are the three that broke v0.42.0-2.15 天前
feat: cache diagnostics v1 — /cache-miss-report, doctor --cache, prefix hash evidence | 缓存诊断 v1 (#2188) * feat(cache): add prompt cache diagnostics * fix(cache): hash sent tool snapshot in diagnostics * perf(cache): memoize prefix diagnostic hashes16 小时前
fix(loop): trim long-session retained payloads (#1799) * fix(loop): prune stale plain reasoning * fix(loop): trim retained tool-call payloads4 天前
fix(tui): break CardStream measure→render→measure loop (#1958) * fix(tui): break CardStream measure→render→measure loop (#1955) CardStream measured the inner scroll Box via useBoxMetrics, derived maxScroll from inner.height - outer.height, then pinned-mode wrote that back to scrollRows — which feeds into marginTop and items composition, which feeds back into inner.height. Several edit_file results streaming in one turn was enough to stack the cascade past React's nested-update limit (50). Inner height is already known per-card in the chat-scroll store — the per-card useBoxMetrics in MeasuredCard reports it. Summing the store gives the same total without ever measuring inner, so scrollRows can no longer feed back into the value used to compute maxScroll. - ink-update-depth-repro.test.tsx: pins down the underlying pattern (a component that decides its own render from its own useBoxMetrics reading trips Max update depth). - cardstream-architecture.test.tsx: side-by-side pre/post-fix harness proving the post-fix path stays linear and converges on the right maxScroll. Closes #1955 * test(tui): assert oscillation by render count, not Max-depth firing CI hit a flaky failure: Linux + Windows runners didn't trip React's nested-update guard within 80ms while local Windows did. The guard firing is timing-dependent (depends on how the event loop happens to interleave commits and setInterval) — what's deterministic is that the broken pattern keeps re-measuring forever. Assert that instead: stable layouts converge in a few renders, the bad pattern compounds without bound. * test(tui): loosen oscillation count threshold for slow CI runners Windows CI cleared 22-26 renders in 80ms versus hundreds locally; the 10× multiplier kept tripping. Switch to an absolute floor of 15 — still far above the stable-layout converge (≤ 3) and proves the "doesn't converge" property without coupling to runner speed. --------- Co-authored-by: reasonix <reasonix@deepseek.com>2 天前
fix(tui): drop stale copy startup hint (#1692)5 天前
fix: restore scroll and memory visibility (#1766)4 天前
chore(quality): expand comment-policy scope + architecture invariants test (#92) The comment-policy gate previously only walked src/. Tests, benchmarks, scripts, and dashboard/src were left to convention — which meant that multi-paragraph module-essay headers and stale phase-narrative comments silently accumulated. Expand the test's roots to cover every code dir, clean the 146 violations that surfaced, and gate them at pre-push. The new tests/architecture-invariants.test.ts promotes two properties from spike #87 (Exp 1) into permanent regression: - reducer projection determinism (identical events → byte-identical messages, regardless of timestamp / id / counter state) - message-level append-only across turn boundaries (every cut is a strict prefix of any later cut) These are the load-bearing properties for the v0.14 event kernel and the forkable-sessions RFC. Encoding them as tests means any reducer / event schema change that violates them fails CI, not a future spike. No behavior change in src/. Comment-policy expansion + invariants test add about 18 new test assertions; full verify is unchanged at ~23s.27 天前
fix: Model was still doing things while pending to approve (#237) * fix: Model was still doing things while user was being prompt to accept tool call. * Checkpoint feedback, remove dead code24 天前
feat(index): config-driven excludes + nested .gitignore Pulls SKIP_DIRS / SKIP_FILES / BINARY_EXTS / maxFileBytes out of chunker.ts (and the duplicate copy in directory_tree) into a single src/index/config.ts. ReasonixConfig gains an optional index block where any field present fully replaces the default for that field. walkChunks now takes a ResolvedIndexConfig and an onSkip callback, honours .gitignore by default (nested per-dir, scoped to the dir the file lives in), and supports picomatch globs via excludePatterns. buildIndex aggregates the callback into a per-reason skipBuckets map that the CLI surfaces in its success line. directory_tree no longer carries its own list — both code paths share DEFAULT_INDEX_EXCLUDES, eliminating the drift the comment at chunker.ts:29 had been warning about. scripts/smoke-index-config.mjs is a tsx one-shot for verifying the walker + .gitignore + glob behaviour against a real repo without needing Ollama. 29 天前
fix(cli): add opt-out for terminal mouse tracking (#1345) Closes #1337.9 天前
fix(cli): preserve bundled CLI version metadata (#1997)2 天前
fix(desktop): refresh context token meter (#1964)2 天前
fix(acp): propagate structured error details through loop, transcript, ACP When a proxy/LB idle timeout cuts an SSE stream, undici throws "terminated" as a bare string and ACP headless clients lose the context needed to decide whether to retry. Thread structured errorDetail ({name, message, code, phase, retryable, recoverable}) through client → loop → kernel events → transcript → ACP so the failure cause survives every hop. All changes are additive: LoopEvent.errorDetail, TranscriptRecord.errorDetail, SessionPromptResult.transcriptPath, and SessionUpdate.metadata.error are all optional, so existing clients that ignore unknown fields keep working. Closes #1884.2 天前
fix(client): wire timeoutMs to fetch when caller passes a signal (#1535) (#1553) opts.signal ?? ctrl.signal orphaned the timer's controller whenever the loop forwarded its _turnAbort.signal — i.e. every real call. A genuinely stalled SSE body (TCP open, no [DONE], no close) then sat on reader.read() forever and the TUI hung in "…waiting for response…" until the user restarted it, even though the server-side request had already completed. Combine both signals with AbortSignal.any and have the timer abort with a reason so the loop's error path surfaces "timed out after Xms" instead of a silent stall. Closes #1535 Co-authored-by: reasonix <reasonix@deepseek.com>7 天前
test: add unit tests for clipboard.ts (#62) Closes #1828 天前
feat(server): events.jsonl cockpit fields (Stage 1B of #64) (#67) Adds the events-derived half of the design §5 cockpit: toolCalls·24h with prior-window delta, recentPlans (4 most recent with completion ratio + done/active status), toolActivity (6 most recent tool calls with ok/warn/err level). Reads up to 8 most-recently-modified *.events.jsonl files from the sessions dir, skips files whose mtime is older than 30 days. Plugs into the existing 30s TTL cache shared with Stage 1A. Adds sessionsDir? to DashboardContext so tests can isolate from the user's real ~/.reasonix/sessions. Closes part of #64.28 天前
feat(server): events.jsonl cockpit fields (Stage 1B of #64) (#67) Adds the events-derived half of the design §5 cockpit: toolCalls·24h with prior-window delta, recentPlans (4 most recent with completion ratio + done/active status), toolActivity (6 most recent tool calls with ok/warn/err level). Reads up to 8 most-recently-modified *.events.jsonl files from the sessions dir, skips files whose mtime is older than 30 days. Plugs into the existing 30s TTL cache shared with Stage 1A. Adds sessionsDir? to DashboardContext so tests can isolate from the user's real ~/.reasonix/sessions. Closes part of #64.28 天前
fix(ui): preserve code operators in code blocks (#1640) Disable font ligatures and contextual alternates for markdown code blocks in both dashboard and desktop, so operator sequences like -> => != render as separate glyphs instead of shaped ligatures. Closes #16365 天前
Fix generated-script shell cwd guidance (#1937) * fix: clarify shell cwd guidance for generated scripts * fix: soften generated script cwd guidance * test: stabilize regex worker restart check2 天前
feat(code-query): tree-sitter get_symbols + find_in_code (TS/JS/Python/Go/Rust/Java) (#1387) * feat(code-query): tree-sitter get_symbols + find_in_code tools for TS/TSX/JS Two grammar-aware tools for single-file code structure queries, scoped per the #556 RFC's "narrowly-scoped one-tier pilot" constraint. - get_symbols: outlines top-level + nested symbols (function, class, method, interface, type, enum, namespace) with 1-based positions - find_in_code: identifier search with role classification (call / definition / reference), AST-filtered so comments + string literals do not match; handles new X() and obj.X() as calls on X Bundles tree-sitter-typescript + tree-sitter-javascript wasm from the official npm packages (~3.4 MB in dist/grammars/). Non-TS/JS files return an "unsupported; use search_content" hint. Within-file only — cross-file resolution stays out of scope per the RFC. Closes #556 * feat(code-query): extend to Python / Go / Rust / Java Adds 4 grammars beyond the TS/TSX/JS/JSX pilot: tree-sitter-python, tree-sitter-go, tree-sitter-rust, tree-sitter-java. Total bundled grammar wasm now 5.5 MB. Per-language symbol queries land function / class / method / interface / type / enum / namespace / property kinds with consistent shape. Methods detected via post-process when a function_definition / function_item sits inside a class, impl, or trait container; Rust impl blocks set the parent name from impl's type field so impl Foo and `impl Trait for Foo` both attribute methods to Foo. find_in_code's classifier learns each language's call shape: - Python call, Java method_invocation, object_creation_expression - Go selector_expression, Rust field_expression, Python attribute, JS member_expression — all handled uniformly via field-name lookups so obj.X() is a call on X across languages --------- Co-authored-by: reasonix <reasonix@deepseek.com>9 天前
feat(code-query): tree-sitter get_symbols + find_in_code (TS/JS/Python/Go/Rust/Java) (#1387) * feat(code-query): tree-sitter get_symbols + find_in_code tools for TS/TSX/JS Two grammar-aware tools for single-file code structure queries, scoped per the #556 RFC's "narrowly-scoped one-tier pilot" constraint. - get_symbols: outlines top-level + nested symbols (function, class, method, interface, type, enum, namespace) with 1-based positions - find_in_code: identifier search with role classification (call / definition / reference), AST-filtered so comments + string literals do not match; handles new X() and obj.X() as calls on X Bundles tree-sitter-typescript + tree-sitter-javascript wasm from the official npm packages (~3.4 MB in dist/grammars/). Non-TS/JS files return an "unsupported; use search_content" hint. Within-file only — cross-file resolution stays out of scope per the RFC. Closes #556 * feat(code-query): extend to Python / Go / Rust / Java Adds 4 grammars beyond the TS/TSX/JS/JSX pilot: tree-sitter-python, tree-sitter-go, tree-sitter-rust, tree-sitter-java. Total bundled grammar wasm now 5.5 MB. Per-language symbol queries land function / class / method / interface / type / enum / namespace / property kinds with consistent shape. Methods detected via post-process when a function_definition / function_item sits inside a class, impl, or trait container; Rust impl blocks set the parent name from impl's type field so impl Foo and `impl Trait for Foo` both attribute methods to Foo. find_in_code's classifier learns each language's call shape: - Python call, Java method_invocation, object_creation_expression - Go selector_expression, Rust field_expression, Python attribute, JS member_expression — all handled uniformly via field-name lookups so obj.X() is a call on X across languages --------- Co-authored-by: reasonix <reasonix@deepseek.com>9 天前
feat(code-query): tree-sitter get_symbols + find_in_code (TS/JS/Python/Go/Rust/Java) (#1387) * feat(code-query): tree-sitter get_symbols + find_in_code tools for TS/TSX/JS Two grammar-aware tools for single-file code structure queries, scoped per the #556 RFC's "narrowly-scoped one-tier pilot" constraint. - get_symbols: outlines top-level + nested symbols (function, class, method, interface, type, enum, namespace) with 1-based positions - find_in_code: identifier search with role classification (call / definition / reference), AST-filtered so comments + string literals do not match; handles new X() and obj.X() as calls on X Bundles tree-sitter-typescript + tree-sitter-javascript wasm from the official npm packages (~3.4 MB in dist/grammars/). Non-TS/JS files return an "unsupported; use search_content" hint. Within-file only — cross-file resolution stays out of scope per the RFC. Closes #556 * feat(code-query): extend to Python / Go / Rust / Java Adds 4 grammars beyond the TS/TSX/JS/JSX pilot: tree-sitter-python, tree-sitter-go, tree-sitter-rust, tree-sitter-java. Total bundled grammar wasm now 5.5 MB. Per-language symbol queries land function / class / method / interface / type / enum / namespace / property kinds with consistent shape. Methods detected via post-process when a function_definition / function_item sits inside a class, impl, or trait container; Rust impl blocks set the parent name from impl's type field so impl Foo and `impl Trait for Foo` both attribute methods to Foo. find_in_code's classifier learns each language's call shape: - Python call, Java method_invocation, object_creation_expression - Go selector_expression, Rust field_expression, Python attribute, JS member_expression — all handled uniformly via field-name lookups so obj.X() is a call on X across languages --------- Co-authored-by: reasonix <reasonix@deepseek.com>9 天前
feat(code-query): tree-sitter get_symbols + find_in_code (TS/JS/Python/Go/Rust/Java) (#1387) * feat(code-query): tree-sitter get_symbols + find_in_code tools for TS/TSX/JS Two grammar-aware tools for single-file code structure queries, scoped per the #556 RFC's "narrowly-scoped one-tier pilot" constraint. - get_symbols: outlines top-level + nested symbols (function, class, method, interface, type, enum, namespace) with 1-based positions - find_in_code: identifier search with role classification (call / definition / reference), AST-filtered so comments + string literals do not match; handles new X() and obj.X() as calls on X Bundles tree-sitter-typescript + tree-sitter-javascript wasm from the official npm packages (~3.4 MB in dist/grammars/). Non-TS/JS files return an "unsupported; use search_content" hint. Within-file only — cross-file resolution stays out of scope per the RFC. Closes #556 * feat(code-query): extend to Python / Go / Rust / Java Adds 4 grammars beyond the TS/TSX/JS/JSX pilot: tree-sitter-python, tree-sitter-go, tree-sitter-rust, tree-sitter-java. Total bundled grammar wasm now 5.5 MB. Per-language symbol queries land function / class / method / interface / type / enum / namespace / property kinds with consistent shape. Methods detected via post-process when a function_definition / function_item sits inside a class, impl, or trait container; Rust impl blocks set the parent name from impl's type field so impl Foo and `impl Trait for Foo` both attribute methods to Foo. find_in_code's classifier learns each language's call shape: - Python call, Java method_invocation, object_creation_expression - Go selector_expression, Rust field_expression, Python attribute, JS member_expression — all handled uniformly via field-name lookups so obj.X() is a call on X across languages --------- Co-authored-by: reasonix <reasonix@deepseek.com>9 天前
feat(plan): wire editMode="plan" into the ToolRegistry dispatch gate (#1681) ToolRegistry.setPlanMode(true) (src/tools.ts:86) already blocks every non-readonly tool at dispatch — but the only path that ever turned it on was the /plan slash command. Persisted editMode = "plan" (set via config edit or a settings UI that surfaces it) was previously inert: the registry's _planMode stayed false across boots, so the model could still call write_file etc. Extend EditMode to include "plan", and have buildCodeToolset mirror the loaded mode into the registry on construction. Desktop host calls the new applyPlanMode helper in three places: on emitSettings, on every buildRuntimeFor, and inside the settings_save handler when the user toggles edit mode — so live changes take effect without a rebuild. CLI's /plan slash continues to work as the direct toggle. This is the minimal foundation needed before a settings UI in desktop or dashboard can offer "plan" as a 4th edit-mode option. Co-authored-by: reasonix <reasonix@deepseek.com> Co-authored-by: T1anjiu <84212214+T1anjiu@users.noreply.github.com>5 天前
test(comment-policy): ban bare TODO, FIXME, and translator notes (#285) TODO/HACK markers without a (#nnn) issue anchor are debt leakage — they accumulate without ever being tracked. Require the anchor; bare markers fail the comment-policy gate. FIXME is banned outright: either fix it now or open an issue and turn it into a TODO(#nnn). The marker without follow-through is just noise. Translator-note comments in i18n files ("english version", "中文版", etc.) restate what the strings already say — i18n entries are self-documenting. src/ currently has zero violations of any of these rules, so this is a floor-keeper, not a cleanup.23 天前
fix(lint): pass biome check for 0.5.24 (format + unused template + exhaustive deps + comma/assign) - biome format pass on EditConfirm / edit-blocks / jobs / shell / filesystem / compact-tokens / App / slash (auto-fixes) - drop backtick template on a plain-string literal ("failed", "(already reverted — …)") - annotate interceptor useEffect with a useExhaustiveDependencies ignore — the closure captures are intentional; adding the refs as deps would tear down the interceptor on every mode flip - add codeHistory / codeShowEdit to handleSubmit deps - expand comma-operator tricks in tests/tools.test.ts into explicit statements (biome rejects (x = 1, "y") and comma operator under lint/style/noCommaOperator + noAssignInExpressions) prepublishOnly (lint + typecheck + test + build) now passes clean. 1055 tests, dist built. 1 个月前
refactor(ink): extract vendored renderer to @esengine/ink + tui polish (#1847) Stage 1 of #1829: ship the renderer as a separate private package instead of vendoring at src/ink/. Picks up the tui-polish work that landed alongside it (visuals, pulse animations, ghosting fix). ## Renderer extraction - src/ink/ removed; renderer lives in esengine/reasonix-ink (private) - package.json: ink dep points at @esengine/ink (file: tarball for local dev; swap to npm: alias post-publish); react-reconciler and yoga-layout added as direct deps so they hoist to a single top-level instance (single React dispatcher, single reconciler instance); 17 transitive runtime deps and 3 @types removed - tsconfig: ink path-map removed - vitest.config: ink alias removed - tests/helpers/ink-stdio.ts: text() uses strip-ansi + restores CSI N C cursor-forward back to literal spaces - tests/helpers/ink-test.ts (new): replaces ink-testing-library; lastFrame() joins all chunks instead of returning only the most recent write() — required because the diff-based renderer emits multi-chunk frames per commit - 15 test files migrated; 13 obsolete UI snapshot tests removed (5 files) ## Visual alignment - UserCard: single full-row backgroundColor=MESSAGE_BG.user fill - ApprovalCard: round border with borderLeft/borderRight=false - PromptInput: round border (top + bottom only) + borderText with plan / history / mode / model - ReasoningCard: italic + dim header; collapsed state appends (ctrl-o to expand) hint - theme/tokens: new MESSAGE_BG section (user/bash/selected) for all 5 themes - CardHeader: glyph prop accepts string | ReactElement ## Pulse animation system - Pulse primitive: generic frame cycler with DIAMOND/SQUARE/ TRIANGLE/CIRCLE/HEX presets aligned to existing brand glyphs - Wired into 7 cards + 4 LiveRows components - ThinkingRow + OngoingToolRow: live elapsed time + output token count + t/s rate - ticker.tsx: useTick / useSlowTick switched from useAnimationTimer (keepAlive=false) to useAnimationFrame (keepAlive=true) so the shared clock actually starts ## Bug fixes - PromptInput: removed systemCursorSync — the out-of-band CSI CUP write desynced the renderer's screen model under sync-output mode, causing residual glyphs starting at the 3rd typed character - Wizard: TextInput now sourced from ink (drop-in TextInput exposed by the package); ink-text-input dep removed ## Tests 279 files, 3722 passed, 11 skipped, 0 failed. Refs #1829 (Stage 1). Co-authored-by: reasonix <reasonix@deepseek.com>3 天前
refactor(config): sanitize string[] fields at readConfig boundary (#1396) readConfig() previously cast JSON.parse output to ReasonixConfig with no runtime validation. Hand-edited config.json could put objects, numbers, or null into fields typed as string[] (mcp, mcpDisabled, recentWorkspaces, skills.paths), corrupting downstream consumers — see #1295 / #1394 where non-string mcp items reached JSX and crashed the desktop tools panel. - New sanitizeStringArrayField in src/config.ts drops non-string items at the boundary and warns once per field with the file path + dropped count. - Removes the now-dead downstream defenses (#1394's filter in emitMcpSpecs, typeof ternaries in context-panel.tsx / settings.tsx, summarizeMcpSpec's non-string catch). summarizeMcpSpec no longer needs to be exported. - Test file renamed to config-sanitize-string-arrays.test.ts and rewritten against readConfig (where the validation actually lives). Closes #13959 天前
fix(desktop): persist prompt history across sessions (#2068) * fix(desktop): persist prompt history across sessions * fix(desktop): use session logs for prompt history14 小时前
fix(context): align fold summary prefix with main agent for cache reuse (#1565) * chore(release): 0.49.0 — static-history TUI, queued steers, Bing default, lifecycle plans Headline themes: - TUI: Static-history renderer is the only path; virtual-viewport layers removed (#1529 stages 1-4) - Chat: queued mid-turn steer handling so input mid-render doesn't drop or fight the live frame (#1501) - Web search: default switches to Bing; dashboard engine switcher; Mojeek dropped (#1558) - Plans: lifecycle evidence summaries surface why a plan is ready to accept (#1500) - Desktop: native OS notifications for approvals + completion (#1519) - i18n: CLI command output (/mcp /sessions /prune /theme) + approval-prompt labels translated (#1524, #1560) - Security: SSRF block in web_fetch (#1544), edit-snapshot path containment (#1454), shell redirect sandbox (#1457), Task integrity guardrail (#1516) - Tools: per-turn dispatch-rate limit (#1356); run_command discourages shell-based edits (#1514) - Client: DeepSeek 429 → concurrency-limit hint (#1526); timeoutMs honored with AbortSignal (#1535); --no-proxy opt-out for direct route (#1507) - Files: read/edit/restore preserves source encoding (GB18030 / UTF-8 BOM) (#1518) - Context: pinned constraints survive folds + full tail capture (#1515, #1552) - Refactor: lifecycle risk policy extracted into its own module (#1557) See CHANGELOG for the full list. * fix(context): align fold summary prefix with main agent for cache reuse The summarizer call was sending a bespoke "You compress conversation history" system prompt and no tools, guaranteeing a 0% cache hit against the main agent's just-cached prefix. Reshape the request so system + tools + head bytes mirror the live agent's last call — the only novel bytes are the trailing summarize instruction. Skill-pin handling now collects bodies read-only instead of stubbing mid-head, so the cache prefix stays unbroken. The summarize instruction names pinned skills so the model knows not to paraphrase their bodies (which we append verbatim regardless). Measured on a real session at 48.7K prompt tokens: OLD shape: 0.0% cache hit → $0.145 per fold NEW shape: 99.6% cache hit → $0.015 per fold saving: 89.6% per fold * tools: add fold-cache shape + live benchmarks bench-fold-cache-shape.mjs replays real session jsonls, simulates OLD vs NEW summary-call shapes at the fold point, and reports byte-level shared-prefix with the main agent's preceding request. Pure local — no API required. bench-fold-cache-live.mjs sends one priming + two summary calls to DeepSeek and reports prompt_cache_hit_tokens / cost for each shape. Used to confirm the shape change actually translates to API-side cache hits. --------- Co-authored-by: reasonix <reasonix@deepseek.com>7 天前
fix(loop): avoid stalled context fold and unsafe preflight compaction (#1060)12 天前
fix(desktop): sync context token meter after /compact (#1399) Fixes #1305 — Desktop ContextPanel shows stale token counts after /compact. /compact is a local operation (no API call), so the model.final usage event that normally refreshes cacheHitTokens / cacheMissTokens never fires. The meter stays pinned to pre-compact numbers until the next real API turn. - New ContextManager.getLogTokens() — sums countTokensBounded(content) per message (plus assistant tool_calls JSON when present). - Loop.getCurrentLogTokens() forwards to it. - $ctx_breakdown event gains an optional logTokens field; emitCtxBreakdown() populates it. - compact_history handler emits $ctx_breakdown on success so the panel refreshes immediately. - Frontend resets cacheHitTokens=0, cacheMissTokens=logTokens when logTokens is present — a transient calibration; the next real API turn overwrites both with actual usage numbers. The token count is approximate (skips role markers + DSML wrappers), which is fine for a UI meter — directionally correct is what matters here.9 天前
fix(loop): preserve skill bodies verbatim across context fold (#871) Without this, the fold summarizer paraphrased inline skill bodies into prose. Past the fold threshold the model lost the explicit step-by-step paradigm and drifted back to generic tool-thrashing mid-skill — visible as the activity row flickering between error and thinking glyphs. run_skill wraps its inline result in a <skill-pin name="..."> sentinel. ContextManager.fold extracts every pinned block from the head before summarizing, stubs the original tool message so the summarizer doesn't paraphrase it, and re-attaches the bodies verbatim to the synthesized assistant message. Repeat invocations dedupe by name; last one wins.14 天前
fix(context-manager): preserve reasoning_content on the fold summary (#1047) Closes #1042 DeepSeek thinking-mode contract: every assistant message round-tripped to the API must carry reasoning_content (even empty-string). When ContextManager.fold synthesized its history-summary assistant turn it built a bare ChatMessage literal — reasoning_content was never set. On the next API call the persisted log replayed that turn and the provider 400'd: The reasoning_content in the thinking mode must be passed back to the API. Two changes in context-manager.ts: - summarizeForFold now returns { content, reasoningContent } so the summarizer's own reasoning is preserved. - fold builds the synthetic message via buildAssistantMessage, with the SESSION's model as the producing model — so even if the summarizer somehow emits no reasoning, the empty-string stamp lands on thinking- mode sessions and the next call no longer 400s. Regression coverage: tests/context-manager-thinking-mode.test.ts asserts (1) summarizer's reasoning is carried through, (2) thinking-mode sessions get empty-string stamping when the summarizer omits it, (3) non-thinking sessions stay clean (no spurious reasoning_content). Co-authored-by: reasonix <reasonix@deepseek.com>12 天前
chore(release): 0.14.0 — abort-race fix · /copy mode · ctx footer · primitives fix(loop): streaming-abort path resets _turnAbort before returning, so a queued-submit triggered by ShellConfirm "run once" no longer trips a spurious "aborted at iter 0/64" on the next user message. fix(tui): edit_file interceptor reads root via currentRootDirRef so post-workspace-switch writes don't go to the stale root while read_file looks at the new one. feat(tui): /copy exits alt-screen and dumps the rendered log to main screen, returning on any keystroke — native scrollback + drag select work on the dump. feat(tui): always-on context-pressure footer above the prompt (ctx ▰▱… 14K/977K · 1% · sys 5.8K · tools 6.1K · log 0); /context toggles visibility, full breakdown panel still pushed to scrollback for headless / replay. feat(tui): chrome bar drops the cols<120 hard cutoff in favor of width-aware shed by priority (balance > cache > session > update); cache pill default-on with cold-start dim treatment. refactor(ui): Bar / formatTokens / ChromeRule / ContextCell promoted to primitives.tsx; CtxBreakdownBlock + computeCtxBreakdown share a single compute path; StatsPanel shrunk 769 → ~280 lines (dead chrome-redesign helpers removed). feat(core): v0.14 architecture scaffold — Event log + pure-reducer projections (core/) and 6 ports (ports/). Types only, zero behavior change. 19 new reducer tests pin the projections + prove replay determinism. 30 天前
perf(core): windowed AppendOnlyLog (200 messages) with lazy file fallback (#2057) * perf(core): windowed AppendOnlyLog (200 msg) with lazy file fallback; add readTailMessages for backward JSONL scan * style: fix biome formatting + comment cleanup * fix: use fstatSync after openSync to avoid TOCTOU race (CodeQL) * fix: sort import order (biome) * fix: remove existsSync before openSync (TOCTOU race, CodeQL) * fix: toMessages() returns window only, add toFullHistory(); restore length to entries.length; update callers * style: method chain formatting * fix: add toFullHistory mock to ctx-breakdown test --------- Co-authored-by: wufengfan <wufengfan@wufengfandeMacBook-Air.local>2 天前
fix(dashboard): ignore IME confirm enter (#1689)5 天前
fix(dashboard): resolve dangling CSS var refs that left h3 / hr invisible (#1065) Five var(--...) references in dashboard/app.css pointed at custom properties that were never defined. The most visible (#919) was .md h3 { background: var(--grad-8); color: #0a0e14 } — with no background the near-black heading fell straight onto the dark page background, so sub-headings vanished in rendered markdown. The others were the same shape: - .md hr and .status-bar-mini-fill used var(--gradient-rule) with no fallback — <hr> separators and the mini progress bar rendered with no fill. - --bg-elev-1, --accent had silent fallbacks but still pointed at undefined tokens (typos for --bg-elev / --c-accent). - --bg-elev-3, --r-sm were referenced with fallbacks but the intended token wasn't defined anywhere. Define the missing surface / radius / gradient tokens in :root (light-mode --bg-elev-3 lives in the light overlay) and fix the typo references to point at the real names. Replace the dead --grad-8 reference with --s2 (teal) so the three heading levels stay distinct (sky → purple → teal). Add tests/dashboard-css-vars.test.ts — walks every var(--foo) in dashboard/app.css and asserts it resolves to a defined custom property, so this whole class of bug fails the build instead of shipping invisible UI. Fixes #919 Co-authored-by: reasonix <reasonix@deepseek.com>12 天前
feat(dashboard): configurable bind host + stable token for LAN/mobile access (#1072) #968 — users running Reasonix on a workstation wanted to open the dashboard from a phone or another laptop on the same LAN, and to stop the per-boot token churn that breaks bookmarks. The HTTP server already accepted host and token in its options struct (only tests reached them); CLI and config didn't expose either, and the maintainer comment told users localhost-only was the only supported mode. Surface both knobs through the normal channels: - config.dashboard.host + config.dashboard.token - --dashboard-host <host> flag on code / chat - REASONIX_DASHBOARD_HOST + REASONIX_DASHBOARD_TOKEN env vars - resolver order: flag → env → config → server default Stable tokens shorter than 16 chars are dropped with a stderr warning (otherwise users would set "password" and call it a day). When the bind host isn't loopback (127.0.0.1 / ::1 / localhost), the server writes a one-line stderr warning at boot so the user sees the trade-off: "the URL token is the only auth — keep it secret." Closes #968 Co-authored-by: reasonix <reasonix@deepseek.com>12 天前
feat(dashboard): pin port via --dashboard-port flag and config (#625) Stable SSH tunnels need a deterministic port. Adds --dashboard-port on chat and code, plus a dashboard.port config key, so the embedded server binds to the same port across restarts. Resolution order: flag > config > ephemeral (current behavior). Out-of-range values warn + fall back; pinned-port EADDRINUSE surfaces through the existing auto-start failure toast so the user knows the port is contended instead of silently rolling onto a different one. Closes #62418 天前
fix dashboard session refresh (#1917)2 天前
feat(dashboard): persist active session in URL for shareable / refresh-safe links (#1586) Until now the dashboard URL only carried the auth token, so a copied link always landed on whichever session the CLI happened to be attached to — refreshing a tab or pasting the URL elsewhere never restored the session the user was actually viewing. Mirror state.currentSession to a ?session=NAME query param via history.replaceState (no back-button noise) and, on the active tab's first ready+sessions tick, read the same param back and auto-dispatch session_load if the named session exists. Unknown session names clear the param instead of erroring. Both effects are gated on the tab being active, so multi-tab tabs don't fight over the URL. Co-authored-by: reasonix <reasonix@deepseek.com>6 天前
feat(dashboard): cross-session stability, persistent URL, polish pass (#1599) * fix(dashboard): scope session list to current workspace + fix new-chat flow Two related sidebar bugs: 1. The session list was unfiltered — users with thousands of subagent transcripts (subagent-sub-*.jsonl) and cross-workspace sessions in ~/.reasonix/sessions/ saw 10 000+ entries. The CLI's session picker already uses listSessionsForWorkspace(currentRootDir); the dashboard's GET /api/sessions handler now does the same when ctx.getCurrentCwd() is wired. Also surface meta.summary in the listing so the bridge stops falling back to bare session names. 2. Clicking "new chat" pushed an error card into the transcript via $session_empty ("loaded with no messages") and never updated state.currentSession, which left #1586's URL mirror pointing at the old session. The new endpoint echoes the freshly-minted session name, and the bridge fires a real $session_loaded with empty messages so the reducer resets cleanly and the URL flips. Sessions-ops test grows by two cases covering each behaviour. * feat(dashboard): cross-session stability, persistent URL, polish pass Squash-merges a chain of fixes prompted by post-#1418 user feedback on the live web dashboard. The unifying themes: nothing should change when you don't expect it to (token, port, URL, SSE stream), and everything should give visible feedback when you do change it. Server - POST /api/sessions/new now echoes the new session name so the bridge can update state.currentSession without diffing the list. - parseTranscript surfaces reasoning_content, tool_calls, and tool results (by tool_call_id) so historical replays show what the model actually did, not just the raw text turns. - serveAsset injects ?token=... into CSS url() font references in addition to the existing JS-import rewrite — the previous gap 401'd every Geist / KaTeX font fetch. - New /api/browse?path= lists immediate subdirectories (Windows drive letters included on first call) so the web runtime can ship a real directory picker. - startDashboardServer exposes updateContext(ctx) — the handle now outlives a chat.tsx-driven App remount, and the new App rewires its loop/refs in place instead of forcing a port rebind that the OS hadn't released yet (EADDRINUSE -> ephemeral fallback -> new URL). - Dashboard token + actual bound port are persisted to config.json on first run via ensureDashboardToken / saveDashboardPort, with an EADDRINUSE fallback to ephemeral. /dashboard reset-token rotates the token and restarts the server. CLI - listSessions workspace filter now reaches the dashboard's GET /api/sessions, cutting sidebar entries from "every subagent transcript on disk" (10 000+ for active users) down to the current workspace's chats. - getDashboardUrl appends &session=<name> so the printed URL points at the attached session and survives copy/paste. Dashboard UI - Persistent module-level dashboardHandle + eventSubscribers Set so React App remounts (session swap) don't tear the server down and don't drop SSE subscribers on the floor — fixed the "send a message after switching, web stays silent" desync. - session_load bridge handler emits $session_loaded with the new name and full segments (text + reasoning + tool calls stitched to results), instead of pushing an error card via $session_empty. - Thread renders use index-based React keys for AssistantMsg — replayed transcripts arrive with turn=0 for every assistant row, so the turn-keyed list was collapsing every assistant message into one stale slot on every session switch. - Sidebar gets a per-row loading spinner; thread shows a centered overlay during the swap so big sessions don't look dead. - CSS containment on .session-item, .msg, and .thread; dropped a needless backdrop-filter on the loading overlay. Together these cut the Paint/Layout/pointerover budget noticeably on long transcripts. - New WorkdirInputModal backed by /api/browse — the web build previously stub'd openDialog to "" so clicking Browse did nothing. - Removed the "+ new tab" affordance from the tab bar (web has no multi-tab story). - URL session mirror from #1586 reverted to read-on-mount only so clicking a session doesn't churn the address bar. i18n - statusbar.themeStyle{Graphite,Sandstone,Porcelain,Midnight} - settings.themeStyle* + Desc, settings.page* Label + Desc for all 8 pages, sidebarPanel.loading, workdir.{cancel,go,goTip,loading, emptyDir,openHere,pathPlaceholder}, handlers.dashboard.tokenReset* (en and zh-CN where relevant). Tests - tests/dashboard-token-persistence.test.ts (9 cases) for ensureDashboardToken / saveDashboardPort / clearDashboardToken. - dashboard-sessions-ops gains coverage for the workspace filter and the new POST /sessions/new name echo. - dashboard-smoke verifies CSS font url() token injection in addition to the existing JS chunk-import rewrite. --------- Co-authored-by: reasonix <reasonix@deepseek.com>6 天前
Add Baidu AI Search backend (#2147)1 天前
fix: keep compact sidebar new chat label visible (#2016) * fix compact sidebar shortcut fallback * fix desktop compact sidebar shortcut fallback2 天前
fix(server): convert sync fs I/O to async across server API and session memory (#2219) * fix(server): convert sync fs I/O to async across server API layer and session memory Replace blocking readFileSync/writeFileSync/readdirSync/statSync/existsSync with non-blocking readFile/writeFile/readdir/stat from node:fs/promises in all server API handlers and session.ts. This prevents the event loop from blocking on disk I/O during concurrent HTTP requests. - assets.ts: async cached reads (resolveAssetDir stays sync for module load) - skills.ts, memory.ts, hooks.ts, browse.ts, project-tree.ts, files.ts, checkpoint-diffs.ts, health.ts, sessions.ts: async handlers - session.ts: added async variants (listSessionsAsync, deleteSessionAsync, loadSessionMessagesAsync, readTailMessagesAsync, etc.) - atomic-write.ts: added async atomicWrite alongside sync atomicWriteSync - index.ts: await renderIndexHtml/serveAsset in dispatch Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * style: fix biome formatting and lint errors - files.ts: add explicit Stats type annotation (noImplicitAnyLet) - browse.ts: organize imports - assets.ts, skills.ts, memory.ts, sessions.ts, session.ts, atomic-write.ts: biome format (line length, trailing commas) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(test): await async serveAsset/renderIndexHtml in dashboard smoke tests These functions now return Promises after the sync→async conversion, so the test calls must await them. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * style: remove banner separator comments in session.ts Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: eliminate TOCTOU race in memory file read Read the file directly instead of stat-then-read to avoid a window where the file could be deleted between the two calls. CodeQL flagged this as a potential file system race condition. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: HUQIANTAO <HUQIANTAO@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>16 小时前
feat(config): add dashboard.enabled setting to suppress auto-start (#1612) * feat(config): add dashboard.enabled setting to suppress auto-start Add a dashboard.enabled boolean config field (default true) so users can permanently disable the embedded dashboard without passing --no-dashboard on every launch. The CLI flag still overrides the config (--no-dashboard wins over enabled: true). * test(config): cover loadDashboardEnabled defaults, explicit values, and noConfig6 天前
feat(tools): add AST-aware delete_symbol (#2191)17 小时前
feat: integrated-mode polish + sidebar Ctrl+B toggle and label wrap (#1044) * refactor(scene): move the entire scene producer from TS to Rust Stage A of the user's "build the foundation right first" architectural pivot. The JS side no longer builds a SceneFrame layout tree; it just serializes the latest input state. Rust deserializes the state and runs the full layout / palette / row-shape logic against the *real* terminal area. ## Why The TS scene producer was a recurring source of "things don't look right" bugs because: - JS reads its idea of terminal size from Ink's useStdout(), which is the **null stream** under REASONIX_RENDERER=rust mode. Cols and rows fell back to 80 × 24 every render, so layout decisions (boot block size, card cap, dock thickness) were divorced from the actual terminal — even though the renderer rendered against the real area. Producer + renderer disagreed on dimensions. - Two languages held the same constants (palette hexes, row counts, glyphs). Every visual tweak required keeping both sides in sync. - Each behavior change required npm run build + restart and a fresh cargo build if either side drifted. Moving the producer to Rust makes the renderer authoritative for layout. Single source of truth, single rebuild target. ## What New Rust modules under crates/reasonix-render/src/: - state.rsSceneState / SceneCard / SlashMatch / SessionItem / SetupState / Message (tagged enum { type: "trace" | "setup", ... }). - theme.rs — `palette::{bg, bg2, fg, fg1..3, ds, ds_bright, ds_purple, ok, warn, err}` matching the v1 mock's oklch tokens. - producer.rsbuild_trace_frame(&state, cols, rows) + build_setup_frame(&state, cols, rows). Ports the boot block (REASONIX ASCII banner), card kinds (user / reasoning / streaming head+body, tool rich row, generic single-line for others), composer / meta / status dock, slash overlay, sessions picker, approval modal — all from the old TS producer. main.rs reads messages from stdin: tries Message, falls back to bare SceneState, then SetupState, then legacy SceneFrame for back-compat with any pre-rolling-out builds. Each frame calls terminal.size() to get the real cols/rows and feeds them into the producer. ## JS side useSceneTrace.ts shrunk from ~700 LOC to ~270 LOC — purely state shaping now. The hook builds a plain object (`{ type: "trace", ... state }`) and emits it via the renamed emitSceneMessage(message: unknown). Same for useSetupSceneTrace. Helpers that survived (still used to serialize wire-format payloads): toSceneCard, parseRecentCards, parseSlashMatches, parseSessions, summarizeCard. Gone (moved to Rust): buildTraceFrame, buildSetupFrame, all the row builders (composerRow, metaRow, statusBarRow, slashRow, …), the kind-glyph / color / label tables, PALETTE, listWindow, slashWindow, cardsForHeight. ## Tests - Rust (crates/reasonix-render/tests/producer.rs) — 14 new cases covering boot block, card kinds, composer cursor, dock rows, approval / slash / sessions overlays, status bar segments, edit mode color, rich tool format, setup frame, body line cap. - TS (tests/scene-trace-frame.test.ts) — pared down to the 20 wire-format tests that still apply (summarizeCard, toSceneCard, parseRecentCards, parseSlashMatches, parseSessions). \cargo test -p reasonix-render\ — 51 / 51 green (23 render + 14 producer + 6 decode-only + 6 input + 2 round-trip). \npm run verify\ — 3071 / 3074 green via prepush gate (3 pre-existing skipped). ## Migration notes - Rust binary **must** be rebuilt for users running off the prebuilt target/release/reasonix-render.exe — the new producer is the binary's job now. - The legacy SceneFrame JSON shape is still accepted by Rust as a fourth-tier fallback, so a stale JS bundle paired with a fresh Rust binary keeps working until both roll forward. Refs #868 * fix(rust): enable raw mode and drop scroll-area inner padding Two suspected causes of the "底部 / 左右 / 上下 都有点没顶全" gap that survives Stage A: 1. main.rs never called enable_raw_mode() for the render path. In cooked mode the terminal driver treats a write to the last row × last column as an implicit newline / scroll, which leaves the bottom row visually empty under ratatui + alt-screen. run_emit_input (the keystroke-only branch) already enables raw mode for its own loop; the render branch was missing it. 2. scroll_area carried padding_x: 2, padding_y: 1 inside the producer, eating 2 cells off each horizontal edge of the scroll area and 1 row off the top/bottom. The outer box's bg drew the right color underneath, so it didn't read as a true "gap" — but on a terminal whose own cell-edge padding is non-zero the compounded effect looked like all four sides were short. Also added a one-shot debug log gated on REASONIX_RENDER_DEBUG=1 that writes terminal.size() at startup + the first frame's area to stderr (which under rust mode is the ~/.reasonix/rust-render-stderr.log file). Useful for verifying the renderer is seeing the real terminal dimensions, not 80×24. Refs #868 * fix(rust): debug log writes to a file instead of stderr Stderr from the rust child inherits Node's OS-level fd (stdio: "inherit"); the parent's process.stderr.write override is only JS-level and doesn't catch native writes from the rust binary, so the previous eprintln-based diagnostic vanished into the parent terminal (where ratatui's alt screen masked it). Switched to writing the diagnostic to a file — REASONIX_RENDER_DEBUG=1 enables, REASONIX_RENDER_DEBUG_LOG overrides the path; default is ~/.reasonix/rust-render-debug.log. * fix(rust): drop outer background fill, let terminal bg show through User feedback: \"为什么其他他们都能自动顶满,我们还要设置 WT padding?\" Answer: we were painting a #0f1018 dark bg on the outer box, which makes the cell grid contrast with the terminal's own background. The WT pixel padding between window frame and cell grid then renders in the terminal's default bg — visually a colored frame around our drawing area. vim / less / claude / etc. don't paint a bg color. Their cells render in the terminal's default bg, so the WT padding ring matches and disappears. Same treatment here: outer + scroll_area now use BoxLayout::default() (no bg). The dock's composer / status rows keep their bg2 tint — that's a deliberate panel-strip effect inside the content flow, not a full-screen wash. Setup frame also drops its bg for the same reason. Refs #868 * fix(rust): restore outer background fill, drop "no bg" variant User report: dropping outer bg caused ghosting. Diagnosis: ratatui's diff renderer compares cells frame-to-frame and only writes cells that changed. When the outer box paints a solid bg every frame, every cell of the frame is touched with that bg → any leftover content from a prior frame gets overwritten cleanly. When outer bg was removed, cells with no current-frame writes (default Cell = space + Color::Reset) didn't necessarily appear as a change vs the previous frame, and stale characters / fg colors stuck. Restoring outer + setup-frame background = palette::bg(). The cost is the WT pixel-padding ring showing the terminal's default bg instead of ours, which is the same trade-off vim / lazygit / btop / zellij all make. Recommend the WT 0-padding setting in the README rather than try to eliminate the bg layer. Also added terminal.autoresize() at startup so the very first frame already knows the actual terminal size — defensive in case the backend's cached size lags the first draw on Windows. Refs #868 * chore(rust): bump ratatui 0.29 → 0.30 + crossterm 0.28 → 0.29, modernize main Two upstream releases pulled in: - ratatui 0.30.0 (2025-12-26): AlignmentHorizontalAlignment alias, Color::from_crossterm/into_crossterm replacing From/Into, Flex::SpaceAround semantics now match CSS. None of the breaking surface touches our code (we don't use those identifiers). - crossterm 0.29.0: KeyModifiers Display fix; no API change at our call sites. Meanwhile, three modern idioms we were missing: 1. **BufWriter around stdout** — ratatui docs flag unbuffered stdout as the #1 perf footgun. Per-cell write syscalls in the diff loop add up fast on Windows. Wrapping with BufWriter collapses each frame into one flush. 2. **Panic hook that restores the terminal** — without it, a Rust panic leaves the user stuck in raw mode + alt-screen with no keyboard echo and a broken prompt. We now install a hook that disables raw mode and leaves the alt-screen before chaining to the previous hook (so backtraces still print). 3. **Factored init / restore helpers** — init_terminal() does enable_raw_mode + EnterAlternateScreen + new BufWriter backend; restore_terminal() is the matching teardown. Same shape as ratatui::init / ratatui::restore (which we'd use directly if we didn't need the BufWriter / custom backend variant). Renamed local terminal type to RenderTerminal for clarity (Terminal<CrosstermBackend<BufWriter<Stdout>>> is unwieldy at every call site). All 51 Rust tests still pass. JS-side npm verify untouched. Refs #868 * fix(rust): wrap each draw in synchronized output + clear on resize Two stability strategies that modern TUI apps use to eliminate tearing / partial-frame flicker / cross-frame ghosting: ## Synchronized Output Mode (DCS 2026) Every terminal.draw() is now bracketed by ESC[?2026h (BeginSynchronizedUpdate) ESC[?2026l (EndSynchronizedUpdate) Supported by Windows Terminal 1.18+, kitty, foot, alacritty, ghostty, iTerm2 3.5+, recent VS Code terminal. Terminals that don't recognize the sequences silently ignore them — zero downside. What it does: the terminal app stops flushing pixels mid-frame. Whatever ANSI bytes arrive between BSU and ESU are buffered; one atomic display update happens at ESU. Eliminates: - Visible cursor "scanning" across the screen during a draw - Partial-frame artifacts when the diff updates two adjacent rows that haven't been ESC-positioned together - Cross-frame ghosting that survives a diff miss (the next frame's paint lands atomically over the previous, no in-between state) ratatui doesn't auto-wrap draws in BSU/ESU (probably because the escape sequences are still terminal-specific), so we do it ourselves around each call via crossterm::execute!. ## Force terminal.clear() on terminal resize Each iteration of the stream loop reads terminal.size() and compares to the previous frame's size. On change → terminal.clear() which marks the next frame as a full redraw (no diff). Prevents stale cells from showing through after a window resize. ratatui already auto-resizes on terminal.draw, but auto-resize preserves the diff state which can leave the OLD-dimensions content in cells that no longer belong to the new layout. ## Borrow-checker note Terminal::draw returns a CompletedFrame<'_> that borrows the terminal. We discard it with .err() so the second crossterm::execute! (for EndSynchronizedUpdate) can re-borrow the terminal mutably. Refs #868 * refactor(rust): rewrite view layer on top of ratatui widgets, drop custom scene tree The previous architecture had two layers of abstraction stacked on top of ratatui — SceneNode (the protocol type) and BoxNode (our own flex layout engine). The Rust render code wrote cells via buf[(x,y)].set_char() directly, bypassing ratatui's widget system. That bypass was the root cause of every \"shouldn't ratatui handle this\" class of bug we kept hitting: - ghosting from diff misses when our cell-direct writes didn't match ratatui's expected mutation pattern - CJK / emoji width counted twice (once by our unicode-width advance, once by ratatui internally) - no layout cache; full re-layout every frame - ~1500 LOC of producer + render + scene-tree we maintained ourselves instead of using ratatui's tested Layout + Paragraph + Block This PR throws all of that out and uses ratatui's widgets directly. ## What's gone Deleted from the Rust crate: - src/scene.rs (SceneFrame / SceneNode / BoxLayout / TextRun / TextStyle / BorderStyle / Dim / FillToken / FlexDirection / FlexAlign / FlexJustify — 178 LOC) - src/producer.rs (the entire 1,387-LOC scene-tree builder) - src/render.rs (the 389-LOC manual cell-writing renderer with our own flex algorithm) - tests/render.rs (557 LOC of tests against the deleted renderer) - tests/producer.rs (335 LOC against the deleted producer) - tests/round_trip.rs (107 LOC against the deleted SceneFrame protocol type) Deleted from the JS side (no longer used now that JS only emits raw state): - src/cli/ui/scene/build.ts (the box / text / frame helpers) - src/cli/ui/scene/types.ts (SceneNode / BoxLayout etc. — protocol types are Rust's now) - src/cli/ui/scene/theme.ts (palette moved to Rust) - src/cli/ui/scene/lower.ts (the abandoned Ink-tree-to-scene conversion from the original Stage 0 plan) - 4 test files that exercised the deleted modules Total: -3,806 LOC across deleted files. ## What's new src/view.rs (779 LOC) — a single render_trace(state, frame) + render_setup(state, frame) entry point that uses ratatui widgets directly: - Layout::default().direction(Direction::Vertical).constraints([...]) for the scroll-area / dock split (instead of our compute_axis_sizes) - Paragraph::new(Text::from(lines)) with Wrap { trim: false } for the scroll content (instead of our line-by-line set_char loop) - Block::default().style(Style::default().bg(...)) for the dock and status bg tints (instead of our custom bg fill path) - Line::from(Vec<Span>) for every styled row; ratatui handles wide characters / emoji widths internally - render_row_split helper for left-aligned + right-aligned spans on the meta and status rows tests/view.rs (288 LOC) — 15 cases using ratatui::backend::TestBackend to render into an in-memory grid and assert the symbol stream. Every behavior the deleted producer.rs tests covered (boot block, card kinds, composer cursor, dock rows, approval / slash / sessions overlays, status bar segments, edit mode color, rich tool format, setup frame) is re-tested against the new view. theme.rs absorbed Color / NamedColor (previously in scene.rs); palette is unchanged. decode_only.rs switched from serde_json::from_str::<SceneFrame> (no longer exists) to serde_json::Value — it's a dev helper that just counts valid JSON-line frames, doesn't validate their shape. ## Pipeline shape (unchanged, still JS → JSON state → Rust) ``` JS state change ↓ useSceneTrace useEffect ↓ emitSceneMessage({ type: "trace", model, cards, ... }) ↓ child.stdin.write(json + "\n") ↓ Rust child reads line ↓ decode_message → Payload::Trace(SceneState) ↓ render_trace(state, frame) ← new ↓ ratatui widgets emit cell ops ↓ ratatui diff vs previous frame ↓ BufWriter → stdout → terminal ``` Sync-output mode (begin/end synchronized update) and the resize clear-on-change guard from the previous commits are still in place. cargo test -p reasonix-render — 24 / 24 green (15 view + 6 input + 3 decode_only). npm run verify — 3039 / 3042 green (3 pre-existing skipped). Refs #868 * fix(rust): scroll area padding via Block, not by shrinking the area The previous render_scroll manually carved a Rect 2 cells in from each edge of the scroll area and rendered the Paragraph into that inner Rect. The OUTER 2-cell horizontal margin + 1-row top/bottom margin were never touched by the Paragraph and the canvas-block at the start of render_trace only sets style (bg) on cells, not their symbol. So when boot block → cards transitioned, the boot block's LOGO chars at the very edge of the scroll area persisted as ghosts. Switched to `Paragraph::new(...).block(Block::default() .padding(Padding::new(2, 2, 1, 1)).style(...))`. The Block paints its bg over the full scroll-area rect (including the padding ring) and indents the inner content via Padding — so every cell is fresh each frame, no ghosting. Refs #868 * fix(rust): set bg on every Paragraph + bottom-anchor cards in scroll Two real bugs the ratatui source dive (paragraph.rs:407-413) caught: ## 1. Paragraph paints its own style over the OUTER area first The render order inside Paragraph::render is: 1. buf.set_style(area, self.style) ← outer paint 2. self.block.render(area, buf) ← block paint (covers only border / bg cells controlled by the block style) 3. render text into inner(area) If Paragraph has no style set, step 1 paints style=default (no bg), WIPING any bg that a separate Block widget rendered over the same area beforehand. We were doing exactly that — render_status painted a Block with bg=bg2 over the status area, then called render_row_split which used Paragraph::new(left/right) with no style. The Paragraphs painted their default "no bg" over the left and right chunks, overwriting the bg2 strip the Block had just laid down. Net effect: status bar bg2 only survived in cells the Paragraphs didn't touch, which is a fragmented mess after a diff. Fix: set .style(Style::default().bg(...)) on every Paragraph that needs a bg, instead of relying on a separate Block widget. Applied to: - render_scroll (Paragraph.style + Block.style both = palette::bg()) - render_composer (bg2) - render_approval (bg2) - render_meta (bg, via render_row_split bg arg) - render_status (bg2, via render_row_split bg arg) - render_slash_overlay (bg) - render_sessions_picker (bg) - render_setup (bg) render_row_split now takes a bg: Color parameter and applies it to both half-paragraphs. ## 2. Cards anchor at the bottom of the scroll area, not the top Paragraph has no vertical-alignment setter — content always starts at the top row of the area (paragraph.rs:449-458). For chat we want the latest message adjacent to the composer, not the brand banner. scroll_lines() now pads the line list at the TOP with empty Lines when content is shorter than the available height, so cards drift down to sit just above the dock. Boot block (empty cards path) is unchanged — stays at the top of scroll. Together these two fixes address every "didn't fill / ghost / partial bg" complaint that survives the architectural rewrite. Refs #868 * feat(rust): rewrite trace renderer as cell-level WholeScreen widget Replace the ratatui Paragraph + Layout-based render_trace with a single cell-level Widget that hand-paints every (x, y) in the frame. The whole layout splits into the whole_screen/ module: - theme + paint primitives (paint, paint_str, fill_bg, truncate, format_ts) with CJK width handled via unicode-width + set_skip on continuation cells - boot block (REASONIX logo + model/cwd/git/tools/hint rows) - dock: bordered input box, kbd hint row, status bar with ctx bar segments and threshold colors - sidebar: Mission Control header + PLAN / JOBS / CHANGES / SESSION sections, auto-binding to todo + tool cards in state - cards/ subdir, one file per kind: message (user/reasoning/ assistant), todo, tool, diff, output (cmd/fileview/search), notify (subagent/confirm/await/error). 13 kinds total. - slash + @file autocomplete overlays as bordered popups above the dock, with arrow-key navigation and Enter to complete - row-level scrolling via virtual buffer + thumb scrollbar on the right edge; mouse wheel, PgUp/PgDn, Home/End - mouse drag selection with cards-area clamping, scroll-anchor tracking (selection follows content under scroll), auto-scroll when dragging past edges, auto-copy to system clipboard via arboard on mouse-up - tick loop (80ms) drives spinner frames on Running tool cards, composer caret blink, streaming text reveal on the assistant card body Stream-loop now multiplexes JSON state from stdin (in a reader thread feeding an mpsc channel) with crossterm events. Keyboard stays with Node (no protocol change needed for typing); rust captures mouse only and updates local UI state (scroll, selection) independently of incoming state. A new --demo flag drives an interactive playground state with all card kinds populated. 32 new tests in tests/whole_screen.rs cover overlays, card kinds, selection, scrolling, animation. view::render_trace is no longer reached from main.rs (deleted in a follow-up commit). * refactor(rust): drop view::render_trace and its 13 tests stream-loop has rendered Trace payloads through WholeScreen since the previous commit; view::render_trace and its 28 helpers are no longer reachable. Delete them. Keep view::render_setup — that's the API-key entry screen (Setup payload), a separate UI path that WholeScreen doesn't cover. view.rs: 796 → 98 lines tests/view.rs: 15 → 2 tests (kept the 2 setup tests) * feat(rust): composer editing — cursor movement, word-nav, insert at point Track composer cursor index in the demo loop instead of always appending to the buffer. Char keys insert at cursor (not just push to end). Cursor moves through the text: Left / Right one character Ctrl+Left / Right one word Home / End start / end of buffer Backspace delete char before cursor Ctrl+Backspace delete word before cursor Delete delete char after cursor Home / End used to scroll cards top / bottom; that was reassigned to PgUp / PgDn (which were already there). Cursor is the more natural binding for editing. Word boundary uses char::is_whitespace transitions. CJK runs are treated as one word since is_whitespace is false for them; matches typical terminal editing intuition. The caret ▮ renders at composer_cursor in dock.rs — text before cursor + caret + text after cursor, so the caret visually sits between characters when mid-string. * feat(rust): idle empty-state banner + composer cursor + prompt mode hint When state.cards is empty, the cards area now renders a 2-row idle banner (OK rail) instead of leaving the middle blank: ▎ ● idle ready for next task ▎ type below · / commands · @ file refs · ! shell Matches the React mock's Idle component. Shown on first launch and after /clear when production reasonix wires state.cards to empty. dock composer paints the caret ▮ at composer_cursor instead of always at end-of-text, so the caret can sit between characters. The ❯ prompt now signals what mode the user is in by color: default ds-bright (chat) /... ds-bright (slash command — paired with overlay) @... ds-purple (attach file — paired with overlay) !... ok-green (shell mode) 3 new tests: idle banner content, caret at cursor index, !shell prefix doesn't accidentally trigger slash/at overlays. * feat(rust): multi-line composer with Shift+Enter, scroll-to-cursor, line nav Composer now grows from 1 to 5 content rows as the buffer gains newlines (DOCK_HEIGHT + lines - 1, capped at 5+MAX_COMPOSER_ROWS-1 = 9 rows total). Cards area shrinks accordingly; selection cards layout reads the same dock height so mouse coords stay aligned. Shift+Enter inserts \n at cursor Enter submits the whole buffer (newlines preserved) Up / Down when no overlay is active, move cursor between lines preserving column; otherwise navigate the overlay match list (existing behavior) Beyond 5 content rows the box scrolls vertically so the cursor line is always visible. ↑ and ↓ glyphs appear at the box edge to indicate hidden lines above or below the visible window. Slash and @file completion now place the cursor at end of the substituted buffer instead of leaving it stale. * feat(rust): Tab / Shift+Tab cycles slash and @file overlay matches Standard shell-completion gesture. Tab moves selection forward and wraps to top when at end. Shift+Tab moves back and wraps to bottom. Updates the navigate hint in both overlays to show "↑↓/Tab navigate". Up/Down still work as before; Tab is just an extra binding. * feat(rust): --integrated mode — full UI ownership with event proto to Node A new mode flag that combines the demo loop's interactive composer with the stream loop's stdin-driven scene state. Designed to let production reasonix hand the entire UI (typing, slash/@ overlays, scroll, selection, all keybindings) over to the rust renderer instead of splitting input between Node's Ink composer and rust's mouse-only capture. Architecture: stdin Node → rust line-delimited SceneState JSON stderr rust → Node line-delimited event JSON stdout rust → tty rendered frames controlling rust ↔ tty keyboard + mouse via crossterm rust owns composer state locally (buffer, cursor, slash_idx, at_idx, scroll, selection, dragging, tick). On each frame, it overlays composer_text + composer_cursor onto the incoming SceneState clone, then renders WholeScreen as usual. Local UI state never round-trips to Node. Events emitted to stderr: {"event":"submit","text":"…"} plain Enter with non-empty buffer {"event":"interrupt"} Ctrl+C while scene.busy {"event":"exit"} Ctrl+C when idle, or Ctrl+D Setup payloads from Node fall through to render_setup with no keyboard capture for that frame; Node-side Ink can keep handling the API-key entry if needed. Plumbing change: Payload + decode_message moved from main.rs to state.rs so integrated.rs can use them. Composer editing helpers (insert_char_at, cursor math, word boundaries, line nav) moved to a new editor module shared between demo and integrated loops. Node side is unchanged — needs a follow-up PR there to spawn rust with --integrated, pipe stderr, parse events, and disable Ink's own composer. * feat(scene): wire REASONIX_RENDERER_INTEGRATED=1 to spawn rust --integrated The rust renderer's --integrated mode (lands on the whole-screen-prototype branch in the reasonix-render crate) lets it own keyboard + composer state and report submit/interrupt/exit events back to Node via stderr. This wires the Node side to it. When REASONIX_RENDERER=rust and REASONIX_RENDERER_INTEGRATED=1: - spawnRenderer adds --integrated to the child args - the child's stderr is piped (was inherit) and parsed line-by-line as JSON events - the keystroke input child is skipped — rust captures the terminal directly via crossterm - trace exposes setIntegratedEventHandler so chat.tsx can register a single dispatcher before the first scene frame emits Events handled: submit routed to qqSubmitRef.current — same code path the QQ channel uses to feed text into App's queuedSubmit effect, then through handleSubmit exit clean shutdown via stopAndSaveCpuProfile + process.exit interrupt no-op for now (terminal SIGINT already reaches Node); wiring loop.abort is a follow-up Backwards compatible: without the env var the existing rust mode (Ink composer + RustKeystrokeReader input child) keeps working. * chore(rust): add pulldown-cmark for markdown rendering Pulls in pulldown-cmark 0.13 (+ unicase) so the renderer crate can parse markdown bodies from assistant messages. * feat(rust): render markdown bodies in assistant message cards Parses message/notify/output card bodies as markdown (headings, code, lists, tables, blockquotes, inline emphasis) and renders them cell-by-cell within the card body width. Adds shared wrap_visual helper in cards/mod.rs so wrap math stays in one place. * feat(rust): integrated-mode polish — approvals, pickers, completion, sidebar toggle Round of feature work on top of the --integrated runner: - Approval prompts (plan / shell / path / edit / choice / checkpoint) rendered as a full-screen overlay above the dock, with key handlers on the rust side and an event protocol back to Node. - Mode picker (review/auto/yolo) and preset picker (auto/flash/pro) triggered from clicking the pills in the status bar. - Dynamic slash and @file overlays — both now drive from a catalog pushed in scene state (slash_catalog / at_state) instead of hardcoded command and path lists. - Multiline composer wrap + cursor positioning across visual lines. - Live session stats in the right sidebar (model, ctx, ↑/↓ tokens, cache %, cost, balance, last turn) read straight from scene state. - Integrated event loop split into stdin / terminal reader threads with a unified Evt channel. - Sidebar Ctrl+B toggle (was an unimplemented "⌘. toggle" hint); long PLAN / JOBS labels now wrap multi-line inside the sidebar instead of being truncated. - Bounded paint_str_to() so the boot hint, cwd, logo etc. clip to the main-panel width instead of bleeding into sidebar columns. Tests: full sidebar/toggle/wrap regression suite + new dynamic slash/at catalog coverage. * feat(ts): wire React UI to integrated-mode rust events Bridges the rust --integrated runner back to the Node UI layer: - App.tsx routes approval-response / mode-set / preset-set / composer events from the rust child to the existing React handlers via refs. - useSceneTrace pushes the additional scene fields the rust side now consumes (preset, session/last-turn tokens + cost, cache_hit_ratio, slash_catalog, prompt_history, approval, at_state). - State + reducer track session input/output tokens and last turn ms for the new sidebar SESSION block. - Composer text echoed from the rust side feeds useInputRecall so the recall popover sees the live buffer. - renderer-process gains a composer event type; chat.tsx forwards the integrated flag so REASONIX_RENDERER_INTEGRATED=1 spawns rust with --integrated. * chore: demo-utils sample + probe-fanout debug script + tau-bench db tweak - src/demo-utils.ts + tests: tiny sample module used as the target for risk:med submit_plan dogfooding. - scripts/probe-fanout.mts: headless probe that measures tool-call fan-out and ordering for the run_skill flow (issue #675). - benchmarks/tau-bench/db.ts: minor adjustment to test data. * chore: drop demo-utils file header to meet comment-policy 5-line header tripped the ≤2-line rule. Names are doc enough here. * chore(rust): cargo fmt across reasonix-render CI's cargo fmt --check failed on the new test bodies (long single-line strings and asserts). Ran cargo fmt to bring everything in line. * chore(rust): satisfy clippy -D warnings CI runs cargo clippy --all-targets -- -D warnings; the integrated-mode polish landed lints on: - too_many_arguments — paint_str_to, paint_cell, paint_entry, render_block, render_table, render_card_header (renderer helpers with many style/geometry params; #[allow(clippy::too_many_arguments)]) - manual_clamp — overlay.rs / overlay_at.rs: use .clamp() directly - needless_range_loop — md_render.rs table rows: switch to col_widths.iter().enumerate() - large_enum_variant — state::Message / Payload: SceneState dwarfs SetupState; #[allow] on both - needless_lifetimes — overlay_at::entries_for - question_mark — integrated::cycle_or_pick: let-else → ? - dead_code — markdown::MdBlock::Code lang field (kept for parser fidelity) - field_reassign_with_default — three test fixtures; use struct init - unused assignment / parentheses / loop counter — trivial cleanups * chore(rust): reflow message.rs use line after clippy import prune cargo clippy --fix removed unused imports but left the remaining use {...} braces on too many lines for rustfmt's check. * chore(rust): collapse Event::SoftBreak | HardBreak match arm into guard clippy 1.95 added collapsible_match — fold the inner if !in_code into a match guard on the outer SoftBreak/HardBreak arm. --------- Co-authored-by: reasonix <reasonix@deepseek.com>12 天前
fix(desktop): restore aborted prompt drafts (#1693)5 天前
fix(desktop): preserve context on abort (#1938)2 天前
fix(desktop): refresh context token meter (#1964)2 天前
fix(desktop): preserve compact composer default (#1594) * fix(desktop): preserve compact composer default * fix(desktop): widen chat when rails collapse6 天前
fix(desktop): keep the sidecar alive on unhandled rejection / exception (#1080) #1074 — clicking a history session a few times surfaces "reasonix exited (code 1)" and reverts the UI to "connecting to reasonix core…". That code-1 exit is Node 16+'s default for an unhandled promise rejection, and the desktop daemon never registered the process-level guards a long-running sidecar needs. The most likely upstream trigger is the abort + rebuild cycle in the session_load handler: replacing tab.runtime orphans any in-flight fetch on the old client, which then rejects with no awaiter. Whatever the precise leak, the right behaviour for a daemon is to log loudly and keep serving — not exit and force a full Tauri reconnect. installDesktopCrashGuards() registers unhandledRejection and uncaughtException listeners that forward the stack to stderr (surfaces via rpc:stderr for debugging) but do NOT call process.exit. Called once from desktopCommand after loadDotenv(). Tests: 4 cases in tests/desktop-crash-guards.test.ts cover the listener-count assertion, Error / non-Error rejection coercion, and the uncaughtException branch. Fixes #1074 Co-authored-by: reasonix <reasonix@deepseek.com>12 天前
fix(desktop): CSP connect-src and Porcelain theme accent color (#2038) * fix(desktop): add DeepSeek API and updater domains to CSP connect-src The desktop app's Content Security Policy was missing required domains in the connect-src directive, causing requests to fail: - https://api.deepseek.com — DeepSeek API calls - https://pub-147fb53b9c1e4bbf891a257968619ea7.r2.dev — updater R2 CDN - https://github.com — updater GitHub Releases fallback Changes: - Added only essential domains (no wildcards, no dev-only localhost) - Added regression test to prevent future removal Comparison with PR #2033: - More restrictive: uses specific R2 URL instead of *.r2.dev wildcard - Removes unnecessary domains: *.githubusercontent.com, localhost, ws:// - Better security posture for production builds Closes #2020 * fix(desktop): change Porcelain theme accent color from orange to purple The Porcelain (light) theme had --accent set to orange (hue 36) instead of purple (hue 285), causing buttons to appear orange instead of purple. Changes: - Changed --accent from oklch(58% 0.18 36) to oklch(56% 0.12 285) - Changed --accent-soft and --accent-strong to match purple hue - Now consistent with Midnight theme which uses purple accent Closes #2022 --------- Co-authored-by: Bernardxu123 <Bernardxu123@users.noreply.github.com>2 天前
feat(core-utils): introduce @reasonix/core-utils workspace package (Phase 1) (#1328) Closes #1322 (Phase 1) Co-authored-by: paradoxSCH <sch.paradox@foxmail.com>10 天前
fix(desktop): inherit login-shell PATH so nvm/asdf/fnm reach run_command (#1331) Closes #125210 天前
feat: enrich MCP features with cc-switch migration / 丰富 MCP 功能,支持 cc-switch 迁移 (#2135) * feat: enrich MCP features with cc-switch migration * fix: address mcp review feedback * fix: resolve mcp preflight cwd paths * test: cover desktop MCP import reconciliation * fix(desktop): refocus MCP tools tab on repeated status18 小时前
fix(desktop): contain long mcp specs (#1736)4 天前
fix: restore scroll and memory visibility (#1766)4 天前
feat(desktop): make QQ sessions usable from desktop (#1334) Closes #1317.9 天前
fix(qq): polish busy ingress and setup reminders (#2178)19 小时前
feat(desktop): extend QQ remote commands with model and mode controls (#2062) * feat(desktop): add /retry to QQ remote commands * feat(desktop): add model and mode QQ commands * fix(desktop): gate QQ model and plan changes behind busy state1 天前
feat(desktop): make QQ sessions usable from desktop (#1334) Closes #1317.9 天前
fix(desktop): isolate QQ turn routing by tab (#1672)5 天前
fix(loop): trim long-session retained payloads (#1799) * fix(loop): prune stale plain reasoning * fix(loop): trim retained tool-call payloads4 天前
feat(desktop): allow renaming session titles from sidebar (#1478) Adds a session_rename IPC command that writes to meta.summary, plus an inline edit affordance in the sidebar (pencil icon, Enter/Esc/blur). Reorders the header title to prefer meta.summary over the first user message so a rename is reflected immediately.8 天前
fix: keep compact sidebar new chat label visible (#2016) * fix compact sidebar shortcut fallback * fix desktop compact sidebar shortcut fallback2 天前
fix(desktop): sync slash setting commands (#1724)4 天前
Show desktop startup failures (#1759)4 天前
Fix desktop stdout partial writes (#1405) Co-authored-by: 粥 <cutagre@zhoudeMacBook-Air.local>9 天前
fix(desktop): bound long-session transcript payloads (#2019)2 天前
feat(code): unified-diff preview for pending edit blocks (0.5.11) Before /apply, the UI used to show only ▸ 2 pending edit block(s) — /apply or /discard src/a.ts (-3 +4 lines) src/b.ts (-1 +1 lines) — a count summary, nothing about what was actually changing. Common mistake shape: press y, look at git diff afterwards, discover the model edited the wrong block / wrong file. Now each pending block is rendered as a compact diff with ±2 lines of context and a hard cap at 20 lines per block: ▸ 2 pending edit block(s) — /apply (or y) to commit · /discard (or n) to drop src/foo.ts (-1 +1 lines) function foo() { - const x = 1; + const x = 2; return x; NEW src/bar.ts (-0 +3 lines) + export const bar = 1; + export const baz = 2; + export default { bar, baz }; ### Mechanism src/code/diff-preview.ts: - formatEditBlockDiff(block, opts) — shared leading/trailing lines collapse to context (default 2 lines each side, `… N unchanged lines above/below when truncated). Middle divergence is -old / +new`. Whole block capped at 20 lines with `… (N more diff lines — full content applies on /apply)` footer when exceeded. Empty-search (new file) case shows all-+ lines. - formatAllBlockDiffs(blocks, opts) — stitches each block's header (path + ±line counts, NEW prefix for new files) + its diff body, separated by blank lines. Zero new deps. Small hand-rolled helper using prefix/suffix-compare; sufficient for the typical SEARCH/REPLACE shape (small change inside a function body, wrapped in a few lines of surrounding context). Myers-diff quality is not needed — the model's SEARCH block already minimizes context. formatPendingPreview in App.tsx dropped its own summary line list (paths now come from the per-block diff header) and its local countLines helper (moved to diff-preview.ts alongside its only consumer). ### No colors yet Plain text, - / + / prefixes. ANSI colorization would need Ink component plumbing that isn't worth the complexity for v1 — the prefix convention is clear and universal (git / hunks / etc.). ### Tests (+10, suite 922 → 932) tests/diff-preview.test.ts — new-file all-+, context collapse for shared prefix/suffix, maxLines truncation with footer, leading and trailing hidden-context annotations, single-line edits, custom indent, multi-block formatAllBlockDiffs (path+counts headers, blank separators, empty-input). Lint + typecheck + build clean. 932 tests. 1 个月前
slash: unified preset+model picker, grouped suggestions, kill 11 commands + harvest/branch (#453) * slash: unified preset+model picker, grouped suggestions, kill 11 commands + harvest/branch The slash surface was 58 commands deep with no organization, two confusable options for "fresh start" (/clear vs /new), separate preset and model commands that solved the same user question ("what AI am I using"), and two heavy features that almost nobody opted into. Pulled all of that apart. Picker * ModelPicker now shows the 3 presets at the top with cost/headline copy and the model catalog below; auto-detects which preset the loop currently matches (model + effort + autoEscalate) so the cursor lands on the active row. Both /preset (no arg) and /model (no arg) open it. SlashSuggestions * SlashCommandSpec gains a group field (chat / setup / info / session / extend / code / jobs / advanced). Suggestion palette renders section headers on a bare /; advanced rows hide behind a "+ N advanced · type to search" footer until the user types a letter. /help walks the same registry, so there's one source of truth. Telemetry * New ~/.reasonix/slash-usage.json counter (read-modify-write, atomic rename) feeds suggestSlashCommands so frequent commands sort first within a prefix. emitSlashInvoked is now wired to the events.jsonl sidecar. Removals (commands) * /models, /keys, /resume, /semantic — redundant with picker / /help / /doctor. * /clear merged into /new (now an alias) — the visual-only clear was the most common confusion source ("why does /clear keep my context?"). * /effort, /rename, /forget, /apply-plan, /think, /tool, /mcp browse — second pass; either covered by other UI (sessions picker has rename/delete; preset locks effort) or self-described as fallback / debug. Removals (features) * /harvest and the Pillar-2 plan-state extractor (src/harvest.ts). * /branch and the parallel-sample selector (src/consistency.ts, src/loop/branch.ts). Streaming/branching coupling, BranchCard, branch_start/progress/done events, transcript planState/harvestedTurns, --harvest/--branch CLI flags, all gone. * benchmarks/harvest/ deleted; ARCHITECTURE.md collapsed from four pillars to three; README + zh-CN + dashboard/PARITY updated. i18n * Cleared dead keys for every removed command (handlers.basic.helpHelp..helpExit, keys*, semantic, resume*, models*, harvest*, branch*, effort*, think*, tool*, apply-plan*, slash.harvest, slash.branch, ui.harvest*, ui.branch*). Tests: 138 files / 2192 tests pass. Public API drops harvest, runBranches, TypedPlanState, BranchSummary, etc.; consumers using those break intentionally. * ci: drop harvest-bench dry-run step Follows the harvest feature removal — benchmarks/harvest/ no longer exists, so the CI step was breaking on ERR_MODULE_NOT_FOUND. τ-bench dry-run stays.21 天前
feat: cache diagnostics v1 — /cache-miss-report, doctor --cache, prefix hash evidence | 缓存诊断 v1 (#2188) * feat(cache): add prompt cache diagnostics * fix(cache): hash sent tool snapshot in diagnostics * perf(cache): memoize prefix diagnostic hashes16 小时前
feat(compat): read Claude .mcp.json + .claude/skills; alias type/context/agent fields (#1259) Stage 1 of the Claude-compat layer — the cheapest 80% of "users can't copy their Claude config / skills folder over" complaints. No behaviour change for existing Reasonix users; new paths and aliases are additive. MCP: - McpServerConfig accepts type as an alias for transport; "http" maps to "streamable-http" so Claude's shorter spelling Just Works. - New src/mcp/dot-mcp-json.ts reads project-level .mcp.json from cwd; its mcpServers block merges into the resolved config with project entries winning on name collision (same precedence Claude uses for shared, git-committed team configs). Skills: - ~/.claude/skills/ and <project>/.claude/skills/ join the existing Reasonix + .agents search roots, so a user can drop a Claude skill folder in and it loads. - Frontmatter aliases: context: fork and any non-empty agent: both flip runAs to subagent — same semantics, different field names. - Missing description: now warns to console instead of silently dropping the skill from the prefix index (the most common "why doesn't my skill show up" footgun). Co-authored-by: reasonix <reasonix@deepseek.com>10 天前
fix(tty): drain pending feature-detection replies on exit (#365) (#391) Reporter saw ^[]11;rgb:...^[\^[[33;1R^[[?62;1;4c printed by fish / bash right after exiting reasonix. Those bytes are RESPONSES — OSC 11 bg-color, CPR cursor-position, DA1 device-attributes — sent by the terminal in reply to queries the runtime / a dep emitted during session startup. The dist bundle has none of those query bytes itself, so the source is bun's tty feature-detection path or a transitive dep doing color-scheme detection. Whoever sent them, the responses sat in stdin's queue until reasonix exited. The parent shell then read them and rendered the bytes as input. Add drainTtyResponses(50ms) and call it in chat.tsx's exit finally, after runtime.closeAll(). Best-effort: sets raw mode, reads-and- discards anything still queued for 50ms, restores cooked. No-op when stdin isn't a TTY or raw mode isn't available. 0.30.3's alt-screen default is the upstream mitigation (DECSET 1049 flushes the input queue on most terminals); this is the program-side mitigation that's independent of which terminal the user is in.22 天前
test(edit-blocks): skip symlink-preservation test when symlink syscall fails (#1753) Windows hosts without Developer Mode (or non-elevated shells) can't create symlinks via fs.symlinkSync — the call throws EPERM. The new symlink-preservation test added in b40a569 didn't guard for this and failed for any contributor running npm test on a default Windows setup. Match the pattern already used in tests/at-mentions.test.ts: probe symlinkSync once, set symlinksWorked = false on throw, return early so the test passes vacuously on hosts that lack the capability. Co-authored-by: reasonix <reasonix@deepseek.com>4 天前
feat: codex/claudecode/cursor catch-up — #memory, -c, /review, per-hunk /apply - #note prompt prefix appends a one-liner to <root>/REASONIX.md (auto-creates with header). \#literal escape passes a real #text to the model; ## headings always pass through. - reasonix -c / reasonix chat -c resume the most-recently-touched session without the picker. Falls back gracefully + warns when no sessions exist. Helper extracted to src/cli/resolve.ts so the lookup is testable without fs. - Built-in review skill (subagent runAs) — reviews current branch diff, reports verdict + per-issue file:line citations, read-only contract. - /apply 1 / /apply 1,3 / /apply 1-4 (and /discard likewise) — pending preview labels each block [N] when 2+ are pending; partial apply leaves the rest in the queue. Pure parseEditIndices + partitionEdits helpers. 1 个月前
fix(tui): hide max effort on non-DeepSeek endpoints (#1798) #1657 dropped the preset abstraction and exposed reasoning effort directly, keeping max as a DeepSeek-only extension that users opt into knowing standard OpenAI / vLLM / Azure reject it with 400. The TUI still advertised max everywhere — /effort argsHint, slash-arg picker, ModelPicker effort rows, /effort handler — even when the active endpoint was a third-party host that can't accept it. Users on those endpoints saw max in every suggestion and reported it as a preset-era leftover (#1794). Endpoint-aware filter: when loop.client.baseUrl is not api.deepseek.com, drop max from the choices the TUI surfaces: - /effort argsHint and argCompleter (autocomplete + arg picker) - ModelPicker effort rows - /effort handler's accept list + status / usage message - new effortUsageNoMax i18n key (EN / zh-CN / de) so the error on bad input doesn't itself name max as an option max stays available on DeepSeek endpoints — that's the design from #1657, just no longer visible where it would 400. Fixes #1794. Co-authored-by: reasonix <reasonix@deepseek.com>4 天前
feat(cli): reasonix events <name> + sweep dead comments off the kernel reasonix events <name> pretty-prints the event-log sidecar — the first user-facing consumer of events.jsonl. Renders one line per event with per-type detail synopses; --type / --since / --tail filters; --json for jq pipelines; --projection dumps the reduced ProjectionSet. Closes the v0.14 kernel loop: events get written by every session, and now there's a tool to read them. Sweep pass: trimmed verbose jsdoc / restated-the-code comments off eventize.ts, event-sink-jsonl.ts, event-source-jsonl.ts, and the tests. ~80 lines of comment cruft gone, no logic changed. - src/cli/commands/events.ts: command formatter + per-event detail table covering all 25 Event variants - src/cli/index.ts: command registration with commander, help text - tests/events-command.test.ts: 6 tests pinning the formatter + each flag (type filter, tail, json passthrough, projection, missing session error path) 1753 tests pass (was 1747 + 6 new). 30 天前
chore(session): events.jsonl sidecar lifecycle + strict workspace match - listSessions: filter out *.events.jsonl — they share the .jsonl suffix and were polluting the picker as phantom sessions - listSessionsForWorkspace: strict equality on meta.workspace; legacy untagged sessions no longer leak into every workspace's picker (resume-by-name still works) - rename / delete: also move/remove the .events.jsonl sidecar so /forget and /rename leave no orphans - JsonlEventSink: skip model.delta — recoverable from model.final.text and would balloon the sidecar with token-by-token frames 28 天前