@hybridaione/hybridclaw 0.1.5
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/.env.example +14 -0
- package/.github/workflows/pages.yml +41 -0
- package/AGENTS.md +24 -0
- package/CHANGELOG.md +113 -0
- package/LICENSE +21 -0
- package/README.md +226 -0
- package/SECURITY.md +67 -0
- package/config.example.json +62 -0
- package/container/Dockerfile +23 -0
- package/container/package-lock.json +246 -0
- package/container/package.json +17 -0
- package/container/src/hybridai-client.ts +38 -0
- package/container/src/index.ts +198 -0
- package/container/src/ipc.ts +37 -0
- package/container/src/tools.ts +1008 -0
- package/container/src/types.ts +74 -0
- package/container/src/web-fetch.ts +389 -0
- package/container/tsconfig.json +16 -0
- package/dist/agent.d.ts +3 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +21 -0
- package/dist/agent.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +32 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +38 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +107 -0
- package/dist/config.js.map +1 -0
- package/dist/container-runner.d.ts +11 -0
- package/dist/container-runner.d.ts.map +1 -0
- package/dist/container-runner.js +246 -0
- package/dist/container-runner.js.map +1 -0
- package/dist/conversation.d.ts +18 -0
- package/dist/conversation.d.ts.map +1 -0
- package/dist/conversation.js +29 -0
- package/dist/conversation.js.map +1 -0
- package/dist/db.d.ts +29 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +205 -0
- package/dist/db.js.map +1 -0
- package/dist/discord.d.ts +17 -0
- package/dist/discord.d.ts.map +1 -0
- package/dist/discord.js +115 -0
- package/dist/discord.js.map +1 -0
- package/dist/env.d.ts +6 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +36 -0
- package/dist/env.js.map +1 -0
- package/dist/gateway-client.d.ts +8 -0
- package/dist/gateway-client.d.ts.map +1 -0
- package/dist/gateway-client.js +57 -0
- package/dist/gateway-client.js.map +1 -0
- package/dist/gateway-service.d.ts +23 -0
- package/dist/gateway-service.d.ts.map +1 -0
- package/dist/gateway-service.js +360 -0
- package/dist/gateway-service.js.map +1 -0
- package/dist/gateway-types.d.ts +40 -0
- package/dist/gateway-types.d.ts.map +1 -0
- package/dist/gateway-types.js +6 -0
- package/dist/gateway-types.js.map +1 -0
- package/dist/gateway.d.ts +2 -0
- package/dist/gateway.d.ts.map +1 -0
- package/dist/gateway.js +138 -0
- package/dist/gateway.js.map +1 -0
- package/dist/hatch.d.ts +7 -0
- package/dist/hatch.d.ts.map +1 -0
- package/dist/hatch.js +99 -0
- package/dist/hatch.js.map +1 -0
- package/dist/health.d.ts +2 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +169 -0
- package/dist/health.js.map +1 -0
- package/dist/heartbeat.d.ts +3 -0
- package/dist/heartbeat.d.ts.map +1 -0
- package/dist/heartbeat.js +103 -0
- package/dist/heartbeat.js.map +1 -0
- package/dist/hybridai-bots.d.ts +5 -0
- package/dist/hybridai-bots.d.ts.map +1 -0
- package/dist/hybridai-bots.js +34 -0
- package/dist/hybridai-bots.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +60 -0
- package/dist/index.js.map +1 -0
- package/dist/ipc.d.ts +33 -0
- package/dist/ipc.d.ts.map +1 -0
- package/dist/ipc.js +142 -0
- package/dist/ipc.js.map +1 -0
- package/dist/logger.d.ts +3 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +21 -0
- package/dist/logger.js.map +1 -0
- package/dist/mount-security.d.ts +28 -0
- package/dist/mount-security.d.ts.map +1 -0
- package/dist/mount-security.js +187 -0
- package/dist/mount-security.js.map +1 -0
- package/dist/onboarding.d.ts +7 -0
- package/dist/onboarding.d.ts.map +1 -0
- package/dist/onboarding.js +445 -0
- package/dist/onboarding.js.map +1 -0
- package/dist/prompt-hooks.d.ts +17 -0
- package/dist/prompt-hooks.d.ts.map +1 -0
- package/dist/prompt-hooks.js +83 -0
- package/dist/prompt-hooks.js.map +1 -0
- package/dist/runtime-config.d.ts +78 -0
- package/dist/runtime-config.d.ts.map +1 -0
- package/dist/runtime-config.js +471 -0
- package/dist/runtime-config.js.map +1 -0
- package/dist/scheduled-task-runner.d.ts +11 -0
- package/dist/scheduled-task-runner.d.ts.map +1 -0
- package/dist/scheduled-task-runner.js +16 -0
- package/dist/scheduled-task-runner.js.map +1 -0
- package/dist/scheduler.d.ts +11 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +165 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/session-maintenance.d.ts +9 -0
- package/dist/session-maintenance.d.ts.map +1 -0
- package/dist/session-maintenance.js +168 -0
- package/dist/session-maintenance.js.map +1 -0
- package/dist/session-transcripts.d.ts +11 -0
- package/dist/session-transcripts.d.ts.map +1 -0
- package/dist/session-transcripts.js +32 -0
- package/dist/session-transcripts.js.map +1 -0
- package/dist/side-effects.d.ts +3 -0
- package/dist/side-effects.d.ts.map +1 -0
- package/dist/side-effects.js +30 -0
- package/dist/side-effects.js.map +1 -0
- package/dist/skills.d.ts +32 -0
- package/dist/skills.d.ts.map +1 -0
- package/dist/skills.js +376 -0
- package/dist/skills.js.map +1 -0
- package/dist/tui.d.ts +2 -0
- package/dist/tui.d.ts.map +1 -0
- package/dist/tui.js +305 -0
- package/dist/tui.js.map +1 -0
- package/dist/types.d.ts +132 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/workspace.d.ts +25 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +154 -0
- package/dist/workspace.js.map +1 -0
- package/docs/chat.html +929 -0
- package/docs/hai_logo_free.png +0 -0
- package/docs/hero.png +0 -0
- package/docs/index.html +1213 -0
- package/package.json +34 -0
- package/skills/current-time/SKILL.md +26 -0
- package/skills/iss-position/SKILL.md +46 -0
- package/skills/iss-position/agents/openai.yaml +3 -0
- package/skills/iss-position/scripts/get_iss_position.py +107 -0
- package/skills/repo-orientation/SKILL.md +74 -0
- package/src/agent.ts +35 -0
- package/src/cli.ts +35 -0
- package/src/config.ts +137 -0
- package/src/container-runner.ts +305 -0
- package/src/conversation.ts +49 -0
- package/src/db.ts +290 -0
- package/src/discord.ts +156 -0
- package/src/env.ts +36 -0
- package/src/gateway-client.ts +73 -0
- package/src/gateway-service.ts +456 -0
- package/src/gateway-types.ts +47 -0
- package/src/gateway.ts +199 -0
- package/src/health.ts +189 -0
- package/src/heartbeat.ts +121 -0
- package/src/hybridai-bots.ts +48 -0
- package/src/ipc.ts +163 -0
- package/src/logger.ts +26 -0
- package/src/mount-security.ts +216 -0
- package/src/onboarding.ts +569 -0
- package/src/prompt-hooks.ts +113 -0
- package/src/runtime-config.ts +588 -0
- package/src/scheduled-task-runner.ts +26 -0
- package/src/scheduler.ts +196 -0
- package/src/session-maintenance.ts +263 -0
- package/src/session-transcripts.ts +45 -0
- package/src/side-effects.ts +41 -0
- package/src/skills.ts +429 -0
- package/src/tui.ts +327 -0
- package/src/types.ts +135 -0
- package/src/workspace.ts +171 -0
- package/templates/AGENTS.md +104 -0
- package/templates/BOOT.md +3 -0
- package/templates/BOOTSTRAP.md +54 -0
- package/templates/HEARTBEAT.md +5 -0
- package/templates/IDENTITY.md +18 -0
- package/templates/MEMORY.md +19 -0
- package/templates/SOUL.md +36 -0
- package/templates/TOOLS.md +22 -0
- package/templates/USER.md +17 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export interface ChatMessage {
|
|
2
|
+
role: 'system' | 'user' | 'assistant' | 'tool';
|
|
3
|
+
content: string | null;
|
|
4
|
+
tool_calls?: ToolCall[];
|
|
5
|
+
tool_call_id?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface ToolCall {
|
|
9
|
+
id: string;
|
|
10
|
+
type: 'function';
|
|
11
|
+
function: {
|
|
12
|
+
name: string;
|
|
13
|
+
arguments: string;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ChatCompletionResponse {
|
|
18
|
+
id: string;
|
|
19
|
+
choices: Array<{
|
|
20
|
+
message: {
|
|
21
|
+
role: string;
|
|
22
|
+
content: string | null;
|
|
23
|
+
tool_calls?: ToolCall[];
|
|
24
|
+
};
|
|
25
|
+
finish_reason: string;
|
|
26
|
+
}>;
|
|
27
|
+
model: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ToolDefinition {
|
|
31
|
+
type: 'function';
|
|
32
|
+
function: {
|
|
33
|
+
name: string;
|
|
34
|
+
description: string;
|
|
35
|
+
parameters: {
|
|
36
|
+
type: 'object';
|
|
37
|
+
properties: Record<string, { type: string; description: string }>;
|
|
38
|
+
required: string[];
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface ContainerInput {
|
|
44
|
+
sessionId: string;
|
|
45
|
+
messages: ChatMessage[];
|
|
46
|
+
chatbotId: string;
|
|
47
|
+
enableRag: boolean;
|
|
48
|
+
apiKey: string;
|
|
49
|
+
baseUrl: string;
|
|
50
|
+
model: string;
|
|
51
|
+
channelId: string;
|
|
52
|
+
scheduledTasks?: { id: number; cronExpr: string; runAt: string | null; everyMs: number | null; prompt: string; enabled: number; lastRun: string | null; createdAt: string }[];
|
|
53
|
+
allowedTools?: string[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface ToolExecution {
|
|
57
|
+
name: string;
|
|
58
|
+
arguments: string;
|
|
59
|
+
result: string;
|
|
60
|
+
durationMs: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface ContainerOutput {
|
|
64
|
+
status: 'success' | 'error';
|
|
65
|
+
result: string | null;
|
|
66
|
+
toolsUsed: string[];
|
|
67
|
+
toolExecutions?: ToolExecution[];
|
|
68
|
+
error?: string;
|
|
69
|
+
sideEffects?: { schedules?: ScheduleSideEffect[] };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export type ScheduleSideEffect =
|
|
73
|
+
| { action: 'add'; cronExpr?: string; runAt?: string; everyMs?: number; prompt: string }
|
|
74
|
+
| { action: 'remove'; taskId: number };
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* web-fetch tool — fetch a URL and extract readable content.
|
|
3
|
+
*
|
|
4
|
+
* Ported from OpenClaw's web-fetch but stripped down:
|
|
5
|
+
* - No Firecrawl fallback
|
|
6
|
+
* - No SSRF guard framework (container is sandboxed, basic URL validation suffices)
|
|
7
|
+
* - No external-content wrapping
|
|
8
|
+
* - No config system — sensible hardcoded defaults
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Constants
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
const DEFAULT_MAX_CHARS = 50_000;
|
|
16
|
+
const MAX_RESPONSE_BYTES = 2_000_000;
|
|
17
|
+
const MAX_REDIRECTS = 5;
|
|
18
|
+
const TIMEOUT_MS = 30_000;
|
|
19
|
+
const CACHE_TTL_MS = 15 * 60_000; // 15 min
|
|
20
|
+
const CACHE_MAX_ENTRIES = 100;
|
|
21
|
+
const READABILITY_MAX_HTML_CHARS = 1_000_000;
|
|
22
|
+
const USER_AGENT =
|
|
23
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36';
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Cache
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
interface CacheEntry {
|
|
30
|
+
value: WebFetchResult;
|
|
31
|
+
expiresAt: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const cache = new Map<string, CacheEntry>();
|
|
35
|
+
|
|
36
|
+
function readCache(key: string): WebFetchResult | null {
|
|
37
|
+
const entry = cache.get(key);
|
|
38
|
+
if (!entry) return null;
|
|
39
|
+
if (Date.now() > entry.expiresAt) {
|
|
40
|
+
cache.delete(key);
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
return entry.value;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function writeCache(key: string, value: WebFetchResult): void {
|
|
47
|
+
if (cache.size >= CACHE_MAX_ENTRIES) {
|
|
48
|
+
const oldest = cache.keys().next();
|
|
49
|
+
if (!oldest.done) cache.delete(oldest.value);
|
|
50
|
+
}
|
|
51
|
+
cache.set(key, { value, expiresAt: Date.now() + CACHE_TTL_MS });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// HTML helpers (inlined from OpenClaw's web-fetch-utils)
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
function decodeEntities(value: string): string {
|
|
59
|
+
return value
|
|
60
|
+
.replace(/ /gi, ' ')
|
|
61
|
+
.replace(/&/gi, '&')
|
|
62
|
+
.replace(/"/gi, '"')
|
|
63
|
+
.replace(/'/gi, "'")
|
|
64
|
+
.replace(/</gi, '<')
|
|
65
|
+
.replace(/>/gi, '>')
|
|
66
|
+
.replace(/&#x([0-9a-f]+);/gi, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
|
|
67
|
+
.replace(/&#(\d+);/gi, (_, dec) => String.fromCharCode(parseInt(dec, 10)));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function stripTags(value: string): string {
|
|
71
|
+
return decodeEntities(value.replace(/<[^>]+>/g, ''));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function normalizeWhitespace(value: string): string {
|
|
75
|
+
return value
|
|
76
|
+
.replace(/\r/g, '')
|
|
77
|
+
.replace(/[ \t]+\n/g, '\n')
|
|
78
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
79
|
+
.replace(/[ \t]{2,}/g, ' ')
|
|
80
|
+
.trim();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function htmlToMarkdown(html: string): { text: string; title?: string } {
|
|
84
|
+
const titleMatch = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
85
|
+
const title = titleMatch ? normalizeWhitespace(stripTags(titleMatch[1])) : undefined;
|
|
86
|
+
|
|
87
|
+
let text = html
|
|
88
|
+
.replace(/<script[\s\S]*?<\/script>/gi, '')
|
|
89
|
+
.replace(/<style[\s\S]*?<\/style>/gi, '')
|
|
90
|
+
.replace(/<noscript[\s\S]*?<\/noscript>/gi, '');
|
|
91
|
+
|
|
92
|
+
// Links
|
|
93
|
+
text = text.replace(/<a\s+[^>]*href=["']([^"']+)["'][^>]*>([\s\S]*?)<\/a>/gi, (_, href, body) => {
|
|
94
|
+
const label = normalizeWhitespace(stripTags(body));
|
|
95
|
+
return label ? `[${label}](${href})` : href;
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Headings
|
|
99
|
+
text = text.replace(/<h([1-6])[^>]*>([\s\S]*?)<\/h\1>/gi, (_, level, body) => {
|
|
100
|
+
const prefix = '#'.repeat(Math.max(1, Math.min(6, parseInt(level, 10))));
|
|
101
|
+
return `\n${prefix} ${normalizeWhitespace(stripTags(body))}\n`;
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// List items
|
|
105
|
+
text = text.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, (_, body) => {
|
|
106
|
+
const label = normalizeWhitespace(stripTags(body));
|
|
107
|
+
return label ? `\n- ${label}` : '';
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Block breaks
|
|
111
|
+
text = text
|
|
112
|
+
.replace(/<(br|hr)\s*\/?>/gi, '\n')
|
|
113
|
+
.replace(/<\/(p|div|section|article|header|footer|table|tr|ul|ol)>/gi, '\n');
|
|
114
|
+
|
|
115
|
+
text = stripTags(text);
|
|
116
|
+
text = normalizeWhitespace(text);
|
|
117
|
+
return { text, title };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function markdownToText(markdown: string): string {
|
|
121
|
+
let text = markdown;
|
|
122
|
+
text = text.replace(/!\[[^\]]*]\([^)]+\)/g, '');
|
|
123
|
+
text = text.replace(/\[([^\]]+)]\([^)]+\)/g, '$1');
|
|
124
|
+
text = text.replace(/```[\s\S]*?```/g, (block) =>
|
|
125
|
+
block.replace(/```[^\n]*\n?/g, '').replace(/```/g, ''),
|
|
126
|
+
);
|
|
127
|
+
text = text.replace(/`([^`]+)`/g, '$1');
|
|
128
|
+
text = text.replace(/^#{1,6}\s+/gm, '');
|
|
129
|
+
text = text.replace(/^\s*[-*+]\s+/gm, '');
|
|
130
|
+
text = text.replace(/^\s*\d+\.\s+/gm, '');
|
|
131
|
+
return normalizeWhitespace(text);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
// Readability extraction (lazy-loaded)
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
|
|
138
|
+
let readabilityDeps: Promise<{
|
|
139
|
+
Readability: typeof import('@mozilla/readability').Readability;
|
|
140
|
+
parseHTML: typeof import('linkedom').parseHTML;
|
|
141
|
+
}> | undefined;
|
|
142
|
+
|
|
143
|
+
function loadReadabilityDeps() {
|
|
144
|
+
if (!readabilityDeps) {
|
|
145
|
+
readabilityDeps = Promise.all([
|
|
146
|
+
import('@mozilla/readability'),
|
|
147
|
+
import('linkedom'),
|
|
148
|
+
]).then(([r, l]) => ({ Readability: r.Readability, parseHTML: l.parseHTML }));
|
|
149
|
+
}
|
|
150
|
+
return readabilityDeps;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function extractReadableContent(
|
|
154
|
+
html: string,
|
|
155
|
+
url: string,
|
|
156
|
+
extractMode: 'markdown' | 'text',
|
|
157
|
+
): Promise<{ text: string; title?: string }> {
|
|
158
|
+
// Fallback: simple regex-based conversion
|
|
159
|
+
const fallback = (): { text: string; title?: string } => {
|
|
160
|
+
const rendered = htmlToMarkdown(html);
|
|
161
|
+
if (extractMode === 'text') {
|
|
162
|
+
return { text: markdownToText(rendered.text), title: rendered.title };
|
|
163
|
+
}
|
|
164
|
+
return rendered;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
if (html.length > READABILITY_MAX_HTML_CHARS) {
|
|
168
|
+
return fallback();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
const { Readability, parseHTML } = await loadReadabilityDeps();
|
|
173
|
+
const { document } = parseHTML(html);
|
|
174
|
+
try {
|
|
175
|
+
(document as { baseURI?: string }).baseURI = url;
|
|
176
|
+
} catch { /* best-effort */ }
|
|
177
|
+
|
|
178
|
+
const reader = new Readability(document, { charThreshold: 0 });
|
|
179
|
+
const parsed = reader.parse();
|
|
180
|
+
if (!parsed?.content) return fallback();
|
|
181
|
+
|
|
182
|
+
const title = parsed.title || undefined;
|
|
183
|
+
if (extractMode === 'text') {
|
|
184
|
+
const text = normalizeWhitespace(parsed.textContent ?? '');
|
|
185
|
+
return text ? { text, title } : fallback();
|
|
186
|
+
}
|
|
187
|
+
const rendered = htmlToMarkdown(parsed.content);
|
|
188
|
+
return { text: rendered.text, title: title ?? rendered.title };
|
|
189
|
+
} catch {
|
|
190
|
+
return fallback();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
// Streaming response reader with size limit
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
|
|
198
|
+
async function readResponseText(
|
|
199
|
+
res: Response,
|
|
200
|
+
maxBytes: number,
|
|
201
|
+
): Promise<{ text: string; truncated: boolean }> {
|
|
202
|
+
const body = res.body;
|
|
203
|
+
if (body && typeof body === 'object' && 'getReader' in body) {
|
|
204
|
+
const reader = (body as ReadableStream<Uint8Array>).getReader();
|
|
205
|
+
const decoder = new TextDecoder();
|
|
206
|
+
let bytesRead = 0;
|
|
207
|
+
let truncated = false;
|
|
208
|
+
const parts: string[] = [];
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
while (true) {
|
|
212
|
+
const { value, done } = await reader.read();
|
|
213
|
+
if (done) break;
|
|
214
|
+
if (!value || value.byteLength === 0) continue;
|
|
215
|
+
|
|
216
|
+
let chunk = value;
|
|
217
|
+
if (bytesRead + chunk.byteLength > maxBytes) {
|
|
218
|
+
const remaining = Math.max(0, maxBytes - bytesRead);
|
|
219
|
+
if (remaining <= 0) { truncated = true; break; }
|
|
220
|
+
chunk = chunk.subarray(0, remaining);
|
|
221
|
+
truncated = true;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
bytesRead += chunk.byteLength;
|
|
225
|
+
parts.push(decoder.decode(chunk, { stream: true }));
|
|
226
|
+
if (truncated || bytesRead >= maxBytes) { truncated = true; break; }
|
|
227
|
+
}
|
|
228
|
+
} catch { /* return what we have */ } finally {
|
|
229
|
+
if (truncated) {
|
|
230
|
+
try { await reader.cancel(); } catch { /* ignore */ }
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
parts.push(decoder.decode());
|
|
235
|
+
return { text: parts.join(''), truncated };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const text = await res.text();
|
|
239
|
+
return { text, truncated: false };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ---------------------------------------------------------------------------
|
|
243
|
+
// Manual redirect following (to track finalUrl)
|
|
244
|
+
// ---------------------------------------------------------------------------
|
|
245
|
+
|
|
246
|
+
async function fetchWithRedirects(
|
|
247
|
+
url: string,
|
|
248
|
+
maxRedirects: number,
|
|
249
|
+
signal: AbortSignal,
|
|
250
|
+
): Promise<{ response: Response; finalUrl: string }> {
|
|
251
|
+
let currentUrl = url;
|
|
252
|
+
for (let i = 0; i <= maxRedirects; i++) {
|
|
253
|
+
const res = await fetch(currentUrl, {
|
|
254
|
+
redirect: 'manual',
|
|
255
|
+
headers: {
|
|
256
|
+
Accept: 'text/markdown, text/html;q=0.9, */*;q=0.1',
|
|
257
|
+
'User-Agent': USER_AGENT,
|
|
258
|
+
'Accept-Language': 'en-US,en;q=0.9',
|
|
259
|
+
},
|
|
260
|
+
signal,
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
const location = res.headers.get('location');
|
|
264
|
+
if (location && res.status >= 300 && res.status < 400) {
|
|
265
|
+
// Resolve relative redirects
|
|
266
|
+
currentUrl = new URL(location, currentUrl).toString();
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
return { response: res, finalUrl: currentUrl };
|
|
270
|
+
}
|
|
271
|
+
throw new Error(`Too many redirects (max ${maxRedirects})`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ---------------------------------------------------------------------------
|
|
275
|
+
// Public API
|
|
276
|
+
// ---------------------------------------------------------------------------
|
|
277
|
+
|
|
278
|
+
export interface WebFetchResult {
|
|
279
|
+
url: string;
|
|
280
|
+
finalUrl: string;
|
|
281
|
+
status: number;
|
|
282
|
+
contentType?: string;
|
|
283
|
+
title?: string;
|
|
284
|
+
extractMode: string;
|
|
285
|
+
extractor: string;
|
|
286
|
+
truncated: boolean;
|
|
287
|
+
length: number;
|
|
288
|
+
fetchedAt: string;
|
|
289
|
+
tookMs: number;
|
|
290
|
+
text: string;
|
|
291
|
+
cached?: boolean;
|
|
292
|
+
warning?: string;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export async function webFetch(params: {
|
|
296
|
+
url: string;
|
|
297
|
+
extractMode?: 'markdown' | 'text';
|
|
298
|
+
maxChars?: number;
|
|
299
|
+
}): Promise<WebFetchResult> {
|
|
300
|
+
const extractMode = params.extractMode ?? 'markdown';
|
|
301
|
+
const maxChars = Math.max(100, Math.min(params.maxChars ?? DEFAULT_MAX_CHARS, DEFAULT_MAX_CHARS));
|
|
302
|
+
|
|
303
|
+
// Validate URL
|
|
304
|
+
let parsedUrl: URL;
|
|
305
|
+
try {
|
|
306
|
+
parsedUrl = new URL(params.url);
|
|
307
|
+
} catch {
|
|
308
|
+
throw new Error('Invalid URL: must be http or https');
|
|
309
|
+
}
|
|
310
|
+
if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
|
|
311
|
+
throw new Error('Invalid URL: must be http or https');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Check cache
|
|
315
|
+
const cacheKey = `fetch:${params.url}:${extractMode}:${maxChars}`.toLowerCase();
|
|
316
|
+
const cached = readCache(cacheKey);
|
|
317
|
+
if (cached) return { ...cached, cached: true };
|
|
318
|
+
|
|
319
|
+
const start = Date.now();
|
|
320
|
+
const controller = new AbortController();
|
|
321
|
+
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
322
|
+
|
|
323
|
+
try {
|
|
324
|
+
const { response: res, finalUrl } = await fetchWithRedirects(
|
|
325
|
+
params.url,
|
|
326
|
+
MAX_REDIRECTS,
|
|
327
|
+
controller.signal,
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
if (!res.ok) {
|
|
331
|
+
throw new Error(`Web fetch failed (${res.status}): ${res.statusText}`);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const contentType = res.headers.get('content-type') ?? 'application/octet-stream';
|
|
335
|
+
const normalizedContentType = contentType.split(';')[0]?.trim() || 'application/octet-stream';
|
|
336
|
+
const bodyResult = await readResponseText(res, MAX_RESPONSE_BYTES);
|
|
337
|
+
const body = bodyResult.text;
|
|
338
|
+
|
|
339
|
+
let title: string | undefined;
|
|
340
|
+
let extractor = 'raw';
|
|
341
|
+
let text = body;
|
|
342
|
+
|
|
343
|
+
if (contentType.includes('text/markdown')) {
|
|
344
|
+
extractor = 'cf-markdown';
|
|
345
|
+
if (extractMode === 'text') text = markdownToText(body);
|
|
346
|
+
} else if (contentType.includes('text/html')) {
|
|
347
|
+
const readable = await extractReadableContent(body, finalUrl, extractMode);
|
|
348
|
+
text = readable.text;
|
|
349
|
+
title = readable.title;
|
|
350
|
+
extractor = 'readability';
|
|
351
|
+
} else if (contentType.includes('application/json')) {
|
|
352
|
+
try {
|
|
353
|
+
text = JSON.stringify(JSON.parse(body), null, 2);
|
|
354
|
+
extractor = 'json';
|
|
355
|
+
} catch {
|
|
356
|
+
extractor = 'raw';
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Truncate
|
|
361
|
+
const truncated = text.length > maxChars;
|
|
362
|
+
if (truncated) text = text.slice(0, maxChars);
|
|
363
|
+
|
|
364
|
+
const warning = bodyResult.truncated
|
|
365
|
+
? `Response body truncated after ${MAX_RESPONSE_BYTES} bytes.`
|
|
366
|
+
: undefined;
|
|
367
|
+
|
|
368
|
+
const result: WebFetchResult = {
|
|
369
|
+
url: params.url,
|
|
370
|
+
finalUrl,
|
|
371
|
+
status: res.status,
|
|
372
|
+
contentType: normalizedContentType,
|
|
373
|
+
title,
|
|
374
|
+
extractMode,
|
|
375
|
+
extractor,
|
|
376
|
+
truncated: truncated || bodyResult.truncated,
|
|
377
|
+
length: text.length,
|
|
378
|
+
fetchedAt: new Date().toISOString(),
|
|
379
|
+
tookMs: Date.now() - start,
|
|
380
|
+
text,
|
|
381
|
+
warning,
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
writeCache(cacheKey, result);
|
|
385
|
+
return result;
|
|
386
|
+
} finally {
|
|
387
|
+
clearTimeout(timer);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"sourceMap": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*"],
|
|
15
|
+
"exclude": ["node_modules", "dist"]
|
|
16
|
+
}
|
package/dist/agent.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ChatMessage, ContainerOutput, ScheduledTask, ToolProgressEvent } from './types.js';
|
|
2
|
+
export declare function runAgent(sessionId: string, messages: ChatMessage[], chatbotId: string, enableRag: boolean, model: string, agentId: string, channelId: string, scheduledTasks?: ScheduledTask[], allowedTools?: string[], onToolProgress?: (event: ToolProgressEvent) => void, abortSignal?: AbortSignal): Promise<ContainerOutput>;
|
|
3
|
+
//# sourceMappingURL=agent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAWjG,wBAAsB,QAAQ,CAC5B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,WAAW,EAAE,EACvB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,OAAO,EAClB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,cAAc,CAAC,EAAE,aAAa,EAAE,EAChC,YAAY,CAAC,EAAE,MAAM,EAAE,EACvB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,EACnD,WAAW,CAAC,EAAE,WAAW,GACxB,OAAO,CAAC,eAAe,CAAC,CAG1B"}
|
package/dist/agent.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent — always runs through a container for consistent sandboxing.
|
|
3
|
+
*/
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { DATA_DIR } from './config.js';
|
|
7
|
+
import { runContainer } from './container-runner.js';
|
|
8
|
+
/** Write full prompt context to data/last_prompt.jsonl for debugging (Pi-Mono style). */
|
|
9
|
+
function dumpPrompt(sessionId, messages, model, chatbotId) {
|
|
10
|
+
try {
|
|
11
|
+
const entry = { ts: new Date().toISOString(), sessionId, model, chatbotId, messages };
|
|
12
|
+
const filePath = path.join(DATA_DIR, 'last_prompt.jsonl');
|
|
13
|
+
fs.writeFileSync(filePath, JSON.stringify(entry) + '\n');
|
|
14
|
+
}
|
|
15
|
+
catch { /* best-effort */ }
|
|
16
|
+
}
|
|
17
|
+
export async function runAgent(sessionId, messages, chatbotId, enableRag, model, agentId, channelId, scheduledTasks, allowedTools, onToolProgress, abortSignal) {
|
|
18
|
+
dumpPrompt(sessionId, messages, model, chatbotId);
|
|
19
|
+
return runContainer(sessionId, messages, chatbotId, enableRag, model, agentId, channelId, scheduledTasks, allowedTools, onToolProgress, abortSignal);
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAGrD,yFAAyF;AACzF,SAAS,UAAU,CAAC,SAAiB,EAAE,QAAuB,EAAE,KAAa,EAAE,SAAiB;IAC9F,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,EAAE,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;QACtF,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,mBAAmB,CAAC,CAAC;QAC1D,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;IAC3D,CAAC;IAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,CAAC;AAC/B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,SAAiB,EACjB,QAAuB,EACvB,SAAiB,EACjB,SAAkB,EAClB,KAAa,EACb,OAAe,EACf,SAAiB,EACjB,cAAgC,EAChC,YAAuB,EACvB,cAAmD,EACnD,WAAyB;IAEzB,UAAU,CAAC,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;IAClD,OAAO,YAAY,CAAC,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,WAAW,CAAC,CAAC;AACvJ,CAAC"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { ensureHybridAICredentials } from './onboarding.js';
|
|
3
|
+
async function main() {
|
|
4
|
+
const command = process.argv[2];
|
|
5
|
+
switch (command) {
|
|
6
|
+
case 'gateway':
|
|
7
|
+
await ensureHybridAICredentials({ commandName: 'hybridclaw gateway' });
|
|
8
|
+
await import('./gateway.js');
|
|
9
|
+
break;
|
|
10
|
+
case 'tui':
|
|
11
|
+
await ensureHybridAICredentials({ commandName: 'hybridclaw tui' });
|
|
12
|
+
await import('./tui.js');
|
|
13
|
+
break;
|
|
14
|
+
case 'onboarding':
|
|
15
|
+
await ensureHybridAICredentials({ force: true, commandName: 'hybridclaw onboarding' });
|
|
16
|
+
break;
|
|
17
|
+
default:
|
|
18
|
+
console.log(`Usage: hybridclaw <command>
|
|
19
|
+
|
|
20
|
+
Commands:
|
|
21
|
+
gateway Start core runtime (web/API/scheduler/heartbeat/optional Discord)
|
|
22
|
+
tui Start terminal adapter (requires running gateway)
|
|
23
|
+
onboarding Run HybridAI account/API key onboarding`);
|
|
24
|
+
process.exit(command ? 1 : 0);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
main().catch((err) => {
|
|
28
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
29
|
+
console.error(`hybridclaw error: ${message}`);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
});
|
|
32
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAE5D,KAAK,UAAU,IAAI;IACjB,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAEhC,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,SAAS;YACZ,MAAM,yBAAyB,CAAC,EAAE,WAAW,EAAE,oBAAoB,EAAE,CAAC,CAAC;YACvE,MAAM,MAAM,CAAC,cAAc,CAAC,CAAC;YAC7B,MAAM;QACR,KAAK,KAAK;YACR,MAAM,yBAAyB,CAAC,EAAE,WAAW,EAAE,gBAAgB,EAAE,CAAC,CAAC;YACnE,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC;YACzB,MAAM;QACR,KAAK,YAAY;YACf,MAAM,yBAAyB,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,uBAAuB,EAAE,CAAC,CAAC;YACvF,MAAM;QACR;YACE,OAAO,CAAC,GAAG,CAAC;;;;;qDAKmC,CAAC,CAAC;YACjD,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAClC,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACjE,OAAO,CAAC,KAAK,CAAC,qBAAqB,OAAO,EAAE,CAAC,CAAC;IAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { onRuntimeConfigChange, type RuntimeConfig } from './runtime-config.js';
|
|
2
|
+
export declare const APP_VERSION: string;
|
|
3
|
+
export declare const DISCORD_TOKEN: string;
|
|
4
|
+
export declare const HYBRIDAI_API_KEY: string;
|
|
5
|
+
export declare let DISCORD_PREFIX: string;
|
|
6
|
+
export declare let HYBRIDAI_BASE_URL: string;
|
|
7
|
+
export declare let HYBRIDAI_MODEL: string;
|
|
8
|
+
export declare let HYBRIDAI_CHATBOT_ID: string;
|
|
9
|
+
export declare let HYBRIDAI_ENABLE_RAG: boolean;
|
|
10
|
+
export declare let HYBRIDAI_MODELS: string[];
|
|
11
|
+
export declare let CONTAINER_IMAGE: string;
|
|
12
|
+
export declare let CONTAINER_MEMORY: string;
|
|
13
|
+
export declare let CONTAINER_CPUS: string;
|
|
14
|
+
export declare let CONTAINER_TIMEOUT: number;
|
|
15
|
+
export declare const MOUNT_ALLOWLIST_PATH: string;
|
|
16
|
+
export declare let ADDITIONAL_MOUNTS: string;
|
|
17
|
+
export declare let CONTAINER_MAX_OUTPUT_SIZE: number;
|
|
18
|
+
export declare let MAX_CONCURRENT_CONTAINERS: number;
|
|
19
|
+
export declare let HEARTBEAT_ENABLED: boolean;
|
|
20
|
+
export declare let HEARTBEAT_INTERVAL: number;
|
|
21
|
+
export declare let HEARTBEAT_CHANNEL: string;
|
|
22
|
+
export declare let HEALTH_HOST: string;
|
|
23
|
+
export declare let HEALTH_PORT: number;
|
|
24
|
+
export declare let WEB_API_TOKEN: string;
|
|
25
|
+
export declare let GATEWAY_BASE_URL: string;
|
|
26
|
+
export declare let GATEWAY_API_TOKEN: string;
|
|
27
|
+
export declare let DB_PATH: string;
|
|
28
|
+
export declare let DATA_DIR: string;
|
|
29
|
+
export declare let SESSION_COMPACTION_ENABLED: boolean;
|
|
30
|
+
export declare let SESSION_COMPACTION_THRESHOLD: number;
|
|
31
|
+
export declare let SESSION_COMPACTION_KEEP_RECENT: number;
|
|
32
|
+
export declare let SESSION_COMPACTION_SUMMARY_MAX_CHARS: number;
|
|
33
|
+
export declare let PRE_COMPACTION_MEMORY_FLUSH_ENABLED: boolean;
|
|
34
|
+
export declare let PRE_COMPACTION_MEMORY_FLUSH_MAX_MESSAGES: number;
|
|
35
|
+
export declare let PRE_COMPACTION_MEMORY_FLUSH_MAX_CHARS: number;
|
|
36
|
+
export { onRuntimeConfigChange as onConfigChange };
|
|
37
|
+
export declare function getConfigSnapshot(): RuntimeConfig;
|
|
38
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAKA,OAAO,EAGL,qBAAqB,EACrB,KAAK,aAAa,EACnB,MAAM,qBAAqB,CAAC;AA6B7B,eAAO,MAAM,WAAW,QAAsB,CAAC;AAG/C,eAAO,MAAM,aAAa,QAAkC,CAAC;AAC7D,eAAO,MAAM,gBAAgB,QAA+B,CAAC;AAG7D,eAAO,IAAI,cAAc,QAAU,CAAC;AAEpC,eAAO,IAAI,iBAAiB,QAAyB,CAAC;AACtD,eAAO,IAAI,cAAc,QAAe,CAAC;AACzC,eAAO,IAAI,mBAAmB,QAAK,CAAC;AACpC,eAAO,IAAI,mBAAmB,SAAO,CAAC;AACtC,eAAO,IAAI,eAAe,EAAE,MAAM,EAA0C,CAAC;AAE7E,eAAO,IAAI,eAAe,QAAqB,CAAC;AAChD,eAAO,IAAI,gBAAgB,QAAS,CAAC;AACrC,eAAO,IAAI,cAAc,QAAM,CAAC;AAChC,eAAO,IAAI,iBAAiB,QAAS,CAAC;AAEtC,eAAO,MAAM,oBAAoB,QAEhC,CAAC;AACF,eAAO,IAAI,iBAAiB,QAAK,CAAC;AAElC,eAAO,IAAI,yBAAyB,QAAa,CAAC;AAClD,eAAO,IAAI,yBAAyB,QAAI,CAAC;AAEzC,eAAO,IAAI,iBAAiB,SAAO,CAAC;AACpC,eAAO,IAAI,kBAAkB,QAAY,CAAC;AAC1C,eAAO,IAAI,iBAAiB,QAAK,CAAC;AAElC,eAAO,IAAI,WAAW,QAAc,CAAC;AACrC,eAAO,IAAI,WAAW,QAAO,CAAC;AAC9B,eAAO,IAAI,aAAa,QAAK,CAAC;AAC9B,eAAO,IAAI,gBAAgB,QAA0B,CAAC;AACtD,eAAO,IAAI,iBAAiB,QAAK,CAAC;AAClC,eAAO,IAAI,OAAO,QAAuB,CAAC;AAC1C,eAAO,IAAI,QAAQ,QAAwB,CAAC;AAE5C,eAAO,IAAI,0BAA0B,SAAO,CAAC;AAC7C,eAAO,IAAI,4BAA4B,QAAM,CAAC;AAC9C,eAAO,IAAI,8BAA8B,QAAK,CAAC;AAC/C,eAAO,IAAI,oCAAoC,QAAQ,CAAC;AACxD,eAAO,IAAI,mCAAmC,SAAO,CAAC;AACtD,eAAO,IAAI,wCAAwC,QAAK,CAAC;AACzD,eAAO,IAAI,qCAAqC,QAAS,CAAC;AAgD1D,OAAO,EAAE,qBAAqB,IAAI,cAAc,EAAE,CAAC;AACnD,wBAAgB,iBAAiB,IAAI,aAAa,CAEjD"}
|