@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 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":"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,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAE3F,IAAI,CAAC,6EAA6E,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;IAC9F,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,yBAAyB,CAAC,CAAC,CAAC;IACjF,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC7D,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,gCAAgC,CAAC,CAAC;IAC9D,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,4BAA4B,CAAC,CAAC;IAC1D,KAAK,MAAM,SAAS,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC;QAAE,MAAM,EAAE,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5G,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,EAAE,mBAAmB,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC;IACvL,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,eAAe,CAAC,EAAE,6BAA6B,CAAC,CAAC;IACpF,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,iBAAiB,CAAC,EAAE,4CAA4C,CAAC,CAAC;IACrG,2EAA2E;IAC3E,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;IACnI,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,EAAE,uBAAuB,CAAC,CAAC;IACxE,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC;IACnH,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,EAAE,mCAAmC,CAAC,CAAC;IACpF,MAAM,CAAC,KAAK,CAAC,MAAM,sBAAsB,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC3D,MAAM,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC;IACxC,MAAM,MAAM,CAAC,OAAO,CAAC,sBAAsB,CAAC,IAAI,CAAC,CAAC,CAAC;AACrD,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,0DAA0D,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;IAC3E,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,kBAAkB,CAAC,CAAC,CAAC;IAC5E,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC/D,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAClD,MAAM,OAAO,GAAG,MAAM,uBAAuB,CAAC,UAAU,CAAC,CAAC;IAC1D,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACjB,MAAM,MAAM,CAAC,OAAO,CAAC,uBAAuB,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,WAAW,CAAC,CAAC;IACtG,MAAM,OAAO,EAAE,CAAC;IAChB,MAAM,CAAC,MAAM,uBAAuB,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC;AACtD,CAAC,CAAC,CAAC"}
@@ -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":"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"}
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":"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"}
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"}