- Sanitize MCP tool result text with recursivelySanitizeUnicode() to prevent
Unicode injection via malicious MCP servers (tool definitions and prompts
were already sanitized, but tool call results were not)
- Read sandbox.enabled only from trusted settings sources (user, local, flag,
policy) — exclude projectSettings to prevent malicious repos from silently
disabling the sandbox via .claude/settings.json
- Disable git hooks in plugin marketplace clone/pull/submodule operations
with core.hooksPath=/dev/null to prevent code execution from cloned repos
- Remove ANTHROPIC_FOUNDRY_API_KEY from SAFE_ENV_VARS to prevent credential
injection from project-scoped settings without trust verification
- Add ssrfGuardedLookup to WebFetch HTTP requests to block DNS rebinding
attacks that could reach cloud metadata or internal services
Security: closes trust boundary gap where project settings could override
security-critical configuration. Follows the existing pattern established
by hasAllowBypassPermissionsMode() which already excludes projectSettings.
Co-authored-by: auriti <auriti@users.noreply.github.com>
* fix: rename .claude.json to .openclaude.json with legacy fallback
Rename the global config file from ~/.claude.json to ~/.openclaude.json,
following the same migration pattern as the config directory
(~/.claude → ~/.openclaude).
- getGlobalClaudeFile() now prefers .openclaude.json; falls back to
.claude.json only if the legacy file exists and the new one does not
- Add .openclaude.json to filesystem permissions allowlist (keep
.claude.json for legacy file protection)
- Update all comment/string references from ~/.claude.json to
~/.openclaude.json across 12 files
New installs get .openclaude.json from the start. Existing users
continue using .claude.json until they rename it (or a future explicit
migration).
* test: add unit tests for getGlobalClaudeFile migration branches
Covers the three cases:
- new install (neither file exists) → .openclaude.json
- existing user (only legacy .claude.json exists) → .claude.json
- migrated user (both files exist) → .openclaude.json
---------
Co-authored-by: Zartris <14197299+Zartris@users.noreply.github.com>
* feat: native Anthropic API mode for Claude models on GitHub Copilot
When using Claude models through GitHub Copilot, automatically switch from
the OpenAI-compatible shim to Anthropic's native messages API format.
The Copilot proxy (api.githubcopilot.com) supports Anthropic's native API
for Claude models. This enables cache_control blocks to be sent and
honoured, allowing explicit prompt caching control (as opposed to relying
solely on server-side auto-caching).
Changes:
- Add isGithubNativeAnthropicMode() in providers.ts that auto-enables when
the resolved model starts with "claude-" and the GitHub provider is active
- Create a native Anthropic client in client.ts using the GitHub base URL
and Bearer token authentication when native mode is detected
- Enable prompt caching in claude.ts for native GitHub mode so cache_control
blocks are sent (previously only allowed for firstParty/bedrock/vertex)
- CLAUDE_CODE_GITHUB_ANTHROPIC_API=1 env var to force native mode for any
model
Benefits:
- Proper Anthropic message format (no lossy OpenAI translation)
- Explicit cache_control blocks for fine-grained caching control
- Potentially better Claude model behaviour with native format
Related: #515
* fix: scope force flag to Claude models and add isGithubNativeAnthropicMode tests
- CLAUDE_CODE_GITHUB_ANTHROPIC_API=1 now returns false for non-Claude models
(force flag still useful for aliases like 'github:copilot' with no model
resolved yet, where it returns true when model is empty)
- Add 7 focused tests covering mode detection: off without GitHub provider,
auto-detect via OPENAI_MODEL and resolvedModel, non-Claude model rejection,
and force-flag behaviour for claude/non-claude/no-model cases
* fix: detect github:copilot:claude- compound format, remove force flag
OPENAI_MODEL for GitHub Copilot uses the format 'github:copilot:MODEL'
(e.g. 'github:copilot:claude-sonnet-4'), which does not start with 'claude-'.
Auto-detection now handles both bare model names and the compound format.
The CLAUDE_CODE_GITHUB_ANTHROPIC_API force flag is removed: with proper
compound-format detection there is no remaining gap it could fill, and
keeping a broad override flag without a concrete use case invites misuse.
Tests updated to cover the compound format, generic alias (false), and
non-Claude compound model (github:copilot:gpt-4o → false).
* fix: use includes('claude-') for model detection, remove force flag
Detection was broken for the standard GitHub Copilot compound format
'github:copilot:claude-sonnet-4' which does not start with 'claude-'.
Using includes('claude-') handles bare names, compound names, and any
future variants without needing updates.
The CLAUDE_CODE_GITHUB_ANTHROPIC_API force flag is removed as it was
a workaround for the broken detection, not a genuine use case.
---------
Co-authored-by: Zartris <14197299+Zartris@users.noreply.github.com>
Reasoning models (MiniMax M2.7, GLM-4.5/5, DeepSeek, Kimi K2) inline
chain-of-thought inside <think>...</think> tags in the content field
rather than using the reasoning_content channel. The prior phrase-matching
sanitizer (looksLikeLeakedReasoningPrefix) only caught English-prose
preambles like "I should"/"the user asked", missed tag-based leaks
entirely, and risked false-stripping legitimate assistant output.
Replace with a structural tag-based approach (same pattern as hermes-agent):
- createThinkTagFilter() — streaming state machine that buffers partial
tags across SSE delta boundaries (<th| + |ink>), so tags split mid-chunk
still parse correctly.
- stripThinkTags() — whole-text cleanup for non-streaming responses and
as a safety net. Handles closed pairs, unterminated opens at block
boundaries, and orphan tags.
- Recognizes think, thinking, reasoning, thought, REASONING_SCRATCHPAD
case-insensitively, including tags with attributes.
- False-negative bias: flush() discards buffered partial tags at stream
end rather than leaking them.
Existing phrase-based shim tests updated to exercise the actual <think>
tag leak. Added regression tests confirming legitimate prose starting
with "I should..." is preserved (the old sanitizer's main false-positive).
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When set, disables strict schema normalization for non-Gemini providers.
Useful for OpenAI-compatible endpoints that reject MCP tools with complex
optional params (e.g. list[dict]) with "Extra required key ... supplied"
errors.
* fix: remove cached mcpClient in diagnostic tracking to prevent stale references
Resolves TODO comment about not caching the connected mcpClient since it can change.
Changes:
- Remove cached mcpClient field from DiagnosticTrackingService
- Add currentMcpClients storage to track active clients
- Update beforeFileEdited, getNewDiagnostics, and ensureFileOpened to accept client parameter
- Add backward-compatible methods to maintain existing API
- Update all callers to use new methods
- Add comprehensive test coverage
This prevents using stale MCP client references during reconnections,
making diagnostic tracking more reliable.
Fixes #TODO
* docs: add my contributions section to README
Add fork-specific section highlighting:
- Diagnostic tracking enhancement (PR #727)
- Technical skills demonstrated
- Links to original project and my work
- Professional contribution showcase
* revert: remove README.md contributions section to comply with reviewer request
- Remove 'My Fork & Contributions' section from README.md
- Keep README.md focused on original project documentation
- Maintain clean, project-focused README as requested by reviewer
* fix(api): drop orphan tool results to satisfy Mistral/OpenAI strict role sequence
* test: add test for orphan tool results and restore gemini comments
Problem: After auto-compaction with DeepSeek models (e.g., deepseek-chat),
the status line displayed ~16% remaining until next auto-compact, but users
expected ~30% (since compaction reduces usage to roughly half of the full
128k context).
Root cause: calculateTokenWarningState() used the auto-compaction threshold
(effectiveContextWindow - 13k buffer) as the denominator for percentLeft.
For DeepSeek-chat:
- Raw context: 128,000
- Effective: 119,808 (128k - 8,192 output reservation)
- Threshold: 106,808 (effective - 13k buffer)
At 90k usage:
- Old: (106,808 - 90k) / 106,808 ≈ 16%
- Expected: (128,000 - 90k) / 128,000 ≈ 30%
Fix: Change percentLeft calculation to use raw context window from
getContextWindowForModel() as denominator, while keeping threshold-based
warnings/triggers unchanged. This makes the displayed percentage show
remaining capacity relative to the model's full context size.
Impact:
- UI now shows correct % of total context remaining
- Auto-compaction trigger point unchanged (still ~90% of effective window)
- All other threshold calculations unaffected
Testing:
- Manual verification: DeepSeek-chat at 90k tokens shows 30% remaining (was 16%)
- Manual verification: Threshold still triggers at ~106k tokens
- Build succeeds: npm run build
- No breaking changes: Callers only depend on percentLeft for display; threshold logic unchanged
Fixes the user-reported discrepancy for DeepSeek and other OpenAI-compatible models.
* fix(mcp): sync required array with properties in tool schemas
MCP servers can emit schemas where the required array contains keys
not present in properties. This causes API 400 errors:
"Extra required key 'X' supplied."
- Add sanitizeSchemaRequired() to filter required arrays
- Apply it to MCP tool inputJSONSchema before sending to API
- Also fix filterSwarmFieldsFromSchema to update required after
removing properties
Fixes#525
* test: add MCP schema required sanitization test
* add mistral and gemini provider type for profile provider field
* load latest locally selected
* env variables take precedence over json save
* add gemini context windows and fix gemini defaulting for env
* load on startup fix
* fix failing tests
* clarify test message
* fix variable mismatches
* fix failing test
* delete keys and set profile.apiKey for mistral and gemini
* switch model as well when switching provider
* set model when adding a new model
Previously, the startup intro screen always displayed
'https://api.anthropic.com' as the endpoint for Anthropic provider,
even when a custom endpoint was configured via ANTHROPIC_BASE_URL.
This fix reads ANTHROPIC_BASE_URL from environment and displays the
actual configured endpoint, providing accurate information to users
about where their API requests will be sent (proxy gateways, staging,
custom Anthropic-compatible APIs).
Also adds isLocal detection for local endpoints to show appropriate
visual indicator in the startup banner.
Co-authored-by: Ali Alakbarli <ali.alakbarli@users.noreply.github.com>
This commit fixes a crash in the CLI that occurs when navigating to the /help commands tab. The issue happens because the truncate function receives an undefined value for the str parameter if a command lacks a description, causing the .indexOf() method to throw an exception. To resolve this, an early return check was added at the beginning of the function to gracefully handle empty values and prevent the UI from crashing.
When returning to the provider manager menu after completing an action
(add, edit, delete, set active, etc.), the cursor now lands on "Done"
instead of the first option ("Add provider"). This prevents accidental
re-entry into the same action if the user presses Enter quickly.
On initial /provider invocation, the cursor still starts on the first
option ("Add provider") as expected.
Co-authored-by: Ali Alakbarli <ali.alakbarli@users.noreply.github.com>
* fix: OAuth tokens secure storage for Windows & Linux
* fix(mcp): MCP Tool Re-exposure & Strict Input Validation
Fixes the MCP re-exposure bug by correctly handling tool deduplication, input validation with Ajv, and structured output (including images). Also disables experimental API betas by default to prevent 500 errors on external accounts.
* fix(mcp): skip official registry prefetch in non-first-party mode
Prevents unnecessary calls to Anthropic's MCP registry when using other API providers.
* fix(cli): disable experimental API betas by default
This prevents 500 errors from Anthropic's API when tool-calling with non-Anthropic accounts or models that don't support certain beta features.
* fix: issues raised in the PR review for #675
* test: add tests for provider model env updates and multi-model profiles
Add comprehensive tests covering:
- OPENAI_MODEL/ANTHROPIC_MODEL env updates on provider activation
- Cross-provider type switches (openai ↔ anthropic) clearing stale env
- Multi-model profile activation using only the first model for env vars
- Model options cache population from comma-separated model lists
- getProfileModelOptions generating correct ModelOption arrays
* feat: multi-model provider support and model auto-switch
Support comma-separated model names in provider profiles (e.g.
"glm-4.7, glm-4.7-flash"). The first model is used as default on
activation; all models appear in the /model picker for easy switching.
When switching active providers, the session model now automatically
updates to the new provider's first model. The multi-model list is
preserved across switches and /model selections.
Changes:
- Add parseModelList, getPrimaryModel, hasMultipleModels utilities
with full test coverage (19 tests)
- Use getPrimaryModel when applying profiles to process.env so only
the primary model is set in OPENAI_MODEL/ANTHROPIC_MODEL
- Update ProviderManager UI to hint at multi-model syntax and show
model count in provider list summaries
- Populate model options cache from multi-model profiles on activation
so all models appear in /model picker regardless of base URL type
- Guard persistActiveProviderProfileModel against overwriting
comma-separated lists: models already in the profile are session
selections, not profile edits
- Set AppState.mainLoopModel to the actual model string on provider
switch so Anthropic profiles use the configured model instead of
falling back to the built-in default
* fix: only show profile models when provider profile env is applied
Guard the profile model picker options behind a
PROFILE_ENV_APPLIED check. getActiveProviderProfile() has a
?? profiles[0] fallback that returns the first profile even when
no profile is explicitly active, causing users with inactive
profiles to lose all standard model options (Opus, Haiku, etc.)
from the /model picker.
* fix: show all model names for profiles with 3 or fewer models
Instead of a summary format for multi-model profiles, display all
model names when there are 3 or fewer. Only use the "+ N more"
format for profiles with 4+ models.
* fix: preserve standard model options in picker alongside profile models
The previous implementation used an early return that replaced all
standard picker options (Opus, Haiku, Sonnet for Anthropic; Codex/GPT
models for OpenAI) with only the profile's custom models.
Changes:
- Collect profile models into a shared array instead of early returning
- Append profile models to firstParty path (Opus + Haiku + Sonnet + custom)
- Append profile models to PAYG 3P path (Codex + Sonnet + Opus + Haiku + custom)
- Guard collection behind PROFILE_ENV_APPLIED to avoid ?? profiles[0] fallback
Fixes review feedback: standard models are no longer hidden when a
provider profile with custom models is active. Users see both the
standard options and their profile's models.
---------
Co-authored-by: Ali Alakbarli <ali.alakbarli@users.noreply.github.com>
* feat: add NVIDIA NIM and MiniMax provider support
- Add nvidia-nim and minimax to --provider CLI flag
- Add model discovery for NVIDIA NIM (160+ models) and MiniMax
- Update /model picker to show provider-specific models
- Fix provider detection in startup banner
- Update .env.example with new provider options
Supported providers:
- NVIDIA NIM: https://integrate.api.nvidia.com/v1
- MiniMax: https://api.minimax.io/v1
* fix: resolve conflict in StartupScreen (keep NVIDIA/MiniMax + add Codex detection)
* fix: resolve providerProfile conflict (add imports from main, keep NVIDIA/MiniMax)
* fix: revert providerSecrets to match main (NVIDIA/MiniMax handled elsewhere)
* fix: add context window entries for NVIDIA NIM and new MiniMax models
* fix: use GLM-5 as NVIDIA NIM default and MiniMax-M2.5 for consistency
* fix: address remaining review items - add GLM/Kimi context entries, max output tokens, fix .env.example, revert to Nemotron default
* fix: filter NVIDIA NIM picker to chat/instruct models only, set provider-specific API keys from saved profiles
* chore: add more NVIDIA NIM context window entries for popular models
* fix: address remaining non-blocking items - fix base model, clear provider API keys on profile switch
* fix: strip comments before scanning for missing imports
The scanForMissingImports regex matched require() and import() patterns
inside JSDoc comments, causing false-positive missing module detection.
A documented path like `require('./commands/proactive.js')` in a comment
was resolved from the wrong directory, marked as missing, then the global
onResolve handler intercepted ALL imports of that specifier — including
valid ones — replacing them with truthy noop stubs that broke runtime.
Strip block (/* */) and line (//) comments from source before scanning.
* fix: repair 10 pre-existing test failures
- promptIdentity.test.ts: define MACRO global (ISSUES_EXPLAINER etc.)
for test mode where Bun.define build-time replacements aren't active
- context.test.ts: clear OPENAI_MODEL env var in each test — the user's
environment (e.g. OPENAI_MODEL=github_copilot/gpt-5.4) polluted the
provider-qualified lookup, returning wrong context windows
- openclaudePaths.test.ts: set CLAUDE_CONFIG_DIR to force .openclaude
path when ~/.openclaude doesn't exist on the test machine
* feat: open useful USER_TYPE-gated features to all users
Remove 13 process.env.USER_TYPE === 'ant' gates that restricted useful
features to Anthropic employees. These features work without Anthropic
infrastructure and are now available to all open-build users.
Features opened:
- Agent nesting (sub-agents can spawn sub-agents)
- Effort 'max' persistence in settings
- Plan mode interview phase (controlled by feature flags)
- Sandbox disabled commands (via ~/.claude/feature-flags.json)
- All tips visible to all users (plan mode, feedback, shift-tab)
Simplified:
- Fullscreen defaults to off (use /config to enable)
- Explore agent always uses haiku model
- Plan mode tool uses conservative prompt for all users
Continues the USER_TYPE cleanup from #637 (dead code) and builds
on #639 (local feature flags).
* fix: address Copilot review comments — remove residual dead code
1. bridgeConfig.ts: ungate bridge override functions — return env vars
directly instead of hardcoded undefined
2. bridgeMain.ts + initReplBridge.ts: ungate sessionIngressUrl — read
CLAUDE_BRIDGE_SESSION_INGRESS_URL without USER_TYPE check
3. tools.ts: remove dead ConfigTool/TungstenTool imports, narrow
eslint-disable scope, stub REPLTool/SuggestBackgroundPRTool to null
4. readOnlyValidation.ts: remove orphaned ANT_ONLY_COMMAND_ALLOWLIST
and unused GH_READ_ONLY_COMMANDS import
5. insights.ts: remove entire remote collection plumbing (types,
functions, options, display logic)
6. osc.ts: hardcode supportsTabStatus() to false (internal-only feature)
7. state.ts: simplify addSlowOperation/getSlowOperations to no-ops,
remove dead constants
* fix: address Copilot review on PR #644
1. settings/types.ts: allow 'max' effort level for all users in Zod
schema — was still gated behind USER_TYPE=ant, causing 'max' to be
silently dropped on settings reload
2. shouldUseSandbox.ts: defensively normalize disabledCommands from
feature flag config with Array.isArray() guards
* fix: address second round of Copilot review on PR #644
1. shouldUseSandbox.ts: validate top-level shape of disabledCommands
before accessing properties (handles null/primitive from feature flag)
2. fullscreen.ts: update JSDoc to reflect removal of USER_TYPE default
3. osc.ts: update JSDoc — "Ant-only" → "Currently disabled"
* fix: resolve 12 bugs across API, MCP, agent tools, web search, and context overflow
API fixes:
- Fix Gemini 400 error: delete 'store: false' field for Gemini endpoints
(was globally injected, Gemini rejects unknown fields)
- Fix session timeout 500 errors after ~25min: add 120s idle timeout
on SSE stream readers in openaiShim and codexShim to detect dead
connections and trigger withRetry reconnection
- Fix context overflow 500 errors: add handler in errors.ts for 500
responses caused by oversized conversation context (too many tokens),
surfacing user-friendly message with recovery actions instead of raw
'API Error: 500'
Agent loop fix:
- Fix premature task completion: detect continuation signals like
'so now I have to do it' in assistant text without tool calls and
inject a meta nudge to force the agent to continue
Web search improvements:
- Increase result counts: Bing/Tavily/Exa/Firecrawl from 10→15,
Mojeek/You/Jina from default→10 (explicit), max_uses 8→15
MCP fixes:
- Reduce default tool timeout from ~27.8 hours to 5 minutes
(tools no longer hang indefinitely on unresponsive servers)
- Add retry logic (3 attempts) for tools/list fetch failures
(prevents all MCP tools from silently disappearing on timeout)
- Add abort signal check in URL elicitation retry loop
- Improve MCP error messages with server and tool name context
Agent tool fixes:
- Fix SendMessage race condition: double-check task status before
auto-resuming stopped agents to prevent duplicate registration
- Fix auto-compact circuit breaker gap: when auto-compact fails 3+
consecutive times, proactively block oversized context BEFORE the
API call instead of letting it 500. Clear message with recovery
instructions (/new, /compact, rewind).
Tests: 850 total, 0 failures (25 new bugfix tests)
* fix: address all 4 review blockers + 6 additional issues from PR #674
Blockers (from Vasanthdev2004 review):
1. Continuation nudge infinite loop — no loop guard
Added continuationNudgeCount to State, capped at MAX_CONTINUATION_NUDGES (3).
Counter increments on each nudge, resets on tool execution (next_turn).
2. Continuation signal regexes too broad — high false-positive rate
Tightened all patterns to require explicit action verbs. Added completion
marker check (done/finished/completed/summary). Broad patterns only fire
on messages <80 chars.
3. BUGFIXES.md in repo root — scope contamination
Removed. PR description already contains this info.
4. AgentTool dump state cleanup is comment-only, not a bug fix
Wrapped clearInvokedSkillsForAgent and clearDumpState in individual
try/catch blocks so one failure doesn't prevent the other.
Additional issues:
5+6. readWithTimeout ignores AbortSignal, timer leak on abort
Added optional signal param to openaiStreamToAnthropic,
codexStreamToAnthropic, collectCodexCompletedResponse, readSseEvents.
Added abort listener that clears idle timer so AbortError surfaces
cleanly instead of spurious idle timeout.
7. MCP error format change breaks consumers
Reverted human-readable message to original errorDetails format.
Moved server/tool context to telemetryMessage param only.
10. AgentTool test broken by comment change
Updated test assertions to match new defensive cleanup text + try/catch.
12. Mojeek test regex dangerously broad
Tightened to match searchParams.set('t', '10') specifically.
14. linkup.ts in providerCounts test — no result count field
Removed from providers list (uses depth param, not result count).
15. Error message overlap between errors.ts and query.ts
Prefixed errorDetails with 'Context overflow (500):' to distinguish.
Tests: 851 pass, 0 fail
---------
Co-authored-by: openclaude-bot <bot@openclaude.ai>
Co-authored-by: Fix Bot <fix@openclaude.dev>
* feat: implement Monitor tool for streaming shell output
Add the Monitor tool that executes shell commands in the background and
streams stdout line-by-line as notifications to the model. This enables
real-time monitoring of logs, builds, and long-running processes.
Implementation:
- MonitorTool (src/tools/MonitorTool/) — spawns LocalShellTask with
kind='monitor', returns immediately with task ID
- MonitorMcpTask (src/tasks/MonitorMcpTask/) — task lifecycle management
and agent cleanup via killMonitorMcpTasksForAgent()
- MonitorPermissionRequest — permission dialog component
The codebase already had all integration points wired (tools.ts, tasks.ts,
PermissionRequest.tsx, LocalShellTask kind='monitor', BashTool prompt).
This PR provides the missing implementations.
* fix: command-specific permission rule + architecture docs
- MonitorPermissionRequest: "don't ask again" now creates a
command-prefix rule (like BashTool) instead of a blanket
tool-name-only rule that would auto-allow all Monitor commands
- MonitorMcpTask: clarify architecture comments explaining why
monitor_mcp type exists as a registry stub while actual tasks
are local_bash with kind='monitor'
* fix: address Copilot review feedback
- Fix permission rule field: expression → ruleContent (Copilot #1)
- Handle empty command prefix: skip rule creation (Copilot #2)
- Remove unused useTheme() import (Copilot #3)
- Save permission rules under 'Bash' toolName so bashToolHasPermission
can match them — Monitor delegates to Bash permission system (Copilot #4)
- Remove unused logError import from MonitorMcpTask (Copilot #6)
- Copilot #5 (getAppState throws): same pattern as BashTool:915, not a bug
* feat: activate local-only team memory in open build
Enable the TEAMMEM feature flag and the isTeamMemoryEnabled() gate so
team memory works in local-only mode for all open-build users.
Team memory is a shared memory system scoped per-project, stored at
~/.claude/projects/<project>/memory/team/. The implementation is
already almost entirely local — extraction, UI, prompts, file
detection, and path validation all work on local files.
The cloud sync overlay (OAuth + API) is cleanly separated: the
watcher does an early return when OAuth is unavailable, so the
feature degrades gracefully to local-only storage with no crashes.
What works locally:
- Memory extraction (auto + team, combined prompts)
- Team MEMORY.md loaded into conversation context
- File selector with team memory folder option
- Collapse tracking (read/search/write counts)
- Secret scanning before persistence
- Path validation + symlink protection
What requires OAuth (not available in open build):
- Cloud sync between team members
- Automatic push/pull via file watcher
* fix: preserve opt-out gate for team memory via feature flag
Change isTeamMemoryEnabled() to read tengu_herring_clock with default
true instead of unconditional return true. This enables team memory by
default while preserving user opt-out via ~/.claude/feature-flags.json.
* feat: activate coordinator mode in open build
Enable the COORDINATOR_MODE feature flag and create the missing
src/coordinator/workerAgent.ts module that provides worker agent
definitions for the coordinator.
Coordinator mode is a multi-agent system where a coordinator agent
orchestrates independent workers via AgentTool, SendMessageTool,
and TaskStopTool. The implementation was already 99% complete
(19KB coordinatorMode.ts, 26 gate sites across 15 files) — only
the workerAgent module was missing from the source snapshot.
Workers get the standard built-in agents (general-purpose, explore,
plan). The coordinator system prompt (252 lines) handles all
orchestration logic.
Activate at runtime: CLAUDE_CODE_COORDINATOR_MODE=1
Optional scratchpad: set {"tengu_scratch": true} in
~/.claude/feature-flags.json (#639)
* fix: add worker agent type for coordinator mode
The coordinator system prompt instructs the model to spawn workers with
subagent_type: "worker", but no agent had agentType === 'worker'.
This caused AgentTool to throw "Agent type 'worker' not found" on
every coordinator spawn attempt.
Add a WORKER_AGENT definition that spreads GENERAL_PURPOSE_AGENT with
agentType: 'worker'. Also use the narrower BuiltInAgentDefinition type.
* feat: activate built-in explore and plan agents in open build
Enable BUILTIN_EXPLORE_PLAN_AGENTS so Explore (fast, haiku, read-only)
and Plan (architect, read-only) agents are available to all users in
both normal and coordinator modes.
This resolves the inconsistency flagged in code review: coordinator
workers had access to Explore/Plan agents while normal sessions did not.
The GrowthBook A/B test gate (tengu_amber_stoat) defaults to true via
the no-telemetry stub. Users can disable via feature-flags.json (#639).
The provider profile activation guard in applyActiveProviderProfileFromConfig()
only checked CLAUDE_CODE_USE_* environment flags, which are never set for the
default anthropic provider. This allowed two terminals sharing ~/.claude.json
to overwrite each other's active provider when one was using anthropic and
the other a third-party provider.
Now also checks the OCODE_PROVIDER_PROFILE_APPLIED flag, which is set by all
profiles including anthropic, preventing cross-terminal interference.
Co-authored-by: Ali Alakbarli <ali.alakbarli@users.noreply.github.com>
* feat: add allowBypassPermissionsMode setting
Allow bypass permissions mode to appear in the mode list via
settings.json without requiring the --allow-dangerously-skip-permissions
CLI flag. The disableBypassPermissionsMode setting retains priority.
* fix: address Copilot review feedback on allowBypassPermissionsMode
- Security: read allowBypassPermissionsMode only from trusted settings
sources (user/local/flag/policy), excluding projectSettings to prevent
a malicious repo from enabling bypass mode
- UX: update error messages to reference the correct CLI flag
(--allow-dangerously-skip-permissions) and the new settings option
- Tests: add schema validation tests for the new field
* feat: enhance codex provider resolution with shortcut aliases and improved base URL handling
* fix: enhance codex alias resolution to include shell model
* feat: enhance Codex provider resolution to support new aliases and base URL handling
* fix: update base URL resolution logic for Codex models in GitHub mode
* fix: update provider transport logic to enforce Codex responses and adjust base URL handling
* fix: update provider request resolution to respect custom base URLs and adjust transport logic
* fix: restore OPENAI_MODEL environment variable handling in tests and provider config
* feat: implement /loop command with fixed and dynamic scheduling modes
Enable cron tools and /loop skill without the AGENT_TRIGGERS build flag
by removing feature guards from tools.ts, REPL.tsx, and skill registration.
The isKairosCronEnabled() runtime gate now enables cron unconditionally for
open builds while preserving the GrowthBook kill switch for ant builds.
The /loop skill supports four modes: fixed-interval with prompt, fixed-interval
maintenance, dynamic-prompt (self-pacing), and dynamic maintenance (bare /loop).
* chore: remove unused DEFAULT_INTERVAL constant from loop skill
* revert: drop infra changes, scope PR to /loop skill rewrite only
The cron activation layer (AGENT_TRIGGERS guard removal, isKairosCronEnabled
hardcode) is covered by an in-flight stack (#633, #639). Scope this PR to
just the loop.ts rewrite and its tests so it can land cleanly on top.
* fix: restore infra changes needed for /loop in open build
Bun's constant folder evaluates feature('AGENT_TRIGGERS') at bundle time
through the bun:bundle shim — even when the flag is flipped to true in
build.ts, the folded value is cached from the previous build and stays false.
This means the feature-gated require() blocks for cron tools, useScheduledTasks,
and loop skill registration all compile to dead code regardless of the flag.
Fix by removing the AGENT_TRIGGERS guards from the specific paths /loop needs:
- tools.ts: cron tools always registered (isEnabled gates visibility)
- REPL.tsx: useScheduledTasks always mounted
- index.ts: registerLoopSkill via static import, called unconditionally
- prompt.ts: isKairosCronEnabled() bypasses feature flag for non-ant builds
* fix: replace backslash line continuations with explicit delimiters in loop prompts
The backslash-newline sequences inside template literals were acting as
line continuations, collapsing newlines and merging prompt content with
surrounding instruction text. Replace with --- BEGIN/END --- markers
for unambiguous delimiting.
Also add tests for trailing "every" clause parsing, human-readable unit
normalization, and the non-interval "check every PR" case.
* fix: remove remaining AGENT_TRIGGERS guards from print.ts and constants/tools.ts
Completes the cron guard removal started in the previous commit.
The cron scheduler in non-interactive (-p) mode was dead because
print.ts still gated cronSchedulerModule/cronGate requires behind
feature('AGENT_TRIGGERS'), which Bun constant-folds to false in open
builds. Similarly, cron tool names were absent from
IN_PROCESS_TEAMMATE_ALLOWED_TOOLS.
Remove all three guards so the scheduler initialises (gated at runtime
by isKairosCronEnabled) and cron tools are allowed for in-process
teammates in all builds.
- Raise context window fallback from 8k to 128k for unknown OpenAI-compat models.
The 8k fallback caused effective context (8k minus output reservation) to go
negative, making auto-compact fire on every single message.
- Add safety floor in getEffectiveContextWindowSize(): effective context is
always at least reservedTokensForSummary + 13k buffer, ensuring the
auto-compact threshold stays positive.
- Add missing MiniMax model entries (M2.5, M2.5-highspeed, M2.1, M2.1-highspeed)
all at 204,800 context / 131,072 max output per MiniMax docs.
- Add tests for MiniMax variants, 128k fallback, and autoCompact floor.
Fixes#635
Co-authored-by: root <root@vm7508.lumadock.com>
The OPENAI_CONTEXT_WINDOWS/OPENAI_MAX_OUTPUT_TOKENS tables only contained
the `github:copilot:<model>` namespaced form used when talking directly to
Copilot via /onboard-github. When OpenClaude is pointed at a LiteLLM proxy
(which routes Copilot using the standard `github_copilot/<model>` convention),
the lookup missed and fell back to the conservative 8k default — causing the
compaction loop to fire repeatedly on every tick and blocking requests
before they left the client with repeated "not in context window table"
warnings on stderr.
Mirror the 11 active Copilot models with LiteLLM-style keys in both tables.
No behavior change for users of /onboard-github since namespaced entries
remain untouched and `lookupByKey` picks exact matches first.
The previous `isPrivateHostname` used a list of regexes against
`URL.hostname`. Several literal-address forms slipped past it:
- IPv4-mapped IPv6 `[::ffff:127.0.0.1]` (WHATWG URL normalizes to
`[::ffff:7f00:1]`, which no regex matched) — lets callers reach
loopback and other private v4 via an IPv6 literal.
- ULA `fc00::/7` (e.g. `[fc00::1]`) — not covered.
- Link-local `fe80::/10` (e.g. `[fe80::1]`) — not covered.
- IPv4 `169.254.0.0/16` (cloud metadata, including 169.254.169.254),
`100.64.0.0/10` (CGNAT), and the full `0.0.0.0/8` — not covered.
- The IPv6 regex `/^\[::1?\]$/` also required brackets, but `URL.hostname`
returns bracketed form anyway, so this part happened to work.
WHATWG `new URL(...)` already normalizes short-form / numeric / hex /
octal IPv4 to dotted-quad before we see it, so those cases were in fact
handled — the remaining gaps were IPv6 and a few missing v4 ranges.
Replace the regex list with:
- a dotted-quad IPv4 parser + int range check covering 0/8, 10/8,
100.64/10, 127/8, 169.254/16, 172.16/12, 192.168/16;
- a small IPv6 parser (handles `::` compression and embedded v4 suffix)
+ a byte-range check covering `::`, `::1`, IPv4-mapped (recursing
into the v4 classifier), IPv4-compatible, `fc00::/7`, `fe80::/10`,
and `fec0::/10`.
Export `isPrivateHostname` and add unit tests covering every bypass
listed above plus public-address negatives.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
`fetchWithRetry` created a fresh `AbortController` per attempt and did:
signal?.addEventListener('abort', () => controller.abort(), { once: true })
The listener was never removed. Consequences:
- On retry, a second listener was attached to the caller's signal,
each closing over a different controller.
- After a successful fetch, the listener remained on the caller's
signal indefinitely, referencing a controller whose work was done.
For a long-lived caller signal this is a slow leak.
- The `{ once: true }` only helps if the signal actually fires — on
non-aborted signals the listener stays attached forever.
Replace the manual controller + timer + listener dance with
`AbortSignal.any([signal, AbortSignal.timeout(ms)])`, which the
codebase already uses elsewhere (see src/services/mcp/xaa.ts). This:
- has no user-code listener to leak,
- gives each attempt a fresh independent timeout,
- cleanly distinguishes caller-initiated abort from timeout via
`signal.aborted` vs `timeoutSignal.aborted` before rewriting the
error as "Custom search timed out after Ns".
Also resets `lastStatus` per attempt so a 5xx on attempt 0 can't leak
into attempt 1's retry decision, and collapses the two redundant
retry branches (`lastStatus >= 500` and `lastStatus === undefined`)
into one.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>