fix(opencode): avoid duplicate home plugin initialization

This commit is contained in:
Huanyi Xie committed 2026-10-06 19:41:18 +03:00
1 parent ef648e0189
commit 990e45e177
4 files changed
+25 -9

No files matched your search

+5
View File
@@ -241,3 +241,8 @@ selection, and its subagents inherit the invoking primary agent's model.
## License
MIT
Home-target installations keep `plugins/index.ts` inert even with hooks enabled.
OpenCode automatically loads `plugins/ecc-hooks.ts`; loading the barrel as well
would initialize every ECC hook twice. The source and published package barrel
remain available for module imports.
+12 -7
View File
@@ -174,10 +174,6 @@ function withoutOpenCodeHookActivation(operation) {
};
}
function transformOpenCodeHookActivationOperations(operations) {
return (Array.isArray(operations) ? operations : []).map(withoutOpenCodeHookActivation);
}
function planSelectsHookRuntime(plan = {}) {
return (
Array.isArray(plan.selectedModuleIds)
@@ -189,17 +185,26 @@ function planSelectsHookRuntime(plan = {}) {
}
function disableUnselectedOpenCodeHooks(plan) {
if (plan.target !== 'opencode' || planSelectsHookRuntime(plan)) {
if (plan.target !== 'opencode') {
return plan;
}
const runtimeSelected = planSelectsHookRuntime(plan);
const transformOperations = operations => (Array.isArray(operations) ? operations : []).map(operation => {
// The source barrel is needed by the published package, but OpenCode scans
// plugins/index.ts and plugins/ecc-hooks.ts independently in home installs.
if (!runtimeSelected || normalizeOperationPath(operation.sourceRelativePath) === '.opencode/plugins/index.ts') {
return withoutOpenCodeHookActivation(operation);
}
return operation;
});
return {
...plan,
operations: transformOpenCodeHookActivationOperations(plan.operations),
operations: transformOperations(plan.operations),
statePreview: plan.statePreview
? {
...plan.statePreview,
operations: transformOpenCodeHookActivationOperations(plan.statePreview.operations),
operations: transformOperations(plan.statePreview.operations),
}
: plan.statePreview,
};
+3 -1
View File
@@ -148,7 +148,9 @@ function runTests() {
const pending = withHookConsent(selected, null);
assert.throws(() => assertHookConsentReady(pending), /automatic hook runtime/);
const enabled = withHookConsent(selected, 'enabled');
assert.ok(enabled.operations.every(operation => operation.contentTransform === undefined));
for (const operation of [...enabled.operations, ...enabled.statePreview.operations]) {
assert.strictEqual(operation.contentTransform, operation.sourceRelativePath.endsWith('/index.ts') ? 'opencode-disable-plugin-entrypoint' : undefined);
}
assert.doesNotThrow(() => assertHookConsentReady(enabled));
const declined = withHookConsent(selected, 'declined');
assert.strictEqual(declined.operations.length, 2);
+5 -1
View File
@@ -990,9 +990,13 @@ function runTests() {
for (const name of OPENCODE_ENTRYPOINTS) {
assert.strictEqual(
fs.readFileSync(path.join(homeDir, '.config', 'opencode', 'plugins', name), 'utf8'),
fs.readFileSync(path.join(REPO_ROOT, '.opencode', 'plugins', name), 'utf8')
name === 'index.ts' ? INERT_OPENCODE_PLUGIN : fs.readFileSync(path.join(REPO_ROOT, '.opencode', 'plugins', name), 'utf8')
);
}
assert.strictEqual(
enabledPlan.statePreview.operations.find(operation => operation.sourceRelativePath.split(path.sep).join('/') === '.opencode/plugins/index.ts').contentTransform,
'opencode-disable-plugin-entrypoint'
);
applyInstallPlanDirect(declinedPlan);
const declinedState = JSON.parse(fs.readFileSync(declinedPlan.installStatePath, 'utf8'));
assert.strictEqual(declinedState.request.hookConsent, 'declined');