diff --git a/AGENTS.md b/AGENTS.md index ac89841bb..a1990625a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -173,26 +173,33 @@ Use `context_history` to navigate the edit DAG: - Log all bugs in root `BUGS.md`, not per-package. Do not create `packages/*/BUGS.md`. - Tracking docs (`PLAN.md`, `STATUS.md`, `WHAT_WE_DID.md`, `DO_NEXT.md`, `BUGS.md`, `GAP_ANALYSIS.md`, `UPSTREAM_STATUS.md`) live at repo root. - Do not create tracking/status markdown files inside `packages/`. +- Update continuity docs at the end of every task, before committing the implementation PR. +- Write continuity docs in past tense so they remain accurate after the PR merges. Avoid wording like "PR is open", "waiting for merge", or "monitor checks" unless the user explicitly asks for a pre-merge handoff. +- Bundle continuity-doc updates with the same feature/fix PR that changed the work. Do not create a separate docs-only PR just to mark a prior PR merged or advance the phase; instead, make the docs accurate for the post-merge state before opening the implementation PR. ## Phase Methodology Work follows the phased plan in `PLAN.md`. Each phase has exit criteria. **Before starting a phase:** + 1. Read `PLAN.md` to understand the phase scope and exit criteria 2. Read `GAP_ANALYSIS.md` to understand current state vs target 3. Read `DO_NEXT.md` for immediate actions 4. Read `BUGS.md` for any open issues that might affect the phase **After completing a phase:** + 1. Update `BUGS.md` — close fixed issues, add any new ones found 2. Update `STATUS.md` — refresh metrics (test count, error count, phase progress) 3. Update `WHAT_WE_DID.md` — add summary of work done 4. Update `GAP_ANALYSIS.md` — mark completed gaps, update current state 5. Update `DO_NEXT.md` — point to the next phase -6. Update `PLAN.md` — mark phase as complete, note PR number +6. Update `PLAN.md` — mark phase as complete, note that the PR merged, and leave next-phase instructions +7. Keep all continuity wording accurate after merge, even before the PR has actually merged locally **When starting a new session:** + 1. Review `STATUS.md` and `DO_NEXT.md` to orient 2. Verify tests pass and tsgo has 0 errors before making changes 3. Follow the current phase in `PLAN.md` diff --git a/BUGS.md b/BUGS.md index 3a69ed920..f25a0a5fa 100644 --- a/BUGS.md +++ b/BUGS.md @@ -4,9 +4,9 @@ Root bug tracker. Do not create per-package `BUGS.md` files. ## Open Security -| ID | Severity | Issue | Location | Status | Next action | -| --- | --- | --- | --- | --- | --- | -| S3 | High | Untrusted `.opencode/` autoloading for MCP/plugins | `packages/opencode/src/mcp`, `packages/opencode/src/plugin` | Mitigated with warning, not fully fixed | Design and implement workspace trust prompt before loading local MCP/plugin config | +| ID | Severity | Issue | Location | Status | Next action | +| --- | -------- | -------------------------------------------------- | ----------------------------------------------------------- | --------------------------------------- | ---------------------------------------------------------------------------------- | +| S3 | High | Untrusted `.opencode/` autoloading for MCP/plugins | `packages/opencode/src/mcp`, `packages/opencode/src/plugin` | Mitigated with warning, not fully fixed | Design and implement workspace trust prompt before loading local MCP/plugin config | ## Open Bugs @@ -14,47 +14,49 @@ No confirmed open runtime bugs. If a test, typecheck, lint, or runtime failure appears during upstream backports, either fix it in the same PR or add it here with enough detail for a fresh session to reproduce. +## Open Test Failures + +No confirmed open test failures. A sandboxed full-suite run failed on 2026-06-06 with socket, watcher, dependency-resolution, and MCP OAuth browser symptoms, but the user-approved unsandboxed rerun passed with `1557 pass`, `8 skip`, `0 fail`. + ## Open Edge Cases -| ID | Severity | Issue | Location | Status | Next action | -| --- | --- | --- | --- | --- | --- | -| E1 | Low | `sweep()` clock skew when `turnWhenSet > currentTurn` | `packages/opencode/src/context-edit/index.ts` | Deferred | Fix only if upstream/session turn ordering changes make this reachable | +| ID | Severity | Issue | Location | Status | Next action | +| --- | -------- | ----------------------------------------------------- | --------------------------------------------- | -------- | ---------------------------------------------------------------------- | +| E1 | Low | `sweep()` clock skew when `turnWhenSet > currentTurn` | `packages/opencode/src/context-edit/index.ts` | Deferred | Fix only if upstream/session turn ordering changes make this reachable | ## Open Code Quality -| ID | Severity | Issue | Location | Status | Next action | -| --- | --- | --- | --- | --- | --- | -| Q1 | Low | Empty `.catch(() => {})` blocks can hide real failures | Various | Deferred | Audit only files touched by current PR; keep intentional benign cleanup comments local | -| Q2 | Low | TODO/FIXME/HACK comments remain | Various | Deferred | Clean when touching affected code | -| Q4 | Medium | Copilot SDK chunk type safety is weak | `packages/opencode/src/provider/sdk/copilot/chat/openai-compatible-chat-language-model.ts` | Deferred | Replace boundary casts with explicit chunk types when touching Copilot provider | -| Q5 | Low | Direct `process.env` usage instead of `Env.set` | `packages/opencode/src/provider/provider.ts` | Deferred | Revisit during provider/runtime cleanup | +| ID | Severity | Issue | Location | Status | Next action | +| --- | -------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------ | -------- | -------------------------------------------------------------------------------------- | +| Q1 | Low | Empty `.catch(() => {})` blocks can hide real failures | Various | Deferred | Audit only files touched by current PR; keep intentional benign cleanup comments local | +| Q2 | Low | TODO/FIXME/HACK comments remain | Various | Deferred | Clean when touching affected code | +| Q4 | Medium | Copilot SDK chunk type safety is weak | `packages/opencode/src/provider/sdk/copilot/chat/openai-compatible-chat-language-model.ts` | Deferred | Replace boundary casts with explicit chunk types when touching Copilot provider | +| Q5 | Low | Direct `process.env` usage instead of `Env.set` | `packages/opencode/src/provider/provider.ts` | Deferred | Revisit during provider/runtime cleanup | ## Upstream Backport Watchlist -These are not confirmed Frankencode bugs yet. PR 1 items have been ported or confirmed already present; the remaining watchlist is PR 2. +These were not confirmed Frankencode bugs yet. PR 1 and the portable PR 2 reliability items were ported, confirmed present, skipped, or deferred with reasons in `PLAN.md`. + +| SHA | Area | Risk if missing | +| ----------- | ---------- | ------------------------------------------------------- | +| `e76cf967e` | Session | Interrupted assistant messages may not finalize cleanly | +| `ca28dd02e` | Compaction | Tail turns may be lost after summarization | -| SHA | Area | Risk if missing | -| --- | --- | --- | -| `2e6ac8ff4` | MCP | Failed/timed-out MCP connections may leak transports | -| `79d6b10d7` | MCP | Bad output schema refs may break MCP client loading | -| `01f031919` | LSP | TypeScript LSP may leak project state | -| `bc1840b19` | Web fetch | Failed fetches may leave timeout handles alive | -| `e26abd8da` | Shell tool | Truncation stream may remain open | -| `e76cf967e` | Session | Interrupted assistant messages may not finalize cleanly | -| `ca28dd02e` | Compaction | Tail turns may be lost after summarization | +The upstream shell truncation stream cleanup `e26abd8da` was skipped because Frankencode did not have upstream's `src/tool/shell.ts` truncation stream architecture. ## Fixed Summary - PR 1 June upstream sync: prompt tool enables already present, `context_length_exceeded` overflow parsing already present, compaction transforms already present, LiteLLM `_noop` discouragement, subagent `todowrite` permissions, Bun `ZlibError` retryability, configured `model.limit.input`, `Tool.define()` wrapper mutation, read permission relative paths, and Plan Mode subagent deny inheritance. +- PR 2 June upstream sync: TypeScript LSP native `tsserver` args, MCP cleanup on failed connection/tool listing/refresh, MCP tolerant tool listing for invalid `outputSchema`, and webfetch timeout cleanup already present. - Security fixed: S1 symlink containment bypass, S2 command injection in GitHub open flow, S4 unauthenticated non-loopback server, S5 sensitive `.env` read exposure. - QA fixed: B53-B64, including CAS transaction/reference safety, edit graph transactions, synthetic ID collisions, plugin trigger errors, objective prompt escaping, MCP add return shape, text timing preservation, ripgrep JSON parse handling, and untracked line counts. - Earlier fixed bugs: PRs #10-#33 in git history. ## Deferred -| ID | Severity | Issue | Location | Status | -| --- | --- | --- | --- | --- | -| B51 | Low | ID generator counter is not atomic | `packages/opencode/src/id/id.ts` | Acceptable while runtime is single-threaded; revisit if worker threads are added | +| ID | Severity | Issue | Location | Status | +| --- | -------- | ---------------------------------- | -------------------------------- | -------------------------------------------------------------------------------- | +| B51 | Low | ID generator counter is not atomic | `packages/opencode/src/id/id.ts` | Acceptable while runtime is single-threaded; revisit if worker threads are added | ## Notes For Fresh Sessions diff --git a/DO_NEXT.md b/DO_NEXT.md index 04de56f15..eb0f0d760 100644 --- a/DO_NEXT.md +++ b/DO_NEXT.md @@ -1,25 +1,30 @@ # Frankencode Do Next -Use this file as the first handoff target in fresh sessions. It should describe only the next actionable work. +Use this file as the first handoff target in fresh sessions. It described only the next actionable work after the PR 2 reliability slice. ## Immediate Task -Start PR 2 from `PLAN.md`: reliability fixes with more coupling. +Start Phase 3 from `PLAN.md`: evaluate small feature candidates that had direct CLI, MCP, provider, plugin, or TUI value and did not depend on upstream's V2 runtime or package split. -Start branch: +Best first candidates: -```sh -git switch dev -git pull --rebase origin dev -git switch -c fix/upstream-reliability-batch-2 -``` +- `ba57718b0` / #31054: non-interactive `mcp add`. +- `3f0ef9b71` / #31053: search in auth logout command. +- `519d34447` / #29493: plugin dispose hook. +- `f965db9e1` / #29484: provider `headerTimeout` config. ## Current State -- PR 1 merged as #37 at `e6c148f54`. +- PR #37 merged at `e6c148f54`. +- PR #38 merged at `9d8296e32` and established bundled continuity-doc updates. +- PR 2 reliability work ran on `fix/upstream-reliability-batch-2`. +- PR 2 ported TypeScript LSP native `tsserver` args and MCP cleanup/schema tolerance. +- PR 2 confirmed webfetch timeout cleanup was already present. +- PR 2 skipped upstream shell truncation cleanup as not applicable to Frankencode's current architecture. +- PR 2 deferred interrupted assistant finalization and compaction tail restoration for dedicated session/compaction work. - `cd packages/opencode && bun typecheck` passed on 2026-06-06. -- `cd packages/opencode && bun test --timeout 30000` passed on 2026-06-06 with `1554 pass`, `8 skip`, `0 fail`. -- Full tests were run outside the sandbox because socket tests failed sandboxed with `EADDRINUSE`. +- `cd packages/opencode && bun test test/lsp/server.test.ts test/mcp/lifecycle.test.ts test/tool/webfetch.test.ts` passed on 2026-06-06 with `6 pass`, `0 fail`. +- User-approved unsandboxed `cd packages/opencode && bun test --timeout 30000` passed on 2026-06-06 with `1557 pass`, `8 skip`, `0 fail`. ## Next Commands @@ -30,17 +35,9 @@ git status --short --branch git fetch origin upstream git switch dev git pull --rebase origin dev -git switch -c fix/upstream-reliability-batch-2 +git switch -c / ``` -Then inspect and port the PR 2 queue: - -- MCP transport cleanup. -- MCP output schema `$ref` tolerance. -- TypeScript LSP native project config. -- Webfetch timeout cleanup. -- Shell truncation stream cleanup. -- Interrupted assistant finalization. -- Compaction tail restoration. +Then inspect the selected Phase 3 upstream diff manually and update continuity docs at the end of the implementation PR in past tense. Do not push directly to `dev`. diff --git a/GAP_ANALYSIS.md b/GAP_ANALYSIS.md index efbdcebc3..d0b64a72b 100644 --- a/GAP_ANALYSIS.md +++ b/GAP_ANALYSIS.md @@ -1,35 +1,40 @@ -# Frankencode — Gap Analysis - -**Date:** 2026-03-22 - -## All Phases Complete - -| Phase | Goal | Status | -|-------|------|--------| -| 1 | Security fixes (S1-S5) | Done — 4 fixed, 1 mitigated | -| 2 | High-priority upstream fixes | Done — 5 backported | -| 3+4 | Quality + community fixes | Done — OpenTUI 0.1.88, agent ordering | -| 5 | Remaining tests | Done — 24 new tests (filterEdited, filterEphemeral, validation) | -| 6 | Effect behavioral analysis | Done — 0 need reimplementation | - -## Remaining Gaps (Backlog) - -| Gap | Priority | Notes | -|-----|----------|-------| -| S3 workspace trust prompt | Med | Warning log added; full VS Code-style trust model planned | -| TUI edit indicators | Low | No visual indicator for hidden/replaced/annotated parts | -| CAS GC improvements | Low | Basic GC exists; size limits and age-based cleanup not implemented | -| Upstream re-sync | Ongoing | Cherry-pick new fixes as they land; 162 commits analyzed | - -## Permanently Out of Scope - -- Desktop/Electron app -- Bun→Node portability refactors -- Zen platform changes - -## Cross-references - -- [PLAN.md](PLAN.md) — completed roadmap -- [DO_NEXT.md](DO_NEXT.md) — backlog items -- [UPSTREAM_STATUS.md](UPSTREAM_STATUS.md) — full catalogue with Phase 6 analysis -- [docs/SECURITY_AUDIT.md](docs/SECURITY_AUDIT.md) — CVEs and vulnerability details +# Frankencode Gap Analysis + +**Date:** 2026-06-06 + +## Current Gaps + +| Gap | Priority | Status | Next action | +| ---------------------------------- | -------- | ------------------------------------ | ----------------------------------------------------------------------------------------- | +| S3 workspace trust prompt | Medium | Warning mitigation remained in place | Design a workspace trust prompt before loading local MCP/plugin config | +| Interrupted assistant finalization | Medium | Deferred from PR 2 | Port upstream #27254 only with focused session interruption coverage | +| Compaction tail restoration | Medium | Deferred from PR 2 | Port upstream #27145 only after mapping Frankencode's current compaction flow | +| TUI edit indicators | Low | Backlog | Add visual indicators for hidden/replaced/annotated parts when touching TUI context views | +| CAS GC improvements | Low | Backlog | Add size and age policies after storage pressure requirements are defined | + +## Completed Maintenance + +| Phase | Goal | Status | +| --------------- | ------------------------------------ | ---------------------------------------------------------------------------------------- | +| March Phase 1 | Security fixes S1-S5 | Done; 4 fixed, S3 mitigated | +| March Phase 2 | High-priority upstream fixes | Done | +| March Phase 3-4 | Quality and community fixes | Done | +| March Phase 5 | Remaining tests | Done | +| March Phase 6 | Effect behavioral analysis | Done; 0 reimplementations needed | +| June PR 1 | Low-risk upstream bugfix backports | Done and merged as #37 | +| June PR 2 | Reliability fixes with more coupling | Done; portable LSP/MCP fixes landed and divergent session/compaction items were deferred | + +## Permanently Out Of Scope + +- Desktop/Electron app. +- Bun-to-Node portability refactors unless Frankencode adopted a Node runtime target. +- Zen platform changes. +- Generated-only, release-only, nix-only, and CI-only upstream churn unless it directly unblocked Frankencode. + +## Cross-References + +- [PLAN.md](PLAN.md) tracked phased upstream maintenance. +- [DO_NEXT.md](DO_NEXT.md) identified the next actionable work. +- [BUGS.md](BUGS.md) tracked confirmed bugs and deferred risks. +- [UPSTREAM_STATUS.md](UPSTREAM_STATUS.md) retained the historical March catalogue. +- [docs/SECURITY_AUDIT.md](docs/SECURITY_AUDIT.md) held security details. diff --git a/PLAN.md b/PLAN.md index 0cd090c73..c9bb0a726 100644 --- a/PLAN.md +++ b/PLAN.md @@ -6,15 +6,15 @@ Frankencode is a fork of OpenCode that adds context editing, content-addressable Resync with upstream `anomalyco/opencode` by porting selected fixes and features from `upstream/dev` into Frankencode. -**Snapshot:** 2026-06-06 +**Snapshot:** 2026-06-06 after PR #38 merged -| Item | State | -| --- | --- | -| Frankencode branch | `dev` at `e6c148f54` | -| Upstream branch | `upstream/dev` at `4519a1da3` | -| Divergence | `34 ahead / 3613 behind` | -| Upstream package version | `packages/opencode` `1.16.2` | -| Frankencode package version | `packages/opencode` `1.2.27` | +| Item | State | +| --------------------------- | -------------------------------------------- | +| Frankencode branch | `dev` at `9d8296e32` before PR 2 branch work | +| Upstream branch | `upstream/dev` at `4519a1da3` | +| Divergence | `34 ahead / 3613 behind` | +| Upstream package version | `packages/opencode` `1.16.2` | +| Frankencode package version | `packages/opencode` `1.2.27` | ## Strategy @@ -34,18 +34,18 @@ Rules for every upstream-sync PR: Port small, high-value fixes that still map to Frankencode's current `packages/opencode` layout. -| SHA | Upstream PR | Area | Fix | Status | -| --- | --- | --- | --- | --- | -| `c2ca1494e` | #17064 | Session | Preserve prompt tool enables with empty agent permissions | Already present; existing `session.llm.stream` test covers it | -| `e718db624` | #17748 | Provider | Treat `code: context_length_exceeded` as context overflow | Already present; existing `message-v2.fromError` test covers it | -| `4cb29967f` | #17823 | Compaction | Apply message transforms during compaction | Already present in `SessionCompaction.process` | -| `196a03caf` | #18539 | Compaction | Discourage `_noop` tool calls during LiteLLM compaction | Ported in `fix/upstream-bugfix-batch-1` | -| `66a56551b` | #19125 | Task tool | Respect agent permission config for `todowrite` | Ported with task/subagent permission coverage | -| `7123aad5a` | #19104 | Retry | Classify Bun `ZlibError` fetch failures as retryable | Ported with `message-v2` and retry tests | -| `7f45943a9` | #16306 | Provider | Honor `model.limit.input` overrides | Ported with provider config test | -| `81d3ac3bf` | #16952 | Tool registry | Prevent `Tool.define()` wrapper accumulation | Ported with `tool-define` tests | -| `ba9e4b67e` | none | Read tool | Match permissions against worktree-relative path | Ported with read permission test | -| `b8ca71d30` | #26597 | Security | Ensure subagents inherit parent deny rules in Plan Mode | Ported with general/custom subagent deny tests | +| SHA | Upstream PR | Area | Fix | Status | +| ----------- | ----------- | ------------- | --------------------------------------------------------- | --------------------------------------------------------------- | +| `c2ca1494e` | #17064 | Session | Preserve prompt tool enables with empty agent permissions | Already present; existing `session.llm.stream` test covers it | +| `e718db624` | #17748 | Provider | Treat `code: context_length_exceeded` as context overflow | Already present; existing `message-v2.fromError` test covers it | +| `4cb29967f` | #17823 | Compaction | Apply message transforms during compaction | Already present in `SessionCompaction.process` | +| `196a03caf` | #18539 | Compaction | Discourage `_noop` tool calls during LiteLLM compaction | Ported in `fix/upstream-bugfix-batch-1` | +| `66a56551b` | #19125 | Task tool | Respect agent permission config for `todowrite` | Ported with task/subagent permission coverage | +| `7123aad5a` | #19104 | Retry | Classify Bun `ZlibError` fetch failures as retryable | Ported with `message-v2` and retry tests | +| `7f45943a9` | #16306 | Provider | Honor `model.limit.input` overrides | Ported with provider config test | +| `81d3ac3bf` | #16952 | Tool registry | Prevent `Tool.define()` wrapper accumulation | Ported with `tool-define` tests | +| `ba9e4b67e` | none | Read tool | Match permissions against worktree-relative path | Ported with read permission test | +| `b8ca71d30` | #26597 | Security | Ensure subagents inherit parent deny rules in Plan Mode | Ported with general/custom subagent deny tests | Exit criteria: @@ -57,43 +57,45 @@ Exit criteria: PR 1 merged as #37 on 2026-06-06. -## Active Phase: PR 2, Reliability Fixes With More Coupling +## Completed Phase: PR 2, Reliability Fixes With More Coupling -Evaluate and port these in a new feature branch. They are likely useful but may need more manual adaptation. +PR 2 bundled reliability fixes that still mapped cleanly to Frankencode's current architecture and explicitly deferred the coupled session/compaction items that needed dedicated analysis. -| SHA | Upstream PR | Area | Fix | Status | -| --- | --- | --- | --- | --- | -| `2e6ac8ff4` | #19200 | MCP | Close transport on failed or timed-out connection | Todo | -| `79d6b10d7` | #26614 | MCP | Tolerate output schema `$ref` failures | Todo | -| `01f031919` | #19953 | LSP | Avoid TypeScript LSP memory leak by using native project config | Todo | -| `bc1840b19` | #21378 | Web fetch | Clear webfetch timeouts on failed fetches | Todo | -| `e26abd8da` | #27517 | Shell tool | Close shell truncation stream | Todo | -| `e76cf967e` | #27254 | Session | Finalize interrupted assistant messages | Todo | -| `ca28dd02e` | #27145 | Compaction | Restore tail turns after summarization | Todo | +| SHA | Upstream PR | Area | Fix | Status | +| ----------- | ----------- | ---------- | --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | +| `2e6ac8ff4` | #19200 | MCP | Close transport on failed or timed-out connection | Ported for remote/local connect failures, failed tool listing, and refresh failures | +| `79d6b10d7` | #26614 | MCP | Tolerate output schema `$ref` failures | Ported with real stdio MCP regression coverage | +| `01f031919` | #19953 | LSP | Avoid TypeScript LSP memory leak by using native project config | Ported with TypeScript LSP argument coverage | +| `bc1840b19` | #21378 | Web fetch | Clear webfetch timeouts on failed fetches | Already present in Frankencode's `webfetch` `finally` cleanup | +| `e26abd8da` | #27517 | Shell tool | Close shell truncation stream | Skipped; Frankencode did not have upstream's `src/tool/shell.ts` truncation stream architecture | +| `e76cf967e` | #27254 | Session | Finalize interrupted assistant messages | Deferred; current `session/prompt.ts` architecture needed a dedicated port and regression plan | +| `ca28dd02e` | #27145 | Compaction | Restore tail turns after summarization | Deferred; current compaction implementation differed from upstream V2 summary flow | Exit criteria: -- Each fix is marked ported, skipped, or deferred with a precise reason. -- Regression coverage exists for any ported behavior. -- Typecheck and relevant tests pass. +- Each fix was marked ported, skipped, already present, or deferred with a precise reason. +- Regression coverage was added for the ported MCP schema tolerance and TypeScript LSP argument behavior. +- `cd packages/opencode && bun typecheck` passed on 2026-06-06. +- `cd packages/opencode && bun test test/lsp/server.test.ts test/mcp/lifecycle.test.ts test/tool/webfetch.test.ts` passed on 2026-06-06. +- User-approved unsandboxed `cd packages/opencode && bun test --timeout 30000` passed on 2026-06-06 with `1557 pass`, `8 skip`, `0 fail`. -## Phase 3: Feature Candidates +## Active Phase: Phase 3, Feature Candidates Evaluate only after the bugfix phases. Prefer features with direct CLI/provider/plugin value and low architectural coupling. -| SHA | Upstream PR | Area | Feature | Notes | -| --- | --- | --- | --- | --- | -| `ba57718b0` | #31054 | CLI/MCP | Non-interactive `mcp add` | Likely useful and contained | -| `3f0ef9b71` | #31053 | CLI/Auth | Search in auth logout command | Small UX improvement | -| `519d34447` | #29493 | Plugin | Plugin dispose hook | Useful for cleanup | -| `f965db9e1` | #29484 | Provider | `headerTimeout` config | Reliability feature | -| `2859ce6e7` | #29901 | Provider | Snowflake Cortex provider | Provider expansion | -| `d34a0194e` | #27394 | Provider | NVIDIA endpoints origin header | Small provider correctness | -| `159964b17` | #26095 | Provider/plugin | DigitalOcean OAuth and inference routers | Medium size | -| `0de5f1ff3` | #28255 | TUI | Configurable prompt size | Small TUI UX | -| `bba76009a` | #29710 | TUI | Wide-character paste safety | Bugfix-grade TUI item | -| `5fb85a6aa` | #28664 | TUI | Wrapped inline tool row layout | Bugfix-grade TUI item | -| `17d66ee4f` + followups | #28476, #28728, #30935 | TUI | Diff viewer and hunk navigation | Larger feature set | +| SHA | Upstream PR | Area | Feature | Notes | +| ----------------------- | ---------------------- | --------------- | ---------------------------------------- | --------------------------- | +| `ba57718b0` | #31054 | CLI/MCP | Non-interactive `mcp add` | Likely useful and contained | +| `3f0ef9b71` | #31053 | CLI/Auth | Search in auth logout command | Small UX improvement | +| `519d34447` | #29493 | Plugin | Plugin dispose hook | Useful for cleanup | +| `f965db9e1` | #29484 | Provider | `headerTimeout` config | Reliability feature | +| `2859ce6e7` | #29901 | Provider | Snowflake Cortex provider | Provider expansion | +| `d34a0194e` | #27394 | Provider | NVIDIA endpoints origin header | Small provider correctness | +| `159964b17` | #26095 | Provider/plugin | DigitalOcean OAuth and inference routers | Medium size | +| `0de5f1ff3` | #28255 | TUI | Configurable prompt size | Small TUI UX | +| `bba76009a` | #29710 | TUI | Wide-character paste safety | Bugfix-grade TUI item | +| `5fb85a6aa` | #28664 | TUI | Wrapped inline tool row layout | Bugfix-grade TUI item | +| `17d66ee4f` + followups | #28476, #28728, #30935 | TUI | Diff viewer and hunk navigation | Larger feature set | ## Deferred Architecture Work diff --git a/STATUS.md b/STATUS.md index 745ddcb7e..3d8579608 100644 --- a/STATUS.md +++ b/STATUS.md @@ -4,29 +4,25 @@ ## Current State -Frankencode is ready for a new upstream maintenance phase. The old March roadmap is complete and no longer the active plan. - -| Item | Value | -| --- | --- | -| Working branch | `docs/pr37-merged-next-phase` | -| Base branch | `dev` | -| Default branch | `dev` | -| Current upstream target | `upstream/dev` | -| Upstream commit reviewed | `4519a1da3` | -| Divergence after fetch | `34 ahead / 3613 behind` | -| Latest merged PR | #37 at `e6c148f54` | -| Last full verified baseline | 2026-03-22: 1512 pass, 0 fail, 8 skip, 0 tsgo errors | -| Current typecheck | 2026-06-06: `cd packages/opencode && bun typecheck` passed | -| Current full package tests | 2026-06-06: `cd packages/opencode && bun test --timeout 30000` passed with `1554 pass`, `8 skip`, `0 fail` | +Frankencode completed the PR 2 reliability slice of the June upstream maintenance plan. The old March roadmap stayed complete and was no longer the active plan. + +| Item | Value | +| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Implementation branch | `fix/upstream-reliability-batch-2` | +| Base branch | `dev` | +| Default branch | `dev` | +| Current upstream target | `upstream/dev` | +| Upstream commit reviewed | `4519a1da3` | +| Divergence after fetch | `34 ahead / 3613 behind` | +| Latest merged continuity baseline | #38 at `9d8296e32` | +| Last full verified baseline | 2026-03-22: 1512 pass, 0 fail, 8 skip, 0 tsgo errors | +| Current typecheck | 2026-06-06: `cd packages/opencode && bun typecheck` passed | +| Current focused tests | 2026-06-06: `cd packages/opencode && bun test test/lsp/server.test.ts test/mcp/lifecycle.test.ts test/tool/webfetch.test.ts` passed with `6 pass`, `0 fail` | +| Current full package tests | 2026-06-06: approved unsandboxed `cd packages/opencode && bun test --timeout 30000` passed with `1557 pass`, `8 skip`, `0 fail` | ## Active Work -Start PR 2 of the June 2026 upstream resync plan: - -1. Start from fresh `dev` after this docs handoff is merged or applied. -2. Create `fix/upstream-reliability-batch-2`. -3. Evaluate the PR 2 queue in `PLAN.md`. -4. Port each fix manually, add focused tests, and update continuity docs before PR creation. +Start Phase 3 from `PLAN.md` after the PR 2 implementation merged. The best next candidates were small CLI/MCP/auth/provider/plugin features that did not require upstream's package split or V2 runtime. ## Fresh Session Checklist @@ -42,15 +38,18 @@ Run these before implementation work: ## Current Risks -| Risk | Handling | -| --- | --- | -| Upstream is much newer and architecturally split | Port manually in small PRs; do not rebase | -| Frankencode-specific context editing can regress during session/tool ports | Add focused tests around prompt/session/tool behavior when touched | -| Old continuity docs can become stale quickly | Update `STATUS.md`, `DO_NEXT.md`, `WHAT_WE_DID.md`, and `BUGS.md` after every PR or handoff | -| S3 workspace trust remains mitigated, not fully fixed | Keep tracked in `BUGS.md`; do not lose during upstream ports | +| Risk | Handling | +| -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | +| Upstream is much newer and architecturally split | Port manually in small PRs; do not rebase | +| Frankencode-specific context editing can regress during session/tool ports | Add focused tests around prompt/session/tool behavior when touched | +| Old continuity docs can become stale quickly | Update continuity docs at the end of every task in past tense and bundle them with the implementation PR | +| S3 workspace trust remains mitigated, not fully fixed | Keep tracked in `BUGS.md`; do not lose during upstream ports | ## Validation Notes - `bun test test/session/retry.test.ts` and the full package suite require local server binds. In the sandbox they failed with `EADDRINUSE`; rerunning outside the sandbox passed. -- Full package test count increased from the March baseline due existing repository changes plus PR 1 tests; current verified result is `1554 pass`, `8 skip`, `0 fail`. +- Full package test count increased from the March baseline due existing repository changes plus PR 1 and PR 2 tests; current verified result is `1557 pass`, `8 skip`, `0 fail`. - PR #37 merged on 2026-06-06. +- PR #38 merged on 2026-06-06 and established the rule that continuity docs were updated in implementation PRs, not separate docs-only PRs. +- PR 2 focused validation passed on 2026-06-06 for TypeScript LSP args, MCP schema tolerance, and existing webfetch behavior. +- PR 2 full-suite validation passed on 2026-06-06 after user-approved unsandboxed execution. diff --git a/UPSTREAM_STATUS.md b/UPSTREAM_STATUS.md index 2c96e5f50..be77a37b7 100644 --- a/UPSTREAM_STATUS.md +++ b/UPSTREAM_STATUS.md @@ -1,5 +1,7 @@ # Upstream Status: anomalyco/opencode → e6qu/frankencode +**Status:** Historical March 2026 catalogue. Current June 2026 upstream maintenance lived in `PLAN.md`, `STATUS.md`, `DO_NEXT.md`, and `BUGS.md`. + **Date:** 2026-03-21 **Our base:** `origin/dev` @ `5560fd8e6` **Upstream:** `upstream/dev` @ `832b8e252` @@ -14,6 +16,7 @@ Frankencode has diverged enough from upstream that a simple `git rebase` with conflict resolution is no longer viable. Each upstream change must be **analyzed individually** for how it can be incorporated into our codebase — not mechanically merged. **Key principles:** + - **No desktop app.** Frankencode will never ship the Electron desktop app. Desktop-only changes are permanently skipped. - **Web app: keep but don't prioritize.** Web app changes may be useful but are not a focus. - **Manual cherry-pick or reimplementation.** Each fix/feature is either cherry-picked (if it applies cleanly), manually reimplemented (if it conflicts with our architecture), or skipped (if irrelevant). @@ -26,36 +29,36 @@ Frankencode has diverged enough from upstream that a simple `git rebase` with co Bug fixes applicable to our fork. Each needs individual analysis for clean application. -| SHA | PR | Author | Description | Priority | -|-----|-----|--------|-------------|----------| -| `cc818f803` | #18283 | Protocol Zero | fix(provider): only set thinkingConfig for models with reasoning capability | High | -| `214a6c6cf` | #18438 | Kit Langton | fix: switch consumers to service imports to break bundle cycles | High | -| `d70099b05` | #18418 | Kit Langton | fix: apply Layer.fresh at instance service definition site | High | -| `7866dbcfc` | #18292 | Luke Parker | fix: avoid truncate permission import cycle | High | -| `d69962b0f` | #18264 | James Long | fix(core): disable chunk timeout by default | High | -| `054075189` | #18259 | James Long | fix(core): use a queue to process events in event routes | High | -| `0d7e62a53` | #17815 | Kit Langton | fix forked prompt attachments losing file parts | High | -| `84e62fc66` | #18165 | Kit Langton | fix(session): preserve tagged error messages | High | -| `24f9df546` | #18426 | Kit Langton | fix: update stale account url/email on re-login | Med | -| `1071aca91` | #18328 | Dax | fix: miscellaneous small fixes | Med | -| `6fcc970de` | #18320 | Dax | fix: include cache bin directory in which() lookups | Med | -| `6e09a1d90` | #18281 | Kit Langton | fix(account): handle pending console login polling | Med | -| `56102ff64` | #17763 | Johannes Loher | fix(core): detect vLLM context overflow errors | Med | -| `5c6ec1caa` | — | Dax Raad | fix question cross out | Med | -| `f80343b87` | — | Dax Raad | fix annotation | Low | +| SHA | PR | Author | Description | Priority | +| ----------- | ------ | -------------- | --------------------------------------------------------------------------- | -------- | +| `cc818f803` | #18283 | Protocol Zero | fix(provider): only set thinkingConfig for models with reasoning capability | High | +| `214a6c6cf` | #18438 | Kit Langton | fix: switch consumers to service imports to break bundle cycles | High | +| `d70099b05` | #18418 | Kit Langton | fix: apply Layer.fresh at instance service definition site | High | +| `7866dbcfc` | #18292 | Luke Parker | fix: avoid truncate permission import cycle | High | +| `d69962b0f` | #18264 | James Long | fix(core): disable chunk timeout by default | High | +| `054075189` | #18259 | James Long | fix(core): use a queue to process events in event routes | High | +| `0d7e62a53` | #17815 | Kit Langton | fix forked prompt attachments losing file parts | High | +| `84e62fc66` | #18165 | Kit Langton | fix(session): preserve tagged error messages | High | +| `24f9df546` | #18426 | Kit Langton | fix: update stale account url/email on re-login | Med | +| `1071aca91` | #18328 | Dax | fix: miscellaneous small fixes | Med | +| `6fcc970de` | #18320 | Dax | fix: include cache bin directory in which() lookups | Med | +| `6e09a1d90` | #18281 | Kit Langton | fix(account): handle pending console login polling | Med | +| `56102ff64` | #17763 | Johannes Loher | fix(core): detect vLLM context overflow errors | Med | +| `5c6ec1caa` | — | Dax Raad | fix question cross out | Med | +| `f80343b87` | — | Dax Raad | fix annotation | Low | ## Backportable Features (8) -| SHA | PR | Author | Description | Relevance | -|-----|-----|--------|-------------|-----------| -| `040f551c5` | #18079 | Sebastian | Upgrade opentui to 0.1.88 | High — TUI dep | -| `92cd908fb` | #18324 | Dax | feat: add Node.js entry point and build script | High — portability | -| `b3d0446d1` | #18175 | Jaaneek | feat: switch xai provider to responses API | Med | -| `05d3e65f7` | #18014 | Vladimir Glafirov | feat: enable GitLab Agent Platform with workflow model discovery | Med | -| `e6f521477` | #17961 | Shoubhit Dash | feat: add git-backed session review modes | Med | -| `81be54498` | #18138 | Kit Langton | feat(filesystem): add AppFileSystem service, migrate Snapshot | Med — Effect | -| `171e69c2f` | #18035 | Aiden Cline | feat: integrate support for multi step auth flows | Med | -| `8e09e8c61` | #18103 | Aiden Cline | feat: integrate multistep auth flows into desktop app | Low — desktop | +| SHA | PR | Author | Description | Relevance | +| ----------- | ------ | ----------------- | ---------------------------------------------------------------- | ------------------ | +| `040f551c5` | #18079 | Sebastian | Upgrade opentui to 0.1.88 | High — TUI dep | +| `92cd908fb` | #18324 | Dax | feat: add Node.js entry point and build script | High — portability | +| `b3d0446d1` | #18175 | Jaaneek | feat: switch xai provider to responses API | Med | +| `05d3e65f7` | #18014 | Vladimir Glafirov | feat: enable GitLab Agent Platform with workflow model discovery | Med | +| `e6f521477` | #17961 | Shoubhit Dash | feat: add git-backed session review modes | Med | +| `81be54498` | #18138 | Kit Langton | feat(filesystem): add AppFileSystem service, migrate Snapshot | Med — Effect | +| `171e69c2f` | #18035 | Aiden Cline | feat: integrate support for multi step auth flows | Med | +| `8e09e8c61` | #18103 | Aiden Cline | feat: integrate multistep auth flows into desktop app | Low — desktop | ## Refactors — Analysis @@ -63,41 +66,41 @@ Each refactor analyzed for whether it provides actual value to Frankencode. ### Worth adopting -| SHA | PR | Author | Description | Why | -|-----|-----|--------|-------------|-----| -| `2dbcd79fd` | #18261 | jorge g | fix: stabilize agent and skill ordering in prompt descriptions | Deterministic ordering prevents flaky LLM behavior | -| `5ddfe4ada` | #18123 | Kit Langton | type Provider.list() as Record, delete dead code | Better types, dead code removal — aligns with our type safety work | -| `4b4dd2b88` | #18009 | Ariane Emory | fix: Add apply_patch to EDIT_TOOLS filter | Bug fix disguised as refactor — apply_patch should be in the filter | -| `fee3c196c` | #17812 | Kit Langton | add prompt schema validation debug logs | Useful for debugging schema issues | +| SHA | PR | Author | Description | Why | +| ----------- | ------ | ------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------- | +| `2dbcd79fd` | #18261 | jorge g | fix: stabilize agent and skill ordering in prompt descriptions | Deterministic ordering prevents flaky LLM behavior | +| `5ddfe4ada` | #18123 | Kit Langton | type Provider.list() as Record, delete dead code | Better types, dead code removal — aligns with our type safety work | +| `4b4dd2b88` | #18009 | Ariane Emory | fix: Add apply_patch to EDIT_TOOLS filter | Bug fix disguised as refactor — apply_patch should be in the filter | +| `fee3c196c` | #17812 | Kit Langton | add prompt schema validation debug logs | Useful for debugging schema issues | ### Not worth adopting (Bun→Node portability) These replace Bun-specific APIs with Node.js equivalents. Only valuable if we plan to support Node.js runtimes. **Frankencode targets Bun only** — these add churn without benefit. -| SHA | PR | Author | Description | Skip reason | -|-----|-----|--------|-------------|-------------| -| `52a7a04ad` | #18318 | Dax | replace Bun shell with portable Process utilities | Bun-only — no Node.js target | -| `37b8662a9` | #18316 | Dax | abstract SQLite behind runtime-conditional #db import | Bun-only — we use bun:sqlite directly | -| `ddcb32ae0` | #18304 | Dax | replace Bun-specific TUI APIs with portable alternatives | Bun-only | -| `63585db6a` | #18301 | Dax | replace Bun.sleep with node:timers/promises sleep | Bun-only | -| `92cd908fb` | #18324 | Dax | add Node.js entry point and build script | Bun-only — no Node target | +| SHA | PR | Author | Description | Skip reason | +| ----------- | ------ | ------ | -------------------------------------------------------- | ------------------------------------- | +| `52a7a04ad` | #18318 | Dax | replace Bun shell with portable Process utilities | Bun-only — no Node.js target | +| `37b8662a9` | #18316 | Dax | abstract SQLite behind runtime-conditional #db import | Bun-only — we use bun:sqlite directly | +| `ddcb32ae0` | #18304 | Dax | replace Bun-specific TUI APIs with portable alternatives | Bun-only | +| `63585db6a` | #18301 | Dax | replace Bun.sleep with node:timers/promises sleep | Bun-only | +| `92cd908fb` | #18324 | Dax | add Node.js entry point and build script | Bun-only — no Node target | ### Evaluate case-by-case -| SHA | PR | Author | Description | Notes | -|-----|-----|--------|-------------|-------| -| `812d1bb32` | #18303 | Dax | inline tool descriptions, remove separate .txt files | **Conflicts** — our Frankencode agents use .txt prompt files. Would need to keep our .txt files. | -| `8ee939c74` | #18140 | Aiden Cline | remove unnecessary parts from the fallback system prompt | Review what was removed — might remove things we rely on | +| SHA | PR | Author | Description | Notes | +| ----------- | ------ | ----------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | +| `812d1bb32` | #18303 | Dax | inline tool descriptions, remove separate .txt files | **Conflicts** — our Frankencode agents use .txt prompt files. Would need to keep our .txt files. | +| `8ee939c74` | #18140 | Aiden Cline | remove unnecessary parts from the fallback system prompt | Review what was removed — might remove things we rely on | ## TUI Fixes (5) -| SHA | PR | Author | Description | Priority | -|-----|-----|--------|-------------|----------| -| `a64f604d5` | #16779 | Kyle Altendorf | fix(tui): check for selected text in dialog escape handler | Med | -| `51fcd04a7` | #17782 | Shoubhit Dash | Wrap question option descriptions instead of truncating | Med | -| `3256886e2` | — | David Hill | tui: make the title bar search easier to scan | Low | -| `e9a17e448` | #17146 | AbigailJixiangyuyu | fix(windows): restore /editor support on Windows | Low | -| `54ed87d53` | #18010 | Luke Parker | fix(windows): use cross-spawn for shim-backed commands | Low | +| SHA | PR | Author | Description | Priority | +| ----------- | ------ | ------------------ | ---------------------------------------------------------- | -------- | +| `a64f604d5` | #16779 | Kyle Altendorf | fix(tui): check for selected text in dialog escape handler | Med | +| `51fcd04a7` | #17782 | Shoubhit Dash | Wrap question option descriptions instead of truncating | Med | +| `3256886e2` | — | David Hill | tui: make the title bar search easier to scan | Low | +| `e9a17e448` | #17146 | AbigailJixiangyuyu | fix(windows): restore /editor support on Windows | Low | +| `54ed87d53` | #18010 | Luke Parker | fix(windows): use cross-spawn for shim-backed commands | Low | ## Effect-ification (12) — Kit Langton — ANALYZED (Phase 6) @@ -105,21 +108,21 @@ These replace Bun-specific APIs with Node.js equivalents. Only valuable if we pl **Phase 6 analysis result: zero items need reimplementation.** All behavioral changes are already in our tree. The 12 PRs are pure structural refactors (move to Effect service, rename, flatten facades) with no new runtime behavior. -| SHA | PR | Description | Analysis | -|-----|-----|-------------|----------| -| `469c3a420` | #17544 | move scoped services to LayerMap | Pure structural — we use registerDisposer | -| `9e740d994` | #17827 | effectify FileWatcherService | Pure structural | -| `e5cbecf17` | #17829 | fix+refactor VcsService | Bug fix (HEAD filter scoping) **already in our tree** | -| `2cbdf04ec` | #17835 | effectify FileTimeService + Semaphore | Bug fix (await + Semaphore) **already in our tree** | -| `335356280` | #17675 | effectify FormatService | Pure structural | -| `69381f6ae` | #17845 | effectify FileService | Pure structural | -| `384982276` | #17849 | effectify SkillService | Pure structural — our skill cache is separate | -| `9e7c136de` | #17878 | effectify SnapshotService | Pure structural | -| `5dfe86dcb` | #17957 | effectify TruncateService, delete Scheduler | Pure structural — we don't use Scheduler | -| `a800583ae` | #18093 | unify service namespaces | Pure rename (drop "Service" suffix) | -| `e78944e9a` | #18266 | effectify Installation | Pure structural | -| `38e0dc9cc` | #18483 | InstanceState + flatten facades | Architectural divergence — N/A | -| `5d2f8d77f` | #18158 | upgrade effect beta (Luke Parker) | Dependency update — we pin our own version | +| SHA | PR | Description | Analysis | +| ----------- | ------ | ------------------------------------------- | ----------------------------------------------------- | +| `469c3a420` | #17544 | move scoped services to LayerMap | Pure structural — we use registerDisposer | +| `9e740d994` | #17827 | effectify FileWatcherService | Pure structural | +| `e5cbecf17` | #17829 | fix+refactor VcsService | Bug fix (HEAD filter scoping) **already in our tree** | +| `2cbdf04ec` | #17835 | effectify FileTimeService + Semaphore | Bug fix (await + Semaphore) **already in our tree** | +| `335356280` | #17675 | effectify FormatService | Pure structural | +| `69381f6ae` | #17845 | effectify FileService | Pure structural | +| `384982276` | #17849 | effectify SkillService | Pure structural — our skill cache is separate | +| `9e7c136de` | #17878 | effectify SnapshotService | Pure structural | +| `5dfe86dcb` | #17957 | effectify TruncateService, delete Scheduler | Pure structural — we don't use Scheduler | +| `a800583ae` | #18093 | unify service namespaces | Pure rename (drop "Service" suffix) | +| `e78944e9a` | #18266 | effectify Installation | Pure structural | +| `38e0dc9cc` | #18483 | InstanceState + flatten facades | Architectural divergence — N/A | +| `5d2f8d77f` | #18158 | upgrade effect beta (Luke Parker) | Dependency update — we pin our own version | ## App/Desktop (20+) — Permanently Skip Desktop, Evaluate Web App @@ -137,19 +140,20 @@ Zen-specific pricing, routing, model updates. ## Other Notable -| SHA | PR | Author | Description | Action | -|-----|-----|--------|-------------|--------| -| `5dc47905a` | — | Dax Raad | allow customizing DB location | Evaluate | -| `bfdc38e42` | #18337 | Aiden Cline | adjust codex plugin logic (oauth plan) | Evaluate | +| SHA | PR | Author | Description | Action | +| ----------- | ------ | ----------- | ------------------------------------------ | -------------- | +| `5dc47905a` | — | Dax Raad | allow customizing DB location | Evaluate | +| `bfdc38e42` | #18337 | Aiden Cline | adjust codex plugin logic (oauth plan) | Evaluate | | `68809365d` | #17847 | Aiden Cline | fix: github copilot enterprise integration | Med — backport | -| `0bbf26a1c` | #18343 | Luke Parker | deslopity deslopity (code cleanup) | Evaluate | -| `1ac1a0287` | #18186 | Dax | anthropic legal requests | Low | +| `0bbf26a1c` | #18343 | Luke Parker | deslopity deslopity (code cleanup) | Evaluate | +| `1ac1a0287` | #18186 | Dax | anthropic legal requests | Low | --- ## Recommended Backport Order **Phase 1 — High-priority fixes (8 commits, cherry-pick individually):** + - `cc818f803` #18283: thinkingConfig for reasoning models only - `7866dbcfc` #18292: truncate permission import cycle - `d69962b0f` #18264: disable chunk timeout by default @@ -160,6 +164,7 @@ Zen-specific pricing, routing, model updates. - `d70099b05` #18418: Layer.fresh at instance service site **Phase 2 — Quality improvements (5 commits):** + - `040f551c5` #18079: OpenTUI 0.1.88 - `2dbcd79fd` #18261: stabilize agent/skill ordering - `4b4dd2b88` #18009: apply_patch in EDIT_TOOLS filter @@ -171,11 +176,13 @@ Cherry-pick remaining backportable fixes and evaluate features. **Phase 4 — Effect behavioral analysis (NOT a rebase):** For each of the 12 Effect PRs, read the diff and extract: + 1. Bug fixes embedded in the refactor (e.g., VcsService #17829 fixes HEAD filter bug) 2. New capabilities (e.g., Semaphore locks in FileTimeService) 3. Reimplement those behaviors in our architecture — do NOT rebase **Permanently skipped:** + - All desktop/Electron changes (Frankencode will never ship desktop) - All Bun→Node portability refactors (Frankencode targets Bun only) - All chore/generate/nix/CI commits @@ -189,73 +196,73 @@ For each of the 12 Effect PRs, read the diff and extract: ### From Recognized/Vouched Contributors -| PR | Author | Title | Priority | -|----|--------|-------|----------| -| [#18527](https://github.com/anomalyco/opencode/pull/18527) | Dax Raad (Vouched) | fix(core): restore SIGHUP exit handler (+1/-0) | **HIGH** | -| [#18551](https://github.com/anomalyco/opencode/pull/18551) | Sebastian (Vouched) | Upgrade opentui to 0.1.90 | **HIGH** | -| [#18113](https://github.com/anomalyco/opencode/pull/18113) | Ariane Emory (Vouched) | fix: Fix default timeout value (+2/-2) | **HIGH** | -| [#12633](https://github.com/anomalyco/opencode/pull/12633) | Dax Raad (beta) | feat(tui): add auto-accept mode for permissions | **HIGH** | -| [#18348](https://github.com/anomalyco/opencode/pull/18348) | rekram1-node (Vouched) | fix: plugins can register providers with config changes | Med | -| [#18155](https://github.com/anomalyco/opencode/pull/18155) | rekram1-node (Vouched) | feat: add model reconciliation hook | Med | -| [#13692](https://github.com/anomalyco/opencode/pull/13692) | Dax Raad | feat: add reference agent for searching external repos | Med | -| [#18173](https://github.com/anomalyco/opencode/pull/18173) | Kit Langton (Vouched) | feat(bus): migrate Bus to Effect PubSub | Med — Effect | -| [#18336](https://github.com/anomalyco/opencode/pull/18336) | Tim Smart | refactor effect runtime | Med — Effect | +| PR | Author | Title | Priority | +| ---------------------------------------------------------- | ---------------------- | ------------------------------------------------------- | ------------ | +| [#18527](https://github.com/anomalyco/opencode/pull/18527) | Dax Raad (Vouched) | fix(core): restore SIGHUP exit handler (+1/-0) | **HIGH** | +| [#18551](https://github.com/anomalyco/opencode/pull/18551) | Sebastian (Vouched) | Upgrade opentui to 0.1.90 | **HIGH** | +| [#18113](https://github.com/anomalyco/opencode/pull/18113) | Ariane Emory (Vouched) | fix: Fix default timeout value (+2/-2) | **HIGH** | +| [#12633](https://github.com/anomalyco/opencode/pull/12633) | Dax Raad (beta) | feat(tui): add auto-accept mode for permissions | **HIGH** | +| [#18348](https://github.com/anomalyco/opencode/pull/18348) | rekram1-node (Vouched) | fix: plugins can register providers with config changes | Med | +| [#18155](https://github.com/anomalyco/opencode/pull/18155) | rekram1-node (Vouched) | feat: add model reconciliation hook | Med | +| [#13692](https://github.com/anomalyco/opencode/pull/13692) | Dax Raad | feat: add reference agent for searching external repos | Med | +| [#18173](https://github.com/anomalyco/opencode/pull/18173) | Kit Langton (Vouched) | feat(bus): migrate Bus to Effect PubSub | Med — Effect | +| [#18336](https://github.com/anomalyco/opencode/pull/18336) | Tim Smart | refactor effect runtime | Med — Effect | Kit Langton has 10 more Effect PRs — all DRAFT, all require behavioral analysis not cherry-pick. ### Security PRs -| PR | Author | Title | Priority | -|----|--------|-------|----------| -| [#10763](https://github.com/anomalyco/opencode/pull/10763) | orbisai0security | Fix CVE-2025-58179 (astrojs/cloudflare) | **HIGH** | -| [#10974](https://github.com/anomalyco/opencode/pull/10974) | MaxMiksa | Guard TUI server exposure | **HIGH** | -| [#14581](https://github.com/anomalyco/opencode/pull/14581) | Nicoo01x | Prevent cross-drive path bypass (Windows) | Med | -| [#17362](https://github.com/anomalyco/opencode/pull/17362) | kvenux | Sanitize markdown link XSS | Med (web only) | +| PR | Author | Title | Priority | +| ---------------------------------------------------------- | ---------------- | ----------------------------------------- | -------------- | +| [#10763](https://github.com/anomalyco/opencode/pull/10763) | orbisai0security | Fix CVE-2025-58179 (astrojs/cloudflare) | **HIGH** | +| [#10974](https://github.com/anomalyco/opencode/pull/10974) | MaxMiksa | Guard TUI server exposure | **HIGH** | +| [#14581](https://github.com/anomalyco/opencode/pull/14581) | Nicoo01x | Prevent cross-drive path bypass (Windows) | Med | +| [#17362](https://github.com/anomalyco/opencode/pull/17362) | kvenux | Sanitize markdown link XSS | Med (web only) | ### Core Bug Fixes (non-contributor, worth evaluating) -| PR | Author | Title | Why | -|----|--------|-------|-----| -| [#18539](https://github.com/anomalyco/opencode/pull/18539) | KnutZuidema | Discourage _noop tool call during compaction | Small, targeted | -| [#18538](https://github.com/anomalyco/opencode/pull/18538) | zaxbysauce | Handle client disconnect in SSE writes | Crash prevention | -| [#18443](https://github.com/anomalyco/opencode/pull/18443) | LucasSantana-Dev | Retry 429 even when provider says non-retryable | Reliability | -| [#18445](https://github.com/anomalyco/opencode/pull/18445) | LucasSantana-Dev | Account for OpenRouter cache write tokens | Cost accuracy | -| [#17834](https://github.com/anomalyco/opencode/pull/17834) | TomRoyls | Cap retry backoff to 30s | 2-line fix | -| [#17758](https://github.com/anomalyco/opencode/pull/17758) | SunCreation | Prevent lone surrogate 400 errors in tool results | Provider compat | -| [#17742](https://github.com/anomalyco/opencode/pull/17742) | RhoninSeiei | Filter empty text content blocks for all providers | Provider compat | -| [#17712](https://github.com/anomalyco/opencode/pull/17712) | jpvelasco | Drop empty messages after reasoning filter | Provider fix | -| [#18412](https://github.com/anomalyco/opencode/pull/18412) | ernestodeoliveira | Don't decode percent-encoding in filesystem paths | Path safety | -| [#18137](https://github.com/anomalyco/opencode/pull/18137) | BYK | Reduce memory during prompting (lazy scan + windowing) | Performance | -| [#18516](https://github.com/anomalyco/opencode/pull/18516) | BYK | Prevent subagent plan escape | Subagent safety | -| [#17818](https://github.com/anomalyco/opencode/pull/17818) | LehaoLin | Validate JSON in tool call arguments | Robustness | -| [#17635](https://github.com/anomalyco/opencode/pull/17635) | SHL0MS | Remove dead LSP clients (memory leak) | Memory | -| [#17651](https://github.com/anomalyco/opencode/pull/17651) | vesector | Recover MCP clients after transient failures | MCP reliability | -| [#17645](https://github.com/anomalyco/opencode/pull/17645) | mollux | Apply config model cost overrides at runtime | Cost accuracy | -| [#18069](https://github.com/anomalyco/opencode/pull/18069) | ihubanov | Timeout for snapshot git add (large worktrees) | Reliability | -| [#17888](https://github.com/anomalyco/opencode/pull/17888) | flacks | Honor model:inherit in subagent frontmatter | 1-line fix | +| PR | Author | Title | Why | +| ---------------------------------------------------------- | ----------------- | ------------------------------------------------------ | ---------------- | +| [#18539](https://github.com/anomalyco/opencode/pull/18539) | KnutZuidema | Discourage \_noop tool call during compaction | Small, targeted | +| [#18538](https://github.com/anomalyco/opencode/pull/18538) | zaxbysauce | Handle client disconnect in SSE writes | Crash prevention | +| [#18443](https://github.com/anomalyco/opencode/pull/18443) | LucasSantana-Dev | Retry 429 even when provider says non-retryable | Reliability | +| [#18445](https://github.com/anomalyco/opencode/pull/18445) | LucasSantana-Dev | Account for OpenRouter cache write tokens | Cost accuracy | +| [#17834](https://github.com/anomalyco/opencode/pull/17834) | TomRoyls | Cap retry backoff to 30s | 2-line fix | +| [#17758](https://github.com/anomalyco/opencode/pull/17758) | SunCreation | Prevent lone surrogate 400 errors in tool results | Provider compat | +| [#17742](https://github.com/anomalyco/opencode/pull/17742) | RhoninSeiei | Filter empty text content blocks for all providers | Provider compat | +| [#17712](https://github.com/anomalyco/opencode/pull/17712) | jpvelasco | Drop empty messages after reasoning filter | Provider fix | +| [#18412](https://github.com/anomalyco/opencode/pull/18412) | ernestodeoliveira | Don't decode percent-encoding in filesystem paths | Path safety | +| [#18137](https://github.com/anomalyco/opencode/pull/18137) | BYK | Reduce memory during prompting (lazy scan + windowing) | Performance | +| [#18516](https://github.com/anomalyco/opencode/pull/18516) | BYK | Prevent subagent plan escape | Subagent safety | +| [#17818](https://github.com/anomalyco/opencode/pull/17818) | LehaoLin | Validate JSON in tool call arguments | Robustness | +| [#17635](https://github.com/anomalyco/opencode/pull/17635) | SHL0MS | Remove dead LSP clients (memory leak) | Memory | +| [#17651](https://github.com/anomalyco/opencode/pull/17651) | vesector | Recover MCP clients after transient failures | MCP reliability | +| [#17645](https://github.com/anomalyco/opencode/pull/17645) | mollux | Apply config model cost overrides at runtime | Cost accuracy | +| [#18069](https://github.com/anomalyco/opencode/pull/18069) | ihubanov | Timeout for snapshot git add (large worktrees) | Reliability | +| [#17888](https://github.com/anomalyco/opencode/pull/17888) | flacks | Honor model:inherit in subagent frontmatter | 1-line fix | ### TUI Feature PRs (evaluate) -| PR | Author | Title | Notes | -|----|--------|-------|-------| -| [#18497](https://github.com/anomalyco/opencode/pull/18497) | amosbird | Sidebar position config | TUI layout | -| [#17644](https://github.com/anomalyco/opencode/pull/17644) | joeyism | /edit command to open files in $EDITOR | TUI UX | -| [#17868](https://github.com/anomalyco/opencode/pull/17868) | jwcrystal | Prompt after /compact (continue or branch) | TUI UX | -| [#17156](https://github.com/anomalyco/opencode/pull/17156) | shiyuhang0 | Show skills in sidebar | TUI feature | -| [#14190](https://github.com/anomalyco/opencode/pull/14190) | mocksoul | Tail-f effect for tool output | TUI UX | -| [#17992](https://github.com/anomalyco/opencode/pull/17992) | saltykovdg | Light-clean theme | TUI theme | +| PR | Author | Title | Notes | +| ---------------------------------------------------------- | ---------- | ---------------------------------------------- | ----------- | +| [#18497](https://github.com/anomalyco/opencode/pull/18497) | amosbird | Sidebar position config | TUI layout | +| [#17644](https://github.com/anomalyco/opencode/pull/17644) | joeyism | /edit command to open files in $EDITOR | TUI UX | +| [#17868](https://github.com/anomalyco/opencode/pull/17868) | jwcrystal | Prompt after /compact (continue or branch) | TUI UX | +| [#17156](https://github.com/anomalyco/opencode/pull/17156) | shiyuhang0 | Show skills in sidebar | TUI feature | +| [#14190](https://github.com/anomalyco/opencode/pull/14190) | mocksoul | Tail-f effect for tool output | TUI UX | +| [#17992](https://github.com/anomalyco/opencode/pull/17992) | saltykovdg | Light-clean theme | TUI theme | | [#18198](https://github.com/anomalyco/opencode/pull/18198) | 2KAbhishek | Syntax highlighting for kotlin, hcl, lua, toml | TUI feature | ### Core Feature PRs (evaluate) -| PR | Author | Title | Notes | -|----|--------|-------|-------| -| [#18317](https://github.com/anomalyco/opencode/pull/18317) | vaporwavie | Quiet mode for CLI runs | CLI UX | -| [#18235](https://github.com/anomalyco/opencode/pull/18235) | dgruzd | Offline mode | Network control | -| [#18178](https://github.com/anomalyco/opencode/pull/18178) | mjdouglas | Custom system prompt per model | Config | -| [#17670](https://github.com/anomalyco/opencode/pull/17670) | dmitryryabkov | Dynamic model discovery for local providers | Provider feature | -| [#18450](https://github.com/anomalyco/opencode/pull/18450) | potlee | Use native Output.object() for structured output | Net code deletion | -| [#18280](https://github.com/anomalyco/opencode/pull/18280) | ryanskidmore | Plugin system robustness improvements | Plugin stability | +| PR | Author | Title | Notes | +| ---------------------------------------------------------- | ------------- | ------------------------------------------------ | ----------------- | +| [#18317](https://github.com/anomalyco/opencode/pull/18317) | vaporwavie | Quiet mode for CLI runs | CLI UX | +| [#18235](https://github.com/anomalyco/opencode/pull/18235) | dgruzd | Offline mode | Network control | +| [#18178](https://github.com/anomalyco/opencode/pull/18178) | mjdouglas | Custom system prompt per model | Config | +| [#17670](https://github.com/anomalyco/opencode/pull/17670) | dmitryryabkov | Dynamic model discovery for local providers | Provider feature | +| [#18450](https://github.com/anomalyco/opencode/pull/18450) | potlee | Use native Output.object() for structured output | Net code deletion | +| [#18280](https://github.com/anomalyco/opencode/pull/18280) | ryanskidmore | Plugin system robustness improvements | Plugin stability | ### Permanently Skipped (~80 PRs) diff --git a/WHAT_WE_DID.md b/WHAT_WE_DID.md index af6aa83a4..f2085caeb 100644 --- a/WHAT_WE_DID.md +++ b/WHAT_WE_DID.md @@ -24,6 +24,18 @@ Compressed continuity log. Use git history and PRs for full details. - Noted that socket-based tests need to run outside the sandbox; sandboxed local binds failed with `EADDRINUSE`. - Committed the batch as `4ab1837d1` and opened PR #37 against `dev`. - PR #37 merged on 2026-06-06 at `e6c148f54`. +- PR #38 merged on 2026-06-06 at `9d8296e32` and converted continuity updates from standalone docs work into updates bundled with implementation PRs. +- Began PR 2 on branch `fix/upstream-reliability-batch-2`. +- Ported upstream TypeScript LSP native project configuration behavior by resolving the local `typescript/lib/tsserver.js`, passing it with `--tsserver-path`, and adding `--ignore-node-modules` only when no `tsconfig.json` or `jsconfig.json` existed at the LSP root. +- Ported MCP cleanup behavior for failed or timed-out remote/local connects, failed initial tool listing, and failed tool refreshes. +- Ported MCP output schema tolerance by retrying `tools/list` with a schema that ignored invalid `outputSchema` fields while preserving tool names, descriptions, and input schemas. +- Confirmed the upstream webfetch timeout cleanup was already present in Frankencode's `finally` block. +- Skipped the upstream shell truncation-stream cleanup because Frankencode did not have upstream's `src/tool/shell.ts` truncation stream architecture. +- Deferred interrupted assistant finalization and compaction tail restoration because both touched divergent session/compaction flows and needed dedicated regression plans. +- Added focused TypeScript LSP argument coverage and a real stdio MCP server regression for invalid `outputSchema` handling. +- Verified `cd packages/opencode && bun typecheck` passed. +- Verified `cd packages/opencode && bun test test/lsp/server.test.ts test/mcp/lifecycle.test.ts test/tool/webfetch.test.ts` passed with `6 pass`, `0 fail`. +- Verified user-approved unsandboxed `cd packages/opencode && bun test --timeout 30000` passed with `1557 pass`, `8 skip`, `0 fail`. ## Completed Baseline Through 2026-03-22 diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index 797dbbd36..1a919c043 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -64,9 +64,23 @@ export namespace LSPServer { extensions: string[] global?: boolean root: RootFunction + args?(root: string, directory: string): Promise<{ tsserver: string; args: string[] } | undefined> spawn(root: string, directory: string, worktree: string): Promise } + async function ts(root: string, directory: string) { + const tsserver = Module.resolve("typescript/lib/tsserver.js", directory) + if (!tsserver) return + const args = ["x", "typescript-language-server", "--stdio", "--tsserver-path", tsserver] + if ( + !(await pathExists(path.join(root, "tsconfig.json"))) && + !(await pathExists(path.join(root, "jsconfig.json"))) + ) { + args.push("--ignore-node-modules") + } + return { tsserver, args } + } + export const Deno: Info = { id: "deno", root: async (file, directory) => { @@ -102,11 +116,12 @@ export namespace LSPServer { ["deno.json", "deno.jsonc"], ), extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"], + args: ts, async spawn(root, directory, worktree) { - const tsserver = Module.resolve("typescript/lib/tsserver.js", directory) - log.info("typescript server", { tsserver: tsserver ?? null }) - if (!tsserver) return - const proc = spawn(BunProc.which(), ["x", "typescript-language-server", "--stdio"], { + const cfg = await ts(root, directory) + log.info("typescript server", { tsserver: cfg?.tsserver ?? null }) + if (!cfg) return + const proc = spawn(BunProc.which(), cfg.args, { cwd: root, env: { ...process.env, @@ -117,7 +132,7 @@ export namespace LSPServer { process: proc, initialization: { tsserver: { - path: tsserver, + path: cfg.tsserver, }, }, } diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index 9c7a552bc..3c1eaa708 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -7,6 +7,7 @@ import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js" import { CallToolResultSchema, type Tool as MCPToolDef, + ToolSchema, ToolListChangedNotificationSchema, } from "@modelcontextprotocol/sdk/types.js" import { Config } from "../config/config" @@ -127,6 +128,15 @@ export namespace MCP { ) type MCPClient = Client + type Closable = { + close: () => Promise + } + + const TolerantToolSchema = ToolSchema.omit({ outputSchema: true }).passthrough() + const TolerantListToolsResultSchema = z.looseObject({ + tools: z.array(TolerantToolSchema), + nextCursor: z.string().optional(), + }) export const Status = z .discriminatedUnion("status", [ @@ -212,6 +222,37 @@ export namespace MCP { }) } + async function dispose(key: string, target: Closable) { + await target.close().catch((error) => { + log.error("Failed to close MCP resource", { key, error }) + }) + } + + function isSchemaError(error: Error) { + return error.message.includes("outputSchema") || error.message.includes("can't resolve reference") + } + + async function list(key: string, client: MCPClient, timeout: number) { + return withTimeout(client.listTools(undefined, { timeout }), timeout).catch(async (error) => { + const err = error instanceof Error ? error : new Error(String(error)) + if (!isSchemaError(err)) throw err + + log.warn("retrying MCP tools list without outputSchema validation", { key, error: err.message }) + const result = await withTimeout( + client.request({ method: "tools/list" }, TolerantListToolsResultSchema, { timeout }), + timeout, + ) + return { + ...result, + tools: result.tools.map((tool) => ({ + name: tool.name, + description: tool.description, + inputSchema: tool.inputSchema, + })), + } + }) + } + // Prompt cache types type PromptInfo = Awaited>["prompts"][number] @@ -397,11 +438,11 @@ export namespace MCP { let lastError: Error | undefined const connectTimeout = mcp.timeout ?? DEFAULT_TIMEOUT for (const { name, transport } of transports) { + const client = new Client({ + name: "opencode", + version: Installation.VERSION, + }) try { - const client = new Client({ - name: "opencode", - version: Installation.VERSION, - }) await withTimeout(client.connect(transport), connectTimeout) registerNotificationHandlers(client, key) mcpClient = client @@ -427,6 +468,8 @@ export namespace MCP { status: "needs_client_registration" as const, error: "Server does not support dynamic client registration. Please provide clientId in config.", } + await dispose(key, client) + await dispose(key, transport) // Show toast for needs_client_registration Bus.publish( TuiEvent.ToastShow, @@ -463,6 +506,8 @@ export namespace MCP { url: mcp.url, error: lastError.message, }) + await dispose(key, client) + await dispose(key, transport) status = { status: "failed" as const, error: lastError.message, @@ -490,11 +535,11 @@ export namespace MCP { }) const connectTimeout = mcp.timeout ?? DEFAULT_TIMEOUT + const client = new Client({ + name: "opencode", + version: Installation.VERSION, + }) try { - const client = new Client({ - name: "opencode", - version: Installation.VERSION, - }) await withTimeout(client.connect(transport), connectTimeout) registerNotificationHandlers(client, key) mcpClient = client @@ -512,6 +557,8 @@ export namespace MCP { status: "failed" as const, error: error instanceof Error ? error.message : String(error), } + await dispose(key, client) + await dispose(key, transport) } } @@ -529,16 +576,12 @@ export namespace MCP { } } - const result = await withTimeout(mcpClient.listTools(), mcp.timeout ?? DEFAULT_TIMEOUT).catch((err) => { + const result = await list(key, mcpClient, mcp.timeout ?? DEFAULT_TIMEOUT).catch((err) => { log.error("failed to get tools from client", { key, error: err }) return undefined }) if (!result) { - await mcpClient.close().catch((error) => { - log.error("Failed to close MCP client", { - error, - }) - }) + await dispose(key, mcpClient) status = { status: "failed", error: "Failed to get tools", @@ -643,7 +686,10 @@ export namespace MCP { const toolsResults = await Promise.all( connectedClients.map(async ([clientName, client]) => { - const toolsResult = await client.listTools().catch((e) => { + const mcpConfig = config[clientName] + const entry = isMcpConfigured(mcpConfig) ? mcpConfig : undefined + const timeout = entry?.timeout ?? defaultTimeout ?? DEFAULT_TIMEOUT + const toolsResult = await list(clientName, client, timeout).catch(async (e) => { log.error("failed to get tools", { clientName, error: e.message }) const failedStatus = { status: "failed" as const, @@ -651,17 +697,15 @@ export namespace MCP { } s.status[clientName] = failedStatus delete s.clients[clientName] + await dispose(clientName, client) return undefined }) - return { clientName, client, toolsResult } + return { clientName, client, timeout, toolsResult } }), ) - for (const { clientName, client, toolsResult } of toolsResults) { + for (const { clientName, client, timeout, toolsResult } of toolsResults) { if (!toolsResult) continue - const mcpConfig = config[clientName] - const entry = isMcpConfigured(mcpConfig) ? mcpConfig : undefined - const timeout = entry?.timeout ?? defaultTimeout for (const mcpTool of toolsResult.tools) { const sanitizedClientName = clientName.replace(/[^a-zA-Z0-9_-]/g, "_") const sanitizedToolName = mcpTool.name.replace(/[^a-zA-Z0-9_-]/g, "_") diff --git a/packages/opencode/test/lsp/server.test.ts b/packages/opencode/test/lsp/server.test.ts new file mode 100644 index 000000000..326ecfca0 --- /dev/null +++ b/packages/opencode/test/lsp/server.test.ts @@ -0,0 +1,34 @@ +import { expect, test } from "bun:test" +import path from "path" +import { tmpdir } from "../fixture/fixture" +import { LSPServer } from "../../src/lsp/server" + +const root = path.join(import.meta.dir, "../..") +const args = LSPServer.Typescript.args +if (!args) throw new Error("typescript LSP args helper was not available") + +test("typescript lsp uses native tsserver path", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write(path.join(dir, "tsconfig.json"), "{}") + }, + }) + + const cfg = await args(tmp.path, root) + expect(cfg).toBeDefined() + if (!cfg) throw new Error("typescript LSP args were not available") + + expect(cfg.args).toContain("--tsserver-path") + expect(cfg.args[cfg.args.indexOf("--tsserver-path") + 1]).toContain("typescript/lib/tsserver.js") + expect(cfg.args).not.toContain("--ignore-node-modules") +}) + +test("typescript lsp ignores node_modules without project config", async () => { + await using tmp = await tmpdir() + + const cfg = await args(tmp.path, root) + expect(cfg).toBeDefined() + if (!cfg) throw new Error("typescript LSP args were not available") + + expect(cfg.args).toContain("--ignore-node-modules") +}) diff --git a/packages/opencode/test/mcp/lifecycle.test.ts b/packages/opencode/test/mcp/lifecycle.test.ts new file mode 100644 index 000000000..8bed6c304 --- /dev/null +++ b/packages/opencode/test/mcp/lifecycle.test.ts @@ -0,0 +1,111 @@ +import { expect, test } from "bun:test" +import path from "path" +import { pathToFileURL } from "url" +import { tmpdir } from "../fixture/fixture" + +const repo = path.join(import.meta.dir, "../../../..") +const root = path.join(import.meta.dir, "../..") +const sdk = path.join(repo, "node_modules", "@modelcontextprotocol", "sdk", "dist", "esm") +const mod = (file: string) => pathToFileURL(path.join(sdk, file)).href +const src = (file: string) => pathToFileURL(path.join(root, file)).href + +test("MCP tools tolerate invalid outputSchema references", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "server.mjs"), + ` +import { Server } from "${mod("server/index.js")}" +import { StdioServerTransport } from "${mod("server/stdio.js")}" +import { CallToolRequestSchema, ListToolsRequestSchema } from "${mod("types.js")}" + +const server = new Server( + { name: "tolerant", version: "1.0.0" }, + { capabilities: { tools: {} } }, +) + +server.setRequestHandler(ListToolsRequestSchema, async () => ({ + tools: [ + { + name: "search", + description: "Search", + inputSchema: { + type: "object", + properties: {}, + }, + outputSchema: { + $ref: "#/$defs/result", + }, + }, + ], +})) + +server.setRequestHandler(CallToolRequestSchema, async () => ({ + content: [{ type: "text", text: "ok" }], +})) + +await server.connect(new StdioServerTransport()) +`, + ) + await Bun.write( + path.join(dir, "runner.ts"), + ` +import path from "path" +import { Instance } from "${src("test/fixture/instance-shim.ts")}" +import { MCP } from "${src("src/mcp/index.ts")}" + +const dir = process.env.OPENCODE_MCP_TEST_DIR +if (!dir) throw new Error("test directory was not provided") + +const bun = Bun.which("bun") +if (!bun) throw new Error("bun executable was not available") + +await Instance.provide({ + directory: dir, + fn: async () => { + const result = await MCP.add("tolerant", { + type: "local", + command: [bun, path.join(dir, "server.mjs")], + }) + + if (result.status.tolerant?.status !== "connected") { + throw new Error("MCP server did not connect") + } + + const tools = await MCP.tools() + if (!tools.tolerant_search) { + throw new Error("tolerant_search tool was not registered") + } + + await MCP.disconnect("tolerant") + }, +}) +`, + ) + }, + }) + + const bun = Bun.which("bun") + if (!bun) throw new Error("bun executable was not available") + + const proc = Bun.spawn( + [bun, "test", "--preload", path.join(root, "test/preload.ts"), path.join(tmp.path, "runner.ts")], + { + cwd: root, + env: { + ...process.env, + OPENCODE_MCP_TEST_DIR: tmp.path, + }, + stdout: "pipe", + stderr: "pipe", + }, + ) + const [code, stdout, stderr] = await Promise.all([ + proc.exited, + new Response(proc.stdout).text(), + new Response(proc.stderr).text(), + ]) + + if (code !== 0) throw new Error(`MCP lifecycle runner failed\nstdout:\n${stdout}\nstderr:\n${stderr}`) + expect(code).toBe(0) +})