@a1hvdy/cc-openclaw 0.29.0 → 0.30.0

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.
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Content-addressed cache-parity decision (v0.30.0).
3
+ *
4
+ * Problem (diagnosed 2026-05-23 from the live sysprompt-cost telemetry):
5
+ * cc-openclaw's cache-parity registry is keyed by sessionKey. The cache HIT
6
+ * path strips the role:system messages so the Claude CLI subprocess reuses its
7
+ * already-cached `--append-system-prompt` block; a MISS inlines the full ~7K
8
+ * system prompt into the user message (uncached). On the live box, 70% of all
9
+ * cache misses were `session_unknown` — the FIRST turn of a session, where the
10
+ * registry has no entry yet. Telegram conversations are short (median 1 turn /
11
+ * session, 23 of 35 single-turn), so cold-start dominated: the hit rate stalled
12
+ * at ~75% vs the terminal CLI's ~95%. The dynamic-envelope churn we originally
13
+ * set out to fix was only ~7.5% of turns.
14
+ *
15
+ * Key observation: every Savvy session shares the *identical* system prefix
16
+ * (same SOUL/USER/AGENTS/TOOLS/MEMORY + harness ⇒ same sysHash). So a brand-new
17
+ * session whose sysHash was already seen for some *other* session is a
18
+ * known-good prefix — its `--append-system-prompt` will be injected at
19
+ * startSession from the registry entry the route patch writes this same turn,
20
+ * so it is SAFE to strip the redundant inline and ride the cached path on
21
+ * turn 1 instead of re-billing the full prompt.
22
+ *
23
+ * Safety: the "warm-hash hit" only applies when the session is NEW (not yet in
24
+ * the SessionManager) — that guarantees startSession runs and appends the
25
+ * prompt. An EXISTING session missing its registry entry (e.g. registry wiped
26
+ * mid-life) keeps the legacy inline path so the model never loses its system
27
+ * prompt. A `hash_mismatch` (entry exists, different hash = genuine mid-session
28
+ * churn) also stays on the inline path: the CLI's append still holds the OLD
29
+ * prompt, so the new one must be delivered in-band.
30
+ *
31
+ * Pure + side-effect-free so the decision is unit-testable independent of the
32
+ * EmbeddedServer route closure (matches the codebase's pure-helper pattern:
33
+ * isPersistedSessionFresh, shouldWriteThroughResumeId).
34
+ */
35
+ export type CacheParityAction = 'hit' | 'warm-hash-hit' | 'miss';
36
+ export interface CacheParityDecisionInput {
37
+ /** Registry entry for THIS sessionKey, if any. */
38
+ entry: {
39
+ hash: string;
40
+ } | undefined;
41
+ /** sha1 of the (stripped) system content for this turn. */
42
+ sysHash: string;
43
+ /** True if sysHash has been seen for ANY session this process (known-good prefix). */
44
+ knownHash: boolean;
45
+ /**
46
+ * True if the SessionManager has no live session for this key yet. A new
47
+ * session guarantees startSession runs and injects appendSystemPrompt from
48
+ * the registry, which is what makes stripping the inline safe.
49
+ */
50
+ sessionIsNew: boolean;
51
+ }
52
+ /**
53
+ * Decide how the route patch should treat the system prompt this turn.
54
+ *
55
+ * - 'hit' → registry entry matches this session: strip role:system,
56
+ * ride the already-cached append.
57
+ * - 'warm-hash-hit' → new session + known-good prefix: write the registry
58
+ * entry (so startSession appends it), strip role:system,
59
+ * ride the cached path. Closes the cold-start gap.
60
+ * - 'miss' → inline the system prompt into the user message (the safe
61
+ * legacy path): first-ever prefix, genuine churn, or an
62
+ * existing session that lost its registry entry.
63
+ */
64
+ export declare function decideCacheParityAction(input: CacheParityDecisionInput): CacheParityAction;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Content-addressed cache-parity decision (v0.30.0).
3
+ *
4
+ * Problem (diagnosed 2026-05-23 from the live sysprompt-cost telemetry):
5
+ * cc-openclaw's cache-parity registry is keyed by sessionKey. The cache HIT
6
+ * path strips the role:system messages so the Claude CLI subprocess reuses its
7
+ * already-cached `--append-system-prompt` block; a MISS inlines the full ~7K
8
+ * system prompt into the user message (uncached). On the live box, 70% of all
9
+ * cache misses were `session_unknown` — the FIRST turn of a session, where the
10
+ * registry has no entry yet. Telegram conversations are short (median 1 turn /
11
+ * session, 23 of 35 single-turn), so cold-start dominated: the hit rate stalled
12
+ * at ~75% vs the terminal CLI's ~95%. The dynamic-envelope churn we originally
13
+ * set out to fix was only ~7.5% of turns.
14
+ *
15
+ * Key observation: every Savvy session shares the *identical* system prefix
16
+ * (same SOUL/USER/AGENTS/TOOLS/MEMORY + harness ⇒ same sysHash). So a brand-new
17
+ * session whose sysHash was already seen for some *other* session is a
18
+ * known-good prefix — its `--append-system-prompt` will be injected at
19
+ * startSession from the registry entry the route patch writes this same turn,
20
+ * so it is SAFE to strip the redundant inline and ride the cached path on
21
+ * turn 1 instead of re-billing the full prompt.
22
+ *
23
+ * Safety: the "warm-hash hit" only applies when the session is NEW (not yet in
24
+ * the SessionManager) — that guarantees startSession runs and appends the
25
+ * prompt. An EXISTING session missing its registry entry (e.g. registry wiped
26
+ * mid-life) keeps the legacy inline path so the model never loses its system
27
+ * prompt. A `hash_mismatch` (entry exists, different hash = genuine mid-session
28
+ * churn) also stays on the inline path: the CLI's append still holds the OLD
29
+ * prompt, so the new one must be delivered in-band.
30
+ *
31
+ * Pure + side-effect-free so the decision is unit-testable independent of the
32
+ * EmbeddedServer route closure (matches the codebase's pure-helper pattern:
33
+ * isPersistedSessionFresh, shouldWriteThroughResumeId).
34
+ */
35
+ /**
36
+ * Decide how the route patch should treat the system prompt this turn.
37
+ *
38
+ * - 'hit' → registry entry matches this session: strip role:system,
39
+ * ride the already-cached append.
40
+ * - 'warm-hash-hit' → new session + known-good prefix: write the registry
41
+ * entry (so startSession appends it), strip role:system,
42
+ * ride the cached path. Closes the cold-start gap.
43
+ * - 'miss' → inline the system prompt into the user message (the safe
44
+ * legacy path): first-ever prefix, genuine churn, or an
45
+ * existing session that lost its registry entry.
46
+ */
47
+ export function decideCacheParityAction(input) {
48
+ const { entry, sysHash, knownHash, sessionIsNew } = input;
49
+ if (entry && entry.hash === sysHash)
50
+ return 'hit';
51
+ if (!entry && knownHash && sessionIsNew)
52
+ return 'warm-hash-hit';
53
+ return 'miss';
54
+ }
@@ -0,0 +1,7 @@
1
+ export * from './register-guard.js';
2
+ export { registerOnce } from './register-guard.js';
3
+ export { stripSysprompt, isStripEnabled, type StripOptions, type StripResult } from './sysprompt-strip.js';
4
+ export { isCacheParityEnabled, hashPrompt, recordAttachment, readRegistry, REGISTRY_PATH, type RegistryEntry, } from './cache-parity.js';
5
+ export { selectEngine, isCcOpenclawEnabled, captureSessionRoute, ACTIVE_FLAG_ENV, ROUTE_FLAG_ENV, type Engine, type SessionRoute, } from './config-service.js';
6
+ export { isTestMode, TEST_MODE_ENV, _setTestModeForTests } from './test-mode.js';
7
+ export { getAggressiveStripEnabled, getCacheParityEnabled, getLogLevel, isLogLevelDebug, } from './config.js';
@@ -0,0 +1,10 @@
1
+ export * from './register-guard.js';
2
+ export { registerOnce } from './register-guard.js';
3
+ export { stripSysprompt, isStripEnabled } from './sysprompt-strip.js';
4
+ export { isCacheParityEnabled, hashPrompt, recordAttachment, readRegistry, REGISTRY_PATH, } from './cache-parity.js';
5
+ // Engine routing — originally `./route-flag.js`; collapsed into
6
+ // `./config-service.js` at Cluster A step 8. Same API, same semantics,
7
+ // single source of truth.
8
+ export { selectEngine, isCcOpenclawEnabled, captureSessionRoute, ACTIVE_FLAG_ENV, ROUTE_FLAG_ENV, } from './config-service.js';
9
+ export { isTestMode, TEST_MODE_ENV, _setTestModeForTests } from './test-mode.js';
10
+ export { getAggressiveStripEnabled, getCacheParityEnabled, getLogLevel, isLogLevelDebug, } from './config.js';
@@ -27,7 +27,7 @@
27
27
  * break the single `jq` pipeline. The `event` field makes filtering trivial.
28
28
  */
29
29
  type PerfEventName = 'cache_check' | 'first_byte' | 'turn_end';
30
- type CacheCheckCause = 'hit' | 'registry_empty' | 'hash_mismatch' | 'session_unknown' | 'disabled';
30
+ type CacheCheckCause = 'hit' | 'warm_hash' | 'registry_empty' | 'hash_mismatch' | 'session_unknown' | 'disabled';
31
31
  interface PerfEventBase {
32
32
  event: PerfEventName;
33
33
  sessionKey?: string;
@@ -37,6 +37,7 @@ import { defaultRegisterGuard } from '../lib/register-guard.js';
37
37
  import { isTestMode } from '../lib/test-mode.js';
38
38
  import { writePerfEvent } from '../observability/perf-telemetry.js';
39
39
  import { collapseSkillList } from '../lib/perf/skill-list-collapse.js';
40
+ import { decideCacheParityAction } from '../lib/cache-parity-decide.js';
40
41
  import { isCacheParityTrackB, isTokenTelemetryEnabled, isSyspromptDumpEnabled, getMaxConcurrentSessions, getSessionTtlMinutes, ensureUxBridgeAllSessionsDefault, } from '../lib/config.js';
41
42
  import { VENDOR_FILES } from '../lib/vendor-paths.js';
42
43
  import { OpenClawConfigSchema, findMainAgent, getAgentPrimaryModel, getDefaultsPrimaryModel, isClaudeRoutedModel, } from '../types/upstream.js';
@@ -99,6 +100,7 @@ const METRICS = {
99
100
  systemPromptInlined: 0,
100
101
  uxMetaSeeded: 0,
101
102
  cacheParityHits: 0,
103
+ cacheParityWarmHashHits: 0,
102
104
  cacheParityMisses: 0,
103
105
  cacheParityRegistryWrites: 0,
104
106
  cacheParityAppendInjections: 0,
@@ -143,6 +145,24 @@ function _setSystemInlineCache(key, val) {
143
145
  }
144
146
  _systemInlineCache.set(key, val);
145
147
  }
148
+ // ── Known sysprompt-hash set (v0.30.0 — content-addressed cache parity) ──────
149
+ // Cross-session record of every sysHash written to the cache-parity registry
150
+ // this process. Lets a brand-new session recognise the shared Savvy system
151
+ // prefix and ride the cached append path on turn 1 instead of inlining the full
152
+ // ~7K prompt — the dominant cold-start miss (see lib/cache-parity-decide.ts).
153
+ // Bounded FIFO: distinct prefixes are few (one per build), 64 is ample headroom.
154
+ const _knownSysHashes = new Set();
155
+ const KNOWN_SYS_HASH_MAX = 64;
156
+ function _rememberSysHash(hash) {
157
+ if (_knownSysHashes.has(hash))
158
+ return;
159
+ if (_knownSysHashes.size >= KNOWN_SYS_HASH_MAX) {
160
+ const oldest = _knownSysHashes.values().next().value;
161
+ if (oldest !== undefined)
162
+ _knownSysHashes.delete(oldest);
163
+ }
164
+ _knownSysHashes.add(hash);
165
+ }
146
166
  // ── Tool dump hash guard (v0.6.0 — per-session-key cache + fast-skip) ──
147
167
  // Pre-v0.6.0: a single global `_lastToolDumpHash` thrashed when multiple
148
168
  // sessions had different tool sets. JSON.stringify + SHA1 ran on EVERY
@@ -655,11 +675,30 @@ function applyRoutePatch(EmbeddedServer) {
655
675
  try {
656
676
  const reg = _readCacheParityRegistry();
657
677
  const entry = reg[sessionKey];
658
- if (entry && entry.hash === sysHash) {
678
+ // sessionIsNew: no live session in the manager ⇒ startSession will
679
+ // run on this request and inject appendSystemPrompt from the
680
+ // registry entry we write below. That guarantee is what makes the
681
+ // warm-hash strip safe (see lib/cache-parity-decide.ts). Default
682
+ // to false on any access failure — conservative: an unproven
683
+ // append guarantee falls back to the safe inline path.
684
+ let sessionIsNew = false;
685
+ try {
686
+ const mgr = this.manager;
687
+ sessionIsNew = !(mgr?.sessions?.has?.('openai-' + sessionKey) ?? false);
688
+ }
689
+ catch { /* keep sessionIsNew=false (inline fallback) */ }
690
+ const action = decideCacheParityAction({
691
+ entry: entry ? { hash: entry.hash } : undefined,
692
+ sysHash,
693
+ knownHash: _knownSysHashes.has(sysHash),
694
+ sessionIsNew,
695
+ });
696
+ if (action === 'hit') {
659
697
  body.messages = messages.filter(m => m?.role !== 'system');
660
698
  METRICS.cacheParityHits++;
661
699
  METRICS.systemPromptInlined++;
662
700
  cacheParityHandled = true;
701
+ _rememberSysHash(sysHash);
663
702
  writePerfEvent({
664
703
  event: 'cache_check',
665
704
  sessionKey,
@@ -668,8 +707,29 @@ function applyRoutePatch(EmbeddedServer) {
668
707
  sysHash,
669
708
  });
670
709
  }
710
+ else if (action === 'warm-hash-hit') {
711
+ // Cold-start win: new session + known-good Savvy prefix. Write
712
+ // the entry so startSession appends it, strip the redundant
713
+ // inline, ride the cached path on turn 1.
714
+ _writeCacheParityEntry(sessionKey, sysHash, sysContent);
715
+ body.messages = messages.filter(m => m?.role !== 'system');
716
+ METRICS.cacheParityWarmHashHits++;
717
+ METRICS.cacheParityRegistryWrites++;
718
+ METRICS.systemPromptInlined++;
719
+ cacheParityHandled = true;
720
+ _rememberSysHash(sysHash);
721
+ logger.info(`${TAG} cache-parity warm-hash hit: session=${sessionKey} hash=${sysHash} sysLen=${sysContent.length} (cold-start dedup; startSession will append)`);
722
+ writePerfEvent({
723
+ event: 'cache_check',
724
+ sessionKey,
725
+ outcome: 'hit',
726
+ cause: 'warm_hash',
727
+ sysHash,
728
+ });
729
+ }
671
730
  else {
672
731
  _writeCacheParityEntry(sessionKey, sysHash, sysContent);
732
+ _rememberSysHash(sysHash);
673
733
  METRICS.cacheParityMisses++;
674
734
  METRICS.cacheParityRegistryWrites++;
675
735
  logger.info(`${TAG} cache-parity miss: session=${sessionKey} oldHash=${entry?.hash || 'none'} newHash=${sysHash} sysLen=${sysContent.length} (registry updated; next session start will append)`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a1hvdy/cc-openclaw",
3
- "version": "0.29.0",
3
+ "version": "0.30.0",
4
4
  "description": "A1xAI's Anthropic CLI bridge plugin for OpenClaw",
5
5
  "author": "@a1cy",
6
6
  "license": "MIT",