In #3859 I bulk-replaced `oh-my-opencode.schema.json` ->
`oh-my-openagent.schema.json` across docs/, on the false assumption
that the schema file had been renamed alongside the package.
It hadn't. Repo state on dev:
- assets/oh-my-opencode.schema.json (only file present)
- package.json export `./schema.json` -> `./dist/oh-my-opencode.schema.json`
- script/build-schema.ts emits the same legacy basename
- root AGENTS.md line 135 documents the same legacy URL
So every doc that pointed at `oh-my-openagent.schema.json` was
returning HTTP 404 for any user copying the example into their config.
Verified live:
https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-openagent.schema.json -> 404
https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json -> 200
Files reverted to canonical legacy URL:
- docs/examples/{coding,default,planning}-focused.jsonc
- docs/guide/{overview,installation,agent-model-matching}.md
- docs/reference/configuration.md (2 occurrences)
The /docs deploy from #3860 still returned HTTP 500 with
`EvalError: Code generation from strings disallowed for this context`
(captured via `wrangler tail`).
`next-mdx-remote/rsc` compiles MDX to JSX *at runtime* using
`new Function()` style code generation. Cloudflare Workers' security
sandbox bans all dynamic code generation from strings, even from inside
trusted code, so any worker invocation that touched the docs page
threw immediately.
Switch to a build-time markdown -> HTML pipeline:
- Drop `next-mdx-remote` and `gray-matter`. Add `marked` (pure-JS,
no eval).
- `web/scripts/generate-docs-content.mjs` now runs each markdown
source through `marked.parse()` (gfm enabled) at build time and
writes the resulting HTML strings into
`web/lib/docs-content.generated.ts`.
- `web/app/[locale]/docs/page.tsx` renders each section as
`<article className="docs-content" dangerouslySetInnerHTML={{ __html: section.html }} />`.
No MDX runtime, no JSX compilation at request time, just static HTML
injection.
- `web/app/globals.css` adds a `@layer components` block targeting
`.docs-content h1..h4, p, a, ul, ol, li, blockquote, code, pre, table,
thead, th, td, hr, strong`. Same shadcn-themed look that the dropped
`mdx-components.tsx` provided, applied via CSS instead of React
component overrides.
- `web/components/docs/mdx-components.tsx` removed.
We lose MDX features (JSX inside markdown), but the docs are pure
markdown anyway. `docs/` remains the SoT; the marketing site renders
identical content with no eval and no fs at runtime.
Atlas was delegating tasks one-by-one because the workflow framed parallel
as a conditional ("if tasks can run in parallel..."), letting models default
to the safer sequential path. The new shared ATLAS_PARALLEL_BY_DEFAULT block
flips the default: parallel is mandatory; sequential requires a NAMED
blocking dependency (input dependency or file conflict).
Adds two new prompt variants — kimi (K2.6 thinking-mode calibration:
commitment framing + concrete budgets) and opus-4-7 (counters 4.7's lower
default subagent count and literal-following bias). Recalibrates default
(Claude 4.6 family), gpt (GPT-5.5 outcome-first / decision rules over
absolutes), and gemini (preserves TOOL_CALL_MANDATE; replaces stale
session_id with task_id). All five variants share the parallel mandate
positioned BEFORE the workflow so "mandate above" references resolve.
Strengthens the orchestrator-direct-edit reminder hooks
(ORCHESTRATOR_DELEGATION_REQUIRED + DIRECT_WORK_REMINDER) with the central
challenge "Do you ACTUALLY need to be the one doing this?" — replacing the
previous bullet-heavy framing.
Tests now parametrized over all 5 variants. Adds prompt-routing.test
covering GPT/Gemini/Kimi/Opus 4.7/default routing and edge cases, plus a
session_id rejection test (every variant must use task_id for retries).
The first build of #3860 failed at type-check because the generated
`lib/docs-content.generated.ts` is gitignored (regenerated on every
build) and CI's `type-check` step runs before `build`. Two fixes:
- web-ci.yml: explicit `Generate docs content from repo-root docs/`
step right after `bun install` so format-check, lint, and type-check
all see the file.
- web/package.json: add `prepare` lifecycle script. `bun install`
invokes it automatically, so a fresh local checkout boots into a
working state too.
Build still re-runs the generator via prebuild, so docs/ edits land in
the bundle without an explicit dev action.
The worker deploy from #3859 returned HTTP 500 on /docs with
`Error: [unenv] fs.readFile is not implemented yet!` (captured via
`wrangler tail`). `loadDocSource` was calling `node:fs/promises`
`readFile` inside an RSC; even though the page is generated as SSG
(`●`), Cloudflare Workers' unenv shim does not implement filesystem
reads, so any code path that reaches the worker (cache miss, prerender
fallback) fails.
Move the read to a prebuild step that emits a TypeScript module:
- `web/scripts/generate-docs-content.mjs` reads each section's source
from `<repo-root>/docs/` and writes
`web/lib/docs-content.generated.ts` containing
`export const DOC_SOURCES: Record<string, string>`.
- `web/scripts/prepare-build.mjs` invokes the generator after the
cache prune, so every `bun run build` and `bunx opennextjs-cloudflare
build` regenerates the constant module from the live `docs/`.
- `web/lib/docs-source.ts` now reads `DOC_SOURCES[file]` synchronously
with no Node I/O.
- `web/app/[locale]/docs/page.tsx` drops the `Promise.all` since reads
are synchronous.
- `web/.gitignore` excludes the generated file (kept generated, not
source-of-truth).
Effect: the bundle ships every doc as a string literal. The worker has
no `fs.readFile` call to fail. `docs/` remains the only place an
editor needs to touch.
Hephaestus's fallbackChain in src/shared/model-requirements.ts has a
single entry: gpt-5.5. The previous wording said "GPT-5.4/5.5" which
implied either was acceptable; only gpt-5.5 is. Note OpenAI API access
as an alternative to ChatGPT Plus/Pro for clarity.
Now that docs/ is the single source of truth that the marketing site
renders at build time, edits to those files must run web CI and trigger
the Cloudflare deploy. Add `docs/**` to the paths filter in both
web-ci.yml and web-deploy.yml.
A markdown-only fix in docs/ is now sufficient to redeploy
oh-my-openagent.com — no companion web/ change needed.
Replace the bespoke 16-section /docs page that pulled prose from
`messages/{locale}.json` with a build-time MDX renderer that reads the
canonical markdown in repo-root `docs/`. Each markdown file becomes
one section of the docs page, scrolled-to via the existing DocsShell
sidebar. Section data structure stays in `lib/docs-sections.ts` so the
sidebar / scroll-spy keeps working with no client changes.
Why this layout:
- One source of truth: `docs/guide/*.md`, `docs/reference/*.md`,
`docs/manifesto.md`. Edits land in one place; the website redeploys
pick them up automatically via the existing web-deploy workflow.
- Build-time only: `MDXRemote` is rendered inside an RSC and the page
is statically generated (`●` SSG). Cloudflare Workers serves the
rendered HTML; no MDX compiler runs at request time.
- next-intl unchanged for everything else: only the docs prose moves
out. `mobileHeader` and `searchPlaceholder` strings stay in
`messages/{locale}.json`; the 18 stale section keys are removed.
Files:
- web/lib/docs-sections.ts: 9 sections matching docs/ files, typed
`DocSection` with `{ id, title, file }`.
- web/lib/docs-source.ts: `loadDocSource(file)` reads
`<repo-root>/docs/<file>` at build time via `node:fs/promises`.
- web/components/docs/mdx-components.tsx: shadcn-styled overrides for
every markdown element (h1-h4, p, a, ul/ol/li, blockquote, code, pre,
table, hr, strong) so the rendered output matches the rest of the
site.
- web/app/[locale]/docs/page.tsx: rewritten as an async RSC that loads
every section's source in parallel and renders one MDXRemote per
section inside DocsShell.
- web/messages/{en,ja,ko,zh}.json: `docs` key trimmed from 20 entries
to 2 (mobileHeader, searchPlaceholder).
- web/package.json: + next-mdx-remote, + gray-matter.
Local verification: `bun run format:check`, `bun run lint`,
`bun run type-check`, `bun run build`, `bunx opennextjs-cloudflare
build` all pass; `/[locale]/docs` builds as static for all 4 locales
at 4.12 kB / 132 kB First Load.
Audit pass against root AGENTS.md (regenerated 2026-05-08) and the
canonical sources in src/shared/model-requirements.ts,
src/features/team-mode/, src/cli/, src/config/schema/. Each touched file
keeps its existing tone but corrects stale facts:
- guide/overview.md: project name + dual-package transition reality
- guide/installation.md: bun-only invocation, dual oh-my-openagent /
oh-my-opencode CLI surface, current doctor categories
- guide/orchestration.md: 11 agents, category routing through
sisyphus-junior, background concurrency, team-mode link
- guide/agent-model-matching.md: Metis primary -> claude-sonnet-4-6
(matches src/shared/model-requirements.ts fallback chain)
- guide/team-mode.md: full schema, member eligibility tiers, 12 team_*
tools added when enabled
- reference/cli.md: trim to current Commander surface (install, doctor,
run, mcp-oauth, refresh-model-capabilities, get-local-version)
- reference/configuration.md: trim to schema-driven reference, JSONC
rules, multi-level merge order, mcp_env_allowlist user-only constraint
- reference/features.md: 5-tier hook composition counts, 3-tier MCPs,
hashline edit, IntentGate, OpenClaw bidirectional dispatcher
Cross-cutting: every `oh-my-opencode.schema.json` URL replaced with
the canonical `oh-my-openagent.schema.json` (legacy package name in
text bodies preserved where it documents the dual-publish transition).
The first auto-deploy from PR #3855 returned HTTP 500 on every page
with the runtime error `TypeError: components.ComponentMod.handler is
not a function`. Captured via `wrangler tail`.
Root cause: Next.js 16.2.6 was published 2026-05-07 19:01 UTC, *after*
@opennextjs/cloudflare 1.19.8 was published earlier the same day at
11:33 UTC. OpenNext 1.19.8's peerDependency declares
`next: '>=15.5.16 <16 || >=16.2.5'` — 16.2.6 falls inside the range
syntactically, but the route component module export shape changed in
that patch and OpenNext has not caught up yet.
Pin Next + eslint-config-next to 15.5.18 (latest 15.x LTS, the other
half of OpenNext's supported range). Revert the migration-only changes
that came with the 16 bump:
- eslint.config.mjs: `nextPlugin.configs["core-web-vitals"]` (v16
shape) -> `nextPlugin.flatConfig.coreWebVitals` (v15 shape).
- tsconfig.json: `jsx: "react-jsx"` -> `jsx: "preserve"` (Next 15
default).
- tsconfig.json: add `noUncheckedSideEffectImports: false` because
TypeScript 6 enabled this option under `strict` and Next 15's
bundled types do not declare ambient CSS modules (Next 16 does).
All other web/ deps stay at latest. lucide-react remains pinned at
0.577.0 from #3853 for the same brand-icon reason. Re-evaluate Next 16
when @opennextjs/cloudflare ships a release explicitly tested against
\>= 16.2.6.
PRs land on `dev` (master is blocked by `block-master-pr`), but the
deploy workflow only listened to `master` pushes — so #3853's web/
dependency bumps merged to dev with no Cloudflare deployment ever
running.
Add `dev` to the push branches list. The existing `paths` filter
keeps the deploy from firing on non-web changes, and `workflow_dispatch`
is preserved as the manual fallback.
"lucide-react": "0" is npm shorthand for >=0.0.0 <1.0.0, so the
dependency was still floating across all 0.x releases. A future
lockfile refresh could pull a newer 0.x that quietly changes brand-icon
inventory.
Pin exact 0.577.0 (no caret) so the lockfile cannot drift until we
explicitly migrate to a brand-icon library compatible with lucide-react
v1.x (which removed Github, X, etc.).
Identified by cubic.
Replaces fsyncSync(tempFileDescriptor) with tolerantFsyncSync, allowing
EPERM/EACCES/ENOTSUP/EINVAL during fsync while still propagating real
errors. Adds an optional deps.fsyncSync injection point used solely by
the new EPERM tolerance regression tests.
Without this fix, plugin startup itself can fail on synced folders
because writeFileAtomically is used by config migrations and posthog
activity state — the same EPERM-on-fsync failure pattern reported for
team_create.
Replaces direct fileHandle.sync() calls in acquireLock and atomicWrite
with tolerantFsync. Users on iCloud Drive / OneDrive / Desktop sync
folders were hitting 'EPERM: operation not permitted, fsync' during
team_create, which propagated up and aborted the entire team_create
flow even though the actual write+rename had succeeded.
Reported on Discord (omo 4.0.0, opencode desktop 1.14.41, project on
synced Desktop). atomicity is preserved by the temp-file rename; only
the durability hint is now best-effort on filesystems that disallow
fsync.
Adds isToleratedFsyncError, tolerantFsync (async, FileHandle), and
tolerantFsyncSync (sync, fd) helpers that swallow filesystem-limitation
errors during fsync (EPERM, EACCES, ENOTSUP, EINVAL) while still
propagating real errors (EIO, ENOSPC, EBADF, etc.). Synced folders
like iCloud Drive, OneDrive, and antivirus-locked files reject fsync
with EPERM even though the underlying write+rename succeeded; for the
runtime data this codebase persists, losing the durability hint is
acceptable in exchange for not blocking the operation entirely.
The helper is intentionally not barrel-exported (consumers import the
file directly), matching the existing convention for write-file-atomically.
Side effects from `bun run format` (prettier 3.8) and `bun run build`
(Next 16) that I missed in the prior commit:
- web/components/ui/badge.tsx, web/components/ui/button.tsx: prettier 3.8
inlines short interface `extends` lists onto a single line.
- web/tsconfig.json: Next 16's build step auto-modernizes tsconfig:
`jsx: "preserve"` → `jsx: "react-jsx"` and reformats `lib`/`paths`
arrays to multi-line. Build is identical either way; committing the
modernized shape so subsequent `next build` runs do not produce
spurious diffs.
Bumped via `bun update --latest` then resolved breakage from two
major-version jumps:
1. Next.js 15.5 → 16.2 + @next/eslint-plugin-next 16: dropped the
`flatConfig` namespace. Updated web/eslint.config.mjs to use
`nextPlugin.configs["core-web-vitals"]` per the new export shape.
2. lucide-react 0.553 → 1.x: lucide upstream removed all brand icons
(Github, etc.) — they are now expected to come from a separate brand
icon library. Pinned lucide-react at the last 0.x (0.577.0) for now;
migrating to a brand-icon library is tracked as a follow-up.
All other deps to latest:
- react/react-dom 19.2.4 → 19.2.6
- next-intl 4.8 → 4.11
- motion 12.35 → 12.38
- tailwind-merge 3.4 → 3.5
- geist 1.5 → 1.7
- @radix-ui/* unchanged (already latest within their ranges)
- @opennextjs/cloudflare 1.17 → 1.19.8
- @playwright/test 1.56 → 1.59
- @tailwindcss/postcss + tailwindcss 4.1 → 4.2.4
- @types/node 22 → 25.6
- @types/react 19 → 19.2.14
- eslint 9 → 10.3 (works because we now reference @next/eslint-plugin-next
configs directly, not eslint-config-next)
- eslint-plugin-prettier 5.5.4 → 5.5.5
- globals 16 → 17.6
- postcss 8.5.6 → 8.5.14
- prettier 3.6.2 → 3.8.3 (no formatting changes detected by --check)
- prettier-plugin-tailwindcss 0.6 → 0.8
- typescript 5.9.3 → 6.0.3
- typescript-eslint 8.56 → 8.59
- wrangler 4.71 → 4.90
Verified locally:
- bun install --frozen-lockfile: 685 packages, no errors
- bun run format:check: pass (no diffs after `bun run format`)
- bun run lint: pass
- bun run type-check: pass (TypeScript 6 + @types/node 25)
- bun run build: pass (Next 16 build, 21 static pages, all 4 locales)
- bunx opennextjs-cloudflare build: pass (.open-next/worker.js produced)
Note: Next 16 `build` log relabels the Middleware row to "Proxy
(Middleware)" — purely cosmetic, no behavior change.
Code-quality review flagged that web/AGENTS.md ANTI-PATTERNS section
asserts `.wrangler/` is gitignored, but the rule was missing from
web/.gitignore. Adding the rule alongside the other Cloudflare/Next
build-output rules so the doc and the file agree.
Commit 2dfa6336f changed Metis primary model from claude-opus-4-7 max
to claude-sonnet-4-6 in src/shared/model-requirements.ts but only
updated the unit test in model-requirements.test.ts. The CLI installer
end-to-end snapshot test (src/cli/model-fallback.test.ts) generates
configs from those same fallback chains via generateModelConfig and
captures them as snapshots, so 17 snapshot assertions broke.
Snapshots regenerated with `bun test src/cli/model-fallback.test.ts -u`.
Verified 86/86 pass across model-requirements + model-fallback +
openai-only-model-catalog test files.
The change is a no-op for the web import that this PR is primarily
about — including it here to unblock CI on dev (where the breakage
landed) rather than spinning up a separate hotfix PR.
web-ci.yml — runs on push/PR to master|dev that touches web/**:
- format:check (prettier --check)
- lint (eslint flat config)
- type-check (tsc --noEmit)
- bun run build (next build, sanity)
- bunx opennextjs-cloudflare build (Cloudflare worker bundle)
web-deploy.yml — runs on push to master that touches web/** OR manual
workflow_dispatch (with optional environment input):
- bun install --frozen-lockfile
- bun run prebuild + bunx opennextjs-cloudflare build
- cloudflare/wrangler-action@v3 deploy with CLOUDFLARE_API_TOKEN +
CLOUDFLARE_ACCOUNT_ID secrets, scoped to working-directory: web
Both gated by paths-filter so plugin-only changes do not trigger them.
Concurrency group cancels in-progress CI runs but NOT in-progress deploys.
A web-production GitHub environment is referenced so deploys can be
gated behind required reviewers / wait timers if desired.
Verified locally end-to-end before push:
- bun install: 678 packages
- format:check: pass after `bun run format` reformatted 21 files
- lint: pass
- type-check: pass
- bun run build: pass (4 locales × pages built)
- bunx opennextjs-cloudflare build: pass (.open-next/worker.js generated)
Imports the public marketing site previously living in
../oh-my-opencode-web. Independent of the npm plugin: own package.json,
bun.lock, tsconfig.json. Not included in the published package — root
files: array still only ships dist/, bin/, postinstall.mjs.
Stack:
- Next.js 15.5 App Router + RSC, deployed to Cloudflare Workers via
@opennextjs/cloudflare (build target .open-next/worker.js).
- Tailwind v4 + shadcn/ui primitives.
- next-intl with 4 locales (en/ja/ko/zh) under app/[locale]/.
- Playwright e2e tests under web/e2e/.
- Custom domains ohmyopenagent.com (primary) and ohmyopencode.org
(legacy alias) declared in web/wrangler.toml.
Source files were re-formatted via `bun run format` to bring them in
line with the existing .prettierrc (singleQuote: false). Functional code
unchanged.
Default `bun test` recurses into every directory and would pick up
`web/e2e/*.spec.ts` once the marketing site lands. Explicitly listing
the existing test roots (matching script/run-ci-tests.ts TEST_ROOTS)
keeps plugin tests isolated from web E2E tests without needing a
`testPathIgnorePatterns` that bunfig.toml does not support yet.
The previous commit (2dfa6336f) used 'git add -A' which swept up files that
prior commits had intentionally deleted but were still present untracked in
the local workspace. This commit re-deletes them to match the upstream intent.
Files removed (each was previously deleted in the cited commit):
- drafts/gpt-5-5/{README,deep,hephaestus,oracle,sisyphus-junior,sisyphus}.md
(deleted in 40ded422c chore(drafts): remove stale gpt-5.5 prompt drafts)
- src/plugin-dispose.{ts,test.ts}
(deleted in e2f5c0d36 refactor(plugin): remove orphaned createPluginDispose)
- src/features/tmux-subagent/{cleanup,session-created-handler,session-deleted-handler}.ts
(deleted in 7a7926f22 chore(tmux-subagent): remove dead event-handler modules)
- src/tools/delegate-task/{model-string-parser,resolve-call-id,resolve-call-id.test}.ts
(model-string-parser deleted in db056346d; resolve-call-id was scratch)
- src/__debug-test.test.ts (debug scratch never intended for git)
Typecheck + model-requirements tests still pass. The legitimate metis +
AGENTS.md edits from the previous commit remain on dev.
Source code change:
- src/shared/model-requirements.ts: prepend claude-sonnet-4-6 to metis fallback
chain so Sonnet becomes the default. Opus 4.7 max remains as the immediate
fallback for callers who want extra reasoning.
- src/shared/model-requirements.test.ts: update assertion to expect Sonnet
primary + Opus secondary.
AGENTS.md accuracy fixes (verified against source):
- Agent modes: Sisyphus/Hephaestus are 'primary' (not 'all'); Sisyphus-Junior
is 'subagent' (not 'all'). Confirmed via 'const MODE: AgentMode = ...' in
each agent file. Also clarified Prometheus has no agentSources factory and
is built via buildPrometheusAgentConfig.
- Sisyphus fallback chain: corrected order to kimi-k2.6 → k2p5 → kimi-k2.5
→ gpt-5.5 medium → glm-5 → big-pickle (was missing kimi-k2.5).
- Librarian/Explore: added missing minimax-m2.7 step between -highspeed and
claude-haiku-4-5.
- Metis chain: removed fictitious gemini-3.1-pro entry.
- Sisyphus-Junior chain: spelled out the actual fallback (was 'user-configurable').
- Temperatures: Sisyphus/Hephaestus do not set explicit temperature (model
default); Sisyphus-Junior is 0.1 via SISYPHUS_JUNIOR_DEFAULTS.
- Quick category default: gpt-5.4-mini (not gpt-5.4-mini-fast).
Team-mode corrections:
- Eligibility registry has 3 verdicts: eligible (sisyphus, atlas, sisyphus-junior),
conditional (hephaestus — needs D-36 teammate permission), hard-reject
(oracle, librarian, explore, multimodal-looker, metis, momus, prometheus).
- Schema has 11 fields, not 4: added max_messages_per_run, max_wall_clock_minutes,
max_member_turns, base_dir, message_payload_max_bytes, recipient_unread_max_bytes,
mailbox_poll_interval_ms.
- Hooks: 'team-session-events' is 4 sub-handlers in src/plugin/event.ts
(team-idle-wake-hint, team-lead-orphan-handler, team-member-error-handler,
team-member-status-handler), not a single Continuation-tier hook.
- Tier counts now show base + team-mode: ToolGuard 14/15, Transform 5/7.
- Total: 52 base hooks, 59 with team-mode.
Doc cascade for the Metis change:
- docs/guide/orchestration.md, agent-model-matching.md, installation.md
- docs/reference/configuration.md, features.md
OpenCode Desktop 1.14.x filters agents by `mode` field. The `mode` was
only present on the factory function as a static property, but not copied
to the generated AgentConfig object.
This fix copies `source.mode` to `base.mode` in `buildAgent()` when:
- source is a factory function (has `mode` static property)
- the generated config doesn't already define `mode`
Test: agent-builder.test.ts (4 pass)
Closes: #3835
Related: #3762, #3812, #3794, #3475, #3474, #3829, #3831, #3824, #3826, #3721, #3806, #3188