@a1hvdy/cc-openclaw 0.21.2 → 0.23.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.
Files changed (70) hide show
  1. package/dist/src/channels/telegram/edit-cadence.d.ts +37 -0
  2. package/dist/src/channels/telegram/edit-cadence.js +50 -0
  3. package/dist/src/channels/telegram/edit-cadence.js.map +1 -0
  4. package/dist/src/channels/telegram/live-card.d.ts +23 -0
  5. package/dist/src/channels/telegram/live-card.js +95 -0
  6. package/dist/src/channels/telegram/live-card.js.map +1 -1
  7. package/dist/src/channels/telegram/speculative-bubble.d.ts +33 -0
  8. package/dist/src/channels/telegram/speculative-bubble.js +42 -0
  9. package/dist/src/channels/telegram/speculative-bubble.js.map +1 -0
  10. package/dist/src/channels/telegram/throttle-controller.d.ts +16 -0
  11. package/dist/src/channels/telegram/throttle-controller.js +54 -1
  12. package/dist/src/channels/telegram/throttle-controller.js.map +1 -1
  13. package/dist/src/channels/telegram/tool-tracker.d.ts +4 -1
  14. package/dist/src/channels/telegram/tool-tracker.js +18 -4
  15. package/dist/src/channels/telegram/tool-tracker.js.map +1 -1
  16. package/dist/src/command-router/cc-handler.js +32 -0
  17. package/dist/src/command-router/cc-handler.js.map +1 -1
  18. package/dist/src/command-router/launch-policy.js +14 -2
  19. package/dist/src/command-router/launch-policy.js.map +1 -1
  20. package/dist/src/index.js +57 -0
  21. package/dist/src/index.js.map +1 -1
  22. package/dist/src/lib/config-service.js +12 -0
  23. package/dist/src/lib/config-service.js.map +1 -1
  24. package/dist/src/lib/config.d.ts +36 -0
  25. package/dist/src/lib/config.js +97 -0
  26. package/dist/src/lib/config.js.map +1 -1
  27. package/dist/src/lib/http-agent.d.ts +47 -0
  28. package/dist/src/lib/http-agent.js +86 -0
  29. package/dist/src/lib/http-agent.js.map +1 -0
  30. package/dist/src/lib/markdown-to-mdv2.d.ts +53 -0
  31. package/dist/src/lib/markdown-to-mdv2.js +141 -0
  32. package/dist/src/lib/markdown-to-mdv2.js.map +1 -0
  33. package/dist/src/lib/perf/async-compact.d.ts +26 -0
  34. package/dist/src/lib/perf/async-compact.js +42 -0
  35. package/dist/src/lib/perf/async-compact.js.map +1 -0
  36. package/dist/src/lib/perf/direct-sdk.d.ts +26 -0
  37. package/dist/src/lib/perf/direct-sdk.js +58 -0
  38. package/dist/src/lib/perf/direct-sdk.js.map +1 -0
  39. package/dist/src/lib/perf/haiku-route.d.ts +19 -0
  40. package/dist/src/lib/perf/haiku-route.js +49 -0
  41. package/dist/src/lib/perf/haiku-route.js.map +1 -0
  42. package/dist/src/lib/perf/predictive-continuation.d.ts +18 -0
  43. package/dist/src/lib/perf/predictive-continuation.js +37 -0
  44. package/dist/src/lib/perf/predictive-continuation.js.map +1 -0
  45. package/dist/src/lib/perf/read-batch.d.ts +33 -0
  46. package/dist/src/lib/perf/read-batch.js +48 -0
  47. package/dist/src/lib/perf/read-batch.js.map +1 -0
  48. package/dist/src/lib/perf/resident-cli-pool.d.ts +39 -0
  49. package/dist/src/lib/perf/resident-cli-pool.js +60 -0
  50. package/dist/src/lib/perf/resident-cli-pool.js.map +1 -0
  51. package/dist/src/lib/perf/skill-list-collapse.d.ts +22 -0
  52. package/dist/src/lib/perf/skill-list-collapse.js +35 -0
  53. package/dist/src/lib/perf/skill-list-collapse.js.map +1 -0
  54. package/dist/src/lib/perf/typing-prefetch.d.ts +25 -0
  55. package/dist/src/lib/perf/typing-prefetch.js +54 -0
  56. package/dist/src/lib/perf/typing-prefetch.js.map +1 -0
  57. package/dist/src/observability/perf-telemetry.d.ts +66 -0
  58. package/dist/src/observability/perf-telemetry.js +79 -0
  59. package/dist/src/observability/perf-telemetry.js.map +1 -0
  60. package/dist/src/openai-compat/streaming-handler.js +35 -1
  61. package/dist/src/openai-compat/streaming-handler.js.map +1 -1
  62. package/dist/src/session-bootstrap/cwd-patch.js +37 -0
  63. package/dist/src/session-bootstrap/cwd-patch.js.map +1 -1
  64. package/dist/src/types/runtime-config.d.ts +36 -0
  65. package/dist/src/types/runtime-config.js +44 -0
  66. package/dist/src/types/runtime-config.js.map +1 -1
  67. package/package.json +1 -1
  68. package/dist/src/patches/sysprompt-strip.spec.d.ts +0 -33
  69. package/dist/src/patches/sysprompt-strip.spec.js +0 -53
  70. package/dist/src/patches/sysprompt-strip.spec.js.map +0 -1
@@ -0,0 +1,58 @@
1
+ /**
2
+ * M12 (perf overhaul idea #12) — direct claude-code SDK in-process.
3
+ *
4
+ * Bypasses the `claude` CLI subprocess entirely by calling the
5
+ * `@anthropic-ai/claude-code` SDK in the cc-openclaw Node process.
6
+ * Eliminates all subprocess overhead (Node bootstrap, bundle parse, auth
7
+ * handshake) at the cost of losing CLI session/checkpoint features and
8
+ * recoupling to upstream SDK API churn.
9
+ *
10
+ * Flag: CC_OPENCLAW_PERF_DIRECT_SDK (default OFF, HIGH RISK — ships LAST
11
+ * with the resident-CLI pool as the fallback).
12
+ *
13
+ * Probes the SDK availability without bringing it as a hard dependency:
14
+ * dynamic require with a swallowed ENOENT. The actual invocation is
15
+ * delegated to the registered handler — session-manager owns the call
16
+ * details (model, prompt, options) and decides whether the SDK supports
17
+ * the requested feature set for this turn.
18
+ */
19
+ import { createRequire } from 'node:module';
20
+ import { getPerfDirectSdkEnabled } from '../config.js';
21
+ const requireFromHere = createRequire(import.meta.url);
22
+ let cachedProbe = null;
23
+ export function probeDirectSdkAvailability() {
24
+ if (!getPerfDirectSdkEnabled()) {
25
+ return { enabled: false, available: false, reason: 'flag_off', version: null };
26
+ }
27
+ if (cachedProbe && cachedProbe.reason !== 'probe_error')
28
+ return cachedProbe;
29
+ try {
30
+ // Dynamic require path — never bring the SDK as a hard dependency.
31
+ const mod = requireFromHere('@anthropic-ai/claude-code');
32
+ if (!mod) {
33
+ cachedProbe = { enabled: true, available: false, reason: 'sdk_missing', version: null };
34
+ return cachedProbe;
35
+ }
36
+ cachedProbe = {
37
+ enabled: true,
38
+ available: true,
39
+ reason: 'sdk_loaded',
40
+ version: mod.VERSION ?? null,
41
+ };
42
+ return cachedProbe;
43
+ }
44
+ catch (err) {
45
+ const msg = err instanceof Error ? err.message : String(err);
46
+ if (msg.includes('Cannot find module')) {
47
+ cachedProbe = { enabled: true, available: false, reason: 'sdk_missing', version: null };
48
+ }
49
+ else {
50
+ cachedProbe = { enabled: true, available: false, reason: 'probe_error', version: null };
51
+ }
52
+ return cachedProbe;
53
+ }
54
+ }
55
+ export function _resetDirectSdkProbeForTests() {
56
+ cachedProbe = null;
57
+ }
58
+ //# sourceMappingURL=direct-sdk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"direct-sdk.js","sourceRoot":"","sources":["../../../../src/lib/perf/direct-sdk.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAEvD,MAAM,eAAe,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AASvD,IAAI,WAAW,GAAiC,IAAI,CAAC;AAErD,MAAM,UAAU,0BAA0B;IACxC,IAAI,CAAC,uBAAuB,EAAE,EAAE,CAAC;QAC/B,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IACjF,CAAC;IACD,IAAI,WAAW,IAAI,WAAW,CAAC,MAAM,KAAK,aAAa;QAAE,OAAO,WAAW,CAAC;IAC5E,IAAI,CAAC;QACH,mEAAmE;QACnE,MAAM,GAAG,GAAG,eAAe,CAAC,2BAA2B,CAAqC,CAAC;QAC7F,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,WAAW,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;YACxF,OAAO,WAAW,CAAC;QACrB,CAAC;QACD,WAAW,GAAG;YACZ,OAAO,EAAE,IAAI;YACb,SAAS,EAAE,IAAI;YACf,MAAM,EAAE,YAAY;YACpB,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,IAAI;SAC7B,CAAC;QACF,OAAO,WAAW,CAAC;IACrB,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,IAAI,GAAG,CAAC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,CAAC;YACvC,WAAW,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC1F,CAAC;aAAM,CAAC;YACN,WAAW,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC1F,CAAC;QACD,OAAO,WAAW,CAAC;IACrB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,4BAA4B;IAC1C,WAAW,GAAG,IAAI,CAAC;AACrB,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * M10 (perf overhaul idea #10) — Haiku 4.5 cheap-route classifier.
3
+ *
4
+ * Pattern-matches trivial queries that don't need Opus-level reasoning:
5
+ * status checks, simple acks, short factual lookups. When matched, the
6
+ * caller routes the request to Haiku via the OpenAI-compat fallback
7
+ * pipeline (NOT direct Anthropic API — A1 has no anthropic.com key, only
8
+ * Max 20x subscription). Sub-500ms; zero Max quota burn.
9
+ *
10
+ * Flag: CC_OPENCLAW_PERF_HAIKU_ROUTE (default OFF).
11
+ *
12
+ * Conservative by design: any uncertainty falls back to Opus. False positives
13
+ * are user-visible quality regressions; false negatives just miss a perf win.
14
+ */
15
+ export interface RouteDecision {
16
+ routeToHaiku: boolean;
17
+ reason: 'trivial_pattern' | 'too_long' | 'complex_token' | 'flag_off' | 'no_match';
18
+ }
19
+ export declare function classifyForHaikuRoute(prompt: string): RouteDecision;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * M10 (perf overhaul idea #10) — Haiku 4.5 cheap-route classifier.
3
+ *
4
+ * Pattern-matches trivial queries that don't need Opus-level reasoning:
5
+ * status checks, simple acks, short factual lookups. When matched, the
6
+ * caller routes the request to Haiku via the OpenAI-compat fallback
7
+ * pipeline (NOT direct Anthropic API — A1 has no anthropic.com key, only
8
+ * Max 20x subscription). Sub-500ms; zero Max quota burn.
9
+ *
10
+ * Flag: CC_OPENCLAW_PERF_HAIKU_ROUTE (default OFF).
11
+ *
12
+ * Conservative by design: any uncertainty falls back to Opus. False positives
13
+ * are user-visible quality regressions; false negatives just miss a perf win.
14
+ */
15
+ import { getPerfHaikuRouteEnabled } from '../config.js';
16
+ const TRIVIAL_PATTERNS = [
17
+ /^(status|ping|hi|hello|hey|yo|ack|ok|okay|thanks|thx|ty)\s*[.!?]?\s*$/i,
18
+ /^(what'?s\s+(?:the\s+)?(?:time|date|day))\s*\??\s*$/i,
19
+ /^(are\s+you\s+(?:there|alive|up|online))\s*\??\s*$/i,
20
+ /^(version|uptime|health)\s*\??\s*$/i,
21
+ ];
22
+ const COMPLEX_TOKENS = [
23
+ 'write', 'create', 'build', 'implement', 'fix', 'debug', 'analyze',
24
+ 'explain', 'refactor', 'review', 'design', 'plan', 'why', 'how',
25
+ 'compare', 'recommend', 'suggest', 'should', 'would', 'could',
26
+ ];
27
+ const TRIVIAL_MAX_CHARS = 60;
28
+ export function classifyForHaikuRoute(prompt) {
29
+ if (!getPerfHaikuRouteEnabled())
30
+ return { routeToHaiku: false, reason: 'flag_off' };
31
+ if (!prompt || typeof prompt !== 'string')
32
+ return { routeToHaiku: false, reason: 'no_match' };
33
+ const trimmed = prompt.trim();
34
+ if (trimmed.length === 0)
35
+ return { routeToHaiku: false, reason: 'no_match' };
36
+ if (trimmed.length > TRIVIAL_MAX_CHARS)
37
+ return { routeToHaiku: false, reason: 'too_long' };
38
+ const lower = trimmed.toLowerCase();
39
+ for (const token of COMPLEX_TOKENS) {
40
+ if (lower.includes(token))
41
+ return { routeToHaiku: false, reason: 'complex_token' };
42
+ }
43
+ for (const pat of TRIVIAL_PATTERNS) {
44
+ if (pat.test(trimmed))
45
+ return { routeToHaiku: true, reason: 'trivial_pattern' };
46
+ }
47
+ return { routeToHaiku: false, reason: 'no_match' };
48
+ }
49
+ //# sourceMappingURL=haiku-route.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"haiku-route.js","sourceRoot":"","sources":["../../../../src/lib/perf/haiku-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAExD,MAAM,gBAAgB,GAAa;IACjC,wEAAwE;IACxE,sDAAsD;IACtD,qDAAqD;IACrD,qCAAqC;CACtC,CAAC;AAEF,MAAM,cAAc,GAAG;IACrB,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS;IAClE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK;IAC/D,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO;CAC9D,CAAC;AAOF,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAE7B,MAAM,UAAU,qBAAqB,CAAC,MAAc;IAClD,IAAI,CAAC,wBAAwB,EAAE;QAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IACpF,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IAC9F,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;IAC9B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IAC7E,IAAI,OAAO,CAAC,MAAM,GAAG,iBAAiB;QAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IAC3F,MAAM,KAAK,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IACpC,KAAK,MAAM,KAAK,IAAI,cAAc,EAAE,CAAC;QACnC,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;IACrF,CAAC;IACD,KAAK,MAAM,GAAG,IAAI,gBAAgB,EAAE,CAAC;QACnC,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC;YAAE,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAClF,CAAC;IACD,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;AACrD,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * M11 (perf overhaul idea #11) — predictive continuation detector.
3
+ *
4
+ * Returns a confident yes/no when a user's incoming message is a pure
5
+ * continuation request ("more", "continue", "and?", "go on"). When true,
6
+ * the caller can start generation against the previous turn's tail before
7
+ * the user finishes typing, giving the perceived-instant feel on common
8
+ * follow-ups. False on ambiguous input keeps the regular dispatch path.
9
+ *
10
+ * Flag: CC_OPENCLAW_PERF_PREDICTIVE_CONTINUE (default OFF).
11
+ */
12
+ export interface ContinuationDecision {
13
+ /** True iff the text is a confident continuation request. */
14
+ isContinuation: boolean;
15
+ /** Matched pattern source for telemetry; empty when isContinuation=false. */
16
+ matched: string;
17
+ }
18
+ export declare function isPredictiveContinuation(text: string): ContinuationDecision;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * M11 (perf overhaul idea #11) — predictive continuation detector.
3
+ *
4
+ * Returns a confident yes/no when a user's incoming message is a pure
5
+ * continuation request ("more", "continue", "and?", "go on"). When true,
6
+ * the caller can start generation against the previous turn's tail before
7
+ * the user finishes typing, giving the perceived-instant feel on common
8
+ * follow-ups. False on ambiguous input keeps the regular dispatch path.
9
+ *
10
+ * Flag: CC_OPENCLAW_PERF_PREDICTIVE_CONTINUE (default OFF).
11
+ */
12
+ import { getPerfPredictiveContinueEnabled } from '../config.js';
13
+ const CONTINUATION_PATTERNS = [
14
+ /^\s*(more|continue|go\s*on|keep\s*going|next|and\??|then\??|\.\.\.)\s*[.!?]?\s*$/i,
15
+ /^\s*(go|do)\s+(on|ahead)\s*[.!?]?\s*$/i,
16
+ /^\s*(yes|yep|yeah|ok|okay|sure)[,!.?]?\s+(more|continue|go\s*on|please)\s*[.!?]?\s*$/i,
17
+ ];
18
+ export function isPredictiveContinuation(text) {
19
+ if (!getPerfPredictiveContinueEnabled()) {
20
+ return { isContinuation: false, matched: '' };
21
+ }
22
+ if (!text || typeof text !== 'string') {
23
+ return { isContinuation: false, matched: '' };
24
+ }
25
+ const trimmed = text.trim();
26
+ // Hard length cap — anything over 40 chars is too substantive to predict.
27
+ if (trimmed.length === 0 || trimmed.length > 40) {
28
+ return { isContinuation: false, matched: '' };
29
+ }
30
+ for (const pat of CONTINUATION_PATTERNS) {
31
+ if (pat.test(trimmed)) {
32
+ return { isContinuation: true, matched: pat.source };
33
+ }
34
+ }
35
+ return { isContinuation: false, matched: '' };
36
+ }
37
+ //# sourceMappingURL=predictive-continuation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"predictive-continuation.js","sourceRoot":"","sources":["../../../../src/lib/perf/predictive-continuation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,gCAAgC,EAAE,MAAM,cAAc,CAAC;AAEhE,MAAM,qBAAqB,GAAa;IACtC,mFAAmF;IACnF,wCAAwC;IACxC,uFAAuF;CACxF,CAAC;AASF,MAAM,UAAU,wBAAwB,CAAC,IAAY;IACnD,IAAI,CAAC,gCAAgC,EAAE,EAAE,CAAC;QACxC,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAChD,CAAC;IACD,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtC,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAChD,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5B,0EAA0E;IAC1E,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;QAChD,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAChD,CAAC;IACD,KAAK,MAAM,GAAG,IAAI,qBAAqB,EAAE,CAAC;QACxC,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YACtB,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC;QACvD,CAAC;IACH,CAAC;IACD,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AAChD,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * M7 (perf overhaul idea #7) — Read([paths]) parallel batch reader.
3
+ *
4
+ * The legacy flow is N serial Read tool calls = N LLM round trips. This
5
+ * helper reads an array of paths concurrently in a single Node.js step,
6
+ * returning a structured result the tool-router collapses into one
7
+ * tool_result delta. Eliminates the per-file LLM round trip cost.
8
+ *
9
+ * Flag: CC_OPENCLAW_PERF_READ_BATCH (default OFF, medium risk — tool
10
+ * schema change requires sub-agent training to use the batched form).
11
+ *
12
+ * Per-file failures are isolated: an unreadable file produces an error
13
+ * entry in the result rather than failing the whole batch. Encoding is
14
+ * fixed to utf8 to match the existing Read tool semantics.
15
+ */
16
+ export interface BatchReadEntry {
17
+ path: string;
18
+ content: string | null;
19
+ error: string | null;
20
+ bytes: number;
21
+ }
22
+ export interface BatchReadResult {
23
+ enabled: boolean;
24
+ entries: BatchReadEntry[];
25
+ totalBytes: number;
26
+ errorCount: number;
27
+ }
28
+ /**
29
+ * Read up to MAX_BATCH_SIZE paths in parallel. When the flag is off,
30
+ * returns `enabled: false` with an empty entry list — caller falls back
31
+ * to the per-file path. Per-file failures populate `.error`, not throw.
32
+ */
33
+ export declare function batchReadPaths(paths: string[]): Promise<BatchReadResult>;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * M7 (perf overhaul idea #7) — Read([paths]) parallel batch reader.
3
+ *
4
+ * The legacy flow is N serial Read tool calls = N LLM round trips. This
5
+ * helper reads an array of paths concurrently in a single Node.js step,
6
+ * returning a structured result the tool-router collapses into one
7
+ * tool_result delta. Eliminates the per-file LLM round trip cost.
8
+ *
9
+ * Flag: CC_OPENCLAW_PERF_READ_BATCH (default OFF, medium risk — tool
10
+ * schema change requires sub-agent training to use the batched form).
11
+ *
12
+ * Per-file failures are isolated: an unreadable file produces an error
13
+ * entry in the result rather than failing the whole batch. Encoding is
14
+ * fixed to utf8 to match the existing Read tool semantics.
15
+ */
16
+ import { readFile } from 'node:fs/promises';
17
+ import { getPerfReadBatchEnabled } from '../config.js';
18
+ const MAX_BATCH_SIZE = 16;
19
+ const PER_FILE_BYTE_CAP = 1_048_576;
20
+ /**
21
+ * Read up to MAX_BATCH_SIZE paths in parallel. When the flag is off,
22
+ * returns `enabled: false` with an empty entry list — caller falls back
23
+ * to the per-file path. Per-file failures populate `.error`, not throw.
24
+ */
25
+ export async function batchReadPaths(paths) {
26
+ if (!getPerfReadBatchEnabled()) {
27
+ return { enabled: false, entries: [], totalBytes: 0, errorCount: 0 };
28
+ }
29
+ if (!Array.isArray(paths) || paths.length === 0) {
30
+ return { enabled: true, entries: [], totalBytes: 0, errorCount: 0 };
31
+ }
32
+ const slice = paths.slice(0, MAX_BATCH_SIZE);
33
+ const entries = await Promise.all(slice.map(async (p) => {
34
+ try {
35
+ const buf = await readFile(p, 'utf8');
36
+ const content = buf.length > PER_FILE_BYTE_CAP ? buf.slice(0, PER_FILE_BYTE_CAP) : buf;
37
+ return { path: p, content, error: null, bytes: content.length };
38
+ }
39
+ catch (err) {
40
+ const msg = err instanceof Error ? err.message : String(err);
41
+ return { path: p, content: null, error: msg, bytes: 0 };
42
+ }
43
+ }));
44
+ const totalBytes = entries.reduce((s, e) => s + e.bytes, 0);
45
+ const errorCount = entries.filter((e) => e.error !== null).length;
46
+ return { enabled: true, entries, totalBytes, errorCount };
47
+ }
48
+ //# sourceMappingURL=read-batch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read-batch.js","sourceRoot":"","sources":["../../../../src/lib/perf/read-batch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAgBvD,MAAM,cAAc,GAAG,EAAE,CAAC;AAC1B,MAAM,iBAAiB,GAAG,SAAS,CAAC;AAEpC;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,KAAe;IAClD,IAAI,CAAC,uBAAuB,EAAE,EAAE,CAAC;QAC/B,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;IACvE,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;IACtE,CAAC;IACD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC;IAC7C,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,EAA2B,EAAE;QAC7C,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;YACtC,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,GAAG,iBAAiB,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;YACvF,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;QAClE,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC7D,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;QAC1D,CAAC;IACH,CAAC,CAAC,CACH,CAAC;IACF,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAC5D,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,MAAM,CAAC;IAClE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC;AAC5D,CAAC"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * M1 (perf overhaul idea #1) — resident-CLI pool per chat_id.
3
+ *
4
+ * Tracks per-chat persistent `claude` child handles. The actual spawn,
5
+ * stdin framing, and process supervision belong to session-manager —
6
+ * this module owns the (chatId → handle) map, the acquire/release
7
+ * lifecycle, and the idle-eviction watchdog.
8
+ *
9
+ * Flag: CC_OPENCLAW_PERF_RESIDENT_CLI (default OFF, medium risk —
10
+ * lifecycle/leak management requires watchdog).
11
+ *
12
+ * Eviction: handles idle longer than IDLE_TIMEOUT_MS are released. Cap
13
+ * at MAX_POOL_SIZE prevents unbounded growth on multi-chat workloads.
14
+ */
15
+ export declare const IDLE_TIMEOUT_MS: number;
16
+ export declare const MAX_POOL_SIZE = 32;
17
+ export interface ResidentCliHandle {
18
+ /** Opaque per-process identifier; session-manager owns the actual subprocess. */
19
+ pid: number;
20
+ chatId: string;
21
+ spawnedAt: number;
22
+ lastUsedAt: number;
23
+ }
24
+ export interface AcquireResult {
25
+ enabled: boolean;
26
+ handle: ResidentCliHandle | null;
27
+ reason: 'flag_off' | 'cached' | 'evicted_stale' | 'pool_full' | 'no_factory';
28
+ }
29
+ /** Register the actual spawn callback. Called by session-manager bootstrap. */
30
+ export declare function setHandleFactory(fn: ((chatId: string) => ResidentCliHandle | null) | null): void;
31
+ /**
32
+ * Acquire (or create) the resident handle for chatId. Evicts stale handles
33
+ * before serving new ones so the eviction work is amortized across acquires.
34
+ */
35
+ export declare function acquireResidentCli(chatId: string, now?: number): AcquireResult;
36
+ /** Release the handle (does not kill the subprocess; just marks idle). */
37
+ export declare function releaseResidentCli(chatId: string): boolean;
38
+ export declare function getPoolSize(): number;
39
+ export declare function _resetResidentCliPoolForTests(): void;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * M1 (perf overhaul idea #1) — resident-CLI pool per chat_id.
3
+ *
4
+ * Tracks per-chat persistent `claude` child handles. The actual spawn,
5
+ * stdin framing, and process supervision belong to session-manager —
6
+ * this module owns the (chatId → handle) map, the acquire/release
7
+ * lifecycle, and the idle-eviction watchdog.
8
+ *
9
+ * Flag: CC_OPENCLAW_PERF_RESIDENT_CLI (default OFF, medium risk —
10
+ * lifecycle/leak management requires watchdog).
11
+ *
12
+ * Eviction: handles idle longer than IDLE_TIMEOUT_MS are released. Cap
13
+ * at MAX_POOL_SIZE prevents unbounded growth on multi-chat workloads.
14
+ */
15
+ import { getPerfResidentCliEnabled } from '../config.js';
16
+ export const IDLE_TIMEOUT_MS = 5 * 60_000;
17
+ export const MAX_POOL_SIZE = 32;
18
+ const pool = new Map();
19
+ let handleFactory = null;
20
+ /** Register the actual spawn callback. Called by session-manager bootstrap. */
21
+ export function setHandleFactory(fn) {
22
+ handleFactory = fn;
23
+ }
24
+ /**
25
+ * Acquire (or create) the resident handle for chatId. Evicts stale handles
26
+ * before serving new ones so the eviction work is amortized across acquires.
27
+ */
28
+ export function acquireResidentCli(chatId, now = Date.now()) {
29
+ if (!getPerfResidentCliEnabled())
30
+ return { enabled: false, handle: null, reason: 'flag_off' };
31
+ if (!chatId)
32
+ return { enabled: true, handle: null, reason: 'no_factory' };
33
+ // Evict stale handles first.
34
+ for (const [k, v] of pool) {
35
+ if (now - v.lastUsedAt > IDLE_TIMEOUT_MS)
36
+ pool.delete(k);
37
+ }
38
+ const existing = pool.get(chatId);
39
+ if (existing) {
40
+ existing.lastUsedAt = now;
41
+ return { enabled: true, handle: existing, reason: 'cached' };
42
+ }
43
+ if (pool.size >= MAX_POOL_SIZE) {
44
+ return { enabled: true, handle: null, reason: 'pool_full' };
45
+ }
46
+ if (!handleFactory)
47
+ return { enabled: true, handle: null, reason: 'no_factory' };
48
+ const handle = handleFactory(chatId);
49
+ if (!handle)
50
+ return { enabled: true, handle: null, reason: 'no_factory' };
51
+ pool.set(chatId, handle);
52
+ return { enabled: true, handle, reason: 'cached' };
53
+ }
54
+ /** Release the handle (does not kill the subprocess; just marks idle). */
55
+ export function releaseResidentCli(chatId) {
56
+ return pool.delete(chatId);
57
+ }
58
+ export function getPoolSize() { return pool.size; }
59
+ export function _resetResidentCliPoolForTests() { pool.clear(); handleFactory = null; }
60
+ //# sourceMappingURL=resident-cli-pool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resident-cli-pool.js","sourceRoot":"","sources":["../../../../src/lib/perf/resident-cli-pool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAC;AAEzD,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,GAAG,MAAM,CAAC;AAC1C,MAAM,CAAC,MAAM,aAAa,GAAG,EAAE,CAAC;AAUhC,MAAM,IAAI,GAAG,IAAI,GAAG,EAA6B,CAAC;AAQlD,IAAI,aAAa,GAA0D,IAAI,CAAC;AAEhF,+EAA+E;AAC/E,MAAM,UAAU,gBAAgB,CAAC,EAAyD;IACxF,aAAa,GAAG,EAAE,CAAC;AACrB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAc,EAAE,MAAc,IAAI,CAAC,GAAG,EAAE;IACzE,IAAI,CAAC,yBAAyB,EAAE;QAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IAC9F,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC;IAC1E,6BAA6B;IAC7B,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;QAC1B,IAAI,GAAG,GAAG,CAAC,CAAC,UAAU,GAAG,eAAe;YAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAC3D,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAClC,IAAI,QAAQ,EAAE,CAAC;QACb,QAAQ,CAAC,UAAU,GAAG,GAAG,CAAC;QAC1B,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IAC/D,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,IAAI,aAAa,EAAE,CAAC;QAC/B,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IAC9D,CAAC;IACD,IAAI,CAAC,aAAa;QAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC;IACjF,MAAM,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IACrC,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC;IAC1E,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzB,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AACrD,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,kBAAkB,CAAC,MAAc;IAC/C,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,WAAW,KAAa,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AAC3D,MAAM,UAAU,6BAA6B,KAAW,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * M4 (perf overhaul idea #4) — collapse <available_skills> sysprompt block.
3
+ *
4
+ * The block is ~3K tokens of skill names + descriptions. Almost every turn
5
+ * the user invokes 0 or 1 skill, so the full listing is dead weight in the
6
+ * cache-relevant prefix. This collapses it to a one-line hint that points
7
+ * the model at an on-demand `openclaw skills list` tool call.
8
+ *
9
+ * The collapse is text-level so it composes with sysprompt-strip.ts without
10
+ * coupling to its pipeline. Caller invokes after the strip pass.
11
+ *
12
+ * Flag: CC_OPENCLAW_PERF_SKILL_ONDEMAND (default OFF).
13
+ *
14
+ * Idempotent — replacing an already-collapsed sysprompt is a no-op.
15
+ */
16
+ export interface CollapseResult {
17
+ content: string;
18
+ changed: boolean;
19
+ bytesRemoved: number;
20
+ }
21
+ /** Collapse the <available_skills> block; no-op when the flag is off. */
22
+ export declare function collapseSkillList(sysprompt: string): CollapseResult;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * M4 (perf overhaul idea #4) — collapse <available_skills> sysprompt block.
3
+ *
4
+ * The block is ~3K tokens of skill names + descriptions. Almost every turn
5
+ * the user invokes 0 or 1 skill, so the full listing is dead weight in the
6
+ * cache-relevant prefix. This collapses it to a one-line hint that points
7
+ * the model at an on-demand `openclaw skills list` tool call.
8
+ *
9
+ * The collapse is text-level so it composes with sysprompt-strip.ts without
10
+ * coupling to its pipeline. Caller invokes after the strip pass.
11
+ *
12
+ * Flag: CC_OPENCLAW_PERF_SKILL_ONDEMAND (default OFF).
13
+ *
14
+ * Idempotent — replacing an already-collapsed sysprompt is a no-op.
15
+ */
16
+ import { getPerfSkillOnDemandEnabled } from '../config.js';
17
+ const SKILL_BLOCK_RE = /<available_skills>[\s\S]*?<\/available_skills>/g;
18
+ const HINT_LINE = '<available_skills>Skills are available via the `openclaw skills list` tool. Invoke it when the user mentions a slash-command or asks "what skills are available?" — do not list them proactively.</available_skills>';
19
+ /** Collapse the <available_skills> block; no-op when the flag is off. */
20
+ export function collapseSkillList(sysprompt) {
21
+ if (!getPerfSkillOnDemandEnabled()) {
22
+ return { content: sysprompt, changed: false, bytesRemoved: 0 };
23
+ }
24
+ let bytesRemoved = 0;
25
+ let changed = false;
26
+ const content = sysprompt.replace(SKILL_BLOCK_RE, (match) => {
27
+ if (match === HINT_LINE)
28
+ return match;
29
+ bytesRemoved += match.length - HINT_LINE.length;
30
+ changed = true;
31
+ return HINT_LINE;
32
+ });
33
+ return { content, changed, bytesRemoved };
34
+ }
35
+ //# sourceMappingURL=skill-list-collapse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-list-collapse.js","sourceRoot":"","sources":["../../../../src/lib/perf/skill-list-collapse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAC;AAE3D,MAAM,cAAc,GAAG,iDAAiD,CAAC;AACzE,MAAM,SAAS,GACb,sNAAsN,CAAC;AAQzN,yEAAyE;AACzE,MAAM,UAAU,iBAAiB,CAAC,SAAiB;IACjD,IAAI,CAAC,2BAA2B,EAAE,EAAE,CAAC;QACnC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC;IACjE,CAAC;IACD,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC,KAAK,EAAE,EAAE;QAC1D,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,KAAK,CAAC;QACtC,YAAY,IAAI,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC;QAChD,OAAO,GAAG,IAAI,CAAC;QACf,OAAO,SAAS,CAAC;IACnB,CAAC,CAAC,CAAC;IACH,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC;AAC5C,CAAC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * M2 (perf overhaul idea #2) — typing-prefetch subprocess warmup.
3
+ *
4
+ * Telegram emits `sendChatAction` upstream events while the user is typing
5
+ * (the "typing…" indicator). This module debounces those signals into a
6
+ * single "warm the subprocess pool for this chatId" trigger, then invokes
7
+ * the registered warmup callback. The actual subprocess.spawn() is owned
8
+ * by the session-manager / cli pool — this module is just the signal layer.
9
+ *
10
+ * Flag: CC_OPENCLAW_PERF_TYPING_PREFETCH (default OFF).
11
+ *
12
+ * Coalescing: at most one warmup per chatId per WARMUP_COOLDOWN_MS. Prevents
13
+ * "typing… typing… typing…" hammer events from spawning N subprocesses.
14
+ */
15
+ export declare const WARMUP_COOLDOWN_MS = 4000;
16
+ /** Register the actual warmup callback (called by session-manager bootstrap). */
17
+ export declare function setWarmupHandler(fn: ((chatId: string) => void) | null): void;
18
+ /**
19
+ * Notify that user is typing in chatId. Triggers warmup at most once per
20
+ * cooldown window. No-op when flag off, no handler registered, or in cooldown.
21
+ * Returns true iff a warmup was actually triggered.
22
+ */
23
+ export declare function notifyTyping(chatId: string, now?: number): boolean;
24
+ /** Test hook — reset the cooldown map. */
25
+ export declare function _resetTypingPrefetchForTests(): void;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * M2 (perf overhaul idea #2) — typing-prefetch subprocess warmup.
3
+ *
4
+ * Telegram emits `sendChatAction` upstream events while the user is typing
5
+ * (the "typing…" indicator). This module debounces those signals into a
6
+ * single "warm the subprocess pool for this chatId" trigger, then invokes
7
+ * the registered warmup callback. The actual subprocess.spawn() is owned
8
+ * by the session-manager / cli pool — this module is just the signal layer.
9
+ *
10
+ * Flag: CC_OPENCLAW_PERF_TYPING_PREFETCH (default OFF).
11
+ *
12
+ * Coalescing: at most one warmup per chatId per WARMUP_COOLDOWN_MS. Prevents
13
+ * "typing… typing… typing…" hammer events from spawning N subprocesses.
14
+ */
15
+ import { getPerfTypingPrefetchEnabled } from '../config.js';
16
+ export const WARMUP_COOLDOWN_MS = 4_000;
17
+ const lastWarmupAt = new Map();
18
+ let warmupFn = null;
19
+ /** Register the actual warmup callback (called by session-manager bootstrap). */
20
+ export function setWarmupHandler(fn) {
21
+ warmupFn = fn;
22
+ }
23
+ /**
24
+ * Notify that user is typing in chatId. Triggers warmup at most once per
25
+ * cooldown window. No-op when flag off, no handler registered, or in cooldown.
26
+ * Returns true iff a warmup was actually triggered.
27
+ */
28
+ export function notifyTyping(chatId, now = Date.now()) {
29
+ if (!getPerfTypingPrefetchEnabled())
30
+ return false;
31
+ if (!chatId || !warmupFn)
32
+ return false;
33
+ const last = lastWarmupAt.get(chatId);
34
+ // First call for this chat always fires; subsequent calls gate on cooldown.
35
+ // Using has-check (not `|| 0`) so the first call works regardless of `now`'s
36
+ // absolute magnitude — earlier `now - last < cooldown` rejected first calls
37
+ // when now < WARMUP_COOLDOWN_MS.
38
+ if (last !== undefined && now - last < WARMUP_COOLDOWN_MS)
39
+ return false;
40
+ lastWarmupAt.set(chatId, now);
41
+ try {
42
+ warmupFn(chatId);
43
+ return true;
44
+ }
45
+ catch {
46
+ return false;
47
+ }
48
+ }
49
+ /** Test hook — reset the cooldown map. */
50
+ export function _resetTypingPrefetchForTests() {
51
+ lastWarmupAt.clear();
52
+ warmupFn = null;
53
+ }
54
+ //# sourceMappingURL=typing-prefetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typing-prefetch.js","sourceRoot":"","sources":["../../../../src/lib/perf/typing-prefetch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,4BAA4B,EAAE,MAAM,cAAc,CAAC;AAE5D,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,CAAC;AAExC,MAAM,YAAY,GAAG,IAAI,GAAG,EAAkB,CAAC;AAC/C,IAAI,QAAQ,GAAsC,IAAI,CAAC;AAEvD,iFAAiF;AACjF,MAAM,UAAU,gBAAgB,CAAC,EAAqC;IACpE,QAAQ,GAAG,EAAE,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,MAAc,EAAE,MAAc,IAAI,CAAC,GAAG,EAAE;IACnE,IAAI,CAAC,4BAA4B,EAAE;QAAE,OAAO,KAAK,CAAC;IAClD,IAAI,CAAC,MAAM,IAAI,CAAC,QAAQ;QAAE,OAAO,KAAK,CAAC;IACvC,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACtC,4EAA4E;IAC5E,6EAA6E;IAC7E,4EAA4E;IAC5E,iCAAiC;IACjC,IAAI,IAAI,KAAK,SAAS,IAAI,GAAG,GAAG,IAAI,GAAG,kBAAkB;QAAE,OAAO,KAAK,CAAC;IACxE,YAAY,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC9B,IAAI,CAAC;QACH,QAAQ,CAAC,MAAM,CAAC,CAAC;QACjB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,0CAA0C;AAC1C,MAAM,UAAU,4BAA4B;IAC1C,YAAY,CAAC,KAAK,EAAE,CAAC;IACrB,QAAQ,GAAG,IAAI,CAAC;AAClB,CAAC"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Perf-baseline telemetry (M8 — cache-miss-telemetry).
3
+ *
4
+ * Appends structured perf events to `~/.openclaw/workspace/memory/sysprompt-cost.jsonl`
5
+ * alongside the existing token-cost records. Every record carries an `event`
6
+ * field so consumers can filter:
7
+ *
8
+ * - "token_cost" → legacy per-request sysprompt+user char/token estimate
9
+ * (emitted by cwd-patch.ts; tagged by this module on read).
10
+ * - "cache_check" → outcome of the Track-B cache parity decision: hit/miss
11
+ * with a structured `cause` (registry_empty | hash_mismatch
12
+ * | session_unknown | disabled). Drives idea #8.
13
+ * - "first_byte" → first user-visible content delta sent to the SSE client.
14
+ * `elapsed_ms` is measured from request receive (turnStartMs
15
+ * in streaming-handler.ts). This is the baseline for the
16
+ * plan's ≥50% p50 latency drop exit criterion.
17
+ * - "turn_end" → final SSE stop chunk written. `elapsed_ms` is total turn
18
+ * wall-clock. Pair with first_byte to derive generation-vs-
19
+ * latency split per turn.
20
+ *
21
+ * Flag: CC_OPENCLAW_PERF_CACHE_TELEMETRY (default ON; opt-out with 0/false/off).
22
+ * Goes through the same shim pattern as the legacy telemetry getters
23
+ * (ConfigService when present, env fallback for tests).
24
+ *
25
+ * Why same file as token_cost: the plan's verify query targets sysprompt-cost.jsonl
26
+ * directly; splitting would require operators to remember two paths and would
27
+ * break the single `jq` pipeline. The `event` field makes filtering trivial.
28
+ */
29
+ export declare const PERF_TELEMETRY_PATH: string;
30
+ export type PerfEventName = 'cache_check' | 'first_byte' | 'turn_end';
31
+ export type CacheCheckCause = 'hit' | 'registry_empty' | 'hash_mismatch' | 'session_unknown' | 'disabled';
32
+ interface PerfEventBase {
33
+ event: PerfEventName;
34
+ sessionKey?: string;
35
+ }
36
+ export interface CacheCheckEvent extends PerfEventBase {
37
+ event: 'cache_check';
38
+ outcome: 'hit' | 'miss';
39
+ cause: CacheCheckCause;
40
+ sysHash?: string;
41
+ }
42
+ export interface FirstByteEvent extends PerfEventBase {
43
+ event: 'first_byte';
44
+ elapsed_ms: number;
45
+ model?: string;
46
+ tool_stream?: boolean;
47
+ }
48
+ export interface TurnEndEvent extends PerfEventBase {
49
+ event: 'turn_end';
50
+ elapsed_ms: number;
51
+ first_byte_ms?: number;
52
+ finish_reason?: 'stop' | 'tool_calls' | 'length' | string;
53
+ tool_calls?: number;
54
+ bytes_out?: number;
55
+ }
56
+ export type PerfEvent = CacheCheckEvent | FirstByteEvent | TurnEndEvent;
57
+ /**
58
+ * Append a perf event to sysprompt-cost.jsonl. No-op when the flag is off
59
+ * or when an I/O error occurs — telemetry must never break a live turn.
60
+ */
61
+ export declare function writePerfEvent(event: PerfEvent): void;
62
+ /** Test hook — replace the file-writing sink with an in-memory collector. */
63
+ export declare function _setPerfWriterForTests(w: (line: string) => void): void;
64
+ /** Test hook — restore the default file-writing sink. */
65
+ export declare function _restorePerfWriterForTests(): void;
66
+ export {};