v0.49.0
One unified tool calling surface. This release consolidates every tool's parameters into a single convention, identical on OpenCode, Pi, and MCP — one file parameter name, one edit calling shape, 1-based indexing everywhere, and no routing knobs. Schema descriptions changed for most tools, so prompt prefix caches re-warm once after upgrading; there are no config changes and no manual migration steps.
One parameter name: path
- Every tool that takes a file now takes
path—read,write,edit, and the wholeaft_*family (aft_zoom,aft_outline,aft_import,aft_refactor,aft_safety,aft_delete,aft_move,aft_callgraph). The mixedfilePath/pathsplit across tools was a documented source of model confusion (#165). aft_callgraph'stoFileis nowtoPathfor the same reason.- The old spellings still work everywhere:
filePath(andtoFile) are accepted silently at every boundary — both plugins, the daemon transport, and MCP clients with cached schemas. They are simply no longer in the schemas. Supplying both spellings with different values is rejected rather than guessing.
One edit shape: edits[]
editnow has exactly three modes, one per call:edits[](find/replace and line-range items),appendContent, orsymbol+content. Mode conflicts and unknown parameters are rejected with errors that name the fix, replacing the old silent mode-priority ladder that made results hard to predict (#165).- The top-level
oldString/newStringform is gone from the schema but still accepted: it folds into a one-itemedits[]internally. Pi-styleoldText/newTextitem names and JSON-stringifiededitsarrays (a shape some models emit) are absorbed the same way. - Batch ambiguity errors are now actionable: when a find/replace item matches multiple locations, the response is
ambiguous_matchwith numbered candidates (line and context for each) instead of a bare failure.
occurrence is 1-based
- The
occurrenceselector on find/replace edits is now 1-based:occurrence: 1selects the first match. It was the only 0-based index in a surface where lines, columns, and candidates are all 1-based, and production logs showed models passingoccurrence: 1to mean "the first match" and failing on exactly that.occurrence: 0is now rejected with a clear message, and ambiguity candidates carry the same 1-based numbers, so the error and the retry finally agree.
aft_search routes itself
- The
hintparameter (regex/literal/semantic) is removed. Usage analysis across ~85,000 production searches showed forced lanes returned zero results at roughly 100x the rate of automatic routing (literal 21.8%, regex 23.2%, auto 0.2%) — mostly models forcing a lane with a query shaped for a different one, then abandoning the tool. Routing is now always automatic; ahintargument from older sessions is accepted and ignored. - Automatic routing gained a second chance: when the chosen lane finds nothing, the query is re-ranked by its terms through the hybrid index, and the result footer says so (
[interpreted_as: literal; no exact match — ranked by terms instead]). Zero results after that means the content genuinely is not there. grepis unchanged and remains the tool for explicit regex and exhaustive literal matching.
Trust corporate and custom certificate authorities
- HTTPS requests (semantic-search embedding backends, URL fetching, binary downloads from the Rust side) now verify TLS through the operating system's trust store — macOS Keychain, Windows certificate store, and the standard Linux mechanisms including
SSL_CERT_FILE. Corporate proxies with custom root CAs work without workarounds, and certificate failures are no longer retried as if they were transient network errors (#168). - Platform verification also avoids enumerating the system trust store on every request — on machines with large or unusual keychains, the previous approach could stall requests for seconds.
UI fixes
- OpenCode Desktop and web now render edit diffs for files larger than the 512KB diff cap; previously those edits showed an empty diff panel. Edit and write results carry the same patch-based diff payload the native tools use.
- Doctor and setup messages now show the correct
npx @cortexkit/aftinvocation instead of the bareaft, which pointed at the wrong binary (the Rust worker has nodoctorcommand).
Compatibility
- No action needed for existing configurations, indexes, or caches.
- Agents notice exactly one behavior change:
occurrenceis 1-based, andoccurrence: 0is rejected with a message that explains the new indexing. - MCP clients get the same unified schemas; old argument spellings from cached schemas keep working.