@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 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 = process.env[key]?.trim();
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 = process.env.OPENCLAW_AGENT_DIR?.trim() || process.env.PI_CODING_AGENT_DIR?.trim();
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 = process.env.HOME?.trim();
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(params.agentDir)
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 config = resolveLcmConfig(process.env);
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 ?? process.env.LCM_SUMMARY_MODEL ?? "").trim();
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
- process.env.LCM_SUMMARY_PROVIDER ||
692
- process.env.OPENCLAW_PROVIDER ||
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(process.env);
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.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 = process.env.LCM_LARGE_FILE_SUMMARY_PROVIDER?.trim() ?? "";
685
- const model = process.env.LCM_LARGE_FILE_SUMMARY_MODEL?.trim() ?? "";
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
  }