From 04db010ef194407574cc47cd410fd4fc67f5ec7b Mon Sep 17 00:00:00 2001 From: Christophe Kafrouni <44137603+ckafrouni@users.noreply.github.com> Date: Mon, 28 Sep 2026 11:34:08 +0200 Subject: [PATCH 1/2] fix: code intelligence works on every runtime and sets up its own language servers (#11) * fix(desktop): TypeScript code intelligence loads the standard library electron-builder drops every *.d.ts under node_modules from app.asar, so the packaged tsserver had no lib.*.d.ts files and reported errors for built-ins like Set, Error, and string methods. macOS and Linux now ship typescript-tsserver as loose files in resources/node_modules, which Node resolution from app.asar still finds, and the build fails if the staged package has no standard library files. Co-Authored-By: Claude Opus 5.5 (1M context) * fix(server): TypeScript and Python code intelligence start on the standalone runtime The standalone t3 runtime is a Node single executable, which always runs its embedded entry and ignores a script argument. Spawning `process.execPath tsserver.js` there printed the CLI help and exited, so every TypeScript request failed with "TypeScript language service stopped", and the bundled Pyright could not start either. Bundled language servers now launch through a `t3 __run-node-script` fast path when running as a single executable, which loads the script with createRequire; Node and Electron keep running the script directly. Co-Authored-By: Claude Opus 5.5 (1M context) * feat(server): Rust and Protobuf code intelligence set themselves up Opening a Rust file failed until rust-analyzer was installed by hand, and rustup's placeholder binary only reported that the component was missing. The first Rust file in a workspace now adds rust-analyzer and rust-src to the toolchain rustup picks for it, once per workspace, and the request waits for that instead of failing. Protobuf falls back to the nearest node_modules/.bin/buf when Buf is not on PATH. Commands set in Settings are never touched. rust-analyzer also runs with build scripts and proc macros on, as in VS Code, so derives no longer resolve to nothing. Co-Authored-By: Claude Opus 5.5 (1M context) --------- Co-authored-by: Claude Opus 5.5 (1M context) --- apps/server/src/bin.ts | 6 +- apps/server/src/nodeScript.test.ts | 35 +++++++ apps/server/src/nodeScript.ts | 23 +++++ .../src/workspace/LspLanguageBackend.ts | 7 +- .../server/src/workspace/TypeScriptSession.ts | 21 ++-- .../src/workspace/WorkspaceLanguageService.ts | 27 ++++-- .../src/workspace/languageServers.test.ts | 73 +++++++++++++- apps/server/src/workspace/languageServers.ts | 96 ++++++++++++++++++- docs/user/composer.md | 7 +- scripts/build-desktop-artifact.test.ts | 47 ++++++++- scripts/build-desktop-artifact.ts | 49 ++++++++++ 11 files changed, 361 insertions(+), 30 deletions(-) create mode 100644 apps/server/src/nodeScript.test.ts create mode 100644 apps/server/src/nodeScript.ts diff --git a/apps/server/src/bin.ts b/apps/server/src/bin.ts index 063093874503..0cecf7595b72 100644 --- a/apps/server/src/bin.ts +++ b/apps/server/src/bin.ts @@ -5,7 +5,8 @@ * terminal-fallback agents run `t3 acp-mcp-call` per tool call, so their * startup sits on first-message latency. Both dispatch here before the full * CLI module graph (seconds of evaluation) loads; everything else defers to - * the real CLI in ./binCli.ts. + * the real CLI in ./binCli.ts. `t3 __run-node-script` runs bundled language + * servers under the single-executable build (see ./nodeScript.ts). */ import { isEntrypoint } from "./entrypoint.ts"; @@ -20,6 +21,9 @@ if ( if (command === "acp-mcp-bridge" || command === "acp-mcp-call") { const { runAcpMcpCliFastPath } = await import("./mcp/AcpMcpStdioBridge.ts"); await runAcpMcpCliFastPath(command, process.argv.slice(3)); + } else if (command === "__run-node-script") { + const { runNodeScript } = await import("./nodeScript.ts"); + runNodeScript(process.argv.slice(3)); } else { const { runCli } = await import("./binCli.ts"); runCli(); diff --git a/apps/server/src/nodeScript.test.ts b/apps/server/src/nodeScript.test.ts new file mode 100644 index 000000000000..b5b49b82dd00 --- /dev/null +++ b/apps/server/src/nodeScript.test.ts @@ -0,0 +1,35 @@ +// @effect-diagnostics nodeBuiltinImport:off - runs a CommonJS script from the filesystem. +import * as NodeFS from "node:fs"; +import * as NodeOS from "node:os"; +import * as NodePath from "node:path"; + +import { afterEach, describe, expect, it } from "vite-plus/test"; + +import { runNodeScript } from "./nodeScript.ts"; + +describe("runNodeScript", () => { + const originalArgv = process.argv; + afterEach(() => { + process.argv = originalArgv; + }); + + it("runs the script with the argv Node would give it", () => { + const directory = NodeFS.realpathSync( + NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-node-script-")), + ); + const script = NodePath.join(directory, "server.cjs"); + const output = NodePath.join(directory, "argv.json"); + NodeFS.writeFileSync( + script, + `require("node:fs").writeFileSync(${JSON.stringify(output)}, JSON.stringify({ argv: process.argv.slice(1), filename: __filename }));`, + ); + + runNodeScript([script, "--stdio"]); + + expect(JSON.parse(NodeFS.readFileSync(output, "utf8"))).toEqual({ + argv: [script, "--stdio"], + filename: script, + }); + NodeFS.rmSync(directory, { recursive: true, force: true }); + }); +}); diff --git a/apps/server/src/nodeScript.ts b/apps/server/src/nodeScript.ts new file mode 100644 index 000000000000..2ab8564dead7 --- /dev/null +++ b/apps/server/src/nodeScript.ts @@ -0,0 +1,23 @@ +// @effect-diagnostics nodeBuiltinImport:off +// Runs before any Effect runtime is built, so it stays on Node built-ins. +import * as NodeModule from "node:module"; +import * as NodeSea from "node:sea"; + +/** + * How to run a JavaScript file shipped with T3 (tsserver, Pyright) on T3's own runtime. + * The single-executable build always starts its embedded entry and ignores a script + * argument, so it goes through `t3 __run-node-script`, which loads the script itself. + */ +export function nodeScriptCommand(script: string, args: ReadonlyArray) { + return { + command: process.execPath, + args: NodeSea.isSea() ? ["__run-node-script", script, ...args] : [script, ...args], + }; +} + +/** Entry for `t3 __run-node-script