@threadcrumbs/threadnauts 1.2.0 → 1.4.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.
- package/NOTICES.md +96 -0
- package/bin/threadnauts.mjs +13 -7
- package/dist/assets/CREDITS.md +1 -4
- package/dist/assets/arrakesh-B6KYoRjN.svg +1 -0
- package/dist/assets/index-CJXuDA5A.js +2517 -0
- package/dist/assets/index-LNc3c53f.css +1 -0
- package/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/dist/assets/inter-tight-latin-ext-wght-normal-B4u9PlvR.woff2 +0 -0
- package/dist/assets/inter-tight-latin-wght-normal-DX-nOvPD.woff2 +0 -0
- package/dist/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/dist/assets/shared-CudSvZVV.js +5015 -0
- package/dist/index.html +3 -3
- package/dist/pro-manifest.json +3 -3
- package/package.json +3 -3
- package/server/accents.mjs +27 -0
- package/server/api.mjs +54 -5
- package/server/http-server.mjs +8 -0
- package/server/license.mjs +72 -69
- package/server/pro-content.mjs +2 -1
- package/server/source-checkout.mjs +11 -0
- package/server/threadbare.mjs +252 -0
- package/server/threadgauge.mjs +129 -42
- package/dist/assets/arrakesh-BW9Bb56U.svg +0 -1
- package/dist/assets/index-C46d6g6p.js +0 -2464
- package/dist/assets/index-PzTcyxAK.css +0 -1
- package/dist/assets/lighting/README.md +0 -10
- package/dist/assets/lighting/studio_small_09_1k.hdr +0 -0
- package/dist/assets/shared-DiTtF988.js +0 -4978
- package/dist/audio/README.md +0 -25
package/server/threadgauge.mjs
CHANGED
|
@@ -1,42 +1,129 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
1
|
+
import { DETAIL_LIMITS, clip, clipBlock } from '@threadcrumbs/common/lib/telemetry-fields'
|
|
2
|
+
|
|
3
|
+
const FIRST_PORT = 5274
|
|
4
|
+
const PORTS_TO_TRY = 20
|
|
5
|
+
const LOOPBACKS = ['127.0.0.1', '::1']
|
|
6
|
+
const PROBE_TIMEOUT_MS = 500
|
|
7
|
+
const SESSIONS_TIMEOUT_MS = 2000
|
|
8
|
+
const HISTORY_TIMEOUT_MS = 8000
|
|
9
|
+
const REUSE_OLDER_MS = 15000
|
|
10
|
+
const REUSE_FOUND_MS = 15000
|
|
11
|
+
const ASK_QUESTIONS = 4
|
|
12
|
+
const ASK_OPTIONS = 8
|
|
13
|
+
const LABEL_MAX = 160
|
|
14
|
+
const STATUS_MAX = 40
|
|
15
|
+
const ASK_ID_MAX = 128
|
|
16
|
+
|
|
17
|
+
const originOf = (host, port) => `http://${host.includes(':') ? `[${host}]` : host}:${port}`
|
|
18
|
+
|
|
19
|
+
async function answersAsThreadgauge(origin, fetch) {
|
|
20
|
+
try {
|
|
21
|
+
const response = await fetch(`${origin}/api/about`, { signal: AbortSignal.timeout(PROBE_TIMEOUT_MS) })
|
|
22
|
+
return (await response.json())?.app === 'threadgauge'
|
|
23
|
+
} catch {
|
|
24
|
+
return false
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function locateThreadgauge({ fetch = globalThis.fetch, first = FIRST_PORT, tries = PORTS_TO_TRY, hosts = LOOPBACKS } = {}) {
|
|
29
|
+
const probes = []
|
|
30
|
+
for (let port = first; port < first + tries; port++) for (const host of hosts) probes.push({ port, origin: originOf(host, port) })
|
|
31
|
+
const found = await Promise.all(probes.map(({ origin }) => answersAsThreadgauge(origin, fetch)))
|
|
32
|
+
const index = found.indexOf(true)
|
|
33
|
+
return index === -1 ? null : { url: `http://localhost:${probes[index].port}/`, origin: probes[index].origin }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const findThreadgauge = async (options) => (await locateThreadgauge(options))?.url ?? null
|
|
37
|
+
|
|
38
|
+
const text = (max) => (v) => (typeof v === 'string' ? clip(v, max) : null)
|
|
39
|
+
const block = (max) => (v) => (typeof v === 'string' ? clipBlock(v, max) : null)
|
|
40
|
+
const label = text(LABEL_MAX)
|
|
41
|
+
const words = block(DETAIL_LIMITS.errorText)
|
|
42
|
+
const number = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : null)
|
|
43
|
+
const list = (v, max, keep) => (Array.isArray(v) ? v.filter((x) => x && typeof x === 'object').slice(0, max).map(keep) : null)
|
|
44
|
+
|
|
45
|
+
export const isAskId = (v) => typeof v === 'string' && v.length > 0 && v.length <= ASK_ID_MAX && !/[\u0000-\u001f\u007f]/.test(v)
|
|
46
|
+
|
|
47
|
+
function keepAsk(ask) {
|
|
48
|
+
if (!ask || typeof ask !== 'object') return null
|
|
49
|
+
if (ask.type === 'question') {
|
|
50
|
+
const option = (o) => ({ label: label(o.label) ?? '', description: words(o.description) ?? '' })
|
|
51
|
+
const question = (q) => ({ header: label(q.header) ?? '', text: words(q.text) ?? '', options: list(q.options, ASK_OPTIONS, option) ?? [], multiple: q.multiple === true })
|
|
52
|
+
return { type: 'question', questions: list(ask.questions, ASK_QUESTIONS, question) ?? [] }
|
|
53
|
+
}
|
|
54
|
+
if (ask.type === 'approval') return { type: 'approval', tool: label(ask.tool) ?? '', target: label(ask.target) ?? '', kind: label(ask.kind) ?? 'other' }
|
|
55
|
+
if (ask.type === 'error') return { type: 'error', text: words(ask.text) ?? '' }
|
|
56
|
+
return { type: 'unknown' }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const SESSION_FIELDS = {
|
|
60
|
+
status: text(STATUS_MAX),
|
|
61
|
+
statusReason: text(STATUS_MAX),
|
|
62
|
+
ask: keepAsk,
|
|
63
|
+
context: (c) => (c && typeof c === 'object' ? { used: number(c.used), limit: number(c.limit) } : null),
|
|
64
|
+
tool: (t) => (t && typeof t === 'object' ? { name: label(t.name) ?? '', startedAt: number(t.startedAt), target: label(t.target) ?? '', kind: label(t.kind) ?? 'other' } : null),
|
|
65
|
+
turnStartedAt: number,
|
|
66
|
+
workingSubagents: (subs) => list(subs, DETAIL_LIMITS.subagents, (s) => ({ id: label(s.id) ?? '', name: label(s.name) ?? '' })),
|
|
67
|
+
awaiting: (threads) => list(threads, DETAIL_LIMITS.subagents, (t) => ({ id: label(t.id) ?? '', title: label(t.title) ?? '' })),
|
|
68
|
+
subagents: number,
|
|
69
|
+
live: (v) => v === true,
|
|
70
|
+
plan: (p) => (p && typeof p === 'object' ? { done: number(p.done), total: number(p.total), current: label(p.current) ?? '' } : null),
|
|
71
|
+
tokens: (t) => (t && typeof t === 'object' ? { output: number(t.output) } : null),
|
|
72
|
+
lines: (l) => (l && typeof l === 'object' ? { added: number(l.added), removed: number(l.removed) } : null),
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function keepSessionFields(session) {
|
|
76
|
+
const kept = {}
|
|
77
|
+
for (const [field, keep] of Object.entries(SESSION_FIELDS)) if (field in session) kept[field] = keep(session[field])
|
|
78
|
+
return kept
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function withoutText(ask) {
|
|
82
|
+
if (!ask) return ask
|
|
83
|
+
if (ask.type === 'question') return { type: 'question', questions: [] }
|
|
84
|
+
if (ask.type === 'approval') return { type: 'approval', tool: ask.tool, target: '', kind: ask.kind }
|
|
85
|
+
if (ask.type === 'error') return { type: 'error', text: '' }
|
|
86
|
+
return ask
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function keepForAnotherDevice(kept) {
|
|
90
|
+
const out = { ...kept }
|
|
91
|
+
if ('ask' in out) out.ask = withoutText(out.ask)
|
|
92
|
+
if (out.tool) out.tool = { ...out.tool, target: '' }
|
|
93
|
+
if (out.awaiting) out.awaiting = out.awaiting.map(({ id }) => ({ id, title: '' }))
|
|
94
|
+
if (out.plan) out.plan = { ...out.plan, current: '' }
|
|
95
|
+
return out
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const listOf = async (response) => {
|
|
99
|
+
const { sessions = [] } = response.ok ? await response.json() : {}
|
|
100
|
+
return (Array.isArray(sessions) ? sessions : []).filter((s) => s && typeof s.id === 'string')
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function threadgaugeSessions({ fetch = globalThis.fetch, find = () => locateThreadgauge({ fetch }), now = Date.now } = {}) {
|
|
104
|
+
let found = { at: -Infinity, where: null }
|
|
105
|
+
let older = { at: -Infinity, origin: null, list: [] }
|
|
106
|
+
async function olderList(origin) {
|
|
107
|
+
if (older.origin === origin && now() - older.at <= REUSE_OLDER_MS) return older.list
|
|
108
|
+
const list = await listOf(await fetch(`${origin}/api/history`, { signal: AbortSignal.timeout(HISTORY_TIMEOUT_MS) }))
|
|
109
|
+
older = { at: now(), origin, list }
|
|
110
|
+
return list
|
|
111
|
+
}
|
|
112
|
+
return async function sessions({ id = null, all = false, anotherDevice = false } = {}) {
|
|
113
|
+
if (now() - found.at > REUSE_FOUND_MS) found = { at: now(), where: await find() }
|
|
114
|
+
const where = found.where
|
|
115
|
+
if (!where) return { url: null, sessions: {} }
|
|
116
|
+
if (!id && !all) return { url: where.url, sessions: {} }
|
|
117
|
+
try {
|
|
118
|
+
const live = await listOf(await fetch(`${where.origin}/api/sessions`, { signal: AbortSignal.timeout(SESSIONS_TIMEOUT_MS) }))
|
|
119
|
+
const listed = new Set(live.map((s) => s.id))
|
|
120
|
+
const everything = all ? [...live, ...(await olderList(where.origin)).filter((s) => !listed.has(s.id))] : live
|
|
121
|
+
const wanted = everything.filter((s) => all || s.id === id)
|
|
122
|
+
const keep = (s) => (anotherDevice ? keepForAnotherDevice(keepSessionFields(s)) : keepSessionFields(s))
|
|
123
|
+
return { url: where.url, sessions: Object.fromEntries(wanted.map((s) => [s.id, keep(s)])) }
|
|
124
|
+
} catch {
|
|
125
|
+
found = { at: -Infinity, where: null }
|
|
126
|
+
return { url: null, sessions: {} }
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" class="world-icon" viewBox="0 0 100 100" aria-hidden="true"><defs><clipPath id="wi-frameg"><circle cx="50" cy="50" r="50"/></clipPath><linearGradient id="wi-l0" x1="0" y1="0" x2="0" y2="64" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#86a6c6"/><stop offset="0.58" stop-color="#c7c9c5"/><stop offset="1" stop-color="#f2e0c4"/></linearGradient><radialGradient id="wi-r1" cx="0.5" cy="0.5" r="0.5" fx="0.5" fy="0.5"><stop offset="0" stop-color="#f6e9d6" stop-opacity="0.3"/><stop offset="0.4" stop-color="#f6e9d6" stop-opacity="0.13"/><stop offset="1" stop-color="#f6e9d6" stop-opacity="0"/></radialGradient><radialGradient id="wi-r2" cx="0.5" cy="0.5" r="0.5" fx="0.5" fy="0.5"><stop offset="0" stop-color="#fff6e4" stop-opacity="0.6"/><stop offset="0.4" stop-color="#fff6e4" stop-opacity="0.25"/><stop offset="1" stop-color="#fff6e4" stop-opacity="0"/></radialGradient><clipPath id="wi-c3"><circle cx="31.1" cy="33.2" r="6.03"/></clipPath><radialGradient id="wi-r4" cx="0.66" cy="0.4" r="0.72" fx="0.66" fy="0.4"><stop offset="0" stop-color="#e3ddd3"/><stop offset="0.7" stop-color="#dcd4c8"/><stop offset="1" stop-color="#aca59c"/></radialGradient><linearGradient id="wi-l5" x1="0" y1="0" x2="1" y2="0"><stop offset="0" stop-color="#000" stop-opacity="0.32"/><stop offset="0.5" stop-color="#000" stop-opacity="0.1"/><stop offset="0.78" stop-color="#000" stop-opacity="0"/></linearGradient><clipPath id="wi-c6"><circle cx="43.7" cy="28" r="4.92"/></clipPath><radialGradient id="wi-r7" cx="0.66" cy="0.4" r="0.72" fx="0.66" fy="0.4"><stop offset="0" stop-color="#d3c6b6"/><stop offset="0.7" stop-color="#c8b8a4"/><stop offset="1" stop-color="#9c9080"/></radialGradient><linearGradient id="wi-l8" x1="0" y1="0" x2="1" y2="0"><stop offset="0" stop-color="#000" stop-opacity="0.32"/><stop offset="0.5" stop-color="#000" stop-opacity="0.1"/><stop offset="0.78" stop-color="#000" stop-opacity="0"/></linearGradient><radialGradient id="wi-r9" cx="0.5" cy="0.5" r="0.5" fx="0.5" fy="0.5"><stop offset="0" stop-color="#ffffff"/><stop offset="0.55" stop-color="#fffbf4"/><stop offset="1" stop-color="#fff6e4"/></radialGradient><linearGradient id="wi-la" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#f2dcb5"/><stop offset="0.35" stop-color="#ebcf9e"/><stop offset="1" stop-color="#c7a06e"/></linearGradient><linearGradient id="wi-lb" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#f3deb7"/><stop offset="0.35" stop-color="#eccf9c"/><stop offset="1" stop-color="#c29965"/></linearGradient><linearGradient id="wi-lc" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#f5e1ba"/><stop offset="0.35" stop-color="#ecce9a"/><stop offset="1" stop-color="#ba8f59"/></linearGradient><linearGradient id="wi-ld" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#f6e3bc"/><stop offset="0.35" stop-color="#ecce98"/><stop offset="1" stop-color="#b4864e"/></linearGradient><radialGradient id="wi-re" cx="0.5" cy="0.5" r="0.5" fx="0.5" fy="0.5"><stop offset="0" stop-color="#ff6a2a" stop-opacity="0.9"/><stop offset="0.4" stop-color="#ff6a2a" stop-opacity="0.38"/><stop offset="1" stop-color="#ff6a2a" stop-opacity="0"/></radialGradient><linearGradient id="wi-lf" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#ead0a8" stop-opacity="0"/><stop offset="0.55" stop-color="#ead0a8" stop-opacity="0.35"/><stop offset="1" stop-color="#ead0a8" stop-opacity="0"/></linearGradient><radialGradient id="wi-rh" cx="0.5" cy="0.5" r="0.5" fx="0.5" fy="0.5"><stop offset="0.62" stop-color="#000" stop-opacity="0"/><stop offset="1" stop-color="#000" stop-opacity="0.3"/></radialGradient></defs><g clip-path="url(#wi-frameg)"><rect width="100" height="100" fill="url(#wi-l0)"/><ellipse cx="75" cy="62" rx="70" ry="14" fill="url(#wi-r1)"/><circle cx="75" cy="24" r="16.2" fill="url(#wi-r2)"/><circle cx="75" cy="24" r="4.5" fill="url(#wi-r9)"/><g opacity="0.6"><circle cx="31.1" cy="33.2" r="6.03" fill="url(#wi-r4)"/><g clip-path="url(#wi-c3)"><ellipse cx="30.94" cy="36.85" rx="0.49" ry="0.35" fill="#9a8e7e" fill-opacity="0.42" transform="rotate(12.36 31.1 33.2)"/><ellipse cx="31.22" cy="29.7" rx="0.49" ry="0.24" fill="#9a8e7e" fill-opacity="0.42" transform="rotate(14.59 31.1 33.2)"/><ellipse cx="28.65" cy="34.93" rx="0.94" ry="0.52" fill="#9a8e7e" fill-opacity="0.42" transform="rotate(-5.2 31.1 33.2)"/><ellipse cx="30.21" cy="34.27" rx="0.95" ry="0.55" fill="#9a8e7e" fill-opacity="0.42" transform="rotate(-17.63 31.1 33.2)"/><ellipse cx="27.06" cy="32.41" rx="1.39" ry="1.07" fill="#9a8e7e" fill-opacity="0.42" transform="rotate(-0.55 31.1 33.2)"/><ellipse cx="30.82" cy="32.39" rx="1.07" ry="0.56" fill="#9a8e7e" fill-opacity="0.42" transform="rotate(-29.66 31.1 33.2)"/><ellipse cx="34.3" cy="35.57" rx="1.2" ry="0.6" fill="#9a8e7e" fill-opacity="0.42" transform="rotate(-11.43 31.1 33.2)"/><ellipse cx="28.24" cy="34.89" rx="1.03" ry="0.66" fill="#9a8e7e" fill-opacity="0.42" transform="rotate(10.85 31.1 33.2)"/><ellipse cx="29.59" cy="33.8" rx="1.93" ry="1.33" fill="#4e463e" fill-opacity=".7"/><circle cx="29.07" cy="31.74" r="0.49" fill="#aca59c"/><path d="M28.59 31.74A0.49 0.49 0 0 0 29.56 31.74" fill="none" stroke="#e7e1d9" stroke-width="0.17"/><circle cx="34.33" cy="33.54" r="0.89" fill="#aca59c"/><path d="M33.44 33.54A0.89 0.89 0 0 0 35.22 33.54" fill="none" stroke="#e7e1d9" stroke-width="0.31"/><circle cx="29.71" cy="34.16" r="0.88" fill="#aca59c"/><path d="M28.83 34.16A0.88 0.88 0 0 0 30.59 34.16" fill="none" stroke="#e7e1d9" stroke-width="0.31"/><rect x="25.07" y="27.17" width="12.07" height="12.07" fill="url(#wi-l5)"/></g><circle cx="31.1" cy="33.2" r="5.73" fill="none" stroke="#fff4e4" stroke-opacity=".35" stroke-width=".6"/></g><g opacity="0.6"><circle cx="43.7" cy="28" r="4.92" fill="url(#wi-r7)"/><g clip-path="url(#wi-c6)"><ellipse cx="43.4" cy="27.14" rx="0.98" ry="0.44" fill="#988874" fill-opacity="0.36" transform="rotate(-26.08 43.7 28)"/><ellipse cx="45.69" cy="26.96" rx="0.92" ry="0.73" fill="#988874" fill-opacity="0.36" transform="rotate(14.56 43.7 28)"/><ellipse cx="42.25" cy="26.6" rx="1.13" ry="0.64" fill="#988874" fill-opacity="0.36" transform="rotate(26.21 43.7 28)"/><ellipse cx="46.27" cy="30.8" rx="1.09" ry="0.51" fill="#988874" fill-opacity="0.36" transform="rotate(-7.29 43.7 28)"/><ellipse cx="42.87" cy="28.47" rx="0.69" ry="0.32" fill="#988874" fill-opacity="0.36" transform="rotate(-11.31 43.7 28)"/><ellipse cx="44.23" cy="25.53" rx="1.17" ry="0.83" fill="#988874" fill-opacity="0.36" transform="rotate(27.81 43.7 28)"/><ellipse cx="43.69" cy="25.53" rx="0.54" ry="0.41" fill="#988874" fill-opacity="0.36" transform="rotate(-2.78 43.7 28)"/><ellipse cx="46.72" cy="26.08" rx="1.14" ry="0.51" fill="#988874" fill-opacity="0.36" transform="rotate(-1.01 43.7 28)"/><circle cx="44.08" cy="26.22" r="0.54" fill="#9c9080"/><path d="M43.54 26.22A0.54 0.54 0 0 0 44.63 26.22" fill="none" stroke="#d9cdbf" stroke-width="0.19"/><circle cx="44.48" cy="30.7" r="0.75" fill="#9c9080"/><path d="M43.73 30.7A0.75 0.75 0 0 0 45.23 30.7" fill="none" stroke="#d9cdbf" stroke-width="0.26"/><circle cx="46.47" cy="29.29" r="0.71" fill="#9c9080"/><path d="M45.76 29.29A0.71 0.71 0 0 0 47.18 29.29" fill="none" stroke="#d9cdbf" stroke-width="0.25"/><rect x="38.78" y="23.08" width="9.85" height="9.85" fill="url(#wi-l8)"/></g><circle cx="43.7" cy="28" r="4.62" fill="none" stroke="#fff0dc" stroke-opacity=".35" stroke-width=".6"/></g><path d="M-3 64L-1.96 64L0.54 60.58L1.14 57.27L21.1 57.27L21.7 60.58L24.2 64L31.17 64L33.67 60.01L34.27 55.38L62.07 55.38L62.67 60.01L65.17 64L71.65 64L74.15 60.33L74.75 56.42L97.19 56.42L97.79 60.33L100.29 64L103 64Z" fill="#b9926b"/><path d="M0.84 58.84H21.4M0.84 60.42H21.4M33.97 57.59H62.37M33.97 59.79H62.37M74.45 58.28H97.49M74.45 60.14H97.49" fill="none" stroke="#987858" stroke-width=".5" stroke-opacity=".7"/><rect x="0" y="56" width="100" height="10" fill="url(#wi-lf)"/><path d="M-2 62H102V70H-2Z" fill="#cca777"/><path d="M-2 64.2C4 60 20 60.3 32 63.6S64 59.4 102 63.3V102H-2Z" fill="url(#wi-la)"/><path d="M-2 64.2C4 60 20 60.3 32 63.6S64 59.4 102 63.3" fill="none" stroke="#f9ebcd" stroke-opacity=".55" stroke-width=".7"/><path d="M-2 70.8C24 64.5 40 64.95 52 69.9S84 63.6 102 69.45V102H-2Z" fill="url(#wi-lb)"/><path d="M-2 70.8C24 64.5 40 64.95 52 69.9S84 63.6 102 69.45" fill="none" stroke="#f9ebcd" stroke-opacity=".55" stroke-width=".7"/><path d="M-2 79.4C4 71 20 71.6 32 78.2S64 69.8 102 77.6V102H-2Z" fill="url(#wi-lc)"/><path d="M-2 79.4C4 71 20 71.6 32 78.2S64 69.8 102 77.6" fill="none" stroke="#f9ebcd" stroke-opacity=".55" stroke-width=".7"/><path d="M-2 91.2C24 80 40 80.8 52 89.6S84 78.4 102 88.8V102H-2Z" fill="url(#wi-ld)"/><path d="M-2 91.2C24 80 40 80.8 52 89.6S84 78.4 102 88.8" fill="none" stroke="#f9ebcd" stroke-opacity=".55" stroke-width=".7"/><ellipse cx="33.35" cy="89.98" rx="5.53" ry="0.88" fill="#8e6a46" fill-opacity="0.61"/><ellipse cx="10.83" cy="72.12" rx="3.84" ry="0.61" fill="#8e6a46" fill-opacity="0.61"/><ellipse cx="50.09" cy="89.96" rx="6.76" ry="1.55" fill="#8e6a46" fill-opacity="0.61"/><ellipse cx="80.31" cy="87.95" rx="6.35" ry="1.5" fill="#8e6a46" fill-opacity="0.61"/><ellipse cx="83.2" cy="87.75" rx="5.87" ry="0.73" fill="#c4643a" fill-opacity="0.5"/><ellipse cx="11.21" cy="96.11" rx="6.95" ry="1.55" fill="#c4643a" fill-opacity="0.5"/><ellipse cx="12.87" cy="94.71" rx="5.66" ry="1.56" fill="#c4643a" fill-opacity="0.5"/><ellipse cx="87.26" cy="91.64" rx="4.63" ry="1.65" fill="#c4643a" fill-opacity="0.5"/><path d="M82.7 72L83.3 69L83.55 66.5L83.9 65L84.3 67L84.8 68.75L85.3 72Z" fill="#6c4c31"/><path d="M83.9 65L84.3 67L84.8 68.75L85.3 72L84.3 72Z" fill="#9a6c46"/><path d="M12.57 74L13.23 70.7L13.51 67.95L13.89 66.3L14.33 68.5L14.88 70.43L15.43 74Z" fill="#6c4c31"/><path d="M13.89 66.3L14.33 68.5L14.88 70.43L15.43 74L14.33 74Z" fill="#9a6c46"/><path d="M23.79 92L24.81 86.9L25.24 82.65L25.83 80.1L26.51 83.5L27.36 86.48L28.21 92Z" fill="#6c4c31"/><path d="M25.83 80.1L26.51 83.5L27.36 86.48L28.21 92L26.51 92Z" fill="#9a6c46"/><path d="M77.12 93L77.66 90.84L79.28 89.76L81.44 90.12L82.88 91.74L83.06 93Z" fill="#9a6c46"/><path d="M77.66 90.84L79.28 89.76L80.54 91L78.56 91.92Z" fill="#b8987e"/><circle cx="50.72" cy="73.64" r="1.6" fill="url(#wi-re)"/><circle cx="15.9" cy="75.19" r="1.6" fill="url(#wi-re)"/><circle cx="52.57" cy="83.81" r="1.6" fill="url(#wi-re)"/><circle cx="19.26" cy="79.5" r="1.6" fill="url(#wi-re)"/><circle cx="85.88" cy="74.14" r="1.6" fill="url(#wi-re)"/><circle cx="59.31" cy="90.22" r="1.6" fill="url(#wi-re)"/><path d="M68.72 24.47l-2 0.3" stroke="#f0d8a8" stroke-opacity=".7" stroke-width=".35" stroke-linecap="round"/><path d="M17.44 42.28l-2 0.3" stroke="#f0d8a8" stroke-opacity=".7" stroke-width=".35" stroke-linecap="round"/><path d="M79.77 80.74l-2 0.3" stroke="#f0d8a8" stroke-opacity=".7" stroke-width=".35" stroke-linecap="round"/><path d="M7.88 95.69l-2 0.3" stroke="#f0d8a8" stroke-opacity=".7" stroke-width=".35" stroke-linecap="round"/><path d="M11.31 43.26l-2 0.3" stroke="#f0d8a8" stroke-opacity=".7" stroke-width=".35" stroke-linecap="round"/><path d="M50.55 14.52l-2 0.3" stroke="#f0d8a8" stroke-opacity=".7" stroke-width=".35" stroke-linecap="round"/><path d="M45.9 84.46l-2 0.3" stroke="#f0d8a8" stroke-opacity=".7" stroke-width=".35" stroke-linecap="round"/><path d="M50.7 84.42l-2 0.3" stroke="#f0d8a8" stroke-opacity=".7" stroke-width=".35" stroke-linecap="round"/><path d="M52.72 9.31l-2 0.3" stroke="#f0d8a8" stroke-opacity=".7" stroke-width=".35" stroke-linecap="round"/><path d="M37.63 45.33l-2 0.3" stroke="#f0d8a8" stroke-opacity=".7" stroke-width=".35" stroke-linecap="round"/><path d="M15.45 82.88l-2 0.3" stroke="#f0d8a8" stroke-opacity=".7" stroke-width=".35" stroke-linecap="round"/><path d="M86.3 33.96l-2 0.3" stroke="#f0d8a8" stroke-opacity=".7" stroke-width=".35" stroke-linecap="round"/><circle cx="91.1" cy="95.63" r="0.25" fill="#d8b090" fill-opacity=".85"/><circle cx="86.84" cy="59.61" r="0.34" fill="#d8b090" fill-opacity=".85"/><circle cx="63.36" cy="22.07" r="0.3" fill="#d8b090" fill-opacity=".85"/><rect width="100" height="100" fill="url(#wi-rh)"/></g><circle cx="50" cy="50" r="49.2" fill="none" stroke="#fff" stroke-opacity=".22" stroke-width="1.6"/></svg>
|