Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion hindsight-integrations/openclaw/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ Optional settings in `~/.openclaw/openclaw.json` under `plugins.entries.hindsigh
| `embedPort` | `0` | Port for `hindsight-embed` server (`0` = auto-assign) |
| `embedVersion` | `"latest"` | hindsight-embed version |
| `embedPackagePath` | — | Local path to `hindsight-embed` package for development |
| `bankMission` | — | Agent identity/purpose stored on the memory bank. Helps the engine understand context for better fact extraction. Set once per bank — not a recall prompt. |
| `bankMission` | — | Mission stamped onto the bank's `reflect_mission` column on first use. **Only affects the `reflect` operation** — does not steer retain or recall. Leave unset (or empty) to manage missions out-of-band via `PATCH /banks/{id}`. |
| `retainMission` | — | Mission stamped onto the bank's `retain_mission` column on first use. Steers what gets extracted as facts during retain. Leave unset to use built-in extraction rules. |
| `observationsMission` | — | Mission stamped onto the bank's `observations_mission` column on first use. Controls what gets synthesised into observations during consolidation. |
| `llmProvider` | — | LLM provider for memory extraction (`openai`, `anthropic`, `gemini`, `groq`, `ollama`, `openai-codex`, `claude-code`). Required unless `hindsightApiUrl` is set. |
| `llmModel` | provider default | LLM model used with `llmProvider` |
| `llmApiKey` | — | API key for the LLM provider. **Sensitive** — set via `openclaw config set ... --ref-source env --ref-id OPENAI_API_KEY` to reference an env var (or `--ref-source file`/`exec` for mounted-secret/Vault sources). |
Expand Down
91 changes: 90 additions & 1 deletion hindsight-integrations/openclaw/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ import {
extractInlineRetainTags,
stripInlineRetainTags,
stripInlineTimestampPrefix,
getPluginConfig,
} from "./index.js";
import type { PluginConfig, MemoryResult } from "./types.js";
import type { PluginConfig, MemoryResult, MoltbotPluginAPI } from "./types.js";

// ---------------------------------------------------------------------------
// stripMemoryTags
Expand Down Expand Up @@ -1177,3 +1178,91 @@ describe("meetsMinimumVersion", () => {
expect(meetsMinimumVersion("", "0.5.0")).toBe(false);
});
});

// ---------------------------------------------------------------------------
// getPluginConfig — whitelist normalisation
// ---------------------------------------------------------------------------

function makeApi(rawConfig: Record<string, unknown>): MoltbotPluginAPI {
return {
config: { plugins: { entries: { "hindsight-openclaw": { config: rawConfig } } } },
registerService: () => undefined,
on: () => undefined,
logger: { info: () => undefined, warn: () => undefined, error: () => undefined },
} as unknown as MoltbotPluginAPI;
}

describe("getPluginConfig — retainQueue whitelist (#1443)", () => {
it("passes retainQueuePath through when set to a non-empty string", () => {
const cfg = getPluginConfig(makeApi({ retainQueuePath: "/custom/path/retain.jsonl" }));
expect(cfg.retainQueuePath).toBe("/custom/path/retain.jsonl");
});

it("drops retainQueuePath when blank or non-string", () => {
expect(getPluginConfig(makeApi({ retainQueuePath: " " })).retainQueuePath).toBeUndefined();
expect(getPluginConfig(makeApi({ retainQueuePath: 42 })).retainQueuePath).toBeUndefined();
expect(getPluginConfig(makeApi({})).retainQueuePath).toBeUndefined();
});

it("passes retainQueueMaxAgeMs through (including the sentinel -1)", () => {
expect(getPluginConfig(makeApi({ retainQueueMaxAgeMs: 86_400_000 })).retainQueueMaxAgeMs).toBe(
86_400_000
);
expect(getPluginConfig(makeApi({ retainQueueMaxAgeMs: -1 })).retainQueueMaxAgeMs).toBe(-1);
});

it("drops retainQueueMaxAgeMs when not a number", () => {
expect(
getPluginConfig(makeApi({ retainQueueMaxAgeMs: "86400000" })).retainQueueMaxAgeMs
).toBeUndefined();
});

it("passes retainQueueFlushIntervalMs through when positive", () => {
expect(
getPluginConfig(makeApi({ retainQueueFlushIntervalMs: 30_000 })).retainQueueFlushIntervalMs
).toBe(30_000);
});

it("drops retainQueueFlushIntervalMs when zero, negative, or non-number", () => {
expect(
getPluginConfig(makeApi({ retainQueueFlushIntervalMs: 0 })).retainQueueFlushIntervalMs
).toBeUndefined();
expect(
getPluginConfig(makeApi({ retainQueueFlushIntervalMs: -5 })).retainQueueFlushIntervalMs
).toBeUndefined();
});
});

describe("getPluginConfig — mission semantics (#1270, #1353)", () => {
it("does not substitute a default mission when bankMission is unset", () => {
const cfg = getPluginConfig(makeApi({}));
expect(cfg.bankMission).toBeUndefined();
});

it("treats empty-string bankMission as opt-out (no default fallback)", () => {
const cfg = getPluginConfig(makeApi({ bankMission: "" }));
expect(cfg.bankMission).toBeUndefined();
});

it("passes through an explicit bankMission verbatim", () => {
const cfg = getPluginConfig(makeApi({ bankMission: "You are Cooper, the orchestrator." }));
expect(cfg.bankMission).toBe("You are Cooper, the orchestrator.");
});

it("exposes retainMission and observationsMission when set", () => {
const cfg = getPluginConfig(
makeApi({
retainMission: "Extract architectural decisions only.",
observationsMission: "Synthesise stable preferences.",
})
);
expect(cfg.retainMission).toBe("Extract architectural decisions only.");
expect(cfg.observationsMission).toBe("Synthesise stable preferences.");
});

it("treats empty-string retainMission and observationsMission as unset", () => {
const cfg = getPluginConfig(makeApi({ retainMission: "", observationsMission: "" }));
expect(cfg.retainMission).toBeUndefined();
expect(cfg.observationsMission).toBeUndefined();
});
});
Loading
Loading