feat(plugin): gate registerTool via __padded.allowTool
Wires HF into the PaddedCell tools-cache filter (decision #37, openclaw side). Hooks into the existing api.registerTool wrap (originally added for ensureMcpContentShape) so each tool: 1. Registers name+description into PaddedCell's catalog 2. Returns null when the per-session cache doesn't include the name (the model doesn't see it that turn) 3. Has its execute() return coerced to MCP content shape (preserved from earlier) Fail-open stub installed if PaddedCell hasn't loaded yet. All 9 HF tools (status, telemetry, monitor_telemetry, calendar_*, restart_status) are gate-able by default — agents `dynamic-cache-tools` them before use. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -86,6 +86,33 @@ interface PluginAPI {
|
||||
* wrap every tool's execute return; doing it at the `registerTool` boundary
|
||||
* keeps each tool body unchanged.
|
||||
*/
|
||||
/**
|
||||
* Install a fail-open globalThis.__padded stub if PaddedCell hasn't loaded
|
||||
* yet (load order isn't guaranteed). PaddedCell's installGlobalApi drains
|
||||
* `_pendingCatalog` and replaces `allowTool` with the real check when it
|
||||
* starts. This means HF tools registered before PaddedCell are visible
|
||||
* to the agent until PaddedCell takes over, after which they fall under
|
||||
* the per-session cache gate (decision #37, openclaw side).
|
||||
*/
|
||||
function ensurePaddedStub(): void {
|
||||
const g = globalThis as unknown as {
|
||||
__padded?: {
|
||||
_pendingCatalog?: Array<{ name: string; description: string }>;
|
||||
registerCatalogEntry?: (n: string, d: string) => void;
|
||||
allowTool?: (n: string, c: unknown) => boolean;
|
||||
};
|
||||
};
|
||||
if (g.__padded) return;
|
||||
const buf: Array<{ name: string; description: string }> = [];
|
||||
g.__padded = {
|
||||
_pendingCatalog: buf,
|
||||
registerCatalogEntry(name: string, description: string): void {
|
||||
buf.push({ name, description });
|
||||
},
|
||||
allowTool: () => true,
|
||||
};
|
||||
}
|
||||
|
||||
function ensureMcpContentShape(result: unknown): { content: Array<{ type: 'text'; text: string }> } {
|
||||
if (
|
||||
result && typeof result === 'object' &&
|
||||
@@ -105,14 +132,35 @@ function register(api: PluginAPI): void {
|
||||
warn: (...args: any[]) => console.warn('[HarborForge]', ...args),
|
||||
};
|
||||
|
||||
// Wrap api.registerTool so every tool's execute() return is coerced into
|
||||
// the MCP `{ content: [...] }` shape openclaw expects. See
|
||||
// `ensureMcpContentShape` above.
|
||||
// PaddedCell tools-cache integration (decision #37, openclaw side).
|
||||
// Stub the global API early so the gate is consistent regardless of
|
||||
// plugin load order; PaddedCell will replace stub with the real impl
|
||||
// when it loads. fail-open until then.
|
||||
ensurePaddedStub();
|
||||
const seenForCatalog = new Set<string>();
|
||||
|
||||
// Wrap api.registerTool so every tool:
|
||||
// (a) registers its name+description into PaddedCell's catalog so
|
||||
// dynamic-list-tools / dynamic-search-tools surface it (#37)
|
||||
// (b) returns null when the per-session cache doesn't include the
|
||||
// name → the tool is hidden from the model that turn
|
||||
// (c) has its execute() return coerced into the MCP `{ content: [...] }`
|
||||
// shape openclaw expects (preserved from earlier).
|
||||
const _origRegisterTool = api.registerTool.bind(api);
|
||||
api.registerTool = (factory: (ctx: any) => any) => {
|
||||
_origRegisterTool((ctx: any) => {
|
||||
const def = factory(ctx);
|
||||
if (!def || typeof def.execute !== 'function') return def;
|
||||
const padded = (globalThis as any).__padded as
|
||||
| { allowTool?: (n: string, c: any) => boolean; registerCatalogEntry?: (n: string, d: string) => void }
|
||||
| undefined;
|
||||
if (def.name && padded?.registerCatalogEntry && !seenForCatalog.has(def.name)) {
|
||||
padded.registerCatalogEntry(def.name, def.description ?? '');
|
||||
seenForCatalog.add(def.name);
|
||||
}
|
||||
if (def.name && padded?.allowTool && !padded.allowTool(def.name, ctx)) {
|
||||
return null;
|
||||
}
|
||||
const origExecute = def.execute;
|
||||
return {
|
||||
...def,
|
||||
|
||||
Reference in New Issue
Block a user