@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.
- package/README.md +45 -0
- package/dist/runner-installation.js +173 -0
- package/dist/runner-installation.js.map +1 -0
- package/dist/runner-installation.test.js +36 -0
- package/dist/runner-installation.test.js.map +1 -0
- package/dist/runner-restart.js +43 -5
- package/dist/runner-restart.js.map +1 -1
- package/dist/runner-restart.test.js +61 -2
- package/dist/runner-restart.test.js.map +1 -1
- package/dist/runner-t3-log-viewer.js +44 -0
- package/dist/runner-t3-log-viewer.js.map +1 -0
- package/dist/runner-t3.js +327 -0
- package/dist/runner-t3.js.map +1 -0
- package/dist/runner-t3.test.js +154 -0
- package/dist/runner-t3.test.js.map +1 -0
- package/dist/runner-update.js +58 -13
- package/dist/runner-update.js.map +1 -1
- package/dist/runner-update.test.js +151 -51
- package/dist/runner-update.test.js.map +1 -1
- package/dist/runner.js +24 -3
- package/dist/runner.js.map +1 -1
- package/dist/service.js +1 -1
- package/dist/service.js.map +1 -1
- package/dist/service.test.js +6 -0
- package/dist/service.test.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -244,6 +244,15 @@ checks or installs are logged and retried at the next five-minute check. Set
|
|
|
244
244
|
`NUDGE_RUNNER_AUTO_UPDATE=0` in the runner's environment to disable release
|
|
245
245
|
checks. Local checkouts and npx installs are not automatically upgraded.
|
|
246
246
|
|
|
247
|
+
Runners sharing an npm prefix serialize updates with a process lock outside
|
|
248
|
+
the package directory. The winner installs into a staging directory and checks
|
|
249
|
+
the real CLI, its imports, package version and bundled Codex executable before
|
|
250
|
+
activation. Waiting runners reuse that verified version. Failed installs leave
|
|
251
|
+
the running package intact; failed activation restores the previous build.
|
|
252
|
+
The previous package is retained beside the installation as
|
|
253
|
+
`.nudge-mcp-previous` until the next successful activation. Restart watchers and
|
|
254
|
+
supervisors wait for a complete, unlocked installation before loading it.
|
|
255
|
+
|
|
247
256
|
A running runner picks up new builds by itself. The process supervises a child
|
|
248
257
|
that watches its own installed code; when `dist/` changes (a rebuild, or an npm
|
|
249
258
|
upgrade in place) it stops claiming jobs, lets in-flight jobs finish on the code
|
|
@@ -671,6 +680,42 @@ Overrides are read at each job startup; already-running turns retain their
|
|
|
671
680
|
existing settings. Do not repeat generated top-level model/auth keys in the
|
|
672
681
|
TOML overrides. Desktop MCP servers remain isolated unless explicitly enabled.
|
|
673
682
|
|
|
683
|
+
### T3 Code desktop runner threads
|
|
684
|
+
|
|
685
|
+
When T3 Code is running on the runner's machine, new jobs automatically create
|
|
686
|
+
a thread under the matching checkout's T3 project (creating the project if
|
|
687
|
+
needed). Look for **Nudge · running · Board · task …** in the T3 Code sidebar.
|
|
688
|
+
Open that thread's **terminal drawer** to watch the task prompt and live runner
|
|
689
|
+
output. The title changes to the final job status, including `needs human`;
|
|
690
|
+
retries and resumed jobs reuse the same thread.
|
|
691
|
+
|
|
692
|
+
This is a view of the external Nudge runner. Output appears in the terminal,
|
|
693
|
+
not as messages in T3's conversation pane. Continue to answer questions, steer,
|
|
694
|
+
or cancel the job in Nudge. The integration does not start a T3 agent turn or
|
|
695
|
+
attach T3's composer to the runner's agent session.
|
|
696
|
+
|
|
697
|
+
Discovery reads `~/.t3/userdata/server-runtime.json` (or the `dev` variant) and
|
|
698
|
+
only connects over loopback. On macOS it finds T3 Code or T3 Code (Nightly) in
|
|
699
|
+
`/Applications` or `~/Applications`; other installs need a `t3` executable on
|
|
700
|
+
the runner's `PATH`. The installed CLI issues a short-lived local session for
|
|
701
|
+
each update and revokes it afterward; credentials are not saved by Nudge.
|
|
702
|
+
The terminal log viewer supports macOS/Linux. Windows gets the thread and
|
|
703
|
+
status updates, with its log saved locally.
|
|
704
|
+
|
|
705
|
+
| Runner environment variable | Purpose |
|
|
706
|
+
| --- | --- |
|
|
707
|
+
| `NUDGE_T3=0` | Disable automatic T3 threads. |
|
|
708
|
+
| `T3CODE_HOME=/path/to/.t3` | Find a nonstandard T3 data directory. |
|
|
709
|
+
| `NUDGE_T3_CLI=/path/to/t3` | Use a specific installed T3 CLI or executable wrapper. |
|
|
710
|
+
|
|
711
|
+
T3 must be open before the job starts; jobs already in progress are not
|
|
712
|
+
backfilled. Missing T3 is silently skipped. An incompatible or unreachable T3
|
|
713
|
+
reports a warning and the runner continues. Verified against T3 Code Nightly
|
|
714
|
+
`0.0.41-nightly.20260908.1400`; this requires its HTTP orchestration and terminal
|
|
715
|
+
RPC interfaces. Local JSONL logs are owner-only under `~/.nudge/t3-logs` and
|
|
716
|
+
retain up to 8 MiB of live output per run, followed by its final result.
|
|
717
|
+
They remain available after completion and can be removed when no longer needed.
|
|
718
|
+
|
|
674
719
|
### Runner output files
|
|
675
720
|
|
|
676
721
|
A coding turn can optionally write the JSON manifest at the absolute path supplied
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// Shared by updaters and supervisors. All coordination files live OUTSIDE the
|
|
2
|
+
// package npm replaces, and a restart requires an executable, complete build.
|
|
3
|
+
import { execFile } from "node:child_process";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import fs from "node:fs/promises";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
8
|
+
import { promisify } from "node:util";
|
|
9
|
+
const execFileAsync = promisify(execFile);
|
|
10
|
+
export function globalPackageDirectory(prefix) {
|
|
11
|
+
return path.join(prefix, ...(process.platform === "win32" ? [] : ["lib"]), "node_modules", "@euqns", "nudge-mcp");
|
|
12
|
+
}
|
|
13
|
+
export function installationLockDirectory(packageDir) {
|
|
14
|
+
return path.join(path.dirname(packageDir), ".nudge-mcp-update.lock");
|
|
15
|
+
}
|
|
16
|
+
const codeOf = (error) => error.code;
|
|
17
|
+
async function removeEmptyLock(directory) {
|
|
18
|
+
try {
|
|
19
|
+
await fs.rmdir(directory);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
if (!["ENOENT", "ENOTEMPTY", "EEXIST"].includes(codeOf(error) ?? ""))
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// Unlink only the dead owner's uniquely named file, then rmdir (never a
|
|
27
|
+
// recursive rm). Two stale-lock recoverers cannot delete a new owner's lock.
|
|
28
|
+
async function reclaimAbandonedLock(directory) {
|
|
29
|
+
let names;
|
|
30
|
+
try {
|
|
31
|
+
names = await fs.readdir(directory);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
if (codeOf(error) === "ENOENT")
|
|
35
|
+
return;
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
if (names.length === 0) {
|
|
39
|
+
const stat = await fs.stat(directory).catch(() => null);
|
|
40
|
+
// Allow the mkdir winner time to publish its owner, including on slow disks.
|
|
41
|
+
if (stat && Date.now() - stat.mtimeMs > 30_000)
|
|
42
|
+
await removeEmptyLock(directory);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
for (const name of names) {
|
|
46
|
+
const pid = Number(/^(\d+)-[\da-f-]+\.lock$/.exec(name)?.[1]);
|
|
47
|
+
if (!Number.isSafeInteger(pid) || pid <= 0)
|
|
48
|
+
continue;
|
|
49
|
+
try {
|
|
50
|
+
process.kill(pid, 0);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
if (codeOf(error) !== "ESRCH")
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
await fs.unlink(path.join(directory, name));
|
|
59
|
+
await removeEmptyLock(directory);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (codeOf(error) !== "ENOENT")
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export async function installationUpdateInProgress(packageDir) {
|
|
68
|
+
const directory = installationLockDirectory(packageDir);
|
|
69
|
+
await reclaimAbandonedLock(directory);
|
|
70
|
+
try {
|
|
71
|
+
await fs.access(directory);
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (codeOf(error) === "ENOENT")
|
|
76
|
+
return false;
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export async function acquireInstallationLock(packageDir, options = {}) {
|
|
81
|
+
// Canonicalize the parent so alternate symlinks to one prefix share a lock.
|
|
82
|
+
await fs.mkdir(path.dirname(packageDir), { recursive: true });
|
|
83
|
+
const parent = await fs.realpath(path.dirname(packageDir));
|
|
84
|
+
const directory = installationLockDirectory(path.join(parent, path.basename(packageDir)));
|
|
85
|
+
const owner = path.join(directory, `${process.pid}-${randomUUID()}.lock`);
|
|
86
|
+
const deadline = Date.now() + (options.timeoutMs ?? 12 * 60_000);
|
|
87
|
+
for (;;) {
|
|
88
|
+
try {
|
|
89
|
+
await fs.mkdir(directory, { mode: 0o700 });
|
|
90
|
+
try {
|
|
91
|
+
await fs.writeFile(owner, "", { flag: "wx", mode: 0o600 });
|
|
92
|
+
// A process can be suspended between mkdir and write. If its empty
|
|
93
|
+
// directory was reclaimed, only one published owner may proceed.
|
|
94
|
+
const owners = await fs.readdir(directory);
|
|
95
|
+
if (owners.length !== 1 || owners[0] !== path.basename(owner)) {
|
|
96
|
+
await fs.unlink(owner).catch(() => undefined);
|
|
97
|
+
throw Object.assign(new Error("Update lock ownership changed"), { code: "EEXIST" });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
await removeEmptyLock(directory);
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
return async () => {
|
|
105
|
+
try {
|
|
106
|
+
await fs.unlink(owner);
|
|
107
|
+
await removeEmptyLock(directory);
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
if (codeOf(error) !== "ENOENT")
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
if (codeOf(error) !== "EEXIST")
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
await reclaimAbandonedLock(directory);
|
|
120
|
+
if (Date.now() >= deadline)
|
|
121
|
+
throw new Error("Timed out waiting for another Nudge package update to finish.");
|
|
122
|
+
await sleep(options.retryMs ?? 250);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
export async function installedPackageVersion(packageDir) {
|
|
126
|
+
const pkg = JSON.parse(await fs.readFile(path.join(packageDir, "package.json"), "utf8"));
|
|
127
|
+
if (pkg.name !== "@euqns/nudge-mcp" || typeof pkg.version !== "string" || !pkg.version) {
|
|
128
|
+
throw new Error("The installed Nudge package has no valid version.");
|
|
129
|
+
}
|
|
130
|
+
return pkg.version;
|
|
131
|
+
}
|
|
132
|
+
// A fresh process catches missing transitive imports that the current runner
|
|
133
|
+
// has already cached. --help loads the real CLI without connecting to Convex or
|
|
134
|
+
// starting jobs. Constructing the SDK also verifies its bundled native binary.
|
|
135
|
+
const VERIFY_SCRIPT = `
|
|
136
|
+
import fs from 'node:fs';
|
|
137
|
+
import path from 'node:path';
|
|
138
|
+
import { createRequire } from 'node:module';
|
|
139
|
+
import { pathToFileURL } from 'node:url';
|
|
140
|
+
import { execFileSync } from 'node:child_process';
|
|
141
|
+
const root = process.argv[1];
|
|
142
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
|
|
143
|
+
const { NUDGE_MCP_VERSION } = await import(pathToFileURL(path.join(root, 'dist/version.js')).href);
|
|
144
|
+
if (NUDGE_MCP_VERSION !== pkg.version) throw new Error('Package and executable versions disagree');
|
|
145
|
+
execFileSync(process.execPath, [path.join(root, 'dist/index.js'), '--help'], { timeout: 15000, stdio: 'pipe' });
|
|
146
|
+
if (pkg.dependencies?.['@openai/codex-sdk']) {
|
|
147
|
+
const require = createRequire(path.join(root, 'package.json'));
|
|
148
|
+
const { Codex } = await import('@openai/codex-sdk');
|
|
149
|
+
new Codex();
|
|
150
|
+
const cliPackage = require.resolve('@openai/codex/package.json');
|
|
151
|
+
const cli = JSON.parse(fs.readFileSync(cliPackage, 'utf8'));
|
|
152
|
+
execFileSync(process.execPath, [path.join(path.dirname(cliPackage), cli.bin.codex), '--version'], { timeout: 15000, stdio: 'pipe' });
|
|
153
|
+
}
|
|
154
|
+
`;
|
|
155
|
+
export async function verifyInstalledPackage(packageDir) {
|
|
156
|
+
const version = await installedPackageVersion(packageDir);
|
|
157
|
+
await execFileAsync(process.execPath, ["--input-type=module", "-e", VERIFY_SCRIPT, packageDir], {
|
|
158
|
+
cwd: packageDir, encoding: "utf8", timeout: 35_000, maxBuffer: 256 * 1024,
|
|
159
|
+
});
|
|
160
|
+
return version;
|
|
161
|
+
}
|
|
162
|
+
export async function canRestartInstalledPackage(packageDir) {
|
|
163
|
+
try {
|
|
164
|
+
if (await installationUpdateInProgress(packageDir))
|
|
165
|
+
return false;
|
|
166
|
+
await verifyInstalledPackage(packageDir);
|
|
167
|
+
return !(await installationUpdateInProgress(packageDir));
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=runner-installation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner-installation.js","sourceRoot":"","sources":["../src/runner-installation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { acquireInstallationLock, verifyInstalledPackage } from "./runner-installation.js";
|
|
7
|
+
test("verification uses the installed SDK's ESM import and checks its bundled CLI", async (t) => {
|
|
8
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), "nudge-sdk-verification-"));
|
|
9
|
+
t.after(() => fs.rm(root, { recursive: true, force: true }));
|
|
10
|
+
const sdk = path.join(root, "node_modules/@openai/codex-sdk");
|
|
11
|
+
const cli = path.join(root, "node_modules/@openai/codex");
|
|
12
|
+
for (const directory of [path.join(root, "dist"), sdk, cli])
|
|
13
|
+
await fs.mkdir(directory, { recursive: true });
|
|
14
|
+
await fs.writeFile(path.join(root, "package.json"), JSON.stringify({ name: "@euqns/nudge-mcp", version: "1.35.1", type: "module", dependencies: { "@openai/codex-sdk": "0.153.4" } }));
|
|
15
|
+
await fs.writeFile(path.join(root, "dist/index.js"), "import '@openai/codex-sdk';");
|
|
16
|
+
await fs.writeFile(path.join(root, "dist/version.js"), 'export const NUDGE_MCP_VERSION = "1.35.1";');
|
|
17
|
+
// The real SDK exposes only an import condition, not require/package.json.
|
|
18
|
+
await fs.writeFile(path.join(sdk, "package.json"), JSON.stringify({ type: "module", exports: { ".": { import: "./index.js" } } }));
|
|
19
|
+
await fs.writeFile(path.join(sdk, "index.js"), "export class Codex {}");
|
|
20
|
+
await fs.writeFile(path.join(cli, "package.json"), JSON.stringify({ type: "module", bin: { codex: "codex.js" } }));
|
|
21
|
+
await fs.writeFile(path.join(cli, "codex.js"), "console.log('codex-cli 0.153.4');");
|
|
22
|
+
assert.equal(await verifyInstalledPackage(root), "1.35.1");
|
|
23
|
+
await fs.rm(path.join(cli, "codex.js"));
|
|
24
|
+
await assert.rejects(verifyInstalledPackage(root));
|
|
25
|
+
});
|
|
26
|
+
test("a live updater's lock cannot be stolen by another caller", async (t) => {
|
|
27
|
+
const parent = await fs.mkdtemp(path.join(os.tmpdir(), "nudge-live-lock-"));
|
|
28
|
+
t.after(() => fs.rm(parent, { recursive: true, force: true }));
|
|
29
|
+
const packageDir = path.join(parent, "nudge-mcp");
|
|
30
|
+
const release = await acquireInstallationLock(packageDir);
|
|
31
|
+
t.after(release);
|
|
32
|
+
await assert.rejects(acquireInstallationLock(packageDir, { timeoutMs: 30, retryMs: 5 }), /Timed out/);
|
|
33
|
+
await release();
|
|
34
|
+
await (await acquireInstallationLock(packageDir))();
|
|
35
|
+
});
|
|
36
|
+
//# sourceMappingURL=runner-installation.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner-installation.test.js","sourceRoot":"","sources":["../src/runner-installation.test.ts"],"names":[],"mappings":"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"}
|
package/dist/runner-restart.js
CHANGED
|
@@ -20,6 +20,9 @@ import { spawn } from "node:child_process";
|
|
|
20
20
|
import fs from "node:fs";
|
|
21
21
|
import path from "node:path";
|
|
22
22
|
import { fileURLToPath } from "node:url";
|
|
23
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
24
|
+
import { canRestartInstalledPackage } from "./runner-installation.js";
|
|
25
|
+
import { installedGlobalPrefix } from "./install-context.js";
|
|
23
26
|
/** BSD EX_TEMPFAIL — the child's "respawn me" signal, distinct from crash (1)
|
|
24
27
|
* and clean stop (0). */
|
|
25
28
|
export const RESTART_EXIT_CODE = 75;
|
|
@@ -44,17 +47,34 @@ export function isCodeFile(name) {
|
|
|
44
47
|
export function watchRunnerBuild(onChange, opts) {
|
|
45
48
|
const dir = opts?.dir ?? path.dirname(fileURLToPath(import.meta.url));
|
|
46
49
|
const quietMs = opts?.quietMs ?? 3_000;
|
|
50
|
+
const packageDir = path.dirname(dir);
|
|
51
|
+
const ready = opts?.ready ?? (() => canRestartInstalledPackage(packageDir));
|
|
47
52
|
let timer = null;
|
|
48
53
|
let fired = false;
|
|
54
|
+
let closed = false;
|
|
55
|
+
let generation = 0;
|
|
56
|
+
const check = async () => {
|
|
57
|
+
const observed = generation;
|
|
58
|
+
const healthy = await ready().catch(() => false);
|
|
59
|
+
if (closed || fired || observed !== generation)
|
|
60
|
+
return;
|
|
61
|
+
if (!healthy) {
|
|
62
|
+
// A failed npm install or another process still installing must never
|
|
63
|
+
// force this healthy, already-loaded process into a restart loop.
|
|
64
|
+
timer = setTimeout(() => void check(), quietMs);
|
|
65
|
+
timer.unref();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
fired = true;
|
|
69
|
+
onChange();
|
|
70
|
+
};
|
|
49
71
|
const trigger = (file) => {
|
|
50
|
-
if (fired || (file !== null && !isCodeFile(path.basename(file))))
|
|
72
|
+
if (closed || fired || (file !== null && !isCodeFile(path.basename(file))))
|
|
51
73
|
return;
|
|
74
|
+
generation++;
|
|
52
75
|
if (timer)
|
|
53
76
|
clearTimeout(timer);
|
|
54
|
-
timer = setTimeout(() =>
|
|
55
|
-
fired = true;
|
|
56
|
-
onChange();
|
|
57
|
-
}, quietMs);
|
|
77
|
+
timer = setTimeout(() => void check(), quietMs);
|
|
58
78
|
timer.unref();
|
|
59
79
|
};
|
|
60
80
|
let watcher;
|
|
@@ -66,10 +86,18 @@ export function watchRunnerBuild(onChange, opts) {
|
|
|
66
86
|
// directory still catches index.js/runner.js, which every build touches.
|
|
67
87
|
watcher = fs.watch(dir, (_event, file) => trigger(file));
|
|
68
88
|
}
|
|
89
|
+
// npm/our updater can replace the entire package directory. The dist watch
|
|
90
|
+
// then follows the old inode, so also observe replacement of its parent.
|
|
91
|
+
const parentWatcher = fs.watch(path.dirname(packageDir), (_event, file) => {
|
|
92
|
+
if (file === null || file.toString() === path.basename(packageDir))
|
|
93
|
+
trigger(null);
|
|
94
|
+
});
|
|
69
95
|
return () => {
|
|
96
|
+
closed = true;
|
|
70
97
|
if (timer)
|
|
71
98
|
clearTimeout(timer);
|
|
72
99
|
watcher.close();
|
|
100
|
+
parentWatcher.close();
|
|
73
101
|
};
|
|
74
102
|
}
|
|
75
103
|
/**
|
|
@@ -112,6 +140,16 @@ export async function supervise(subcommand, argv, extraEnv) {
|
|
|
112
140
|
process.off("SIGINT", onInt);
|
|
113
141
|
process.off("SIGTERM", onTerm);
|
|
114
142
|
if (code === RESTART_EXIT_CODE && !shuttingDown) {
|
|
143
|
+
if (installedGlobalPrefix(fileURLToPath(import.meta.url))) {
|
|
144
|
+
const packageDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
145
|
+
let waiting = false;
|
|
146
|
+
while (!(await canRestartInstalledPackage(packageDir))) {
|
|
147
|
+
if (!waiting)
|
|
148
|
+
console.log(`[${subcommand}] waiting for a complete package installation before restarting…`);
|
|
149
|
+
waiting = true;
|
|
150
|
+
await sleep(1_000);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
115
153
|
console.log(`[${subcommand}] ⟳ restarting with the updated build…`);
|
|
116
154
|
respawns += 1;
|
|
117
155
|
continue;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner-restart.js","sourceRoot":"","sources":["../src/runner-restart.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,EAAE;AACF,4EAA4E;AAC5E,4EAA4E;AAC5E,8EAA8E;AAC9E,6EAA6E;AAC7E,2EAA2E;AAC3E,EAAE;AACF,4DAA4D;AAC5D,4DAA4D;AAC5D,iEAAiE;AACjE,qEAAqE;AACrE,oEAAoE;AACpE,8DAA8D;AAC9D,EAAE;AACF,+EAA+E;AAC/E,2EAA2E;AAC3E,sEAAsE;AAEtE,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"runner-restart.js","sourceRoot":"","sources":["../src/runner-restart.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,6 +4,7 @@ import fs from "node:fs/promises";
|
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
7
|
+
import { acquireInstallationLock } from "./runner-installation.js";
|
|
7
8
|
import { RESTART_EXIT_CODE, isCodeFile, watchRunnerBuild, } from "./runner-restart.js";
|
|
8
9
|
test("restart exit code is distinct from crash and clean exit", () => {
|
|
9
10
|
assert.notEqual(RESTART_EXIT_CODE, 0);
|
|
@@ -22,7 +23,7 @@ test("only real code files count as an update", () => {
|
|
|
22
23
|
test("a burst of build writes fires onChange once, after the tree goes quiet", async () => {
|
|
23
24
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "nudge-restart-"));
|
|
24
25
|
let fired = 0;
|
|
25
|
-
const unwatch = watchRunnerBuild(() => fired++, { dir, quietMs: 60 });
|
|
26
|
+
const unwatch = watchRunnerBuild(() => fired++, { dir, quietMs: 60, ready: async () => true });
|
|
26
27
|
try {
|
|
27
28
|
await fs.writeFile(path.join(dir, "a.js"), "1");
|
|
28
29
|
await sleep(20);
|
|
@@ -44,7 +45,7 @@ test("a burst of build writes fires onChange once, after the tree goes quiet", a
|
|
|
44
45
|
test("non-code churn never fires", async () => {
|
|
45
46
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "nudge-restart-"));
|
|
46
47
|
let fired = 0;
|
|
47
|
-
const unwatch = watchRunnerBuild(() => fired++, { dir, quietMs: 40 });
|
|
48
|
+
const unwatch = watchRunnerBuild(() => fired++, { dir, quietMs: 40, ready: async () => true });
|
|
48
49
|
try {
|
|
49
50
|
await fs.writeFile(path.join(dir, "runner.js.map"), "{}");
|
|
50
51
|
await fs.writeFile(path.join(dir, "types.d.ts"), "");
|
|
@@ -56,4 +57,62 @@ test("non-code churn never fires", async () => {
|
|
|
56
57
|
await fs.rm(dir, { recursive: true, force: true });
|
|
57
58
|
}
|
|
58
59
|
});
|
|
60
|
+
async function buildFixture(root) {
|
|
61
|
+
await fs.mkdir(path.join(root, "dist"), { recursive: true });
|
|
62
|
+
await fs.writeFile(path.join(root, "package.json"), JSON.stringify({ name: "@euqns/nudge-mcp", version: "1.35.0", type: "module" }));
|
|
63
|
+
await fs.writeFile(path.join(root, "dist/version.js"), 'export const NUDGE_MCP_VERSION = "1.35.0";');
|
|
64
|
+
await fs.writeFile(path.join(root, "dist/index.js"), "import './runtime.js';");
|
|
65
|
+
await fs.writeFile(path.join(root, "dist/runtime.js"), "");
|
|
66
|
+
}
|
|
67
|
+
async function until(check) {
|
|
68
|
+
const deadline = Date.now() + 5_000;
|
|
69
|
+
while (!check() && Date.now() < deadline)
|
|
70
|
+
await sleep(20);
|
|
71
|
+
assert.ok(check(), "expected the verified build to become restartable");
|
|
72
|
+
}
|
|
73
|
+
test("a sibling updater can replace the package without restarting consumers until it releases the lock", async (t) => {
|
|
74
|
+
const parent = await fs.mkdtemp(path.join(os.tmpdir(), "nudge-replacement-"));
|
|
75
|
+
t.after(() => fs.rm(parent, { recursive: true, force: true }));
|
|
76
|
+
const root = path.join(parent, "nudge-mcp");
|
|
77
|
+
await buildFixture(root);
|
|
78
|
+
let fired = 0;
|
|
79
|
+
const unwatch = watchRunnerBuild(() => fired++, { dir: path.join(root, "dist"), quietMs: 30 });
|
|
80
|
+
t.after(unwatch);
|
|
81
|
+
const release = await acquireInstallationLock(root);
|
|
82
|
+
t.after(release);
|
|
83
|
+
await fs.rename(root, path.join(parent, "previous"));
|
|
84
|
+
await buildFixture(root);
|
|
85
|
+
await sleep(250);
|
|
86
|
+
assert.equal(fired, 0);
|
|
87
|
+
await release();
|
|
88
|
+
await until(() => fired === 1);
|
|
89
|
+
});
|
|
90
|
+
test("a partial install keeps the loaded process running and retries when dependencies return", async (t) => {
|
|
91
|
+
const parent = await fs.mkdtemp(path.join(os.tmpdir(), "nudge-partial-"));
|
|
92
|
+
t.after(() => fs.rm(parent, { recursive: true, force: true }));
|
|
93
|
+
const root = path.join(parent, "nudge-mcp");
|
|
94
|
+
await buildFixture(root);
|
|
95
|
+
let fired = 0;
|
|
96
|
+
const unwatch = watchRunnerBuild(() => fired++, { dir: path.join(root, "dist"), quietMs: 30 });
|
|
97
|
+
t.after(unwatch);
|
|
98
|
+
await fs.rm(path.join(root, "dist/runtime.js"));
|
|
99
|
+
await sleep(250);
|
|
100
|
+
assert.equal(fired, 0);
|
|
101
|
+
await fs.writeFile(path.join(root, "dist/runtime.js"), "");
|
|
102
|
+
await until(() => fired === 1);
|
|
103
|
+
});
|
|
104
|
+
test("closing a watcher cancels an in-flight readiness check", async (t) => {
|
|
105
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "nudge-watcher-close-"));
|
|
106
|
+
t.after(() => fs.rm(dir, { recursive: true, force: true }));
|
|
107
|
+
let fired = 0, checking = false;
|
|
108
|
+
let complete;
|
|
109
|
+
const readiness = new Promise((resolve) => { complete = resolve; });
|
|
110
|
+
const unwatch = watchRunnerBuild(() => fired++, { dir, quietMs: 10, ready: () => { checking = true; return readiness; } });
|
|
111
|
+
await fs.writeFile(path.join(dir, "index.js"), "");
|
|
112
|
+
await until(() => checking);
|
|
113
|
+
unwatch();
|
|
114
|
+
complete(true);
|
|
115
|
+
await sleep(30);
|
|
116
|
+
assert.equal(fired, 0);
|
|
117
|
+
});
|
|
59
118
|
//# sourceMappingURL=runner-restart.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner-restart.test.js","sourceRoot":"","sources":["../src/runner-restart.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,UAAU,IAAI,KAAK,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"runner-restart.test.js","sourceRoot":"","sources":["../src/runner-restart.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Invoked in a T3 terminal. The JSONL file is data, never shell input.
|
|
2
|
+
import { open, stat } from "node:fs/promises";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
export function safeTerminalText(text) {
|
|
5
|
+
// Strip controls including ESC, OSC and C1 so agent output cannot issue
|
|
6
|
+
// terminal/clipboard commands. Preserve readable tabs and line breaks.
|
|
7
|
+
return text.replace(/[\x00-\x08\x0b-\x1f\x7f-\x9f]/g, "");
|
|
8
|
+
}
|
|
9
|
+
export async function viewRunnerLog(file) {
|
|
10
|
+
let offset = 0;
|
|
11
|
+
let pending = "";
|
|
12
|
+
const decoder = new TextDecoder();
|
|
13
|
+
for (;;) {
|
|
14
|
+
const handle = await open(file, "r");
|
|
15
|
+
let bytesRead;
|
|
16
|
+
try {
|
|
17
|
+
const buffer = Buffer.alloc(64 * 1024);
|
|
18
|
+
({ bytesRead } = await handle.read(buffer, 0, buffer.length, offset));
|
|
19
|
+
offset += bytesRead;
|
|
20
|
+
pending += decoder.decode(buffer.subarray(0, bytesRead), { stream: true });
|
|
21
|
+
}
|
|
22
|
+
finally {
|
|
23
|
+
await handle.close();
|
|
24
|
+
}
|
|
25
|
+
let end;
|
|
26
|
+
while ((end = pending.indexOf("\n")) >= 0) {
|
|
27
|
+
const entry = JSON.parse(pending.slice(0, end));
|
|
28
|
+
pending = pending.slice(end + 1);
|
|
29
|
+
process.stdout.write(safeTerminalText(String(entry.text ?? "")));
|
|
30
|
+
if (entry.done)
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (!bytesRead) {
|
|
34
|
+
// A crashed runner must not leave a viewer alive indefinitely.
|
|
35
|
+
if (Date.now() - (await stat(file)).mtimeMs > 24 * 60 * 60 * 1000)
|
|
36
|
+
return;
|
|
37
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (process.argv[1] === fileURLToPath(import.meta.url) && process.argv[2]) {
|
|
42
|
+
viewRunnerLog(process.argv[2]).catch(() => { console.error("Nudge runner log is unavailable."); process.exitCode = 1; });
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=runner-t3-log-viewer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner-t3-log-viewer.js","sourceRoot":"","sources":["../src/runner-t3-log-viewer.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,wEAAwE;IACxE,uEAAuE;IACvE,OAAO,IAAI,CAAC,OAAO,CAAC,gCAAgC,EAAE,EAAE,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAY;IAC9C,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;IAClC,SAAS,CAAC;QACR,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QACrC,IAAI,SAAiB,CAAC;QACtB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC;YACvC,CAAC,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;YACtE,MAAM,IAAI,SAAS,CAAC;YACpB,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7E,CAAC;gBAAS,CAAC;YAAC,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QAAC,CAAC;QACnC,IAAI,GAAW,CAAC;QAChB,OAAO,CAAC,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;YAChD,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;YACjC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;YACjE,IAAI,KAAK,CAAC,IAAI;gBAAE,OAAO;QACzB,CAAC;QACD,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,+DAA+D;YAC/D,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI;gBAAE,OAAO;YAC1E,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC;AACH,CAAC;AAED,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1E,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC3H,CAAC"}
|