@euqns/nudge-mcp 1.35.0 → 1.37.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,327 @@
1
+ // Optional local T3 view. Only public HTTP commands and terminal RPCs are used:
2
+ // never write T3's database or start a second provider turn for a runner job.
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 terminalLog(threadId, cwd, logPath) {
92
+ if (process.platform === "win32")
93
+ return;
94
+ const socket = new WebSocket(`${this.origin.replace("http:", "ws:")}/ws`, {
95
+ headers: { authorization: `Bearer ${this.token}` },
96
+ handshakeTimeout: TIMEOUT,
97
+ maxPayload: 1024 * 1024,
98
+ });
99
+ // Keep an error handler installed even during teardown.
100
+ socket.on("error", () => undefined);
101
+ try {
102
+ await new Promise((resolve, reject) => {
103
+ const timer = setTimeout(() => reject(new Error("T3 socket timed out")), TIMEOUT);
104
+ socket.once("open", () => { clearTimeout(timer); resolve(); });
105
+ socket.once("error", () => { clearTimeout(timer); reject(new Error("T3 socket failed")); });
106
+ });
107
+ await t3Rpc(socket, "terminal.open", { threadId, terminalId: "term-1", cwd, cols: 120, rows: 32 });
108
+ let viewer = fileURLToPath(new URL("./runner-t3-log-viewer.js", import.meta.url));
109
+ const nodeArgs = [];
110
+ try {
111
+ await access(viewer);
112
+ }
113
+ catch {
114
+ viewer = fileURLToPath(new URL("./runner-t3-log-viewer.ts", import.meta.url));
115
+ nodeArgs.push("--import", createRequire(import.meta.url).resolve("tsx"));
116
+ }
117
+ // Never send task text to a shell. The viewer only prints a local data file.
118
+ await t3Rpc(socket, "terminal.write", {
119
+ threadId, terminalId: "term-1",
120
+ data: `${[process.execPath, ...nodeArgs, viewer, logPath].map(shellQuote).join(" ")}\r`,
121
+ });
122
+ }
123
+ finally {
124
+ socket.terminate();
125
+ }
126
+ }
127
+ }
128
+ export function t3Rpc(socket, tag, payload) {
129
+ return new Promise((resolve, reject) => {
130
+ const id = randomUUID();
131
+ const cleanup = () => {
132
+ clearTimeout(timer);
133
+ socket.off("message", onMessage);
134
+ socket.off("close", onClose);
135
+ socket.off("error", onClose);
136
+ };
137
+ const onClose = () => { cleanup(); reject(new Error("T3 socket closed")); };
138
+ const onMessage = (raw) => {
139
+ try {
140
+ const parsed = JSON.parse(raw.toString());
141
+ for (const message of Array.isArray(parsed) ? parsed : [parsed]) {
142
+ if (message._tag !== "Exit" || message.requestId !== id)
143
+ continue;
144
+ cleanup();
145
+ if (message.exit?._tag === "Success")
146
+ resolve(message.exit.value);
147
+ else
148
+ reject(new Error(`T3 ${tag} failed`));
149
+ }
150
+ }
151
+ catch {
152
+ cleanup();
153
+ reject(new Error("Invalid T3 RPC response"));
154
+ }
155
+ };
156
+ const timer = setTimeout(() => { cleanup(); reject(new Error(`T3 ${tag} timed out`)); }, TIMEOUT);
157
+ socket.on("message", onMessage);
158
+ socket.once("close", onClose);
159
+ socket.once("error", onClose);
160
+ try {
161
+ socket.send(JSON.stringify({ _tag: "Request", id, tag, payload, headers: [] }));
162
+ }
163
+ catch {
164
+ cleanup();
165
+ reject(new Error("T3 socket send failed"));
166
+ }
167
+ });
168
+ }
169
+ async function discover() {
170
+ if (process.env.NUDGE_T3 === "0")
171
+ return null;
172
+ const home = path.resolve(process.env.T3CODE_HOME ?? path.join(os.homedir(), ".t3"));
173
+ for (const variant of ["userdata", "dev"]) {
174
+ let runtime;
175
+ try {
176
+ runtime = JSON.parse(await readFile(path.join(home, variant, "server-runtime.json"), "utf8"));
177
+ }
178
+ catch (error) {
179
+ if (error.code === "ENOENT")
180
+ continue;
181
+ throw new Error("Cannot read T3 runtime descriptor");
182
+ }
183
+ const origin = localT3Origin(runtime);
184
+ if (!Number.isInteger(runtime.pid) || runtime.pid < 1)
185
+ continue;
186
+ try {
187
+ process.kill(runtime.pid, 0);
188
+ }
189
+ catch {
190
+ continue;
191
+ }
192
+ const probe = await fetch(`${origin}/.well-known/t3/environment`, { signal: AbortSignal.timeout(TIMEOUT), redirect: "error" });
193
+ if (!probe.ok)
194
+ throw new Error("Local T3 server is unavailable");
195
+ const descriptor = await probe.json();
196
+ if (typeof descriptor.environmentId !== "string" || typeof descriptor.serverVersion !== "string")
197
+ throw new Error("Unsupported T3 environment descriptor");
198
+ const cli = await findCli();
199
+ if (!cli)
200
+ throw new Error("T3 CLI not found; set NUDGE_T3_CLI to its executable");
201
+ return { origin, cli, home, dev: variant === "dev" };
202
+ }
203
+ return null;
204
+ }
205
+ async function withClient(connection, use) {
206
+ const { cli, home, dev } = connection;
207
+ const location = ["--base-dir", home, ...(dev ? ["--dev-url", "http://localhost"] : [])];
208
+ const run = async (args) => {
209
+ try {
210
+ return await exec(cli.command, [...cli.args, "auth", "session", ...args, ...location], {
211
+ env: cli.env, timeout: 10_000, maxBuffer: 64 * 1024, windowsHide: true,
212
+ });
213
+ }
214
+ catch {
215
+ throw new Error("T3 local session command failed");
216
+ }
217
+ };
218
+ const { stdout } = await run(["issue", "--ttl", "5m", "--label", "Nudge runner view", "--json"]);
219
+ let session;
220
+ try {
221
+ session = JSON.parse(stdout);
222
+ }
223
+ catch {
224
+ throw new Error("Invalid T3 session response");
225
+ }
226
+ if (typeof session.token !== "string" || typeof session.sessionId !== "string")
227
+ throw new Error("Invalid T3 session response");
228
+ try {
229
+ return await use(new T3Client(connection.origin, session.token));
230
+ }
231
+ finally {
232
+ await run(["revoke", session.sessionId]).catch(() => undefined);
233
+ }
234
+ }
235
+ export async function ensureT3JobThread(client, job) {
236
+ const snapshot = await client.request("/api/orchestration/snapshot");
237
+ const cwd = await realpath(job.cwd);
238
+ let project = snapshot.projects.find((p) => path.resolve(p.workspaceRoot) === cwd && !p.deletedAt);
239
+ if (!project) {
240
+ const projectId = `nudge-project-${createHash("sha256").update(cwd).digest("hex").slice(0, 24)}`;
241
+ try {
242
+ await client.dispatch({ type: "project.create", projectId, title: path.basename(cwd), workspaceRoot: cwd, createdAt: new Date().toISOString() });
243
+ }
244
+ catch (error) {
245
+ // Concurrent jobs may have created the same project first.
246
+ const latest = await client.request("/api/orchestration/snapshot");
247
+ if (!latest.projects.some((p) => p.id === projectId && !p.deletedAt))
248
+ throw error;
249
+ }
250
+ project = { id: projectId };
251
+ }
252
+ const threadId = t3JobThreadId(job.deployment, job.id);
253
+ const title = t3JobTitle(job, "running");
254
+ const existing = snapshot.threads.find((t) => t.id === threadId && !t.deletedAt);
255
+ if (!existing) {
256
+ await client.dispatch({
257
+ type: "thread.create", threadId, projectId: project.id, title,
258
+ modelSelection: project.defaultModelSelection ?? { instanceId: job.engine === "claude" ? "claudeAgent" : job.engine, model: job.model },
259
+ runtimeMode: "approval-required", interactionMode: "default",
260
+ branch: null, worktreePath: null, createdAt: new Date().toISOString(),
261
+ });
262
+ }
263
+ else {
264
+ if (existing.archivedAt)
265
+ await client.dispatch({ type: "thread.unarchive", threadId });
266
+ if (existing.settledAt || existing.settledOverride === "settled") {
267
+ await client.dispatch({ type: "thread.unsettle", threadId, reason: "user" });
268
+ }
269
+ await client.dispatch({ type: "thread.meta.update", threadId, title });
270
+ }
271
+ return threadId;
272
+ }
273
+ export async function startT3JobView(job, warn) {
274
+ try {
275
+ const connection = await discover();
276
+ if (!connection)
277
+ return null;
278
+ const threadId = t3JobThreadId(job.deployment, job.id);
279
+ const directory = path.join(os.homedir(), ".nudge", "t3-logs");
280
+ await mkdir(directory, { recursive: true, mode: 0o700 });
281
+ // Separate files for resumes prevent an old viewer consuming the new run.
282
+ const logPath = path.join(directory, `${threadId}-${randomUUID()}.jsonl`);
283
+ const encode = (text, done = false) => `${JSON.stringify({ text, done })}\n`;
284
+ 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`;
285
+ await writeFile(logPath, encode(heading), { mode: 0o600 });
286
+ await withClient(connection, async (client) => {
287
+ await ensureT3JobThread(client, job);
288
+ // A terminal failure must not discard a successfully created thread.
289
+ await client.terminalLog(threadId, job.cwd, logPath).catch(() => warn(`T3 thread created; its log viewer could not open. Log: ${logPath}`));
290
+ });
291
+ let queued = Promise.resolve();
292
+ let bytes = Buffer.byteLength(heading);
293
+ let truncated = false;
294
+ let finished = false;
295
+ const append = (text, done = false) => {
296
+ queued = queued.then(() => appendFile(logPath, encode(text, done))).catch(() => undefined);
297
+ };
298
+ return {
299
+ threadId, logPath,
300
+ log(stream, text) {
301
+ if (finished || truncated)
302
+ return;
303
+ bytes += Buffer.byteLength(text);
304
+ if (bytes > MAX_LOG_BYTES) {
305
+ truncated = true;
306
+ append("\n[Live log limit reached. Final result will follow.]\n");
307
+ return;
308
+ }
309
+ append(`[${stream}] ${text}\n`);
310
+ },
311
+ async finish(status, summary, error) {
312
+ if (finished)
313
+ return;
314
+ finished = true;
315
+ append(`\nNudge job: ${status}\n${summary ?? ""}\n${error ?? ""}\n`, true);
316
+ await queued;
317
+ await withClient(connection, (client) => client.dispatch({ type: "thread.meta.update", threadId, title: t3JobTitle(job, status) }))
318
+ .catch(() => warn("T3 thread status could not be updated; the job result is recorded in Nudge."));
319
+ },
320
+ };
321
+ }
322
+ catch (error) {
323
+ warn(`T3 view unavailable: ${error instanceof Error ? error.message : "local integration failed"}. The runner will continue.`);
324
+ return null;
325
+ }
326
+ }
327
+ //# sourceMappingURL=runner-t3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner-t3.js","sourceRoot":"","sources":["../src/runner-t3.ts"],"names":[],"mappings":"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"}
@@ -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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"}
@@ -14,7 +14,10 @@
14
14
  import { execFile } from "node:child_process";
15
15
  import { promisify } from "node:util";
16
16
  import { fileURLToPath } from "node:url";
17
+ import fs from "node:fs/promises";
18
+ import path from "node:path";
17
19
  import { installedGlobalPrefix } from "./install-context.js";
20
+ import { acquireInstallationLock, globalPackageDirectory, installedPackageVersion, verifyInstalledPackage, } from "./runner-installation.js";
18
21
  const execFileAsync = promisify(execFile);
19
22
  export const NUDGE_MCP_PACKAGE = "@euqns/nudge-mcp";
20
23
  /** A version string npm will accept as a spec — bare semver or a dist-tag. */
@@ -105,27 +108,69 @@ export async function performSelfUpdate(plan, deps = {}) {
105
108
  }
106
109
  const run = deps.run ?? defaultRun;
107
110
  let output = "";
111
+ let release;
112
+ let staging;
108
113
  try {
109
- const installed = await run("npm", plan.args);
114
+ const prefix = plan.prefix ?? (await run("npm", ["prefix", "--global"])).stdout.trim();
115
+ if (!path.isAbsolute(prefix))
116
+ throw new Error("Could not resolve npm's absolute global prefix.");
117
+ const packageDir = globalPackageDirectory(prefix);
118
+ release = await acquireInstallationLock(packageDir);
119
+ const previous = path.join(path.dirname(packageDir), ".nudge-mcp-previous");
120
+ // Recover the short rename window if the previous updater was killed.
121
+ if (!(await fs.stat(packageDir).catch(() => null)) && await fs.stat(previous).catch(() => null)) {
122
+ await fs.rename(previous, packageDir);
123
+ }
124
+ const target = normalizeTargetVersion(plan.spec.slice(`${NUDGE_MCP_PACKAGE}@`.length));
125
+ const current = await installedPackageVersion(packageDir).catch(() => null);
126
+ const exactTarget = /^\d+\.\d+\.\d+(?:[-+][\w.-]+)?$/.test(target);
127
+ if (current && exactTarget && (current === target || newerStableVersion(target, current))) {
128
+ try {
129
+ await verifyInstalledPackage(packageDir);
130
+ return { ok: true, installedVersion: current, output: `Already installed ${current}; reused the verified build.` };
131
+ }
132
+ catch {
133
+ // A broken install of the requested version still needs repairing.
134
+ }
135
+ }
136
+ // npm never writes into the running package. Keep staging on the same
137
+ // filesystem so activation uses renames, with the previous build retained.
138
+ staging = await fs.mkdtemp(path.join(path.dirname(packageDir), ".nudge-mcp-stage-"));
139
+ const installed = await run("npm", ["install", "-g", "--prefix", staging, plan.spec]);
110
140
  output = tailOutput(`${installed.stdout}\n${installed.stderr}`);
141
+ const candidate = globalPackageDirectory(staging);
142
+ const version = await verifyInstalledPackage(candidate);
143
+ if (exactTarget && version !== target)
144
+ throw new Error(`Expected Nudge ${target}, but npm installed ${version}.`);
145
+ if (current && newerStableVersion(version, current))
146
+ throw new Error(`Refusing to downgrade Nudge ${current} to ${version}.`);
147
+ await fs.rm(previous, { recursive: true, force: true });
148
+ const hadPrevious = !!(await fs.stat(packageDir).catch(() => null));
149
+ if (hadPrevious)
150
+ await fs.rename(packageDir, previous);
151
+ try {
152
+ await fs.rename(candidate, packageDir);
153
+ await verifyInstalledPackage(packageDir);
154
+ }
155
+ catch (error) {
156
+ await fs.rm(packageDir, { recursive: true, force: true });
157
+ if (hadPrevious)
158
+ await fs.rename(previous, packageDir);
159
+ throw error;
160
+ }
161
+ return { ok: true, installedVersion: version, output };
111
162
  }
112
163
  catch (err) {
113
164
  const e = err;
114
165
  const detail = tailOutput(`${e.stdout ?? ""}\n${e.stderr ?? ""}`) || e.message;
115
- return { ok: false, error: `npm ${plan.args.join(" ")} failed: ${detail}`, output: detail };
116
- }
117
- let installedVersion = null;
118
- try {
119
- const ls = await run("npm", ["ls", "-g", ...(plan.prefix ? ["--prefix", plan.prefix] : []), NUDGE_MCP_PACKAGE, "--json", "--depth=0"]);
120
- installedVersion = parseInstalledVersion(ls.stdout);
166
+ return { ok: false, error: `Nudge update failed: ${detail}`, output: detail };
121
167
  }
122
- catch (err) {
123
- // `npm ls` exits non-zero for peer warnings while still printing the tree;
124
- // the JSON on stdout is what matters.
125
- const e = err;
126
- installedVersion = e.stdout ? parseInstalledVersion(e.stdout) : null;
168
+ finally {
169
+ // Cleanup must not mask the install result or strand the shared lock.
170
+ if (staging)
171
+ await fs.rm(staging, { recursive: true, force: true }).catch(() => undefined);
172
+ await release?.().catch(() => undefined);
127
173
  }
128
- return { ok: true, installedVersion, output };
129
174
  }
130
175
  /** Only adopt newer stable releases; never downgrade a locally newer build. */
131
176
  export function newerStableVersion(current, candidate) {