1189 Commits
Author SHA1 Message Date
github-actions[bot]GitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
6038681fc8 chore(main): release 0.22.0 (#1832)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
v0.22.0
2026-07-06 08:55:37 +08:00
BogdanandGitHub 62fa7d48c1 fix(api): quiet expected side-task aborts (#1868)
* fix(api): quiet expected side-task aborts

* fix(api): tighten expected abort handling

* fix(memory): keep cursor on superseded extraction
2026-07-06 08:21:54 +08:00
BogdanandGitHub ad796e0d9f fix: bound profiler performance entries (#1865)
* fix: bound profiler performance entries

* test: restore profiler retention mocks cleanly
2026-07-06 08:21:11 +08:00
BogdanandGitHub 9700bd3c41 fix(lsp): suppress empty diagnostic deliveries (#1859)
* fix(lsp): suppress empty diagnostic deliveries

* fix(lsp): preserve storm summary diagnostics

* test(lsp): clarify diagnostic attachment guard
2026-07-06 08:16:38 +08:00
JATMNandGitHub cd13a61537 fix(memory): recover from autocompact overflow failures (#1858)
* fix(memory): recover from autocompact overflow failures

* fix(memory): address autocompact review findings

* fix(memory): close autocompact recovery gaps

* fix(memory): reduce OpenAI conversion pressure

* test(memory): add long-session guard smoke

* fix(memory): add runtime memory guard diagnostics

* fix(memory): surface autocompact failure diagnostics

* fix(memory): reuse hard-cap resolver in diagnostics

* fix(memory): avoid hard-cap diagnostic drift

* fix(memory): clarify hard-cap diagnostics
2026-07-06 08:16:01 +08:00
BogdanandGitHub cd1cf3ca70 fix(bash): share parser analysis across checks (#1735)
* fix(bash): share parser analysis across checks

* test(bash): tighten parser analysis test helpers

* fix(bash): keep execution sandbox fail-closed

* fix(bash): align sandbox presentation fallback
2026-07-06 08:10:51 +08:00
203f05538e fix(build): shim jsxDEV when bundling production React — TUI rendered nothing (#1863)
Since 354feb48 (#1856) mapped react/jsx-dev-runtime to React's production
file, the CLI launched to the startup banner and then rendered no UI at
all — no prompt box, no typing, no visible error.

Root cause: Bun transpiles our JSX with the dev transform (no
NODE_ENV=production at build time), so every JSX callsite compiles to
jsxDEV(). React's react-jsx-dev-runtime.production.js deliberately exports
`jsxDEV: undefined` (production bundles are expected to use the non-dev
transform), so every element creation invoked undefined() and React never
committed a single frame. Nothing surfaced because the failure happens
while building the element tree, before the renderer's error callbacks.

Fix: map react/jsx-dev-runtime to a local shim that dispatches jsxDEV onto
the production jsx/jsxs — the same dispatch React's own dev runtime
performs, minus dev-only validation. The shim's own react/jsx-runtime
import is remapped by the plugin, so the bundle stays all-production
(memory goal of #1856 intact): react, jsx-runtime, reconciler, constants,
and scheduler all resolve to .production.js, with no development copies.

Regression tests (per review) pin the shim's dispatch: jsxDEV must exist,
route to jsx/jsxs on isStaticChildren, pass the key through, re-export the
real Fragment, and produce the standard element shape — compared directly
against the real react/jsx-runtime exports so the tests track React.

Verified live in the TUI (tmux): prompt box renders, typing echoes, slash
menu opens and filters, Esc dismisses; sourcemap shows only production
React modules plus the shim.

Co-authored-by: OpenClaude <openclaude@gitlawb.com>
2026-07-05 21:24:12 +08:00
77c0a0d780 feat(ux): honest feedback pass — visible retries, statusline truncation marker, hint grace period (#1862)
* feat(ux): honest feedback pass — visible retries, statusline truncation marker, hint grace period

Three fixes with one principle: never look frozen, never silently hide state.

- SystemAPIErrorMessage: retries were fully hidden until attempt 4, so
  transient rate limits / overloads were indistinguishable from a hang.
  Attempts 1-3 now render a compact dim line ("Rate limited — retrying
  in 4s… (attempt 2/10)") with a live countdown; the full error block
  is unchanged at attempt >= 4. The transcript already keeps only the
  last api_error message and hides it on the next non-error message,
  so early visibility adds no stacking. New briefAPIErrorReason()
  classifies 429/529/5xx/connection failures, including the
  OpenAI-compat shim's plain-text transport errors that carry no cause
  chain. Component rewritten from react-compiler output to plain React.

- BuiltinStatusLine: fitSegments dropped rate-limit -> cost -> context
  silently on narrow terminals. Segments now degrade to short forms
  first (ctx 37% -> 37%, $1.23 -> $1), and anything still dropped is
  marked with a trailing dim "…" so hidden data is visible as hidden.
  The marker is best-effort: at extreme widths the bare model name
  beats showing nothing.

- PromptInputFooter: "? for shortcuts" was suppressed whenever a status
  line rendered — the default state since the builtin statusline
  shipped, killing the hint's discoverability path entirely. New users
  (numStartups <= 10) keep the hint alongside the status line;
  established users get the quieter footer.

- docs: BASH_MAX_OUTPUT_LENGTH env var documented on the website env
  reference (default 30000, cap 150000).

Verified live in the TUI (tmux + mock OpenAI endpoint): compact retry
line from attempt 1 against a dead endpoint, full block at attempt 4,
Esc interrupts cleanly; statusline at 100/32/24 cols shows full /
degraded / "test-model · 2% · …"; hint present at numStartups=2,
suppressed at 50.

Co-Authored-By: OpenClaude <openclaude@gitlawb.com>

* fix(review): address CodeRabbit feedback — custom statusline yields immediately, timeout test, docs wording

- shouldSuppressShortcutsHint: a custom status line is explicit user
  configuration, so it now always wins over the discoverability grace
  period; only the builtin status line grants new users the hint.
  Test added to lock the semantics.
- Test the ETIMEDOUT -> "Request timed out" branch. Note: CodeRabbit's
  suggested test shape (plain object with a cause) would not exercise
  the branch — extractConnectionErrorDetails only walks Error
  instances — so the error itself carries the code.
- Docs: soften "full output saved" to reflect the persisted-file cap.

Co-Authored-By: OpenClaude <openclaude@gitlawb.com>

---------

Co-authored-by: OpenClaude <openclaude@gitlawb.com>
2026-07-05 19:31:14 +08:00
JATMNandGitHub 354feb483c fix(memory): prevent reported idle retention paths (#1856)
* fix(build): bundle production React in CLI

* fix(memory): bound reported idle retention paths

* fix(memory): address review feedback

* fix(memory): keep fps average stable after sample cap

* test(memory): cover heap dump filenames
2026-07-05 13:30:25 +08:00
JATMNandGitHub 2ac20c759b fix(clipboard): use .NET Clipboard.GetImage() for Windows raw bitmap paste (#1855)
* fix(clipboard): use .NET Clipboard.GetImage() for Windows raw bitmap paste

Fixes #1844

Replace Get-Clipboard -Format Image with System.Windows.Forms
Clipboard.GetImage() / ContainsImage() on Windows. The old PowerShell
cmdlet does not properly convert raw DIB/CF_BITMAP data placed on the
clipboard by PrintScreen and Win+Shift+S (Snipping Tool). The .NET API
natively handles all clipboard image formats.

Also enables hasImageInClipboard() on Windows so the 'Image in
clipboard' hint notification fires when the terminal regains focus.

Changes:
- Extract WIN32_CLIPBOARD_HAS_IMAGE_CMD shared constant
- Update getClipboardCommands() win32 checkImage and saveImage
- Add Windows path to hasImageInClipboard()
- Add clarifying comment on checkImage exit code behavior
- Add unit tests for exported imagePaste functions

* test(clipboard): make Windows path assertion platform-aware

* fix(clipboard): address Windows image paste review findings

* test(clipboard): cover Windows image paste success path

* test(clipboard): expect Windows image dimensions

* test(clipboard): allow optional image dimensions

* test(clipboard): assert mocked image dimensions
2026-07-05 13:29:33 +08:00
JATMNandGitHub 68fcb91930 fix(bash): use indexOf instead of lastIndexOf for multi-flag shell prefix (#1851)
* fix(bash): use indexOf instead of lastIndexOf for multi-flag shell prefix (#1849)

* fix(bash): handle space-dash shell prefix paths

* fix(bash): preserve space-dash shell prefix basenames
2026-07-05 13:28:59 +08:00
BogdanandGitHub 5226fb9ee7 fix(query): configure hard max and abort reasons (#1850)
* fix(query): configure hard max and abort reasons

* fix(query): normalize legacy abort reasons

* test(query): dedupe abort classification setup
2026-07-05 11:48:46 +08:00
00b8c15b5b fix: governance controls for memory and git attribution (#1806)
* fix: add governance controls for memory and git attribution

Default persistent memory writes to explicit approval unless memory.requireApprovalBeforeWrite is disabled. Skip automatic memory extraction/dream writes while approval is required, avoid auto-creating memory directories in that mode, and surface approval prompts for auto-memory file writes.

Make generated commit and PR attribution opt-in through git.addAICoAuthor and git.addGeneratedWithFooter while preserving explicit custom attribution. Add forbidden commit-message patterns and surface them in commit prompts.

Enforce commit-message governance in Bash and PowerShell permission checks, including global git options, file-backed messages, shell wrappers, env split strings, chained commands, and PowerShell call/operator forms. Add regression coverage for the policy paths.

* test: isolate governance policy settings state

* fix: address governance policy review findings

* fix: require approval for overridden memory writes

* fix: fail closed on expandable commit messages

Treat Bash and PowerShell expandable commit-message sources as uninspectable when commit-message governance policy is active. This prevents runtime-expanded variables, command substitutions, and expandable here-strings from bypassing forbidden attribution checks while preserving literal quoted heredocs/here-strings.

Validation: bun test src/tools/BashTool/bashSecurity.test.ts src/tools/BashTool/bashPermissions.test.ts; bun test src/tools/PowerShellTool/powershellPermissions.test.ts; bun test src/tools/BashTool/bashSecurity.test.ts src/tools/BashTool/bashPermissions.test.ts src/tools/BashTool/readOnlyValidation.test.ts src/tools/BashTool/utils.test.ts src/tools/PowerShellTool/powershellPermissions.test.ts src/utils/permissions/filesystem.test.ts src/utils/permissions/permissions.test.ts src/memdir/paths.test.ts; bun run typecheck; bun run smoke; bun run security:pr-scan -- --base origin/main --head HEAD

* fix: fail closed on unquoted expandable commit messages

Extend Bash and PowerShell commit-message governance to treat unquoted expandable message tokens as uninspectable under active policy. This covers Bash forms like `git commit -m fix` and PowerShell forms like `git commit -m `, alongside the quoted and heredoc cases already covered.

Validation: bun test src/tools/BashTool/bashSecurity.test.ts src/tools/BashTool/bashPermissions.test.ts; bun test src/tools/PowerShellTool/powershellPermissions.test.ts; bun test src/tools/BashTool/bashSecurity.test.ts src/tools/BashTool/bashPermissions.test.ts src/tools/BashTool/readOnlyValidation.test.ts src/tools/BashTool/utils.test.ts src/tools/PowerShellTool/powershellPermissions.test.ts src/utils/permissions/filesystem.test.ts src/utils/permissions/permissions.test.ts src/memdir/paths.test.ts; bun run typecheck; bun run smoke; bun run security:pr-scan -- --base origin/main --head HEAD

* fix: fail closed on unquoted cat substitutions

Treat plain unquoted command substitutions like git commit -m $(cat .git/OPENCLAUDE_COMMIT_MSG) as uninspectable while still letting the heredoc-specific parser handle literal cat heredoc commit messages.

Validation: bun test src/tools/BashTool/bashSecurity.test.ts src/tools/BashTool/bashPermissions.test.ts; bun test src/tools/BashTool/bashSecurity.test.ts src/tools/BashTool/bashPermissions.test.ts src/tools/BashTool/readOnlyValidation.test.ts src/tools/BashTool/utils.test.ts src/tools/PowerShellTool/powershellPermissions.test.ts src/utils/permissions/filesystem.test.ts src/utils/permissions/permissions.test.ts src/memdir/paths.test.ts; bun run typecheck; bun run security:pr-scan -- --base origin/main --head HEAD; bun run smoke

* fix: honor PR footer governance opt-in

Make getEnhancedPRAttribution use the same generated PR attribution opt-in and block semantics as getAttributionTexts, so git.addGeneratedWithFooter enables the enhanced PR footer path and false blocks legacy generated PR attribution.

Validation: bun test src/utils/attribution.test.ts; bun test src/utils/governancePolicy.test.ts src/utils/attribution.test.ts src/commands/commit-push-pr.test.ts (commit-push-pr test path absent, 32 tests ran); bun run typecheck; bun run security:pr-scan -- --base origin/main --head HEAD; bun run smoke; git diff --check

* fix: fail closed on bare git commit governance

Recognize bare git commit as an uninspectable commit-message source in both Bash and PowerShell governance checks, so active forbidden-pattern policy prompts instead of passing through the editor/template path.

Validation: bun test --feature=UNATTENDED_RETRY src/tools/BashTool/bashSecurity.test.ts src/tools/BashTool/bashPermissions.test.ts src/tools/PowerShellTool/powershellPermissions.test.ts; bun run typecheck; bun run smoke; bun run security:pr-scan -- --base origin/main --head HEAD; git diff --check

---------

Co-authored-by: jatmn <jatmn@users.noreply.github.com>
2026-07-05 11:48:13 +08:00
BogdanandGitHub d1530c28aa fix(query): clean up lifecycle tool tracking (#1845) 2026-07-04 08:00:20 +08:00
0xfandomandGitHub b9b5685143 fix(permissions): make legacy tool-name alias lookup prototype-safe (#1847)
normalizeLegacyToolName looked up a caller-supplied tool name in the plain
object LEGACY_TOOL_NAME_ALIASES with a bare bracket access. Names that collide
with Object.prototype members — `constructor`, `toString`, `valueOf`,
`hasOwnProperty`, `__proto__` — resolved to the inherited function/object,
and the `?? name` fallback never fired because that value is non-null.

The function is declared to return `string` but returned a function for those
inputs, breaking every caller: availableToolNames.has() strips a tool named
`constructor` from tool_reference blocks, hook matcher `===` comparisons
misbehave, and permission rules like `hasOwnProperty(...)` parse to a function
as toolName. Guard the lookup with Object.hasOwn so only own aliases match.

Adds proto-name coverage for normalizeLegacyToolName and
permissionRuleValueFromString.
2026-07-04 07:59:32 +08:00
0xfandomandGitHub d03b6a879c fix(codex): make Codex alias lookup prototype-safe (#1833)
* fix(codex): make Codex alias lookup prototype-safe

CODEX_ALIAS_MODELS is a plain object literal, and the three alias lookups
(isCodexAlias, parseModelDescriptor x2) keyed it directly with a
config/CLI-controlled model string. Because the string is lower-cased first,
the already-lowercase inherited names `constructor` and `__proto__` resolve
through Object.prototype: `key in map` returns true and `map[key]` returns a
truthy inherited value.

Effect: isCodexAlias('constructor') returns true, so with no explicit base URL
shouldUseCodexTransport misroutes the request through the Codex transport; and
parseModelDescriptor('constructor') passes its `if (aliasConfig)` guard and
returns baseModel = undefined, which then flows into API-name resolution.

Guard each read with Object.hasOwn so only own alias entries match. Keeps the
`as const` map (and the CodexAlias keyof type) intact, unlike a null-proto
rewrite. Adds a regression test covering both reachable proto keys plus
positive controls.

* fix(codex): guard the reasoning-effort alias lookup too; cover the descriptor path

getReasoningEffortForModel indexed CODEX_ALIAS_MODELS without an own-property
check — the fourth alias lookup surface, alongside the two parseModelDescriptor
branches and isCodexAlias. It feeds supportsCodexReasoningEffort, /effort, and
EffortPicker, so an inherited property matching a model id could be read as a
Codex reasoning default. Apply the same Object.hasOwn guard so all four lookup
sites are protected together.

Extend the proto-alias test:
- a polluted-prototype case (plant an alias on Object.prototype, cleaned up in
  finally) that proves getReasoningEffortForModel / supportsCodexReasoningEffort
  ignore inherited entries — this red-greens the new guard, which constructor/
  __proto__ can't since they carry no reasoningEffort.
- resolveProviderRequest cases for `constructor` / `__proto__`, exercising the
  parseModelDescriptor path so its guards can't be silently removed (a missing
  guard makes descriptor.baseModel undefined → resolvedModel wrong).

* test(codex): red-green the descriptor query-branch alias guard

The proto-alias coverage exercised only the no-query parseModelDescriptor
read. Add a query-branch case that plants an inherited alias whose .model
differs from the key and asserts a `<key>?reasoning=medium` request keeps
the literal base model, so removing the own-property guard from the
`baseModel?reasoning=...` read now fails the suite.
2026-07-04 07:58:53 +08:00
0xfandomandGitHub 069febb737 fix(gitdiff): count in-hunk lines that start with ++ or -- in raw diff stats (#1843)
parseRawDiffToToolUseDiff skipped hunk lines matching `startsWith('+++')` /
`startsWith('---')`, intending to ignore the `+++ b/file` / `--- a/file`
headers. But those headers only appear before the first `@@`, so they never
reach the counter (which is already gated on `inHunks`). Inside a hunk the
guards instead dropped genuine content whose text begins with `++`/`--` — a
YAML `---` separator, `+++quiet-flag`, `---legacy-peer-deps`, a Markdown rule —
undercounting additions/deletions/changes in the tool-result gitDiff payload.

Remove the redundant guards so every in-hunk `+`/`-` line is counted. This is
the same defect the sibling parseGitDiff already documents and guards against
with its `!currentHunk` check; parseRawDiffToToolUseDiff had the latent
instance. Export it and add a regression covering the `--`/`++` content lines.
2026-07-03 22:54:56 +08:00
0xfandomandGitHub 17f4a5b963 fix(plugins): match reserved-marketplace git URL owner by host, not substring (#1840)
validateOfficialNameSource gated the reserved-name git-URL path on
`url.includes('github.com/anthropics/')` / `url.includes('git@github.com:anthropics/')`.
A substring match also accepts URLs whose host is not github.com or where that
text sits in the path — e.g. `https://notgithub.com/anthropics/x`,
`https://evilgithub.com/anthropics/x`, or `https://evil.com/github.com/anthropics/x`.
An attacker could register a repo they control under a reserved official
marketplace name and have it validated as official.

Parse the URL instead and require the exact host `github.com` with the official
org as the first path segment. Handles https/http, `ssh://`, and scp-like SSH
(`git@github.com:anthropics/...`) forms, case-insensitively. The GitHub `repo`
branch already anchored with startsWith and is unchanged.

Adds a regression covering the real bypasses plus genuine official URLs and
wrong-org rejection.
2026-07-03 08:05:08 +08:00
0xfandomandGitHub e6019d3797 fix(model): resolve [1m]-tagged aliases when 1M context is disabled (#1822)
* fix(model): resolve [1m]-tagged aliases when 1M context is disabled

parseUserSpecifiedModel gated stripping of the [1m] tag on has1mContext,
which returns false when CLAUDE_CODE_DISABLE_1M_CONTEXT is set (the
C4E/HIPAA admin control). With the flag on, an aliased request like
'sonnet[1m]' kept the tag attached, never matched the 'sonnet' alias, and
returned the literal unservable string 'sonnet[1m]' — so disabling 1M
broke [1m]-aliased model selection entirely instead of gracefully serving
the base model.

Separate 'tag is present in the input' (always strip before matching) from
'1M is active' (re-append the suffix). When 1M is disabled, the tag is
dropped and the alias/custom id resolves to its base model. Covers the
alias and custom-model paths; the ant path already stripped unconditionally.

Adds regression coverage for both the disabled (tag dropped, base resolved)
and enabled (tag preserved) directions.

* fix(model): drop disabled [1m] from custom default alias targets

The alias branches resolved to getDefaultSonnetModel()/getDefaultOpusModel()
etc. and appended the tag, but a custom default override such as
ANTHROPIC_DEFAULT_SONNET_MODEL=MySonnetDeploy[1m] bakes the suffix into the
resolved value. With CLAUDE_CODE_DISABLE_1M_CONTEXT=1 a request like
`sonnet[1m]` still resolved to `MySonnetDeploy[1m]`, leaving the disabled tag
on (and a tagged input doubled it to `...[1m][1m]`).

Route the alias- and legacy-opus-resolved defaults through a small helper that
strips whatever [1m] is present and re-attaches it only when a tag was
requested (input or resolved default) AND 1M context is enabled. Disabled 1M
now drops the tag regardless of source; enabled still honors an env default's
opt-in without duplicating it. Custom deployment casing is preserved.

Adds coverage for default-model env overrides (disabled + enabled directions)
and a mixed-case custom id.

* test(model): cover disabled-1M mixed-case custom ids and Codex aliases

The disabled-1M block covered the Claude-family aliases and a lowercase custom
id, but not two paths flagged in review: a mixed-case custom deployment id
(must drop [1m]/[1M] while preserving casing) and the Codex aliases, which are
resolved by a separate branch from the Claude-family aliases and so need their
own disabled-1M assertions (codexplan[1m] / codexspark[1M] drop the tag).
2026-07-03 07:59:05 +08:00
BogdanandGitHub ac2b575b6e Fix shell abort classification for Bash and PowerShell (#1688)
* fix: classify shell aborts as cancellations

* fix: enumerate shell abort messages

* fix: preserve PowerShell large error output
2026-07-03 07:58:03 +08:00
BogdanandGitHub 5b1db554fd feat(lsp): expose captured diagnostics (#1813) 2026-07-02 08:12:36 +08:00
Ahmar YaseenandGitHub b73a879c54 fix(memoryscan): abort remaining workers when one throws on iterator … (#1836)
* fix(memoryscan): abort remaining workers when one throws on iterator error

scanMemoryFilesWithDependencies spawns HEADER_READ_CONCURRENCY workers consuming a shared async generator. When one worker's fileIterator.next() throws (e.g. filesystem error), the error is re-thrown, Promise.all rejects, and the outer catch returns []. But without an abort signal, the remaining workers keep iterating indefinitely, leaking unresolved promises and open file handles.

Create an internal AbortController linked to the caller's AbortSignal, and call controller.abort() before re-throwing so peer workers terminate on any error.

* fix: reorder abort check so throw error is reachable
2026-07-02 07:01:17 +08:00
784d9a92ef fix(format): show sub-second durations with one decimal instead of "0s" (#1820)
* fix(format): show sub-second durations with one decimal instead of "0s"

formatDuration's sub-second branch was guarded by `ms < 1` (1 millisecond)
while its comment documents "For durations < 1s, show 1 decimal place". At
that threshold the decimal branch could only fire for sub-millisecond values
(always returning "0.0s"), so real sub-second durations fell through to
Math.floor(ms / 1000) and rendered as "0s" — e.g. a 500ms agent run showed
"0s". Corrected the threshold to `ms < 1000`. Adds formatDuration tests
(the function was previously untested).

* fix(format): round sub-second durations in integer milliseconds

Rounding the raw fraction with (ms / 1000).toFixed(1) is unstable because
values like 0.95 are not exactly representable in binary floating point,
so 950ms rendered as 0.9s and 850ms as 0.8s. Round in integer
milliseconds first via a shared oneDecimalSeconds helper, used by both
formatSecondsShort and formatDuration's sub-second branch. Added halfway
regression cases.

---------

Co-authored-by: Pablosinyores <nikhilbajaj0182@gmail.com>
2026-07-02 07:00:37 +08:00
0xfandomandGitHub 0c9b81493a fix(bash): surface rolled-output file path on non-zero exit (#1359) (#1392)
* fix(bash): surface rolled-output file path on non-zero exit (#1359)

When a Bash command produces more than `getMaxOutputLength()` bytes the
shell rolls the captured output to a file and leaves only the first
chunk on `result.stdout`. The success path already persists that file
into the tool-results dir so the model can read it back via FileRead;
the non-zero-exit path threw `ShellError` before reaching that block, so
the captured-but-rolled output was effectively unreachable. The model
saw the exit code and the truncated chunk and had no way to recover the
failure body — the user had to re-run the command with explicit
redirection. Issue #1359 reads as "almost every command returns only
the exit code on error" because the offenders (`bun run build`, test
runners, language compilers) routinely emit more than 30k bytes before
they fail.

Extract the persist step into a shared `persistShellOutputFile` helper
and call it from both the success path and the error path. On the
error path, append an `[output truncated above — full output (<bytes>
bytes) saved to <path>; read with the Read tool]` marker to the stdout
slot of the `ShellError` so `formatError` / `getErrorParts` carries the
location alongside the exit code and the in-memory preview.

Tests: new regression in BashTool.errorOutput.test.ts drives ~50k bytes
into a command that exits 1 and asserts the canonical marker phrasing
appears in `formatError(err)` along with the exit code.

Closes #1359

* fix(bash): mark capped persisted output instead of calling it full

persistShellOutputFile() truncates the rolled-output file to
MAX_PERSISTED_SHELL_OUTPUT_SIZE (64 MB) before linking it into the
tool-results dir. The non-zero-exit hint still labelled it the
"full output (N bytes)", so for a failing command emitting more than
64 MB the model was told the complete failure body was on disk even
though the tail past the cap was dropped — hiding a compiler/test
error that appears late in the log.

Thread a truncated flag out of persistShellOutputFile() and, when set,
report the cap ("first 64 MB of the N-byte output saved ... (capped,
tail not saved)") instead of claiming full output. The success path
keeps reporting the original total size unchanged.

Refs #1359

* fix(bash): don't claim full output on the success path when the file was capped

The success path persisted persistedOutputPath/persistedOutputSize but dropped
persistShellOutputFile's truncated flag, so buildLargeToolResultMessage always
said 'Full output saved to:'. For a successful command whose rolled output
exceeds the 64 MiB cap, the saved file holds only the first 64 MiB while the
model was told the complete output was available.

Thread the truncated state through the success-path result and message;
buildLargeToolResultMessage now says 'Partial output ... (output was capped)'
when the persisted file is truncated.

* test(bash): read back the persisted file and cover capped-output wording

- The large-output error test now extracts the saved path from the marker,
  asserts the file is readable and contains the tail line (line 0700) that
  #1359 needs the model to recover, and removes the artifact in a finally block
  so it isn't left under the project storage.
- Add buildLargeToolResultMessage cases for the full vs capped wording.

* fix(bash): cap persisted output copy, not the rolled-output source

persistShellOutputFile truncated the shell's rolled-output file in place
before the link/copy, so the error fallback and resizeShellImageOutput
read a source that had already lost its tail. Copy the oversized output
first (a hardlink shares the inode), then cap the destination copy,
leaving the source intact for downstream recovery.

Refs #1359

* fix(bash): write only capped bytes when persisting oversized shell output

The oversized branch copied the entire rolled-output source into tool-results
and only then truncated the copy. For a multi-GB failure log that briefly
materializes the whole file under session storage, and if the post-copy
truncate failed the helper caught the error and returned null while leaving the
full-size copy behind.

Stream a bounded byte range (first maxSize bytes, inclusive end = maxSize-1)
straight into the destination instead, and unlink any partial destination on
failure before bubbling to the outer catch. The source is still left untouched
so the error fallback and image-resize paths can read its tail.

Adds a test pinning the destination to exactly the first maxSize bytes
(distinguishable head/tail halves catch an off-by-one on the read range).
2026-07-02 06:59:55 +08:00
0xfandomandGitHub deb41761c1 fix(openai-shim): strip store when baseUrl points at Mistral (#1047)
* fix(openai-shim): strip `store` when baseUrl points at Mistral

Mistral's chat-completions endpoint rejects requests with a `store`
field — `422 body.store: Extra inputs are not permitted`. The shim
already strips `store` for Gemini and Cerebras hosts via
`hasGeminiApiHost` / `hasCerebrasApiHost`; add the symmetric host check
for Mistral so users hitting `api.mistral.ai` directly (without
`CLAUDE_CODE_USE_MISTRAL=1` to engage the gateway profile) don't hit
the same wall.

Closes #739.

* test(openai-shim): cover the Mistral-host store-strip fallback directly

The existing api.mistral.ai test resolves to the Mistral descriptor route,
whose removeBodyFields already strips store, so it passes without the
hasMistralApiHost change. Add a test on an unresolved Mistral-host proxy
(proxy.mistral.ai → no descriptor route) where store is stripped only by
the host fallback, plus a hasMistralApiHost predicate test asserting
subdomains match while look-alikes (notmistral.ai, api.mistral.ai.evil.com)
keep store. Export hasMistralApiHost for the predicate test.

* fix(openai-shim): map max_completion_tokens on Mistral-host fallback

The unresolved Mistral-host route (e.g. proxy.mistral.ai) does not carry
the descriptor's max_tokens field mapping, so chat completions still sent
max_completion_tokens, which Mistral rejects with the same 422 as store.
Gate the token-field rewrite on hasMistralApiHost too, mirroring the
store strip, and assert the fallback body sends max_tokens.

Closes #739
2026-07-02 06:59:10 +08:00
BogdanandGitHub 67227cf772 fix(openai-shim): recover stalled provider streams (#1817)
* fix(openai-shim): recover from stalled streams

Bound SSE reader waits with an idle timeout so non-streaming fallback can recover before the parent query is force-aborted. Preserve parent-abort cancellation semantics and cover fallback, disabled-fallback, and slow-active stream cases.

* test(openai-shim): bound fallback recovery regression

* test(openai-shim): relax CI fallback timing guard

* test(openai-shim): stabilize idle fallback regression

* test(openai-shim): force idle timeout fixture error

* test(openai-shim): stabilize idle timeout fallback fixture

* test(openai-shim): use real stalled stream fallback fixture

* test(openai-shim): assert fallback recovery outcome

* test(claude): isolate fallback feature flags

* test(claude): stabilize idle fallback fixture

* fix(claude): fallback on live stream abort timeouts

* test(claude): drop unstable idle fallback fixture

* test(claude): decouple idle timeout assertion budget
2026-07-01 21:19:04 +08:00
BogdanandGitHub bb61d8430b fix(openai-shim): wire stream controller abort (#1828)
* fix(openai-shim): wire stream controller abort

* test(openai-shim): guard Ollama abort fixture cleanup
2026-07-01 07:56:50 +08:00
f6ecee0c00 chore: remove unused Python helper suite (#1827)
Remove the unused Python helper island under python/, including the standalone Ollama adapter, smart router, pytest tests, and Python requirements file.

Drop the corresponding Python setup, dependency install, and pytest steps from the PR checks workflow now that no repo-level Python helper suite remains.

Clean contributor-facing references in README, AGENTS.md, and CONTRIBUTING.md so the repository map and validation guidance no longer point at deleted Python helper code.

Validation:

- bun run build: passed

- bun run typecheck: passed

- bun run typecheck:type-tests: passed

- bun run test:provider-recommendation: passed

- bun run security:pr-scan -- --base upstream/main --head HEAD: passed

- bun run check: failed in existing broader test suites unrelated to this removal (bughunter git context, Conversation Arc Scale and Stability, xAI OAuth callback)

- bun run test:provider: failed in existing xAI OAuth callback tests

Co-authored-by: jatmn <jatmn@users.noreply.github.com>
2026-07-01 07:42:08 +08:00
BogdanandGitHub 8182a46441 feat(report): render task reports as markdown (#1826) 2026-07-01 06:43:05 +08:00
BogdanandGitHub 166d0ce784 feat(resume): group branched sessions in picker (#1824)
* feat(resume): group branched sessions in picker

* test(resume): stabilize branch metadata fixtures

* fix(resume): keep branch base titles searchable

* test(resume): release picker lock on setup failure

* fix(resume): count expanded branch rows for load more

* fix(resume): keep branch metadata reads bounded

* test(resume): assert hidden branch log is loaded
2026-07-01 06:36:15 +08:00
github-actions[bot]GitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
64955db0fb chore(main): release 0.21.0 (#1783)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
v0.21.0
2026-06-30 21:28:13 +08:00
BogdanandGitHub c1a9dadea5 fix(claude): make stream watchdog deterministic (#1823)
* fix(claude): make stream watchdog deterministic

* test(claude): harden watchdog env restore
2026-06-30 20:17:09 +08:00
0xfandomandGitHub a7945e5a70 fix(plugins): keep marketplace reconciliation prototype-safe (#1821)
Marketplace names come from user settings (extraKnownMarketplaces), and
diffMarketplaces looks each one up with materialized[name]. When the
materialized map is a plain object, a name colliding with an
Object.prototype member (constructor / toString / valueOf / hasOwnProperty)
resolves to the inherited value instead of undefined, so the entry is
misclassified as already-materialized (sourceChanged) rather than missing.

reconcileMarketplaces produced exactly such a plain object in its error
fallback. Two changes:

- diffMarketplaces now does an own-property-exact lookup via Object.hasOwn,
  so the comparison is correct regardless of the map's prototype.
- reconcileMarketplaces uses loadKnownMarketplacesConfigSafe, which already
  degrades a corrupted/unreadable config to a null-prototype empty map (and
  logs via logForDebugging rather than the error file). The snapshot is only
  used to diff — the install step re-reads and mutates the real config — so
  the graceful empty fallback cannot clobber the user's file.

Same prototype-pollution class as the marketplace cache fix in #1787.
Adds the first tests for diffMarketplaces, which was previously untested.
2026-06-30 20:16:05 +08:00
BogdanandGitHub 1827d84709 feat(agents): add per-agent step limits (#1815)
* feat(agents): add per-agent step limits

Add maxSteps agent configuration for markdown, JSON, plugin, and SDK agent definitions. Enforce the limit in subagent query execution by blocking over-limit tool calls, preserving a no-tool summary turn, and recording an agent_step_limit terminal reason. Add focused coverage for default behavior, invalid values, multi-turn accumulation, plugin parsing, failure-loop interaction, and summary-tool blocking.

* test(agents): isolate agent loader fixtures

* test(agents): stabilize agent loader config fixtures

* fix(agents): harden step-limit summaries

* fix(sdk): harden agent injection follow-up

* fix(sdk): report invalid agent step limits
2026-06-30 11:23:21 +08:00
JATMNandGitHub 985984b9ff feat(ClinePass): add gateway provider with usage support (#1818)
* feat(integrations): add ClinePass gateway provider with usage support

Adds ClinePass (https://cline.bot) as an OpenAI-compatible gateway provider.

Gateway
- New descriptor at src/integrations/gateways/clinepass.ts with 10 static models.
- Uses wireFormat: 'reasoning_effort' and full granular levels (low/medium/high/xhigh)
  so each model can expose reasoning controls consistent with Atlas Cloud.
- Dedicated credentials only: requires CLINE_API_KEY and ignores stale OPENAI_API_KEY.
- Generated integration artifacts updated via bun run integrations:generate.

/usage support
- New service module under src/services/api/clinepassUsage/ for types, fetching,
  and normalizing the ClinePass usage-limits response.
- New UI component src/components/Settings/ClinePassUsage.tsx rendered by Usage.tsx.
- Displays 5-hour, weekly, and monthly usage progress bars with longer progress bars.

Provider profile fixes
- routeMetadata.ts now resolves the active provider from the saved profile even when
  CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED is not yet set, so /usage works immediately
  after switching providers with /provider.
- providerProfile.ts / providerProfiles.ts learn CLINE_API_KEY so saved profiles apply
  the ClinePass credential alongside the OpenAI-compatible env vars.

Tests & docs
- Updated ProviderManager.test.tsx and compatibility.test.ts for the new preset.
- Added routeMetadata.test.ts cases for ClinePass and generic profile fallback.
- Added clinepassUsage.test.ts for payload normalization and row building.
- Updated .env.example and README.md with CLINE_API_KEY instructions.

Validation
- bun run typecheck
- bun run build
- bun run test:provider (964 pass)
- bun run check (5331 pass; 1 unrelated Windows file-mode failure in branch.test.ts)

* fixup: wire CLINE_API_KEY/CLINE_API_MODEL into runtime routing and profile persistence

- Add 'clinepass' to the ProviderProfile union so saved profiles can use it.
- Add CLINE_API_KEY to PROFILE_ENV_KEYS so profile switching clears stale keys.
- routeMetadata.ts: env-only CLINE_API_KEY now selects the clinepass route; other
  env-only intents treat CLINE_API_KEY as a competing credential. Added
  isClinePassBaseUrl/getClinePassBaseUrlOverride and activeProfileBaseUrl option
  to resolveActiveRouteIdFromEnv so custom/unknown profiles targeting api.cline.bot
  resolve correctly.
- providerConfig.ts: resolveProviderRequest now reads CLINE_API_MODEL when
  CLINE_API_KEY is present and defaults the base URL to https://api.cline.bot/api/v1.
- providerProfiles.ts: CLINE_API_KEY is mirrored into startup/profile env for
  clinepass and custom profiles at api.cline.bot, and is checked in
  isProcessEnvAlignedWithProfile.
- parse.ts: toIsoDate drops invalid resetsAt values instead of echoing them.
- Add fetchClinePassUsage tests covering auth, headers, non-OK responses, and errors.
- Add routeMetadata and providerConfig regression tests for ClinePass env/model wiring.
- Add providerProfiles tests for CLINE_API_KEY propagation in apply/env and startup persistence.
- Remove extra blank line in .env.example.

Validation:
- bun run typecheck
- bun run build
- bun run test:provider (972 pass)
- bun run check (5346 pass; 1 unrelated Windows file-mode failure in branch.test.ts)

* fixup: address PR review findings for ClinePass env routing and profile persistence

- routeMetadata.ts: Let the concrete OPENAI_BASE_URL match win before falling
  back to activeProfileProvider / activeProfileBaseUrl. Prevents a saved ClinePass
  profile from overriding an explicit CLAUDE_CODE_USE_OPENAI env pointing at
  another gateway.
- providerConfig.ts: Gate the ClinePass model branch behind !isGithubMode so a
  stale CLINE_API_KEY/CLINE_API_MODEL does not override GitHub Copilot model
  selection.
- providerProfiles.ts: Introduce isClinePassProfile() predicate that uses route
  resolution (routeId === 'clinepass' || baseUrl includes api.cline.bot) and
  share it across live application, alignment, and startup persistence paths so
  saved ClinePass profiles keep the dedicated credential consistently even with
  non-default base URLs.
- Add regression tests covering all three findings.

Validation:
- bun run typecheck
- bun run build
- bun run test:provider (973 pass)

* fixup: use hostname-based ClinePass detection in providerProfiles

Replace includes('api.cline.bot') substring matching with isClinePassBaseUrl
which validates the exact hostname via URL parsing, preventing spoofed hosts
like api.cline.bot.evil.example from triggering CLINE_API_KEY mirroring.

Validation:
- bun run typecheck
- bun run build
- bun run test:provider (973 pass)

* fixup: gate ClinePass model selection on resolved base URL

Move base URL resolution before model selection in resolveProviderRequest
so effectiveClinePassMode is only active when no explicit non-ClinePass base
URL is set via options.baseUrl, OPENAI_BASE_URL, or OPENAI_API_BASE.

Previously CLINE_API_KEY=cp-key + CLINE_API_MODEL=cline-pass/qwen3.7-max
+ OPENAI_BASE_URL=https://api.openai.com/v1 would return
requestedModel=cline-pass/qwen3.7-max with baseUrl=https://api.openai.com/v1,
sending a ClinePass model ID to a non-Cline provider.

Now the resolver returns the correct OPENAI_MODEL and OpenAI base URL in that
scenario, and only uses ClinePass model/default-base when the resolved base
URL is absent or actually api.cline.bot.

Added regression tests for:
- CLINE_API_KEY + CLINE_API_MODEL + explicit OPENAI_BASE_URL
- CLINE_API_KEY + CLINE_API_MODEL + explicit baseUrl option
- CLINE_API_KEY + CLINE_API_MODEL with no base URL (ClinePass default)

Validation:
- bun run typecheck
- bun run build
- bun run test:provider (976 pass)

* fixup: default ClinePass model for blank env

Treat whitespace-only CLINE_API_MODEL as unset so OPENAI_MODEL can still provide the ClinePass model override.

When CLINE_API_KEY selects ClinePass without any model env, fall back to the ClinePass route default instead of the generic codexplan alias.

Validation:

- timeout 600 bun test src/services/api/providerConfig.test.ts

- timeout 600 bun test src/services/api/clinepassUsage.test.ts src/integrations/routeMetadata.test.ts src/services/api/providerConfig.test.ts src/utils/providerProfiles.test.ts src/integrations/compatibility.test.ts src/components/ProviderManager.test.tsx

- timeout 600 bun test src/services/api/providerConfig.test.ts src/services/api/client.test.ts src/integrations/routeMetadata.test.ts src/integrations/runtimeMetadata.test.ts src/services/api/clinepassUsage.test.ts src/services/api/minimaxUsage.test.ts src/utils/providerProfiles.test.ts

- timeout 600 bun run integrations:check

- timeout 600 bun run typecheck

- timeout 600 bun run build

- timeout 600 bun run security:pr-scan

- git diff --check origin/main...HEAD
2026-06-30 08:29:02 +08:00
BogdanandGitHub d0843bed0b fix(compaction): make snip nudges model-aware (#1816)
Scale HISTORY_SNIP context-efficiency nudges with the active model window so large-context sessions do not get prompted after fixed low token growth.

Keep existing reset behavior and add focused regression coverage for custom intervals and model-aware attachment gating.
2026-06-30 08:28:18 +08:00
0xfandomandGitHub 185ffea893 fix(ollama): cap qwen3-coder-next:cloud output at 32768 (#1814)
* fix(ollama): cap qwen3-coder-next:cloud output at 32768

Ollama Cloud rejects qwen3-coder-next:cloud requests with
max_tokens above 32768 (400). The shared qwen3-coder-next descriptor
stays at its 65536 default; add a gateway catalog override so only the
:cloud variant is capped to 32768, with the 262144 context window
inherited from the descriptor. A local qwen3-coder-next run keeps the
descriptor default.

Re-submission of the abandoned #1133, with the deepseek-v4-pro:cloud
trailing comma kept and one added after the new entry's notes.

* test(discovery): include qwen3-coder-next:cloud in the ollama stale-cache fixture

The new gateway catalog entry adds qwen3-coder-next:cloud to the ollama
route, so the stale-cache discovery fixture must list it alongside
deepseek-v4-pro:cloud and the discovered model.
2026-06-30 08:27:35 +08:00
keyarrandGitHub 5a7969785a feat: add /set-context-window and /clear-context-window commands (#1810)
* feat: add /set-context-window and /clear-context-window commands

Add session-scoped context window overrides for OpenAI-compatible models
that fall back to the default 128k context window.

- /set-context-window [model] <tokens>: set override for current model
- /clear-context-window [model]: clear override(s)
- Overrides are in-memory, per-session, and die with the process
- Auto-compact and all context display commands respect the override
- Minimum 32k tokens to avoid auto-compact floor paradox

* fix: tighten parsing, fix minimum floor, add tests

- Reject extra arguments in /set-context-window (require exactly 1 or 2 tokens)
- Reject non-integer tokens like '64000foo' with strict regex check
- Fix MIN_CONTEXT_WINDOW_OVERRIDE from 32k to 33k to match documented floor
- Add 8 tests for session override set/get/clear/normalization/precedence

* fix: test isolation, integer validation, env override precedence test

- Add clearSessionContextWindowOverride to beforeEach/afterEach for test isolation
- Add Number.isInteger check to setSessionContextWindowOverride
- Split regression test into env override and unknown model fallback cases
- Add test for fractional values (64000.5)

* fix: provider-qualified model isolation, session cleanup, model resolution

- Store full normalized model name (lowercase only, no prefix stripping)
- Lookup tries full name first, then stripped prefix fallback
- Provider-qualified names (zai-org/glm-5.2) no longer collide with unqualified (glm-5.2)
- Clear overrides in clearSessionCaches() so /clear resets state
- Resolve active model via getMainLoopModel() instead of raw context.options.mainLoopModel
- Add tests for known model precedence, provider isolation, session isolation

* fix: use session-active model, clear both exact and stripped keys

- Revert to context.options.mainLoopModel for session-active model (not global getMainLoopModel)
- clearSessionContextWindowOverride now deletes both exact and stripped-prefix keys
- Add regression test: clearing openai/gpt-4o also clears gpt-4o fallback

* fix: store both exact and stripped keys on write for symmetric lookup

- setSessionContextWindowOverride now stores both normalized and stripped-prefix keys
- Reading openai/gpt-4o via gpt-4o now works on first query (no fallback needed)
- Update test: provider-qualified writes now create both keys (symmetric with clear)

* test: canonicalize session context overrides and fix mixed-order alias tests

* test: verify CLAUDE_CODE_MAX_CONTEXT_TOKENS precedence over session overrides
2026-06-30 08:24:50 +08:00
0xfandomandGitHub 1cdca1cc7b fix(remote-session): match ingress host by hostname, not raw substring (#1792)
* fix(remote-session): match ingress host by hostname, not raw substring

isRemoteSessionLocal and isRemoteSessionStaging decided which Claude AI
base URL receives remote-session traffic (production, staging, or
http://localhost:4000) by testing whether the ingress URL contained the
substring 'localhost' / 'staging' anywhere in the string. A production
ingress URL that merely carried those words in its path or query — e.g.
https://claude.ai/code/x?ref=staging — was misrouted to the staging or
local-dev endpoint.

Parse the ingress hostname and match it precisely instead: localhost or
127.0.0.1 for local (mirroring isLocalhostBaseUrl), and a 'staging'
hostname label for staging. A malformed URL yields no match. The
session-id (_local_ / _staging_) signals are unchanged, and the real
local/staging hosts still resolve correctly. Add product.ts tests.

* fix(remote-session): allowlist real staging ingress hosts

The dot-label match (`hostname.split('.').includes('staging')`) both
missed the real default staging ingress host `api-staging.anthropic.com`
(no bare `staging` label, so staging remote-control links fell back to
production claude.ai) and over-matched unrelated hosts such as
`foo.staging.example.com`. Replace it with an explicit allowlist: exact
`api-staging.anthropic.com` or a subdomain of the `.staging.ant.dev`
zone. Add regression coverage for both directions.
2026-06-30 08:24:10 +08:00
0xfandomandGitHub 8859c5d6e2 fix(plugins): treat prototype-shadowing marketplace names as not found (#1787)
* fix(plugins): treat prototype-shadowing marketplace names as not found

Marketplace names are user-supplied and used directly as object keys for
membership and lookup (config[name]) throughout marketplaceManager. The config
came back as a normal object, so a name shadowing an Object.prototype member —
constructor, __proto__, toString, hasOwnProperty, valueOf — resolved up the
prototype chain to a truthy inherited value. 'claude plugin marketplace remove
constructor' therefore skipped the "not found" guard and acted on a bogus
inherited entry: it crashes on entry.installLocation when a plugin seed dir is
configured, or silently no-ops the removal otherwise, instead of reporting that
no such marketplace exists.

Return a null-prototype object from loadKnownMarketplacesConfig (and its safe
variant) so every config[name] lookup across this module is own-property exact.
Add a regression test covering removeMarketplaceSource for the shadowing names.

* fix(plugins): harden cache-only marketplace lookups against prototype names

getMarketplaceCacheOnly and getPluginByIdCacheOnly parsed
known_marketplaces.json directly with jsonParse, leaving the result on
the normal Object prototype. A marketplace name shadowing a prototype
member (constructor, __proto__, ...) resolved to the inherited value, so
the not-found guard was skipped and the readers took a bogus path. Route
both through toNullProtoConfig so the lookup matches the rest of the
module. Add cache-only regression coverage and assert the removal path
never rms a directory derived from an inherited entry.

* docs(plugins): reunite loadKnownMarketplacesConfig JSDoc with its function

The toNullProtoConfig helper and its comment were inserted between the
loader's JSDoc block and the loader, orphaning the documentation. Move
toNullProtoConfig (and its explanation) above the loader's JSDoc so the
doc sits directly on loadKnownMarketplacesConfig again, and cross-link
the null-prototype rationale from the loader doc.

* test(plugins): assert getMarketplaceCacheOnly never reads a bogus cache path

The direct getMarketplaceCacheOnly proto-name test only asserted a null
result, which the unhardened path also yields (it reaches
readCachedMarketplace on an inherited entry, which then fails and returns
null). Add a deterministic regression signal: seed a sentinel
installLocation on Object.prototype so a removed null-prototype guard
would drive the reader into a cache path derived from the bogus inherited
entry, and assert no such path is ever read. Fails if the
toNullProtoConfig wrapper is dropped from getMarketplaceCacheOnly alone.
2026-06-30 08:22:53 +08:00
BogdanandGitHub 9bf6aa2308 feat(session): add branch command for conversation forks (#1808)
* feat(session): add branch command for conversation forks

* test(session): harden branch test cache cleanup

* test(session): isolate branch loader checks

* test(session): guard branch project cache cleanup
2026-06-29 18:06:28 +08:00
259c7ec27a fix(ollama): preserve chat history with native context (#1805)
* fix(ollama): preserve chat history with native context

Route Ollama chat requests through the native /api/chat endpoint so OpenClaude can send request-level options.num_ctx instead of relying on Ollama's OpenAI-compatible shim.

Default the Ollama request context to 32768 tokens, support OPENCLAUDE_OLLAMA_NUM_CTX and OLLAMA_CONTEXT_LENGTH overrides, and map max tokens/temperature/top_p into native Ollama options.

Adapt native Ollama streaming and non-streaming responses back into the existing OpenAI-shaped conversion pipeline, including usage, text, structured tool calls, and tool_use stop reasons.

Normalize native Ollama request messages for images and historical tool calls, avoiding OpenAI-only image_url/id/type payload fields in /api/chat requests.

Add Ollama context diagnostics, loopback-only ollama ps status checks, regression coverage, and documentation for verifying active context length.

* fix(ollama): address native routing review feedback

* fix(ollama): restrict loopback host matching

* fix(ollama): exclude wildcard bind address

* fix(ollama): keep https localhost proxies on chat completions

---------

Co-authored-by: jatmn <12479882+jatmn@users.noreply.github.com>
2026-06-29 18:04:42 +08:00
b15660e388 chore: remove unused atomic chat python helper (#1804)
Remove the unused top-level Python Atomic Chat helper and its dedicated pytest coverage. The active Atomic Chat provider path is implemented in TypeScript through the OpenAI-compatible shim, so keeping this standalone Python module only adds dead code.

Leave the remaining Python helper modules, pytest suite, and CI workflow intact.

Co-authored-by: jatmn <12479882+jatmn@users.noreply.github.com>
2026-06-29 17:58:49 +08:00
ff8d47d6c5 fix(status-line): estimate usage for unsupported providers (#1803)
Treat all-zero assistant usage from providers that do not report token counts as unsupported usage instead of real zero usage. Estimate current input and output tokens from the transcript, mark the values as estimated in the status-line payload, and use estimated totals whenever the active provider lacks usage data so mixed-provider sessions do not show stale cumulative totals.

Preserve tiny nonzero context percentages in the status JSON and render them as ctx <1% in the built-in status line instead of rounding back to 0%. Update the /statusline setup schema and add focused regression coverage for normal reported usage, unsupported all-zero usage, stale-provider fallback avoidance, tiny percentage display, and context percentage rounding.

Validation run locally with Bun 1.3.13: bun run build, bun run smoke, bun run typecheck, bun run typecheck:type-tests, focused tests for tokens/context/BuiltinStatusLine, bun run check, bun run test:provider, bun run test:provider-recommendation, python -m pytest -q python/tests, and bun run security:pr-scan -- --base upstream/main.

Co-authored-by: jatmn <12479882+jatmn@users.noreply.github.com>
2026-06-29 17:57:58 +08:00
BogdanandGitHub a47493342f feat(report): generate deterministic session task reports (#1802)
* feat(report): generate deterministic session task reports

* fix(report): address task report review findings

* fix(report): stabilize task report paths on Windows

* test(report): expect redacted git metadata cwd

* test(report): assert literal redacted git cwd

* fix(report): capture PowerShell and backgrounded validations

* fix(report): detect quoted validation commands

* fix(report): reconcile background validation notifications

* fix(report): keep foreground command statuses authoritative

* test(report): assert command status precedence
2026-06-29 17:57:09 +08:00
BogdanandGitHub 8023356841 feat(session): harden fork-session branching (#1801)
* feat(session): harden fork-session branching

Add explicit fork-session branching metadata, preserve fork-owned transcript state, and seed retained content replacement records for forked resumes.

Document --fork-session behavior and cover forked resume transcript/materialization behavior with focused tests.

* fix(session): respect print persistence for fork seeding
2026-06-28 06:17:29 +08:00
BogdanandGitHub 320d63c812 fix(compaction): skip microcompact when compaction is off (#1800)
* fix(compaction): respect disabled microcompact setting

Skip automatic query-loop microcompact when the message-count compaction threshold is explicitly set to off, while preserving default, numeric, and explicit compact behavior.

* test(query): isolate auto-compact config regression

* test(query): type auto-compact deps fixture
2026-06-28 06:16:42 +08:00
JATMNandGitHub ab8645da9f fix: OpenClaude native launcher after Linux install (#1798)
* fix linux openclaude native install launcher

Create the package-aware OpenClaude launcher for native installs while keeping the downloaded native payload name unchanged.

Repair the native launcher after npm cleanup with a relink-only helper so npm uninstall cannot remove ~/.local/bin/openclaude.

Update protocol registration and cleanup fallbacks to use the OpenClaude command name, with regression coverage for install surfaces.

* test linux install repair flow
2026-06-28 06:16:04 +08:00
JATMNandGitHub 13cf30afa4 fix(moonshot): Add verified Kimi effort metadata (#1796)
* Add verified Kimi effort metadata

Add kimi-k2.7-code to the Kimi Code gateway catalog and the direct Moonshot catalog, ordered newest/highest first.

Annotate Moonshot and Kimi Code Kimi models with context/output limits, reasoning capabilities, and reasoning_effort metadata limited to the verified low/medium/high levels.

Add runtime metadata, effort resolution, and provider override tests covering catalog order, qualified aliases, stale xhigh clamping, and Kimi Code request serialization.

Validation run locally: bun run build; bun run smoke; bun run check; bun run test:provider; bun run typecheck; bun run integrations:check; py -3.13 -m pytest -q -p no:cacheprovider python/tests; bun run typecheck:type-tests; bun run test:provider-recommendation; bun run security:pr-scan; focused effort/runtime/client/providerConfig tests; git diff --check.

* Fix Kimi K2.7 PR review findings

Clamp Kimi K2.7 max output metadata to 32,768 for Kimi Code, direct Moonshot, and Atlas Cloud so runtime limits do not advertise the context window as a generation cap.

Update the Moonshot runtime metadata assertion to the corrected cap and keep Hicap's separate K2.7 cap unchanged.

Restore globalThis.fetch in the Kimi Code providerOverride test with try/finally to avoid cross-test leakage.

Validation: bun test --feature=UNATTENDED_RETRY src/integrations/runtimeMetadata.test.ts src/services/api/client.test.ts src/utils/effort.codex.test.ts; bun run integrations:check; bun run build; bun run test:provider; bun run typecheck; git diff --check; bun run check.

* Fix Moonshot K2.6 and K2.5 output caps

Raise direct Moonshot Kimi K2.6 and K2.5 max output metadata from the default token count to the provider context-budget ceiling.

Add runtime metadata assertions so direct Moonshot K2.6 and K2.5 keep the 262,144 output cap while K2.7 Code remains capped at 32,768.

Validation: bun test --feature=UNATTENDED_RETRY src/integrations/runtimeMetadata.test.ts; bun run integrations:check; bun run typecheck; git diff --check; bun run build.

* Clamp Atlas Kimi K2.7 effort levels

Remove xhigh from the Atlas Cloud moonshotai/kimi-k2.7-code metadata so the gateway no longer advertises an unverified effort level for that model.

Update providerOverride and effort metadata tests to cover the exact Atlas K2.7 path and assert stale xhigh settings clamp to high.

Validation: bun test --feature=UNATTENDED_RETRY src/services/api/client.test.ts src/utils/effort.codex.test.ts src/integrations/runtimeMetadata.test.ts; git diff --check; bun run integrations:check; bun run typecheck; bun run build.

* Address Kimi metadata review findings
2026-06-27 11:25:06 +08:00
JATMNandGitHub 6f794f4185 fix(xAI): Update xAI model metadata and effort handling (#1795)
* Update xAI model metadata and effort handling

Refresh xAI direct provider catalog with live Grok 4.3, Grok Build, and Grok 4.20 model metadata, aliases, context windows, and output caps.

Align Atlas Cloud xAI entries with the refreshed model aliases and verified effort semantics.

Treat Grok Build as a coding model without reasoning effort support while keeping its xAI Responses endpoint override.

Update xAI defaults, display names, alias descriptor lookup, and regression coverage for runtime metadata, effort selection, context limits, model options, and vision capability lookup.

Validated with bun run integrations:check, focused provider/model tests, bun run typecheck, bun run build, and git diff --check.

* Address xAI PR review findings

Add the Grok 4.20 max output cap to both xAI descriptor paths and assert the runtime metadata.

Keep route-less vision catalog lookup from resolving aliases globally while preserving route-specific alias resolution.
2026-06-27 09:26:46 +08:00