Commit Graph
31 Commits
Author SHA1 Message Date
Martin Vogel 74982efc60 test(cli): restore Pi abort-kill assertion dropped alongside #2787 test
The #2787 change added client_adapter_pi_emits_string_enum_issue2787 directly below client_adapter_pi_wraps_result_and_honors_abort and, in the same hunk, removed that test's check that the generated abort handler kills the cli child (if (!child.killed) child.kill();). The generator still emits the line; restore the assertion byte-for-byte so the abort path keeps its coverage.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-27 23:41:04 +01:00
Divakant Pandey 86320afa97 fix(cli): emit string enums as Type.String({ enum }) in Pi adapter (#2787)
Signed-off-by: Divakant Pandey <divakant01@gmail.com>
2026-10-26 16:06:49 +05:30
Lance Woodson 6e8e785679 fix(cli): bound emitted OpenCode augment() with SIGKILL timeout and stdin guard
augment() is awaited inline in 'tool.execute.after', so a hook-augment
child that never exits blocked every grep/glob/read tool result in the
host OpenCode session. The emitted helper now:

- kills the child (SIGKILL) and resolves '' after AUGMENT_TIMEOUT_MS
  (10000ms, a backstop above hook-augment's own in-process deadline,
  which is clamped to 10000ms, so the child's graceful self-exit and
  its hook-augment-timeouts.log breadcrumb always fire first)
- listens for child.stdin 'error' and guards stdin.end(), because stream
  errors fire on the stream, not the child, and previously escaped as
  unhandled 'error' events (EPIPE with a >64KB buffered write, ENOENT
  spawn), crashing the host under Bun 1.4.0
- destroys stdout in the timeout path to release the read end promptly

Fixes #2693

Signed-off-by: Lance Woodson <65194841+woodsonl@users.noreply.github.com>
2026-10-18 15:28:45 -05:00
Martin Vogel 11957ae655 Merge branch 'main' into fix/2077-opencode-v2-plugin-shape 2026-09-28 00:30:10 +02:00
Martin Vogel 19d9e4ac4d Merge branch 'main' into fix-pi-extension-typebox-schemas 2026-09-21 07:40:01 +02:00
Amir Fathi e171bd48b5 fix(cli): move opencode plugin hooks under server(), leave setup() empty
DeusData asked (2026-09-19T20:25:34Z on PR #2089) to place the hooks in
the server entry rather than returning them from setup(), since the
server runtime reads default.server and dispatches the hooks it
returns, while the V2 config loader only reads id + setup()/effect()
and has no tool domain yet to register against.

Moved the existing hook body under server: async (ctx) => {...} and
added an empty setup() {}. Updated the emission test to check for both
and to assert the old async setup(ctx) { shape is gone.

Signed-off-by: Amir Fathi <amirfathi.me@gmail.com>
2026-09-20 09:15:32 +00:00
Amir Fathi 8f3c15044b fix(cli): emit the OpenCode plugin as a V2 default export
OpenCode's V2 loader validates only the module's default export,
expecting {id, setup()|effect()}. cbm_client_adapter_opencode still
emitted the V1 named export (export const CodebaseMemory = ...), so
every server start failed with SchemaError: Missing key at ["default"].

Wrap the existing hook logic in a default export with an id and a
setup(ctx) function; the returned hooks object is unchanged.

Fixes #2077

Signed-off-by: Amir Fathi <amirfathi.me@gmail.com>
2026-09-10 06:15:19 +00:00
Choti Wongbussakorn 8d46d25862 fix(opencode): pass input args to augment hook and allow tool search in subagents
- Fix client adapter passing output?.args instead of input?.args in tool.execute.after hook (closes #1737)
- Add tool_search and tool_search_regex permissions to OpenCode subagent profiles so deferred MCP tools can be discovered and unblocked
- Add regression tests for OpenCode adapter hook args and subagent profile permissions

Signed-off-by: Choti Wongbussakorn <126886556+Chewji9875@users.noreply.github.com>
2026-09-04 21:45:24 +07:00
Sakil Ansari d402623c20 test: match optional TypeBox depth schema
Signed-off-by: Sakil Ansari <sakilansari@Sakils-MacBook-Pro.local>
2026-09-03 21:23:55 +05:30
Sakil Ansari b026a69078 Merge branch 'main' into fix-pi-extension-typebox-schemas 2026-09-02 21:52:12 +05:30
Sakil Ansari 88269ac2ee Fix Pi TypeBox schema generation
Signed-off-by: Sakil Ansari <sakilansari@Sakils-MacBook-Pro.local>
2026-09-02 21:39:22 +05:30
Martin Vogel 1e3f2fd209 Merge pull request #1487 from JCVIDev/feat/opencode-context-hooks
feat(cli): extend the OpenCode plugin to every documented context surface
2026-08-31 13:32:07 +02:00
Martin Vogel 51c48e2093 Merge pull request #1858 from DeusData/fix/issue-1834-pi-stdin-transport
fix(pi): send tool arguments over stdin
2026-08-29 01:07:08 +02:00
Martin Vogel 93f7d27c44 Merge remote-tracking branch 'origin/main' into pr1487-work 2026-08-28 14:47:41 +02:00
Martin Vogel ee32816e3c fix(pi): send tool arguments over stdin
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-27 18:51:36 +02:00
JCVIDev 0b67621d3a Merge branch 'main' into feat/opencode-context-hooks 2026-08-27 01:42:36 +02:00
Sakil Ansari f05893757b Fix Pi extension to use TypeBox schemas instead of JSON
Resolves #1806 where Pi extension crashed on startup with 'Cannot read
properties of undefined (reading properties)' error.

- Convert JSON schema to TypeBox format in Pi extension generation
- Add TypeBox import to generated extension
- Update test to expect TypeBox format instead of JSON
- Handle all schema types: string, integer, boolean, array, enums
- Preserve descriptions and optional field handling

The generated extension now properly works with Pi's tool registration
API that requires TypeBox Type.Object schemas.

Signed-off-by: Sakil Ansari <sakilansari@example.com>
Signed-off-by: Sakil Ansari <sakilansari@Sakils-MacBook-Pro.local>
2026-08-23 22:44:32 +05:30
DavidHLP 410cd9a334 fix(cli): respect OMP agent directory and preserve instructions
Signed-off-by: DavidHLP <lysf15520112973@163.com>
2026-08-21 20:07:37 +08:00
DavidHLP f518eaee7b feat(cli): add Oh My Pi (omp) as 44th client surface
Add omp (Oh My Pi) as a STABLE, detected client surface with full
four-layer integration: MCP server registration, durable instructions,
skill, and three direct-MCP tiered subagent profiles (Scout/Verify/
Auditor). This matches Claude Code's integration depth — omp subagents
have direct MCP access (confirmed via ablation testing), unlike
parent-handoff-only clients (Pochi, Cursor, Rovo, Augment).

Schema (agent_clients.h):
  New CBM_AGENT_CLIENT_OMP enum value, positioned before COUNT.

Profile (agent_clients.c):
  - stability: CBM_AGENT_STABLE (auto-detected, auto-installed)
  - capabilities: MCP | INSTRUCTIONS | SKILL | AGENT
    No HOOK — omp's context event is fire-once advisory only; the
    recommended upstream path is skill + agents + MCP, not TS hooks.
  - detection_command: "omp" (specific enough per existing clients)
  - marker: ~/.omp/agent (omp-managed config root)
  - resolve_path: ~/.omp/agent/mcp.json
  - json_client: true (uses standard mcpServers JSON edit path)
  - agent_json_canonical: added to type:"stdio" branch alongside
    GitLab Duo and Visual Studio (omp's schema expects this field)

Dialect (agent_profiles.h/.c):
  New CBM_GRAPH_DIALECT_OMP, direct-capable (not in handoff-only list).
  - tool prefix: "mcp__codebase_memory_mcp_" (single underscore,
    hyphens→underscores — verified at runtime via omp device inventory)
  - render: YAML frontmatter with tools (read/grep/glob + tier-specific
    MCP tool allowlist), read-summarize: false (exact source verification
    for evidence tiers — omp's read returns structural summaries by
    default), autoloadSkills: [codebase-memory] (subagents start blank)

Install/uninstall (cli.c):
  - install_omp_durable_context: writes AGENTS.md + skill +
    3 tiered agent profiles to ~/.omp/agent/
  - uninstall_omp_durable_context: removes managed block + skill +
    tiered profiles
  - legacy_omp_verify_agent_content: migration source for prior installs
  - Wired into install_agent_client_registry and
    uninstall_agent_client_registry loops

Design decisions (documented for reviewers):
  1. No HOOK capability: omp's tool_call event can only block/pass (no
     augmentation channel like Claude's additionalContext). A context-
     event nudge was tested but is advisory-only and excluded from the
     upstream path to keep the PR minimal.
  2. read-summarize: false: omp's read tool returns structural summaries
     by default; Tier 2/3 require exact snippets for material claims.
  3. autoloadSkills: omp subagents start with no conversation history;
     without autoload the skill decision matrix is never loaded.
  4. Direct-capable (not handoff): ablation testing confirmed omp
     subagents can call mcp__* tools directly. write→xd:// dispatch
     does NOT work in subagents ("xd:// is not mounted in this session").

Test coverage:
  - test_agent_clients.c: omp in stable_ids (19 entries), capabilities
    array, json_schemas (type:stdio assertion), foreign_entries
  - test_agent_profiles.c: omp in direct_dialects array, direct-capable
    assertion, dedicated test verifying direct profile has
    mcp__codebase_memory_mcp_ prefix + read-summarize + autoloadSkills,
    and handoff profile excludes MCP tools
  - test_cli.c: required_agents updated to 44, all 43→44 / 37→38 string
    contracts updated across README/npm/index.html/llms.txt/main.c

Build: scripts/build.sh passes (exit 0).
Tests: scripts/test.sh passes (exit 0, all suites green).
Lint: scripts/lint.sh has one pre-existing error in pass_lsp_cross.h
(parameter name mismatch imp_keys/imp_vals vs imp_names/imp_qns),
unrelated to this change.

Signed-off-by: DavidHLP <lysf15520112973@163.com>
2026-08-21 20:07:37 +08:00
Alex Musichen 2ae84e7f73 test(pi): pin result wrap, error throw, and abort in generated adapter
Maintainer asked for in-tree coverage of {content, details} wrapping and
how tool errors are surfaced, plus a cheap abort-signal line. String-shape
only; no live Pi process.

Signed-off-by: Alex Musichen <alex.musichen@gmail.com>
2026-08-21 04:11:51 +02:00
Alex Musichen 109299f262 fix(pi): match ToolDefinition.execute arity on 0.84.2
Pi 0.84.2 calls execute(toolCallId, params, signal, onUpdate, ctx).
The generated (args, ctx) shape bound the call id as the MCP arguments.

Forward params and signal, pin the @earendil-works/pi-coding-agent
contract in the generated header, and lock the 5-arg form in tests.

Signed-off-by: Alex Musichen <alex.musichen@gmail.com>
2026-08-18 04:11:49 +02:00
Alex Musichen cf5eb61c20 fix(pi): return a valid AgentToolResult and request raw JSON
The generated execute forwarded the raw MCP JSON directly, but pi's
ToolDefinition.execute must return { content: [{ type: 'text', text }],
details } — a result without a content array crashes the TUI's
getTextOutput on result.content.filter(...).

Request raw JSON from the CLI ('--json') so the bridge parses the MCP
result instead of the human-readable text, then wrap it: pass the content
array through, throw on transport errors, and stringify anything else.
Adds coverage asserting the corrected execute shape and the --json flag.

Signed-off-by: Alex Musichen <alex.musichen@gmail.com>
2026-08-16 22:26:34 +02:00
Alex Musichen 263cf22b9c fix(pi): emit parameters and execute in the generated pi adapter
The generated Pi extension registered each MCP tool as { name, run }, but
Pi's ToolDefinition requires label, description, parameters, and execute.
Tools registered that way carried no parameter schema, so strict providers
such as xAI/Grok reject the request with a 422 'missing field parameters',
and the tools were uncallable because Pi invokes execute, never run.

Emit the full tool shape from the registry: label/description via new
accessors, the input_schema embedded directly as a JSON object literal, and
execute instead of run.

Signed-off-by: Alex Musichen <alex.musichen@gmail.com>
2026-08-16 16:41:36 +02:00
42e320a6cb docs(pi): name the loader mechanism behind the default-export requirement
Two contributors independently sent the same fix for #1550 while ours was
already in CI (#1553 by @musichen, who also reported the bug, and #1564 by
@henri-edh). Rather than close both as duplicates, this takes the part of
theirs that ours was missing.

@henri-edh's comment names the actual mechanism: Pi loads an extension via
jiti.import(path, { default: true }) and rejects anything that is not a
function, so a named-only export hands the loader the module namespace object
and the install fails with "Extension does not export a valid factory
function". Ours said "Pi calls the default export" — true, but it does not tell
the next reader why a named export cannot simply be added alongside, which is
exactly the change someone would try. Their wording is now ours.

The assertion is tightened the same way, from `export default function` to
`export default function (pi)`, so a default export with the wrong shape cannot
satisfy it.

Co-Authored-By: musichen <musichen@users.noreply.github.com>
Co-Authored-By: henri-edh <henri-edh@users.noreply.github.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-12 12:52:46 +02:00
Martin Vogel a154d18a2e fix(cli): stop breaking commands people already type (#1544, #1550)
Two regressions we shipped ourselves, both with the same shape: a change that
was correct in the abstract broke a concrete thing users had working.

**`update --ui` became a hard error (#1544).** v0.10.2 removed the ui/standard
chooser and deleted the flags that drove it, so a command in scripts, aliases
and muscle memory started failing with "unknown update option" where v0.10.1
had printed the install-script handoff. Retiring a CHOICE is fine; retiring the
WORDS is a break users get nothing for. Both flags are accepted again, do
nothing, and print one note explaining that every build now includes the UI.
A genuinely unknown flag is still rejected — the point is compatibility, not
silence. Reported by @ehendrix23.

**The generated Pi extension could not load (#1550).** Since #1392 the
extension has been generated from the tool registry, emitting
`export function register(pi)`. Pi loads an extension by calling its DEFAULT
export as a factory, so the file failed to load — and a Pi extension that fails
to load takes every `pi` command with it, `pi doctor` included, not just cbm's
tools. It now default-exports the factory. Reported by @musichen.

The existing generator test passed throughout: it asserted WHICH tools the file
lists, never that the file Pi loads is loadable. The new test pins the export
shape, which is the property that actually matters to the consumer.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-12 11:52:38 +02:00
jcandClaude Fable 5 63be29800d feat(cli): extend the OpenCode plugin to every documented context surface
The generated OpenCode adapter covered only the grep/glob graph lookup, so
OpenCode sessions missed the read-coverage and lifecycle context other
clients get through their hook configuration. Extend the plugin to the
surfaces OpenCode documents:

- unwrap hook-augment's additionalContext envelope so plain text, not raw
  JSON, lands in the tool output
- post-read index-coverage notes via tool.execute.after, mapping OpenCode's
  filePath argument to the file_path key the default dialect reads
- session-start tier routing injected once per sessionID on the first tool
  result, since OpenCode documents no context-output lifecycle hook
- post-compaction reinjection through the documented experimental
  session.compacting surface (output.context)

Every failure path resolves to the empty string: the adapter can degrade to
a no-op but never blocks a tool call. Pin the new surfaces in
test_agent_clients (31/31 pass; the #616 payload guard stays green) and
record the wider surface in the README client matrix.

Signed-off-by: jc <jcvi007@gmail.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 09:22:34 +02:00
3a5790b075 feat(install): install generated pi and OpenCode extensions
Wires the registry-driven generators into install, plan and uninstall.

pi gets ~/.pi/agent/extensions/cbmem.ts, its only route to the graph -- pi has
no MCP client. OpenCode gets ~/.config/opencode/plugins/cbm-augment.ts, which
adds no tools (it already reaches all of them over MCP) and supplies only the
automatic graph lookup before a grep/glob that other clients get from their own
hook configuration. OpenCode has none; its plugin system is the only extension
point, verified against their plugin documentation.

Both go in as a MARKED BLOCK rather than a whole-file write. Those directories
are auto-loaded, so a user may legitimately keep their own module there, and an
install routine that clobbers it is destroying user content. Uninstall removes
only our block and leaves the rest of the file, matching every other uninstall
path here.

A generator returning NULL is a hard error rather than a skip. A silently absent
extension is precisely the failure that left the earlier proposal a no-op for six
weeks, so it must surface as an install error instead of a quiet success.

Note for reviewers: the generated body deliberately carries NO ownership
markers. cbm_text_upsert_managed_block adds them itself and rejects content that
already contains them -- an existing guard test (test_cli.c:6018) caught that
integration mistake before CI did, which is the test doing exactly its job.

Reimplements the ideas from two community PRs, both closed as superseded by this
work, with credit to their authors:

- #534 (@Tensorboyalive) proposed the pi extension. It embedded 380 lines of
  TypeScript as C string literals and registered 7 of the 15 registry tools;
  this generates from the registry so the surface cannot drift, and ships no .ts
  in the repository. Their marker-delimited editing, their uninstall test
  asserting the markers are GONE, and their defensive stdout parsing (take the
  last line that parses, not the first) are all carried forward here.
- #616 (@ZeR020) proposed the OpenCode plugin. Its payload omitted
  hook_event_name, which hook-augment requires, so it produced zero bytes; and
  its path template rejected the double quote but not the backslash, so a
  Windows home like C:\Users\urs\bin produced an invalid unicode escape and the
  auto-loaded plugin failed to parse. Both are closed by construction here and
  pinned by tests. Their hook-point research is what made the OpenCode side
  possible at all.

Tests: install writes both extensions and the dry run does not; uninstall
removes our block while preserving a user's own content in the same file.

Co-Authored-By: Tensorboyalive <Tensorboyalive@users.noreply.github.com>
Co-Authored-By: ZeR020 <ZeR020@users.noreply.github.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-31 22:02:19 +02:00
Martin Vogel 2a353b2a33 feat(cli): generate client extension modules from the tool registry
pi has no MCP client, and OpenCode has no declarative hook configuration --
verified against OpenCode's own plugin documentation, which states hooks are
available only through JavaScript/TypeScript plugin modules. For those two
clients a module is the only extension point.

We do not want to ship such a module as a repository asset. #534 proposed one
embedding 380 lines of TypeScript as C string literals and registering 7 of the
15 registry tools; every tool added afterwards would have been silently missing
for that client. Hand-maintained copies of the tool list have already produced
defects here (#1361, and the smoke-invariants count).

So the module is GENERATED from the live registry instead. cbm_client_adapter_pi
walks cbm_mcp_tool_count()/cbm_mcp_tool_name() and registers every tool, which
makes drift structurally impossible rather than merely discouraged: adding a
tool to TOOLS[] adds it to every generated adapter with no second edit. Nothing
in the repository is a .ts file.

Both emitters wrap their output in ownership markers so a caller can rewrite its
own block and leave a user-authored file alone.

Two defects from the prior proposals are closed by construction:

- Path escaping. #616's template rejected the double quote but not the
  backslash, so a Windows home like C:\Users\urs\bin produced an invalid
  unicode escape and the whole auto-loaded plugin failed to parse, which is
  worse than an absent plugin. cbm_client_adapter_escape_js escapes backslash,
  quote, newline and CR, and fails closed rather than emitting a truncated
  literal; generation aborts if the path cannot be escaped.
- The silent no-op. #616's payload omitted hook_event_name, which hook-augment
  requires and without which it accepts nothing, so the plugin emitted zero
  bytes for six weeks. The generated payload carries it and a test pins it.

Recorded honestly: the OpenCode emitter hooks tool.execute.after, whose ability
to modify a tool's output is NOT part of OpenCode's documented plugin contract
(only tool.execute.before's argument mutation is). If they change it the
augmentation stops with no error. That risk is accepted deliberately and the
emitter says so in a comment, so a future reader does not have to rediscover it.

This commit adds the generator and its tests only; wiring into the install and
uninstall routines follows separately so the two are reviewable apart.

Tests: every registry tool appears in the pi module (revert-checked by
simulating the 7-of-15 subset, which fails); Windows/quote/newline escaping and
its truncation boundary; the OpenCode payload carries hook_event_name and
registers no tools; NULL/empty binary paths generate nothing.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-31 20:48:39 +02:00
Martin Vogel 90fab0844a test: model Windows agent hook contracts
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-13 05:21:55 +02:00
Martin Vogel f0db224140 feat: add coverage-aware agent integrations
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-13 01:50:03 +02:00
Martin Vogel a3903caa0b feat(cli): expand agent integration coverage
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-13 01:50:02 +02:00