feat: trim-tool-result + list-tool-results for agent-driven session pruning

Two new openclaw tools that let an agent reclaim ctx tokens consumed by
past tool results once it has extracted what it needs:

  list-tool-results — enumerates past toolResults in the agent's own
    session jsonl (size, tool name, turns-ago, args summary, already-
    trimmed flag); does NOT return content body
  trim-tool-result — replaces a past toolResult's content[].text with a
    short sentinel-tagged replacement, identified by tool_call_id

The actual file rewrite is deferred to the `agent_end` hook (drained
from an in-memory queue) because writing during tool execute() trips
openclaw's session-file fence (EmbeddedAttemptSessionTakeoverError) —
the fingerprint check around releaseForPrompt rejects third-party
writes. By agent_end the lock window is closed and the next turn's
fence baseline picks up our mutation cleanly.

Queue-time validation rejects bad tool_call_ids up-front so weak
models that confuse opaque call_function_*_N ids with topic/fact
numeric ids get a clear error instead of a silent skip at drain time.

install.mjs now auto-sets plugins.entries.padded-cell.hooks.
allowConversationAccess=true (required for the agent_end hook on
non-bundled plugins; without it the drain never fires and queued
trims rot in memory).

Sim-verified end-to-end: model dispatches trim, drain fires on
agent_end, next turn's list shows already_trimmed=true.
This commit is contained in:
h z
2026-06-02 07:23:02 +01:00
parent 787d88cd33
commit 209ab0d82e
4 changed files with 511 additions and 1 deletions

View File

@@ -363,6 +363,15 @@ async function configure() {
const existingProfile = getOpenclawConfig(`${cfgPath}.openclawProfilePath`, undefined);
if (existingProfile === undefined) setOpenclawConfig(`${cfgPath}.openclawProfilePath`, openclawPath);
// hooks.allowConversationAccess gates `agent_end` (and similar
// conversation-scoped hooks) for non-bundled plugins. trim-tool-result's
// deferred drain runs in agent_end — without this flag openclaw logs
// "typed hook agent_end blocked" and the drain never fires, leaving
// trim requests queued in memory forever.
const hooksPath = `${entryPath}.hooks`;
const existingHookAccess = getOpenclawConfig(`${hooksPath}.allowConversationAccess`, undefined);
if (existingHookAccess === undefined) setOpenclawConfig(`${hooksPath}.allowConversationAccess`, true);
logOk('Plugin entry configured (set missing defaults only)');
} catch (err) {
logWarn(`Config failed: ${err.message}`);