Skip to content

Commit 69fb951

Browse files
authored
Merge pull request #2089 from AmirF194/fix/2077-opencode-v2-plugin-shape
fix(cli): emit the OpenCode plugin as a V2 default export
2 parents 2b34ebc + 11957ae commit 69fb951

2 files changed

Lines changed: 76 additions & 51 deletions

File tree

‎src/cli/client_adapter.c‎

Lines changed: 58 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -461,57 +461,64 @@ char *cbm_client_adapter_opencode(const char *binary_path) {
461461
" });\n"
462462
"}\n\n");
463463

464-
sb_append(&sb,
465-
"export const CodebaseMemory = async (ctx) => {\n"
466-
" const dir = ctx?.directory;\n"
467-
" const seen = new Set();\n"
468-
" const lifecycle = () =>\n"
469-
" augment({ hook_event_name: 'SessionStart', cwd: dir });\n"
470-
" return {\n"
471-
" 'tool.execute.after': async (input, output) => {\n"
472-
" if (typeof output?.output !== 'string') return;\n"
473-
" const pieces = [];\n"
474-
" const sid = input?.sessionID;\n"
475-
" if (typeof sid === 'string' && !seen.has(sid)) {\n"
476-
" seen.add(sid);\n"
477-
" pieces.push(await lifecycle());\n"
478-
" }\n"
479-
" const args = input?.args ?? {};\n"
480-
" const search =\n"
481-
" input?.tool === 'grep' ? 'Grep' : input?.tool === 'glob' ? 'Glob' : null;\n"
482-
" if (search) {\n"
483-
" pieces.push(await augment({\n"
484-
" hook_event_name: 'PreToolUse',\n"
485-
" tool_name: search,\n"
486-
" tool_input: args,\n"
487-
" cwd: dir,\n"
488-
" }));\n"
489-
" } else if (input?.tool === 'read') {\n"
490-
" const filePath = args.filePath ?? args.file_path ?? args.path;\n"
491-
" if (typeof filePath === 'string' && filePath) {\n"
492-
" pieces.push(await augment({\n"
493-
" hook_event_name: 'PostToolUse',\n"
494-
" tool_name: 'Read',\n"
495-
" tool_input: { file_path: filePath },\n"
496-
" cwd: dir,\n"
497-
" }));\n"
498-
" }\n"
499-
" }\n"
500-
" const extra = pieces.filter(Boolean).join('\\n');\n"
501-
" if (extra) {\n"
502-
" output.output += '\\n' + extra;\n"
503-
" }\n"
504-
" },\n"
505-
" // Documented (experimental) compaction surface: output.context is the\n"
506-
" // mutable array of context strings for the rebuilt session.\n"
507-
" 'experimental.session.compacting': async (_input, output) => {\n"
508-
" const note = await lifecycle();\n"
509-
" if (note && Array.isArray(output?.context)) {\n"
510-
" output.context.push(note);\n"
511-
" }\n"
512-
" },\n"
513-
" };\n"
514-
"};\n");
464+
sb_append(
465+
&sb, "export default {\n"
466+
" id: 'codebase-memory-augment',\n"
467+
" // V2 config loader: requires id + setup|effect; no tool domain yet\n"
468+
" // at this point, so there is nothing to register here.\n"
469+
" setup() {},\n"
470+
" // Server runtime: reads default.server and dispatches the hooks it returns.\n"
471+
" server: async (ctx) => {\n"
472+
" const dir = ctx?.directory;\n"
473+
" const seen = new Set();\n"
474+
" const lifecycle = () =>\n"
475+
" augment({ hook_event_name: 'SessionStart', cwd: dir });\n"
476+
" return {\n"
477+
" 'tool.execute.after': async (input, output) => {\n"
478+
" if (typeof output?.output !== 'string') return;\n"
479+
" const pieces = [];\n"
480+
" const sid = input?.sessionID;\n"
481+
" if (typeof sid === 'string' && !seen.has(sid)) {\n"
482+
" seen.add(sid);\n"
483+
" pieces.push(await lifecycle());\n"
484+
" }\n"
485+
" const args = input?.args ?? {};\n"
486+
" const search =\n"
487+
" input?.tool === 'grep' ? 'Grep' : input?.tool === 'glob' ? 'Glob' : null;\n"
488+
" if (search) {\n"
489+
" pieces.push(await augment({\n"
490+
" hook_event_name: 'PreToolUse',\n"
491+
" tool_name: search,\n"
492+
" tool_input: args,\n"
493+
" cwd: dir,\n"
494+
" }));\n"
495+
" } else if (input?.tool === 'read') {\n"
496+
" const filePath = args.filePath ?? args.file_path ?? args.path;\n"
497+
" if (typeof filePath === 'string' && filePath) {\n"
498+
" pieces.push(await augment({\n"
499+
" hook_event_name: 'PostToolUse',\n"
500+
" tool_name: 'Read',\n"
501+
" tool_input: { file_path: filePath },\n"
502+
" cwd: dir,\n"
503+
" }));\n"
504+
" }\n"
505+
" }\n"
506+
" const extra = pieces.filter(Boolean).join('\\n');\n"
507+
" if (extra) {\n"
508+
" output.output += '\\n' + extra;\n"
509+
" }\n"
510+
" },\n"
511+
" // Documented (experimental) compaction surface: output.context is the\n"
512+
" // mutable array of context strings for the rebuilt session.\n"
513+
" 'experimental.session.compacting': async (_input, output) => {\n"
514+
" const note = await lifecycle();\n"
515+
" if (note && Array.isArray(output?.context)) {\n"
516+
" output.context.push(note);\n"
517+
" }\n"
518+
" },\n"
519+
" };\n"
520+
" },\n"
521+
"};\n");
515522

516523
if (sb.failed) {
517524
free(sb.buf);

‎tests/test_agent_clients.c‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1321,6 +1321,23 @@ TEST(client_adapter_opencode_covers_lifecycle_read_and_compaction) {
13211321
PASS();
13221322
}
13231323

1324+
/* #2077: OpenCode's V2 loader only reads the default export and needs an
1325+
* id plus a setup()/effect() function; the old named export had neither. */
1326+
TEST(client_adapter_opencode_exports_the_v2_default_definition_issue2077) {
1327+
char *js = cbm_client_adapter_opencode("/usr/local/bin/codebase-memory-mcp");
1328+
ASSERT_NOT_NULL(js);
1329+
ASSERT_NOT_NULL(strstr(js, "export default {"));
1330+
ASSERT_NOT_NULL(strstr(js, "id: 'codebase-memory-augment'"));
1331+
/* Hooks live under server(), which the server runtime reads; setup()
1332+
* stays empty since the V2 config loader has no tool domain yet. */
1333+
ASSERT_NOT_NULL(strstr(js, "setup() {}"));
1334+
ASSERT_NOT_NULL(strstr(js, "server: async (ctx) => {"));
1335+
ASSERT_NULL(strstr(js, "async setup(ctx) {"));
1336+
ASSERT_NULL(strstr(js, "export const CodebaseMemory"));
1337+
free(js);
1338+
PASS();
1339+
}
1340+
13241341
/* Empty/NULL inputs must not produce a module at all. */
13251342
TEST(client_adapter_rejects_missing_binary_path) {
13261343
ASSERT_NULL(cbm_client_adapter_pi(NULL));
@@ -1367,5 +1384,6 @@ SUITE(agent_clients) {
13671384
RUN_TEST(client_adapter_escapes_windows_paths_and_quotes);
13681385
RUN_TEST(client_adapter_opencode_sends_the_required_hook_event);
13691386
RUN_TEST(client_adapter_opencode_covers_lifecycle_read_and_compaction);
1387+
RUN_TEST(client_adapter_opencode_exports_the_v2_default_definition_issue2077);
13701388
RUN_TEST(client_adapter_rejects_missing_binary_path);
13711389
}

0 commit comments

Comments
 (0)