Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
e107b4b
chore: untrack .specgit.yaml as permanent local-only state
LeXwDeX Aug 28, 2026
5763c84
fix(opencode): display proper session import errors (#36258)
OpeOginni Jul 20, 2026
8e64cf5
refactor(opencode): simplify retry error matching (#40694)
rekram1-node Aug 5, 2026
f7a32a0
fix(opencode): expand retryable error patterns (#40707)
rekram1-node Aug 5, 2026
2577c5b
fix(opencode): ignore unknown config fields (#41312)
opencode-agent[bot] Aug 8, 2026
8438aaf
fix(opencode): cap session retries with jitter (#41939)
rekram1-node Aug 12, 2026
45fd3f9
fix(opencode): retry xAI capacity stream errors (#43640)
opencode-agent[bot] Aug 20, 2026
177b0a7
fix(opencode): retry network error variants (#43806)
opencode-agent[bot] Aug 21, 2026
1bd34f7
fix(opencode): retry raw network finish errors (#43813)
opencode-agent[bot] Aug 21, 2026
ce0ab5d
fix(opencode): send parent session header (#44752)
opencode-agent[bot] Aug 24, 2026
0eeaec7
fix(core): refresh cached remote skills (#34059)
nexxeln Jun 26, 2026
e39d726
fix(core): authorize external read paths
thdxr Jun 26, 2026
49061a4
fix(core): expand home-relative permission paths (#35737)
StarpTech Jul 7, 2026
ab55ae4
fix(core): watch only git projects (#35957)
opencode-agent[bot] Jul 8, 2026
c19c51c
fix(core): disable unused fff content caches (#36453)
opencode-agent[bot] Jul 11, 2026
547401f
fix(core): tolerate AlreadyExists in FSUtil.ensureDir (#36542)
BB-84C Jul 15, 2026
7e8c575
fix(core): correct MCP environment field in built-in skill (#39175)
OpeOginni Jul 27, 2026
752deee
fix(core): preserve unicode in grep previews (#42356)
kitlangton Aug 13, 2026
f703c12
fix(provider): enable sonnet 5 adaptive thinking (#34673)
rekram1-node Jun 30, 2026
0cc84f4
fix(provider): force openai reasoning variants (#34702)
rekram1-node Jul 1, 2026
88f652e
fix(provider): preserve OpenRouter small model effort (#35478)
opencode-agent[bot] Jul 5, 2026
64e6d79
fix(opencode): expose Grok reasoning variants (#36017)
rekram1-node Jul 9, 2026
2b5a373
fix(provider): route gateway variants by api id (#36614)
rekram1-node Jul 13, 2026
007af57
fix(xai): default store to false for Responses (#36629)
geraint0923 Jul 13, 2026
82fb943
fix(opencode): default Meta reasoning to xhigh (#36976)
opencode-agent[bot] Jul 15, 2026
bccf350
fix(provider): normalize Mistral family tool call IDs (#37982)
rekram1-node Jul 20, 2026
142d198
fix(provider): correct MiniMax M3 thinking variants (#38330)
rekram1-node Jul 22, 2026
526c4e1
fix(provider): select prompt cache keys by SDK (#38424)
rekram1-node Jul 23, 2026
2c0227c
fix(provider): omit deprecated Gemini sampling defaults (#38924)
opencode-agent[bot] Jul 28, 2026
1e4c3be
fix: gpt-5.5+ in combination with azure fails with reasoningEffort (#…
frederiknsgo Aug 3, 2026
cff1d8e
test(opencode): cover Azure completion reasoning (#40340)
opencode-agent[bot] Aug 3, 2026
929331b
fix(provider): scope DeepSeek V4 Flash sampling defaults (#41620)
opencode-agent[bot] Aug 11, 2026
49d8e5d
fix(opencode): detect Copilot PDF input support (#41522)
stevenao Aug 11, 2026
6783b0b
fix(core): fall back on oversized websocket requests (#43099)
neriousy Aug 17, 2026
34d0505
fix(opencode): properly show authed providers on /connect command (#3…
OpeOginni Aug 18, 2026
2b31cad
fix(opencode): remove Qwen sampling defaults (#43310)
opencode-agent[bot] Aug 18, 2026
3eab934
fix(provider): guard textVerbosity injection for @ai-sdk/openai-compa…
joelstucki-taulia Aug 21, 2026
c77cde3
fix(provider): filter unreplayable Bedrock reasoning before caching (…
opencode-agent[bot] Aug 27, 2026
bc860e6
feat(llm): pass strict through tool definitions for Codex parity (#33…
rekram1-node Jun 25, 2026
25db352
feat(core): split MCP timeout configuration (#33977)
opencode-agent[bot] Jun 26, 2026
6488bd6
fix(acp): enrich permission prompts (#34079)
nexxeln Jun 26, 2026
327e432
fix(llm): end reasoning before responses
thdxr Jun 26, 2026
e2f9191
feat(llm): add response reducer (#34417)
nexxeln Jun 29, 2026
e9a7e8f
test(llm): verify generate reducer law (#34418)
nexxeln Jun 29, 2026
a4b2123
test(llm): lock event reducer laws (#34423)
nexxeln Jun 29, 2026
243c603
feat(llm): enforce request precedence (#34440)
nexxeln Jun 29, 2026
fde187d
feat(llm): add tool schema projections (#34454)
nexxeln Jun 29, 2026
24be3b5
fix(core): expose models.dev modes as models (#34521)
rekram1-node Jun 29, 2026
c5da95c
feat(tui): exit subagent menu with up arrow (#36951)
opencode-agent[bot] Jul 15, 2026
aaba2dc
fix(server): don't forward host directory to remote workspace (#40136)
jamesmurdza Aug 4, 2026
b28bc52
fix(server): log upstream 5xx bodies from proxied workspace requests …
jamesmurdza Aug 4, 2026
c642589
fix(tui): support copying over ssh with `set-clipboard on` tmux confi…
ayubun Aug 6, 2026
ff47b05
fix(skills): Update global config path in documentation (#42337)
TechyAditya Aug 13, 2026
11ffd09
feat(llm): add model defaults and compatibility data (#34436)
nexxeln Jun 29, 2026
6a2eb20
docs(release): add v1.0.36 series notes
LeXwDeX Aug 29, 2026
a24eef8
Merge pull request #462 from LeXwDeX/feat/sync-upstream
LeXwDeX Aug 29, 2026
0fa37f7
fix(tui): re-exec with package tsconfig when cwd cannot compile solid…
LeXwDeX Aug 29, 2026
833cc86
chore: record delivery binding for tui-crashes-on
LeXwDeX Aug 29, 2026
71c8a85
chore: record delivery binding for tui-crashes-on
LeXwDeX Aug 29, 2026
cd2ba42
Merge pull request #464 from LeXwDeX/fix/tui-jsx-runtime
LeXwDeX Aug 29, 2026
ef5a43a
chore: sync main to dev (resolve specgit local-asset conflicts)
LeXwDeX Aug 29, 2026
e533acf
Merge pull request #467 from LeXwDeX/chore/sync-main-to-dev
LeXwDeX Aug 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions .github/releases/v1.0.36.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
## opencode {VERSION}

{Prerelease/Stable} release from `{branch}` branch. Phased upstream sync: 53 curated commits absorbed from sst/opencode: session retry hardening, 17 provider-correctness fixes, an LLM reducer/schema feature set, MCP timeout configuration, and core stability fixes: with zero overlap into the fork-owned DAG / SpecGit / release-pipeline surfaces.

---

### 🎯 Features

- **MCP timeout configuration split, #33977**: startup and request phases now take independent timeout settings instead of one shared knob, so slow server initialization no longer starves per-call budgets.
- **LLM response reducer feature set, #34417/#34418/#34423/#34440/#34454**: response and event reducers with locked reducer laws, enforced request precedence, and tool schema projections in `packages/llm`.
- **Model defaults and compatibility data, #34436**: `packages/llm` models now carry `defaults` and `compatibility` metadata, the foundation the protocol fixes below build on.
- **Strict tool definitions passthrough, #33392**: tool definitions carry a `strict` flag for OpenAI Codex parity, defaulting to `false` so wire behavior is unchanged.
- **TUI subagent menu exits on up arrow, #36951**: pressing Up at the top of the subagent menu now closes it instead of doing nothing.
- **models.dev modes surface as standalone models, #34521**: modes are no longer collapsed into variants, making mode-specific model selection visible downstream.

---

### 🐛 Bug Fixes

- **Session retry hardening, #40694/#40707/#41939/#43640/#43806/#43813**: retry error matching simplified then expanded (xAI capacity, network error variants, raw `network_error` finish reasons), with exponential backoff capped by jitter so a flapping provider can no longer produce unbounded retry storms.
- **Parent session header missing, #44752**: proxied requests now send the parent session header.
- **Session import errors were opaque, #36258**: import failures now surface the underlying cause.
- **Unknown config fields broke parsing, #41312**: unknown top-level config keys are ignored instead of failing the parse, keeping older and forward configs loadable.
- **Provider correctness sweep, #34673/#34702/#35478/#36017/#36614/#36629/#36976/#37982/#38330/#38924/#41620/#43310/#43915/#45769**: sonnet 5 adaptive thinking, forced OpenAI reasoning variants, OpenRouter small-model effort preserved, Grok reasoning variants exposed, gateway variants routed by API id, xAI Responses `store` defaulted off, Meta reasoning defaulted to xhigh, Mistral tool-call IDs normalized, MiniMax M3 thinking variants corrected, deprecated Gemini sampling defaults omitted, DeepSeek V4 Flash sampling scoped, Qwen sampling defaults removed, `textVerbosity` injection guarded on openai-compatible providers, and unreplayable Bedrock reasoning filtered before prompt caching.
- **Prompt cache keys selected by SDK, #38424**: cache-key assignment keys off the npm SDK rather than provider id, fixing cross-provider cache misses.
- **Azure gpt-5.5+ reasoningEffort failure, #40265**: gpt-5.5-class models on Azure no longer fail with invalid reasoning effort.
- **Copilot PDF input detection, #41522**: models advertising PDF media support are detected from capabilities instead of a hardcoded list.
- **`/connect` hid authenticated providers, #39915**: already-authed providers now show correctly in the connect list.
- **Core stability, #34059/e39d726e/#35737/#35957/#36453/#36542/#39175/#42356/#43099**: remote skills refresh from cache, external read path authorization, home-relative permission paths matched on POSIX and Windows, the file watcher only watches git projects, unused FFF content caches disabled, `FSUtil.ensureDir` tolerates `AlreadyExists`, the built-in skill's MCP environment field corrected, grep previews preserve unicode, and oversized WebSocket requests fall back instead of dying.
- **Server safety and observability, #40135/#40136**: proxied workspace requests log upstream 5xx bodies for debugging, and the host directory is no longer forwarded to remote workspaces.
- **ACP permission prompts enriched, #34079**: permission prompts carry the context needed to make a decision.
- **TUI clipboard over ssh, #30472**: copying works under tmux `set-clipboard on` remote sessions.
- **LLM reasoning closed before responses, 327e432d**: reasoning is finalized before the response phase to avoid interleaved-state corruption.

---

### 🧪 Test Summary

- `bun typecheck` green across opencode/core/llm; `test:dag-core` behavior and coverage gate green (767 assertions).
- `packages/llm` suite 309 pass / 0 fail; targeted provider, config, and processor suites (505 tests) 0 fail.

```
typecheck: opencode PASS core PASS llm PASS
test:dag-core: 767 assertions, 0 fail
packages/llm: 309 pass / 0 fail, targeted suites: 505 pass / 0 fail
```

---

### 🔍 Verification

- Fork-owned surfaces (DAG runtime, SpecGit harness, release pipeline, TUI feature plugins) verified untouched: zero diff overlap, with `compaction.ts`, `azure.ts`, and `layer-node.ts` byte-identical to the pre-sync base.
- Two-axis code review (Standards/Spec) passed: no conflict-marker residue, no duplicate test blocks, all skipped upstream commits confirmed absent from the tree.

---

**Full changelog:** [`{previous_tag}`...`{current_tag}`](https://github.com/LeXwDeX/OpenCode-GraphAgent/compare/{previous_tag}...{current_tag})
12 changes: 6 additions & 6 deletions .specgit.yaml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
version: 1
delivery: release-notes-v1-0-35
delivery: tui-crashes-on
context:
kind: branch
branch: docs/460-release-notes-v1-0-35
branch: fix/465-tui-crashes-on
issues:
- 460
- 465
issueKinds:
- issue: 460
kind: kind::docs
pr: 461
- issue: 465
kind: kind::fix
pr: 466
15 changes: 12 additions & 3 deletions packages/core/src/config/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@ export * as ConfigMCP from "./mcp"
import { Schema } from "effect"
import { PositiveInt } from "../schema"

export class Timeout extends Schema.Class<Timeout>("ConfigV2.MCP.Timeout")({
startup: PositiveInt.pipe(Schema.optional).annotate({
description: "Maximum time in milliseconds to establish and initialize the MCP server.",
}),
request: PositiveInt.pipe(Schema.optional).annotate({
description: "Maximum time in milliseconds to wait for each MCP request after initialization.",
}),
}) {}

export class Local extends Schema.Class<Local>("ConfigV2.MCP.Local")({
type: Schema.Literal("local"),
command: Schema.String.pipe(Schema.Array),
Expand All @@ -11,7 +20,7 @@ export class Local extends Schema.Class<Local>("ConfigV2.MCP.Local")({
}),
environment: Schema.Record(Schema.String, Schema.String).pipe(Schema.optional),
disabled: Schema.Boolean.pipe(Schema.optional),
timeout: PositiveInt.pipe(Schema.optional),
timeout: Timeout.pipe(Schema.optional),
}) {}

export class OAuth extends Schema.Class<OAuth>("ConfigV2.MCP.OAuth")({
Expand All @@ -28,12 +37,12 @@ export class Remote extends Schema.Class<Remote>("ConfigV2.MCP.Remote")({
headers: Schema.Record(Schema.String, Schema.String).pipe(Schema.optional),
oauth: Schema.Union([OAuth, Schema.Literal(false)]).pipe(Schema.optional),
disabled: Schema.Boolean.pipe(Schema.optional),
timeout: PositiveInt.pipe(Schema.optional),
timeout: Timeout.pipe(Schema.optional),
}) {}

export const Server = Schema.Union([Local, Remote]).pipe(Schema.toTaggedUnion("type"))

export class Info extends Schema.Class<Info>("ConfigV2.MCP")({
timeout: PositiveInt.pipe(Schema.optional),
timeout: Timeout.pipe(Schema.optional),
servers: Schema.Record(Schema.String, Server).pipe(Schema.optional),
}) {}
43 changes: 39 additions & 4 deletions packages/core/src/config/plugin/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ import { FSUtil } from "../../fs-util"
import { ModelV2 } from "../../model"
import { ConfigAgentV1 } from "../../v1/config/agent"
import { ConfigMigrateV1 } from "../../v1/config/migrate"
import { Global } from "../../global"
import { PermissionV2 } from "../../permission"
import type { LocationMutation } from "../../location-mutation"
import type { ReadTool } from "../../tool/read"
import type { EditTool } from "../../tool/edit"

const legacySources = [
{ pattern: "{agent,agents}/**/*.md", primary: false },
Expand All @@ -19,6 +24,11 @@ const legacySources = [
const decodeAgent = Schema.decodeUnknownOption(ConfigAgent.Info)
const decodeLegacyAgent = Schema.decodeUnknownOption(ConfigAgentV1.Info)
const decodeConfig = Schema.decodeUnknownOption(Config.Info)
type PathAction =
| LocationMutation.ExternalDirectoryAuthorization["action"]
| typeof ReadTool.name
| typeof EditTool.name
const pathActions = ["external_directory", "read", "edit"] as const satisfies readonly PathAction[]
const agentKeys = new Set([
"model",
"variant",
Expand All @@ -38,6 +48,7 @@ export const Plugin = define({
effect: Effect.fn(function* (ctx) {
const config = yield* Config.Service
const fs = yield* FSUtil.Service
const global = yield* Global.Service
yield* ctx.agent.transform(
Effect.fn(function* (draft) {
const documents = yield* Effect.forEach(yield* config.entries(), (entry) => {
Expand All @@ -56,11 +67,14 @@ export const Plugin = define({
)
})
}).pipe(Effect.map((documents) => documents.flat()))
const global = documents.flatMap((document) => document.info.permissions ?? [])
const permissions = expandPermissions(
documents.flatMap((document) => document.info.permissions ?? []),
global.home,
)
const configuredDefault = Config.latest(documents, "default_agent")
if (configuredDefault !== undefined) draft.default(AgentV2.ID.make(configuredDefault))
for (const current of draft.list()) {
draft.update(current.id, (agent) => agent.permissions.push(...global))
draft.update(current.id, (agent) => agent.permissions.push(...permissions))
}

for (const document of documents) {
Expand All @@ -73,7 +87,7 @@ export const Plugin = define({

const exists = draft.get(agentID) !== undefined
draft.update(agentID, (agent) => {
if (!exists) agent.permissions.push(...global)
if (!exists) agent.permissions.push(...permissions)
if (item.model !== undefined) {
const model = ModelV2.parse(item.model)
agent.model = { id: model.modelID, providerID: model.providerID, variant: agent.model?.variant }
Expand All @@ -91,7 +105,9 @@ export const Plugin = define({
if (item.hidden !== undefined) agent.hidden = item.hidden
if (item.color !== undefined) agent.color = item.color
if (item.steps !== undefined) agent.steps = item.steps
if (item.permissions !== undefined) agent.permissions.push(...item.permissions)
if (item.permissions !== undefined) {
agent.permissions.push(...expandPermissions(item.permissions, global.home))
}
})
}
}
Expand All @@ -100,6 +116,25 @@ export const Plugin = define({
}),
})

function expandPermissions(rules: PermissionV2.Ruleset, home: string): PermissionV2.Ruleset {
// Expand only resources tools resolve as filesystem paths. Bash resources are raw shell text:
// rewriting `$HOME/private/**` would miss `$HOME/private/key`, and safe expansion needs shell-aware parsing.
return rules.map((rule) => (isPathAction(rule.action) ? { ...rule, resource: expandHome(rule.resource, home) } : rule))
}

function isPathAction(action: string): action is PathAction {
return pathActions.some((item) => item === action)
}

function expandHome(resource: string, home: string) {
if (resource.startsWith("~/")) return home + resource.slice(1)
if (resource === "~") return home
if (resource === "$HOME") return home
if (resource.startsWith("$HOME/")) return home + resource.slice(5)
if (resource.startsWith("$HOME\\")) return home + resource.slice(5)
return resource
}

function discover(fs: FSUtil.Interface, directory: string) {
return Effect.forEach(legacySources, (source) =>
fs
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/filesystem/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ export const fffLayer = Layer.effect(
Fff.create({
basePath: location.directory,
aiMode: true,
disableMmapCache: true,
disableContentIndexing: true,
}),
catch: (cause) => cause,
}).pipe(
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/filesystem/watcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export const layer = Layer.effect(
const config = (yield* (yield* Config.Service).entries())
.filter((entry): entry is Config.Document => entry.type === "document")
.flatMap((item) => item.info.watcher?.ignore ?? [])
if (yield* Flag.OPENCODE_EXPERIMENTAL_FILEWATCHER) {
if (location.vcs && (yield* Flag.OPENCODE_EXPERIMENTAL_FILEWATCHER)) {
yield* Effect.forkScoped(
subscribe(location.directory, [...Ignore.PATTERNS, ...config, ...protecteds(location.directory)]),
)
Expand Down
9 changes: 8 additions & 1 deletion packages/core/src/fs-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,14 @@ export namespace FSUtil {
})

const ensureDir = Effect.fn("FileSystem.ensureDir")(function* (path: string) {
yield* fs.makeDirectory(path, { recursive: true })
yield* fs.makeDirectory(path, { recursive: true }).pipe(
// Bun on Windows can throw EEXIST here despite recursive mode.
// https://github.com/oven-sh/bun/issues/21901
Effect.catchIf(
(error) => error.reason._tag === "AlreadyExists",
(error) => isDir(path).pipe(Effect.flatMap((exists) => (exists ? Effect.void : Effect.fail(error)))),
),
)
})

const writeWithDirs = Effect.fn("FileSystem.writeWithDirs")(function* (
Expand Down
Loading
Loading