Skip to content
Open
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
7 changes: 7 additions & 0 deletions openclaw.plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@
"expansionProvider": {
"label": "Expansion Provider",
"help": "Provider override for lcm_expand_query sub-agent (e.g., 'anthropic')"
},
"customInstructions": {
"label": "Custom Instructions",
"help": "Natural language instructions injected into all summarization prompts (e.g., formatting rules, tone control)"
}
},
"configSchema": {
Expand Down Expand Up @@ -111,6 +115,9 @@
},
"expansionProvider": {
"type": "string"
},
"customInstructions": {
"type": "string"
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/db/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ export type LcmConfig = {
timezone: string;
/** When true, retroactively delete HEARTBEAT_OK turn cycles from LCM storage. */
pruneHeartbeatOk: boolean;
/** Custom instructions injected into all summarization prompts. */
customInstructions: string;
};

/** Safely coerce an unknown value to a finite number, or return undefined. */
Expand Down Expand Up @@ -185,5 +187,7 @@ export function resolveLcmConfig(
env.LCM_PRUNE_HEARTBEAT_OK !== undefined
? env.LCM_PRUNE_HEARTBEAT_OK === "true"
: toBool(pc.pruneHeartbeatOk) ?? false,
customInstructions:
env.LCM_CUSTOM_INSTRUCTIONS?.trim() ?? toStr(pc.customInstructions) ?? "",
};
}
2 changes: 1 addition & 1 deletion src/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1234,7 +1234,7 @@ export class LcmContextEngine implements ContextEngine {
const runtimeSummarizer = await createLcmSummarizeFromLegacyParams({
deps: this.deps,
legacyParams: lp,
customInstructions: params.customInstructions,
customInstructions: params.customInstructions || this.config.customInstructions || undefined,
});
if (runtimeSummarizer) {
return { summarize: runtimeSummarizer.fn, summaryModel: runtimeSummarizer.model };
Expand Down
58 changes: 58 additions & 0 deletions test/custom-instructions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { describe, it, expect } from "vitest";
import manifest from "../openclaw.plugin.json" with { type: "json" };
import { resolveLcmConfig } from "../src/db/config.js";

describe("customInstructions config", () => {
it("defaults customInstructions to empty string", () => {
const config = resolveLcmConfig({}, {});
expect(config.customInstructions).toBe("");
});

it("reads customInstructions from plugin config", () => {
const config = resolveLcmConfig({}, {
customInstructions: "Write as a neutral documenter. Use third person.",
});
expect(config.customInstructions).toBe("Write as a neutral documenter. Use third person.");
});

it("env var overrides plugin config for customInstructions", () => {
const config = resolveLcmConfig(
{ LCM_CUSTOM_INSTRUCTIONS: "env instructions" } as NodeJS.ProcessEnv,
{ customInstructions: "plugin instructions" },
);
expect(config.customInstructions).toBe("env instructions");
});

it("trims whitespace from env var customInstructions", () => {
const config = resolveLcmConfig(
{ LCM_CUSTOM_INSTRUCTIONS: " trimmed " } as NodeJS.ProcessEnv,
{},
);
expect(config.customInstructions).toBe("trimmed");
});

it("trims whitespace from plugin config customInstructions", () => {
const config = resolveLcmConfig({}, {
customInstructions: " trimmed ",
});
expect(config.customInstructions).toBe("trimmed");
});

it("falls through to default when plugin config value is empty string", () => {
const config = resolveLcmConfig({}, {
customInstructions: " ",
});
expect(config.customInstructions).toBe("");
});

it("ignores non-string plugin config values", () => {
const config = resolveLcmConfig({}, {
customInstructions: 42,
});
expect(config.customInstructions).toBe("");
});

it("ships a manifest with customInstructions in schema", () => {
expect(manifest.configSchema.properties.customInstructions).toEqual({ type: "string" });
});
});