26 Commits

Author SHA1 Message Date
Maxence Rossignol 926481e8c0 fix(opencode): convert Zod v3 schemas to v4 for in-process plugin host (#734) 2026-05-31 16:30:39 +03:00
Mickey Lazarevic 45100c75be fix(opencode): convert zod v3 schemas to v4 for kilo platform compatibility (#632)
* fix(opencode): convert zod v3 schemas to v4 for kilo platform compatibility

KiloCode's runtime bundles Zod v4 internally, but plugin tool definitions
may contain Zod v3 schemas. This causes crashes with "undefined is not an object"
when accessing ._zod.def.

Added a utility that converts Zod v3 schema shapes to v4 equivalents
specifically when platform === "kilo". OpenCode uses Zod 3 natively
and receives the original shapes unchanged.

The conversion handles all common Zod types including objects, arrays,
unions, optionals, nullables, defaults, and effects. Includes comprehensive
test coverage for all supported types.

* fix(opencode): remove package.json version reading at module load - dead code
2026-05-19 17:17:26 +02:00
Mert Koseoglu 7c82220023 fix(opencode): coerce stringified primitives on native plugin path (#627)
v1.0.139 (#621) added inputSchema.parse(args) on the OpenCode native
plugin bridge so z.preprocess / z.default would fire. That fixed
ctx_batch_execute.commands and ctx_search.queries (both wrapped in
preprocessors) but exposed three primitive-type rejections that the MCP
stdio transport used to absorb:

  • ctx_search.limit was z.number() — rejects "4" with
    "Expected number, received string"
  • ctx_search.queries with a *bare* string (not JSON-stringified array)
    survived coerceJsonArray unchanged and hit z.array(z.string())
  • ctx_execute.background / ctx_purge.confirm were z.boolean() —
    reject "false" with "Expected boolean, received string"

Several LLM providers' tool-call JSON stringifies primitives, and
OpenCode's plugin host passes them through verbatim. With #621's parse
step in place the bridge surfaces these as "Invalid arguments for
<tool>" instead of coercing transparently.

Fix:

  • Widen coerceJsonArray to lift a bare non-empty string into [val]
    (empty strings still bubble to Zod's .min(1) so we don't mask
    real input errors).
  • Add coerceBoolean preprocessor — maps only literal "true"/"false"
    to booleans; passes everything else through. z.coerce.boolean()
    is unsafe here because Boolean("false") is true.
  • Apply coerceBoolean to ctx_execute.background and ctx_purge.confirm.
  • Switch ctx_search.limit to z.coerce.number() — Number coerce is
    safe (Number("4") === 4) and mirrors what ctx_batch_execute's
    timeout/concurrency and ctx_fetch_and_index's concurrency already
    do.

TDD slices in tests/opencode-plugin.test.ts (#621 describe extended,
no new test files per CONTRIBUTING L275):

  • ctx_search accepts stringified limit (exact reporter case)
  • ctx_search lifts bare-string queries into single-element array
  • ctx_execute accepts stringified background boolean
  • ctx_purge accepts stringified confirm boolean

Full suite: 3362 / 3362 pass (145 files, 28 unrelated skips).

refs/platforms/opencode/packages/opencode/src/tool/registry.ts:145
confirms OpenCode does not coerce or decode plugin tool args — the
plugin owns its parsing contract.
2026-05-19 12:09:44 +03:00
Mert Koseoglu a7c74303df fix(opencode): run zod schema parse on native plugin tool args (#621)
OpenCode's plugin tool registry (refs/platforms/opencode/packages/
opencode/src/tool/registry.ts:127) uses the Zod schema only as a
boolean type guard via .safeParse(u).success — it passes RAW args
to def.execute(). Our ctx_batch_execute / ctx_search handlers rely
on z.preprocess(coerceCommandsArray | coerceJsonArray, …) to coerce
JSON-string args back into arrays and to fill defaults.

PR #574 / #597 wired ctx_* tools natively via the plugin tool map and
called registered.handler(args ?? {}) directly, bypassing the MCP
SDK's safeParseAsync wrapper. Result: when the LLM delivered commands
as a JSON-stringified array or omitted them entirely, the handler
crashed with "commands.map is not a function" instead of either
coercing the value or producing an actionable validation error.

Fix: run inputSchema.parse(args) inside buildNativeTools before
invoking the handler — same contract as the MCP framework
(server/mcp.js safeParseAsync line 174). Validation failures now
surface as "Invalid arguments for <tool>: <zod error>" rather than
opaque TypeErrors downstream.

TDD slices (tests/opencode-plugin.test.ts):
  - baseline well-formed args still work
  - JSON-stringified commands array is coerced
  - bare-string commands are lifted to {label,command}
  - missing commands raises a clear "Invalid arguments" error
  - JSON-stringified queries on ctx_search are coerced
2026-05-18 23:29:56 +03:00
Omer Cohen 09efefdc4a feat(opencode): register ctx tools natively via plugin (#574) (#597)
Move OpenCode/Kilo from plugin+MCP dual registration to plugin-native ctx_*
tools. The plugin now imports the shared server tool registry without
starting stdio, exposes all 11 ctx_* tools via the OpenCode/Kilo tool map,
and uses AsyncLocalStorage to pass project/session context into existing
handlers without a process.env race.

Upgrade safety for existing users:
- configureAllHooks removes only legacy mcp.context-mode while preserving
  other MCP servers.
- doctor warns when a legacy mcp.context-mode block remains and points to
  context-mode upgrade.
- stale legacy OpenCode/Kilo MCP children suppress ctx_* registration and
  become no-op rather than exposing duplicate tools.

Safety cleanup:
- Remove CONTEXT_MODE_IDLE_TIMEOUT_MS entirely; plugin-native tools remove
  the need for timer-driven MCP death, and timer shutdown was unsafe for
  hosts that keep registered tool handles.
- Guard process-wide exception handlers so importing server.js for native
  tools does not alter OpenCode/Kilo host crash semantics.
- Scope CONTEXT_MODE_EMBEDDED_PLUGIN_TOOLS to the dynamic import and restore
  it so child commands do not inherit the internal guard.

Tests cover native tool registration, native ctx_stats execution, host
side-effect leakage, native session attribution, legacy MCP config cleanup,
doctor warning, and stale MCP no-op predicate.

Refs: #574, #565, #592

Co-authored-by: Ousama Ben Younes <benyounes.ousama@gmail.com>
2026-05-18 17:10:16 +02:00
Mert Koseoglu d923716d00 refactor(adapter/C2): drop getSessionDBPath/EventsPath from Adapter interface
The two methods were SHALLOW pure derivatives of `getSessionDir() +
projectDir` (interface complexity == implementation complexity). The Codex
override added no behavior — its `normalizeWorktreePath` pre-pass was a
no-op because `resolveSessionDbPath` and `hashProjectDirCanonical` already
normalize internally. The events.md override included a worktree suffix
that nothing in production consumed (server.ts and hooks build their own
events.md path inline).

Apply the deletion test: removing them from BaseAdapter / Adapter / Codex
collapses to ONE site of computation (`resolveSessionDbPath` in
src/session/db.ts). Complexity does NOT reappear in N callers — only one
production caller existed (opencode/plugin.ts), updated to call the helper
directly. Adapter authors can no longer cargo-cult overrides; the methods
do not exist on the interface.

Migration shape (preserved by tests):
  before: new SessionDB({ dbPath: adapter.getSessionDBPath(projectDir) })
  after:  new SessionDB({ dbPath: resolveSessionDbPath({
            projectDir, sessionsDir: adapter.getSessionDir(),
          }) })

Test counts: 2627→2630 passed (+2 new C2 narrowing pins, +1 less flake);
19 baseline failures unchanged (security policy + statusline cross-OS,
unrelated to this change).

Cross-OS validation: `tests/hooks/precompact-snapshot-event.test.ts:81-84`
documents the slash-folding pattern that resolveSessionDbPath performs
internally; refs/platforms/codex/codex-rs/core/src/config/mod.rs:622-627
confirms upstream Codex separates platform dir state (`codex_home`) from
per-project file paths — the separation we are formalizing.

Bundles regenerated via `npm run build`. hooks/*.bundle.mjs unchanged
(they don't import adapter classes).
2026-05-10 16:34:38 +03:00
Mert Koseoglu 5a6fe4bd3b feat(adapters): add PiAdapter to fix getAdapter('pi') fallthrough (#473 follow-up) 2026-05-09 23:48:51 +03:00
Mert Koseoglu dedb433da8 fix(opencode): restore AGENTS.md capture for snapshot/auto-memory parity (#487 follow-up) 2026-05-09 23:44:20 +03:00
Mert Koseoglu 087bfa9c38 fix(opencode): wrap debug logger awaits in safeLog to preserve turn (#448 follow-up) 2026-05-09 23:44:20 +03:00
Mert Koseoglu 9b5c3b9d47 fix(opencode): replace overlapping quorum markers with distinct tokens (#487 follow-up) 2026-05-09 23:44:20 +03:00
Mickey Lazarevic 961a749c25 docs: update grep documentation and remove routing markers (#499)
* docs: update grep documentation and remove routing markers

Update grep guidance across configuration files to warn about context flooding. Remove XML comment markers from OpenCode plugin routing logic to streamline system message injection.

* test: remove context-mode marker comment from OpenCode plugin test

Remove XML comment markers from snapshot assertions in OpenCode plugin test to streamline snapshot validation. Update test description to focus on snapshot emission rather than marker comments.
2026-05-09 22:29:15 +03:00
Mickey Lazarevic 5aade76749 fix(opencode): remove duplicate routing block injection and AGENTS.md capture (#487)
- Remove redundant routing block injection when system prompt already contains context-mode instructions
- Remove AGENTS.md/CLAUDE.md rule capture functionality as it's handled by the host
- Add quorum-based routing detection to prevent duplicate instruction injection
- Update plugin documentation to reflect simplified hook behavior
2026-05-09 11:56:52 +03:00
Ben Younes c805410878 fix(pi): bridge MCP tools into pi.registerTool() so the LLM can call them (#426) (#472)
* fix(pi): bridge MCP tools into pi.registerTool() so the LLM can call them (#426)

Pi 0.73.x has no native MCP support — its README is explicit:

> No MCP. Build CLI tools with READMEs (see Skills), or build an
> extension that adds MCP support.

Without a bridge inside the context-mode Pi extension, the routing
block tells the LLM to call `ctx_execute` / `ctx_search` / etc. but
those tools never enter Pi's tool list and the LLM cannot reach them.
The reporter measured 18 sessions over 2 days: ~2,500 tokens of
system-prompt overhead per window, 0 actual ctx_* calls, 447 events
recorded but never retrieved. Net ROI on Pi was negative.

This adds a stdio JSON-RPC client (`MCPStdioClient`) plus a thin
bootstrap (`bootstrapMCPTools`) that:

  - spawns `server.bundle.mjs` as a long-lived MCP child,
  - performs the standard MCP handshake (initialize →
    notifications/initialized),
  - lists tools once via `tools/list`, and
  - registers each tool through `pi.registerTool({ name, label,
    description, parameters, execute })` so the LLM sees the canonical
    bare names (matching what hooks/core/tool-naming.mjs emits for
    Pi).

Each Pi `execute()` callback forwards into the MCP child via
`tools/call`. Errors are translated to `throw` (Pi's contract for
"tool failed") so the LLM sees the MCP server's diagnostic text.

Lifecycle:

  - Bridge bootstrap is fire-and-forget at extension load — the rest
    of the extension (session capture, hooks, slash commands) is not
    blocked by spawn / handshake latency.
  - `session_shutdown` terminates the child via SIGTERM.
  - A missing `server.bundle.mjs` or any spawn / handshake error is
    surfaced once on stderr, then the extension keeps running with
    only the existing hooks + commands. Defense-in-depth so the bridge
    can never break Pi sessions for users with broken installs.

## Why a JSON Schema parameters object instead of TypeBox

MCP `tools/list` returns JSON Schema. Pi's parameter validator accepts
JSON Schema directly (TypeBox just produces JSON Schema with extra
Symbol metadata for type inference). Passing the schema through
unchanged avoids a runtime translation pass and keeps the bridge a
true thin layer over the MCP protocol — what works in Claude Code,
Gemini CLI, and the other adapters now also works in Pi.

## No new runtime dependencies

Pure `node:child_process` + `node:path`. The `@earendil-works/pi-*`
packages are NOT pulled in as build deps — `pi` is typed structurally
as `any` (matching the existing src/pi-extension.ts style) and the
bridge only touches the documented `pi.registerTool()` shape.

## Tests

Added two new `describe` blocks in `tests/pi-extension.test.ts`:

  1. `MCPStdioClient` (5 tests) — wire-protocol contract pinned with
     fake stdio servers: id-matched responses, concurrent in-flight
     requests with out-of-order delivery, child-exit cancellation,
     timeout, non-JSON noise tolerance.
  2. `bootstrapMCPTools` (2 integration tests) — spawn the real
     `start.mjs` MCP server, assert that the canonical ctx_* set
     (`ctx_execute`, `ctx_execute_file`, `ctx_search`, `ctx_index`,
     `ctx_batch_execute`, `ctx_fetch_and_index`, `ctx_doctor`,
     `ctx_stats`, `ctx_purge`) is registered, and round-trip
     `ctx_index` through `tools/call` to confirm execute() forwards
     args and returns text.

## Test plan

  - [x] `npm run build`
  - [x] `npm run typecheck` clean
  - [x] `npm test` — 73 files, 2406 pass / 25 skipped / 0 fail
  - [x] `npx vitest run tests/pi-extension.test.ts` — 44 pass
        (37 pre-existing + 7 new for the bridge)
  - [x] see-real-bug repro: `pi.registerTool` count = 0 in installed
        binary (`/home/$USER/.nvm/.../context-mode/build/pi-extension.js`)
        on `next` @ 1f70bee, plus Pi README's "No MCP" stance, plus
        the issue reporter's 18-session measurements — all three
        agree.
  - [-] Live LLM tool-call probe in Pi: blocked — free-tier Gemini
        quota was exhausted on every available key during the fix
        session. The integration test exercises the same code path
        (real MCP server + the Pi-facing registerTool surface), so
        the regression contract is enforced from CI.

## Out of scope

  - Removing the MCP server stanza from the Pi install README. Once
    this lands, the `~/.pi/agent/mcp.json` step is still harmless but
    no longer load-bearing. Cleanup left to a docs-only follow-up.
  - In-process refactor of server.ts handlers. The subprocess bridge
    is the same model used by every other adapter; a refactor that
    inlines the handlers is its own scope.

Co-Authored-By: Ora Studio <noreply@oratelecom.net>

* fix(pi): address self-review findings on the MCP bridge (#426)

Three follow-up changes from the empirical self-review on PR #472:

1. **C1 HIGH — wiring not test-covered.**
   Phase A of the empirical review only failed because removing
   `src/pi-mcp-bridge.ts` produced an import error, not because the
   bug reproduced behaviorally. If a future refactor dropped the
   `bootstrapMCPTools(pi, …)` call from `src/pi-extension.ts` while
   keeping the bridge module intact, every existing bridge test
   stayed green and the bug silently re-entered.

   Fix: export `_mcpBridgeReady: Promise<void>` from
   `src/pi-extension.ts`. Bootstrap is still fire-and-forget (so
   spawn / handshake latency does not block session_start), but the
   promise gives tests a deterministic await point. Reset to a fresh
   promise on every `piExtension(pi)` call so multiple registrations
   in one process do not see a stale resolution.

   New test in `tests/pi-extension.test.ts` ("pi-extension.ts wiring
   (#426 regression guard)"): calls `registerPiExtension(api)`,
   awaits `_mcpBridgeReady`, asserts `api.registerTool.mock.calls`
   includes at least the canonical `ctx_execute` / `ctx_search` /
   `ctx_index` / `ctx_batch_execute` / `ctx_fetch_and_index` set.
   Verified red-on-revert: with the bridge module intact but the
   `bootstrapMCPTools(...)` call reverted to next, this test fails
   with `registeredNames: []`. Pre-fix it would have stayed green.

2. **C2 LOW — duplicated path resolution in the integration tests.**
   `tests/pi-extension.test.ts` had `path.dirname(...) +
   path.resolve(here, "..", "start.mjs")` recomputed in each `it()`.
   Lifted to a single `mcpEntry` const at the top of the
   `bootstrapMCPTools — registers every ctx_* tool with Pi` describe
   block, plus a shared `mcpEnv` for the `CONTEXT_MODE_DISABLE_VERSION_CHECK`
   override. One place to update if `start.mjs` ever moves.

3. **C3 LOW — dead `running` getter on MCPStdioClient.**
   Exported in the original commit but had zero callers anywhere in
   `src/` or `tests/`. Dropped — five lines, no behavioral impact.

## Test plan

- [x] `npm run build`
- [x] `npm run typecheck` — clean
- [x] `npm test` — 73/73 files, 2407 pass / 25 skipped / 0 fail
- [x] `npx vitest run tests/pi-extension.test.ts -t "MCP bridge|wiring"` — 8 pass
- [x] Phase A re-validation: revert ONLY the wiring in
      `src/pi-extension.ts` (keep `src/pi-mcp-bridge.ts` intact),
      run the wiring test → fails with `registeredNames: []`. Restore
      and the test goes green again.

Co-Authored-By: Ora Studio <noreply@oratelecom.net>

* refactor(openclaw): consolidate src/openclaw/* into src/adapters/openclaw/

Pre-fix layout split OpenClaw across two locations:
  - src/adapters/openclaw/ — config, hooks, index, session-db (standard
    adapter pattern matching every other platform)
  - src/openclaw/         — mcp-tools, workspace-router (rogue location)

The split predates the adapter pattern: workspace-router.ts was added
first by Pedro Almeida (#aa8d93c), then mcp-tools.ts by the maintainer
(#ff0a9a2 v1.0.107), while the adapter dir was bootstrapped later by
the copilot-swe-agent (#5fd6a9e). Source-of-truth for every platform
should live under src/adapters/<platform>/, so we move the two
stragglers in.

## Changes

  - git mv src/openclaw/mcp-tools.ts        → src/adapters/openclaw/mcp-tools.ts
  - git mv src/openclaw/workspace-router.ts → src/adapters/openclaw/workspace-router.ts
  - rmdir src/openclaw

  - src/openclaw-plugin.ts: 3 import-path updates
  - tests/plugins/openclaw.test.ts: 1 import-path update
  - tests/core/cli.test.ts: 1 readFileSync source-grep path update
    (the existing PR #183 path-traversal regression test reads the
    workspace-router source file directly to grep for safe-regex
    patterns; pin updated to the new location)

## Test plan

  - [x] npm run typecheck — clean
  - [x] npx vitest run tests/plugins/openclaw.test.ts tests/core/cli.test.ts
        → 225/225 pass
  - [x] npm test — 73 files, 2405+ pass / 25 skipped / 0 fail

Co-Authored-By: Ora Studio <noreply@oratelecom.net>

* refactor: flatten src/concurrency/runPool.ts → src/runPool.ts

The src/concurrency/ directory held a single file. A whole directory
for one module is structural noise — flatten it to src/runPool.ts.

## Changes

  - git mv src/concurrency/runPool.ts → src/runPool.ts
  - rmdir src/concurrency
  - src/server.ts: import path updated
  - tests/core/server.test.ts: import path updated

## Test plan

  - [x] npm run typecheck — clean
  - [x] npx vitest run tests/core/server.test.ts -t "runPool" — pass

Co-Authored-By: Ora Studio <noreply@oratelecom.net>

* refactor: relocate plugin entry files into src/adapters/<platform>/

Pre-fix layout had three platform plugin entry files at the src/ root:

  src/pi-extension.ts        — Pi Coding Agent extension
  src/pi-mcp-bridge.ts       — Pi MCP bridge (added in #426)
  src/openclaw-plugin.ts     — OpenClaw gateway plugin
  src/opencode-plugin.ts     — OpenCode plugin

Every other platform follows the src/adapters/<name>/ pattern (config,
hooks, index, …). The four root-level files were the last hold-outs:
inconsistent layout, plus they made adapter discovery harder for new
contributors.

## Changes (file moves)

  - git mv src/pi-extension.ts        → src/adapters/pi/extension.ts
  - git mv src/pi-mcp-bridge.ts       → src/adapters/pi/mcp-bridge.ts
  - git mv src/openclaw-plugin.ts     → src/adapters/openclaw/plugin.ts
  - git mv src/opencode-plugin.ts     → src/adapters/opencode/plugin.ts

## Internal import-path updates inside the moved files

  - ./session/db.js    → ../../session/db.js   (depth +2)
  - ./types.js         → ../../types.js
  - ./adapters/X/Y.js  → ./Y.js                (now sibling)
  - ./adapters/types.js → ../types.js          (now parent)
  - ./pi-mcp-bridge.js → ./mcp-bridge.js       (renamed + sibling)

## Runtime path-resolution updates

The plugins read sibling resources (hooks/, package.json, etc.) via
`resolve(buildDir, "..")`. After the move buildDir lives 2 dirs
deeper, so every `..` is now `../../..`:

  - resolve(buildDir, "..")                                 → resolve(buildDir, "..", "..", "..")
  - resolve(buildDir, "..", "hooks", "core", "routing.mjs")    → resolve(buildDir, "..", "..", "..", "hooks", "core", "routing.mjs")
  - (and similar for routing-block / tool-naming / auto-injection)

For opencode/plugin.ts the version-from-package.json walker prepends
`../../../package.json` to its search list (keeps the legacy
`../package.json` and `./package.json` entries as fall-backs so
unbundled or old-layout dev environments still resolve).

## Build-output paths in package.json

tsc preserves src/ structure under build/, so:

  ./build/pi-extension.js     → ./build/adapters/pi/extension.js
  ./build/openclaw-plugin.js  → ./build/adapters/openclaw/plugin.js
  ./build/opencode-plugin.js  → ./build/adapters/opencode/plugin.js

Updated:

  - package.json: pi.extensions[0], openclaw.extensions[0], main,
    exports["."], exports["./plugin"], exports["./openclaw"]
  - .pi/extensions/context-mode/index.ts: re-export delegate path
  - .openclaw-plugin/index.ts: re-export delegate path + JSDoc

## Test-side updates

  - tests/pi-extension.test.ts: dynamic-import paths updated
  - tests/opencode-plugin.test.ts: dynamic-import paths updated
  - tests/plugins/openclaw.test.ts: dynamic-import paths updated
  - tests/core/cli.test.ts: 4 dynamic-import paths + 1
    `readFileSync(src/openclaw-plugin.ts)` source-grep updated to the
    new location
  - src/adapters/detect.ts: comment-line ref updated
  - tests/adapters/detect.test.ts: comment-line ref updated

## Test plan

  - [x] npm run build                                        clean
  - [x] npm run typecheck                                    clean
  - [x] npm test                                             73 files,
        2407 pass / 25 skipped / 0 fail
  - [x] npx vitest run tests/opencode-plugin.test.ts         33/33 pass
        (regression: marker test that needed package.json walker fix)
  - [x] npx vitest run tests/plugins/openclaw.test.ts        225/225 pass
  - [x] npx vitest run tests/pi-extension.test.ts            45/45 pass
        (incl. the wiring guard added in the previous commit)
  - [x] npx vitest run tests/core/cli.test.ts -t "openclaw-plugin.ts doctor/upgrade"
        passes against the new src/adapters/openclaw/plugin.ts location
  - [x] Manual sanity: every old root-level path (build/pi-extension.js,
        src/opencode-plugin.ts, etc.) is gone from the repo — grep
        confirms zero stale refs in src/ + tests/ + package.json + the
        .pi/.openclaw-plugin/ thin wrappers.

Co-Authored-By: Ora Studio <noreply@oratelecom.net>

* fix(ci): update E2E + install scripts for relocated openclaw plugin path

The structural refactor in 4911c07 (src/openclaw-plugin.ts → src/adapters/
openclaw/plugin.ts) moved the build output from build/openclaw-plugin.js
to build/adapters/openclaw/plugin.js. Three scripts still pointed at the
legacy path and broke on next-CI.

## OpenClaw E2E (failing on ubuntu-latest + macos-latest)

  scripts/test-openclaw-e2e.sh:34
    join(process.cwd(), "build", "openclaw-plugin.js")

The Phase 1 plugin-load check failed at "❌ build/openclaw-plugin.js
exists" → exit 1. Updated to look for the new path first, fall back to
the legacy one for transition safety:

  build/adapters/openclaw/plugin.js → fall back → build/openclaw-plugin.js

Loaded-tag tracks which path actually resolved.

## OpenClaw global install (would have broken at user-install time)

  scripts/install-openclaw-plugin.sh:49 (auto-generated index.ts stub)

Updated the absolute re-export path written into the generated stub
plus the jiti cache-clear glob (now matches both
`build-adapters-openclaw-plugin.*.cjs` and the legacy
`build-openclaw-plugin.*.cjs` filenames).

## Bonus: security.js path was wrong post-refactor

The opencode + openclaw plugins called `routing.initSecurity(buildDir)`
where buildDir = build/adapters/<platform>/. That made initSecurity look
for build/adapters/<platform>/security.js — which never exists. The
security module lives at build/security.js (top-level). The fix-open
fallback meant tests still passed but every plugin load emitted a
spurious WARNING about deny-policy enforcement being off.

  - opencode/plugin.ts: pass `resolve(buildDir, "..", "..")` (= build/)
  - openclaw/plugin.ts: same

Verified locally: `bash scripts/test-openclaw-e2e.sh` → 39/39 pass, no
security warning.

## Test plan

  - [x] npm run build              clean
  - [x] npm run typecheck          clean
  - [x] npm test                   73 files, 2405+ pass / 25 skipped /
        0 fail (2 pre-existing flake worker-pool timeouts on
        kiro-hooks + insight-cors; both pass when run in isolation)
  - [x] bash scripts/test-openclaw-e2e.sh
        → "Results: 39 passed  0 warned  0 failed"  +  "✅ E2E test PASSED"

Co-Authored-By: Ora Studio <noreply@oratelecom.net>

* docs(openclaw): update Key Files paths after src/adapters/<platform>/ refactor

Independent PR review on #472 caught 3 stale path strings in
`docs/adapters/openclaw.md` that the structural refactor (4911c07)
missed:

  - src/openclaw-plugin.ts          → src/adapters/openclaw/plugin.ts
  - src/openclaw/workspace-router.ts → src/adapters/openclaw/workspace-router.ts (×2)

Doc-only — no code paths reference these strings.

Co-Authored-By: Ora Studio <noreply@oratelecom.net>

---------

Co-authored-by: Ora Studio <noreply@oratelecom.net>
2026-05-08 02:30:46 +03:00
Mickey Lazarevic 51fd51e45b fix(opencode-plugin):add structured logging, remove per-session dedup sets, tighten plugin types (#448)
* fix(opencode-plugin):add structured logging, remove per-session dedup sets, tighten plugin types

- Replace loose PluginContext with typed client/log interfaces matching
  the OpenCode plugin contract (client.app.log)
- Add OPENCODE_DEBUG-gated logger() at key injection points: routing
  block, compaction snapshot, auto-injection block, and resume snapshot
- Remove routingInjected/resumeInjected per-session Sets — routing block
  now injects on every chat.system.transform turn for reliability;
  resume snapshot dedup is handled by DB claim consumption
- Add `id` field to default export for plugin identification
- Defensive fallback to process.cwd() when ctx.directory is undefined
- Add verbatim-display instruction to ctx_commands in routing block
- Update tests to match new behavior: routing block re-injects every
  turn, resume snapshot gated by DB consumption only

* docs(hooks): remove verbose command output instruction
2026-05-06 20:37:34 +03:00
Mickey Lazarevic 3ef7b6ebb4 feat(plugin): add context injection support for additionalContext (#422)
Previously, context actions were no-ops in the OpenCode plugin. Now, when a context action includes additionalContext, it gets injected into the output args for OpenCode to consume.

Adds test case to verify guidance injection for allowed commands.
2026-05-04 20:36:40 +03:00
Mert Koseoglu ff0a9a2665 fix(14-adapter parity): v1.0.107 — Mickey ROUTING_BLOCK + 30+ verified gaps
Comprehensive 14-adapter parity audit + line-by-line clone verification
(no agent fabrication: code-driven `grep` + websearch + `gh code search`
where source closed). 9 fabrications caught and retracted; 22 verified
P0 gaps closed across 13 adapters.

═══ Mickey-class routing block (ana şikayet) ═══

Mickey @mikij retest of v1.0.106 — "routing block is not injected and
everything rely on AGENTS.md which btw is sadly unreliable."

VERIFIED via grep: `ROUTING_BLOCK | createRoutingBlock` returned 0 hits
in src/opencode-plugin.ts. Real gap.

Fixed in 3 adapters (same pattern, evidence-based):

  • OpenCode (src/opencode-plugin.ts:430-446) — `experimental.chat
    .system.transform` now splices `createRoutingBlock(createToolNamer
    ("opencode"))` at index 1 with visible marker
    `<!-- context-mode v1.0.107: routing block injected (sessionID=...) -->`.
    Separate `routingInjected: Set<string>` (NOT same as resumeInjected) so
    routing fires even without prior snapshot. splice(1,0) preserves
    OpenCode's `system[0]` cache-fold header per
    agentisd/refs/opencode/packages/opencode/src/session/llm.ts:117-128.

  • OpenClaw (src/openclaw-plugin.ts:638-647) — `before_prompt_build`
    handler replaced static `readFileSync(configs/openclaw/AGENTS.md)`
    with dynamic `createRoutingBlock(createToolNamer("openclaw"))`. Same
    visible marker pattern.

  • Pi (src/pi-extension.ts:321-330) — `before_agent_start` handler
    prepends routing block via dynamic-import `getRoutingBlock(pluginRoot)`.
    Same visible marker.

Verification path: `OPENCODE_DEBUG=1 opencode` → `grep
"<!-- context-mode v1.0.107: routing block" ~/.cache/opencode/log/*.log`.

═══ Per-adapter P0 fixes (verified by Phase 7 line-by-line review) ═══

OpenCode (4 P0 — Mickey-class):
  OC-1 ROUTING_BLOCK injection (above)
  OC-2 chat.message hook wired (was missing — SDK has it at
       agentisd/refs/opencode/packages/plugin/src/index.ts:233);
       captures user prompts + extractUserEvents + CCv2 inline
       <system-reminder> filter
  OC-3 buildAutoInjection (P1/P2/P3/P4 priority bands, 500-token cap)
       in experimental.session.compacting (was raw buildResumeSnapshot)
  OC-4 captureAgentsMd() in tool.execute.after — captures AGENTS.md /
       CLAUDE.md / CONTEXT.md as rule_content events
       (FILES order verified against agentisd/refs/opencode/packages
       /opencode/src/session/instruction.ts)

OpenClaw (6 critical):
  OClaw-1 NEW src/openclaw/mcp-tools.ts — registers 11 ctx_* MCP tools
          via `api.registerTool` (mirrors swarmvault standard MCP at
          refs/plugin-examples/openclaw/swarmvault/packages/engine
          /src/mcp.ts:46-51). Routing block was telling agents to
          use tools that DIDN'T EXIST in OpenClaw sessions
  OClaw-2 dynamic createRoutingBlock (above)
  OClaw-3 isSystemReminderMessage 4-prefix filter in
          before_model_resolve (Mickey-class false-positive guard)
  OClaw-4 session_end handler finalizes resume snapshot
          (per refs/platforms/openclaw/docs/plugins/hooks.md:110)
  OClaw-5 subagent_spawning handler injects routing block via
          inputOverride.prompt
  OClaw-6 closure flag at lines 239,483,564,568 — F6 retraction
          confirmed: per-register() singleton scope, NOT a bug

Pi (1 P0 + 3 P1):
  Pi-1 routing block injection (above)
  Pi-2 before_provider_response handler (was unregistered — captures
       {model, provider, latencyMs, tokens})
  Pi-3 active_memory always-built (removed compact_count===0 early-return)
  Pi-4 buildAutoInjection 500-token cap

Qwen Code (4 silent bugs — wire identical at PAYLOAD level only):
  Qwen-1 NEW hooks/core/platform-detect.mjs — env-var detection mirrors
         PLATFORM_ENV_VARS in src/adapters/detect.ts (single source of
         truth). Replaces hardcoded createToolNamer("claude-code") in
         hooks/sessionstart.mjs:22 → Qwen agents now see correct tool
         names instead of CC-prefix namespace
  Qwen-2 configureAllHooks 2 → 5 hooks (was dropping PostToolUse,
         PreCompact, UserPromptSubmit)
  Qwen-3 hooks/core/routing.mjs TOOL_ALIASES extended with Qwen native
         (write_file, edit, glob, todo_write, ask_user_question,
         list_directory, save_memory, skill, exit_plan_mode) +
         OpenClaw native (exec/read/grep/search) + Codex native
         (shell, local_shell, etc) — curl/wget/build redirects now
         work on all platforms
  Qwen-4 src/session/extract.ts TOOL_NAME_NORMALIZE shim normalizes
         platform-native tool names → CC equivalents at extractEvents
         entry → no more silently-dropped events on Qwen

Kiro (3 P0):
  Kiro-1 NEW hooks/kiro/userpromptsubmit.mjs — was declared in HOOK_TYPES
         but no script existed
  Kiro-2 NEW hooks/kiro/agentspawn.mjs — Kiro's SessionStart-equivalent.
         Capability flipped to true, parser added, routing block injected
         via additionalContext

VSCode Copilot (1 P0 + path bug):
  A1 hooks.ts:98 path bug — was resolving to Claude-Code generic
     `hooks/pretooluse.mjs` instead of `hooks/vscode-copilot/...`.
     Plus created missing hooks/vscode-copilot/sessionstart.mjs (mirror
     of jetbrains-copilot pattern, captures
     .github/copilot-instructions.md as rule_content events)
  Z13 HOOK_TYPES STOP/SUBAGENT_START/SUBAGENT_STOP orphan removed
      (declared but never had scripts). copilot-base made these
      optional; jetbrains still declares them but it's also out of
      scope (no scripts shipped either)

Cursor (3 P0):
  C-1 sessionStart capability flag flipped true (script + dispatcher
      already existed, capability said false)
  C-2 NEW hooks/cursor/afteragentresponse.mjs (74 lines, mirrors
      stop.mjs); was registered in HOOK_TYPES with no script (dangling)
  C-3 Task-tool deferral comment (closed-source unknown, no probe data)

Gemini CLI (1 P1):
  G-1 NEW hooks/gemini-cli/beforeagent.mjs — Gemini's UserPromptSubmit
      analog per refs/platforms/gemini-cli/packages/core/src/hooks
      /types.ts:580-602 + hookRunner.ts:215-228 (additionalContext
      appended to prompt)

Antigravity (1 P0):
  A-1 configs/antigravity/GEMINI.md prepended `trigger: always_on`
      frontmatter — confirmed via refs/plugin-examples/antigravity
      /antigravity-kit/.agent/rules/GEMINI.md:1

Zed (1 LOW):
  Z-1 configs/zed/AGENTS.md added "NEVER use ctx_execute/shell to
      create files" clause (was missing per CC routing-block.mjs:46-50)

Kilo (1 LOW):
  K-1 src/adapters/opencode/index.ts paths() includes .kilocode/kilo.json
      and .kilocode/kilo.jsonc (Kilo runtime accepts these per
      refs/platforms/kilo/packages/.../config.ts:50,408)

═══ 9 FABRICATIONS retracted (kanıtla) ═══

F1 V1: "Codex hooks not registered, 10-line fix needed" → already
       at src/adapters/codex/index.ts:244-276, all 5 registered
F2 V1+V2: "GEMINI_PROJECT_DIR invented by us" → REVERSED. hookRunner.ts:355
       SETS it (plus GEMINI_SESSION_ID, GEMINI_CWD, GEMINI_PLANS_DIR,
       CLAUDE_PROJECT_DIR alias). Detection KEPT.
F3 V2: "Add KIRO_API_REGION to detection" → CONFIRMED FABRICATION.
       Only in kiro-gateway test fixtures, NOT IDE source. KEPT detect.ts:76.
F4 V1: "VSCode `system` priority injection exists" → wrong, only User/
       Assistant in chat API. systemMessage field is UI warning-only.
F5 V1: "mcp-jetbrains exposes IDE injection" → pure stdio↔HTTP proxy
F6 V1: "OpenClaw closure-flag bad per community" → unverifiable
       (sample size 0). Retraction-of-retraction: actually fine
       per-register() singleton, kept as-is.
F7 V1: "swarmvault is OpenClaw-specific MCP" → standard
       @modelcontextprotocol/sdk only
F8 V1: "pi/awesome-agent-skills shows routing patterns" → empty repo
       (4 scaffold files only)
F9 V1: "Zed has zero chat-influence APIs" → REVERSED. slash_command +
       context_server_command traits CONFIRMED at extension.wit:151-160,
       wasm_host.rs:306-340/351-386. (CC10 Zed extension viable —
       deferred to v1.0.108+, requires wasm32 build)
+ V2 "Cursor MDC alwaysApply" — Cursor-Rules-Awesome only manages
     legacy .cursorrules (no frontmatter). Dropped.
+ Initial v1.0.107 "10-file generic SDD scaffold deploy" (cc-sdd
     mirror — product/structure/tech + steering-custom/api-standards/
     auth/database/deployment/error-handling/security/testing) →
     Mert review caught: those are end-user project templates, NOT
     adapter wiring. Reverted; we ship single configs/kiro/KIRO.md
     and let users opt in by copying manually.

═══ Tests ═══

All in EXISTING files (CONTRIBUTING L275). Cumulative test counts
post-Phase-8:
  • 32/32 OpenCode (8 new, 8 updated)
  • 95/95 OpenClaw (12 new)
  • 36/36 Pi (7 new)
  • 17 new Qwen + 165 regression
  • 49/49 Kiro (after steering-scaffold revert: 1 assertion adjusted)
  • 32/32 Cursor (2 new)
  • 67/67 VSCode + Gemini (4 new)
  • 333+ adapter-specific tests pass total
  • Full suite: 2268 pass / 9 fail (pre-existing baseline: opencode-
    adapter tsx subprocess infra, .cw worktree node_modules/tsx
    missing — verified pre-existing via stash)
  • TypeScript: clean

═══ Validation discipline ═══

3-tier verification per gap:
  1. Code-driven `grep` against fresh `--depth=1` clones in refs/platforms/
  2. Re-grep against community plugin examples in refs/plugin-examples/
  3. WebSearch + `gh code search` for closed-source platforms (Cursor,
     JetBrains AI Assistant, Antigravity, Kiro IDE)

Rejected approach: agent narrative claims (V1+V2 audits had 9
fabrications detected via this discipline). Phase 7 used 1 main +
13 parallel adapter agents reading line-by-line; Phase 8 used 9
parallel staff-engineer agents writing TDD red→green per slice;
2 architect grills caught remaining issues including Q1 platform-
detect.mjs PLATFORM_ENV_VARS misalignment (now mirrors src/adapters
/detect.ts:33-77 as single source of truth).

═══ NOT shipped (out-of-scope reverts during review) ═══

  • 10-file Kiro generic SDD steering scaffold (Mert review: ~~cc-sdd
    project templates, not adapter wiring~~)
  • PRD-*.md files (16 dev artifacts, untracked)
  • refs/ clones (~3GB, untracked)
  • Companion VSIX extension for VSCode (CC12) — defer
  • Zed wasm32 extension (CC10) — defer
  • JetBrains companion plugin via postStartupActivity — defer
  • Qwen 7 enhancement events (Stop/SubagentStart/Stop/StopFailure/
    SessionEnd/PostToolUseFailure/PostCompact) — defer
2026-05-03 18:25:06 +03:00
Mert Koseoglu 131079db22 fix(opencode-plugin+db+server): v1.0.106 — Mickey resume injection follow-up + version-check TTL
Mickey @mikij retest of v1.0.105 (PR #376 follow-up):
  > "source has changed but snapshot is undefined still and I does
  >  not do nothing yet as I can see. resumeInjected.add(sessionId);
  >  is executed no matter session injected something or not. What I
  >  need to do to get this snapshot active? At least I can't find
  >  use case for it"

Three findings, all addressed here:

1. `resumeInjected.add(sessionId)` ordering bug (Mickey's correct observation)
   v1.0.105 marked the session as "tried" BEFORE checking whether the DB had
   a snapshot to claim. A new session that polled too early — before any
   prior session had compacted — was permanently locked out of retroactive
   injection within the same plugin process.
   Fix: src/opencode-plugin.ts moves `resumeInjected.add(sessionId)` to AFTER
   a successful splice. No row → no mark → next chat turn retries.

2. Self-injection bug (would have shipped if we'd applied #1 naively)
   With #1 alone, a session that compacts mid-flight (Session B turn 5
   produces B's own snapshot row) would claim its OWN snapshot back into its
   OWN system prompt on turn 6 — wasted tokens AND consumed the snapshot
   meant for the next fresh session.
   Fix: src/session/db.ts `claimLatestUnconsumedResume(currentSessionId)`
   now takes a `currentSessionId` parameter. SQL adds `AND session_id != ?`
   to the inner SELECT, so the current session can never claim its own row.
   Passing an empty string disables the guard (legacy/test paths).

3. Visible signal — Mickey's "I can't find use case for it"
   Cross-session resume was working but invisible. No log line, no UI
   surface, no way for the user to confirm it fired. We now prepend a
   plain XML comment to the snapshot so OPENCODE_DEBUG=1 (and any provider
   that echoes the system prompt) shows:
     <!-- context-mode v1.0.106: resumed prior session abc12345 (N events, M chars) -->
     <session_resume>...</session_resume>
   The marker is harmless to the model and proves the feature is live.

Bonus: `_latestVersion` TTL refresh
   src/server.ts now schedules a 1-hour `setInterval` to re-fetch
   `npm view context-mode/latest`. The first call still happens at startup;
   the interval keeps long-running sessions (24h+ MCP servers) aware of
   new releases instead of caching the value from process boot. `.unref()`
   ensures graceful shutdown isn't blocked by the pending interval.
   Discovered when v1.0.105 release notification didn't reach a 14h-old
   session.

Tests (all in EXISTING files per CONTRIBUTING.md L275):
  • tests/session/session-db.test.ts — 2 new self-exclusion slices:
    - excludes the current session's own row (no self-injection)
    - returns another session's row when current also has one
  • tests/opencode-plugin.test.ts — 4 new behavior tests:
    - does NOT inject snapshot back into producing session (self-injection guard)
    - retries on next turn when no row exists (no premature gate)
    - snapshot from B is consumed by C, not by B itself
    - emits a visible context-mode marker comment

Existing 79+24 tests all updated to the new claim signature
(empty-string sentinel where no current session is meaningful).

Validation:
  • TypeScript: clean
  • 440/440 tests pass across session-db + opencode-plugin + server + executor + routing
  • 3 parallel sub-agents (diagnose / grill-with-docs / architect) converged on RESHAPE
    before this change, SHIP after the self-exclusion + visible marker landed
  • Verified against agentisd/refs/opencode source: chat.system.transform fires
    per-turn from `LLM.process` (session/llm.ts:103-122), system enters as
    1-entry joined string, our splice(1, 0, marker+snapshot) preserves cache fold

Docs:
  docs/platform-support.md OpenCode entry now lists the 4th hook
  (experimental.chat.system.transform), explains the cross-session resume
  workflow, and tells users how to verify via OPENCODE_DEBUG=1.

Mickey's repro after this lands:
  1. cd ~/proj && opencode → do enough work to trigger auto-compact (or run /compact)
  2. exit, restart `opencode` in same dir
  3. send first message → grep log for `<!-- context-mode v1.0.106:`
2026-05-03 15:20:45 +03:00
Mert Koseoglu eea5877bd3 fix(opencode-plugin+executor+routing): Mickey 3-issue + Issue #406 timeout policy
Mickey @mikij (PR #376 follow-up — github.com/mksglu/context-mode/pull/376):

  1. db.getResume() returned null on every chat turn
     Root cause: src/opencode-plugin.ts pivoted on the wrong hook —
     `experimental.chat.messages.transform` ships with input `{}` (no
     sessionID) and output `{info,parts}[]`. The prior `output.messages
     .unshift({role,content})` was the wrong shape and silently dropped
     by OpenCode. Verified against
     agentisd/refs/opencode/packages/plugin/src/index.ts:281-295 and
     packages/opencode/src/session/llm.ts:117-128.
     Fix: pivot to `experimental.chat.system.transform` (input
     `{sessionID?, model}`, output `system: string[]`). Insert at
     index 1 (`splice(1, 0, snapshot)`) — NOT `unshift` — to preserve
     OpenCode's `system[0]` cache-fold header invariant; otherwise the
     provider prompt cache invalidates on every resume injection.

  2. `sessionStartInjected = true` survived only first session per process
     Root cause: closure-scope boolean + a process-global `randomUUID()`
     binding. Multi-session reuse (which OpenCode/Kilo do — the plugin
     factory runs once and serves many sessions) tripped the flag and
     never reset it; the fabricated UUID also never matched any prior
     resume row.
     Fix: drop `randomUUID()` entirely; plumb `input.sessionID` from each
     hook (system.transform, tool.execute.after, session.compacting);
     gate injection with `Set<string>` keyed by sessionID.

  3. Z.ai GLM 4.7 → "client is not defined" on ctx_doctor
     Root cause: Z.ai's MCP renderer mounts a custom React component
     for GFM task-list syntax (`- [x]`, `- [ ]`, `- [-]`) that depends
     on a missing `client` context.
     Fix: refactor ctx_doctor output to plain-text `[OK]/[FAIL]/[WARN]`
     prefixes and drop the `## ` h2 — renderer-safe across all MCP
     clients. Skill instructions updated to match.

Cross-session resume claim is now race-safe (architect ask):
  src/session/db.ts gains `claimLatestUnconsumedResume()` — atomic
  `UPDATE session_resume SET consumed=1 WHERE id=(SELECT id FROM
  session_resume WHERE consumed=0 ORDER BY created_at DESC, id DESC
  LIMIT 1) RETURNING session_id, snapshot`. Two concurrent processes
  for the same project cannot inject the same snapshot (verified by
  test).

Issue #406 @wax911 (github.com/mksglu/context-mode/issues/406):

  Long-running build commands (Gradle/Maven/SBT) were terminated by the
  Zod `.default(30000)` ms timeout in ctx_execute / ctx_execute_file
  / `.default(60000)` in ctx_batch_execute whenever the caller forgot
  to pass an explicit value. Server-side timeout policy is the wrong
  layer — every MCP host already enforces its own RPC timeout — and
  stacking env-var defaults on top would create dead config.

  Fix:
   - Drop the Zod `.default(...)` from all three tools' timeout schemas;
     keep `.optional()`. When the caller omits `timeout`, no server-side
     timer fires (the host's RPC timeout governs).
   - PolyglotExecutor: when `timeout === undefined`, do not set the
     setTimeout timer at all. Long-running processes complete naturally.
   - runBatchCommands: `BatchRunOptions.timeout` is `number | undefined`;
     serial path lifts the shared budget, parallel path passes
     `undefined` to the executor (no per-cmd kill).
   - hooks/core/routing.mjs: word-boundary-anchored regex now also
     redirects `sbt` / `./sbt` to ctx_execute (was only gradle/mvn).
     Guard against false positives like `gradle-wrapper-config`.

Tests (all in EXISTING files per CONTRIBUTING.md L275):
  • tests/session/session-db.test.ts — 4 atomic-claim slices (returns
    null on empty / on consumed-only, returns latest unconsumed and
    marks consumed atomically, two parallel claims return distinct rows)
  • tests/opencode-plugin.test.ts — 5 system.transform behavior tests
    (factory exposes new hook + omits old, no-sessionID no-op,
    no-resume no-op, prepends snapshot first call, preserves system[0]
    header for cache fold, does NOT re-inject same sessionID)
  • tests/core/server.test.ts — Z.ai renderer-safe regression + 2 #406
    serial-batch undefined-timeout tests
  • tests/executor.test.ts — 2 #406 no-timeout tests (JS + shell)
  • tests/hooks/core-routing.test.ts — 3 sbt/word-boundary tests
  • Removed: tests/opencode-session-start.test.ts (orphan + asserted
    the now-removed messages.transform hook — coverage moved into
    tests/opencode-plugin.test.ts)

Bundles rebuilt: server.bundle.mjs, cli.bundle.mjs,
hooks/session-db.bundle.mjs, hooks/session-extract.bundle.mjs

Reviewed by 8 parallel sub-agent passes (3 diagnose + 3 grill +
2 architect+validation). Verdicts converged on SHIP after the cache-
fold splice fix.

14 adapter × 3 OS coverage:
  • 10 hook-paradigm adapters unaffected (chat.system.transform is
    OpenCode SDK plugin paradigm only)
  • SQLite 3.53 bundled with better-sqlite3 — `UPDATE … RETURNING`
    works on Windows
  • openclaw + pi unchanged — they receive a real session_start event
    so the Mickey #2 closure-flag pattern doesn't apply
2026-05-03 14:28:39 +03:00
Mert Koseoglu 35e264caa4 fix(hooks): replace PPID sentinel with directory-scan for MCP readiness (#347)
Claude Code spawns hooks via `bash -c "node ..."` on WSL2/Linux. The
intermediate shell makes process.ppid point to a transient bash PID,
not Claude Code. The PPID-keyed sentinel is never found, causing all
MCP redirects to be bypassed — context window floods.

Fix: hooks now scan /tmp for `context-mode-mcp-ready-*` files and probe
each PID with kill(pid,0). Server writes sentinel with process.pid (not
ppid). Hardcoded /tmp on Unix avoids TMPDIR mismatch.

Changes:
- hooks/core/mcp-ready.mjs: glob scan + PID liveness + stale cleanup
- src/server.ts: sentinel uses process.pid, /tmp on Unix
- 11 test files: updated sentinel path pattern

Closes #347

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-26 15:35:06 +03:00
Mert Koseoglu 0f80c851e8 test: add MCP sentinel setup to all routing test files (#230)
mcpRedirect() returns null when sentinel absent, breaking 32 tests
across 8 files that expect deny/modify behavior. Added sentinel
write/cleanup in beforeEach/afterEach for all affected test suites.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 23:23:15 +03:00
Mert Koseoglu 3893fd5272 fix: update opencode-plugin test assertions for reference-based snapshots
Tests at lines 218 and 276 expected full file paths (/src/index.ts,
/app/main.ts) in snapshot output, but v1.0.68 reference-based format
uses filename-only display (index.ts, main.ts). Updated assertions.

Fixes CI failure on ubuntu/windows/macos.
2026-04-04 21:13:52 +03:00
rich-jojo 715d6f45f9 fix(opencode): remove stale AGENTS.md auto-write fallback
* ci: update server.bundle.mjs, cli.bundle.mjs & session hook bundles

* ci: update install stats

* fix(opencode): stop auto-writing AGENTS.md

* fix(opencode): remove stale routing fallback

* docs: clarify OpenCode continuity limits

* chore: retrigger flaky macOS CI

* test(ci): use forks pool on macOS

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-02 01:15:49 +03:00
rich-jojo b77736a561 test: isolate HOME during vitest runs (#180)
* test: isolate HOME during vitest runs

* test: assert fake HOME isolation

* test: avoid teardown races in fake HOME setup

* test: scope fake HOME isolation to homedir suites

* docs(test): explain fake HOME scoping

* test: isolate opencode plugin HOME writes

* chore: retrigger macOS CI
2026-03-28 20:53:57 +03:00
Mert Koseoglu 815de2b56d fix: marketplace sync, OpenCode hooks, routing guard, OpenClaw env vars (#156 #158 #159 #160)
## Issue #158 — start.mjs unconditionally writes CLAUDE.md
- Added hook-capable platform guard: skip routing file write when
  CLAUDE_SESSION_ID, GEMINI_PROJECT_DIR, OPENCODE, OPENCLAW_HOME,
  or OPENCLAW_CLI env vars are present (sessionStart hook handles it)

## Issue #159 — OpenCode hook interface mismatch
- Fixed opencode-plugin.ts: two-param (input, output) hook signature
  matching upstream @opencode-ai/plugin API
- Field mapping: tool_name→input.tool, tool_input→output.args,
  tool_output→output.output, is_error removed
- Arg mutation now correctly targets output.args (read by OpenCode)
- Updated adapter index.ts and all tests

## Issue #160 — Marketplace sync: cli.bundle.mjs missing
- ctx_doctor: runs diagnostics in-process (no CLI dependency)
- ctx_upgrade: temp .mjs file fallback when CLI missing (cross-platform)
- start.mjs: CLI self-heal shim (cli.bundle.mjs from build/cli.js)
- session-loaders.mjs: bundle-first with build/session fallback
- Skills: prefer MCP tool, Bash as fallback

## Additional fixes
- Replaced fabricated OPENCLAW_PROJECT_DIR with real OPENCLAW_CLI env var
  (verified against openclaw/openclaw source: src/infra/openclaw-exec-env.ts)
- postinstall.mjs: improved nvm4w detection, isSafeWindowsPath guard
- Removed dead basename import from session-loaders.mjs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 04:42:05 +03:00
Lukas Lee Navarro a01106eda2 Feat: "sessionStart: true" platforms autocreate instructions on windows (#87)
* feat(vscode-copilot): auto-create or merge copilot-instructions.md on session start

- Create .github/copilot-instructions.md if it doesn't exist
- Append routing rules if file exists but lacks context-mode instructions
- Skip silently if routing rules already present (idempotent)
- Create .github/ directory if needed (mkdirSync recursive)
- Outer try/catch ensures missing configs never block session start
- Update README to document all three cases for Step 4

* feat(adapters): auto-write routing instructions on Gemini/OpenCode startup

* docs(pr): explain run_in_terminal alias and PR purpose

* Revert "docs(pr): explain run_in_terminal alias and PR purpose"

This reverts commit a9fa891ac4.
2026-03-10 22:19:54 +03:00
Mert Koseoglu 956a46ab6e feat: multi-platform session continuity hooks + OpenCode plugin
- Add OpenCode TypeScript plugin entry point (src/opencode-plugin.ts)
  with routing enforcement, event capture, and compaction snapshot
- Implement Gemini CLI session hooks (aftertool, precompress, sessionstart)
  with full lifecycle: startup cleanup, event capture, snapshot generation,
  and compaction/resume recovery
- Implement VS Code Copilot session hooks (posttooluse, precompact,
  sessionstart) with sessionId camelCase support and copilot-instructions.md
  rule capture
- Extend hooks/session-helpers.mjs with platform params (GEMINI_OPTS,
  VSCODE_OPTS, getProjectDir) for cross-platform DB/session paths
- Fix Claude Code hook import paths (packages/session/dist → build/session)
- Add 36 new tests (14 OpenCode plugin + 11 Gemini + 11 VS Code hooks)
- Update README platform tables and session continuity notes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 04:34:10 +03:00