Skip to content

Commit 686c343

Browse files
committed
feat(ru-fork): Windows spawn hardening, {cli} session-id copy, smarter installer, MCP probe
Spawn policy - Route every process spawn through a shared policy (resolveSpawn): shell:false by default for executables (fixes project-add on Windows, upstream pingdotgg#2950), optional bash-routing via --windows-use-bash-for=<bin> / RU_FORK_WINDOWS_USE_BASH_FOR, and shell:true only when a caller explicitly asks for it. Add ProcessSpawnError and move processRunner onto Effect's ChildProcessSpawner. - RepositoryIdentityResolver: classify git health instead of swallowing failures — git missing/unspawnable and timeouts surface as real problems, "not a repo" stays quiet, and None results aren't cached so a later `git init` is picked up. - terminal/Manager: detect live subprocess activity inside a Windows PTY via powershell.exe so a session isn't torn down while work is still running. - ServerEnvironment(Label): resolve the environment label through the spawn policy and normalize git output. Copy {cli} session id - Thread right-click "Copy" now copies the underlying {cli} session id, parsed from the assistant message id (assistant:assistant:<sessionId>:...), so a dialog can be resumed in the {cli} with `--resume <id>`. Scans the thread's messages newest->oldest, falls back to latestTurn, then to the internal thread id; brand-neutral via CLI_DISPLAY_NAME. Installer / CLI resolution - install: smarter shell-profile handling. When no shell rc file exists, create the right one for the user's login shell (.bash_profile for Git Bash/bash, .zshrc for zsh) and write PATH there. Make PATH setup non-fatal — warn instead of abort when no rc is writable (the bin still runs via its full path), distinguishing "no rc at all" (create one) from "rc is read-only" (warn). - resolve.ts: fix Windows cli.js path matching. Canonicalize an MSYS/Git-Bash .install-dir record (/c/...) to native form (C:\...) and compare case-insensitively, so the recorded path matches the home-bin copy and Windows installs don't trip a false SOURCES_DISAGREE. MCP probe (new, throwaway) - mcp-probe/: self-contained harness proving the planned MCP-management engine contract (settings overlay via the {cli}'s system-settings-path env + --allowed-mcp-server-names) against the real {cli} 0.13.1 binary, before building the CQRS/projection/reactor subsystem. Ships stdio + HTTP mock servers, a monitor, and probe-close.mjs; pins @modelcontextprotocol/sdk in the workspace catalog. Tests - Add/expand processRunner, RepositoryIdentityResolver and ServerEnvironmentLabel coverage; touch OrchestrationEngine and ProjectionSnapshotQuery suites.
1 parent 7219315 commit 686c343

23 files changed

Lines changed: 2373 additions & 453 deletions

‎apps/server/src/environment/Layers/ServerEnvironment.ts‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import * as Layer from "effect/Layer";
55
import * as Path from "effect/Path";
66
import * as Random from "effect/Random";
77

8+
import * as ProcessRunner from "../../processRunner.ts";
89
import { ServerConfig } from "../../config.ts";
910
import { ServerEnvironment, type ServerEnvironmentShape } from "../Services/ServerEnvironment.ts";
1011
import packageJson from "../../../package.json" with { type: "json" };
@@ -93,4 +94,7 @@ export const makeServerEnvironment = Effect.fn("makeServerEnvironment")(function
9394
} satisfies ServerEnvironmentShape;
9495
});
9596

96-
export const ServerEnvironmentLive = Layer.effect(ServerEnvironment, makeServerEnvironment());
97+
export const ServerEnvironmentLive = Layer.effect(
98+
ServerEnvironment,
99+
makeServerEnvironment(),
100+
).pipe(Layer.provide(ProcessRunner.layer));

‎apps/server/src/environment/Layers/ServerEnvironmentLabel.ts‎

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as OS from "node:os";
44
import * as Effect from "effect/Effect";
55
import * as FileSystem from "effect/FileSystem";
66

7-
import { runProcess } from "../../processRunner.ts";
7+
import * as ProcessRunner from "../../processRunner.ts";
88

99
interface ResolveServerEnvironmentLabelInput {
1010
readonly cwdBaseName: string;
@@ -53,19 +53,16 @@ const runFriendlyLabelCommand = Effect.fn("runFriendlyLabelCommand")(function* (
5353
command: string,
5454
args: readonly string[],
5555
) {
56-
const result = yield* Effect.tryPromise({
57-
try: () =>
58-
runProcess(command, args, {
59-
allowNonZeroExit: true,
60-
}),
61-
catch: () => null,
62-
}).pipe(Effect.orElseSucceed(() => null));
63-
64-
if (!result || result.code !== 0) {
56+
const processRunner = yield* ProcessRunner.ProcessRunner;
57+
const result = yield* processRunner
58+
.run({ command, args: [...args], timeoutBehavior: "timedOutResult" })
59+
.pipe(Effect.option);
60+
61+
if (result._tag === "None" || result.value.code !== 0) {
6562
return null;
6663
}
6764

68-
return normalizeLabel(result.stdout);
65+
return normalizeLabel(result.value.stdout);
6966
});
7067

7168
const resolveFriendlyHostLabel = Effect.fn("resolveFriendlyHostLabel")(function* (

0 commit comments

Comments
 (0)