wotann 0.5.39 → 0.5.41
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/autopilot/completion-oracle.d.ts +71 -0
- package/dist/autopilot/completion-oracle.d.ts.map +1 -1
- package/dist/autopilot/completion-oracle.js +69 -2
- package/dist/autopilot/completion-oracle.js.map +1 -1
- package/dist/context/compaction-floor.d.ts +100 -0
- package/dist/context/compaction-floor.d.ts.map +1 -0
- package/dist/context/compaction-floor.js +94 -0
- package/dist/context/compaction-floor.js.map +1 -0
- package/dist/context/compaction.d.ts +27 -1
- package/dist/context/compaction.d.ts.map +1 -1
- package/dist/context/compaction.js +31 -0
- package/dist/context/compaction.js.map +1 -1
- package/dist/daemon/kairos-rpc.d.ts +1 -0
- package/dist/daemon/kairos-rpc.d.ts.map +1 -1
- package/dist/daemon/kairos-rpc.js +24 -0
- package/dist/daemon/kairos-rpc.js.map +1 -1
- package/dist/daemon/rpc-handlers/ports-rpc.d.ts +100 -0
- package/dist/daemon/rpc-handlers/ports-rpc.d.ts.map +1 -0
- package/dist/daemon/rpc-handlers/ports-rpc.js +278 -0
- package/dist/daemon/rpc-handlers/ports-rpc.js.map +1 -0
- package/dist/hooks/agentmemory-event-taxonomy.d.ts +144 -0
- package/dist/hooks/agentmemory-event-taxonomy.d.ts.map +1 -0
- package/dist/hooks/agentmemory-event-taxonomy.js +85 -0
- package/dist/hooks/agentmemory-event-taxonomy.js.map +1 -0
- package/dist/hooks/built-in.d.ts.map +1 -1
- package/dist/hooks/built-in.js +7 -3
- package/dist/hooks/built-in.js.map +1 -1
- package/dist/hooks/engine.d.ts +14 -0
- package/dist/hooks/engine.d.ts.map +1 -1
- package/dist/hooks/engine.js +62 -0
- package/dist/hooks/engine.js.map +1 -1
- package/dist/index.js +91 -2
- package/dist/index.js.map +1 -1
- package/dist/lib.d.ts +1 -1
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +1 -1
- package/dist/lib.js.map +1 -1
- package/dist/memory/injection-scanner.d.ts +78 -0
- package/dist/memory/injection-scanner.d.ts.map +1 -0
- package/dist/memory/injection-scanner.js +204 -0
- package/dist/memory/injection-scanner.js.map +1 -0
- package/dist/middleware/output-truncation.d.ts +18 -5
- package/dist/middleware/output-truncation.d.ts.map +1 -1
- package/dist/middleware/output-truncation.js +73 -1
- package/dist/middleware/output-truncation.js.map +1 -1
- package/dist/middleware/ttsr.d.ts +79 -3
- package/dist/middleware/ttsr.d.ts.map +1 -1
- package/dist/middleware/ttsr.js +136 -16
- package/dist/middleware/ttsr.js.map +1 -1
- package/dist/orchestration/autonomous.d.ts +182 -0
- package/dist/orchestration/autonomous.d.ts.map +1 -1
- package/dist/orchestration/autonomous.js +288 -1
- package/dist/orchestration/autonomous.js.map +1 -1
- package/dist/orchestration/ports-bridge.d.ts +325 -0
- package/dist/orchestration/ports-bridge.d.ts.map +1 -0
- package/dist/orchestration/ports-bridge.js +712 -0
- package/dist/orchestration/ports-bridge.js.map +1 -0
- package/dist/orchestration/textgrad-refinement.d.ts +123 -0
- package/dist/orchestration/textgrad-refinement.d.ts.map +1 -0
- package/dist/orchestration/textgrad-refinement.js +111 -0
- package/dist/orchestration/textgrad-refinement.js.map +1 -0
- package/dist/prompt/engine.d.ts +15 -0
- package/dist/prompt/engine.d.ts.map +1 -1
- package/dist/prompt/engine.js +20 -7
- package/dist/prompt/engine.js.map +1 -1
- package/dist/prompt/modules/capabilities.d.ts.map +1 -1
- package/dist/prompt/modules/capabilities.js +1 -0
- package/dist/prompt/modules/capabilities.js.map +1 -1
- package/dist/prompt/modules/caveman.d.ts.map +1 -1
- package/dist/prompt/modules/caveman.js +1 -0
- package/dist/prompt/modules/caveman.js.map +1 -1
- package/dist/prompt/modules/conventions.d.ts.map +1 -1
- package/dist/prompt/modules/conventions.js +1 -0
- package/dist/prompt/modules/conventions.js.map +1 -1
- package/dist/prompt/modules/identity.d.ts.map +1 -1
- package/dist/prompt/modules/identity.js +1 -0
- package/dist/prompt/modules/identity.js.map +1 -1
- package/dist/prompt/modules/index.d.ts +29 -0
- package/dist/prompt/modules/index.d.ts.map +1 -1
- package/dist/prompt/modules/index.js +51 -9
- package/dist/prompt/modules/index.js.map +1 -1
- package/dist/prompt/modules/llms-txt.d.ts.map +1 -1
- package/dist/prompt/modules/llms-txt.js +2 -4
- package/dist/prompt/modules/llms-txt.js.map +1 -1
- package/dist/prompt/modules/safety.d.ts.map +1 -1
- package/dist/prompt/modules/safety.js +1 -0
- package/dist/prompt/modules/safety.js.map +1 -1
- package/dist/prompt/modules/security.d.ts.map +1 -1
- package/dist/prompt/modules/security.js +1 -0
- package/dist/prompt/modules/security.js.map +1 -1
- package/dist/prompt/modules/skills.d.ts.map +1 -1
- package/dist/prompt/modules/skills.js +1 -0
- package/dist/prompt/modules/skills.js.map +1 -1
- package/dist/prompt/modules/tools.d.ts.map +1 -1
- package/dist/prompt/modules/tools.js +1 -0
- package/dist/prompt/modules/tools.js.map +1 -1
- package/dist/prompt/modules/user.d.ts.map +1 -1
- package/dist/prompt/modules/user.js +1 -0
- package/dist/prompt/modules/user.js.map +1 -1
- package/dist/providers/credential-pool.d.ts +45 -1
- package/dist/providers/credential-pool.d.ts.map +1 -1
- package/dist/providers/credential-pool.js +94 -1
- package/dist/providers/credential-pool.js.map +1 -1
- package/dist/providers/sticky-rotation-wire.d.ts +133 -0
- package/dist/providers/sticky-rotation-wire.d.ts.map +1 -0
- package/dist/providers/sticky-rotation-wire.js +185 -0
- package/dist/providers/sticky-rotation-wire.js.map +1 -0
- package/dist/runtime-hooks/ttsr-rule.d.ts +82 -0
- package/dist/runtime-hooks/ttsr-rule.d.ts.map +1 -0
- package/dist/runtime-hooks/ttsr-rule.js +207 -0
- package/dist/runtime-hooks/ttsr-rule.js.map +1 -0
- package/dist/runtime-hooks/ttsr-runner.d.ts +129 -0
- package/dist/runtime-hooks/ttsr-runner.d.ts.map +1 -0
- package/dist/runtime-hooks/ttsr-runner.js +193 -0
- package/dist/runtime-hooks/ttsr-runner.js.map +1 -0
- package/dist/runtime-hooks/ttsr-scope.d.ts +115 -0
- package/dist/runtime-hooks/ttsr-scope.d.ts.map +1 -0
- package/dist/runtime-hooks/ttsr-scope.js +378 -0
- package/dist/runtime-hooks/ttsr-scope.js.map +1 -0
- package/dist/sandbox/unified-exec.d.ts.map +1 -1
- package/dist/sandbox/unified-exec.js +6 -1
- package/dist/sandbox/unified-exec.js.map +1 -1
- package/dist/skills/cli-anything.d.ts.map +1 -1
- package/dist/skills/cli-anything.js +4 -1
- package/dist/skills/cli-anything.js.map +1 -1
- package/dist/skills/limits.d.ts +86 -0
- package/dist/skills/limits.d.ts.map +1 -0
- package/dist/skills/limits.js +140 -0
- package/dist/skills/limits.js.map +1 -0
- package/dist/skills/loader.d.ts +9 -0
- package/dist/skills/loader.d.ts.map +1 -1
- package/dist/skills/loader.js +29 -1
- package/dist/skills/loader.js.map +1 -1
- package/dist/storage/session-artifacts.d.ts +105 -0
- package/dist/storage/session-artifacts.d.ts.map +1 -0
- package/dist/storage/session-artifacts.js +198 -0
- package/dist/storage/session-artifacts.js.map +1 -0
- package/dist/tools/workspace-pack.d.ts +97 -0
- package/dist/tools/workspace-pack.d.ts.map +1 -0
- package/dist/tools/workspace-pack.js +228 -0
- package/dist/tools/workspace-pack.js.map +1 -0
- package/dist/tui/composer-history.d.ts +99 -0
- package/dist/tui/composer-history.d.ts.map +1 -0
- package/dist/tui/composer-history.js +169 -0
- package/dist/tui/composer-history.js.map +1 -0
- package/dist/ui/components/PromptInput.d.ts.map +1 -1
- package/dist/ui/components/PromptInput.js +80 -9
- package/dist/ui/components/PromptInput.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session artifacts — ported from Hmbown/DeepSeek-TUI's `artifacts.rs`
|
|
3
|
+
* (crates/tui/src/artifacts.rs).
|
|
4
|
+
*
|
|
5
|
+
* WHAT: Spill large tool outputs to disk so the in-context transcript can
|
|
6
|
+
* reference them by path/sha rather than inlining the full payload.
|
|
7
|
+
* `spill()` writes a tool output under
|
|
8
|
+
* `~/.wotann/artifacts/{sessionId}/{sha8}-{name}` and returns metadata
|
|
9
|
+
* the caller (transcript renderer) embeds inline. `read()` and `list()`
|
|
10
|
+
* are the round-trip / index APIs.
|
|
11
|
+
*
|
|
12
|
+
* WHY: Tool outputs over ~8 KB consume an outsized share of the context
|
|
13
|
+
* window and burn the prefix cache on every replay. Most large outputs
|
|
14
|
+
* are reference material — `ls -R`, `cat <big-file>`, `npm test` logs —
|
|
15
|
+
* that the model wants to fetch on demand, not re-read on every turn.
|
|
16
|
+
* Spilling them to disk lets the transcript carry a small reference
|
|
17
|
+
* block (`artifact_id`, path, sha, preview, size) instead of the body;
|
|
18
|
+
* the model can `read_artifact` to pull the full content back in only
|
|
19
|
+
* when it actually needs it.
|
|
20
|
+
*
|
|
21
|
+
* WHERE: Wire from the tool-result handler in the runtime. After a tool
|
|
22
|
+
* produces output:
|
|
23
|
+
* if (output.length >= threshold) {
|
|
24
|
+
* const { path, sizeBytes, sha } = artifacts.spill(sessionId, name, output);
|
|
25
|
+
* transcript.push(renderArtifactRef({ name, path, sizeBytes, sha, preview }));
|
|
26
|
+
* } else {
|
|
27
|
+
* transcript.push(output);
|
|
28
|
+
* }
|
|
29
|
+
*
|
|
30
|
+
* HOW: For each spill we compute a SHA-256 of the content, take the first
|
|
31
|
+
* 8 hex chars as the filename prefix, write `{sha8}-{name}` to the
|
|
32
|
+
* session directory, and write a `.meta.json` sidecar with the full
|
|
33
|
+
* metadata block. `read()` looks the artifact up by name and returns
|
|
34
|
+
* the body. `list()` enumerates the session directory and returns the
|
|
35
|
+
* metadata for each artifact.
|
|
36
|
+
*
|
|
37
|
+
* Safety:
|
|
38
|
+
* - Names are sanitised (only `[A-Za-z0-9._-]`; anything else becomes `_`).
|
|
39
|
+
* - Session ids are validated (must be non-empty alphanumeric / `-` / `_`).
|
|
40
|
+
* - We never follow symlinks out of the session directory.
|
|
41
|
+
* - All file operations are best-effort: if disk is full or the user
|
|
42
|
+
* deleted the session dir mid-flight, we surface the error rather
|
|
43
|
+
* than corrupting the in-context transcript.
|
|
44
|
+
*/
|
|
45
|
+
import { createHash } from "node:crypto";
|
|
46
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
47
|
+
import { homedir } from "node:os";
|
|
48
|
+
import { join } from "node:path";
|
|
49
|
+
/** Default threshold (in bytes) above which we spill rather than inline. */
|
|
50
|
+
export const DEFAULT_SPILL_THRESHOLD_BYTES = 8 * 1024; // 8 KB
|
|
51
|
+
/** Default artifacts root: `~/.wotann/artifacts`. */
|
|
52
|
+
export function defaultArtifactsRoot() {
|
|
53
|
+
return join(homedir(), ".wotann", "artifacts");
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* `SessionArtifacts` is the spill / read / list API.
|
|
57
|
+
*
|
|
58
|
+
* Construct one per process. The class is stateless beyond its root path
|
|
59
|
+
* — there's no in-memory index, so two `SessionArtifacts` instances over
|
|
60
|
+
* the same root see the same filesystem state.
|
|
61
|
+
*/
|
|
62
|
+
export class SessionArtifacts {
|
|
63
|
+
root;
|
|
64
|
+
threshold;
|
|
65
|
+
constructor(opts) {
|
|
66
|
+
this.root = opts?.root ?? defaultArtifactsRoot();
|
|
67
|
+
this.threshold = opts?.spillThresholdBytes ?? DEFAULT_SPILL_THRESHOLD_BYTES;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The spill threshold in bytes. Exposed so callers can decide whether to
|
|
71
|
+
* spill *before* paying the disk write — e.g. small outputs can stay
|
|
72
|
+
* inline without touching disk at all.
|
|
73
|
+
*/
|
|
74
|
+
get spillThreshold() {
|
|
75
|
+
return this.threshold;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Write `content` to disk under the session directory and return its
|
|
79
|
+
* metadata. The metadata block is what the transcript should reference
|
|
80
|
+
* — `path`, `sizeBytes`, `sha` — instead of inlining the body.
|
|
81
|
+
*
|
|
82
|
+
* Throws if `sessionId` or `name` are invalid (would otherwise let a
|
|
83
|
+
* malicious tool result escape the session directory).
|
|
84
|
+
*/
|
|
85
|
+
spill(sessionId, name, content) {
|
|
86
|
+
const sid = ensureValidSessionId(sessionId);
|
|
87
|
+
const safeName = sanitizeName(name);
|
|
88
|
+
const sha = sha256Hex(content);
|
|
89
|
+
const filename = `${sha.slice(0, 8)}-${safeName}`;
|
|
90
|
+
const sessionDir = join(this.root, sid);
|
|
91
|
+
mkdirSync(sessionDir, { recursive: true });
|
|
92
|
+
const absPath = join(sessionDir, filename);
|
|
93
|
+
writeFileSync(absPath, content, "utf8");
|
|
94
|
+
const sizeBytes = Buffer.byteLength(content, "utf8");
|
|
95
|
+
const meta = {
|
|
96
|
+
name: safeName,
|
|
97
|
+
path: absPath,
|
|
98
|
+
sizeBytes,
|
|
99
|
+
sha,
|
|
100
|
+
};
|
|
101
|
+
writeFileSync(`${absPath}.meta.json`, JSON.stringify(meta, null, 2), "utf8");
|
|
102
|
+
return {
|
|
103
|
+
name: safeName,
|
|
104
|
+
path: absPath,
|
|
105
|
+
sizeBytes,
|
|
106
|
+
sha,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Read back a previously-spilled artifact's content by session + name.
|
|
111
|
+
* Returns `null` if no artifact with that name exists in the session
|
|
112
|
+
* directory.
|
|
113
|
+
*/
|
|
114
|
+
read(sessionId, name) {
|
|
115
|
+
const sid = ensureValidSessionId(sessionId);
|
|
116
|
+
const safeName = sanitizeName(name);
|
|
117
|
+
const sessionDir = join(this.root, sid);
|
|
118
|
+
if (!existsSync(sessionDir))
|
|
119
|
+
return null;
|
|
120
|
+
let entries;
|
|
121
|
+
try {
|
|
122
|
+
entries = readdirSync(sessionDir);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
// Filenames look like `{sha8}-{name}`. We look for the matching trailing name.
|
|
128
|
+
const target = entries.find((e) => !e.endsWith(".meta.json") && e.endsWith(`-${safeName}`));
|
|
129
|
+
if (target === undefined)
|
|
130
|
+
return null;
|
|
131
|
+
try {
|
|
132
|
+
return readFileSync(join(sessionDir, target), "utf8");
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* List every artifact for `sessionId`. Returns an empty array if the
|
|
140
|
+
* session directory doesn't exist or is unreadable.
|
|
141
|
+
*
|
|
142
|
+
* The returned array is sorted by name for deterministic output.
|
|
143
|
+
*/
|
|
144
|
+
list(sessionId) {
|
|
145
|
+
const sid = ensureValidSessionId(sessionId);
|
|
146
|
+
const sessionDir = join(this.root, sid);
|
|
147
|
+
if (!existsSync(sessionDir))
|
|
148
|
+
return [];
|
|
149
|
+
let entries;
|
|
150
|
+
try {
|
|
151
|
+
entries = readdirSync(sessionDir);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return [];
|
|
155
|
+
}
|
|
156
|
+
const records = [];
|
|
157
|
+
for (const entry of entries) {
|
|
158
|
+
if (!entry.endsWith(".meta.json"))
|
|
159
|
+
continue;
|
|
160
|
+
try {
|
|
161
|
+
const raw = readFileSync(join(sessionDir, entry), "utf8");
|
|
162
|
+
const parsed = JSON.parse(raw);
|
|
163
|
+
records.push({
|
|
164
|
+
name: parsed.name,
|
|
165
|
+
path: parsed.path,
|
|
166
|
+
sizeBytes: parsed.sizeBytes,
|
|
167
|
+
sha: parsed.sha,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
// Skip unparseable sidecars — never abort the whole list.
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return records.sort((a, b) => a.name.localeCompare(b.name));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/** Lower-level helper exposed for tests. */
|
|
178
|
+
export function sanitizeName(input) {
|
|
179
|
+
if (input.length === 0)
|
|
180
|
+
return "artifact";
|
|
181
|
+
return input
|
|
182
|
+
.split("")
|
|
183
|
+
.map((c) => (/[A-Za-z0-9._-]/.test(c) ? c : "_"))
|
|
184
|
+
.join("");
|
|
185
|
+
}
|
|
186
|
+
function ensureValidSessionId(input) {
|
|
187
|
+
if (input.length === 0) {
|
|
188
|
+
throw new Error("session id must be non-empty");
|
|
189
|
+
}
|
|
190
|
+
if (!/^[A-Za-z0-9_-]+$/.test(input)) {
|
|
191
|
+
throw new Error(`session id must contain only [A-Za-z0-9_-]; got "${input}"`);
|
|
192
|
+
}
|
|
193
|
+
return input;
|
|
194
|
+
}
|
|
195
|
+
function sha256Hex(content) {
|
|
196
|
+
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=session-artifacts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-artifacts.js","sourceRoot":"","sources":["../../src/storage/session-artifacts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace pack — ported from UI-TARS-desktop's `workspace-pack.ts`
|
|
3
|
+
* (multimodal/tarko/context-engineer/src/node/workspace-pack.ts).
|
|
4
|
+
*
|
|
5
|
+
* WHAT: Recursive workspace packer for LLM consumption. Walks a directory
|
|
6
|
+
* tree, skips binary extensions and ignored directories, reads each
|
|
7
|
+
* eligible file in parallel, deduplicates, enforces per-file and total
|
|
8
|
+
* byte caps, and produces a single markdown-formatted string.
|
|
9
|
+
*
|
|
10
|
+
* WHY: When prompting an LLM with a workspace as context, naïve serialization
|
|
11
|
+
* (a recursive `cat` over the tree) explodes on `node_modules`, binaries,
|
|
12
|
+
* and oversize files.
|
|
13
|
+
* This packer applies the same filters the upstream pattern uses while
|
|
14
|
+
* keeping the output deterministic (sorted by relative path) so the
|
|
15
|
+
* prefix cache survives across repeated calls.
|
|
16
|
+
*
|
|
17
|
+
* WHERE: Use from any tool that needs to hand the model a workspace
|
|
18
|
+
* snapshot — context priming, code search corpora, "review this folder"
|
|
19
|
+
* flows. The result is immutable and safe to cache on disk.
|
|
20
|
+
*
|
|
21
|
+
* HOW: Pipeline is:
|
|
22
|
+
* 1. Walk the tree breadth-first with depth limit, collecting files
|
|
23
|
+
* that pass the binary-extension and ignored-dir filters.
|
|
24
|
+
* 2. Dedupe absolute paths (in case of overlapping inputs).
|
|
25
|
+
* 3. Read all files in parallel (`Promise.all`).
|
|
26
|
+
* 4. Enforce per-file size cap (emit `... (truncated)` marker on hit).
|
|
27
|
+
* 5. Enforce total-bytes cap (stop adding files once the total ceiling
|
|
28
|
+
* is reached; set `truncated: true`).
|
|
29
|
+
* 6. Render each file as `## <relpath>\n\n<content>\n\n`.
|
|
30
|
+
*
|
|
31
|
+
* This port simplifies the upstream by:
|
|
32
|
+
* - Dropping XML/`<directory>` framing in favour of plain markdown
|
|
33
|
+
* headings (more cache-friendly with chat models).
|
|
34
|
+
* - Adding `maxTotalBytes` enforcement (upstream had per-file only).
|
|
35
|
+
* - Skipping magic-byte sniffing — the extension filter covers the
|
|
36
|
+
* overwhelming majority of binaries we care about, and the cost of
|
|
37
|
+
* reading every file's prefix to sniff it is not worth the marginal
|
|
38
|
+
* accuracy on a workspace packer.
|
|
39
|
+
*
|
|
40
|
+
* Output format example:
|
|
41
|
+
*
|
|
42
|
+
* ## src/index.ts
|
|
43
|
+
*
|
|
44
|
+
* export function main() { ... }
|
|
45
|
+
*
|
|
46
|
+
* ## src/lib/helper.ts
|
|
47
|
+
*
|
|
48
|
+
* export const ANSWER = 42;
|
|
49
|
+
*/
|
|
50
|
+
/**
|
|
51
|
+
* Options for `packWorkspace`.
|
|
52
|
+
*
|
|
53
|
+
* All fields are optional; sensible defaults are applied for missing fields.
|
|
54
|
+
* Use `readonly` arrays so callers can pass frozen defaults safely.
|
|
55
|
+
*/
|
|
56
|
+
export interface WorkspacePackOptions {
|
|
57
|
+
/** Maximum recursion depth. Default: 8. */
|
|
58
|
+
readonly maxDepth?: number;
|
|
59
|
+
/** Per-file byte cap. Files larger than this are truncated. Default: 1 MB. */
|
|
60
|
+
readonly maxBytesPerFile?: number;
|
|
61
|
+
/** Total byte cap across the whole pack. Default: 8 MB. */
|
|
62
|
+
readonly maxTotalBytes?: number;
|
|
63
|
+
/** Lower-case extensions to skip (binary / asset files). */
|
|
64
|
+
readonly skipExtensions?: readonly string[];
|
|
65
|
+
/** Directory names to skip outright (build artefacts, vendor dirs). */
|
|
66
|
+
readonly skipDirs?: readonly string[];
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Result of `packWorkspace`.
|
|
70
|
+
*
|
|
71
|
+
* `truncated` flips to `true` when either:
|
|
72
|
+
* - any individual file hit `maxBytesPerFile` (its body was replaced with
|
|
73
|
+
* a `... (truncated)` marker), or
|
|
74
|
+
* - the total pack reached `maxTotalBytes` (later files were skipped).
|
|
75
|
+
*/
|
|
76
|
+
export interface WorkspacePackResult {
|
|
77
|
+
readonly content: string;
|
|
78
|
+
readonly fileCount: number;
|
|
79
|
+
readonly bytesRead: number;
|
|
80
|
+
readonly truncated: boolean;
|
|
81
|
+
}
|
|
82
|
+
/** Default binary / asset extensions to skip. Lower-case, dot-prefixed. */
|
|
83
|
+
export declare const DEFAULT_SKIP_EXTENSIONS: readonly string[];
|
|
84
|
+
/** Default directories to skip. */
|
|
85
|
+
export declare const DEFAULT_SKIP_DIRS: readonly string[];
|
|
86
|
+
/**
|
|
87
|
+
* Pack a workspace directory into a single LLM-friendly markdown string.
|
|
88
|
+
*
|
|
89
|
+
* The returned `content` lists each included file in sorted (relative-path)
|
|
90
|
+
* order with a `## <relpath>` heading followed by its contents. This is
|
|
91
|
+
* deterministic across runs so the prefix cache stays warm.
|
|
92
|
+
*
|
|
93
|
+
* @param rootPath Absolute path to the workspace root (or a single file).
|
|
94
|
+
* @param options Optional caps and skip lists. Sensible defaults applied.
|
|
95
|
+
*/
|
|
96
|
+
export declare function packWorkspace(rootPath: string, options?: WorkspacePackOptions): Promise<WorkspacePackResult>;
|
|
97
|
+
//# sourceMappingURL=workspace-pack.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-pack.d.ts","sourceRoot":"","sources":["../../src/tools/workspace-pack.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAKH;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACnC,2CAA2C;IAC3C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,8EAA8E;IAC9E,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,2DAA2D;IAC3D,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,4DAA4D;IAC5D,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5C,uEAAuE;IACvE,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAED,2EAA2E;AAC3E,eAAO,MAAM,uBAAuB,EAAE,SAAS,MAAM,EAqBpD,CAAC;AAEF,mCAAmC;AACnC,eAAO,MAAM,iBAAiB,EAAE,SAAS,MAAM,EAW9C,CAAC;AAsHF;;;;;;;;;GASG;AACH,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,mBAAmB,CAAC,CAmC9B"}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace pack — ported from UI-TARS-desktop's `workspace-pack.ts`
|
|
3
|
+
* (multimodal/tarko/context-engineer/src/node/workspace-pack.ts).
|
|
4
|
+
*
|
|
5
|
+
* WHAT: Recursive workspace packer for LLM consumption. Walks a directory
|
|
6
|
+
* tree, skips binary extensions and ignored directories, reads each
|
|
7
|
+
* eligible file in parallel, deduplicates, enforces per-file and total
|
|
8
|
+
* byte caps, and produces a single markdown-formatted string.
|
|
9
|
+
*
|
|
10
|
+
* WHY: When prompting an LLM with a workspace as context, naïve serialization
|
|
11
|
+
* (a recursive `cat` over the tree) explodes on `node_modules`, binaries,
|
|
12
|
+
* and oversize files.
|
|
13
|
+
* This packer applies the same filters the upstream pattern uses while
|
|
14
|
+
* keeping the output deterministic (sorted by relative path) so the
|
|
15
|
+
* prefix cache survives across repeated calls.
|
|
16
|
+
*
|
|
17
|
+
* WHERE: Use from any tool that needs to hand the model a workspace
|
|
18
|
+
* snapshot — context priming, code search corpora, "review this folder"
|
|
19
|
+
* flows. The result is immutable and safe to cache on disk.
|
|
20
|
+
*
|
|
21
|
+
* HOW: Pipeline is:
|
|
22
|
+
* 1. Walk the tree breadth-first with depth limit, collecting files
|
|
23
|
+
* that pass the binary-extension and ignored-dir filters.
|
|
24
|
+
* 2. Dedupe absolute paths (in case of overlapping inputs).
|
|
25
|
+
* 3. Read all files in parallel (`Promise.all`).
|
|
26
|
+
* 4. Enforce per-file size cap (emit `... (truncated)` marker on hit).
|
|
27
|
+
* 5. Enforce total-bytes cap (stop adding files once the total ceiling
|
|
28
|
+
* is reached; set `truncated: true`).
|
|
29
|
+
* 6. Render each file as `## <relpath>\n\n<content>\n\n`.
|
|
30
|
+
*
|
|
31
|
+
* This port simplifies the upstream by:
|
|
32
|
+
* - Dropping XML/`<directory>` framing in favour of plain markdown
|
|
33
|
+
* headings (more cache-friendly with chat models).
|
|
34
|
+
* - Adding `maxTotalBytes` enforcement (upstream had per-file only).
|
|
35
|
+
* - Skipping magic-byte sniffing — the extension filter covers the
|
|
36
|
+
* overwhelming majority of binaries we care about, and the cost of
|
|
37
|
+
* reading every file's prefix to sniff it is not worth the marginal
|
|
38
|
+
* accuracy on a workspace packer.
|
|
39
|
+
*
|
|
40
|
+
* Output format example:
|
|
41
|
+
*
|
|
42
|
+
* ## src/index.ts
|
|
43
|
+
*
|
|
44
|
+
* export function main() { ... }
|
|
45
|
+
*
|
|
46
|
+
* ## src/lib/helper.ts
|
|
47
|
+
*
|
|
48
|
+
* export const ANSWER = 42;
|
|
49
|
+
*/
|
|
50
|
+
import { stat, readdir, readFile } from "node:fs/promises";
|
|
51
|
+
import { extname, join, relative } from "node:path";
|
|
52
|
+
/** Default binary / asset extensions to skip. Lower-case, dot-prefixed. */
|
|
53
|
+
export const DEFAULT_SKIP_EXTENSIONS = [
|
|
54
|
+
".png",
|
|
55
|
+
".jpg",
|
|
56
|
+
".jpeg",
|
|
57
|
+
".gif",
|
|
58
|
+
".pdf",
|
|
59
|
+
".zip",
|
|
60
|
+
".tar",
|
|
61
|
+
".gz",
|
|
62
|
+
".bin",
|
|
63
|
+
".exe",
|
|
64
|
+
".so",
|
|
65
|
+
".dylib",
|
|
66
|
+
".dll",
|
|
67
|
+
".node",
|
|
68
|
+
".wasm",
|
|
69
|
+
".woff",
|
|
70
|
+
".woff2",
|
|
71
|
+
".ttf",
|
|
72
|
+
".eot",
|
|
73
|
+
".ico",
|
|
74
|
+
];
|
|
75
|
+
/** Default directories to skip. */
|
|
76
|
+
export const DEFAULT_SKIP_DIRS = [
|
|
77
|
+
"node_modules",
|
|
78
|
+
".git",
|
|
79
|
+
"dist",
|
|
80
|
+
"dist-cjs",
|
|
81
|
+
".next",
|
|
82
|
+
".turbo",
|
|
83
|
+
"target",
|
|
84
|
+
"build",
|
|
85
|
+
".venv",
|
|
86
|
+
"__pycache__",
|
|
87
|
+
];
|
|
88
|
+
const DEFAULT_MAX_DEPTH = 8;
|
|
89
|
+
const DEFAULT_MAX_BYTES_PER_FILE = 1_048_576; // 1 MB
|
|
90
|
+
const DEFAULT_MAX_TOTAL_BYTES = 8_388_608; // 8 MB
|
|
91
|
+
function resolveOptions(options) {
|
|
92
|
+
const skipExtensions = new Set((options?.skipExtensions ?? DEFAULT_SKIP_EXTENSIONS).map((ext) => ext.toLowerCase()));
|
|
93
|
+
const skipDirs = new Set(options?.skipDirs ?? DEFAULT_SKIP_DIRS);
|
|
94
|
+
return {
|
|
95
|
+
maxDepth: options?.maxDepth ?? DEFAULT_MAX_DEPTH,
|
|
96
|
+
maxBytesPerFile: options?.maxBytesPerFile ?? DEFAULT_MAX_BYTES_PER_FILE,
|
|
97
|
+
maxTotalBytes: options?.maxTotalBytes ?? DEFAULT_MAX_TOTAL_BYTES,
|
|
98
|
+
skipExtensions,
|
|
99
|
+
skipDirs,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Walk `rootPath` recursively, returning absolute paths to every file that
|
|
104
|
+
* passes the binary-extension and ignored-dir filters.
|
|
105
|
+
*
|
|
106
|
+
* Depth-bounded by `opts.maxDepth`. Errors from `readdir` (permission denied,
|
|
107
|
+
* symlink loops with broken targets, etc.) are swallowed per-directory so a
|
|
108
|
+
* single broken subtree doesn't abort the whole pack.
|
|
109
|
+
*/
|
|
110
|
+
async function collectFilePaths(rootPath, opts) {
|
|
111
|
+
const acc = [];
|
|
112
|
+
async function walk(dir, depth) {
|
|
113
|
+
if (depth > opts.maxDepth)
|
|
114
|
+
return;
|
|
115
|
+
let entries;
|
|
116
|
+
try {
|
|
117
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
for (const entry of entries) {
|
|
123
|
+
const fullPath = join(dir, entry.name);
|
|
124
|
+
if (entry.isDirectory()) {
|
|
125
|
+
if (opts.skipDirs.has(entry.name))
|
|
126
|
+
continue;
|
|
127
|
+
await walk(fullPath, depth + 1);
|
|
128
|
+
}
|
|
129
|
+
else if (entry.isFile()) {
|
|
130
|
+
const ext = extname(entry.name).toLowerCase();
|
|
131
|
+
if (opts.skipExtensions.has(ext))
|
|
132
|
+
continue;
|
|
133
|
+
acc.push(fullPath);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// Handle the case where rootPath is itself a file rather than a directory.
|
|
138
|
+
try {
|
|
139
|
+
const rootStat = await stat(rootPath);
|
|
140
|
+
if (rootStat.isFile()) {
|
|
141
|
+
const ext = extname(rootPath).toLowerCase();
|
|
142
|
+
if (!opts.skipExtensions.has(ext))
|
|
143
|
+
acc.push(rootPath);
|
|
144
|
+
}
|
|
145
|
+
else if (rootStat.isDirectory()) {
|
|
146
|
+
await walk(rootPath, 0);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return acc;
|
|
151
|
+
}
|
|
152
|
+
return acc;
|
|
153
|
+
}
|
|
154
|
+
async function readOne(absPath, opts) {
|
|
155
|
+
try {
|
|
156
|
+
const st = await stat(absPath);
|
|
157
|
+
const size = st.size;
|
|
158
|
+
if (size > opts.maxBytesPerFile) {
|
|
159
|
+
const buf = await readFile(absPath);
|
|
160
|
+
const slice = buf.subarray(0, opts.maxBytesPerFile).toString("utf8");
|
|
161
|
+
return {
|
|
162
|
+
absPath,
|
|
163
|
+
body: `${slice}\n... (truncated)`,
|
|
164
|
+
bytesRead: opts.maxBytesPerFile,
|
|
165
|
+
fileTruncated: true,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
const body = await readFile(absPath, "utf8");
|
|
169
|
+
return {
|
|
170
|
+
absPath,
|
|
171
|
+
body,
|
|
172
|
+
bytesRead: size,
|
|
173
|
+
fileTruncated: false,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
178
|
+
return {
|
|
179
|
+
absPath,
|
|
180
|
+
body: `... (read error: ${msg})`,
|
|
181
|
+
bytesRead: 0,
|
|
182
|
+
fileTruncated: true,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Pack a workspace directory into a single LLM-friendly markdown string.
|
|
188
|
+
*
|
|
189
|
+
* The returned `content` lists each included file in sorted (relative-path)
|
|
190
|
+
* order with a `## <relpath>` heading followed by its contents. This is
|
|
191
|
+
* deterministic across runs so the prefix cache stays warm.
|
|
192
|
+
*
|
|
193
|
+
* @param rootPath Absolute path to the workspace root (or a single file).
|
|
194
|
+
* @param options Optional caps and skip lists. Sensible defaults applied.
|
|
195
|
+
*/
|
|
196
|
+
export async function packWorkspace(rootPath, options) {
|
|
197
|
+
const opts = resolveOptions(options);
|
|
198
|
+
const filePaths = await collectFilePaths(rootPath, opts);
|
|
199
|
+
// Dedup absolute paths (the walker shouldn't produce duplicates, but
|
|
200
|
+
// defending against future callers that pass overlapping inputs).
|
|
201
|
+
const uniquePaths = Array.from(new Set(filePaths)).sort();
|
|
202
|
+
// Read all eligible files in parallel. Upstream's `Promise.all` is fine
|
|
203
|
+
// for reasonable workspaces; for >10k files the caller should pre-filter.
|
|
204
|
+
const reads = await Promise.all(uniquePaths.map((p) => readOne(p, opts)));
|
|
205
|
+
const sections = [];
|
|
206
|
+
let bytesRead = 0;
|
|
207
|
+
let fileCount = 0;
|
|
208
|
+
let truncated = false;
|
|
209
|
+
for (const r of reads) {
|
|
210
|
+
if (bytesRead + r.bytesRead > opts.maxTotalBytes) {
|
|
211
|
+
truncated = true;
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
const rel = relative(rootPath, r.absPath) || r.absPath;
|
|
215
|
+
sections.push(`## ${rel}\n\n${r.body}\n\n`);
|
|
216
|
+
bytesRead += r.bytesRead;
|
|
217
|
+
fileCount += 1;
|
|
218
|
+
if (r.fileTruncated)
|
|
219
|
+
truncated = true;
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
content: sections.join(""),
|
|
223
|
+
fileCount,
|
|
224
|
+
bytesRead,
|
|
225
|
+
truncated,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
//# sourceMappingURL=workspace-pack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-pack.js","sourceRoot":"","sources":["../../src/tools/workspace-pack.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composer history — ported from Hmbown/DeepSeek-TUI's `composer_history.rs`
|
|
3
|
+
* (crates/tui/src/composer_history.rs).
|
|
4
|
+
*
|
|
5
|
+
* WHAT: Cross-session composer input history. Persists user-typed prompts to
|
|
6
|
+
* `~/.wotann/composer-history.txt` so pressing Up-arrow at the composer
|
|
7
|
+
* recalls submissions from previous sessions, not just the current one.
|
|
8
|
+
* One entry per line, oldest first.
|
|
9
|
+
*
|
|
10
|
+
* WHY: A composer that forgets every prompt the moment the session ends
|
|
11
|
+
* actively destroys the user's working memory. Persisting the history
|
|
12
|
+
* means recurring prompts ("run the build", "show me the diff", a specific
|
|
13
|
+
* debugging incantation) survive across restarts. The cap keeps the file
|
|
14
|
+
* small (≤200 KB at 1000 entries × ≤200 chars/entry).
|
|
15
|
+
*
|
|
16
|
+
* WHERE: Wire from the TUI composer's keyboard handler. On submit, call
|
|
17
|
+
* `history.add(entry)`. On Up-arrow, call `previous()` and render the
|
|
18
|
+
* returned string into the composer buffer. On Down-arrow, call `next()`.
|
|
19
|
+
*
|
|
20
|
+
* HOW: In-memory ring (capped at 1000 entries via FIFO eviction) backed by
|
|
21
|
+
* a plaintext file. Slash-commands (entries starting with `/`) and
|
|
22
|
+
* whitespace-only inputs are skipped — they pollute recall and the slash
|
|
23
|
+
* menu already covers them. Consecutive duplicates are also deduped so
|
|
24
|
+
* repeated submission of the same prompt doesn't bloat the file.
|
|
25
|
+
*
|
|
26
|
+
* Cursor model (matches readline / bash):
|
|
27
|
+
* - `previous()` walks one step toward the oldest entry. Returns the
|
|
28
|
+
* newest entry on first call, the next-older on the second, and so on.
|
|
29
|
+
* - `next()` walks one step toward the newest. Returns `undefined` once
|
|
30
|
+
* past the newest (so the composer can clear the buffer).
|
|
31
|
+
* - `add()` resets the cursor to "just past the newest" so the next
|
|
32
|
+
* `previous()` returns the entry that was just added.
|
|
33
|
+
* - `reset()` puts the cursor in the same "just past the newest" state
|
|
34
|
+
* without modifying entries.
|
|
35
|
+
*
|
|
36
|
+
* Persistence is best-effort — failures are swallowed because composer
|
|
37
|
+
* history is a UX nicety, not a correctness concern.
|
|
38
|
+
*/
|
|
39
|
+
/** Hard cap on persisted history. Keeps the file under ~200 KB. */
|
|
40
|
+
export declare const MAX_HISTORY_ENTRIES = 1000;
|
|
41
|
+
/** Default history file path (~/.wotann/composer-history.txt). */
|
|
42
|
+
export declare function defaultComposerHistoryPath(): string;
|
|
43
|
+
/**
|
|
44
|
+
* `ComposerHistory` keeps in-memory entries in append-only order (oldest
|
|
45
|
+
* first) and persists them to a flat text file. It is NOT shared between
|
|
46
|
+
* processes safely; the read/write pattern is "load once on construction,
|
|
47
|
+
* rewrite-on-add". If a second process appends concurrently the last
|
|
48
|
+
* writer wins.
|
|
49
|
+
*/
|
|
50
|
+
export declare class ComposerHistory {
|
|
51
|
+
private readonly path;
|
|
52
|
+
private entries;
|
|
53
|
+
/**
|
|
54
|
+
* Cursor for `previous` / `next`. Invariant:
|
|
55
|
+
* `cursor === entries.length` ⇒ "just past the newest entry"
|
|
56
|
+
* `cursor in [0, entries.length)` ⇒ pointing at that entry
|
|
57
|
+
*/
|
|
58
|
+
private cursor;
|
|
59
|
+
constructor(path?: string);
|
|
60
|
+
/**
|
|
61
|
+
* Append an entry to history. Skips slash-commands and pure-whitespace
|
|
62
|
+
* inputs. Dedupes consecutive duplicates so repeated submission of the
|
|
63
|
+
* same prompt doesn't bloat the file. Resets the cursor to "just past
|
|
64
|
+
* the newest entry" so the next `previous()` returns the just-added one.
|
|
65
|
+
*
|
|
66
|
+
* Best-effort persistence — any I/O error is swallowed. The in-memory
|
|
67
|
+
* state is always consistent regardless of disk outcome.
|
|
68
|
+
*/
|
|
69
|
+
add(entry: string): void;
|
|
70
|
+
/**
|
|
71
|
+
* Walk one step toward the oldest entry. Returns the entry the cursor
|
|
72
|
+
* lands on, or `undefined` if there's no older entry.
|
|
73
|
+
*
|
|
74
|
+
* First call (cursor at `entries.length`) returns the newest entry.
|
|
75
|
+
* Subsequent calls return progressively older entries. Once at the
|
|
76
|
+
* oldest, further calls clamp and return that oldest entry again.
|
|
77
|
+
*/
|
|
78
|
+
previous(): string | undefined;
|
|
79
|
+
/**
|
|
80
|
+
* Walk one step toward the newest entry. Returns the entry the cursor
|
|
81
|
+
* lands on, or `undefined` if there's no newer entry (cursor moved past
|
|
82
|
+
* the newest — the composer should clear its buffer).
|
|
83
|
+
*/
|
|
84
|
+
next(): string | undefined;
|
|
85
|
+
/** Reset the cursor to "just past the newest entry" without modifying entries. */
|
|
86
|
+
reset(): void;
|
|
87
|
+
/** Snapshot of all entries, oldest first. Returns a fresh array (immutable). */
|
|
88
|
+
getAll(): readonly string[];
|
|
89
|
+
private persist;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Read the persisted history into memory. Returns an empty array if the
|
|
93
|
+
* file doesn't exist or can't be parsed — this is best-effort.
|
|
94
|
+
*
|
|
95
|
+
* Exposed for tests and for callers that want to peek at the history
|
|
96
|
+
* without constructing a `ComposerHistory` instance.
|
|
97
|
+
*/
|
|
98
|
+
export declare function loadEntries(path: string): string[];
|
|
99
|
+
//# sourceMappingURL=composer-history.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composer-history.d.ts","sourceRoot":"","sources":["../../src/tui/composer-history.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAMH,mEAAmE;AACnE,eAAO,MAAM,mBAAmB,OAAO,CAAC;AAExC,kEAAkE;AAClE,wBAAgB,0BAA0B,IAAI,MAAM,CAEnD;AAED;;;;;;GAMG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,OAAO,CAAW;IAC1B;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAS;gBAEX,IAAI,GAAE,MAAqC;IAMvD;;;;;;;;OAQG;IACH,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAsBxB;;;;;;;OAOG;IACH,QAAQ,IAAI,MAAM,GAAG,SAAS;IAM9B;;;;OAIG;IACH,IAAI,IAAI,MAAM,GAAG,SAAS;IAW1B,kFAAkF;IAClF,KAAK,IAAI,IAAI;IAIb,gFAAgF;IAChF,MAAM,IAAI,SAAS,MAAM,EAAE;IAI3B,OAAO,CAAC,OAAO;CAShB;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAWlD"}
|