@euqns/nudge-mcp 1.36.0 → 1.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,343 @@
1
+ // Local T3 transport and fallback log view. Native execution lives in
2
+ // runner-t3-engine.ts; neither integration writes T3's database.
3
+ import { execFile } from "node:child_process";
4
+ import { createHash, randomUUID } from "node:crypto";
5
+ import { access, appendFile, mkdir, readFile, realpath, writeFile } from "node:fs/promises";
6
+ import os from "node:os";
7
+ import { createRequire } from "node:module";
8
+ import path from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+ import { promisify } from "node:util";
11
+ import WebSocket from "ws";
12
+ const exec = promisify(execFile);
13
+ const TIMEOUT = 3_000;
14
+ const MAX_LOG_BYTES = 8 * 1024 * 1024;
15
+ export function localT3Origin(runtime) {
16
+ if (runtime.version !== 1 || !Number.isInteger(runtime.port) || runtime.port < 1 || runtime.port > 65535) {
17
+ throw new Error("Unsupported T3 runtime descriptor");
18
+ }
19
+ const url = new URL(runtime.origin);
20
+ if (url.protocol !== "http:" || !["127.0.0.1", "localhost", "[::1]", "0.0.0.0", "[::]"].includes(url.hostname)
21
+ || url.username || url.password || url.search || url.hash || url.pathname !== "/"
22
+ || Number(url.port || 80) !== runtime.port) {
23
+ throw new Error("T3 runner integration requires a loopback server");
24
+ }
25
+ // Wildcard listeners are reachable through loopback; never follow a remote origin.
26
+ return `http://${url.hostname.includes(":") ? "[::1]" : "127.0.0.1"}:${runtime.port}`;
27
+ }
28
+ export function t3JobThreadId(deployment, jobId) {
29
+ return `nudge-${createHash("sha256").update(`${deployment}\0${jobId}`).digest("hex").slice(0, 32)}`;
30
+ }
31
+ export function t3JobTitle(job, status) {
32
+ return `Nudge · ${status.replaceAll("_", " ")} · ${job.boardTitle} · ${job.kind} ${job.id.slice(-6)}`.slice(0, 220);
33
+ }
34
+ // T3's terminals use the machine's shell. This integration currently opens log
35
+ // viewers only on POSIX hosts; Windows still gets a thread and status updates.
36
+ export function shellQuote(value) {
37
+ return `'${value.replaceAll("'", "'\\''")}'`;
38
+ }
39
+ async function findCli() {
40
+ const env = { ...process.env };
41
+ // An explicit executable supports nonstandard installs and wrapper scripts.
42
+ if (env.NUDGE_T3_CLI)
43
+ return { command: env.NUDGE_T3_CLI, args: [], env };
44
+ if (process.platform === "darwin") {
45
+ for (const root of ["/Applications", path.join(os.homedir(), "Applications")]) {
46
+ for (const name of ["T3 Code (Nightly)", "T3 Code"]) {
47
+ const app = path.join(root, `${name}.app`, "Contents");
48
+ const command = path.join(app, "MacOS", name);
49
+ const entry = path.join(app, "Resources/app.asar/apps/server/dist/bin.mjs");
50
+ try {
51
+ await access(command);
52
+ return { command, args: [entry], env: { ...env, ELECTRON_RUN_AS_NODE: "1" } };
53
+ }
54
+ catch { /* Try the next installation. */ }
55
+ }
56
+ }
57
+ }
58
+ for (const dir of (env.PATH ?? "").split(path.delimiter)) {
59
+ const command = path.join(dir, process.platform === "win32" ? "t3.exe" : "t3");
60
+ try {
61
+ await access(command);
62
+ return { command, args: [], env };
63
+ }
64
+ catch { /* Not installed here. */ }
65
+ }
66
+ return null;
67
+ }
68
+ export class T3Client {
69
+ origin;
70
+ token;
71
+ constructor(origin, token) {
72
+ this.origin = origin;
73
+ this.token = token;
74
+ }
75
+ async request(route, body) {
76
+ const response = await fetch(`${this.origin}${route}`, {
77
+ method: body ? "POST" : "GET",
78
+ headers: { authorization: `Bearer ${this.token}`, "content-type": "application/json" },
79
+ ...(body ? { body: JSON.stringify(body) } : {}),
80
+ redirect: "error",
81
+ signal: AbortSignal.timeout(TIMEOUT),
82
+ });
83
+ // Error bodies can contain prompts or credentials: do not include them in logs.
84
+ if (!response.ok)
85
+ throw new Error(`T3 ${route} returned HTTP ${response.status}`);
86
+ return await response.json();
87
+ }
88
+ dispatch(command) {
89
+ return this.request("/api/orchestration/dispatch", { commandId: randomUUID(), ...command });
90
+ }
91
+ async rpc(tag, payload = {}) {
92
+ const socket = new WebSocket(`${this.origin.replace("http:", "ws:")}/ws`, {
93
+ headers: { authorization: `Bearer ${this.token}` }, handshakeTimeout: TIMEOUT,
94
+ });
95
+ socket.on("error", () => undefined);
96
+ try {
97
+ await new Promise((resolve, reject) => {
98
+ socket.once("open", resolve);
99
+ socket.once("error", () => reject(new Error("T3 socket failed")));
100
+ });
101
+ return await t3Rpc(socket, tag, payload);
102
+ }
103
+ finally {
104
+ socket.terminate();
105
+ }
106
+ }
107
+ async terminalLog(threadId, cwd, logPath) {
108
+ if (process.platform === "win32")
109
+ return;
110
+ const socket = new WebSocket(`${this.origin.replace("http:", "ws:")}/ws`, {
111
+ headers: { authorization: `Bearer ${this.token}` },
112
+ handshakeTimeout: TIMEOUT,
113
+ maxPayload: 1024 * 1024,
114
+ });
115
+ // Keep an error handler installed even during teardown.
116
+ socket.on("error", () => undefined);
117
+ try {
118
+ await new Promise((resolve, reject) => {
119
+ const timer = setTimeout(() => reject(new Error("T3 socket timed out")), TIMEOUT);
120
+ socket.once("open", () => { clearTimeout(timer); resolve(); });
121
+ socket.once("error", () => { clearTimeout(timer); reject(new Error("T3 socket failed")); });
122
+ });
123
+ await t3Rpc(socket, "terminal.open", { threadId, terminalId: "term-1", cwd, cols: 120, rows: 32 });
124
+ let viewer = fileURLToPath(new URL("./runner-t3-log-viewer.js", import.meta.url));
125
+ const nodeArgs = [];
126
+ try {
127
+ await access(viewer);
128
+ }
129
+ catch {
130
+ viewer = fileURLToPath(new URL("./runner-t3-log-viewer.ts", import.meta.url));
131
+ nodeArgs.push("--import", createRequire(import.meta.url).resolve("tsx"));
132
+ }
133
+ // Never send task text to a shell. The viewer only prints a local data file.
134
+ await t3Rpc(socket, "terminal.write", {
135
+ threadId, terminalId: "term-1",
136
+ data: `${[process.execPath, ...nodeArgs, viewer, logPath].map(shellQuote).join(" ")}\r`,
137
+ });
138
+ }
139
+ finally {
140
+ socket.terminate();
141
+ }
142
+ }
143
+ }
144
+ export function t3Rpc(socket, tag, payload) {
145
+ return new Promise((resolve, reject) => {
146
+ const id = randomUUID();
147
+ const cleanup = () => {
148
+ clearTimeout(timer);
149
+ socket.off("message", onMessage);
150
+ socket.off("close", onClose);
151
+ socket.off("error", onClose);
152
+ };
153
+ const onClose = () => { cleanup(); reject(new Error("T3 socket closed")); };
154
+ const onMessage = (raw) => {
155
+ try {
156
+ const parsed = JSON.parse(raw.toString());
157
+ for (const message of Array.isArray(parsed) ? parsed : [parsed]) {
158
+ if (message._tag !== "Exit" || message.requestId !== id)
159
+ continue;
160
+ cleanup();
161
+ if (message.exit?._tag === "Success")
162
+ resolve(message.exit.value);
163
+ else
164
+ reject(new Error(`T3 ${tag} failed`));
165
+ }
166
+ }
167
+ catch {
168
+ cleanup();
169
+ reject(new Error("Invalid T3 RPC response"));
170
+ }
171
+ };
172
+ const timer = setTimeout(() => { cleanup(); reject(new Error(`T3 ${tag} timed out`)); }, TIMEOUT);
173
+ socket.on("message", onMessage);
174
+ socket.once("close", onClose);
175
+ socket.once("error", onClose);
176
+ try {
177
+ socket.send(JSON.stringify({ _tag: "Request", id, tag, payload, headers: [] }));
178
+ }
179
+ catch {
180
+ cleanup();
181
+ reject(new Error("T3 socket send failed"));
182
+ }
183
+ });
184
+ }
185
+ export async function discoverT3() {
186
+ if (process.env.NUDGE_T3 === "0")
187
+ return null;
188
+ const home = path.resolve(process.env.T3CODE_HOME ?? path.join(os.homedir(), ".t3"));
189
+ for (const variant of ["userdata", "dev"]) {
190
+ let runtime;
191
+ try {
192
+ runtime = JSON.parse(await readFile(path.join(home, variant, "server-runtime.json"), "utf8"));
193
+ }
194
+ catch (error) {
195
+ if (error.code === "ENOENT")
196
+ continue;
197
+ throw new Error("Cannot read T3 runtime descriptor");
198
+ }
199
+ const origin = localT3Origin(runtime);
200
+ if (!Number.isInteger(runtime.pid) || runtime.pid < 1)
201
+ continue;
202
+ try {
203
+ process.kill(runtime.pid, 0);
204
+ }
205
+ catch {
206
+ continue;
207
+ }
208
+ const probe = await fetch(`${origin}/.well-known/t3/environment`, { signal: AbortSignal.timeout(TIMEOUT), redirect: "error" });
209
+ if (!probe.ok)
210
+ throw new Error("Local T3 server is unavailable");
211
+ const descriptor = await probe.json();
212
+ if (typeof descriptor.environmentId !== "string" || typeof descriptor.serverVersion !== "string")
213
+ throw new Error("Unsupported T3 environment descriptor");
214
+ const cli = await findCli();
215
+ if (!cli)
216
+ throw new Error("T3 CLI not found; set NUDGE_T3_CLI to its executable");
217
+ return { origin, cli, home, dev: variant === "dev", environmentId: descriptor.environmentId };
218
+ }
219
+ return null;
220
+ }
221
+ export async function withT3Client(connection, use, ttl = "5m") {
222
+ const { cli, home, dev } = connection;
223
+ const location = ["--base-dir", home, ...(dev ? ["--dev-url", "http://localhost"] : [])];
224
+ const run = async (args) => {
225
+ try {
226
+ return await exec(cli.command, [...cli.args, "auth", "session", ...args, ...location], {
227
+ env: cli.env, timeout: 10_000, maxBuffer: 64 * 1024, windowsHide: true,
228
+ });
229
+ }
230
+ catch {
231
+ throw new Error("T3 local session command failed");
232
+ }
233
+ };
234
+ const { stdout } = await run(["issue", "--ttl", ttl, "--label", "Nudge runner", "--json"]);
235
+ let session;
236
+ try {
237
+ session = JSON.parse(stdout);
238
+ }
239
+ catch {
240
+ throw new Error("Invalid T3 session response");
241
+ }
242
+ if (typeof session.token !== "string" || typeof session.sessionId !== "string")
243
+ throw new Error("Invalid T3 session response");
244
+ try {
245
+ return await use(new T3Client(connection.origin, session.token));
246
+ }
247
+ finally {
248
+ await run(["revoke", session.sessionId]).catch(() => undefined);
249
+ }
250
+ }
251
+ export async function ensureT3JobThread(client, job, native) {
252
+ const snapshot = await client.request("/api/orchestration/snapshot");
253
+ const cwd = await realpath(job.cwd);
254
+ let project = snapshot.projects.find((p) => path.resolve(p.workspaceRoot) === cwd && !p.deletedAt);
255
+ if (!project) {
256
+ const projectId = `nudge-project-${createHash("sha256").update(cwd).digest("hex").slice(0, 24)}`;
257
+ try {
258
+ await client.dispatch({ type: "project.create", projectId, title: path.basename(cwd), workspaceRoot: cwd, createdAt: new Date().toISOString() });
259
+ }
260
+ catch (error) {
261
+ // Concurrent jobs may have created the same project first.
262
+ const latest = await client.request("/api/orchestration/snapshot");
263
+ if (!latest.projects.some((p) => p.id === projectId && !p.deletedAt))
264
+ throw error;
265
+ }
266
+ project = { id: projectId };
267
+ }
268
+ const threadId = native?.threadId ?? t3JobThreadId(job.deployment, job.id);
269
+ const title = t3JobTitle(job, "running");
270
+ const existing = snapshot.threads.find((t) => t.id === threadId && !t.deletedAt);
271
+ if (!existing) {
272
+ await client.dispatch({
273
+ type: "thread.create", threadId, projectId: project.id, title,
274
+ modelSelection: native?.modelSelection ?? project.defaultModelSelection ?? { instanceId: job.engine === "claude" ? "claudeAgent" : job.engine, model: job.model },
275
+ runtimeMode: native?.runtimeMode ?? "approval-required", interactionMode: "default",
276
+ branch: null, worktreePath: null, createdAt: new Date().toISOString(),
277
+ });
278
+ }
279
+ else {
280
+ if (existing.archivedAt)
281
+ await client.dispatch({ type: "thread.unarchive", threadId });
282
+ if (existing.settledAt || existing.settledOverride === "settled") {
283
+ await client.dispatch({ type: "thread.unsettle", threadId, reason: "user" });
284
+ }
285
+ await client.dispatch({ type: "thread.meta.update", threadId, title });
286
+ }
287
+ return threadId;
288
+ }
289
+ export async function startT3JobView(job, warn) {
290
+ try {
291
+ const connection = await discoverT3();
292
+ if (!connection)
293
+ return null;
294
+ const threadId = t3JobThreadId(job.deployment, job.id);
295
+ const directory = path.join(os.homedir(), ".nudge", "t3-logs");
296
+ await mkdir(directory, { recursive: true, mode: 0o700 });
297
+ // Separate files for resumes prevent an old viewer consuming the new run.
298
+ const logPath = path.join(directory, `${threadId}-${randomUUID()}.jsonl`);
299
+ const encode = (text, done = false) => `${JSON.stringify({ text, done })}\n`;
300
+ const heading = `Nudge runner · ${job.boardTitle}\nJob: ${job.id} · attempt ${job.attempt}\nAgent: ${job.engine} · ${job.model}\n\nThis terminal displays the external runner. Reply or steer in Nudge.\n\n${job.prompt}\n\n`;
301
+ await writeFile(logPath, encode(heading), { mode: 0o600 });
302
+ await withT3Client(connection, async (client) => {
303
+ await ensureT3JobThread(client, job);
304
+ // A terminal failure must not discard a successfully created thread.
305
+ await client.terminalLog(threadId, job.cwd, logPath).catch(() => warn(`T3 thread created; its log viewer could not open. Log: ${logPath}`));
306
+ });
307
+ let queued = Promise.resolve();
308
+ let bytes = Buffer.byteLength(heading);
309
+ let truncated = false;
310
+ let finished = false;
311
+ const append = (text, done = false) => {
312
+ queued = queued.then(() => appendFile(logPath, encode(text, done))).catch(() => undefined);
313
+ };
314
+ return {
315
+ threadId, logPath,
316
+ log(stream, text) {
317
+ if (finished || truncated)
318
+ return;
319
+ bytes += Buffer.byteLength(text);
320
+ if (bytes > MAX_LOG_BYTES) {
321
+ truncated = true;
322
+ append("\n[Live log limit reached. Final result will follow.]\n");
323
+ return;
324
+ }
325
+ append(`[${stream}] ${text}\n`);
326
+ },
327
+ async finish(status, summary, error) {
328
+ if (finished)
329
+ return;
330
+ finished = true;
331
+ append(`\nNudge job: ${status}\n${summary ?? ""}\n${error ?? ""}\n`, true);
332
+ await queued;
333
+ await withT3Client(connection, (client) => client.dispatch({ type: "thread.meta.update", threadId, title: t3JobTitle(job, status) }))
334
+ .catch(() => warn("T3 thread status could not be updated; the job result is recorded in Nudge."));
335
+ },
336
+ };
337
+ }
338
+ catch (error) {
339
+ warn(`T3 view unavailable: ${error instanceof Error ? error.message : "local integration failed"}. The runner will continue.`);
340
+ return null;
341
+ }
342
+ }
343
+ //# sourceMappingURL=runner-t3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner-t3.js","sourceRoot":"","sources":["../src/runner-t3.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,iEAAiE;AACjE,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC5F,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,SAAS,MAAM,IAAI,CAAC;AAE3B,MAAM,IAAI,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AACjC,MAAM,OAAO,GAAG,KAAK,CAAC;AACtB,MAAM,aAAa,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AAqBtC,MAAM,UAAU,aAAa,CAAC,OAAa;IACzC,IAAI,OAAO,CAAC,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,IAAI,GAAG,CAAC,IAAI,OAAO,CAAC,IAAI,GAAG,KAAK,EAAE,CAAC;QACzG,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;IACvD,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACpC,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,CAAC,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC;WACzG,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,QAAQ,KAAK,GAAG;WAC9E,MAAM,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,IAAI,EAAE,CAAC;QAC7C,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACtE,CAAC;IACD,mFAAmF;IACnF,OAAO,UAAU,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;AACxF,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,UAAkB,EAAE,KAAa;IAC7D,OAAO,SAAS,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,UAAU,KAAK,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;AACtG,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,GAA8C,EAAE,MAAc;IACvF,OAAO,WAAW,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,GAAG,CAAC,UAAU,MAAM,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AACtH,CAAC;AAED,+EAA+E;AAC/E,+EAA+E;AAC/E,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,OAAO,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC;AAC/C,CAAC;AAED,KAAK,UAAU,OAAO;IACpB,MAAM,GAAG,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAC/B,4EAA4E;IAC5E,IAAI,GAAG,CAAC,YAAY;QAAE,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,YAAY,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC;IAC1E,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAClC,KAAK,MAAM,IAAI,IAAI,CAAC,eAAe,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC;YAC9E,KAAK,MAAM,IAAI,IAAI,CAAC,mBAAmB,EAAE,SAAS,CAAC,EAAE,CAAC;gBACpD,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,IAAI,MAAM,EAAE,UAAU,CAAC,CAAC;gBACvD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;gBAC9C,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,6CAA6C,CAAC,CAAC;gBAC5E,IAAI,CAAC;oBACH,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC;oBACtB,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,EAAE,GAAG,GAAG,EAAE,oBAAoB,EAAE,GAAG,EAAE,EAAE,CAAC;gBAChF,CAAC;gBAAC,MAAM,CAAC,CAAC,gCAAgC,CAAC,CAAC;YAC9C,CAAC;QACH,CAAC;IACH,CAAC;IACD,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;QACzD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC/E,IAAI,CAAC;YAAC,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC;YAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,yBAAyB,CAAC,CAAC;IACvG,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,OAAO,QAAQ;IACE;IAAiC;IAAtD,YAAqB,MAAc,EAAmB,KAAa;QAA9C,WAAM,GAAN,MAAM,CAAQ;QAAmB,UAAK,GAAL,KAAK,CAAQ;IAAG,CAAC;IAEvE,KAAK,CAAC,OAAO,CAAC,KAAa,EAAE,IAAW;QACtC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,CAAC,MAAM,GAAG,KAAK,EAAE,EAAE;YACrD,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK;YAC7B,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,IAAI,CAAC,KAAK,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YACtF,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/C,QAAQ,EAAE,OAAO;YACjB,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC;SACrC,CAAC,CAAC;QACH,gFAAgF;QAChF,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,MAAM,KAAK,kBAAkB,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;QAClF,OAAO,MAAM,QAAQ,CAAC,IAAI,EAAU,CAAC;IACvC,CAAC;IAED,QAAQ,CAAC,OAAa;QACpB,OAAO,IAAI,CAAC,OAAO,CAAC,6BAA6B,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;IAC9F,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,UAAgB,EAAE;QACvC,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,KAAK,EAAE;YACxE,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,IAAI,CAAC,KAAK,EAAE,EAAE,EAAE,gBAAgB,EAAE,OAAO;SAC9E,CAAC,CAAC;QACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACpC,IAAI,CAAC;YACH,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBAC1C,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;gBAC7B,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC;YACpE,CAAC,CAAC,CAAC;YACH,OAAO,MAAM,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,CAAS,CAAC;QACnD,CAAC;gBAAS,CAAC;YAAC,MAAM,CAAC,SAAS,EAAE,CAAC;QAAC,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,QAAgB,EAAE,GAAW,EAAE,OAAe;QAC9D,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO;YAAE,OAAO;QACzC,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,KAAK,EAAE;YACxE,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,IAAI,CAAC,KAAK,EAAE,EAAE;YAClD,gBAAgB,EAAE,OAAO;YACzB,UAAU,EAAE,IAAI,GAAG,IAAI;SACxB,CAAC,CAAC;QACH,wDAAwD;QACxD,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACpC,IAAI,CAAC;YACH,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBAC1C,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;gBAClF,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC/D,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC9F,CAAC,CAAC,CAAC;YACH,MAAM,KAAK,CAAC,MAAM,EAAE,eAAe,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;YACnG,IAAI,MAAM,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,2BAA2B,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;YAClF,MAAM,QAAQ,GAAa,EAAE,CAAC;YAC9B,IAAI,CAAC;gBAAC,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC;gBACnC,MAAM,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,2BAA2B,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;gBAC9E,QAAQ,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;YAC3E,CAAC;YACD,6EAA6E;YAC7E,MAAM,KAAK,CAAC,MAAM,EAAE,gBAAgB,EAAE;gBACpC,QAAQ,EAAE,UAAU,EAAE,QAAQ;gBAC9B,IAAI,EAAE,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI;aACxF,CAAC,CAAC;QACL,CAAC;gBAAS,CAAC;YACT,MAAM,CAAC,SAAS,EAAE,CAAC;QACrB,CAAC;IACH,CAAC;CACF;AAED,MAAM,UAAU,KAAK,CAAC,MAAiB,EAAE,GAAW,EAAE,OAAa;IACjE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,EAAE,GAAG,UAAU,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;YACjC,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC7B,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC/B,CAAC,CAAC;QACF,MAAM,OAAO,GAAG,GAAG,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5E,MAAM,SAAS,GAAG,CAAC,GAAsB,EAAE,EAAE;YAC3C,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;gBAC1C,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;oBAChE,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,IAAI,OAAO,CAAC,SAAS,KAAK,EAAE;wBAAE,SAAS;oBAClE,OAAO,EAAE,CAAC;oBACV,IAAI,OAAO,CAAC,IAAI,EAAE,IAAI,KAAK,SAAS;wBAAE,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;;wBAC7D,MAAM,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;gBAC7C,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBAAC,OAAO,EAAE,CAAC;gBAAC,MAAM,CAAC,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC,CAAC;YAAC,CAAC;QACtE,CAAC,CAAC;QACF,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;QAClG,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QAChC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC9B,IAAI,CAAC;YAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;QAAC,CAAC;QACxF,MAAM,CAAC;YAAC,OAAO,EAAE,CAAC;YAAC,MAAM,CAAC,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC,CAAC;QAAC,CAAC;IAClE,CAAC,CAAC,CAAC;AACL,CAAC;AAGD,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IAC9C,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC;IACrF,KAAK,MAAM,OAAO,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC;QAC1C,IAAI,OAAa,CAAC;QAClB,IAAI,CAAC;YAAC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,qBAAqB,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QAAC,CAAC;QACtG,OAAO,KAAK,EAAE,CAAC;YACb,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;gBAAE,SAAS;YACjE,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;QACvD,CAAC;QACD,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;QACtC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,GAAG,GAAG,CAAC;YAAE,SAAS;QAChE,IAAI,CAAC;YAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC;YAAC,SAAS;QAAC,CAAC;QACzD,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,GAAG,MAAM,6BAA6B,EAAE,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;QAC/H,IAAI,CAAC,KAAK,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;QACjE,MAAM,UAAU,GAAG,MAAM,KAAK,CAAC,IAAI,EAAU,CAAC;QAC9C,IAAI,OAAO,UAAU,CAAC,aAAa,KAAK,QAAQ,IAAI,OAAO,UAAU,CAAC,aAAa,KAAK,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;QAC3J,MAAM,GAAG,GAAG,MAAM,OAAO,EAAE,CAAC;QAC5B,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;QAClF,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,KAAK,KAAK,EAAE,aAAa,EAAE,UAAU,CAAC,aAAa,EAAE,CAAC;IAChG,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAI,UAAwB,EAAE,GAAqC,EAAE,GAAG,GAAG,IAAI;IAC/G,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,UAAU,CAAC;IACtC,MAAM,QAAQ,GAAG,CAAC,YAAY,EAAE,IAAI,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,kBAAkB,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACzF,MAAM,GAAG,GAAG,KAAK,EAAE,IAAc,EAAE,EAAE;QACnC,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,GAAG,QAAQ,CAAC,EAAE;gBACrF,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,GAAG,IAAI,EAAE,WAAW,EAAE,IAAI;aACvE,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YAAC,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;QAAC,CAAC;IACjE,CAAC,CAAC;IACF,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC3F,IAAI,OAAa,CAAC;IAClB,IAAI,CAAC;QAAC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;IAAC,CAAC;IAC/F,IAAI,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,IAAI,OAAO,OAAO,CAAC,SAAS,KAAK,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;IAC/H,IAAI,CAAC;QAAC,OAAO,MAAM,GAAG,CAAC,IAAI,QAAQ,CAAC,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IAAC,CAAC;YACjE,CAAC;QAAC,MAAM,GAAG,CAAC,CAAC,QAAQ,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAAC,CAAC;AAC9E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,MAAgB,EAAE,GAAU,EAAE,MAAwE;IAC5I,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,6BAA6B,CAAC,CAAC;IACrE,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,OAAO,GAAI,QAAQ,CAAC,QAAmB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAC/G,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,SAAS,GAAG,iBAAiB,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;QACjG,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,aAAa,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;QACnJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,2DAA2D;YAC3D,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,6BAA6B,CAAC,CAAC;YACnE,IAAI,CAAE,MAAM,CAAC,QAAmB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,SAAS,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;gBAAE,MAAM,KAAK,CAAC;QAChG,CAAC;QACD,OAAO,GAAG,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC;IAC9B,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,EAAE,QAAQ,IAAI,aAAa,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;IAC3E,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IACzC,MAAM,QAAQ,GAAI,QAAQ,CAAC,OAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,QAAQ,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAC7F,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,MAAM,CAAC,QAAQ,CAAC;YACpB,IAAI,EAAE,eAAe,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,EAAE,KAAK;YAC7D,cAAc,EAAE,MAAM,EAAE,cAAc,IAAI,OAAO,CAAC,qBAAqB,IAAI,EAAE,UAAU,EAAE,GAAG,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE;YACjK,WAAW,EAAE,MAAM,EAAE,WAAW,IAAI,mBAAmB,EAAE,eAAe,EAAE,SAAS;YACnF,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACtE,CAAC,CAAC;IACL,CAAC;SAAM,CAAC;QACN,IAAI,QAAQ,CAAC,UAAU;YAAE,MAAM,MAAM,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,QAAQ,EAAE,CAAC,CAAC;QACvF,IAAI,QAAQ,CAAC,SAAS,IAAI,QAAQ,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;YACjE,MAAM,MAAM,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QAC/E,CAAC;QACD,MAAM,MAAM,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,oBAAoB,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,GAAU,EAAE,IAA+B;IAC9E,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,MAAM,UAAU,EAAE,CAAC;QACtC,IAAI,CAAC,UAAU;YAAE,OAAO,IAAI,CAAC;QAC7B,MAAM,QAAQ,GAAG,aAAa,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;QACvD,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;QAC/D,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QACzD,0EAA0E;QAC1E,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,QAAQ,IAAI,UAAU,EAAE,QAAQ,CAAC,CAAC;QAC1E,MAAM,MAAM,GAAG,CAAC,IAAY,EAAE,IAAI,GAAG,KAAK,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,IAAI,CAAC;QACrF,MAAM,OAAO,GAAG,kBAAkB,GAAG,CAAC,UAAU,UAAU,GAAG,CAAC,EAAE,cAAc,GAAG,CAAC,OAAO,YAAY,GAAG,CAAC,MAAM,MAAM,GAAG,CAAC,KAAK,+EAA+E,GAAG,CAAC,MAAM,MAAM,CAAC;QAC9N,MAAM,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAC3D,MAAM,YAAY,CAAC,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;YAC9C,MAAM,iBAAiB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YACrC,qEAAqE;YACrE,MAAM,MAAM,CAAC,WAAW,CAAC,QAAQ,EAAE,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,0DAA0D,OAAO,EAAE,CAAC,CAAC,CAAC;QAC9I,CAAC,CAAC,CAAC;QACH,IAAI,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;QAC/B,IAAI,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;QACvC,IAAI,SAAS,GAAG,KAAK,CAAC;QACtB,IAAI,QAAQ,GAAG,KAAK,CAAC;QACrB,MAAM,MAAM,GAAG,CAAC,IAAY,EAAE,IAAI,GAAG,KAAK,EAAE,EAAE;YAC5C,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC7F,CAAC,CAAC;QACF,OAAO;YACL,QAAQ,EAAE,OAAO;YACjB,GAAG,CAAC,MAAM,EAAE,IAAI;gBACd,IAAI,QAAQ,IAAI,SAAS;oBAAE,OAAO;gBAClC,KAAK,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;gBACjC,IAAI,KAAK,GAAG,aAAa,EAAE,CAAC;oBAAC,SAAS,GAAG,IAAI,CAAC;oBAAC,MAAM,CAAC,yDAAyD,CAAC,CAAC;oBAAC,OAAO;gBAAC,CAAC;gBAC3H,MAAM,CAAC,IAAI,MAAM,KAAK,IAAI,IAAI,CAAC,CAAC;YAClC,CAAC;YACD,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK;gBACjC,IAAI,QAAQ;oBAAE,OAAO;gBACrB,QAAQ,GAAG,IAAI,CAAC;gBAChB,MAAM,CAAC,gBAAgB,MAAM,KAAK,OAAO,IAAI,EAAE,KAAK,KAAK,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;gBAC3E,MAAM,MAAM,CAAC;gBACb,MAAM,YAAY,CAAC,UAAU,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,oBAAoB,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;qBAClI,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,6EAA6E,CAAC,CAAC,CAAC;YACtG,CAAC;SACF,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,wBAAwB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,0BAA0B,6BAA6B,CAAC,CAAC;QAC/H,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,154 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { createServer } from "node:http";
4
+ import { once } from "node:events";
5
+ import { appendFile, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
6
+ import os from "node:os";
7
+ import path from "node:path";
8
+ import { execFile } from "node:child_process";
9
+ import { promisify } from "node:util";
10
+ import { createRequire } from "node:module";
11
+ import WebSocket, { WebSocketServer } from "ws";
12
+ import { ensureT3JobThread, localT3Origin, shellQuote, startT3JobView, T3Client, t3JobThreadId, t3Rpc } from "./runner-t3.js";
13
+ import { safeTerminalText } from "./runner-t3-log-viewer.js";
14
+ test("discovery accepts local wildcard listeners and rejects credential-bearing or remote origins", () => {
15
+ assert.equal(localT3Origin({ version: 1, port: 3773, origin: "http://0.0.0.0:3773" }), "http://127.0.0.1:3773");
16
+ assert.equal(localT3Origin({ version: 1, port: 3773, origin: "http://[::]:3773" }), "http://[::1]:3773");
17
+ for (const origin of ["https://example.com:3773", "http://example.com:3773", "http://user:secret@localhost:3773", "http://localhost:3773/redirect", "http://localhost:3773?token=secret", "http://localhost:80"]) {
18
+ assert.throws(() => localT3Origin({ version: 1, port: 3773, origin }));
19
+ }
20
+ assert.throws(() => localT3Origin({ version: 2, port: 3773, origin: "http://localhost:3773" }));
21
+ });
22
+ test("job identity survives attempts but is scoped to the Nudge deployment", () => {
23
+ assert.equal(t3JobThreadId("a", "job"), t3JobThreadId("a", "job"));
24
+ assert.notEqual(t3JobThreadId("a", "job"), t3JobThreadId("b", "job"));
25
+ assert.match(t3JobThreadId("a", "../../job"), /^nudge-[a-f0-9]{32}$/);
26
+ });
27
+ test("POSIX arguments cannot execute task-controlled shell syntax", { skip: process.platform === "win32" }, async () => {
28
+ const input = "space ' quote `echo BAD` $(echo BAD) ; \n end";
29
+ const { stdout } = await promisify(execFile)("/bin/sh", ["-c", `printf %s ${shellQuote(input)}`]);
30
+ assert.equal(stdout, input);
31
+ });
32
+ test("missing or malformed T3 does not prevent a runner job", async (t) => {
33
+ const directory = await mkdtemp(path.join(os.tmpdir(), "nudge-t3-missing-"));
34
+ t.after(() => rm(directory, { recursive: true, force: true }));
35
+ const previous = process.env.T3CODE_HOME;
36
+ process.env.T3CODE_HOME = directory;
37
+ const warnings = [];
38
+ try {
39
+ assert.equal(await startT3JobView({}, (message) => warnings.push(message)), null);
40
+ assert.equal(warnings.length, 0);
41
+ await mkdir(path.join(directory, "userdata"));
42
+ await writeFile(path.join(directory, "userdata/server-runtime.json"), "invalid descriptor");
43
+ assert.equal(await startT3JobView({}, (message) => warnings.push(message)), null);
44
+ assert.equal(warnings.length, 1);
45
+ assert.match(warnings[0], /runner will continue/);
46
+ }
47
+ finally {
48
+ if (previous === undefined)
49
+ delete process.env.T3CODE_HOME;
50
+ else
51
+ process.env.T3CODE_HOME = previous;
52
+ }
53
+ });
54
+ test("disabled integration performs no T3 work", async () => {
55
+ const previous = process.env.NUDGE_T3;
56
+ process.env.NUDGE_T3 = "0";
57
+ try {
58
+ assert.equal(await startT3JobView({}, () => assert.fail("must stay quiet")), null);
59
+ }
60
+ finally {
61
+ if (previous === undefined)
62
+ delete process.env.NUDGE_T3;
63
+ else
64
+ process.env.NUDGE_T3 = previous;
65
+ }
66
+ });
67
+ test("public API creates one thread, then resumes it without starting another agent", async (t) => {
68
+ const cwd = await mkdtemp(path.join(os.tmpdir(), "nudge-t3-"));
69
+ t.after(() => rm(cwd, { recursive: true, force: true }));
70
+ const commands = [];
71
+ const projects = [];
72
+ const threads = [];
73
+ const server = createServer(async (req, res) => {
74
+ assert.equal(req.headers.authorization, "Bearer test-local-token");
75
+ res.setHeader("content-type", "application/json");
76
+ if (req.url === "/api/orchestration/snapshot")
77
+ return res.end(JSON.stringify({ projects, threads }));
78
+ assert.equal(req.url, "/api/orchestration/dispatch");
79
+ let body = "";
80
+ for await (const part of req)
81
+ body += part;
82
+ const command = JSON.parse(body);
83
+ commands.push(command);
84
+ if (command.type === "project.create")
85
+ projects.push({ id: command.projectId, workspaceRoot: command.workspaceRoot });
86
+ if (command.type === "thread.create")
87
+ threads.push({ id: command.threadId, projectId: command.projectId });
88
+ res.end(JSON.stringify({ sequence: commands.length }));
89
+ });
90
+ server.listen(0, "127.0.0.1");
91
+ await once(server, "listening");
92
+ t.after(() => { server.closeAllConnections(); server.close(); });
93
+ const client = new T3Client(`http://127.0.0.1:${server.address().port}`, "test-local-token");
94
+ const job = { id: "job1", deployment: "local", cwd, boardTitle: "Board", kind: "task", prompt: "Do work", engine: "codex", model: "gpt-6-astra", attempt: 1 };
95
+ const id = await ensureT3JobThread(client, job);
96
+ threads[0].archivedAt = new Date().toISOString();
97
+ threads[0].settledAt = new Date().toISOString();
98
+ assert.equal(await ensureT3JobThread(client, { ...job, attempt: 2 }), id);
99
+ assert.deepEqual(commands.map((c) => c.type), ["project.create", "thread.create", "thread.unarchive", "thread.unsettle", "thread.meta.update"]);
100
+ assert.equal(commands[1].runtimeMode, "approval-required");
101
+ assert.match(commands[4].title, /running/);
102
+ });
103
+ test("T3 errors and redirects never expose response bodies or send a token to another origin", async (t) => {
104
+ let followed = false;
105
+ const server = createServer((req, res) => {
106
+ if (req.url === "/redirect") {
107
+ res.writeHead(302, { location: "/destination" });
108
+ return res.end();
109
+ }
110
+ if (req.url === "/destination")
111
+ followed = true;
112
+ res.writeHead(500);
113
+ res.end("secret-response-body");
114
+ });
115
+ server.listen(0, "127.0.0.1");
116
+ await once(server, "listening");
117
+ t.after(() => { server.closeAllConnections(); server.close(); });
118
+ const client = new T3Client(`http://127.0.0.1:${server.address().port}`, "secret-token");
119
+ await assert.rejects(client.request("/test"), (error) => error.message.includes("HTTP 500") && !error.message.includes("secret"));
120
+ await assert.rejects(client.request("/redirect"));
121
+ assert.equal(followed, false);
122
+ });
123
+ test("terminal RPC handles batched replies and disconnected servers", async (t) => {
124
+ const server = new WebSocketServer({ port: 0, host: "127.0.0.1" });
125
+ await once(server, "listening");
126
+ t.after(() => server.close());
127
+ server.on("connection", (socket) => socket.on("message", (raw) => {
128
+ const message = JSON.parse(raw.toString());
129
+ if (message.tag === "disconnect")
130
+ return socket.close();
131
+ assert.equal(message._tag, "Request");
132
+ socket.send(JSON.stringify([{ _tag: "Pong" }, { _tag: "Exit", requestId: message.id, exit: { _tag: "Success", value: { ready: true } } }]));
133
+ }));
134
+ const socket = new WebSocket(`ws://127.0.0.1:${server.address().port}`);
135
+ socket.on("error", () => undefined);
136
+ await once(socket, "open");
137
+ t.after(() => socket.terminate());
138
+ assert.deepEqual(await t3Rpc(socket, "terminal.open", {}), { ready: true });
139
+ await assert.rejects(t3Rpc(socket, "disconnect", {}), /closed/);
140
+ });
141
+ test("log viewer handles split UTF-8 and exits on the final record", async (t) => {
142
+ const directory = await mkdtemp(path.join(os.tmpdir(), "nudge-t3-viewer-"));
143
+ t.after(() => rm(directory, { recursive: true, force: true }));
144
+ const file = path.join(directory, "log.jsonl");
145
+ const text = "İstanbul ✓ ".repeat(20_000);
146
+ await writeFile(file, `${JSON.stringify({ text, done: false })}\n`);
147
+ const result = promisify(execFile)(process.execPath, ["--import", createRequire(import.meta.url).resolve("tsx"), new URL("./runner-t3-log-viewer.ts", import.meta.url).pathname, file], { maxBuffer: 1024 * 1024, timeout: 10_000 });
148
+ await once(result.child.stdout, "data");
149
+ await appendFile(file, `${JSON.stringify({ text: "Finished", done: true })}\n`);
150
+ const { stdout } = await result;
151
+ assert.equal(stdout, text + "Finished");
152
+ assert.equal(safeTerminalText("hello\x1b]52;c;clipboard\x07\n\tworld\x9b"), "hello]52;c;clipboard\n\tworld");
153
+ });
154
+ //# sourceMappingURL=runner-t3.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner-t3.test.js","sourceRoot":"","sources":["../src/runner-t3.test.ts"],"names":[],"mappings":"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"}
package/dist/runner.js CHANGED
@@ -35,6 +35,8 @@
35
35
  // so the frontend can animate progress without a continuous database stream.
36
36
  import { spawnSync } from "node:child_process";
37
37
  import { readArtifactManifest, uploadRunnerArtifacts } from "./runner-artifacts.js";
38
+ import { startT3JobView } from "./runner-t3.js";
39
+ import { createT3Engine } from "./runner-t3-engine.js";
38
40
  import { access, mkdtemp, rm } from "node:fs/promises";
39
41
  import { constants as fsConstants } from "node:fs";
40
42
  import os from "node:os";
@@ -298,6 +300,7 @@ class LogBatcher {
298
300
  token;
299
301
  runnerId;
300
302
  jobId;
303
+ localView = null;
301
304
  buffer = [];
302
305
  stageId;
303
306
  inFlight = Promise.resolve();
@@ -311,6 +314,7 @@ class LogBatcher {
311
314
  // No timer and no size-triggered flush. Keep a bounded tail in memory and
312
315
  // persist only at finish / failure.
313
316
  push(stream, text) {
317
+ this.localView?.log(stream, text);
314
318
  if (!text)
315
319
  return;
316
320
  this.buffer.push({ stream, text, stageId: this.stageId });
@@ -698,7 +702,35 @@ export async function runNudgeRunner(argv) {
698
702
  logs.push("event", `⚠ ${credentialsIssue}`);
699
703
  if (proxyIssue)
700
704
  logs.push("event", `⚠ ${proxyIssue}`);
701
- const engine = pickEngine(detected, job.engine);
705
+ let engine = pickEngine(detected, job.engine);
706
+ const profile = job.kind === "task" ? phaseProfiles(job.engine).coding : phaseProfiles(job.engine).analysis;
707
+ const t3Job = {
708
+ id: job.id,
709
+ deployment: options.convexUrl,
710
+ cwd: options.cwd,
711
+ boardTitle: board.title,
712
+ kind: job.kind,
713
+ prompt: job.resuming && job.resumePrompt ? job.resumePrompt : job.prompt,
714
+ engine: job.engine,
715
+ model: profile.model,
716
+ attempt: job.attempt,
717
+ };
718
+ const t3Warn = (message) => {
719
+ console.warn(`[runner] ${message}`);
720
+ logs.push("event", message);
721
+ };
722
+ const native = engine ? await createT3Engine(engine, t3Job, job.resuming ? job.threadId : null, t3Warn)
723
+ .catch((error) => ({
724
+ // A stored T3 reference must never reach a CLI resume command. Report
725
+ // unavailable/mismatched T3 through the normal job failure lifecycle.
726
+ engine: { ...engine, execute: async () => { throw error; } }, view: null,
727
+ })) : null;
728
+ if (native) {
729
+ engine = native.engine;
730
+ logs.localView = native.view;
731
+ }
732
+ else
733
+ logs.localView = await startT3JobView(t3Job, t3Warn);
702
734
  console.log(`[runner] ▶ job ${job.id} on ${job.engine} (attempt ${job.attempt}/${job.maxAttempts})`);
703
735
  // Should be unreachable — the server filters on our reported engines — but
704
736
  // a runner that lost an engine since registering would otherwise loop.
@@ -708,6 +740,7 @@ export async function runNudgeRunner(argv) {
708
740
  await report(job.id, "failed", {
709
741
  error: `Runner has no usable ${job.engine} engine`,
710
742
  });
743
+ await logs.localView?.finish("failed", undefined, `Runner has no usable ${job.engine} engine`);
711
744
  active.delete(job.id);
712
745
  return;
713
746
  }
@@ -720,6 +753,7 @@ export async function runNudgeRunner(argv) {
720
753
  logs.push("stderr", error);
721
754
  await logs.flush();
722
755
  await report(job.id, "failed", { error });
756
+ await logs.localView?.finish("failed", undefined, error);
723
757
  active.delete(job.id);
724
758
  return;
725
759
  }
@@ -998,6 +1032,7 @@ Optional output files: write a JSON array to ${artifactManifest} with entries {"
998
1032
  outputModel: outputProfile.model,
999
1033
  outputEffort: outputProfile.effort,
1000
1034
  });
1035
+ await logs.localView?.finish(settled ?? status, summary, error);
1001
1036
  console.log(`[runner] ■ job ${job.id} → ${settled ?? status}${costUsd !== undefined ? ` ($${costUsd.toFixed(4)})` : ""}`);
1002
1037
  active.delete(job.id);
1003
1038
  }