@a1hvdy/cc-openclaw 0.15.0 → 0.18.1
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/dist/src/channels/telegram/completion-summary.js +24 -2
- package/dist/src/channels/telegram/completion-summary.js.map +1 -1
- package/dist/src/channels/telegram/event-reducer.js +15 -3
- package/dist/src/channels/telegram/event-reducer.js.map +1 -1
- package/dist/src/channels/telegram/live-card.d.ts +90 -7
- package/dist/src/channels/telegram/live-card.js +286 -25
- package/dist/src/channels/telegram/live-card.js.map +1 -1
- package/dist/src/channels/telegram/tool-tracker.d.ts +16 -2
- package/dist/src/channels/telegram/tool-tracker.js +81 -18
- package/dist/src/channels/telegram/tool-tracker.js.map +1 -1
- package/dist/src/config/loader.js +2 -0
- package/dist/src/config/loader.js.map +1 -1
- package/dist/src/config/schema.d.ts +20 -1
- package/dist/src/config/schema.js +12 -1
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/constants.d.ts +13 -1
- package/dist/src/constants.js +13 -1
- package/dist/src/constants.js.map +1 -1
- package/dist/src/lib/config.d.ts +6 -0
- package/dist/src/lib/config.js +81 -0
- package/dist/src/lib/config.js.map +1 -1
- package/dist/src/lib/sysprompt-strip.d.ts +14 -0
- package/dist/src/lib/sysprompt-strip.js +79 -2
- package/dist/src/lib/sysprompt-strip.js.map +1 -1
- package/dist/src/lib/trajectory.d.ts +1 -10
- package/dist/src/lib/trajectory.js +6 -37
- package/dist/src/lib/trajectory.js.map +1 -1
- package/dist/src/lib/turn-trace.d.ts +34 -0
- package/dist/src/lib/turn-trace.js +75 -0
- package/dist/src/lib/turn-trace.js.map +1 -0
- package/dist/src/observability/event-bus.d.ts +0 -10
- package/dist/src/observability/event-bus.js.map +1 -1
- package/dist/src/openai-compat/insight-format.d.ts +36 -0
- package/dist/src/openai-compat/insight-format.js +140 -0
- package/dist/src/openai-compat/insight-format.js.map +1 -0
- package/dist/src/openai-compat/non-streaming-handler.js +38 -58
- package/dist/src/openai-compat/non-streaming-handler.js.map +1 -1
- package/dist/src/openai-compat/openai-compat.js +23 -0
- package/dist/src/openai-compat/openai-compat.js.map +1 -1
- package/dist/src/openai-compat/response-formatter.d.ts +6 -1
- package/dist/src/openai-compat/response-formatter.js +9 -2
- package/dist/src/openai-compat/response-formatter.js.map +1 -1
- package/dist/src/openai-compat/streaming-handler.js +35 -82
- package/dist/src/openai-compat/streaming-handler.js.map +1 -1
- package/dist/src/session/session-manager.d.ts +8 -4
- package/dist/src/session/session-manager.js +86 -76
- package/dist/src/session/session-manager.js.map +1 -1
- package/dist/src/session-bootstrap/cwd-patch.js +35 -0
- package/dist/src/session-bootstrap/cwd-patch.js.map +1 -1
- package/mcp-tools.json +1 -1
- package/package.json +1 -1
|
@@ -14,6 +14,18 @@
|
|
|
14
14
|
* Source: savvy-claude-code/cwd-enhancer.js lines 800–856 (inside applyRoutePatch)
|
|
15
15
|
* Original P1 port: src/session-bootstrap/sysprompt-strip.ts
|
|
16
16
|
*/
|
|
17
|
+
/**
|
|
18
|
+
* Extract the last ★ Insight block from assistant text, applying the same
|
|
19
|
+
* empty-body and length guards as the renderer-side tips-engine.
|
|
20
|
+
*
|
|
21
|
+
* Returns null when:
|
|
22
|
+
* - no fence is found
|
|
23
|
+
* - the body is whitespace-only
|
|
24
|
+
* - the body is shorter than MIN_INSIGHT_CHARS (20)
|
|
25
|
+
*
|
|
26
|
+
* Used by trajectory.ts to avoid emitting empty insight_strip events.
|
|
27
|
+
*/
|
|
28
|
+
export declare function extractInsight(assistantText: string | null | undefined): string | null;
|
|
17
29
|
export interface StripOptions {
|
|
18
30
|
/** Override env detection — force strip enabled. Used by tests + lifecycle wiring. */
|
|
19
31
|
forceEnabled?: boolean;
|
|
@@ -26,6 +38,8 @@ export interface StripResult {
|
|
|
26
38
|
changed: boolean;
|
|
27
39
|
/** true if aggressive strip made additional changes (always true when enabled) */
|
|
28
40
|
aggressiveStripped: boolean;
|
|
41
|
+
/** true if Track C v2 cold-prompt strips applied (opt-in via OPENCLAW_COLD_PROMPT_STRIP_V2=1) */
|
|
42
|
+
coldPromptV2Stripped?: boolean;
|
|
29
43
|
}
|
|
30
44
|
/**
|
|
31
45
|
* Resolve whether strip should run based on env + options.
|
|
@@ -14,7 +14,50 @@
|
|
|
14
14
|
* Source: savvy-claude-code/cwd-enhancer.js lines 800–856 (inside applyRoutePatch)
|
|
15
15
|
* Original P1 port: src/session-bootstrap/sysprompt-strip.ts
|
|
16
16
|
*/
|
|
17
|
-
import { getAggressiveStripEnabled } from './config.js';
|
|
17
|
+
import { getAggressiveStripEnabled, getColdPromptStripV2Enabled } from './config.js';
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Insight extractor (T1 — empty-insight defense)
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
/** Matches the last ★ Insight ─ … ─ fence in assistant output. */
|
|
22
|
+
const INSIGHT_FENCE_RE_STRIP = /★\s*Insight\s*[─—\-]{3,}[ \t]*\r?\n([\s\S]*?)\r?\n[ \t]*[─—\-]{3,}[ \t]*$/mg;
|
|
23
|
+
const MIN_INSIGHT_CHARS_STRIP = 20;
|
|
24
|
+
const MAX_INSIGHT_CHARS_STRIP = 350;
|
|
25
|
+
/**
|
|
26
|
+
* Extract the last ★ Insight block from assistant text, applying the same
|
|
27
|
+
* empty-body and length guards as the renderer-side tips-engine.
|
|
28
|
+
*
|
|
29
|
+
* Returns null when:
|
|
30
|
+
* - no fence is found
|
|
31
|
+
* - the body is whitespace-only
|
|
32
|
+
* - the body is shorter than MIN_INSIGHT_CHARS (20)
|
|
33
|
+
*
|
|
34
|
+
* Used by trajectory.ts to avoid emitting empty insight_strip events.
|
|
35
|
+
*/
|
|
36
|
+
export function extractInsight(assistantText) {
|
|
37
|
+
if (!assistantText || typeof assistantText !== 'string')
|
|
38
|
+
return null;
|
|
39
|
+
let match;
|
|
40
|
+
let last = null;
|
|
41
|
+
INSIGHT_FENCE_RE_STRIP.lastIndex = 0;
|
|
42
|
+
while ((match = INSIGHT_FENCE_RE_STRIP.exec(assistantText)) !== null) {
|
|
43
|
+
last = match[1];
|
|
44
|
+
}
|
|
45
|
+
if (!last)
|
|
46
|
+
return null;
|
|
47
|
+
const cleaned = last
|
|
48
|
+
.replace(/\r/g, '')
|
|
49
|
+
.replace(/\n+/g, ' ')
|
|
50
|
+
.replace(/\s+/g, ' ')
|
|
51
|
+
.trim();
|
|
52
|
+
if (!cleaned)
|
|
53
|
+
return null;
|
|
54
|
+
if (cleaned.length < MIN_INSIGHT_CHARS_STRIP)
|
|
55
|
+
return null;
|
|
56
|
+
if (cleaned.length > MAX_INSIGHT_CHARS_STRIP) {
|
|
57
|
+
return cleaned.slice(0, MAX_INSIGHT_CHARS_STRIP - 1).trimEnd() + '…';
|
|
58
|
+
}
|
|
59
|
+
return cleaned;
|
|
60
|
+
}
|
|
18
61
|
// v0.6.0: regex literals lifted to module scope to avoid recompilation on
|
|
19
62
|
// every stripSysprompt() call. With cwd-patch firing strip on every chat
|
|
20
63
|
// completion, that was 4-6 regex compilations per turn → 0.
|
|
@@ -22,6 +65,28 @@ const RE_SELF_IMPROVEMENT_FILENAME = /\n?##\s+SELF_IMPROVEMENT_REMINDER\.md(?=\n
|
|
|
22
65
|
const RE_SELF_IMPROVEMENT_BLOCK = /\n?##\s+Self-Improvement Reminder[\s\S]*?(?=\n##\s|\n?$)/g;
|
|
23
66
|
const RE_LOCATION_LINES = /\n\s*<location>[^<]*<\/location>/g;
|
|
24
67
|
const RE_TOOL_ESCALATION_BLOCK = /\n\*\*Tool escalation:\*\*\n[\s\S]*?(?=\n\*\*Scope selection rule:\*\*)/g;
|
|
68
|
+
// ── Track C v0.18 — Cold-prompt strip V2 ────────────────────────────────
|
|
69
|
+
// Opt-in via OPENCLAW_COLD_PROMPT_STRIP_V2=1. Adds 3 strips for
|
|
70
|
+
// OpenClaw-injected workspace-file bloat that the audit identified
|
|
71
|
+
// (sysprompt-cost.jsonl baseline ~8,342 tok cold turn).
|
|
72
|
+
// 1. MEMORY.md "Promoted From Short-Term Memory" tails — auto-generated
|
|
73
|
+
// low-signal sections with [score=...] markup. Match the dated heading
|
|
74
|
+
// through to the next `## /home/` injected-file boundary or EOF.
|
|
75
|
+
// Workspace File injects use `## /home/a1xai/.openclaw/workspace/X.md`
|
|
76
|
+
// as the framing header (see system prompt format).
|
|
77
|
+
const RE_MEMORY_PROMOTED_TAIL = /\n## Promoted From Short-Term Memory[\s\S]*?(?=\n## \/home\/|$)/g;
|
|
78
|
+
// 2. IDENTITY.md stub — single HTML-comment line wrapped between the file
|
|
79
|
+
// header and the next `## /home/` boundary. SOUL.md is the canonical
|
|
80
|
+
// identity source; IDENTITY.md is a 1-line back-compat shim per the
|
|
81
|
+
// file's own comment ("Minimal stub kept to satisfy auto-restore").
|
|
82
|
+
const RE_IDENTITY_STUB_BLOCK = /\n## \/home\/[^\n]+\/IDENTITY\.md\n<!--[^\n]*-->\n*(?=## \/home\/|$)/g;
|
|
83
|
+
// 3. HEARTBEAT.md when disabled — file is wrapped in a `<!-- DISABLED`
|
|
84
|
+
// HTML comment with no live `tasks:` block (upstream-broken heartbeat
|
|
85
|
+
// model override #9556/#13009/#14279; the file is dormant). Strip the
|
|
86
|
+
// entire injected block including its header.
|
|
87
|
+
// Lookahead stops at the next `## ` heading (either a `## /home/` workspace
|
|
88
|
+
// inject or any other H2 like `## Messaging`) — empty cold prompts hit EOF.
|
|
89
|
+
const RE_HEARTBEAT_DISABLED_BLOCK = /\n## \/home\/[^\n]+\/HEARTBEAT\.md\n# HEARTBEAT\.md[^\n]*\n\n<!-- DISABLED[\s\S]*?(?=\n## |$)/g;
|
|
25
90
|
/**
|
|
26
91
|
* Resolve whether strip should run based on env + options.
|
|
27
92
|
*
|
|
@@ -69,7 +134,19 @@ export function stripSysprompt(content, opts = {}) {
|
|
|
69
134
|
// F2.2 (REMOVED 2026-04-25) — The 45-bullet tool-name enumeration no longer appears.
|
|
70
135
|
// Re-add only if bullets reappear in a future runtime.
|
|
71
136
|
const aggressiveStripped = stripped !== beforeAggr;
|
|
137
|
+
// Track C v0.18 — Cold-prompt strip V2 (opt-in OPENCLAW_COLD_PROMPT_STRIP_V2=1).
|
|
138
|
+
// Layered on top of aggressive strip. Targets OpenClaw workspace-file inject
|
|
139
|
+
// bloat that direct CLI doesn't carry. Safe to disable: just unset the env.
|
|
140
|
+
let coldPromptV2Stripped = false;
|
|
141
|
+
if (getColdPromptStripV2Enabled()) {
|
|
142
|
+
const beforeV2 = stripped;
|
|
143
|
+
stripped = stripped
|
|
144
|
+
.replace(RE_MEMORY_PROMOTED_TAIL, '')
|
|
145
|
+
.replace(RE_IDENTITY_STUB_BLOCK, '')
|
|
146
|
+
.replace(RE_HEARTBEAT_DISABLED_BLOCK, '');
|
|
147
|
+
coldPromptV2Stripped = stripped !== beforeV2;
|
|
148
|
+
}
|
|
72
149
|
const changed = stripped !== original;
|
|
73
|
-
return { content: stripped, changed, aggressiveStripped };
|
|
150
|
+
return { content: stripped, changed, aggressiveStripped, coldPromptV2Stripped };
|
|
74
151
|
}
|
|
75
152
|
//# sourceMappingURL=sysprompt-strip.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sysprompt-strip.js","sourceRoot":"","sources":["../../../src/lib/sysprompt-strip.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"sysprompt-strip.js","sourceRoot":"","sources":["../../../src/lib/sysprompt-strip.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* Size-based rotation deferred to v0.4.x. v0.4.0 ships unbounded; if file
|
|
15
15
|
* exceeds 100 MB, run `gzip cc-openclaw-trajectory.jsonl && rm` manually.
|
|
16
16
|
*/
|
|
17
|
-
export type EventType = 'session_start' | 'session_end' | 'request_in' | 'response_complete' | 'tool_use' | 'tool_result' | 'fallback_step' | 'drift_warning' | 'budget_event' | 'error' | 'thinking_block' | 'session_stalled_killed' | 'runaway_loop_killed' | '
|
|
17
|
+
export type EventType = 'session_start' | 'session_end' | 'request_in' | 'response_complete' | 'tool_use' | 'tool_result' | 'fallback_step' | 'drift_warning' | 'budget_event' | 'error' | 'thinking_block' | 'session_stalled_killed' | 'runaway_loop_killed' | 'turn_preflight' | 'insight_strip' | 'false_done_backstop' | 'card_collapse_fired' | 'no_greet_strip';
|
|
18
18
|
export interface TrajectoryEvent {
|
|
19
19
|
ts: string;
|
|
20
20
|
sessionId?: string;
|
|
@@ -29,15 +29,6 @@ export interface TrajectoryEvent {
|
|
|
29
29
|
* throughput is needed in v0.4.x, swap to a buffered write stream.
|
|
30
30
|
*/
|
|
31
31
|
export declare function emit(eventType: EventType, payload?: Record<string, unknown>, sessionId?: string): void;
|
|
32
|
-
/**
|
|
33
|
-
* v0.14.0 — turn-trace probe. Emits a `turn_complete` event independent of
|
|
34
|
-
* the main trajectory gate so A1 can capture per-turn diagnostics
|
|
35
|
-
* (userPrompt → tools → output → backstop) without flipping on the full
|
|
36
|
-
* event firehose. Gated by CC_OPENCLAW_TURN_TRACE=1; also fires when the
|
|
37
|
-
* primary CC_OPENCLAW_TRAJECTORY gate is on (so trajectory consumers see
|
|
38
|
-
* turn_complete in the normal stream too).
|
|
39
|
-
*/
|
|
40
|
-
export declare function emitTurnTrace(payload: Record<string, unknown>, sessionId?: string): void;
|
|
41
32
|
/** Reset module state for tests (re-checks env, re-creates dir on next emit). */
|
|
42
33
|
export declare function _resetForTests(): void;
|
|
43
34
|
/** Read all events from current trajectory file. Test-only. */
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import { appendFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
19
19
|
import { dirname, join } from 'node:path';
|
|
20
20
|
import { homedir } from 'node:os';
|
|
21
|
+
import * as turnTrace from './turn-trace.js';
|
|
21
22
|
// ─── Config ──────────────────────────────────────────────────────────────────
|
|
22
23
|
const DEFAULT_PATH = join(homedir(), '.openclaw', 'workspace', 'memory', 'cc-openclaw-trajectory.jsonl');
|
|
23
24
|
function isEnabled() {
|
|
@@ -26,12 +27,6 @@ function isEnabled() {
|
|
|
26
27
|
return false;
|
|
27
28
|
return v === '1' || v.toLowerCase() === 'true' || v.toLowerCase() === 'on';
|
|
28
29
|
}
|
|
29
|
-
function isTurnTraceEnabled() {
|
|
30
|
-
const v = process.env.CC_OPENCLAW_TURN_TRACE;
|
|
31
|
-
if (!v)
|
|
32
|
-
return false;
|
|
33
|
-
return v === '1' || v.toLowerCase() === 'true' || v.toLowerCase() === 'on';
|
|
34
|
-
}
|
|
35
30
|
function getPath() {
|
|
36
31
|
return process.env.CC_OPENCLAW_TRAJECTORY_PATH || DEFAULT_PATH;
|
|
37
32
|
}
|
|
@@ -72,37 +67,11 @@ export function emit(eventType, payload, sessionId) {
|
|
|
72
67
|
// endpoint's errors_total counter still records the underlying error
|
|
73
68
|
// via the calling code's error-formatter path.
|
|
74
69
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
* event firehose. Gated by CC_OPENCLAW_TURN_TRACE=1; also fires when the
|
|
81
|
-
* primary CC_OPENCLAW_TRAJECTORY gate is on (so trajectory consumers see
|
|
82
|
-
* turn_complete in the normal stream too).
|
|
83
|
-
*/
|
|
84
|
-
export function emitTurnTrace(payload, sessionId) {
|
|
85
|
-
if (!isEnabled() && !isTurnTraceEnabled())
|
|
86
|
-
return;
|
|
87
|
-
const event = {
|
|
88
|
-
ts: new Date().toISOString(),
|
|
89
|
-
eventType: 'turn_complete',
|
|
90
|
-
laptopId: getLaptopId(),
|
|
91
|
-
payload,
|
|
92
|
-
};
|
|
93
|
-
if (sessionId)
|
|
94
|
-
event.sessionId = sessionId;
|
|
95
|
-
try {
|
|
96
|
-
const path = getPath();
|
|
97
|
-
if (!_ensuredDir) {
|
|
98
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
99
|
-
_ensuredDir = true;
|
|
100
|
-
}
|
|
101
|
-
appendFileSync(path, JSON.stringify(event) + '\n', 'utf8');
|
|
102
|
-
}
|
|
103
|
-
catch {
|
|
104
|
-
// Observability path — never break production on disk-full / perms.
|
|
105
|
-
}
|
|
70
|
+
// v0.15.0 Slice 1 — fan out to the per-session turn-trace sink. Independent
|
|
71
|
+
// env (OPENCLAW_TURN_TRACE) so users can enable shardable per-session traces
|
|
72
|
+
// without keeping the unbounded global trajectory log on. Sink is no-op when
|
|
73
|
+
// disabled and swallows its own errors.
|
|
74
|
+
turnTrace.capture(event);
|
|
106
75
|
}
|
|
107
76
|
// ─── Test helpers ────────────────────────────────────────────────────────────
|
|
108
77
|
/** Reset module state for tests (re-checks env, re-creates dir on next emit). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trajectory.js","sourceRoot":"","sources":["../../../src/lib/trajectory.ts"],"names":[],"mappings":"AAAA,+CAA+C;AAC/C;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"trajectory.js","sourceRoot":"","sources":["../../../src/lib/trajectory.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* turn-trace — per-session disk sink for cc-openclaw trajectory events.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors every `trajectory.emit()` call into a per-session JSONL file at
|
|
5
|
+
* `~/.openclaw/workspace/debug/turn-trace/<sessionId>.jsonl`. The global
|
|
6
|
+
* trajectory log keeps working as before; this sink adds shardable output
|
|
7
|
+
* for debugging "what did session X actually see and emit".
|
|
8
|
+
*
|
|
9
|
+
* Motivation: v0.14.0's NO_REPLY 4-layer defense interacted badly with
|
|
10
|
+
* short questions, producing greeting-only output ("Hey A1.") that looked
|
|
11
|
+
* just as broken as silence. A1 needs ground-truth to tune the classifier
|
|
12
|
+
* without guessing — this sink captures the data.
|
|
13
|
+
*
|
|
14
|
+
* Default OFF — set OPENCLAW_TURN_TRACE=1 in env to opt in. Independent
|
|
15
|
+
* from CC_OPENCLAW_TRAJECTORY (the global sink); either, both, or neither
|
|
16
|
+
* can run.
|
|
17
|
+
*
|
|
18
|
+
* v0.15.0 ships per-session sharding (one file per sessionId lifecycle).
|
|
19
|
+
* v0.15.1 will switch to per-turn sharding once a turnId is threaded
|
|
20
|
+
* through the request_in → response_complete event chain.
|
|
21
|
+
*/
|
|
22
|
+
import type { TrajectoryEvent } from './trajectory.js';
|
|
23
|
+
/**
|
|
24
|
+
* Append a trajectory event to the per-session JSONL file. No-op if disabled
|
|
25
|
+
* or if the event has no sessionId (can't shard without a key).
|
|
26
|
+
*
|
|
27
|
+
* Writes are sync (matches trajectory.emit semantics — preserves ordering).
|
|
28
|
+
* File-system errors are swallowed: observability must never break prod.
|
|
29
|
+
*/
|
|
30
|
+
export declare function capture(event: TrajectoryEvent): void;
|
|
31
|
+
/** Reset module state for tests (re-creates dir on next capture). */
|
|
32
|
+
export declare function _resetForTests(): void;
|
|
33
|
+
/** Compute the on-disk path a given sessionId would resolve to. Test-only. */
|
|
34
|
+
export declare function _pathForSession(sessionId: string): string;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// phase: v0.15.0 | pillar: Observability | agent: Primary
|
|
2
|
+
/**
|
|
3
|
+
* turn-trace — per-session disk sink for cc-openclaw trajectory events.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors every `trajectory.emit()` call into a per-session JSONL file at
|
|
6
|
+
* `~/.openclaw/workspace/debug/turn-trace/<sessionId>.jsonl`. The global
|
|
7
|
+
* trajectory log keeps working as before; this sink adds shardable output
|
|
8
|
+
* for debugging "what did session X actually see and emit".
|
|
9
|
+
*
|
|
10
|
+
* Motivation: v0.14.0's NO_REPLY 4-layer defense interacted badly with
|
|
11
|
+
* short questions, producing greeting-only output ("Hey A1.") that looked
|
|
12
|
+
* just as broken as silence. A1 needs ground-truth to tune the classifier
|
|
13
|
+
* without guessing — this sink captures the data.
|
|
14
|
+
*
|
|
15
|
+
* Default OFF — set OPENCLAW_TURN_TRACE=1 in env to opt in. Independent
|
|
16
|
+
* from CC_OPENCLAW_TRAJECTORY (the global sink); either, both, or neither
|
|
17
|
+
* can run.
|
|
18
|
+
*
|
|
19
|
+
* v0.15.0 ships per-session sharding (one file per sessionId lifecycle).
|
|
20
|
+
* v0.15.1 will switch to per-turn sharding once a turnId is threaded
|
|
21
|
+
* through the request_in → response_complete event chain.
|
|
22
|
+
*/
|
|
23
|
+
import { appendFileSync, mkdirSync } from 'node:fs';
|
|
24
|
+
import { join } from 'node:path';
|
|
25
|
+
import { homedir } from 'node:os';
|
|
26
|
+
// ─── Config ──────────────────────────────────────────────────────────────────
|
|
27
|
+
const DEFAULT_DIR = join(homedir(), '.openclaw', 'workspace', 'debug', 'turn-trace');
|
|
28
|
+
function isEnabled() {
|
|
29
|
+
const v = process.env.OPENCLAW_TURN_TRACE;
|
|
30
|
+
if (!v)
|
|
31
|
+
return false;
|
|
32
|
+
return v === '1' || v.toLowerCase() === 'true' || v.toLowerCase() === 'on';
|
|
33
|
+
}
|
|
34
|
+
function getDir() {
|
|
35
|
+
return process.env.OPENCLAW_TURN_TRACE_DIR || DEFAULT_DIR;
|
|
36
|
+
}
|
|
37
|
+
// ─── Sink ────────────────────────────────────────────────────────────────────
|
|
38
|
+
let _ensuredDir = false;
|
|
39
|
+
/**
|
|
40
|
+
* Append a trajectory event to the per-session JSONL file. No-op if disabled
|
|
41
|
+
* or if the event has no sessionId (can't shard without a key).
|
|
42
|
+
*
|
|
43
|
+
* Writes are sync (matches trajectory.emit semantics — preserves ordering).
|
|
44
|
+
* File-system errors are swallowed: observability must never break prod.
|
|
45
|
+
*/
|
|
46
|
+
export function capture(event) {
|
|
47
|
+
if (!isEnabled())
|
|
48
|
+
return;
|
|
49
|
+
if (!event.sessionId)
|
|
50
|
+
return;
|
|
51
|
+
try {
|
|
52
|
+
const dir = getDir();
|
|
53
|
+
if (!_ensuredDir) {
|
|
54
|
+
mkdirSync(dir, { recursive: true });
|
|
55
|
+
_ensuredDir = true;
|
|
56
|
+
}
|
|
57
|
+
const safeKey = event.sessionId.replace(/[^a-zA-Z0-9_.-]/g, '_');
|
|
58
|
+
const path = join(dir, `${safeKey}.jsonl`);
|
|
59
|
+
appendFileSync(path, JSON.stringify(event) + '\n', 'utf8');
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// observability — never break production
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// ─── Test helpers ────────────────────────────────────────────────────────────
|
|
66
|
+
/** Reset module state for tests (re-creates dir on next capture). */
|
|
67
|
+
export function _resetForTests() {
|
|
68
|
+
_ensuredDir = false;
|
|
69
|
+
}
|
|
70
|
+
/** Compute the on-disk path a given sessionId would resolve to. Test-only. */
|
|
71
|
+
export function _pathForSession(sessionId) {
|
|
72
|
+
const safeKey = sessionId.replace(/[^a-zA-Z0-9_.-]/g, '_');
|
|
73
|
+
return join(getDir(), `${safeKey}.jsonl`);
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=turn-trace.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turn-trace.js","sourceRoot":"","sources":["../../../src/lib/turn-trace.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAC1D;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,IAAI,EAAW,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAGlC,gFAAgF;AAEhF,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC;AAErF,SAAS,SAAS;IAChB,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC;IAC1C,IAAI,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACrB,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,KAAK,MAAM,IAAI,CAAC,CAAC,WAAW,EAAE,KAAK,IAAI,CAAC;AAC7E,CAAC;AAED,SAAS,MAAM;IACb,OAAO,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,WAAW,CAAC;AAC5D,CAAC;AAED,gFAAgF;AAEhF,IAAI,WAAW,GAAG,KAAK,CAAC;AAExB;;;;;;GAMG;AACH,MAAM,UAAU,OAAO,CAAC,KAAsB;IAC5C,IAAI,CAAC,SAAS,EAAE;QAAE,OAAO;IACzB,IAAI,CAAC,KAAK,CAAC,SAAS;QAAE,OAAO;IAE7B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;QACrB,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACpC,WAAW,GAAG,IAAI,CAAC;QACrB,CAAC;QACD,MAAM,OAAO,GAAG,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,kBAAkB,EAAE,GAAG,CAAC,CAAC;QACjE,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,QAAQ,CAAC,CAAC;QAC3C,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;IAC7D,CAAC;IAAC,MAAM,CAAC;QACP,yCAAyC;IAC3C,CAAC;AACH,CAAC;AAED,gFAAgF;AAEhF,qEAAqE;AACrE,MAAM,UAAU,cAAc;IAC5B,WAAW,GAAG,KAAK,CAAC;AACtB,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,eAAe,CAAC,SAAiB;IAC/C,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC,kBAAkB,EAAE,GAAG,CAAC,CAAC;IAC3D,OAAO,IAAI,CAAC,MAAM,EAAE,EAAE,GAAG,OAAO,QAAQ,CAAC,CAAC;AAC5C,CAAC"}
|
|
@@ -35,16 +35,6 @@ export type EventMap = {
|
|
|
35
35
|
name: SessionName;
|
|
36
36
|
tokens: number;
|
|
37
37
|
};
|
|
38
|
-
/**
|
|
39
|
-
* Emitted when a session subprocess closes outside an explicit stop() —
|
|
40
|
-
* v0.14.1 root-cause observability for the stale-session class of bugs.
|
|
41
|
-
* `code` is the OS exit code (null when killed by signal). The reaper in
|
|
42
|
-
* SessionManager.sendMessage restarts the subprocess on the next turn.
|
|
43
|
-
*/
|
|
44
|
-
'session.died': {
|
|
45
|
-
name: SessionName;
|
|
46
|
-
code: number | null;
|
|
47
|
-
};
|
|
48
38
|
/** Emitted on every tool_use event within a session turn. */
|
|
49
39
|
'session.tool-use': {
|
|
50
40
|
name: SessionName;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-bus.js","sourceRoot":"","sources":["../../../src/observability/event-bus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,iFAAiF;AAEjF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,GAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"event-bus.js","sourceRoot":"","sources":["../../../src/observability/event-bus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,iFAAiF;AAEjF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,GAAY,CAAC;AA6BvD,iFAAiF;AAEjF,MAAM,OAAO,QAAQ;IACF,SAAS,GAAG,IAAI,GAAG,EAAiD,CAAC;IAEtF;;;OAGG;IACH,EAAE,CAA2B,KAAQ,EAAE,QAAqB;QAC1D,IAAI,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACpC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,GAAG,GAAG,IAAI,GAAG,EAAE,CAAC;YAChB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QACjC,CAAC;QACD,wEAAwE;QACvE,GAAwB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAExC,OAAO,GAAG,EAAE;YACT,GAAwB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC7C,CAAC,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,IAAI,CAA2B,KAAQ,EAAE,OAAoB;QAC3D,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACtC,IAAI,CAAC,GAAG;YAAE,OAAO;QACjB,KAAK,MAAM,QAAQ,IAAI,GAAG,EAAE,CAAC;YAC1B,QAAwB,CAAC,OAAO,CAAC,CAAC;QACrC,CAAC;IACH,CAAC;IAED,wDAAwD;IACxD,IAAI,aAAa;QACf,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC;YAC1C,KAAK,IAAI,GAAG,CAAC,IAAI,CAAC;QACpB,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;CACF"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Insight + greeting filters applied to model output before it's emitted to
|
|
3
|
+
* the OpenAI-compat client. Three pure transforms, gated by env flags:
|
|
4
|
+
*
|
|
5
|
+
* 1. `stripEmptyInsights` — v0.14.0. Removes ★ Insight ─ blocks that are
|
|
6
|
+
* empty, filler ("memory loaded"), or oversized. Server-side backstop
|
|
7
|
+
* for SOUL.md NO_EMPTY_INSIGHT.
|
|
8
|
+
*
|
|
9
|
+
* 2. `stripGreeting` — v0.18 Slice 1 / Track A3. Strips opener lines like
|
|
10
|
+
* "Hey A1.", "Hi A1,", "Sure thing A1 —" from the very start of an
|
|
11
|
+
* assistant message. Belt-and-suspenders for SOUL.md NO_GREET.
|
|
12
|
+
* Telemetry event: `no_greet_strip`.
|
|
13
|
+
*
|
|
14
|
+
* 3. `normalizeInsightFences` — v0.17 F2. Rewrites long U+2500 fences
|
|
15
|
+
* to exactly 3 chars so mobile Telegram doesn't wrap the divider.
|
|
16
|
+
*
|
|
17
|
+
* Pure transforms — no I/O. `stripEmptyInsights` and `stripGreeting`
|
|
18
|
+
* emit trajectory events when they remove content, but trajectory is
|
|
19
|
+
* append-only and observability-only.
|
|
20
|
+
*
|
|
21
|
+
* Apply order: strip empty → strip greeting → normalize fences. Empty
|
|
22
|
+
* blocks must be removed before fence normalization so the normalized
|
|
23
|
+
* block doesn't tempt the next emit pass to keep it.
|
|
24
|
+
*/
|
|
25
|
+
export declare function stripEmptyInsights(text: string, sessionId: string): string;
|
|
26
|
+
export declare function stripGreeting(text: string, sessionId: string): string;
|
|
27
|
+
export declare function isInsightNormalizeEnabled(): boolean;
|
|
28
|
+
export declare function normalizeInsightFences(text: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* Apply all three filters in the canonical order:
|
|
31
|
+
* strip empty insights → strip greeting → normalize fences.
|
|
32
|
+
*
|
|
33
|
+
* Call this at every emit site instead of stitching the three calls by
|
|
34
|
+
* hand. Single source of truth for the order.
|
|
35
|
+
*/
|
|
36
|
+
export declare function applyInsightAndGreetingFilters(text: string, sessionId: string): string;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Insight + greeting filters applied to model output before it's emitted to
|
|
3
|
+
* the OpenAI-compat client. Three pure transforms, gated by env flags:
|
|
4
|
+
*
|
|
5
|
+
* 1. `stripEmptyInsights` — v0.14.0. Removes ★ Insight ─ blocks that are
|
|
6
|
+
* empty, filler ("memory loaded"), or oversized. Server-side backstop
|
|
7
|
+
* for SOUL.md NO_EMPTY_INSIGHT.
|
|
8
|
+
*
|
|
9
|
+
* 2. `stripGreeting` — v0.18 Slice 1 / Track A3. Strips opener lines like
|
|
10
|
+
* "Hey A1.", "Hi A1,", "Sure thing A1 —" from the very start of an
|
|
11
|
+
* assistant message. Belt-and-suspenders for SOUL.md NO_GREET.
|
|
12
|
+
* Telemetry event: `no_greet_strip`.
|
|
13
|
+
*
|
|
14
|
+
* 3. `normalizeInsightFences` — v0.17 F2. Rewrites long U+2500 fences
|
|
15
|
+
* to exactly 3 chars so mobile Telegram doesn't wrap the divider.
|
|
16
|
+
*
|
|
17
|
+
* Pure transforms — no I/O. `stripEmptyInsights` and `stripGreeting`
|
|
18
|
+
* emit trajectory events when they remove content, but trajectory is
|
|
19
|
+
* append-only and observability-only.
|
|
20
|
+
*
|
|
21
|
+
* Apply order: strip empty → strip greeting → normalize fences. Empty
|
|
22
|
+
* blocks must be removed before fence normalization so the normalized
|
|
23
|
+
* block doesn't tempt the next emit pass to keep it.
|
|
24
|
+
*/
|
|
25
|
+
import { emit as emitTrajectory } from '../lib/trajectory.js';
|
|
26
|
+
// ─── Empty-insight stripper ───────────────────────────────────────────────────
|
|
27
|
+
// v0.18 Slice 1: use `[ \t]*` (horizontal whitespace) instead of `\s*`
|
|
28
|
+
// around the opening and closing fence newlines. The original v0.14.0
|
|
29
|
+
// regex used `\s*` which crosses newlines, causing an empty insight
|
|
30
|
+
// followed by another insight (Hey A1 → empty insight → substantive
|
|
31
|
+
// insight) to swallow BOTH blocks as one huge non-greedy match instead
|
|
32
|
+
// of stripping the empty one and keeping the substantive one. Switching
|
|
33
|
+
// to horizontal-only whitespace anchors each fence to its own line.
|
|
34
|
+
const INSIGHT_BLOCK_RE = /★\s*Insight\s*[─\-]{3,}[ \t]*\n([\s\S]*?)\n[ \t]*[─\-]{3,}/g;
|
|
35
|
+
const INSIGHT_FILLER_RE = /^(memory loaded|session active|ready|loading|done)\.?$/i;
|
|
36
|
+
export function stripEmptyInsights(text, sessionId) {
|
|
37
|
+
if (!text || text.indexOf('★') === -1)
|
|
38
|
+
return text;
|
|
39
|
+
let stripped = false;
|
|
40
|
+
let reason = '';
|
|
41
|
+
let result = text.replace(INSIGHT_BLOCK_RE, (match, inner) => {
|
|
42
|
+
const innerTrimmed = (inner ?? '').trim();
|
|
43
|
+
let shouldStrip = false;
|
|
44
|
+
if (innerTrimmed.length < 20) {
|
|
45
|
+
shouldStrip = true;
|
|
46
|
+
reason = 'empty-or-short';
|
|
47
|
+
}
|
|
48
|
+
else if (INSIGHT_FILLER_RE.test(innerTrimmed)) {
|
|
49
|
+
shouldStrip = true;
|
|
50
|
+
reason = 'filler-phrase';
|
|
51
|
+
}
|
|
52
|
+
else if (match.length > 600) {
|
|
53
|
+
shouldStrip = true;
|
|
54
|
+
reason = 'oversized';
|
|
55
|
+
}
|
|
56
|
+
if (shouldStrip) {
|
|
57
|
+
stripped = true;
|
|
58
|
+
return '';
|
|
59
|
+
}
|
|
60
|
+
return match;
|
|
61
|
+
});
|
|
62
|
+
if (stripped) {
|
|
63
|
+
result = result.replace(/\n{3,}/g, '\n\n').trim();
|
|
64
|
+
try {
|
|
65
|
+
emitTrajectory('insight_strip', { event: 'insight-strip', reason, lenIn: text.length, lenOut: result.length }, sessionId);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// Trajectory is observability — must never break production
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
// ─── NO_GREET stripper (Track A3) ─────────────────────────────────────────────
|
|
74
|
+
/**
|
|
75
|
+
* Match a greeting opener at the very start of the message. Examples:
|
|
76
|
+
* "Hey A1.\n..."
|
|
77
|
+
* "Hi A1,"
|
|
78
|
+
* "Sure, A1 — ..."
|
|
79
|
+
* "Hey there A1!"
|
|
80
|
+
* "Hello A1: ..."
|
|
81
|
+
*
|
|
82
|
+
* The pattern requires the greeting word + an optional separator + the
|
|
83
|
+
* literal token "A1" + a small trailing punctuation/whitespace set. It
|
|
84
|
+
* is anchored to the message start (`^`) with `s` flag DISABLED so that
|
|
85
|
+
* "A1" mid-paragraph is never matched (we only strip the opener line).
|
|
86
|
+
*
|
|
87
|
+
* The trailing `(?:\n+|\s*—\s*|\s*-\s*)` swallows the line-break (or
|
|
88
|
+
* em-dash continuation) that joined the greeting to the substantive
|
|
89
|
+
* reply, so the cleaned text starts at the real content.
|
|
90
|
+
*/
|
|
91
|
+
const GREETING_RE = /^(?:Hey there|Hey|Hi there|Hi|Hello|Sure thing|Sure|Of course|Okay|OK)[,!:.\s]+A1\b[.,!:]?(?:\n+|\s*—\s*|\s*-\s*)/i;
|
|
92
|
+
export function stripGreeting(text, sessionId) {
|
|
93
|
+
if (!text)
|
|
94
|
+
return text;
|
|
95
|
+
const match = text.match(GREETING_RE);
|
|
96
|
+
if (!match)
|
|
97
|
+
return text;
|
|
98
|
+
const result = text.slice(match[0].length).replace(/^\s+/, '');
|
|
99
|
+
try {
|
|
100
|
+
emitTrajectory('no_greet_strip', { matched: match[0].slice(0, 80), lenIn: text.length, lenOut: result.length }, sessionId);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// Trajectory is observability — must never break production
|
|
104
|
+
}
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
// ─── Insight fence normalization (v0.17 F2, unchanged) ───────────────────────
|
|
108
|
+
/** Short fence used in normalized output. 3 chars satisfies the
|
|
109
|
+
* extractor regex `[─—\-]{3,}` and avoids the wrap-to-`<hr>` glitch. */
|
|
110
|
+
const SHORT_FENCE = '───';
|
|
111
|
+
const INSIGHT_FENCE_RE = /(★\s*Insight\s*)[─—\-]{3,}([ \t]*\r?\n[\s\S]*?\r?\n[ \t]*)[─—\-]{3,}([ \t]*)$/gm;
|
|
112
|
+
export function isInsightNormalizeEnabled() {
|
|
113
|
+
return process.env.CC_OPENCLAW_INSIGHT_NORMALIZE !== '0';
|
|
114
|
+
}
|
|
115
|
+
export function normalizeInsightFences(text) {
|
|
116
|
+
if (!text)
|
|
117
|
+
return text;
|
|
118
|
+
if (!isInsightNormalizeEnabled())
|
|
119
|
+
return text;
|
|
120
|
+
if (text.indexOf('★') === -1)
|
|
121
|
+
return text;
|
|
122
|
+
return text.replace(INSIGHT_FENCE_RE, (_match, header, body, trailing) => `${header}${SHORT_FENCE}${body}${SHORT_FENCE}${trailing}`);
|
|
123
|
+
}
|
|
124
|
+
// ─── Composite filter ─────────────────────────────────────────────────────────
|
|
125
|
+
/**
|
|
126
|
+
* Apply all three filters in the canonical order:
|
|
127
|
+
* strip empty insights → strip greeting → normalize fences.
|
|
128
|
+
*
|
|
129
|
+
* Call this at every emit site instead of stitching the three calls by
|
|
130
|
+
* hand. Single source of truth for the order.
|
|
131
|
+
*/
|
|
132
|
+
export function applyInsightAndGreetingFilters(text, sessionId) {
|
|
133
|
+
if (!text)
|
|
134
|
+
return text;
|
|
135
|
+
let out = stripEmptyInsights(text, sessionId);
|
|
136
|
+
out = stripGreeting(out, sessionId);
|
|
137
|
+
out = normalizeInsightFences(out);
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=insight-format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"insight-format.js","sourceRoot":"","sources":["../../../src/openai-compat/insight-format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,IAAI,IAAI,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE9D,iFAAiF;AAEjF,uEAAuE;AACvE,sEAAsE;AACtE,oEAAoE;AACpE,oEAAoE;AACpE,uEAAuE;AACvE,wEAAwE;AACxE,oEAAoE;AACpE,MAAM,gBAAgB,GAAG,6DAA6D,CAAC;AACvF,MAAM,iBAAiB,GAAG,yDAAyD,CAAC;AAEpF,MAAM,UAAU,kBAAkB,CAAC,IAAY,EAAE,SAAiB;IAChE,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAEnD,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,MAAM,GAAG,EAAE,CAAC;IAEhB,IAAI,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC,KAAK,EAAE,KAAa,EAAE,EAAE;QACnE,MAAM,YAAY,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAC1C,IAAI,WAAW,GAAG,KAAK,CAAC;QAExB,IAAI,YAAY,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;YAC7B,WAAW,GAAG,IAAI,CAAC;YACnB,MAAM,GAAG,gBAAgB,CAAC;QAC5B,CAAC;aAAM,IAAI,iBAAiB,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;YAChD,WAAW,GAAG,IAAI,CAAC;YACnB,MAAM,GAAG,eAAe,CAAC;QAC3B,CAAC;aAAM,IAAI,KAAK,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;YAC9B,WAAW,GAAG,IAAI,CAAC;YACnB,MAAM,GAAG,WAAW,CAAC;QACvB,CAAC;QAED,IAAI,WAAW,EAAE,CAAC;YAChB,QAAQ,GAAG,IAAI,CAAC;YAChB,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC,CAAC,CAAC;IAEH,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QAClD,IAAI,CAAC;YACH,cAAc,CACZ,eAAe,EACf,EAAE,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,EAC7E,SAAS,CACV,CAAC;QACJ,CAAC;QAAC,MAAM,CAAC;YACP,4DAA4D;QAC9D,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,iFAAiF;AAEjF;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,GACf,oHAAoH,CAAC;AAEvH,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,SAAiB;IAC3D,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IACtC,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAExB,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC/D,IAAI,CAAC;QACH,cAAc,CACZ,gBAAgB,EAChB,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,EAC7E,SAAS,CACV,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,4DAA4D;IAC9D,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,gFAAgF;AAEhF;yEACyE;AACzE,MAAM,WAAW,GAAG,KAAK,CAAC;AAE1B,MAAM,gBAAgB,GACpB,iFAAiF,CAAC;AAEpF,MAAM,UAAU,yBAAyB;IACvC,OAAO,OAAO,CAAC,GAAG,CAAC,6BAA6B,KAAK,GAAG,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,IAAY;IACjD,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,IAAI,CAAC,yBAAyB,EAAE;QAAE,OAAO,IAAI,CAAC;IAC9C,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1C,OAAO,IAAI,CAAC,OAAO,CACjB,gBAAgB,EAChB,CAAC,MAAM,EAAE,MAAc,EAAE,IAAY,EAAE,QAAgB,EAAE,EAAE,CACzD,GAAG,MAAM,GAAG,WAAW,GAAG,IAAI,GAAG,WAAW,GAAG,QAAQ,EAAE,CAC5D,CAAC;AACJ,CAAC;AAED,iFAAiF;AAEjF;;;;;;GAMG;AACH,MAAM,UAAU,8BAA8B,CAAC,IAAY,EAAE,SAAiB;IAC5E,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,IAAI,GAAG,GAAG,kBAAkB,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IAC9C,GAAG,GAAG,aAAa,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IACpC,GAAG,GAAG,sBAAsB,CAAC,GAAG,CAAC,CAAC;IAClC,OAAO,GAAG,CAAC;AACb,CAAC"}
|