From 58be69114f1c5143f00b5bd34565ee50775f1d35 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Thu, 9 Apr 2026 15:14:40 +0900 Subject: [PATCH] docs: update AGENTS.md hierarchy with openclaw, runtime-fallback, skill-mcp-manager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add src/openclaw/AGENTS.md: bidirectional Discord/Telegram/webhook integration - Add src/hooks/runtime-fallback/AGENTS.md: reactive provider error recovery - Add src/features/skill-mcp-manager/AGENTS.md: tier-3 MCP lifecycle - Update root AGENTS.md: refresh commit hash, add openclaw/IntentGate/Hashline refs - Fix src/features/AGENTS.md: skill-mcp-manager file count 14→18, complexity MEDIUM→HIGH Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- AGENTS.md | 44 +++++---- src/features/AGENTS.md | 4 +- src/features/skill-mcp-manager/AGENTS.md | 111 +++++++++++++++++++++++ src/hooks/runtime-fallback/AGENTS.md | 102 +++++++++++++++++++++ src/openclaw/AGENTS.md | 82 +++++++++++++++++ 5 files changed, 324 insertions(+), 19 deletions(-) create mode 100644 src/features/skill-mcp-manager/AGENTS.md create mode 100644 src/hooks/runtime-fallback/AGENTS.md create mode 100644 src/openclaw/AGENTS.md diff --git a/AGENTS.md b/AGENTS.md index 86c7d8245..216ba3153 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,10 +1,10 @@ -# oh-my-opencode — O P E N C O D E Plugin +# oh-my-opencode — OpenCode Plugin -**Generated:** 2026-04-08 | **Commit:** 4f196f49 | **Branch:** dev +**Generated:** 2026-04-09 | **Commit:** dc7a4680 | **Branch:** dev ## OVERVIEW -OpenCode plugin (npm: `oh-my-opencode`) that extends Claude Code (OpenCode fork) with multi-agent orchestration, 52 lifecycle hooks, 26 tools, skill/command/MCP systems, and Claude Code compatibility. ~1602 TypeScript source files, ~214k LOC. +OpenCode plugin (npm: `oh-my-opencode`) extending Claude Code with multi-agent orchestration, 52 lifecycle hooks, 26 tools, skill/command/MCP systems, Hashline edit tool, IntentGate classifier, and Claude Code compatibility. ~1600 TypeScript source files. Dual-published as `oh-my-opencode` + `oh-my-openagent` during transition. ## STRUCTURE @@ -15,16 +15,19 @@ oh-my-opencode/ │ ├── plugin-config.ts # JSONC multi-level config: user → project → defaults (Zod v4) │ ├── agents/ # 11 agents (Sisyphus, Hephaestus, Oracle, Librarian, Explore, Atlas, Prometheus, Metis, Momus, Multimodal-Looker, Sisyphus-Junior) │ ├── hooks/ # 52 lifecycle hooks across dedicated modules and standalone files -│ ├── tools/ # 26 tools across 16 directories -│ ├── features/ # 19 feature modules (background-agent, skill-loader, tmux, MCP-OAuth, etc.) -│ ├── shared/ # 100+ utility files +│ ├── tools/ # 26 tools across 16 directories (includes Hashline edit with LINE#ID content hashing) +│ ├── features/ # 19 feature modules (background-agent, skill-loader, tmux, MCP-OAuth, skill-mcp-manager, etc.) +│ ├── shared/ # 170+ utility files (barrel-exported, logger → /tmp/oh-my-opencode.log) │ ├── config/ # Zod v4 schema system (27 files) │ ├── cli/ # CLI: install, run, doctor, mcp-oauth (Commander.js) │ ├── mcp/ # 3 built-in remote MCPs (websearch, context7, grep_app) -│ ├── plugin/ # 8 OpenCode hook handlers + 52 hook composition -│ └── plugin-handlers/ # 6-phase config loading pipeline -├── packages/ # Monorepo: cli-runner, 11 platform binaries -└── local-ignore/ # Dev-only test fixtures +│ ├── plugin/ # 10 OpenCode hook handlers + 52 hook composition +│ ├── plugin-handlers/ # 6-phase config loading pipeline +│ └── openclaw/ # Bidirectional external integration (Discord/Telegram/webhook/command) +├── packages/ # 11 platform-specific compiled binaries (darwin/linux/windows, AVX2 + baseline variants) +├── script/ # Build/publish automation (singular, not scripts/) +├── .sisyphus/ # AI agent workspace (rules, plans, tasks, notepads) +└── .local-ignore/ # Dev-only test fixtures + PR worktrees ``` ## INITIALIZATION FLOW @@ -44,13 +47,13 @@ OhMyOpenCodePlugin(ctx) |---------|---------| | `config` | 6-phase: provider → plugin-components → agents → tools → MCPs → commands | | `tool` | 26 registered tools | -| `chat.message` | First-message variant, session setup, keyword detection | -| `chat.params` | Anthropic effort level adjustment | +| `chat.message` | First-message variant, session setup, keyword detection (ultrawork/search/analyze) | +| `chat.params` | Anthropic effort level, think mode, runtime fallback override | | `chat.headers` | Copilot x-initiator header injection | -| `event` | Session lifecycle (created, deleted, idle, error) | -| `tool.execute.before` | Pre-tool hooks (file guard, label truncator, rules injector) | -| `tool.execute.after` | Post-tool hooks (output truncation, metadata store) | -| `experimental.chat.messages.transform` | Context injection, thinking block validation | +| `event` | Session lifecycle (created, deleted, idle, error), openclaw dispatch, runtime fallback | +| `tool.execute.before` | Pre-tool hooks (file guard, label truncator, rules injector, prometheus md-only) | +| `tool.execute.after` | Post-tool hooks (output truncation, comment checker, hashline read enhancer) | +| `experimental.chat.messages.transform` | Context injection, thinking block validation, tool pair validation | | `experimental.session.compacting` | Context + todo preservation during compaction | ## WHERE TO LOOK @@ -61,13 +64,16 @@ OhMyOpenCodePlugin(ctx) | Add new hook | `src/hooks/{name}/` + register in `src/plugin/hooks/create-*-hooks.ts` | Match event type to tier | | Add new tool | `src/tools/{name}/` + register in `src/plugin/tool-registry.ts` | Follow createXXXTool factory | | Add new feature module | `src/features/{name}/` | Standalone module, wire in plugin/ | -| Add new MCP | `src/mcp/` + register in `createBuiltinMcps()` | Remote HTTP only | +| Add new MCP | `src/mcp/` + register in `createBuiltinMcps()` | Remote HTTP only (tier 1 of 3) | | Add new skill | `src/features/builtin-skills/skills/` | Implement BuiltinSkill interface | | Add new command | `src/features/builtin-commands/` | Template in templates/ | | Add new CLI command | `src/cli/cli-program.ts` | Commander.js subcommand | | Add new doctor check | `src/cli/doctor/checks/` | Register in checks/index.ts | | Modify config schema | `src/config/schema/` + update root schema | Zod v4, add to OhMyOpenCodeConfigSchema | | Add new category | `src/tools/delegate-task/constants.ts` | DEFAULT_CATEGORIES + CATEGORY_MODEL_REQUIREMENTS | +| Debug provider errors | `src/hooks/runtime-fallback/` | Reactive error recovery (distinct from model-fallback) | +| External notifications | `src/openclaw/` | Bidirectional Discord/Telegram/webhook integration | +| Skill-embedded MCP | `src/features/skill-mcp-manager/` | Tier 3 MCPs (stdio + HTTP, per-session) | ## MULTI-LEVEL CONFIG @@ -153,10 +159,14 @@ bunx oh-my-opencode run # Non-interactive session - Background tasks: 5 concurrent per model/provider (configurable, circuit breaker support) - Plugin load timeout: 10s for Claude Code plugins - Model fallback: per-agent chains in `shared/model-requirements.ts`, not a single global priority +- Two fallback systems: `model-fallback` (proactive, chat.params) vs `runtime-fallback` (reactive, session.error) - Config migration: idempotent via `_migrations` tracking, creates timestamped backups before atomic writes - Build: bun build (ESM) + tsc --emitDeclarationOnly, externals: @ast-grep/napi - Test setup: `test-setup.ts` preloaded via bunfig.toml, resets session/cache state between tests +- Test split: `script/run-ci-tests.ts` auto-isolates files using `mock.module()` (plus `src/openclaw/__tests__/reply-listener-discord.test.ts`) - 104 barrel export files (index.ts) establish module boundaries - Architecture rules enforced via `.sisyphus/rules/modular-code-enforcement.md` - Windows builds run on `windows-latest` runner (not cross-compiled) to avoid Bun segfaults - Platform binaries detect AVX2 + libc family at runtime, fallback to baseline if needed +- Hashline edit: every Read output tagged with `LINE#ID` content hashes; edits reject on hash mismatch +- IntentGate: classifies user intent (research/implementation/investigation/evaluation/fix) before routing diff --git a/src/features/AGENTS.md b/src/features/AGENTS.md index 8a6890ec2..fed217805 100644 --- a/src/features/AGENTS.md +++ b/src/features/AGENTS.md @@ -15,8 +15,8 @@ Standalone feature modules wired into plugin/ layer. Each is self-contained with | **tmux-subagent** | 34 | HIGH | Tmux pane management, grid planning, session orchestration | | **mcp-oauth** | 18 | HIGH | OAuth 2.0 + PKCE + DCR (RFC 7591) for MCP servers | | **builtin-skills** | 17 | LOW | 8 skills: git-master, playwright, playwright-cli, agent-browser, dev-browser, frontend-ui-ux, review-work, ai-slop-remover | -| **skill-mcp-manager** | 14 | MEDIUM | MCP client lifecycle per session (stdio + HTTP) | -| **claude-code-plugin-loader** | 10 | MEDIUM | Unified plugin discovery from .opencode/plugins/ | +| **skill-mcp-manager** | 18 | HIGH | Tier-3 MCP client lifecycle per session (stdio + HTTP + OAuth step-up) | +| **claude-code-plugin-loader** | 15 | MEDIUM | Unified plugin discovery from .opencode/plugins/ | | **builtin-commands** | 11 | LOW | Command templates: refactor, init-deep, handoff, etc. | | **claude-tasks** | 7 | MEDIUM | Task schema + file storage + OpenCode todo sync | | **claude-code-mcp-loader** | 6 | MEDIUM | .mcp.json loading with ${VAR} env expansion | diff --git a/src/features/skill-mcp-manager/AGENTS.md b/src/features/skill-mcp-manager/AGENTS.md new file mode 100644 index 000000000..34bfa44d0 --- /dev/null +++ b/src/features/skill-mcp-manager/AGENTS.md @@ -0,0 +1,111 @@ +# src/features/skill-mcp-manager/ — Skill-Embedded MCP Client Lifecycle + +**Generated:** 2026-04-09 + +## OVERVIEW + +18 files. Manages **tier 3** of the MCP system: skill-embedded MCP servers declared in SKILL.md YAML frontmatter. Per-session client isolation, dual transport (stdio + HTTP), OAuth 2.0 with step-up authentication, idle cleanup. + +## THREE-TIER MCP CONTEXT + +| Tier | Manager | Scope | +|------|---------|-------| +| 1. Built-in | `createBuiltinMcps()` (src/mcp/) | Global, 3 remote HTTP | +| 2. Claude Code | `claude-code-mcp-loader` (src/features/) | From `.mcp.json` | +| 3. **Skill-embedded** | **`SkillMcpManager` (this module)** | **Per-session, from SKILL.md YAML** | + +## CLIENT KEY FORMAT + +``` +${sessionID}:${skillName}:${serverName} +``` + +Enables: per-session isolation, same skill usable in multiple sessions concurrently, multiple servers per skill. + +## DUAL TRANSPORT + +| Type | File | Backend | +|------|------|---------| +| **stdio** | `stdio-client.ts` | `StdioClientTransport` (local process) | +| **http** | `http-client.ts` | `StreamableHTTPClientTransport` (remote) | + +**Detection** (connection-type.ts): explicit `type` field → URL presence → command presence. Legacy `"sse"` mapped to http. + +## STATE + +```typescript +interface SkillMcpManagerState { + clients: Map // Active connections + pendingConnections: Map> // Race prevention + disconnectedSessions: Map // Stale connection detection + authProviders: Map // OAuth state per server + inFlightConnections: Map // Connection counting +} +``` + +## KEY FILES + +| File | Purpose | +|------|---------| +| `manager.ts` | `SkillMcpManager` class — main API (getOrCreateClient, disconnectSession, listTools, callTool, etc.) | +| `types.ts` | `ManagedStdioClient`, `ManagedHttpClient`, `SkillMcpManagerState`, `ConnectionType` | +| `connection.ts` | Client factory with race prevention, retry, env var expansion | +| `connection-type.ts` | Detect stdio vs http from config (legacy sse → http) | +| `stdio-client.ts` | Stdio transport factory | +| `http-client.ts` | HTTP transport factory | +| `cleanup.ts` | SIGINT/SIGTERM handlers, idle timer (60s interval, 5min TTL) | +| `oauth-handler.ts` | OAuth token management, refresh, step-up (403 scope escalation) | +| `env-cleaner.ts` | Filter npm/pnpm/yarn config + 25+ secret patterns (_KEY, _SECRET, _TOKEN) | +| `error-redaction.ts` | Redact sensitive data from error messages before logging | + +## LIFECYCLE INTEGRATION + +**Hook**: `src/plugin/event.ts` on `session.deleted`: +```typescript +await managers.skillMcpManager.disconnectSession(sessionInfo.id) +``` + +## LIFECYCLE FLOW + +``` +1. session.created → No action (lazy connection) +2. First MCP tool call → getOrCreateClient() creates + caches +3. Ongoing use → lastUsedAt timestamp updated +4. Idle >5min → cleanup timer removes +5. session.deleted → disconnectSession() closes session clients +6. Process exit → disconnectAll() via SIGINT/SIGTERM handlers +``` + +## RACE CONDITION PREVENTION + +- **pendingConnections**: Deduplicates concurrent connection attempts for same key +- **inFlightConnections**: Per-session counter, prevents premature cleanup during connection setup +- **shutdownGeneration**: Counter-based stale connection detection after disconnect + +## PUBLIC API + +```typescript +class SkillMcpManager { + constructor(options?: { createOAuthProvider? }) + getOrCreateClient(info, config): Promise + disconnectSession(sessionID): Promise + disconnectAll(): Promise + listTools/Resources/Prompts(info, context): Promise<...[]> + callTool(info, context, name, args): Promise + readResource(info, context, uri): Promise + getPrompt(info, context, name, args): Promise + getConnectedServers(): string[] + isConnected(info): boolean +} +``` + +## RETRY SEMANTICS + +- `getOrCreateClientWithRetry()` — 3 attempts with force reconnect on failure +- `withOperationRetry()` — OAuth-aware wrapper: step-up on 403, token refresh on 401 + +## SECURITY + +- **env-cleaner.ts** — strips npm/pnpm config vars (prevents pnpm project isolation issues) and secret patterns before stdio spawn +- **error-redaction.ts** — masks tokens/secrets in error messages before logger.log +- **OAuth isolation** — auth providers keyed by server URL, tokens never cross servers diff --git a/src/hooks/runtime-fallback/AGENTS.md b/src/hooks/runtime-fallback/AGENTS.md new file mode 100644 index 000000000..92ca303c9 --- /dev/null +++ b/src/hooks/runtime-fallback/AGENTS.md @@ -0,0 +1,102 @@ +# src/hooks/runtime-fallback/ — Reactive Provider Error Recovery + +**Generated:** 2026-04-09 + +## OVERVIEW + +32 files. Session Tier hook that **reactively** switches to fallback models when API providers return errors at runtime (429, 503, quota exhausted, cooldown signals). Distinct from `model-fallback` (which applies preemptively at chat.params). + +## RUNTIME-FALLBACK vs MODEL-FALLBACK + +| Aspect | runtime-fallback | model-fallback | +|--------|-----------------|----------------| +| **Trigger** | Reactive — after error occurs | Proactive — at request time | +| **Event** | session.error, message.updated, session.status | chat.params | +| **Config source** | `categories[].fallback_models`, `agents[].fallback_models` | `AGENT_MODEL_REQUIREMENTS` hardcoded chains | +| **State** | Per-session FallbackState + cooldown tracking | Module-global pendingModelFallbacks | +| **Use case** | Provider errors during execution | Pre-configured agent fallback chains | + +They operate **independently** — no direct integration. + +## ERROR DETECTION + +### HTTP Status Codes (configurable) +Default retry codes: `429, 500, 502, 503, 504` + +### Error Message Patterns (constants.ts) +``` +/rate.?limit/i, /too.?many.?requests/i, /quota.*reset.*after/i, +/exhausted.*capacity/i, /all.*credentials.*for.*model/i, +/cool(?:ing)?.?down/i, /model.*not.*supported/i, +/service.?unavailable/i, /overloaded/i, /temporarily.?unavailable/i +``` + +### Error Type Classification (error-classifier.ts) +- `missing_api_key` — provider rejects auth +- `model_not_found` — model unavailable +- `quota_exceeded` — billing/quota hit +- Auto-retry signal detection via `auto-retry-signal.ts` — extracts "retrying in ~2 weeks" style signals, triggers immediate fallback + +## FALLBACK STATE MACHINE + +```typescript +interface FallbackState { + originalModel: string + currentModel: string + fallbackIndex: number + failedModels: Map // model → cooldown-until timestamp + attemptCount: number + pendingFallbackModel?: string +} +``` + +## FALLBACK CHAIN RESOLUTION (fallback-models.ts) + +Priority order: +1. **Session category** (via SessionCategoryRegistry) +2. **Agent config** `fallback_models` +3. **Agent's category** `fallback_models` +4. **Session ID pattern match** (detect agent from session ID format) + +## RETRY FLOW + +``` +session.error / message.updated (with error) / session.status (retry signal) + → isRetryableError(error)? + → getFallbackModelsForSession(sessionID, agent) + → findNextAvailableFallback() — skip cooldown models + → prepareFallback() — update state, mark current failed + → dispatchFallbackRetry() — toast notification + promptAsync with new model + → 30s timeout — abort and try next if exceeded +``` + +## COOLDOWN MECHANISM + +Failed models enter 60s cooldown. `findNextAvailableFallback()` skips models in cooldown, preventing thrashing on persistently failing models. + +## KEY FILES + +| File | Purpose | +|------|---------| +| `hook.ts` | `createRuntimeFallbackHook()` — composes all handlers | +| `event-handler.ts` | Route session lifecycle (created, error, stop, idle) | +| `message-update-handler.ts` | Handle error parts in `message.updated` | +| `session-status-handler.ts` | Handle provider retry signals in session.status | +| `chat-message-handler.ts` | Apply fallback model override on chat.message | +| `error-classifier.ts` | `isRetryableError()`, `classifyErrorType()` | +| `auto-retry-signal.ts` | Extract "retrying in..." signals | +| `fallback-state.ts` | State machine: createFallbackState, prepareFallback, findNextAvailableFallback, isModelInCooldown | +| `fallback-models.ts` | Resolve chain from config hierarchy (strings + raw objects) | +| `fallback-bootstrap-model.ts` | Derive initial model when state missing | +| `fallback-retry-dispatcher.ts` | Toast + dispatch retry orchestration | +| `auto-retry.ts` | Abort, timeout scheduling, cleanup | +| `agent-resolver.ts` | Session → agent name normalization | +| `retry-model-payload.ts` | Build model payload (providerID/modelID/variant/reasoningEffort) | +| `visible-assistant-response.ts` | Detect if assistant produced real output vs just errors | +| `last-user-retry-parts.ts` | Extract last user message parts for retry | + +## NOTES + +- Cooldown and failure tracking are **per-session** — concurrent sessions don't share state +- `visible-assistant-response.ts` prevents retry if the assistant already produced a partial valid response +- Runtime-fallback is registered in the Session Tier via `create-session-hooks.ts` diff --git a/src/openclaw/AGENTS.md b/src/openclaw/AGENTS.md new file mode 100644 index 000000000..2019180ac --- /dev/null +++ b/src/openclaw/AGENTS.md @@ -0,0 +1,82 @@ +# src/openclaw/ — Bidirectional External Integration + +**Generated:** 2026-04-09 + +## OVERVIEW + +18 files. Bidirectional integration system: **outbound** session event notifications (Discord/Telegram/HTTP webhook/shell command) AND **inbound** reply handling (daemon polls chat apps, injects replies back into tmux session). Named "claw" because it reaches out from OpenCode and pulls replies back in. + +## BIDIRECTIONAL FLOW + +### Outbound (OpenCode → External) +``` +OpenCode session event → dispatchOpenClawEvent() + → runtime-dispatch.ts: map event to OpenClaw event + → dispatcher.ts: execute gateway (HTTP POST or shell command) + → session-registry.ts: record message ID ↔ sessionID ↔ tmux pane +``` + +### Inbound (External → OpenCode) +``` +Discord/Telegram API → reply-listener daemon (separate Bun process) + → reply-listener-{discord,telegram}.ts: poll every 3s + → session-registry.ts: look up target tmux session from message ID + → reply-listener-injection.ts: send-keys into tmux pane (rate limited) +``` + +## KEY FILES + +| File | Purpose | +|------|---------| +| `index.ts` | `wakeOpenClaw()`, `initializeOpenClaw()` — main entry | +| `types.ts` | `OpenClawConfig`, `OpenClawPayload`, `WakeResult` types | +| `config.ts` | Gateway resolution + URL validation (HTTPS required, localhost exception) | +| `dispatcher.ts` | HTTP POST + shell command execution with variable interpolation | +| `runtime-dispatch.ts` | Maps OpenCode events → OpenClaw events, orchestrates dispatch | +| `session-registry.ts` | JSONL registry correlating message IDs ↔ sessions ↔ panes (file-locked) | +| `reply-listener.ts` | Daemon lifecycle: start/stop, poll loop, state persistence | +| `reply-listener-discord.ts` | Discord API polling | +| `reply-listener-telegram.ts` | Telegram API polling | +| `reply-listener-injection.ts` | Inject received reply into tmux pane (rate limiting + user filtering) | +| `reply-listener-state.ts` | Daemon state: PID, config signature, poll tracking | +| `daemon.ts` | Daemon entry point (runs as detached Bun process) | +| `tmux.ts` | `capturePane()`, `sendToPane()` utilities | + +## GATEWAY TYPES + +| Type | Config | Execution | +|------|--------|-----------| +| **HTTP webhook** | `url` field | POST with JSON payload | +| **Shell command** | `command` field | Execute with env vars (OPENCLAW_*) | + +## PAYLOAD VARIABLES (interpolation) + +`{sessionId}`, `{projectPath}`, `{tmuxSession}`, `{timestamp}`, `{eventType}` (session.created/deleted/idle), `{messageContent}`, `{promptSummary}` + +## INTEGRATION POINTS + +- `src/index.ts` — calls `initializeOpenClaw(pluginConfig.openclaw)` at plugin startup (if `enabled`) +- `src/plugin/event.ts` — calls `dispatchOpenClawEvent()` for session.created/deleted/idle +- `src/config/schema/openclaw.ts` — Zod config schema + +## DAEMON LIFECYCLE + +``` +initializeOpenClaw(config) + → wakeOpenClaw() if reply_listener.enabled + → spawn daemon.ts as detached process + → daemon writes PID to .opencode/openclaw.state.json + → daemon polls Discord/Telegram every 3s + → on reply: lookup in session-registry → inject into tmux via send-keys +``` + +## SECURITY + +- **URL validation**: HTTPS required except localhost (config.ts) +- **Authorized users**: Inbound replies filtered by allowed user ID list +- **Token redaction**: Secrets masked in logs and error messages +- **Rate limiting**: Reply injection throttled per pane + +## TESTING NOTE + +`reply-listener-discord.test.ts` is **always isolated** in CI (listed in `ALWAYS_ISOLATED_TEST_FILES` of `script/run-ci-tests.ts`). Reason: mocks `globalThis.fetch` for Discord API simulation — needs process isolation to avoid interference with shared test batch.