@martian-engineering/lossless-claw 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.ts +43 -15
- package/package.json +1 -1
- package/src/db/config.ts +6 -0
- package/src/engine.ts +2 -2
package/index.ts
CHANGED
|
@@ -39,8 +39,29 @@ function normalizeAgentId(agentId: string | undefined): string {
|
|
|
39
39
|
return normalized.length > 0 ? normalized : "main";
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
type PluginEnvSnapshot = {
|
|
43
|
+
lcmSummaryModel: string;
|
|
44
|
+
lcmSummaryProvider: string;
|
|
45
|
+
openclawProvider: string;
|
|
46
|
+
agentDir: string;
|
|
47
|
+
home: string;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
type ReadEnvFn = (key: string) => string | undefined;
|
|
51
|
+
|
|
52
|
+
/** Capture plugin env values once during initialization. */
|
|
53
|
+
function snapshotPluginEnv(env: NodeJS.ProcessEnv = process.env): PluginEnvSnapshot {
|
|
54
|
+
return {
|
|
55
|
+
lcmSummaryModel: env.LCM_SUMMARY_MODEL?.trim() ?? "",
|
|
56
|
+
lcmSummaryProvider: env.LCM_SUMMARY_PROVIDER?.trim() ?? "",
|
|
57
|
+
openclawProvider: env.OPENCLAW_PROVIDER?.trim() ?? "",
|
|
58
|
+
agentDir: env.OPENCLAW_AGENT_DIR?.trim() || env.PI_CODING_AGENT_DIR?.trim() || "",
|
|
59
|
+
home: env.HOME?.trim() ?? "",
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
42
63
|
/** Resolve common provider API keys from environment. */
|
|
43
|
-
function resolveApiKey(provider: string): string | undefined {
|
|
64
|
+
function resolveApiKey(provider: string, readEnv: ReadEnvFn): string | undefined {
|
|
44
65
|
const keyMap: Record<string, string[]> = {
|
|
45
66
|
openai: ["OPENAI_API_KEY"],
|
|
46
67
|
anthropic: ["ANTHROPIC_API_KEY"],
|
|
@@ -59,7 +80,7 @@ function resolveApiKey(provider: string): string | undefined {
|
|
|
59
80
|
keys.push(normalizedProviderEnv);
|
|
60
81
|
|
|
61
82
|
for (const key of keys) {
|
|
62
|
-
const value =
|
|
83
|
+
const value = readEnv(key)?.trim();
|
|
63
84
|
if (value) {
|
|
64
85
|
return value;
|
|
65
86
|
}
|
|
@@ -255,19 +276,19 @@ function mergeAuthProfileStores(stores: AuthProfileStore[]): AuthProfileStore |
|
|
|
255
276
|
}
|
|
256
277
|
|
|
257
278
|
/** Determine candidate auth store paths ordered by precedence. */
|
|
258
|
-
function resolveAuthStorePaths(agentDir?: string): string[] {
|
|
279
|
+
function resolveAuthStorePaths(params: { agentDir?: string; envSnapshot: PluginEnvSnapshot }): string[] {
|
|
259
280
|
const paths: string[] = [];
|
|
260
|
-
const directAgentDir = agentDir?.trim();
|
|
281
|
+
const directAgentDir = params.agentDir?.trim();
|
|
261
282
|
if (directAgentDir) {
|
|
262
283
|
paths.push(join(directAgentDir, "auth-profiles.json"));
|
|
263
284
|
}
|
|
264
285
|
|
|
265
|
-
const envAgentDir =
|
|
286
|
+
const envAgentDir = params.envSnapshot.agentDir;
|
|
266
287
|
if (envAgentDir) {
|
|
267
288
|
paths.push(join(envAgentDir, "auth-profiles.json"));
|
|
268
289
|
}
|
|
269
290
|
|
|
270
|
-
const home =
|
|
291
|
+
const home = params.envSnapshot.home;
|
|
271
292
|
if (home) {
|
|
272
293
|
paths.push(join(home, ".openclaw", "agents", "main", "agent", "auth-profiles.json"));
|
|
273
294
|
}
|
|
@@ -334,8 +355,12 @@ async function resolveApiKeyFromAuthProfiles(params: {
|
|
|
334
355
|
agentDir?: string;
|
|
335
356
|
runtimeConfig?: unknown;
|
|
336
357
|
piAiModule: PiAiModule;
|
|
358
|
+
envSnapshot: PluginEnvSnapshot;
|
|
337
359
|
}): Promise<string | undefined> {
|
|
338
|
-
const storesWithPaths = resolveAuthStorePaths(
|
|
360
|
+
const storesWithPaths = resolveAuthStorePaths({
|
|
361
|
+
agentDir: params.agentDir,
|
|
362
|
+
envSnapshot: params.envSnapshot,
|
|
363
|
+
})
|
|
339
364
|
.map((path) => {
|
|
340
365
|
try {
|
|
341
366
|
const parsed = parseAuthProfileStore(readFileSync(path, "utf8"));
|
|
@@ -526,7 +551,9 @@ function readLatestAssistantReply(messages: unknown[]): string | undefined {
|
|
|
526
551
|
|
|
527
552
|
/** Construct LCM dependencies from plugin API/runtime surfaces. */
|
|
528
553
|
function createLcmDependencies(api: OpenClawPluginApi): LcmDependencies {
|
|
529
|
-
const
|
|
554
|
+
const envSnapshot = snapshotPluginEnv();
|
|
555
|
+
const readEnv: ReadEnvFn = (key) => process.env[key];
|
|
556
|
+
const config = resolveLcmConfig();
|
|
530
557
|
|
|
531
558
|
return {
|
|
532
559
|
config,
|
|
@@ -602,7 +629,7 @@ function createLcmDependencies(api: OpenClawPluginApi): LcmDependencies {
|
|
|
602
629
|
maxTokens: 8_000,
|
|
603
630
|
};
|
|
604
631
|
|
|
605
|
-
let resolvedApiKey = apiKey?.trim() || resolveApiKey(providerId);
|
|
632
|
+
let resolvedApiKey = apiKey?.trim() || resolveApiKey(providerId, readEnv);
|
|
606
633
|
if (!resolvedApiKey && typeof mod.getEnvApiKey === "function") {
|
|
607
634
|
resolvedApiKey = mod.getEnvApiKey(providerId)?.trim();
|
|
608
635
|
}
|
|
@@ -613,6 +640,7 @@ function createLcmDependencies(api: OpenClawPluginApi): LcmDependencies {
|
|
|
613
640
|
agentDir,
|
|
614
641
|
runtimeConfig,
|
|
615
642
|
piAiModule: mod,
|
|
643
|
+
envSnapshot,
|
|
616
644
|
});
|
|
617
645
|
}
|
|
618
646
|
|
|
@@ -673,7 +701,7 @@ function createLcmDependencies(api: OpenClawPluginApi): LcmDependencies {
|
|
|
673
701
|
}
|
|
674
702
|
},
|
|
675
703
|
resolveModel: (modelRef, providerHint) => {
|
|
676
|
-
const raw = (modelRef ??
|
|
704
|
+
const raw = (modelRef ?? envSnapshot.lcmSummaryModel).trim();
|
|
677
705
|
if (!raw) {
|
|
678
706
|
throw new Error("No model configured for LCM summarization.");
|
|
679
707
|
}
|
|
@@ -688,15 +716,15 @@ function createLcmDependencies(api: OpenClawPluginApi): LcmDependencies {
|
|
|
688
716
|
|
|
689
717
|
const provider = (
|
|
690
718
|
providerHint?.trim() ||
|
|
691
|
-
|
|
692
|
-
|
|
719
|
+
envSnapshot.lcmSummaryProvider ||
|
|
720
|
+
envSnapshot.openclawProvider ||
|
|
693
721
|
"openai"
|
|
694
722
|
).trim();
|
|
695
723
|
return { provider, model: raw };
|
|
696
724
|
},
|
|
697
|
-
getApiKey: (provider) => resolveApiKey(provider),
|
|
725
|
+
getApiKey: (provider) => resolveApiKey(provider, readEnv),
|
|
698
726
|
requireApiKey: (provider) => {
|
|
699
|
-
const key = resolveApiKey(provider);
|
|
727
|
+
const key = resolveApiKey(provider, readEnv);
|
|
700
728
|
if (!key) {
|
|
701
729
|
throw new Error(`Missing API key for provider '${provider}'.`);
|
|
702
730
|
}
|
|
@@ -756,7 +784,7 @@ const lcmPlugin = {
|
|
|
756
784
|
? (value as Record<string, unknown>)
|
|
757
785
|
: {};
|
|
758
786
|
const enabled = typeof raw.enabled === "boolean" ? raw.enabled : undefined;
|
|
759
|
-
const config = resolveLcmConfig(
|
|
787
|
+
const config = resolveLcmConfig();
|
|
760
788
|
if (enabled !== undefined) {
|
|
761
789
|
config.enabled = enabled;
|
|
762
790
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@martian-engineering/lossless-claw",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Lossless Context Management plugin for OpenClaw — DAG-based conversation summarization with incremental compaction",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
package/src/db/config.ts
CHANGED
|
@@ -15,6 +15,10 @@ export type LcmConfig = {
|
|
|
15
15
|
condensedTargetTokens: number;
|
|
16
16
|
maxExpandTokens: number;
|
|
17
17
|
largeFileTokenThreshold: number;
|
|
18
|
+
/** Provider override for large-file text summarization. */
|
|
19
|
+
largeFileSummaryProvider: string;
|
|
20
|
+
/** Model override for large-file text summarization. */
|
|
21
|
+
largeFileSummaryModel: string;
|
|
18
22
|
autocompactDisabled: boolean;
|
|
19
23
|
/** IANA timezone for timestamps in summaries (from TZ env or system default) */
|
|
20
24
|
timezone: string;
|
|
@@ -37,6 +41,8 @@ export function resolveLcmConfig(env: NodeJS.ProcessEnv = process.env): LcmConfi
|
|
|
37
41
|
condensedTargetTokens: parseInt(env.LCM_CONDENSED_TARGET_TOKENS ?? "2000", 10),
|
|
38
42
|
maxExpandTokens: parseInt(env.LCM_MAX_EXPAND_TOKENS ?? "4000", 10),
|
|
39
43
|
largeFileTokenThreshold: parseInt(env.LCM_LARGE_FILE_TOKEN_THRESHOLD ?? "25000", 10),
|
|
44
|
+
largeFileSummaryProvider: env.LCM_LARGE_FILE_SUMMARY_PROVIDER?.trim() ?? "",
|
|
45
|
+
largeFileSummaryModel: env.LCM_LARGE_FILE_SUMMARY_MODEL?.trim() ?? "",
|
|
40
46
|
autocompactDisabled: env.LCM_AUTOCOMPACT_DISABLED === "true",
|
|
41
47
|
timezone: env.TZ ?? Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
42
48
|
pruneHeartbeatOk: env.LCM_PRUNE_HEARTBEAT_OK === "true",
|
package/src/engine.ts
CHANGED
|
@@ -681,8 +681,8 @@ export class LcmContextEngine implements ContextEngine {
|
|
|
681
681
|
}
|
|
682
682
|
this.largeFileTextSummarizerResolved = true;
|
|
683
683
|
|
|
684
|
-
const provider =
|
|
685
|
-
const model =
|
|
684
|
+
const provider = this.deps.config.largeFileSummaryProvider;
|
|
685
|
+
const model = this.deps.config.largeFileSummaryModel;
|
|
686
686
|
if (!provider || !model) {
|
|
687
687
|
return undefined;
|
|
688
688
|
}
|