@songsid/agend 2.0.11-beta.9 β 2.0.12-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backend/antigravity.d.ts +12 -0
- package/dist/backend/antigravity.js +66 -1
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-code.d.ts +2 -0
- package/dist/backend/claude-code.js +2 -0
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex.d.ts +10 -0
- package/dist/backend/codex.js +45 -0
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/gemini-cli.d.ts +2 -0
- package/dist/backend/gemini-cli.js +3 -0
- package/dist/backend/gemini-cli.js.map +1 -1
- package/dist/backend/kiro.d.ts +2 -0
- package/dist/backend/kiro.js +4 -0
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/mock.d.ts +2 -0
- package/dist/backend/mock.js +2 -0
- package/dist/backend/mock.js.map +1 -1
- package/dist/backend/opencode.d.ts +2 -0
- package/dist/backend/opencode.js +3 -0
- package/dist/backend/opencode.js.map +1 -1
- package/dist/backend/types.d.ts +12 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/channel/adapters/discord.js +21 -16
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.js +1 -0
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/mcp-tools.js +3 -0
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/tool-router.js +29 -2
- package/dist/channel/tool-router.js.map +1 -1
- package/dist/channel/types.d.ts +2 -0
- package/dist/classic-channel-manager.d.ts +59 -12
- package/dist/classic-channel-manager.js +121 -30
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli.js +70 -21
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.d.ts +20 -0
- package/dist/config-validator.js +154 -0
- package/dist/config-validator.js.map +1 -0
- package/dist/daemon.js +1 -1
- package/dist/daemon.js.map +1 -1
- package/dist/fleet-manager.d.ts +21 -5
- package/dist/fleet-manager.js +421 -259
- package/dist/fleet-manager.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +1 -1
- package/dist/instance-lifecycle.js +12 -11
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/locale.d.ts +19 -0
- package/dist/locale.js +179 -0
- package/dist/locale.js.map +1 -0
- package/dist/outbound-schemas.d.ts +2 -1
- package/dist/outbound-schemas.js +1 -0
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/quickstart.js +222 -9
- package/dist/quickstart.js.map +1 -1
- package/dist/scheduler/scheduler.js +8 -2
- package/dist/scheduler/scheduler.js.map +1 -1
- package/dist/settings-api.d.ts +33 -0
- package/dist/settings-api.js +283 -0
- package/dist/settings-api.js.map +1 -0
- package/dist/topic-commands.d.ts +28 -1
- package/dist/topic-commands.js +110 -19
- package/dist/topic-commands.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/ui/settings.html +617 -0
- package/dist/ui/view.html +271 -43
- package/dist/view-api.js +86 -37
- package/dist/view-api.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { join, dirname } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import yaml from "js-yaml";
|
|
5
|
+
import { validateFleetConfig, validateClassicBotConfig } from "./config-validator.js";
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
function json(res, code, body) {
|
|
8
|
+
res.writeHead(code, { "Content-Type": "application/json" });
|
|
9
|
+
res.end(JSON.stringify(body));
|
|
10
|
+
}
|
|
11
|
+
function readBody(req, maxBytes) {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
const chunks = [];
|
|
14
|
+
let size = 0;
|
|
15
|
+
req.on("data", (c) => {
|
|
16
|
+
size += c.length;
|
|
17
|
+
if (size > maxBytes) {
|
|
18
|
+
reject(new Error("payload too large"));
|
|
19
|
+
req.destroy();
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
chunks.push(c);
|
|
23
|
+
});
|
|
24
|
+
req.on("end", () => resolve(Buffer.concat(chunks)));
|
|
25
|
+
req.on("error", reject);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
export function isSettingsPath(path) {
|
|
29
|
+
return path === "/settings" || path.startsWith("/api/settings/");
|
|
30
|
+
}
|
|
31
|
+
const classicPath = (ctx) => join(ctx.dataDir, "classicBot.yaml");
|
|
32
|
+
function readClassic(ctx) {
|
|
33
|
+
const p = classicPath(ctx);
|
|
34
|
+
if (!existsSync(p))
|
|
35
|
+
return {};
|
|
36
|
+
try {
|
|
37
|
+
return yaml.load(readFileSync(p, "utf-8")) ?? {};
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
ctx.logger.warn({ err }, "settings: failed to parse classicBot.yaml");
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const issueKey = (i) => i.path + "\u0000" + i.message;
|
|
45
|
+
/**
|
|
46
|
+
* Reject a write only if it INTRODUCES new validation errors. Pre-existing
|
|
47
|
+
* errors elsewhere in the config (common while a fleet is being assembled)
|
|
48
|
+
* must not block an unrelated edit β otherwise one bad channel would lock the
|
|
49
|
+
* user out of saving anything, which reads as "save didn't persist".
|
|
50
|
+
* Returns true (and responds 400) when the edit adds errors.
|
|
51
|
+
*/
|
|
52
|
+
function rejectIfWorse(res, before, after) {
|
|
53
|
+
const had = new Set(before.errors.map(issueKey));
|
|
54
|
+
const introduced = after.errors.filter(e => !had.has(issueKey(e)));
|
|
55
|
+
if (introduced.length) {
|
|
56
|
+
json(res, 400, { ok: false, errors: introduced, warnings: after.warnings });
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
/** Warnings to surface on a successful save: new warnings + any pre-existing errors (informational). */
|
|
62
|
+
function saveWarnings(before, after) {
|
|
63
|
+
const had = new Set(before.errors.map(issueKey));
|
|
64
|
+
const preExisting = after.errors.filter(e => had.has(issueKey(e))).map(e => ({ path: e.path, message: "pre-existing: " + e.message }));
|
|
65
|
+
return [...after.warnings, ...preExisting];
|
|
66
|
+
}
|
|
67
|
+
export function handleSettingsRequest(req, res, url, ctx) {
|
|
68
|
+
const path = url.pathname;
|
|
69
|
+
if (!isSettingsPath(path))
|
|
70
|
+
return false;
|
|
71
|
+
const method = req.method ?? "GET";
|
|
72
|
+
// ββ Static page ββ
|
|
73
|
+
if (method === "GET" && path === "/settings") {
|
|
74
|
+
try {
|
|
75
|
+
const html = readFileSync(join(__dirname, "ui", "settings.html"), "utf-8");
|
|
76
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
77
|
+
res.end(html);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
json(res, 500, { error: "settings.html not found" });
|
|
81
|
+
}
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
// ββ Reads ββ
|
|
85
|
+
if (method === "GET" && path === "/api/settings/fleet") {
|
|
86
|
+
json(res, 200, ctx.fleetConfig ?? {});
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
if (method === "GET" && path === "/api/settings/classic") {
|
|
90
|
+
json(res, 200, readClassic(ctx));
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
// Everything below mutates β needs an in-memory fleet config.
|
|
94
|
+
const cfg = ctx.fleetConfig;
|
|
95
|
+
// ββ Fleet defaults ββ
|
|
96
|
+
if (method === "PUT" && path === "/api/settings/fleet/defaults") {
|
|
97
|
+
if (!cfg) {
|
|
98
|
+
json(res, 503, { error: "fleet not loaded" });
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
readBody(req, 512 * 1024).then(buf => {
|
|
102
|
+
let body;
|
|
103
|
+
try {
|
|
104
|
+
body = JSON.parse(buf.toString("utf-8") || "{}");
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return json(res, 400, { error: "invalid JSON" });
|
|
108
|
+
}
|
|
109
|
+
if (typeof body !== "object" || body === null || Array.isArray(body))
|
|
110
|
+
return json(res, 400, { error: "expected an object" });
|
|
111
|
+
const merged = { ...cfg.defaults, ...body };
|
|
112
|
+
const before = validateFleetConfig(cfg);
|
|
113
|
+
const after = validateFleetConfig({ ...cfg, defaults: merged });
|
|
114
|
+
if (rejectIfWorse(res, before, after))
|
|
115
|
+
return;
|
|
116
|
+
cfg.defaults = merged;
|
|
117
|
+
ctx.saveFleetConfig();
|
|
118
|
+
json(res, 200, { ok: true, warnings: saveWarnings(before, after) });
|
|
119
|
+
}).catch(() => json(res, 400, { error: "bad request" }));
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
// ββ Fleet channels (full replace) ββ
|
|
123
|
+
if (method === "PUT" && path === "/api/settings/fleet/channels") {
|
|
124
|
+
if (!cfg) {
|
|
125
|
+
json(res, 503, { error: "fleet not loaded" });
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
readBody(req, 512 * 1024).then(buf => {
|
|
129
|
+
let body;
|
|
130
|
+
try {
|
|
131
|
+
body = JSON.parse(buf.toString("utf-8") || "[]");
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return json(res, 400, { error: "invalid JSON" });
|
|
135
|
+
}
|
|
136
|
+
if (!Array.isArray(body))
|
|
137
|
+
return json(res, 400, { error: "expected an array of channels" });
|
|
138
|
+
const next = { ...cfg, channels: body };
|
|
139
|
+
delete next.channel; // channels[] supersedes the legacy single channel
|
|
140
|
+
const before = validateFleetConfig(cfg);
|
|
141
|
+
const after = validateFleetConfig(next);
|
|
142
|
+
if (rejectIfWorse(res, before, after))
|
|
143
|
+
return;
|
|
144
|
+
cfg.channels = body;
|
|
145
|
+
delete cfg.channel;
|
|
146
|
+
ctx.saveFleetConfig();
|
|
147
|
+
json(res, 200, { ok: true, warnings: saveWarnings(before, after) });
|
|
148
|
+
}).catch(() => json(res, 400, { error: "bad request" }));
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
// ββ Classic defaults ββ
|
|
152
|
+
if (method === "PUT" && path === "/api/settings/classic/defaults") {
|
|
153
|
+
readBody(req, 512 * 1024).then(buf => {
|
|
154
|
+
let body;
|
|
155
|
+
try {
|
|
156
|
+
body = JSON.parse(buf.toString("utf-8") || "{}");
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
return json(res, 400, { error: "invalid JSON" });
|
|
160
|
+
}
|
|
161
|
+
if (typeof body !== "object" || body === null || Array.isArray(body))
|
|
162
|
+
return json(res, 400, { error: "expected an object" });
|
|
163
|
+
const classic = readClassic(ctx);
|
|
164
|
+
const merged = { ...(classic.defaults ?? {}), ...body };
|
|
165
|
+
const before = validateClassicBotConfig(classic);
|
|
166
|
+
const after = validateClassicBotConfig({ ...classic, defaults: merged });
|
|
167
|
+
if (rejectIfWorse(res, before, after))
|
|
168
|
+
return;
|
|
169
|
+
classic.defaults = merged;
|
|
170
|
+
writeFileSync(classicPath(ctx), yaml.dump(classic, { lineWidth: -1 }));
|
|
171
|
+
ctx.logger.info("settings: updated classicBot defaults");
|
|
172
|
+
json(res, 200, { ok: true, warnings: saveWarnings(before, after) });
|
|
173
|
+
}).catch(() => json(res, 400, { error: "bad request" }));
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
// ββ Reload (SIGHUP) ββ
|
|
177
|
+
if (method === "POST" && path === "/api/settings/reload") {
|
|
178
|
+
ctx.logger.info("settings: reload requested β sending SIGHUP");
|
|
179
|
+
try {
|
|
180
|
+
process.kill(process.pid, "SIGHUP");
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
ctx.logger.warn({ err }, "settings: SIGHUP failed");
|
|
184
|
+
}
|
|
185
|
+
json(res, 200, { ok: true });
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
// ββ Instances (create / patch / delete) ββ
|
|
189
|
+
const validName = (n) => !!n && /^[^\\/\x00]+$/.test(n);
|
|
190
|
+
// Create-or-merge an instance, blocking only on newly-introduced errors.
|
|
191
|
+
const commitInstance = (name, exists, body) => {
|
|
192
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)) {
|
|
193
|
+
json(res, 400, { error: "expected an object" });
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const base = (exists ? cfg.instances[name] : {});
|
|
197
|
+
const mergedInst = { ...base, ...body };
|
|
198
|
+
const before = validateFleetConfig(cfg);
|
|
199
|
+
const after = validateFleetConfig({ ...cfg, instances: { ...cfg.instances, [name]: mergedInst } });
|
|
200
|
+
if (rejectIfWorse(res, before, after))
|
|
201
|
+
return;
|
|
202
|
+
cfg.instances[name] = mergedInst;
|
|
203
|
+
ctx.saveFleetConfig();
|
|
204
|
+
json(res, 200, { ok: true, warnings: saveWarnings(before, after) });
|
|
205
|
+
};
|
|
206
|
+
// POST /api/settings/fleet/instances β create, name taken from the body.
|
|
207
|
+
if (method === "POST" && path === "/api/settings/fleet/instances") {
|
|
208
|
+
if (!cfg) {
|
|
209
|
+
json(res, 503, { error: "fleet not loaded" });
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
readBody(req, 512 * 1024).then(buf => {
|
|
213
|
+
let body;
|
|
214
|
+
try {
|
|
215
|
+
body = JSON.parse(buf.toString("utf-8") || "{}");
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
return json(res, 400, { error: "invalid JSON" });
|
|
219
|
+
}
|
|
220
|
+
if (typeof body !== "object" || body === null || Array.isArray(body))
|
|
221
|
+
return json(res, 400, { error: "expected an object" });
|
|
222
|
+
const name = typeof body.name === "string" ? body.name.trim() : "";
|
|
223
|
+
if (!validName(name))
|
|
224
|
+
return json(res, 400, { error: "missing or invalid instance name (provide `name` in the body)" });
|
|
225
|
+
if (cfg.instances[name])
|
|
226
|
+
return json(res, 409, { error: "instance already exists" });
|
|
227
|
+
const { name: _n, ...instBody } = body;
|
|
228
|
+
commitInstance(name, false, instBody);
|
|
229
|
+
}).catch(() => json(res, 400, { error: "bad request" }));
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
// /api/settings/fleet/instances/:name β create / patch / delete.
|
|
233
|
+
const instPrefix = "/api/settings/fleet/instances/";
|
|
234
|
+
if (path.startsWith(instPrefix)) {
|
|
235
|
+
if (!cfg) {
|
|
236
|
+
json(res, 503, { error: "fleet not loaded" });
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
const name = decodeURIComponent(path.slice(instPrefix.length));
|
|
240
|
+
if (!validName(name)) {
|
|
241
|
+
json(res, 400, { error: "invalid instance name" });
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
if (method === "DELETE") {
|
|
245
|
+
if (!cfg.instances[name]) {
|
|
246
|
+
json(res, 404, { error: "instance not found" });
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
// DELETE never blocks on validation; surface any resulting warnings.
|
|
250
|
+
delete cfg.instances[name];
|
|
251
|
+
ctx.saveFleetConfig();
|
|
252
|
+
json(res, 200, { ok: true, warnings: validateFleetConfig(cfg).warnings });
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
if (method === "POST" || method === "PATCH") {
|
|
256
|
+
const exists = !!cfg.instances[name];
|
|
257
|
+
if (method === "POST" && exists) {
|
|
258
|
+
json(res, 409, { error: "instance already exists" });
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
if (method === "PATCH" && !exists) {
|
|
262
|
+
json(res, 404, { error: "instance not found" });
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
readBody(req, 512 * 1024).then(buf => {
|
|
266
|
+
let body;
|
|
267
|
+
try {
|
|
268
|
+
body = JSON.parse(buf.toString("utf-8") || "{}");
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
return json(res, 400, { error: "invalid JSON" });
|
|
272
|
+
}
|
|
273
|
+
commitInstance(name, exists, body);
|
|
274
|
+
}).catch(() => json(res, 400, { error: "bad request" }));
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
json(res, 405, { error: "method not allowed" });
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
json(res, 404, { error: "not found" });
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
//# sourceMappingURL=settings-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings-api.js","sourceRoot":"","sources":["../src/settings-api.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,IAAI,MAAM,SAAS,CAAC;AAG3B,OAAO,EAAE,mBAAmB,EAAE,wBAAwB,EAAyB,MAAM,uBAAuB,CAAC;AAE7G,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAU1D,SAAS,IAAI,CAAC,GAAmB,EAAE,IAAY,EAAE,IAAa;IAC5D,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;IAC5D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;AAChC,CAAC;AAED,SAAS,QAAQ,CAAC,GAAoB,EAAE,QAAgB;IACtD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAS,EAAE,EAAE;YAC3B,IAAI,IAAI,CAAC,CAAC,MAAM,CAAC;YACjB,IAAI,IAAI,GAAG,QAAQ,EAAE,CAAC;gBAAC,MAAM,CAAC,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC,CAAC;gBAAC,GAAG,CAAC,OAAO,EAAE,CAAC;gBAAC,OAAO;YAAC,CAAC;YACvF,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,CAAC,CAAC,CAAC;QACH,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACpD,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,IAAI,KAAK,WAAW,IAAI,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,WAAW,GAAG,CAAC,GAAuB,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC;AAEtF,SAAS,WAAW,CAAC,GAAuB;IAC1C,MAAM,CAAC,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IAC3B,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IAC9B,IAAI,CAAC;QAAC,OAAQ,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,EAAE,OAAO,CAAC,CAA6B,IAAI,EAAE,CAAC;IAAC,CAAC;IACtF,OAAO,GAAG,EAAE,CAAC;QAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,EAAE,2CAA2C,CAAC,CAAC;QAAC,OAAO,EAAE,CAAC;IAAC,CAAC;AACnG,CAAC;AAED,MAAM,QAAQ,GAAG,CAAC,CAAoC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,QAAQ,GAAG,CAAC,CAAC,OAAO,CAAC;AAEzF;;;;;;GAMG;AACH,SAAS,aAAa,CAAC,GAAmB,EAAE,MAAwB,EAAE,KAAuB;IAC3F,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC;IACjD,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACnE,IAAI,UAAU,CAAC,MAAM,EAAE,CAAC;QACtB,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;QAC5E,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,wGAAwG;AACxG,SAAS,YAAY,CAAC,MAAwB,EAAE,KAAuB;IACrE,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC;IACjD,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,gBAAgB,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IACvI,OAAO,CAAC,GAAG,KAAK,CAAC,QAAQ,EAAE,GAAG,WAAW,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,qBAAqB,CACnC,GAAoB,EACpB,GAAmB,EACnB,GAAQ,EACR,GAAuB;IAEvB,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC;IAC1B,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACxC,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,KAAK,CAAC;IAEnC,oBAAoB;IACpB,IAAI,MAAM,KAAK,KAAK,IAAI,IAAI,KAAK,WAAW,EAAE,CAAC;QAC7C,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,eAAe,CAAC,EAAE,OAAO,CAAC,CAAC;YAC3E,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,0BAA0B,EAAE,CAAC,CAAC;YACnE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAChB,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC,CAAC;QACvD,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,cAAc;IACd,IAAI,MAAM,KAAK,KAAK,IAAI,IAAI,KAAK,qBAAqB,EAAE,CAAC;QACvD,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;QACtC,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,MAAM,KAAK,KAAK,IAAI,IAAI,KAAK,uBAAuB,EAAE,CAAC;QACzD,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;QACjC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,8DAA8D;IAC9D,MAAM,GAAG,GAAG,GAAG,CAAC,WAAW,CAAC;IAE5B,uBAAuB;IACvB,IAAI,MAAM,KAAK,KAAK,IAAI,IAAI,KAAK,8BAA8B,EAAE,CAAC;QAChE,IAAI,CAAC,GAAG,EAAE,CAAC;YAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC,CAAC;YAAC,OAAO,IAAI,CAAC;QAAC,CAAC;QACzE,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;YACnC,IAAI,IAA6B,CAAC;YAClC,IAAI,CAAC;gBAAC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC;gBAAC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC;YAAC,CAAC;YACrH,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;gBAAE,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC,CAAC;YAC7H,MAAM,MAAM,GAAG,EAAE,GAAG,GAAG,CAAC,QAAQ,EAAE,GAAG,IAAI,EAAE,CAAC;YAC5C,MAAM,MAAM,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC;YACxC,MAAM,KAAK,GAAG,mBAAmB,CAAC,EAAE,GAAG,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;YAChE,IAAI,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC;gBAAE,OAAO;YAC9C,GAAG,CAAC,QAAQ,GAAG,MAA6B,CAAC;YAC7C,GAAG,CAAC,eAAe,EAAE,CAAC;YACtB,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;QACtE,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC;QACzD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,sCAAsC;IACtC,IAAI,MAAM,KAAK,KAAK,IAAI,IAAI,KAAK,8BAA8B,EAAE,CAAC;QAChE,IAAI,CAAC,GAAG,EAAE,CAAC;YAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC,CAAC;YAAC,OAAO,IAAI,CAAC;QAAC,CAAC;QACzE,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;YACnC,IAAI,IAAa,CAAC;YAClB,IAAI,CAAC;gBAAC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC;gBAAC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC;YAAC,CAAC;YACrH,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;gBAAE,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,+BAA+B,EAAE,CAAC,CAAC;YAC5F,MAAM,IAAI,GAAG,EAAE,GAAG,GAAG,EAAE,QAAQ,EAAE,IAA+B,EAAE,CAAC;YACnE,OAAQ,IAA8B,CAAC,OAAO,CAAC,CAAC,kDAAkD;YAClG,MAAM,MAAM,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC;YACxC,MAAM,KAAK,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;YACxC,IAAI,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC;gBAAE,OAAO;YAC9C,GAAG,CAAC,QAAQ,GAAG,IAA+B,CAAC;YAC/C,OAAQ,GAA6B,CAAC,OAAO,CAAC;YAC9C,GAAG,CAAC,eAAe,EAAE,CAAC;YACtB,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;QACtE,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC;QACzD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,yBAAyB;IACzB,IAAI,MAAM,KAAK,KAAK,IAAI,IAAI,KAAK,gCAAgC,EAAE,CAAC;QAClE,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;YACnC,IAAI,IAA6B,CAAC;YAClC,IAAI,CAAC;gBAAC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC;gBAAC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC;YAAC,CAAC;YACrH,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;gBAAE,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC,CAAC;YAC7H,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;YACjC,MAAM,MAAM,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,QAAmC,IAAI,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,CAAC;YACnF,MAAM,MAAM,GAAG,wBAAwB,CAAC,OAAO,CAAC,CAAC;YACjD,MAAM,KAAK,GAAG,wBAAwB,CAAC,EAAE,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;YACzE,IAAI,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC;gBAAE,OAAO;YAC9C,OAAO,CAAC,QAAQ,GAAG,MAAM,CAAC;YAC1B,aAAa,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YACvE,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,uCAAuC,CAAC,CAAC;YACzD,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;QACtE,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC;QACzD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,wBAAwB;IACxB,IAAI,MAAM,KAAK,MAAM,IAAI,IAAI,KAAK,sBAAsB,EAAE,CAAC;QACzD,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,6CAA6C,CAAC,CAAC;QAC/D,IAAI,CAAC;YAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QAAC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,EAAE,yBAAyB,CAAC,CAAC;QAAC,CAAC;QACjH,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,4CAA4C;IAC5C,MAAM,SAAS,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAChE,yEAAyE;IACzE,MAAM,cAAc,GAAG,CAAC,IAAY,EAAE,MAAe,EAAE,IAAa,EAAQ,EAAE;QAC5E,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC,CAAC;YAAC,OAAO;QAAC,CAAC;QAClI,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAA4B,CAAC;QAC7E,MAAM,UAAU,GAAG,EAAE,GAAG,IAAI,EAAE,GAAI,IAAgC,EAAE,CAAC;QACrE,MAAM,MAAM,GAAG,mBAAmB,CAAC,GAAI,CAAC,CAAC;QACzC,MAAM,KAAK,GAAG,mBAAmB,CAAC,EAAE,GAAG,GAAI,EAAE,SAAS,EAAE,EAAE,GAAG,GAAI,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC;QACrG,IAAI,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC;YAAE,OAAO;QAC9C,GAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,UAAyD,CAAC;QACjF,GAAG,CAAC,eAAe,EAAE,CAAC;QACtB,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;IACtE,CAAC,CAAC;IAEF,0EAA0E;IAC1E,IAAI,MAAM,KAAK,MAAM,IAAI,IAAI,KAAK,+BAA+B,EAAE,CAAC;QAClE,IAAI,CAAC,GAAG,EAAE,CAAC;YAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC,CAAC;YAAC,OAAO,IAAI,CAAC;QAAC,CAAC;QACzE,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;YACnC,IAAI,IAA6B,CAAC;YAClC,IAAI,CAAC;gBAAC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC;gBAAC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC;YAAC,CAAC;YACrH,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;gBAAE,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC,CAAC;YAC7H,MAAM,IAAI,GAAG,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACnE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;gBAAE,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,+DAA+D,EAAE,CAAC,CAAC;YACxH,IAAI,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC;gBAAE,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC,CAAC;YACrF,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,QAAQ,EAAE,GAAG,IAAI,CAAC;YACvC,cAAc,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;QACxC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC;QACzD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,kEAAkE;IAClE,MAAM,UAAU,GAAG,gCAAgC,CAAC;IACpD,IAAI,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAChC,IAAI,CAAC,GAAG,EAAE,CAAC;YAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC,CAAC;YAAC,OAAO,IAAI,CAAC;QAAC,CAAC;QACzE,MAAM,IAAI,GAAG,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;QAC/D,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;YAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,uBAAuB,EAAE,CAAC,CAAC;YAAC,OAAO,IAAI,CAAC;QAAC,CAAC;QAE1F,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;YACxB,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;gBAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC,CAAC;gBAAC,OAAO,IAAI,CAAC;YAAC,CAAC;YAC3F,qEAAqE;YACrE,OAAO,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YAC3B,GAAG,CAAC,eAAe,EAAE,CAAC;YACtB,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,mBAAmB,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;YAC1E,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,MAAM,KAAK,MAAM,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;YAC5C,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YACrC,IAAI,MAAM,KAAK,MAAM,IAAI,MAAM,EAAE,CAAC;gBAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC,CAAC;gBAAC,OAAO,IAAI,CAAC;YAAC,CAAC;YACvG,IAAI,MAAM,KAAK,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC;gBAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC,CAAC;gBAAC,OAAO,IAAI,CAAC;YAAC,CAAC;YACpG,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;gBACnC,IAAI,IAA6B,CAAC;gBAClC,IAAI,CAAC;oBAAC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,CAAC;gBAAC,CAAC;gBAAC,MAAM,CAAC;oBAAC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC;gBAAC,CAAC;gBACrH,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;YACrC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC;YACzD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC,CAAC;QAChD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;IACvC,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/dist/topic-commands.d.ts
CHANGED
|
@@ -13,12 +13,25 @@ export declare function saveCommandForBackend(backend: string, filename: string,
|
|
|
13
13
|
export declare function parseSaveFilename(text: string): string;
|
|
14
14
|
/** Shared message when a backend doesn't support /save. */
|
|
15
15
|
export declare const SAVE_UNSUPPORTED_MSG = "\u26A0\uFE0F /save is not supported for this backend (only kiro-cli and claude-code)";
|
|
16
|
+
/**
|
|
17
|
+
* The in-session compact/context-reset command for a backend NAME (the fleet
|
|
18
|
+
* process routes /compact via IPC and only has the backend string, not a
|
|
19
|
+
* CliBackend instance). Keep in sync with each backend's getCompactCommand().
|
|
20
|
+
* Most CLIs (claude-code, kiro-cli, codex, opencode, gemini-cli) use "/compact".
|
|
21
|
+
* Antigravity (agy) has NO summarizing compact β its only manual context-reset
|
|
22
|
+
* is "/clear" (a full reset; it also auto-summarizes at a token threshold).
|
|
23
|
+
*/
|
|
24
|
+
export declare function compactCommandForBackend(backend: string): string;
|
|
16
25
|
/**
|
|
17
26
|
* Extract context-usage % from a captured CLI pane. Scans bottom-up so the
|
|
18
27
|
* MOST RECENT prompt wins (a captured scrollback may hold several). Covers the
|
|
19
28
|
* common CLI prompt formats:
|
|
20
29
|
* kiro-cli classic: "6% !>" kiro-cli TUI: "β 6%"
|
|
21
30
|
* bracketed: "[6%]" claude/others prompt: "6% β―" / "6% >"
|
|
31
|
+
* codex TUI footer: "Context 94% left" (remaining) or "Context 6% used"
|
|
32
|
+
* opencode footer: "1.2K (6%)" (token count then parenthesized %)
|
|
33
|
+
* All values returned are context USED (low % = fresh session); codex's
|
|
34
|
+
* "N% left" is remaining, so it's inverted to 100 - N.
|
|
22
35
|
*/
|
|
23
36
|
export declare function parseContextPercent(pane: string): number | null;
|
|
24
37
|
export declare class TopicCommands {
|
|
@@ -28,11 +41,25 @@ export declare class TopicCommands {
|
|
|
28
41
|
private getReplyAdapter;
|
|
29
42
|
/** Parse and dispatch commands from the General topic */
|
|
30
43
|
handleGeneralCommand(msg: InboundMessage): Promise<boolean>;
|
|
44
|
+
/**
|
|
45
|
+
* Build the dashboard URL text (View / Settings / Web UI). The Settings/Web UI
|
|
46
|
+
* URLs carry the web token; when `htmlSpoiler` is set they're wrapped in a
|
|
47
|
+
* Telegram HTML spoiler (`<tg-spoiler>`) so the token isn't shown in the clear
|
|
48
|
+
* in a shared topic (the caller must send with format: "html"). /view is
|
|
49
|
+
* public, so it's never spoilered. DC uses the plain form (ephemeral reply).
|
|
50
|
+
*/
|
|
51
|
+
getDashboardText(htmlSpoiler?: boolean): string;
|
|
52
|
+
/**
|
|
53
|
+
* /dashboard (TG): admin-only. Replies directly in the topic; the token-
|
|
54
|
+
* bearing URLs are wrapped in a Telegram HTML spoiler so they aren't shown in
|
|
55
|
+
* the clear (the adapter supports plain/HTML, not MarkdownV2's `||β¦||`).
|
|
56
|
+
*/
|
|
57
|
+
private handleDashboardCommand;
|
|
31
58
|
/** Handle /ctx or /compact in any instance topic β returns true if handled */
|
|
32
59
|
handleInstanceCommand(msg: InboundMessage, instanceName: string): Promise<boolean>;
|
|
33
60
|
/** Get context usage text for an instance (shared by TG + DC) */
|
|
34
61
|
getCtxText(instanceName: string): Promise<string>;
|
|
35
|
-
/** Send
|
|
62
|
+
/** Send the backend-appropriate compact command to an instance's tmux pane */
|
|
36
63
|
sendCompact(instanceName: string): Promise<string>;
|
|
37
64
|
/** Send a backend-appropriate session-save command to a fleet-topic instance. */
|
|
38
65
|
sendSave(instanceName: string, filename: string): Promise<string>;
|
package/dist/topic-commands.js
CHANGED
|
@@ -6,6 +6,8 @@ import { createRequire } from "node:module";
|
|
|
6
6
|
const execAsync = promisify(exec);
|
|
7
7
|
import { DEFAULT_INSTANCE_CONFIG } from "./config.js";
|
|
8
8
|
import { formatCents } from "./cost-guard.js";
|
|
9
|
+
import { getAgendHome } from "./paths.js";
|
|
10
|
+
import { t } from "./locale.js";
|
|
9
11
|
/** Sanitize a directory name into a valid instance name. Keeps Unicode letters (incl. CJK). */
|
|
10
12
|
export function sanitizeInstanceName(name) {
|
|
11
13
|
const sanitized = name.toLowerCase().replace(/[^\p{L}\d-]/gu, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
@@ -31,18 +33,43 @@ export function parseSaveFilename(text) {
|
|
|
31
33
|
}
|
|
32
34
|
/** Shared message when a backend doesn't support /save. */
|
|
33
35
|
export const SAVE_UNSUPPORTED_MSG = "β οΈ /save is not supported for this backend (only kiro-cli and claude-code)";
|
|
36
|
+
/**
|
|
37
|
+
* The in-session compact/context-reset command for a backend NAME (the fleet
|
|
38
|
+
* process routes /compact via IPC and only has the backend string, not a
|
|
39
|
+
* CliBackend instance). Keep in sync with each backend's getCompactCommand().
|
|
40
|
+
* Most CLIs (claude-code, kiro-cli, codex, opencode, gemini-cli) use "/compact".
|
|
41
|
+
* Antigravity (agy) has NO summarizing compact β its only manual context-reset
|
|
42
|
+
* is "/clear" (a full reset; it also auto-summarizes at a token threshold).
|
|
43
|
+
*/
|
|
44
|
+
export function compactCommandForBackend(backend) {
|
|
45
|
+
if (backend === "antigravity")
|
|
46
|
+
return "/clear";
|
|
47
|
+
return "/compact";
|
|
48
|
+
}
|
|
34
49
|
/**
|
|
35
50
|
* Extract context-usage % from a captured CLI pane. Scans bottom-up so the
|
|
36
51
|
* MOST RECENT prompt wins (a captured scrollback may hold several). Covers the
|
|
37
52
|
* common CLI prompt formats:
|
|
38
53
|
* kiro-cli classic: "6% !>" kiro-cli TUI: "β 6%"
|
|
39
54
|
* bracketed: "[6%]" claude/others prompt: "6% β―" / "6% >"
|
|
55
|
+
* codex TUI footer: "Context 94% left" (remaining) or "Context 6% used"
|
|
56
|
+
* opencode footer: "1.2K (6%)" (token count then parenthesized %)
|
|
57
|
+
* All values returned are context USED (low % = fresh session); codex's
|
|
58
|
+
* "N% left" is remaining, so it's inverted to 100 - N.
|
|
40
59
|
*/
|
|
41
60
|
export function parseContextPercent(pane) {
|
|
42
61
|
const lines = pane.split("\n");
|
|
43
62
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
44
63
|
const line = lines[i];
|
|
45
|
-
|
|
64
|
+
// codex "context-remaining" item shows "Context N% left" (REMAINING) β used = 100 - N
|
|
65
|
+
const left = line.match(/Context\s+(\d+)%\s+left/i);
|
|
66
|
+
if (left)
|
|
67
|
+
return 100 - parseInt(left[1], 10);
|
|
68
|
+
const m = line.match(/(\d+)%.*[!β―>]/)
|
|
69
|
+
|| line.match(/β\s*(\d+)%/)
|
|
70
|
+
|| line.match(/\[(\d+)%\]/)
|
|
71
|
+
|| line.match(/Context\s+(\d+)%\s+used/i) // codex "context-used" variant
|
|
72
|
+
|| line.match(/\d+(?:\.\d+)?[KM]?\s*\((\d+)%\)/); // opencode "1.2K (6%)"
|
|
46
73
|
if (m)
|
|
47
74
|
return parseInt(m[1], 10);
|
|
48
75
|
}
|
|
@@ -86,8 +113,60 @@ export class TopicCommands {
|
|
|
86
113
|
await this.handleUpdateCommand(msg);
|
|
87
114
|
return true;
|
|
88
115
|
}
|
|
116
|
+
if (text === "/dashboard" || text.startsWith("/dashboard@")) {
|
|
117
|
+
await this.handleDashboardCommand(msg);
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
89
120
|
return false;
|
|
90
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* Build the dashboard URL text (View / Settings / Web UI). The Settings/Web UI
|
|
124
|
+
* URLs carry the web token; when `htmlSpoiler` is set they're wrapped in a
|
|
125
|
+
* Telegram HTML spoiler (`<tg-spoiler>`) so the token isn't shown in the clear
|
|
126
|
+
* in a shared topic (the caller must send with format: "html"). /view is
|
|
127
|
+
* public, so it's never spoilered. DC uses the plain form (ephemeral reply).
|
|
128
|
+
*/
|
|
129
|
+
getDashboardText(htmlSpoiler = false) {
|
|
130
|
+
const port = this.ctx.fleetConfig?.health_port ?? 19280;
|
|
131
|
+
const host = this.ctx.fleetConfig?.hostname || "localhost";
|
|
132
|
+
let token = "";
|
|
133
|
+
try {
|
|
134
|
+
token = readFileSync(join(getAgendHome(), "web.token"), "utf-8").trim();
|
|
135
|
+
}
|
|
136
|
+
catch { /* not started yet */ }
|
|
137
|
+
const base = `http://${host}:${port}`;
|
|
138
|
+
const hide = (u) => htmlSpoiler ? `<tg-spoiler>${u}</tg-spoiler>` : u;
|
|
139
|
+
return [
|
|
140
|
+
t("dashboard.title"),
|
|
141
|
+
"",
|
|
142
|
+
`β’ View: ${base}/view`,
|
|
143
|
+
`β’ View (edit): ${hide(`${base}/view?token=${token}`)}`,
|
|
144
|
+
`β’ Settings: ${hide(`${base}/settings?token=${token}`)}`,
|
|
145
|
+
`β’ Web UI: ${hide(`${base}/ui?token=${token}`)}`,
|
|
146
|
+
].join("\n");
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* /dashboard (TG): admin-only. Replies directly in the topic; the token-
|
|
150
|
+
* bearing URLs are wrapped in a Telegram HTML spoiler so they aren't shown in
|
|
151
|
+
* the clear (the adapter supports plain/HTML, not MarkdownV2's `||β¦||`).
|
|
152
|
+
*/
|
|
153
|
+
async handleDashboardCommand(msg) {
|
|
154
|
+
const adapter = this.getReplyAdapter(msg);
|
|
155
|
+
if (!adapter)
|
|
156
|
+
return;
|
|
157
|
+
const chatId = msg.chatId;
|
|
158
|
+
const threadId = msg.threadId;
|
|
159
|
+
const allowed = this.ctx.fleetConfig?.channel?.access?.allowed_users ?? [];
|
|
160
|
+
if (allowed.length === 0) {
|
|
161
|
+
await adapter.sendText(chatId, t("dashboard.disabled"), { threadId });
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (!allowed.some(u => String(u) === String(msg.userId))) {
|
|
165
|
+
await adapter.sendText(chatId, t("not_authorized"), { threadId });
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
await adapter.sendText(chatId, this.getDashboardText(true), { threadId, format: "html" });
|
|
169
|
+
}
|
|
91
170
|
/** Handle /ctx or /compact in any instance topic β returns true if handled */
|
|
92
171
|
async handleInstanceCommand(msg, instanceName) {
|
|
93
172
|
const text = msg.text?.trim();
|
|
@@ -99,8 +178,8 @@ export class TopicCommands {
|
|
|
99
178
|
return false;
|
|
100
179
|
const isCollab = this.ctx.toggleFleetCollab(instanceName);
|
|
101
180
|
await adapter.sendText(msg.chatId, isCollab
|
|
102
|
-
? "
|
|
103
|
-
: "
|
|
181
|
+
? t("collab.on")
|
|
182
|
+
: t("collab.off"), { threadId: msg.threadId });
|
|
104
183
|
return true;
|
|
105
184
|
}
|
|
106
185
|
if (text === "/compact" || text.startsWith("/compact@")) {
|
|
@@ -116,7 +195,7 @@ export class TopicCommands {
|
|
|
116
195
|
if (!adapter)
|
|
117
196
|
return false;
|
|
118
197
|
const ok = this.ctx.cancelInstance(instanceName);
|
|
119
|
-
await adapter.sendText(msg.chatId, ok ?
|
|
198
|
+
await adapter.sendText(msg.chatId, ok ? t("cancel.sent", instanceName) : t("cancel.not_running", instanceName), { threadId: msg.threadId });
|
|
120
199
|
return true;
|
|
121
200
|
}
|
|
122
201
|
if (text === "/save" || text.startsWith("/save ") || text.startsWith("/save@")) {
|
|
@@ -125,11 +204,11 @@ export class TopicCommands {
|
|
|
125
204
|
return false;
|
|
126
205
|
const filename = parseSaveFilename(text);
|
|
127
206
|
if (!filename) {
|
|
128
|
-
await adapter.sendText(msg.chatId, "
|
|
207
|
+
await adapter.sendText(msg.chatId, t("save.usage"), { threadId: msg.threadId });
|
|
129
208
|
return true;
|
|
130
209
|
}
|
|
131
210
|
if (!SAVE_FILENAME_RE.test(filename)) {
|
|
132
|
-
await adapter.sendText(msg.chatId, "
|
|
211
|
+
await adapter.sendText(msg.chatId, t("filename.invalid"), { threadId: msg.threadId });
|
|
133
212
|
return true;
|
|
134
213
|
}
|
|
135
214
|
const result = await this.sendSave(instanceName, filename);
|
|
@@ -173,15 +252,18 @@ export class TopicCommands {
|
|
|
173
252
|
catch { /* ignore */ }
|
|
174
253
|
}
|
|
175
254
|
return context != null
|
|
176
|
-
?
|
|
177
|
-
:
|
|
255
|
+
? `${t("ctx.used", context)}\n${t("ctx.backend", backend)}\n${t("ctx.instance", instanceName)}`
|
|
256
|
+
: `${t("ctx.unavailable")}\n${t("ctx.backend", backend)}\n${t("ctx.instance", instanceName)}`;
|
|
178
257
|
}
|
|
179
|
-
/** Send
|
|
258
|
+
/** Send the backend-appropriate compact command to an instance's tmux pane */
|
|
180
259
|
async sendCompact(instanceName) {
|
|
181
260
|
const ipc = this.ctx.instanceIpcClients.get(instanceName);
|
|
182
261
|
if (ipc?.connected) {
|
|
183
|
-
|
|
184
|
-
|
|
262
|
+
const backend = this.ctx.fleetConfig?.instances[instanceName]?.backend
|
|
263
|
+
?? this.ctx.fleetConfig?.defaults?.backend ?? "claude-code";
|
|
264
|
+
const cmd = compactCommandForBackend(backend);
|
|
265
|
+
ipc.send({ type: "raw_paste", content: cmd });
|
|
266
|
+
return `ποΈ Compact command sent (\`${cmd}\`).`;
|
|
185
267
|
}
|
|
186
268
|
return "β Instance not connected (IPC unavailable)";
|
|
187
269
|
}
|
|
@@ -207,10 +289,10 @@ export class TopicCommands {
|
|
|
207
289
|
const threadId = msg.threadId;
|
|
208
290
|
const allowed = this.ctx.fleetConfig?.channel?.access?.allowed_users ?? [];
|
|
209
291
|
if (allowed.length > 0 && !allowed.some(u => String(u) === String(msg.userId))) {
|
|
210
|
-
await adapter.sendText(chatId, "
|
|
292
|
+
await adapter.sendText(chatId, t("not_authorized"), { threadId });
|
|
211
293
|
return;
|
|
212
294
|
}
|
|
213
|
-
await adapter.sendText(chatId, "
|
|
295
|
+
await adapter.sendText(chatId, t("restart.graceful"), { threadId });
|
|
214
296
|
process.kill(process.pid, "SIGUSR2");
|
|
215
297
|
}
|
|
216
298
|
async handleStatusCommand(msg) {
|
|
@@ -314,14 +396,14 @@ export class TopicCommands {
|
|
|
314
396
|
// Access control β only allowed users can trigger update; empty = disabled
|
|
315
397
|
const allowed = this.ctx.fleetConfig?.channel?.access?.allowed_users ?? [];
|
|
316
398
|
if (allowed.length === 0) {
|
|
317
|
-
await adapter.sendText(chatId, "
|
|
399
|
+
await adapter.sendText(chatId, t("update.disabled"), { threadId });
|
|
318
400
|
return;
|
|
319
401
|
}
|
|
320
402
|
if (!allowed.some(u => String(u) === String(msg.userId))) {
|
|
321
|
-
await adapter.sendText(chatId, "
|
|
403
|
+
await adapter.sendText(chatId, t("not_authorized"), { threadId });
|
|
322
404
|
return;
|
|
323
405
|
}
|
|
324
|
-
await adapter.sendText(chatId, "
|
|
406
|
+
await adapter.sendText(chatId, t("update.running"), { threadId });
|
|
325
407
|
const currentVersion = createRequire(import.meta.url)("../package.json").version ?? "";
|
|
326
408
|
const updateCmd = currentVersion.includes("beta") ? "agend update --beta" : "agend update";
|
|
327
409
|
const { spawn } = await import("node:child_process");
|
|
@@ -336,10 +418,10 @@ export class TopicCommands {
|
|
|
336
418
|
const threadId = msg.threadId;
|
|
337
419
|
const allowed = this.ctx.fleetConfig?.channel?.access?.allowed_users ?? [];
|
|
338
420
|
if (allowed.length > 0 && !allowed.some(u => String(u) === String(msg.userId))) {
|
|
339
|
-
await adapter.sendText(chatId, "
|
|
421
|
+
await adapter.sendText(chatId, t("not_authorized"), { threadId });
|
|
340
422
|
return;
|
|
341
423
|
}
|
|
342
|
-
await adapter.sendText(chatId, "
|
|
424
|
+
await adapter.sendText(chatId, t("doctor.running"), { threadId });
|
|
343
425
|
try {
|
|
344
426
|
const { execSync } = await import("node:child_process");
|
|
345
427
|
const backend = this.ctx.fleetConfig?.defaults?.backend || "claude-code";
|
|
@@ -419,7 +501,15 @@ export class TopicCommands {
|
|
|
419
501
|
}
|
|
420
502
|
if (platformType === "discord") {
|
|
421
503
|
const ch = channels.find(c => c.type === "discord");
|
|
422
|
-
|
|
504
|
+
const gcid = ch?.options?.general_channel_id;
|
|
505
|
+
// A Discord general needs a real channel id β NOT the TG-convention
|
|
506
|
+
// "1", which makes the DC adapter throw fetching channel "1". Skip
|
|
507
|
+
// (leave unbound) if there's no valid channel to bind to.
|
|
508
|
+
if (gcid == null || !/^\d{17,}$/.test(String(gcid))) {
|
|
509
|
+
this.ctx.logger.warn({ name }, "Discord general has no valid general_channel_id β skipping topic bind (set channel.options.general_channel_id)");
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
config.topic_id = gcid;
|
|
423
513
|
}
|
|
424
514
|
else {
|
|
425
515
|
config.topic_id = 1;
|
|
@@ -463,6 +553,7 @@ export class TopicCommands {
|
|
|
463
553
|
commands: [
|
|
464
554
|
{ command: "status", description: "Show fleet status and costs" },
|
|
465
555
|
{ command: "sysinfo", description: "System diagnostics" },
|
|
556
|
+
{ command: "dashboard", description: "π Get dashboard URLs" },
|
|
466
557
|
{ command: "ctx", description: "Show context usage" },
|
|
467
558
|
{ command: "compact", description: "Compact agent context" },
|
|
468
559
|
{ command: "restart", description: "π Graceful restart all instances" },
|