feat(kbblock,fade): TS fade port + scaffold renderFaded/tick (not wired in v1)
Phase 4b openclaw side. Provides fade primitives matching Plexum-sdk-
go/fade so renderFaded / tick are API-parity available, but stops
short of activating fade in the live before_prompt_build hook since
openclaw plugin SDK ctx doesn't currently expose currentTurn to
plugins. Plexum side fully wires fade per turn.
plugin/tools/fade.ts (new): TS port of the fade algorithm. Maskable
rune set matches Plexum source. PRNG is Mulberry32 instead of Go's
math/rand — documented in the file: cross-runtime mask patterns
DIFFER, but each runtime fades its own session-local kb-block
independently so no comparison is expected.
plugin/tools/kbblock.ts: Entry gains seed (random u31 at add());
render() unchanged behaviour, plus new renderFaded(currentTurn,
params) + tick(currentTurn, params) for future wiring when
openclaw exposes turn signal to plugin hooks.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,12 +9,16 @@
|
||||
// <OPENCLAW_PATH>/agents/<agentId>/sessions/<sessionId>/plugins/
|
||||
// harbor-forge/kb-block.json
|
||||
//
|
||||
// Fade NOT applied in v1 (§9 #3 placeholder; defer until prod data).
|
||||
// Cross-runtime aligned with Plexum kbblock.
|
||||
// Fade algorithm + types are implemented (see fade.ts + renderFaded /
|
||||
// tick methods), but the openclaw `before_prompt_build` hook ctx
|
||||
// doesn't currently expose currentTurn to plugins. Plain render() (no
|
||||
// fade) is used in the hook for v1; renderFaded scaffolding stays for
|
||||
// when turn signal lands. Plexum side fully wires fade per turn.
|
||||
|
||||
import * as fs from 'node:fs';
|
||||
import * as os from 'node:os';
|
||||
import * as path from 'node:path';
|
||||
import { fade, shouldDrop, type FadeParams } from './fade.js';
|
||||
|
||||
export interface Entry {
|
||||
id: number; // HF backend DB primary key
|
||||
@@ -24,6 +28,7 @@ export interface Entry {
|
||||
insert_seq: number;
|
||||
added_at_turn: number;
|
||||
last_refresh_at_turn: number;
|
||||
seed: number; // PRNG seed for fade — generated at add() time
|
||||
}
|
||||
|
||||
interface BlockShape {
|
||||
@@ -92,6 +97,7 @@ export class Block {
|
||||
insert_seq: this.next_seq,
|
||||
added_at_turn: at_turn,
|
||||
last_refresh_at_turn: at_turn,
|
||||
seed: Math.floor(Math.random() * 0x7FFFFFFF),
|
||||
};
|
||||
this.next_seq += 1;
|
||||
this.entries.push(e);
|
||||
@@ -135,9 +141,23 @@ export class Block {
|
||||
/**
|
||||
* Render the <kb-block> inner body — flat sequence of
|
||||
* <kb-fact id=N kb=<code> source=topic:<slug>>content</kb-fact>
|
||||
* ordered by insert_seq (§9 #4). Empty block → "".
|
||||
* ordered by insert_seq (§9 #4). Empty block → "". No fade applied.
|
||||
*/
|
||||
render(): string {
|
||||
return this.renderInner(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* RenderFaded mirrors render() but applies fade() per entry given
|
||||
* currentTurn + params. Available for callers that have turn signal
|
||||
* (Plexum side does; openclaw `before_prompt_build` hook doesn't
|
||||
* currently — uses plain render()).
|
||||
*/
|
||||
renderFaded(currentTurn: number, params: FadeParams): string {
|
||||
return this.renderInner((e) => fade(e.content, e.seed, currentTurn - e.last_refresh_at_turn, params).rendered);
|
||||
}
|
||||
|
||||
private renderInner(transform: ((e: Entry) => string) | null): string {
|
||||
if (this.entries.length === 0) return '';
|
||||
const ordered = [...this.entries].sort((a, b) => a.insert_seq - b.insert_seq);
|
||||
const lines: string[] = [];
|
||||
@@ -147,9 +167,31 @@ export class Block {
|
||||
if (e.source_topic) head += ` source=topic:${e.source_topic}`;
|
||||
head += '>';
|
||||
lines.push(head);
|
||||
lines.push(e.content);
|
||||
lines.push(transform ? transform(e) : e.content);
|
||||
lines.push('</kb-fact>');
|
||||
});
|
||||
return lines.join('\n') + '\n';
|
||||
}
|
||||
|
||||
/**
|
||||
* Tick applies fade tick — drops entries whose underscore ratio
|
||||
* crossed the m% threshold. Returns IDs dropped. Caller must
|
||||
* save() after.
|
||||
*/
|
||||
tick(currentTurn: number, params: FadeParams): number[] {
|
||||
if (this.entries.length === 0) return [];
|
||||
const dropped: number[] = [];
|
||||
const kept: Entry[] = [];
|
||||
for (const e of this.entries) {
|
||||
const d = currentTurn - e.last_refresh_at_turn;
|
||||
const r = fade(e.content, e.seed, d, params);
|
||||
if (shouldDrop(r, params)) {
|
||||
dropped.push(e.id);
|
||||
} else {
|
||||
kept.push(e);
|
||||
}
|
||||
}
|
||||
this.entries = kept;
|
||||
return dropped;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user