feat(dynamic-trim): rename trim-tool-result, add self-compact, drop list-tool-results
Align the openclaw side of the dynamic-* tool family with the new Plexum design (decision #31, 2026-06-04 revision): - trim-tool-result → dynamic-trim (same on-wire schema; same semantics) - Drop list-tool-results entirely. Agents find the opaque tool_call_id by reading their own prior assistant message's toolCall block id instead of querying a separate "directory" tool. This removes a workflow-step prerequisite and matches how Anthropic-shaped APIs surface tool_use ids to the model anyway. - On agent_end drain, ALSO self-compact the dynamic-trim's own tool_use.input: rewrite to {tool_call_id, _self_compacted: true}. Without this the bulky `replacement` text sits duplicated — once in the rewritten target tool_result, once in dynamic-trim's call input. Picks up the selfCallId from openclaw's execute(toolCallId, ...) first arg (was previously discarded as _id). Cross-runtime contract: tool name, input schema, return shape, and sentinel prefix ("[trimmed by self] ") match Plexum's dynamic-trim in internal/dynmem/trim.go + internal/persistence/trim.go. Sim e2e tested: dynamic-trim queues, agent_end drain rewrites both the target tool_result content AND the trim call's tool_use input. No takeover errors. trimmed_bytes positive on real workloads.
This commit is contained in:
@@ -9,8 +9,7 @@ import type { OpenClawPluginApi } from 'openclaw/plugin-sdk/core';
|
||||
|
||||
import { pcexec, pcexecSync } from './tools/pcexec.js';
|
||||
import {
|
||||
queueTrimToolResult,
|
||||
listToolResults,
|
||||
queueDynamicTrim,
|
||||
drainTrimQueueForSession,
|
||||
} from './tools/session-rewrite.js';
|
||||
import {
|
||||
@@ -192,76 +191,50 @@ function register(api: OpenClawPluginApi): void {
|
||||
} as any;
|
||||
});
|
||||
|
||||
// Register trim-tool-result — rewrite a past toolResult to free ctx tokens.
|
||||
// Register dynamic-trim — agent-driven rewrite of a past tool_result
|
||||
// block. On the next turn boundary (agent_end hook) the target tool
|
||||
// result's content[].text is shrunk AND this dynamic-trim call's own
|
||||
// tool_use input is also self-compacted to {tool_call_id, _self_compacted}
|
||||
// so the bulky replacement text doesn't sit duplicated.
|
||||
//
|
||||
// Cross-runtime alignment with Plexum's dynamic-* family — same tool
|
||||
// name, same input schema, same semantics; only the lifecycle hook name
|
||||
// differs (openclaw agent_end / Plexum block mutation point).
|
||||
api.registerTool((ctx) => {
|
||||
const agentDir = ctx.agentDir;
|
||||
const agentIdInner = ctx.agentId;
|
||||
const sessionId = ctx.sessionId;
|
||||
return {
|
||||
name: 'trim-tool-result',
|
||||
name: 'dynamic-trim',
|
||||
description:
|
||||
'Replace a past tool result in your own session with a shorter version (or empty sentinel). Use after extracting what you need from a noisy tool output to free ctx tokens. Irreversible — re-run the original tool if you mis-trim.',
|
||||
'Rewrite a past tool_result block in your own session to a shorter version (or sentinel). ' +
|
||||
'Use after extracting what you need from a noisy tool output to free ctx tokens for future turns. ' +
|
||||
'The tool_call_id is the OPAQUE id (looks like "call_function_abc123_1") of the prior tool_use — ' +
|
||||
'find it in your own prior assistant messages; topic/fact ids and small integers will NOT work here. ' +
|
||||
'Irreversible — re-run the original tool if you mis-trim. ' +
|
||||
'Side effect: this dynamic-trim call\'s own tool_use input is self-compacted on the same turn boundary.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
tool_call_id: {
|
||||
type: 'string',
|
||||
description: 'The id of the toolCall whose result you want to trim (from list-tool-results).',
|
||||
description: 'The opaque id of the target tool_use (you find this by reading your own prior assistant messages — the id appears on each toolCall block).',
|
||||
},
|
||||
replacement: {
|
||||
type: 'string',
|
||||
description: 'Optional condensed text to keep. Omit to fully elide the result.',
|
||||
description: 'Optional condensed text to keep in place of the original result. Omit / empty = full elide.',
|
||||
},
|
||||
},
|
||||
required: ['tool_call_id'],
|
||||
},
|
||||
async execute(_id: string, params: any) {
|
||||
const r = queueTrimToolResult(
|
||||
async execute(selfCallId: string, params: any) {
|
||||
const r = queueDynamicTrim(
|
||||
{ agentDir, agentId: agentIdInner, sessionId },
|
||||
{
|
||||
tool_call_id: String(params.tool_call_id ?? ''),
|
||||
replacement: params.replacement != null ? String(params.replacement) : undefined,
|
||||
},
|
||||
);
|
||||
return { content: [{ type: 'text', text: JSON.stringify(r) }] };
|
||||
},
|
||||
} as any;
|
||||
});
|
||||
|
||||
// Register list-tool-results — enumerate past toolResults to pick trim candidates.
|
||||
api.registerTool((ctx) => {
|
||||
const agentDir = ctx.agentDir;
|
||||
const agentIdInner = ctx.agentId;
|
||||
const sessionId = ctx.sessionId;
|
||||
return {
|
||||
name: 'list-tool-results',
|
||||
description:
|
||||
'List past toolResults in your own session ordered by size (largest first), with tool name, args summary, byte size, and turns-ago. Does NOT include the result content itself — use this to pick trim-tool-result targets without re-reading bulky outputs.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
min_bytes: { type: 'number', description: 'Only include results at least this many bytes.' },
|
||||
older_than_turns: {
|
||||
type: 'number',
|
||||
description: 'Only include results from at least this many assistant turns ago (default 0).',
|
||||
},
|
||||
include_trimmed: {
|
||||
type: 'boolean',
|
||||
description: 'Include already-trimmed results (default false).',
|
||||
},
|
||||
limit: { type: 'number', description: 'Max entries to return (default 50).' },
|
||||
},
|
||||
},
|
||||
async execute(_id: string, params: any) {
|
||||
const r = await listToolResults(
|
||||
{ agentDir, agentId: agentIdInner, sessionId },
|
||||
{
|
||||
min_bytes: typeof params.min_bytes === 'number' ? params.min_bytes : undefined,
|
||||
older_than_turns:
|
||||
typeof params.older_than_turns === 'number' ? params.older_than_turns : undefined,
|
||||
include_trimmed: params.include_trimmed === true,
|
||||
limit: typeof params.limit === 'number' ? params.limit : undefined,
|
||||
},
|
||||
selfCallId,
|
||||
);
|
||||
return { content: [{ type: 'text', text: JSON.stringify(r) }] };
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user