17 Commits
Author SHA1 Message Date
Ken Jo 8b1c018635 docs(readme): correct Zed MCP config shape (#870) 2026-06-23 18:20:17 +03:00
Ken JoandClaude Opus 4.8 2608e344bb refactor(antigravity-cli): one-command agy plugin install (drop npm wrapper) + doc cleanup (#853)
refactor(antigravity-cli): one-command agy plugin install; drop npm wrapper

agy 1.0.7 added GitHub-subpath plugin install (with branch resolution), so the
former three-step flow shipped in #787 — `npm install -g` + `git clone` +
`npm run install:agy` (scripts/install-antigravity-cli-plugin.mjs) — is dead
weight. agy (<=1.0.6) `plugin install` accepted only a local directory, which is
why the wrapper existed; that constraint is gone.

Install is now one command, no clone, no wrapper:

  npm install -g context-mode
  agy plugin install https://github.com/mksglu/context-mode/tree/main/configs/antigravity-cli

- remove scripts/install-antigravity-cli-plugin.mjs + the install:agy npm script
  and its files[] entry
- antigravity-cli doctor `fix` strings now point at the one-command install
- README: split the conflated "Antigravity" entry into Antigravity IDE vs
  Antigravity CLI (agy); bring the agy section to the other install guides'
  level (Prerequisites / Install / MCP-only / Verify / Routing / Full configs);
  Verify points to the existing "Try It" prompts. Deep mechanics and
  troubleshooting stay in docs/platform-support.md
- docs/platform-support.md: one-command update + a "Verified: agy 1.0.10" note
  recording the >=1.0.7 install floor; hook contract unchanged through 1.0.10
  (config/hooks.json canonical since 1.0.8)
- tests: drop the wrapper-shape regression assertions (no leftover trace);
  bundle-content tests still guard the installable artifact

Preserved invariants: the bundle still registers MCP via its native
mcp_config.json (command: context-mode, env-pinned
CONTEXT_MODE_PLATFORM=antigravity-cli); the dual hooks.json + hooks/hooks.json
is kept (agy runtime reads root, validate reads subdir).

Verified on agy 1.0.10 (Linux): clean-room single-command install registers
MCP + hooks + skill from a zero baseline; tools/list exposes 11 Gemini-safe
ctx_* tools (0 const / 0 additionalProperties); `agy -p` smoke returns 12.
npm run build + tsc --noEmit + targeted vitest (47) pass. Bundles are
CI-managed (bundle.yml) and not included.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 04:50:52 +03:00
Ken JoandClaude Opus 4.8 77e22a46cc fix(lifecycle): reap idle MCP bridge children to stop pi/omp accumulation (#854) (#855)
On hosts that spawn one MCP server per sub-context/subagent under a single
long-lived parent (pi / oh-my-pi `omp`), context-mode servers never self-
terminate and accumulate for the parent's whole lifetime. Reproduced on macOS:
one `omp` session delegating to subagents accumulated 13 `server.bundle.mjs`
children (all `CONTEXT_MODE_BRIDGE_DEPTH=1`), none reaped; the reporter saw 188
(~8.8 GB) over a multi-day session.

Root cause: the pi extension spawns a bridge child (`server.bundle.mjs`,
depth=1) from `before_agent_start` — which fires per sub-context — but tears it
down only at `session_shutdown`, which never fires for sub-contexts while the
parent lives. The lifecycle guard only reaps on parent-death (ppid poll +
signals + a stdin-EOF assist itself gated on parent-death), so with the parent
alive nothing fires. Same class as #565, whose generic idle-shutdown was
reverted in #602 ("restore tools on 12 hosts").

Fix — a request-idle self-shutdown scoped strictly to MCP bridge children
(`CONTEXT_MODE_BRIDGE_DEPTH>0`):
- src/lifecycle.ts: a depth-gated idle timer shuts the child down after no MCP
  activity for `bridgeChildIdleTimeoutMs()` (default 3 min; override
  `CONTEXT_MODE_BRIDGE_IDLE_MS`, non-positive disables). It NEVER fires while a
  tool call is in flight (`noteRequestStart`/`noteRequestEnd`), so a long single
  ctx_execute/ctx_batch_execute that emits no further inbound frames (#643
  unbounded calls) is not reaped mid-execution. Logs one stderr line when it
  reaps. New testable export `attachMcpActivityTap()`.
- src/server.ts: wrap the SDK transport's `onmessage` (via attachMcpActivityTap)
  and mark tool calls in-flight in `wrapToolHandler` (try/finally, success+error);
  responses also refresh the idle clock via `trackResponse`. No stdin touch.

Safety / invariants preserved:
- depth-0 / absent -> timeout 0 -> reaper never installed, so the long-lived
  keep-alive servers #602 restored are never reaped on idle.
- Trigger is idle TIME via the MCP message layer, never stdin EOF — the #236
  contract and lifecycle's hands-off-stdin invariant are intact.
- #311/#388 orphan fast-detect and #534 1s bridge poll unchanged.

Hardened against an in-flight false-reap regression surfaced by an adversarial
review. Verified on macOS (omp 16.1.11 + Claude); lifecycle (24) + pi suites
pass, `npm run build` + typecheck pass. Bundles regenerate via CI.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 04:49:15 +03:00
Ken JoandMert Köseoğlu ff5b0cff21 fix(codex): MCP sentinel namespace + PreToolUse rewrites + tool annotations (#844 #845 #846) (#851)
* fix(hooks): keep MCP sentinel alive across isolated PID namespaces (#844)

isMCPReady() unlinked any sentinel whose PID failed process.kill(pid, 0).
In a sandbox that shares /tmp but runs in a separate PID namespace, a live
host MCP PID is invisible, so the probe threw ESRCH and the readiness
sentinel was deleted — flipping readiness to false and making hooks fail
open while the MCP server was still running.

The reader now treats EPERM as alive, and on ESRCH trusts a recently
refreshed sentinel (90s window) instead of deleting it; only sentinels
older than the window are cleaned up. The server refreshes its sentinel
mtime every 30s (3x margin) while alive and clears the timer on shutdown.

Tests live in tests/hooks/core-routing.test.ts, which already owns the
mcp-ready directory-scan contract.

Fixes #844.

* fix(codex): emit allow+updatedInput / additionalContext, fail closed on older builds (#845)

The Codex PreToolUse formatter returned null for modify and context, so
curl/wget/HTTP/build redirects (routing `modify`) and guidance nudges
(routing `context`) were silently dropped — the original command ran and
its output flooded the model context. The drop dated to #225 when Codex
rejected updatedInput; current Codex (codex-cli >= 0.141.0) honors
permissionDecision:"allow" + updatedInput and additionalContext.

Capability is detected at runtime (codex-caps.mjs parses `codex --version`,
caches with a TTL, fails closed on any error) — no opt-in env flag, which
would rot into dead code. When supported, modify emits allow+updatedInput
(command rewrite) and context emits additionalContext. When not supported,
a command redirect FAILS CLOSED as a deny carrying the same guidance
(mirrors the claude-code / antigravity-cli echo extraction); non-command
rewrites and advisory context nudges are dropped rather than blocking the
tool. `ask` stays dropped (Codex still rejects permissionDecision:"ask").

The TS adapter (src/adapters/codex/index.ts) is a separate in-process layer
not on the external hook path; its conservative behavior is unchanged.

Tests (formatter + capability detection) live in tests/hooks/formatters.test.ts,
which owns Hook formatting. codex-caps.mjs is a new source module because no
existing module owns Codex runtime capability detection.

Fixes #845.

* fix(mcp): add accurate tool annotations to ctx_* descriptors (#846)

context-mode registered its ctx_* MCP tools without annotations. Codex
cancels unannotated tool calls before execution ("user cancelled MCP tool
call"), so read-only tools like ctx_stats and ctx_doctor never ran even
though the server was reachable.

Each tool now carries explicit annotations classified by real behavior
(no blanket readOnlyHint): read-only query/diagnostic tools (ctx_search,
ctx_stats, ctx_doctor) are readOnlyHint:true; executing/mutating/destructive
tools (ctx_execute, ctx_execute_file, ctx_batch_execute, ctx_index,
ctx_fetch_and_index, ctx_purge, ctx_upgrade, ctx_insight) are not, with
destructiveHint / openWorldHint set per behavior.

Tests live in tests/core/server.test.ts (Server & tools) and inspect the
actual registered descriptors via REGISTERED_CTX_TOOLS, not descriptions.

Fixes #846.

* refactor(codex): replace regex with algorithmic equivalents per no-regex rule (#844 #845 #846)

---------

Co-authored-by: Mert Köseoğlu <bm.ksglu@gmail.com>
2026-06-21 19:19:59 +03:00
9f34c6f11b Add GitHub Copilot CLI + Antigravity CLI (agy) support (#787)
* feat(adapters): add Antigravity CLI (agy) + GitHub Copilot CLI support

Add two agentic CLI adapters onto next's existing adapter registration —
without the abandoned PR's setup subcommand / consolidated registry.

Antigravity CLI (agy):
- MCP + capture-only PostToolUse hook adapter (agy honors no stdout veto in
  auto-run mode; verified against agy 1.0.5). The agy hook payload
  {conversationId, toolCall, workspacePaths} is mapped onto the shared
  capture pipeline.
- Ships a Claude-layout plugin bundle (configs/antigravity-cli/) installed via
  `npm run install:agy` (mirrors install:openclaw), with a version-skew
  capture-hook probe in the installer.

GitHub Copilot CLI (1.0.59):
- json-stdio hook adapter with six events: PreToolUse, PostToolUse, PreCompact,
  SessionStart, UserPromptSubmit, Stop. Overrides CopilotBaseAdapter to emit the
  FLAT {type,command} + top-level "version": 1 hook config Copilot CLI requires.
- MCP install via `copilot mcp add context-mode -- context-mode`.
- Fix a latent Stop-hook bug: a session_end event with no `data` threw inside
  insertEvent (createHash(undefined)) and was silently dropped.

Cross-cutting:
- #774: probe agy/copilot config markers before the generic ~/.claude check.
  The copilot marker is narrowed to context-mode-written files
  (~/.copilot/mcp-config.json | hooks/context-mode.json), not a bare ~/.copilot/
  dir, so a co-installed-but-unconfigured Copilot CLI cannot steal detection
  from a Claude Code user.
- Dispatcher fails OPEN (exit 0) on a missing hook script: GitHub Copilot CLI
  treats an exit-1 PreToolUse hook as DENY, so a version skew (a newer adapter's
  hook command on an older global) would otherwise brick the agent.

Fixes #774. Fixes #775.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci: regenerate bundles for antigravity-cli + copilot-cli support

Picks up the new HOOK_MAP entries, client-map keys, validPlatforms,
getSessionDirSegments cases, and the fail-open dispatcher into the
esbuild-generated runtime bundles.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(platform-support): sync support docs to 18 platforms + fix stale Kiro classification

Make README.md and docs/platform-support.md internally consistent and aligned
with the adapter source of truth.

Header sync (18 platforms everywhere):
- The Main Comparison Table (was 11 cols), the Capability Matrix (was 11), and
  the README Platform Compatibility table (was 17, missing Kimi Code) now list
  the SAME 18 platforms in one shared order. Adds the two branch-new platforms
  (GitHub Copilot CLI, Antigravity CLI `agy`) plus previously-omitted Qwen Code,
  KiloCode, OpenClaw, Zed, Pi as columns. Each cell sourced from the per-platform
  detail sections / adapter source and independently verified.
- Fix five ragged rows in the Main Comparison Table (a dropped trailing OMP cell)
  and add CLI Hook Dispatcher rows for qwen-code + copilot-cli.
- GitHub Copilot CLI section: normalize the `**Hook Names:**` label and add the
  missing `**Output Modification:**` field for json-stdio-family parity.

Fix stale Kiro classification (code is the source of truth):
- The kiro adapter is json-stdio with working preToolUse/postToolUse hooks
  (hooks/kiro/{pretooluse,posttooluse}.mjs + a kiro HOOK_MAP entry), yet the docs
  called it "MCP-only (Phase 2 — not implemented)" and the README contradicted
  itself ("no hook support" in one place, "native preToolUse/postToolUse" in two
  others).
- Reclassify Kiro as json-stdio with PreToolUse + PostToolUse + exit-code-2
  blocking across the Overview paradigm table, both wide tables, the dispatcher
  table, and the Kiro detail section; document that agentSpawn (SessionStart) and
  stop are not yet wired, so session restore after compaction is unavailable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(antigravity-cli): drop vestigial .mcp.json dependency that broke fresh clones

The agy plugin-bundle test asserted configs/antigravity-cli/.mcp.json, but
.mcp.json is gitignored repo-wide and was never committed — so the test passed
on the dev machine (file present locally) yet failed on a fresh clone with
ENOENT. Committing the file is the wrong fix: the .gitignore comment documents
that shipping .mcp.json has silently broken fresh installs before (#253/#531).

- The bundle declares MCP the Claude way via .claude-plugin/plugin.json
  mcpServers (committed — the mechanism agy reads on `agy plugin install`),
  mirrored by the agy-native mcp_config.json (committed). Remove the vestigial
  bundle .mcp.json and stop the test + docs from requiring it. Every file the
  plugin test reads is now git-tracked, so a fresh clone passes.
- README: Kiro was still grouped under "Non-hook platforms" in the routing-
  enforcement note. Kiro has native preToolUse/postToolUse hooks; it needs the
  manual KIRO.md copy only because agentSpawn/SessionStart is not yet wired.
  Reword to say so.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(adapters): cross-platform agy installer + copilot-cli COPILOT_HOME parity

Windows fix (real): replace the bash-only agy plugin installer with a
cross-platform Node script so `npm run install:agy` runs natively on Windows
(PowerShell/cmd), not just Git Bash/WSL. agy runs on Windows, so its installer
must too — the old `node -e` wrapper hard-exited 1 on win32. openclaw stays
bash-only (it is genuinely POSIX-only). Removes
scripts/install-antigravity-cli-plugin.sh in favor of
scripts/install-antigravity-cli-plugin.mjs (same preflight + version-skew probe).

copilot-cli hardening (COPILOT_HOME edge case only — the default ~/.copilot
install was and remains correct):
- CopilotCliAdapter.getSessionDir() now roots at getConfigDir() (COPILOT_HOME-
  aware), mirroring codex/kimi, so the TS server reads sessions from the same
  place the hook runtime (COPILOT_OPTS configDirEnv: COPILOT_HOME) writes them.
  Previously a relocated COPILOT_HOME split hook writes ($COPILOT_HOME/...) from
  server reads (~/.copilot/...), making sessions appear empty.
- detect.ts copilot-cli marker honors COPILOT_HOME, not just ~/.copilot.

No change to the default (COPILOT_HOME-unset) behavior; a regression test pins
both the ~/.copilot default and the COPILOT_HOME-rooted path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci: regenerate bundles for copilot-cli COPILOT_HOME parity

Picks up CopilotCliAdapter.getSessionDir() and the COPILOT_HOME-aware detect.ts
marker into the esbuild runtime bundles.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(antigravity-cli): installer registers the MCP server (agy plugin install skips it)

`npm run install:agy` ran only `agy plugin install`, which — verified against
agy 1.0.5 — processes a bundle's skills + hooks but logs "mcpServers : skipped
(not found)" and registers NO MCP server. agy reads a plugin's MCP only from a
bundle `.mcp.json` (intentionally not shipped — gitignored repo-wide after
#253/#531) and has no `agy mcp add` command, so context-mode's MCP server was
never registered: users had to add it to ~/.gemini/config/mcp_config.json by hand
(reported on Windows; reproduced on Linux: `mcpServers : skipped (not found)`).

The installer now also writes context-mode into agy's GLOBAL MCP profile
~/.gemini/config/mcp_config.json (idempotent JSON merge, preserves other servers,
tolerates a malformed file) — the file agy actually loads and `context-mode
doctor` checks. Verified end-to-end on agy 1.0.5: `npm run install:agy` →
mcp_config.json gains context-mode → `agy -p "... ctx_execute ... 7 + 5"` → 12.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(server): emit Gemini-safe tool schemas so agy/Gemini CLI expose ctx_* tools

Antigravity CLI (agy) and Gemini CLI use Gemini's function-calling API, which
rejects JSON Schema `const` and `additionalProperties`. When a tool's parameter
schema contains either, the host SILENTLY DROPS that tool from the model's
function list — so agy never sees the ctx_* tools and works around them by
hand-rolling the MCP protocol through its Bash tool (verified on Windows: agy
wrote scratch/call_ctx_stats.js + list_mcp_tools.js MCP clients instead of
calling the tools natively). That defeats the point of context-mode — bash
output floods the context window instead of staying in the sandbox.

context-mode builds schemas with Zod, which emits `const` (from coerce/preprocess
constructs) and `additionalProperties`, with no Gemini sanitization. Wrap the
SDK's tools/list handler to rewrite the EMITTED schema:
  - `const: X` -> `enum: [X]`   (an identical single-value constraint)
  - drop `additionalProperties` (advisory-only; every ctx_* handler parses args
    with Zod, which strips unknown keys server-side regardless)

Both transforms are behavior-preserving for every other client (Claude Code,
Copilot, Cursor): const and a one-value enum are equivalent, and no model sends
undeclared properties — only the wire schema changes, never validation or how a
tool is called. Best-effort: if the MCP SDK internals shift, the original handler
is left untouched (no regression). Verified on the real tools/list: all 11 ctx_*
tools now emit 0 `const` / 0 `additionalProperties`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci: regenerate bundles for Gemini-safe tool schema sanitizer

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(antigravity-cli): clear agy's stale MCP tool-schema cache on install

agy caches each MCP server's tool schemas under
~/.gemini/antigravity-cli/mcp/<server>/ and does NOT refresh them on reconnect
(verified on agy 1.0.6 against a live Windows install). A cache captured by a
context-mode older than the Gemini-safe-schema fix (ae6e7d3) keeps the
`const` / `additionalProperties` schemas that make Antigravity CLI silently drop
the ctx_* tools from the model's function list — so the schema fix never reaches
the model and the agent keeps working around the tools via shell scripts.

The installer now clears that cache after registering the MCP server, so agy
re-fetches the current Gemini-safe tools/list on its next launch. Verified on
Windows: clearing the cache + reconnecting makes agy re-store ctx_execute.json
with 0 `const` / 0 `additionalProperties`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: document agy Gemini-safe schemas + installer cache-clear + copilot COPILOT_HOME

Reflect this branch's recent behavior changes in the support docs:
- agy: context-mode emits Gemini-safe tool schemas (const->enum, additionalProperties
  stripped) so Antigravity CLI exposes the ctx_* tools instead of silently dropping
  them; agy caches tool schemas and never refreshes them, so `npm run install:agy`
  clears that cache. Added to the agy Known Issues + install steps (platform-support.md
  + README).
- copilot-cli: COPILOT_HOME now relocates the session-DB root too (getSessionDir honors
  it), and the detection marker honors COPILOT_HOME.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: correct GitHub Copilot CLI plugin capability (plugins DO support MCP + hooks)

The README + platform-support docs claimed Copilot CLI plugins register only
skills/agents — not MCP servers or hooks. That's wrong: `copilot plugin --help`
and `copilot mcp --help` (Copilot CLI 1.x) confirm a plugin can register MCP
servers (a `.mcp.json` in the plugin root or `.github/mcp.json`) and hooks
(`hooks.json`), installed in one command via `copilot plugin install owner/repo:path`
(from a GitHub repo subdirectory, no clone). The "direct installs deprecated for
plugin@marketplace" note was also inaccurate (all source forms are current).

Corrected both docs. context-mode still registers via `copilot mcp add` +
`context-mode upgrade` today; a shippable Copilot plugin bundle
(configs/copilot-cli/) is noted as a planned follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(copilot-cli): ship a GitHub Copilot CLI plugin bundle (MCP + skill, phase 1)

`copilot plugin install mksglu/context-mode:configs/copilot-cli` registers the
context-mode MCP server + routing skill in one command — no `context-mode
upgrade` / agent call.

The bundle's .mcp.json pins CONTEXT_MODE_PLATFORM=copilot-cli so the server
self-identifies as Copilot. This fixes the detection trap where a co-installed
Claude Code (~/.claude/plugins/installed_plugins.json) makes standalone
`context-mode upgrade` — and even ctx_upgrade — resolve claude-code and write
Claude's config instead of Copilot's.

Real Copilot plugins discover MCP from a root `.mcp.json`, so this is the one
bundle whose .mcp.json is committed: .gitignore un-ignores exactly this path
(the repo-wide ignore from #253/#531 guards the repo-ROOT dev file, not a
plugin's own config).

Phase 2 (capture hooks via the plugin's hooks.json) follows once its format is
verified on Windows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(copilot-cli): add capture hooks to the Copilot CLI plugin bundle (phase 2)

configs/copilot-cli/hooks.json registers all six Copilot hook events
(PreToolUse, PostToolUse, SessionStart, UserPromptSubmit, Stop, PreCompact),
each dispatching `context-mode hook copilot-cli <event>` against the global
binary. It is byte-equivalent to what `context-mode upgrade` writes to
~/.copilot/hooks/context-mode.json (the format verified against the
@github/copilot binary), so `copilot plugin install …:configs/copilot-cli` now
registers MCP + skill + capture hooks in one command — no `upgrade` / agent call.

Verified on Windows: with the plugin's env-pinned MCP config + a current global
context-mode, Copilot calls ctx_execute (→ 12) and ctx_upgrade resolves
copilot-cli (writes the Copilot hook, leaves Claude Code's config untouched).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(copilot-cli): document the plugin bundle as the recommended install

README + platform-support now lead with `copilot plugin install
mksglu/context-mode:configs/copilot-cli` (one command: MCP + hooks + skill, no
upgrade/agent call), keeping `copilot mcp add` + `context-mode upgrade` as the
manual no-plugin path. Notes the .mcp.json env pin (CONTEXT_MODE_PLATFORM=
copilot-cli) that fixes detection under a co-installed Claude Code, the
.gitignore un-ignore for the bundle's .mcp.json, and the `copilot --plugin-dir`
local-test path. Drops the earlier "planned follow-up" wording.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(antigravity-cli): ship .mcp.json so `agy plugin install` registers MCP directly

The agy bundle declared MCP in two places that nothing consumed — a `mcpServers`
block in .claude-plugin/plugin.json (which `agy plugin install` SKIPS) and a dead
mcp_config.json (read by no code) — and relied on the installer writing agy's
GLOBAL ~/.gemini/config/mcp_config.json as a workaround for not shipping .mcp.json.

agy's plugin system is Claude-compatible and reads MCP from a bundle `.mcp.json`,
exactly like the Copilot bundle. Verified on agy 1.0.6: `agy plugin install` with
a bundle .mcp.json logs "mcpServers : 1 processed" and registers the server (env
preserved) into ~/.gemini/config/plugins/<name>/mcp_config.json. So:

- ship configs/antigravity-cli/.mcp.json (un-ignored via a .gitignore negation),
  pinning CONTEXT_MODE_PLATFORM=antigravity-cli so the server self-identifies as
  agy — fixing the #774 mis-detection at the MCP level, not only via dir markers;
- drop the dead mcp_config.json and the manifest's redundant mcpServers;
- simplify the installer: `agy plugin install` now registers MCP + skill + hook;
  it keeps the stale tool-schema cache-clear + version-skew probe, and now
  self-verifies the plugin-scoped MCP registration (one-line manual fallback if a
  future agy skips it) instead of blindly writing the global profile.

Both CLI plugin bundles (copilot-cli, antigravity-cli) are now consistent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(antigravity-cli): doctor recognizes the plugin-scoped MCP + hook registration

After the bundle moved to `.mcp.json` (so `agy plugin install` registers MCP +
the capture hook into agy's plugin profile ~/.gemini/config/plugins/context-mode/),
doctor still only checked the global ~/.gemini/config/{mcp_config,hooks}.json and
warned "context-mode not found" / "capture hook not configured" on a working install.

- checkPluginRegistration + validateHooks now accept the plugin profile (the
  canonical `agy plugin install` location) OR the global path (manual fallback).
- getInstalledVersion reads the installed plugin.json version so the version line
  shows a real semver (PASS when current) instead of the bogus "vconfigured".
- fix hints point to `npm run install:agy`.

Unit-tested (plugin-scoped PASS for both MCP + hook). Runtime already confirmed on
agy 1.0.6: `npm run install:agy` + `agy -p "...ctx_execute...7+5..."` → 12.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: clarify supported client count

* fix(copilot-cli): fail-open PreToolUse hook + gate debug logs (#787 review)

A thrown PreToolUse hook exited non-zero with empty stdout, which GitHub
Copilot CLI 1.0.59 treats as "Denied by preToolUse hook (hook errored)" and
uses to block EVERY tool — bricking the agent. parseStdin runs JSON.parse, so
a malformed payload alone triggers it. Wrap the hook body in a fail-open
try/catch: a legitimate veto is a normal stdout write + return (never a
throw), so only real errors are swallowed (empty stdout + exit 0 => ALLOW).
Adds a regression test that spawns the hook with a throwing payload.

Also gate the per-invocation debug logs (posttooluse/precompact/sessionstart)
behind CONTEXT_MODE_DEBUG, matching the kimi hooks — the PostToolUse log grew
on every tool call under the user's config dir.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(util/jsonc): string-aware trailing-comma strip (#787 review)

stripJsonComments stripped trailing commas with a regex over the whole string,
silently eating commas INSIDE string values (e.g. "[1, ]" -> "[1 ]") on the
comment-strip path (reached whenever strict JSON.parse fails). Move the
trailing-comma removal into a second string-aware pass over the comment-free
output: in-string commas are preserved while real trailing commas — including
those separated from } or ] by a comment — are still stripped. Regenerated
bundles (jsonc is bundled into cli/server.bundle.mjs).

The identical duplicates in src/server.ts and src/adapters/opencode/index.ts
are left for a follow-up consolidation PR (they parse third-party configs;
wider blast radius).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test: consolidate per-adapter test files per CONTRIBUTING (#787 review)

CONTRIBUTING.md ("Test file organization") keeps one test file per adapter /
core module. Merge the standalone bundle-guard + schema files into their
canonical homes and delete the standalones — zero net-new test files:
  - copilot-cli-plugin.test.ts    -> adapters/copilot-cli.test.ts
  - antigravity-cli-plugin.test.ts -> adapters/antigravity.test.ts
  - strict-client-schema.test.ts  -> core/server.test.ts (sanitizeSchemaForStrictClients)

Also add the jsonc string-aware regression test to core/server.test.ts (its
home per the domain table; jsonc.ts has no test file of its own).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test: rename copilot capture hooks file to the <platform>-hooks convention (#787 review)

The repo's per-platform hook test files are named tests/hooks/<platform>-hooks.test.ts
(cursor-hooks, gemini-hooks, vscode-hooks, jetbrains-hooks, kiro-hooks, kimi-hooks).
copilot-cli's was the lone deviation (copilot-cli-capture.test.ts). Rename it to
copilot-cli-hooks.test.ts and add the matching row to the CONTRIBUTING.md test-file
table. (antigravity-cli stays folded into antigravity.test.ts — capture-only single
hook, mirroring the GUI variant in the same family file, per the repo's precedent.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(version-sync): register the Copilot CLI bundle manifest (#787 review)

configs/copilot-cli/.github/plugin/plugin.json carries a pinned "version" but,
unlike the antigravity-cli bundle, was missing from version-sync — so it would
freeze on the next `npm version` bump (the .cursor-plugin v1.0.111 drift class
the version-sync test guards against). Add it to scripts/version-sync.mjs targets,
the package.json `version` git-add list, and the version-sync test (targets + pkg
list + SHIPPED lockstep + end-to-end), mirroring the agy bundle.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(antigravity-cli): bounded PreToolUse enforcement via agy's native decision contract

agy honors a top-level PreToolUse decision `{"decision":"deny"|"ask",reason}`
(verified on agy 1.0.6) — not Claude's permissionDecision/additionalContext — so
context-mode can ENFORCE routing on agy, not just capture.

- PreToolUse routing hook (hooks/antigravity-cli/pretooluse.mjs) emits agy's
  native decision; deny/ask enforce, context/modify collapse to an enforceable
  deny (agy ignores additionalContext). Fail-open.
- Shared agy payload mapper (hooks/antigravity-cli/payload.mjs) used by
  pre/post/stop; posttooluse refactored onto it. New capture-only Stop hook
  (best-effort — agy Stop firing unconfirmed, so it's excluded from doctor health).
- Native root bundle: ships plugin.json + mcp_config.json + hooks.json +
  rules/context-mode.md (agy reads bundle-ROOT files); .mcp.json and
  .claude-plugin/plugin.json removed. hooks/hooks.json kept as the validate/install
  mirror — agy runtime fires from root hooks.json, but `agy plugin validate/install`
  only REPORTS hooks when the subdir hooks/hooks.json also exists.
- routing.mjs agy aliases (run_command->Bash, view_file->Read, ...) + CommandLine/
  AbsolutePath/URL extractors; tool-naming.mjs maps agy to context-mode/<tool>.
- adapter: capabilities preToolUse/postToolUse true, paradigm json-stdio, native
  decision formatter, doctor; cli.ts HOOK_MAP pretooluse/posttooluse/stop;
  version-sync tracks the bundle plugin.json.

Fixes a marker-handoff bug: pretooluse keyed rejected/redirect markers on
conversationId while posttooluse reads via getSessionId (which prefers the
transcript UUID) — both now use getSessionId, with a <uuid>.jsonl round-trip
regression test. Also corrects a stale core-routing assertion to agy's
context-mode/<tool> surface, adds the CONTRIBUTING test-file row, and includes
incidental CODEX_* test-env isolation hardening.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor(antigravity-cli): review polish — modify guidance, ask fallback, sync comments, test placement

- formatters: agy `modify` now surfaces routing's per-tool redirect guidance
  (curl/build-tool/inline-HTTP) extracted from the echo payload instead of one
  generic line; `ask` carries a fallback reason so a security-policy confirmation
  prompt is never bare. Adapter formatPreToolUseResponse ask branch mirrored.
- comments: cross-reference the three agy tool-name maps (payload.mjs /
  routing.mjs / extract.ts) and the two agyContextReason copies (formatters.mjs /
  adapter) so they don't silently drift (single shared table = follow-up).
- tests: move the agy formatter tests to the canonical tests/hooks/formatters.test.ts
  (formatDecision wrapper style, beside the other per-platform blocks); assert the
  surfaced modify guidance + the ask fallback. Update the run_command deny test to
  the specific (non-generic) guidance.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(antigravity-cli): default exec timeout under agy + anti-dump rules

Two agy-specific hardening fixes surfaced by interactive testing:

- ctx_execute / ctx_execute_file / ctx_batch_execute apply a default execution
  timeout (120s, tunable via CONTEXT_MODE_AGY_EXEC_TIMEOUT_MS) ONLY under agy.
  agy does not enforce an MCP RPC timeout, so a runaway/blocking script hung
  forever and had to be interrupted; every other host keeps the unbounded
  behavior (Issue #406). resolveExecTimeout() centralizes this; timed-out
  messages now report the effective timeout (was "undefinedms"). Unit-tested +
  e2e-verified (runaway ctx_execute killed at the bound instead of hanging).
- rules/context-mode.md: add a prominent "Do not dump — derive" section. agy
  artifacts each MCP tool's stdout to a step file the model then reads back, so
  a whole-file dump costs the context window twice; steer the model to
  value/match/known-slice extraction instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(copilot-cli): use camelCase hook event names so hooks actually fire

GitHub Copilot CLI (verified against the @github/copilot 1.0.60 binary)
dispatches hooks by camelCase event names ONLY — preToolUse / postToolUse /
sessionStart / userPromptSubmitted / agentStop / preCompact. The adapter
shipped PascalCase keys (PreToolUse / ...), which the binary silently ignores,
so context-mode's PreToolUse routing enforcement and PostToolUse capture never
fired on Copilot CLI. MCP tool exposure (.mcp.json auto-discovery) was
unaffected, which masked the regression.

- HOOK_TYPES values -> Copilot's camelCase. UserPromptSubmit->userPromptSubmitted
  and Stop->agentStop are NAME changes, not just casing.
- Decouple the CLI dispatch token from the event name: buildHookCommand now
  derives the token from the .mjs script base (pretooluse, ...), so the event
  KEY can be camelCase while the dispatcher and cli.ts hook handler stay stable.
- Update configs/copilot-cli/hooks.json keys, README, index.ts comments, tests.

Verified e2e on real Copilot CLI 1.0.60 via the documented plugin install:
PreToolUse denied a raw `curl` and redirected to ctx_fetch_and_index (the model
obeyed); PostToolUse fired (posttooluse-debug.log advanced under
CONTEXT_MODE_DEBUG). The internal DB event-type labels in hooks/copilot-cli/*.mjs
are context-mode's cross-adapter taxonomy and are intentionally unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Keep Copilot CLI plugin MCP config loadable on older CLI

Mac smoke testing found that Copilot CLI 1.0.44 rejects the plugin MCP entry before startup unless the no-argument server still declares an explicit empty args array.

Constraint: Copilot CLI 1.0.44 requires an explicit args array for plugin stdio MCP entries

Rejected: Omit args because context-mode takes no arguments | older Copilot CLI rejects the plugin config before MCP startup

Confidence: high

Scope-risk: narrow

Directive: Keep args: [] in the Copilot plugin .mcp.json unless Copilot documents it as optional across supported versions

Tested: vitest copilot-cli adapter and hook suites; real Copilot CLI 1.0.44 loaded context-mode MCP after patch; real agy prompt returned 12

Not-tested: Copilot prompt completion, because local Copilot CLI fails to list models even without this plugin

Co-authored-by: OmX <omx@oh-my-codex.dev>

* Ship the agy installer in the npm package

Clean-install testing exposed that the package declared npm run install:agy but omitted the installer file from package.json files, so the installed tarball failed before agy plugin install could run.

Constraint: npm tarball contents are limited by package.json files

Rejected: Rely on repository-local installer presence | npm install -g ships only allowlisted files

Confidence: high

Scope-risk: narrow

Directive: Keep package scripts and package.json files in lockstep for shipped install commands

Tested: vitest antigravity and copilot adapter hook suites; npm pack includes scripts/install-antigravity-cli-plugin.mjs; npm uninstall -g context-mode then npm install -g tarball; npm --prefix installed package run install:agy; real agy prompt returned 12; Copilot loaded installed plugin MCP

Not-tested: Copilot prompt completion, because local Copilot CLI fails to list models after MCP startup

Co-authored-by: OmX <omx@oh-my-codex.dev>

* test(server): use valid tsc option for on-demand build

* fix(copilot-cli): validate plugin runtime hooks

* docs(copilot-cli,antigravity-cli): correct hook comments + fields to match upstream refs

Ground the new Copilot CLI / Antigravity CLI adapters against the real
upstream sources (refs/platforms) and fix misleading comments + one
contradicted field. No runtime behavior change to working paths.

Copilot CLI:
- version:1 is OPTIONAL, not mandatory — the CLI accepts hook configs
  that omit the version field (copilot-cli changelog.md:1109). Keep
  emitting version:1 (harmless, self-documenting); fix the comments,
  README, and docs that claimed hooks never fire without it.
- PascalCase event names are ACCEPTED and fire — the CLI loads configs
  across VS Code / Claude Code / CLI by accepting PascalCase alongside
  camelCase (changelog.md:1065, :811, :1081). Drop the 'silently
  ignored / never fires' claim; we use camelCase as the native naming.
- session_id (snake_case) is the documented payload field
  (changelog.md:811). Read it first; keep sessionId (camelCase) as a
  defensive, undocumented fallback.

Antigravity CLI:
- The only refs-backed payload field is workspace.current_dir, an object
  field (examples/title/title.sh:10, examples/title/README.md:11). Read
  workspace.current_dir FIRST for the project dir, falling back to the
  empirically-derived workspacePaths[0]. Annotate conversationId /
  workspacePaths as unverified. Stop stays best-effort/unverified on
  agy 1.0.6.

Docs: platform-support table + README continuity matrix now show
Antigravity CLI Stop as best-effort/unverified and the corrected
session-id / project-dir fields; 17-platform count unchanged (correct).

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Mert Köseoğlu <bm.ksglu@gmail.com>
Co-authored-by: OmX <omx@oh-my-codex.dev>
2026-06-21 16:06:25 +03:00
Ken JoandMert Köseoğlu 85f64d2cc2 fix(hooks): respect subagent ctx tool availability (#834)
* fix(hooks): respect subagent ctx tool availability

* test(hooks): exercise subagent pretooluse routing

* test(hooks): strengthen subagent routing coverage

* refactor(hooks): drop redundant CONTEXT_MODE_DISABLE_AGENT_INJECTION env var

The #832 foreground-subagent hang root cause is already fixed by the #794
mcpToolsAvailable path: subagent-originated WebFetch/curl/inline-HTTP/build
redirects now pass through instead of forcing blocking ctx_* calls. The
Agent prompt-injection block is advisory text and cannot itself hang, so
the dedicated opt-out env var gates no remaining behavior.

Removes the env-var read in routing.mjs, the orphaned isTruthyEnv helper,
the README entry, and the dead #832 unit test. The #794 fix and its
coverage are left intact.

---------

Co-authored-by: Mert Köseoğlu <bm.ksglu@gmail.com>
2026-06-21 14:55:03 +03:00
Ken Jo 6c513fbf60 test: isolate next full-suite env assumptions (#837) 2026-06-16 17:07:12 +03:00
Ken Jo c5e0fbc066 fix(server): exit on fatal uncaught exception storm (#833) 2026-06-15 19:27:33 +03:00
Ken Jo 98d239b7f5 fix(codex): resolve plugin-manager root for doctor and upgrade (#771)
* Fix Codex plugin-root drift diagnostics

Codex marketplace installs can run doctor or MCP from a cache/global root that differs from the runtime root reported by the Codex plugin manager. That made doctor check a stale hooks manifest and made upgrade remove native fallback hooks before plugin ownership was proven.

Constraint: Codex plugin-manager roots can differ across marketplace tmp roots, plugin cache roots, global npm roots, and already-running MCP sessions.

Rejected: Always deleting native fallback hooks in plugin mode | unsafe when the current package root and plugin-manager runtime root diverge.

Confidence: high

Scope-risk: moderate

Directive: Only let Codex substitute the plugin-manager runtime root; other adapters can coexist with Codex on the same machine.

Tested: npm test; npm run build; npm run assert-bundle; git diff --check; runtime doctor; Codex hook smoke; direct MCP ctx_doctor smoke.

Not-tested: CI matrix before PR creation.

* test(server): use valid tsc option for on-demand build
2026-06-14 11:51:19 +03:00
Ken Jo e2bce0b518 fix(windows): preserve Git Bash path conversion for native git (#826)
* fix(windows): preserve Git Bash path conversion for native git

* test(windows): cover shell override edge cases
2026-06-13 17:59:52 +03:00
Ken JoandClaude Fable 5 8ca114c473 fix(opencode): string-aware JSONC stripping via shared util/jsonc (#808)
fix(opencode,server): string-aware JSONC stripping via shared util/jsonc (#806)

opencode.jsonc/kilo.jsonc with URLs in string values (e.g. "$schema":
"https://opencode.ai/config.json") were corrupted by the adapter's naive
stripJsonComments regex (/\/\/.*$/gm eats everything after "https:"),
so readSettings() returned null and ctx doctor emitted the false
"[FAIL] Plugin configuration: Could not read opencode.json or
opencode.jsonc".

- add src/util/jsonc.ts: two-pass string-aware comment + trailing-comma
  stripping and tolerant parseJsonc (byte-identical to the #787-reviewed
  implementation so the branches merge cleanly)
- opencode adapter: drop the naive regex stripper (introduced in 7189ed6
  to avoid a jsonc-parser dependency; the shared util keeps that
  property — zero imports, esbuild inlines it), route through the util
- server.ts: drop its local copy whose trailing-comma regex corrupted
  commas inside string values; import the shared util
- tests: URL-bearing opencode.jsonc/kilo.jsonc through the public
  adapter API, in-string comma regression, escaped quotes, CRLF, block
  comments with URLs, trailing commas, plain-JSON passthrough

Verified end-to-end on real OpenCode 1.17.1: URL-bearing opencode.jsonc
parses, doctor reports PASS, plugin loads and captures session events.

Closes #806

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 17:31:37 +03:00
Ken JoandOmX 1df5d3ca93 Fix mobile overflow on Insights persona cards (#772)
Prevent persona cards from overflowing on mobile

The live Insights landing uses long tool-call and pattern identifiers inside grid cards. Mobile one-column grids still kept min-content widths, so persona content pushed beyond the viewport and duplicated CSS quotes made the first question look broken.

Constraint: / and /insights route to web/insights.html through the Workers router.

Rejected: Copying live HTML into web/index.html | /oss uses index.html, while the broken live route is served from insights.html.

Confidence: high

Scope-risk: narrow

Directive: Keep mobile grid columns as minmax(0, 1fr) when cards contain long code-like tokens.

Tested: Playwright mobile render at 320, 375, 390, and 430 px; document/body width matched viewport and persona cards/tool calls stayed within card bounds. Copilot review follow-up kept persona calls accessible with overflow-x:auto and removed non-standard word-break usage.

Not-tested: Production deploy; this commit only updates the source file.

Co-authored-by: OmX <omx@oh-my-codex.dev>
2026-06-07 17:07:01 +03:00
Ken JoandOmX 62bc5ad654 Fix mobile overflow on Insights persona cards (#772)
Prevent persona cards from overflowing on mobile

The live Insights landing uses long tool-call and pattern identifiers inside grid cards. Mobile one-column grids still kept min-content widths, so persona content pushed beyond the viewport and duplicated CSS quotes made the first question look broken.

Constraint: / and /insights route to web/insights.html through the Workers router.

Rejected: Copying live HTML into web/index.html | /oss uses index.html, while the broken live route is served from insights.html.

Confidence: high

Scope-risk: narrow

Directive: Keep mobile grid columns as minmax(0, 1fr) when cards contain long code-like tokens.

Tested: Playwright mobile render at 320, 375, 390, and 430 px; document/body width matched viewport and persona cards/tool calls stayed within card bounds. Copilot review follow-up kept persona calls accessible with overflow-x:auto and removed non-standard word-break usage.

Not-tested: Production deploy; this commit only updates the source file.

Co-authored-by: OmX <omx@oh-my-codex.dev>
2026-06-03 08:52:24 +03:00
KenJo ec48166e68 fix(codex): dedupe plugin-owned context-mode hooks (#746) 2026-06-01 09:19:52 +03:00
KenJo 9e2e623792 feat(session): persist /goal directives across compaction and resume (#695) 2026-05-31 16:30:46 +03:00
a01c8531e6 fix(doctor): surface missing launch files when integrity helper is absent (#689)
* fix(doctor): surface missing launch files when integrity helper itself is absent

When an interrupted /ctx-upgrade leaves the plugin cache partially populated,
scripts/plugin-cache-integrity.mjs (the integrity helper, shipped in
package.json files[]) can itself be among the missing files.
checkPluginCacheIntegritySync then reported only 'integrity helper unavailable',
masking that the MCP launch entrypoint (start.mjs / server bundle) was also
gone — the actual reason the MCP server failed to start.

Add findMissingLaunchFiles(): a dependency-free (fs-only) check of start.mjs
(the no-fallback command target from .claude-plugin/plugin.json) and the server
bundle (server.bundle.mjs, or its build/server.js fallback). When the integrity
helper cannot load, the doctor now names the missing launch files and points to
the reinstall command instead of stopping at an opaque 'helper unavailable'.

Verified: new tests/util/plugin-cache-launch-files.test.ts (5 cases) + full
suite (3663 pass) + build (tsc + bundle + assert-bundle + assert-asymmetric-drift) green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(cli): fold findMissingLaunchFiles tests into tests/core/cli.test.ts

CONTRIBUTING L282 prohibits new test files — extend the existing file
that covers the same domain. The 5 findMissingLaunchFiles cases from
the original tests/util/plugin-cache-launch-files.test.ts naturally
belong alongside the existing tests/core/cli.test.ts plugin-cache-
integrity coverage (assertPluginCacheIntegrity, derivePluginManifest,
package.json files[] shipping). All in the same describe block now.

No behaviour change. 5/5 GREEN.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Mert Koseoglu <bm.ksglu@gmail.com>
2026-05-24 19:41:40 +03:00
61fd3fb0a0 Fix worktree suffix project root detection (#435)
* ci: update install stats

* ci: update install stats

* fix worktree suffix project root detection

* fix: normalize project paths for session helpers

* fix: align kiro hooks with normalized session paths

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Mert Köseoğlu <bm.ksglu@gmail.com>
Co-authored-by: Ken Jo <ikchan.jo@lge.com>
2026-05-08 18:26:39 +03:00