Skip to content

Commit 7a20e91

Browse files
committed
fix(desktop): restore account shell PATH discovery
1 parent 7fcb96c commit 7a20e91

3 files changed

Lines changed: 76 additions & 31 deletions

File tree

‎apps/desktop/src/shell/DesktopShellEnvironment.test.ts‎

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,21 @@ function runShellEnvironment(input: {
100100
}
101101

102102
describe("DesktopShellEnvironment", () => {
103+
it("uses the OS account shell when a packaged GUI launch omits SHELL", () => {
104+
assert.deepEqual(
105+
DesktopShellEnvironment.resolveDesktopLoginShellCandidates(
106+
{ env: { PATH: "/usr/bin" }, platform: "darwin" },
107+
"/opt/homebrew/bin/fish",
108+
),
109+
["/opt/homebrew/bin/fish", "/bin/zsh"],
110+
);
111+
});
112+
103113
it.effect("hydrates PATH and missing SSH_AUTH_SOCK from the login shell on macOS", () =>
104114
Effect.gen(function* () {
105115
const env: NodeJS.ProcessEnv = {
106116
SHELL: "/bin/zsh",
107-
PATH: "/Users/test/.local/bin:/usr/bin",
117+
PATH: "/usr/bin",
108118
};
109119
const commands: ChildProcess.Command[] = [];
110120

@@ -114,7 +124,7 @@ describe("DesktopShellEnvironment", () => {
114124
handler: (command) => {
115125
commands.push(command);
116126
return envOutput({
117-
PATH: "/opt/homebrew/bin:/usr/bin",
127+
PATH: "/Users/test/.local/bin:/opt/homebrew/bin:/usr/bin",
118128
SSH_AUTH_SOCK: "/tmp/secretive.sock",
119129
HOMEBREW_PREFIX: "/opt/homebrew",
120130
});
@@ -123,7 +133,7 @@ describe("DesktopShellEnvironment", () => {
123133

124134
assert.equal(commands.length, 1);
125135
assert.equal(commands[0]?._tag === "StandardCommand" ? commands[0].command : "", "/bin/zsh");
126-
assert.equal(env.PATH, "/opt/homebrew/bin:/usr/bin:/Users/test/.local/bin");
136+
assert.equal(env.PATH, "/Users/test/.local/bin:/opt/homebrew/bin:/usr/bin");
127137
assert.equal(env.SSH_AUTH_SOCK, "/tmp/secretive.sock");
128138
assert.equal(env.HOMEBREW_PREFIX, "/opt/homebrew");
129139
}),
@@ -331,13 +341,13 @@ describe("DesktopShellEnvironment", () => {
331341
const errors = messages
332342
.flatMap((message) => (Array.isArray(message) ? message : [message]))
333343
.filter(isDesktopShellEnvironmentCommandError);
334-
assert.lengthOf(errors, 1);
335-
assert.equal(errors[0]?.probe, "login-shell");
336-
assert.equal(errors[0]?.executable, "bash");
337-
assert.equal(errors[0]?.argumentCount, 2);
338-
assert.notProperty(errors[0] ?? {}, "args");
339-
assert.equal(errors[0]?.cause, cause);
340-
assert.notInclude(errors[0]?.message ?? "", cause.message);
344+
const bashErrors = errors.filter((error) => error.executable === "bash");
345+
assert.lengthOf(bashErrors, 1);
346+
assert.equal(bashErrors[0]?.probe, "login-shell");
347+
assert.equal(bashErrors[0]?.argumentCount, 2);
348+
assert.notProperty(bashErrors[0] ?? {}, "args");
349+
assert.equal(bashErrors[0]?.cause, cause);
350+
assert.notInclude(bashErrors[0]?.message ?? "", cause.message);
341351
}),
342352
),
343353
Effect.provide(Logger.layer([logger], { mergeWithExisting: false })),

‎apps/desktop/src/shell/DesktopShellEnvironment.ts‎

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { listLoginShellCandidates } from "@t3tools/shared/shell";
12
import * as Context from "effect/Context";
23
import * as Duration from "effect/Duration";
34
import * as Effect from "effect/Effect";
@@ -15,7 +16,6 @@ type EnvironmentPatch = Record<string, string>;
1516
interface ShellEnvironmentConfig {
1617
readonly env: NodeJS.ProcessEnv;
1718
readonly platform: NodeJS.Platform;
18-
readonly userShell: Option.Option<string>;
1919
}
2020

2121
interface WindowsProbeOptions {
@@ -172,24 +172,10 @@ const mergePaths = (
172172
return entries.length > 0 ? Option.some(entries.join(delimiter)) : Option.none();
173173
};
174174

175-
const listLoginShellCandidates = (config: ShellEnvironmentConfig): ReadonlyArray<string> => {
176-
const fallback =
177-
config.platform === "darwin" ? "/bin/zsh" : config.platform === "linux" ? "/bin/bash" : "";
178-
const seen = new Set<string>();
179-
const candidates: string[] = [];
180-
181-
for (const candidate of [
182-
trimNonEmpty(config.env.SHELL),
183-
config.userShell,
184-
trimNonEmpty(fallback),
185-
]) {
186-
if (Option.isNone(candidate) || seen.has(candidate.value)) continue;
187-
seen.add(candidate.value);
188-
candidates.push(candidate.value);
189-
}
190-
191-
return candidates;
192-
};
175+
export const resolveDesktopLoginShellCandidates = (
176+
config: ShellEnvironmentConfig,
177+
userShell?: string,
178+
): ReadonlyArray<string> => listLoginShellCandidates(config.platform, config.env.SHELL, userShell);
193179

194180
const knownWindowsCliDirs = (env: NodeJS.ProcessEnv): ReadonlyArray<string> => [
195181
...trimNonEmpty(env.APPDATA).pipe(
@@ -421,7 +407,7 @@ const installPosixEnvironment = Effect.fn("desktop.shellEnvironment.installPosix
421407
const fileSystem = yield* FileSystem.FileSystem;
422408
const shellEnvironment: EnvironmentPatch = {};
423409

424-
for (const shell of listLoginShellCandidates(config)) {
410+
for (const shell of resolveDesktopLoginShellCandidates(config)) {
425411
Object.assign(
426412
shellEnvironment,
427413
yield* readLoginShellEnvironment(shell, LOGIN_SHELL_ENV_NAMES),
@@ -510,7 +496,6 @@ export const make = Effect.gen(function* () {
510496
installShellEnvironment({
511497
env: process.env,
512498
platform: environment.platform,
513-
userShell: Option.none(),
514499
}).pipe(
515500
Effect.provideService(FileSystem.FileSystem, fileSystem),
516501
Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),

‎apps/server/src/provider/prime/PrimeAgentBackendSelection.test.ts‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
// @effect-diagnostics nodeBuiltinImport:off
2+
import * as NodeFSP from "node:fs/promises";
3+
import * as NodeOS from "node:os";
24
import * as NodePath from "node:path";
35

46
import * as NodeServices from "@effect/platform-node/NodeServices";
57
import { expect, it } from "@effect/vitest";
68
import { ProviderInstanceId } from "@t3tools/contracts";
9+
import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
10+
import { resolveCommandPath } from "@t3tools/shared/shell";
711
import * as Effect from "effect/Effect";
812

913
import {
@@ -28,6 +32,52 @@ const baseInput: PrimeAgentBackendNegotiationInput = {
2832
};
2933

3034
it.layer(NodeServices.layer)("negotiatePrimeAgentBackend", (it) => {
35+
it.effect("selects daemon with a PATH-resolved Prime Agent executable", () =>
36+
Effect.scoped(
37+
Effect.gen(function* () {
38+
const tempDir = yield* Effect.acquireRelease(
39+
Effect.promise(() =>
40+
NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "prime-agent-backend-path-")),
41+
),
42+
(directory) =>
43+
Effect.promise(() => NodeFSP.rm(directory, { recursive: true, force: true })),
44+
);
45+
const platform = yield* HostProcessPlatform;
46+
const executableName = platform === "win32" ? "prime-agent.cmd" : "prime-agent";
47+
const executablePath = NodePath.join(tempDir, executableName);
48+
yield* Effect.promise(() => NodeFSP.writeFile(executablePath, "", "utf8"));
49+
if (platform !== "win32") {
50+
yield* Effect.promise(() => NodeFSP.chmod(executablePath, 0o755));
51+
}
52+
53+
const managerCalls: PrimeAgentDaemonManagerInput[] = [];
54+
const manager = testManager("path-resolved");
55+
const selected = yield* negotiatePrimeAgentBackend(
56+
{
57+
...baseInput,
58+
environment: {
59+
PATH: tempDir,
60+
...(platform === "win32" ? { PATHEXT: ".CMD" } : {}),
61+
},
62+
},
63+
{
64+
resolveExecutable: (command, environment) =>
65+
resolveCommandPath(command, { env: environment }),
66+
makeManager: (input) =>
67+
Effect.sync(() => {
68+
managerCalls.push(input);
69+
return manager;
70+
}),
71+
},
72+
);
73+
74+
expect(selected).toEqual({ runtime: "daemon", manager });
75+
expect(managerCalls).toHaveLength(1);
76+
expect(managerCalls[0]?.executablePath).toBe(executablePath);
77+
}),
78+
),
79+
);
80+
3181
it.effect(
3282
"resolves empty-launch-args binaries to absolute paths before one manager attempt",
3383
() =>

0 commit comments

Comments
 (0)