Skip to content

Commit d0a731f

Browse files
author
Tehan
committed
fix(plugin): move buildHiddenAgentConfig out of the entry module — opencode 1.17 invokes every entry export as a plugin factory
opencode 1.17 calls every exported function in a plugin's entry module (index.ts) as its own plugin factory: fn(ctx). The helper buildHiddenAgentConfig was exported from index.ts (introduced upstream in 29a49cb 'D19 resilience safe parts'), so opencode invoked it as buildHiddenAgentConfig(ctx) — passing the plugin context as `prompt` and undefined as `allowedTools` — which threw 'undefined is not an object (evaluating allowedTools)' during plugin load. Result: no hooks/tools registered, all ctx_* tools dead, empty MC runtime log, migration churn each boot. The entry module must export ONLY default (the plugin factory). Move the helper to a sibling module (hidden-agent-config.ts) where it can still be exported and unit-tested without being mis-invoked as a factory. index-refresh.test.ts now imports it from there. Verified: dist export keys = ['default']; full plugin suite green (the lone tui-config ordering flake is pre-existing on master); tsc + lint clean.
1 parent f3e681b commit d0a731f

3 files changed

Lines changed: 53 additions & 43 deletions

File tree

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { buildAllowOnlyPermission } from "./agents/permissions";
2+
3+
/**
4+
* Build a hidden-agent config with a deny-everything-by-default permission
5+
* baseline and a hard tool-iteration ceiling. User overrides may lower
6+
* `steps`/`maxSteps`, but cannot raise either above the built-in cap.
7+
*
8+
* Lives in its own module — NOT in the plugin entry (`index.ts`) — because
9+
* opencode 1.17 invokes EVERY exported function in a plugin's entry module as
10+
* its own plugin factory. Exporting this helper from `index.ts` made opencode
11+
* call it as `buildHiddenAgentConfig(ctx)`, passing the plugin context as
12+
* `prompt` and `undefined` as `allowedTools`, which crashed plugin load
13+
* ("undefined is not an object (evaluating 'allowedTools')"). The entry module
14+
* must export only `default`; helpers that need to be exported (e.g. for tests)
15+
* live in sibling modules like this one.
16+
*/
17+
export function buildHiddenAgentConfig(
18+
prompt: string,
19+
allowedTools: readonly string[],
20+
maxSteps: number,
21+
overrides?: Record<string, unknown>,
22+
) {
23+
const { permission: overridePermission, ...restOverrides } = (overrides ?? {}) as {
24+
permission?: Record<string, unknown>;
25+
[key: string]: unknown;
26+
};
27+
const basePermission = buildAllowOnlyPermission(allowedTools);
28+
return {
29+
prompt,
30+
// No builtin fallback chain: the user's `fallback_models` (if any) flow
31+
// through `restOverrides`. A hardcoded chain names providers the user may
32+
// not have, producing `Model not found` retry storms.
33+
...restOverrides,
34+
steps: clampHiddenAgentStepLimit(restOverrides.steps, maxSteps),
35+
maxSteps: clampHiddenAgentStepLimit(restOverrides.maxSteps, maxSteps),
36+
// Permission baseline goes after `restOverrides` so that accidental
37+
// `permission` keys in user overrides we DIDN'T explicitly destructure
38+
// can't bypass the deny. The explicit override (destructured above) is
39+
// then layered on top.
40+
permission: {
41+
...basePermission,
42+
...(overridePermission ?? {}),
43+
},
44+
mode: "subagent" as const,
45+
hidden: true,
46+
};
47+
}
48+
49+
function clampHiddenAgentStepLimit(value: unknown, cap: number): number {
50+
return typeof value === "number" && Number.isFinite(value) ? Math.min(value, cap) : cap;
51+
}

packages/plugin/src/index-refresh.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { describe, expect, test } from "bun:test";
22
import { readFileSync } from "node:fs";
33
import { join } from "node:path";
4-
import { buildHiddenAgentConfig } from "./index";
4+
import { buildHiddenAgentConfig } from "./hidden-agent-config";
55

66
describe("plugin model-limit cache warmup", () => {
77
test("warms model limits once at startup and does not schedule periodic refresh", () => {

packages/plugin/src/index.ts

Lines changed: 1 addition & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { DREAMER_AGENT } from "./agents/dreamer";
33
import { HISTORIAN_AGENT, HISTORIAN_EDITOR_AGENT } from "./agents/historian";
44
import {
55
applyDisallowedTools,
6-
buildAllowOnlyPermission,
76
DREAMER_ALLOWED_TOOLS,
87
HISTORIAN_ALLOWED_TOOLS,
98
SIDEKICK_ALLOWED_TOOLS,
@@ -23,6 +22,7 @@ import {
2322
} from "./features/magic-context/storage-db";
2423
import { recordToolDefinition } from "./features/magic-context/tool-definition-tokens";
2524
import { runDeferredV22Backfill } from "./features/magic-context/v22-deferred-backfill";
25+
import { buildHiddenAgentConfig } from "./hidden-agent-config";
2626
import { createAutoUpdateCheckerHook } from "./hooks/auto-update-checker";
2727
import {
2828
COMPARTMENT_AGENT_SYSTEM_PROMPT,
@@ -52,47 +52,6 @@ const HISTORIAN_MAX_STEPS = 40;
5252
const SIDEKICK_MAX_STEPS = 40;
5353
const DREAMER_MAX_STEPS = 150;
5454

55-
function clampHiddenAgentStepLimit(value: unknown, cap: number): number {
56-
return typeof value === "number" && Number.isFinite(value) ? Math.min(value, cap) : cap;
57-
}
58-
59-
/**
60-
* Build a hidden-agent config with a deny-everything-by-default permission
61-
* baseline and a hard tool-iteration ceiling. User overrides may lower
62-
* `steps`/`maxSteps`, but cannot raise either above the built-in cap.
63-
*/
64-
export function buildHiddenAgentConfig(
65-
prompt: string,
66-
allowedTools: readonly string[],
67-
maxSteps: number,
68-
overrides?: Record<string, unknown>,
69-
) {
70-
const { permission: overridePermission, ...restOverrides } = (overrides ?? {}) as {
71-
permission?: Record<string, unknown>;
72-
[key: string]: unknown;
73-
};
74-
const basePermission = buildAllowOnlyPermission(allowedTools);
75-
return {
76-
prompt,
77-
// No builtin fallback chain: the user's `fallback_models` (if any) flow
78-
// through `restOverrides`. A hardcoded chain names providers the user may
79-
// not have, producing `Model not found` retry storms.
80-
...restOverrides,
81-
steps: clampHiddenAgentStepLimit(restOverrides.steps, maxSteps),
82-
maxSteps: clampHiddenAgentStepLimit(restOverrides.maxSteps, maxSteps),
83-
// Permission baseline goes after `restOverrides` so that accidental
84-
// `permission` keys in user overrides we DIDN'T explicitly destructure
85-
// can't bypass the deny. The explicit override (destructured above) is
86-
// then layered on top.
87-
permission: {
88-
...basePermission,
89-
...(overridePermission ?? {}),
90-
},
91-
mode: "subagent" as const,
92-
hidden: true,
93-
};
94-
}
95-
9655
const plugin: Plugin = async (ctx) => {
9756
const pluginConfig = loadPluginConfig(ctx.directory);
9857
// Apply SQLite connection tuning before the first openDatabase() below.

0 commit comments

Comments
 (0)