@martian-engineering/lossless-claw 0.2.4 → 0.2.5

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.
Files changed (3) hide show
  1. package/index.ts +20 -1
  2. package/package.json +1 -1
  3. package/src/engine.ts +12 -7
package/index.ts CHANGED
@@ -43,6 +43,7 @@ type PluginEnvSnapshot = {
43
43
  lcmSummaryModel: string;
44
44
  lcmSummaryProvider: string;
45
45
  openclawProvider: string;
46
+ openclawDefaultModel: string;
46
47
  agentDir: string;
47
48
  home: string;
48
49
  };
@@ -62,11 +63,27 @@ function snapshotPluginEnv(env: NodeJS.ProcessEnv = process.env): PluginEnvSnaps
62
63
  lcmSummaryModel: env.LCM_SUMMARY_MODEL?.trim() ?? "",
63
64
  lcmSummaryProvider: env.LCM_SUMMARY_PROVIDER?.trim() ?? "",
64
65
  openclawProvider: env.OPENCLAW_PROVIDER?.trim() ?? "",
66
+ openclawDefaultModel: "",
65
67
  agentDir: env.OPENCLAW_AGENT_DIR?.trim() || env.PI_CODING_AGENT_DIR?.trim() || "",
66
68
  home: env.HOME?.trim() ?? "",
67
69
  };
68
70
  }
69
71
 
72
+ /** Read OpenClaw's configured default model from the validated runtime config. */
73
+ function readDefaultModelFromConfig(config: unknown): string {
74
+ if (!config || typeof config !== "object") {
75
+ return "";
76
+ }
77
+
78
+ const model = (config as { agents?: { defaults?: { model?: unknown } } }).agents?.defaults?.model;
79
+ if (typeof model === "string") {
80
+ return model.trim();
81
+ }
82
+
83
+ const primary = (model as { primary?: unknown } | undefined)?.primary;
84
+ return typeof primary === "string" ? primary.trim() : "";
85
+ }
86
+
70
87
  /** Resolve common provider API keys from environment. */
71
88
  function resolveApiKey(provider: string, readEnv: ReadEnvFn): string | undefined {
72
89
  const keyMap: Record<string, string[]> = {
@@ -596,6 +613,7 @@ function readLatestAssistantReply(messages: unknown[]): string | undefined {
596
613
  /** Construct LCM dependencies from plugin API/runtime surfaces. */
597
614
  function createLcmDependencies(api: OpenClawPluginApi): LcmDependencies {
598
615
  const envSnapshot = snapshotPluginEnv();
616
+ envSnapshot.openclawDefaultModel = readDefaultModelFromConfig(api.config);
599
617
  const readEnv: ReadEnvFn = (key) => process.env[key];
600
618
  const pluginConfig =
601
619
  api.pluginConfig && typeof api.pluginConfig === "object" && !Array.isArray(api.pluginConfig)
@@ -789,7 +807,8 @@ function createLcmDependencies(api: OpenClawPluginApi): LcmDependencies {
789
807
  }
790
808
  },
791
809
  resolveModel: (modelRef, providerHint) => {
792
- const raw = (modelRef ?? envSnapshot.lcmSummaryModel).trim();
810
+ const raw =
811
+ (modelRef?.trim() || envSnapshot.lcmSummaryModel || envSnapshot.openclawDefaultModel).trim();
793
812
  if (!raw) {
794
813
  throw new Error("No model configured for LCM summarization.");
795
814
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@martian-engineering/lossless-claw",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
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/engine.ts CHANGED
@@ -1509,6 +1509,10 @@ export class LcmContextEngine implements ContextEngine {
1509
1509
  observedTokens !== undefined
1510
1510
  ? await this.compaction.evaluate(conversationId, tokenBudget, observedTokens)
1511
1511
  : await this.compaction.evaluate(conversationId, tokenBudget);
1512
+ const targetTokens =
1513
+ params.compactionTarget === "threshold" ? decision.threshold : tokenBudget;
1514
+ const liveContextStillExceedsTarget =
1515
+ observedTokens !== undefined && observedTokens >= targetTokens;
1512
1516
 
1513
1517
  if (!forceCompaction && !decision.shouldCompact) {
1514
1518
  return {
@@ -1533,27 +1537,28 @@ export class LcmContextEngine implements ContextEngine {
1533
1537
  });
1534
1538
 
1535
1539
  return {
1536
- ok: true,
1540
+ ok: sweepResult.actionTaken || !liveContextStillExceedsTarget,
1537
1541
  compacted: sweepResult.actionTaken,
1538
1542
  reason: sweepResult.actionTaken
1539
1543
  ? "compacted"
1540
1544
  : manualCompactionRequested
1541
1545
  ? "nothing to compact"
1542
- : "already under target",
1546
+ : liveContextStillExceedsTarget
1547
+ ? "live context still exceeds target"
1548
+ : "already under target",
1543
1549
  result: {
1544
1550
  tokensBefore: decision.currentTokens,
1545
1551
  tokensAfter: sweepResult.tokensAfter,
1546
1552
  details: {
1547
1553
  rounds: sweepResult.actionTaken ? 1 : 0,
1548
- targetTokens:
1549
- params.compactionTarget === "threshold" ? decision.threshold : tokenBudget,
1554
+ targetTokens,
1550
1555
  },
1551
1556
  },
1552
1557
  };
1553
1558
  }
1554
1559
 
1555
1560
  // When forced, use the token budget as target
1556
- const targetTokens = forceCompaction
1561
+ const convergenceTargetTokens = forceCompaction
1557
1562
  ? tokenBudget
1558
1563
  : params.compactionTarget === "threshold"
1559
1564
  ? decision.threshold
@@ -1562,7 +1567,7 @@ export class LcmContextEngine implements ContextEngine {
1562
1567
  const compactResult = await this.compaction.compactUntilUnder({
1563
1568
  conversationId,
1564
1569
  tokenBudget,
1565
- targetTokens,
1570
+ targetTokens: convergenceTargetTokens,
1566
1571
  ...(observedTokens !== undefined ? { currentTokens: observedTokens } : {}),
1567
1572
  summarize,
1568
1573
  });
@@ -1581,7 +1586,7 @@ export class LcmContextEngine implements ContextEngine {
1581
1586
  tokensAfter: compactResult.finalTokens,
1582
1587
  details: {
1583
1588
  rounds: compactResult.rounds,
1584
- targetTokens,
1589
+ targetTokens: convergenceTargetTokens,
1585
1590
  },
1586
1591
  },
1587
1592
  };