import { EnvironmentId, PROVIDER_SEND_TURN_MAX_FILE_BYTES, type ExecutionEnvironmentDescriptor,} from "@t3tools/contracts";import { HostProcessArchitecture, HostProcessPlatform } from "@t3tools/shared/hostProcess";import * as Context from "effect/Context";import * as Crypto from "effect/Crypto";import * as Effect from "effect/Effect";import * as FileSystem from "effect/FileSystem";import * as Layer from "effect/Layer";import * as Path from "effect/Path";import * as Schema from "effect/Schema"; import packageJson from "../../package.json" with { type: "json" };import * as ServerSecretStore from "../auth/ServerSecretStore.ts";import { readAgentActivityPublishingActive } from "../cloud/config.ts";import { resolveServerSelfUpdateCapability } from "../cloud/selfUpdate.ts";import { resolveServiceLauncherMode } from "../cloud/serviceLauncherClient.ts";import * as ServerConfig from "../config.ts";import * as ProcessRunner from "../processRunner.ts";import { resolveServerEnvironmentLabel } from "./ServerEnvironmentLabel.ts";import { detectServerEnvironmentMachineKind } from "./ServerEnvironmentMachine.ts"; export class ServerEnvironmentIdPersistenceError extends Schema.TaggedError<ServerEnvironmentIdPersistenceError>()( "ServerEnvironmentIdPersistenceError", { operation: Schema.Literals(["check", "read", "write", "initialize"]), environmentIdPath: Schema.String, cause: Schema.optional(Schema.Defect()), },) { override get message(): string { if (this.operation === "initialize") { return `Server environment ID file is missing or empty after initialization at '${this.environmentIdPath}'.`; } return `Server environment ID ${this.operation} failed at '${this.environmentIdPath}'.`; }} export class ServerEnvironment extends Context.Service< ServerEnvironment, { readonly getEnvironmentId: Effect.Effect<EnvironmentId>; readonly getDescriptor: Effect.Effect<ExecutionEnvironmentDescriptor>; }>()("t3/environment/ServerEnvironment") {} export class ServerEnvironmentIdentity extends Context.Service< ServerEnvironmentIdentity, { readonly getEnvironmentId: Effect.Effect<EnvironmentId>; }>()("t3/environment/ServerEnvironment/ServerEnvironmentIdentity") {} function platformOs(platform: NodeJS.Platform): ExecutionEnvironmentDescriptor["platform"]["os"] { switch (platform) { case "darwin": return "darwin"; case "linux": return "linux"; case "win32": return "windows"; default: return "unknown"; }} function platformArch( architecture: NodeJS.Architecture,): ExecutionEnvironmentDescriptor["platform"]["arch"] { switch (architecture) { case "arm64": return "arm64"; case "x64": return "x64"; default: return "other"; }} const makeIdentity = Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; const serverConfig = yield* ServerConfig.ServerConfig; const crypto = yield* Crypto.Crypto; const readPersistedEnvironmentId = Effect.gen(function* () { const exists = yield* fileSystem.exists(serverConfig.environmentIdPath).pipe( Effect.mapError( (cause) => new ServerEnvironmentIdPersistenceError({ operation: "check", environmentIdPath: serverConfig.environmentIdPath, cause, }), ), ); if (!exists) { return null; } const raw = yield* fileSystem.readFileString(serverConfig.environmentIdPath).pipe( Effect.map((value) => value.trim()), Effect.mapError( (cause) => new ServerEnvironmentIdPersistenceError({ operation: "read", environmentIdPath: serverConfig.environmentIdPath, cause, }), ), ); return raw.length > 0 ? raw : null; }); const persistEnvironmentId = Effect.fn("ServerEnvironmentIdentity.persistEnvironmentId")( function* (value: string, mode: "create" | "recover") { const destinationPath = mode === "recover" ? `${serverConfig.environmentIdPath}.recovery` : serverConfig.environmentIdPath; const tempPath = yield* fileSystem.makeTempFileScoped({ directory: serverConfig.stateDir, prefix: ".environment-id-", }); yield* fileSystem.writeFileString(tempPath, `${value}\n`); // Publish the completed file without replacing an ID created by another process. yield* fileSystem .link(tempPath, destinationPath) .pipe( Effect.catch((cause) => cause.reason._tag === "AlreadyExists" ? Effect.void : Effect.fail(cause), ), ); if (mode === "recover") { // Keep the recovery ID so delayed initializers also publish the same winner. yield* fileSystem.remove(tempPath); yield* fileSystem.copyFile(destinationPath, tempPath); yield* fileSystem.rename(tempPath, serverConfig.environmentIdPath); } }, Effect.scoped, Effect.mapError( (cause) => new ServerEnvironmentIdPersistenceError({ operation: "write", environmentIdPath: serverConfig.environmentIdPath, cause, }), ), ); const environmentIdRaw = yield* Effect.gen(function* () { const persisted = yield* readPersistedEnvironmentId; if (persisted) { return persisted; } const generated = yield* crypto.randomUUIDv4; yield* persistEnvironmentId(generated, "create"); let winner = yield* readPersistedEnvironmentId; if (winner === null) { yield* persistEnvironmentId(generated, "recover"); winner = yield* readPersistedEnvironmentId; } if (winner === null) { return yield* new ServerEnvironmentIdPersistenceError({ operation: "initialize", environmentIdPath: serverConfig.environmentIdPath, }); } return winner; }); const environmentId = EnvironmentId.make(environmentIdRaw); return ServerEnvironmentIdentity.of({ getEnvironmentId: Effect.succeed(environmentId), });}); /** @public Service construction is part of the canonical Effect module API. */export const make = Effect.gen(function* () { const path = yield* Path.Path; const serverConfig = yield* ServerConfig.ServerConfig; const secrets = yield* ServerSecretStore.ServerSecretStore; const identity = yield* ServerEnvironmentIdentity; const hostPlatform = yield* HostProcessPlatform; const hostArchitecture = yield* HostProcessArchitecture; const environmentId = yield* identity.getEnvironmentId; const cwdBaseName = path.basename(serverConfig.cwd).trim(); const label = yield* resolveServerEnvironmentLabel({ cwdBaseName }); const machine = yield* detectServerEnvironmentMachineKind(); const launcher = yield* resolveServiceLauncherMode(); const serverSelfUpdate = resolveServerSelfUpdateCapability({ desktopManaged: serverConfig.mode === "desktop", launcherManaged: launcher.managed, }); // Static is correct: the control fd is known at bootstrap, and the desktop // app and its bundled server ship in one artifact, so a present fd means // the app speaks the requestDesktopUpdate protocol. WSL backends never get // the fd and correctly do not advertise. const desktopAppUpdate = serverSelfUpdate === "desktop-managed" && serverConfig.desktopTelemetryControlFd !== undefined; const descriptor: ExecutionEnvironmentDescriptor = { environmentId, label, platform: { os: platformOs(hostPlatform), arch: platformArch(hostArchitecture), ...(machine === null ? {} : { machine }), }, serverVersion: packageJson.version, capabilities: { repositoryIdentity: true, connectionProbe: true, attachmentUploads: true, questionAttachments: true, fileAttachments: { maxUploadBytes: PROVIDER_SEND_TURN_MAX_FILE_BYTES }, pullRequests: true, threadSettlement: true, threadAutoSettlement: true, threadRestartContinuation: true, threadSnooze: true, environmentThemes: true, usageLimitSources: true, usagePriceOverrides: true, threadPinning: true, threadPinReorder: true, threadActiveReorder: true, threadTitleRegeneration: true, threadPullRequests: true, pullRequestStackActions: true, threadPullRequestLinking: true, environmentIcon: true, ...(serverSelfUpdate === null ? {} : { serverSelfUpdate }), ...(serverSelfUpdate === "boot-service" || desktopAppUpdate ? { serverSelfUpdateProgress: true, serverUpdateThreadContinuation: true, } : {}), ...(desktopAppUpdate ? { desktopAppUpdate: true } : {}), }, }; return ServerEnvironment.of({ getEnvironmentId: Effect.succeed(environmentId), // The publish opt-in and relay link change at runtime (`t3 connect // publish`, the client settings toggle), so the capability is read per // descriptor request rather than baked in at startup. getDescriptor: readAgentActivityPublishingActive(secrets).pipe( Effect.map((agentActivityPublishing) => ({ ...descriptor, capabilities: { ...descriptor.capabilities, agentActivityPublishing }, })), ), });}); export const identityLayer = Layer.effect(ServerEnvironmentIdentity, makeIdentity); /** * ServerEnvironment is acquired from persisted filesystem and host-process * state. It intentionally has no fallback Layer.succeed value: callers must * provide the external platform services, a ServerConfig, and the * ServerSecretStore backing the descriptor's publishing capability. */export const layer = Layer.effect(ServerEnvironment, make).pipe( Layer.provideMerge(identityLayer), Layer.provide(ProcessRunner.layer),);
import {
EnvironmentId,
PROVIDER_SEND_TURN_MAX_FILE_BYTES,
type ExecutionEnvironmentDescriptor,
} from "@t3tools/contracts";
import { HostProcessArchitecture, HostProcessPlatform } from "@t3tools/shared/hostProcess";
import * as Context from "effect/Context";
import * as Crypto from "effect/Crypto";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Layer from "effect/Layer";
import * as Path from "effect/Path";
import * as Schema from "effect/Schema";
import packageJson from "../../package.json" with { type: "json" };
import * as ServerSecretStore from "../auth/ServerSecretStore.ts";
import { readAgentActivityPublishingActive } from "../cloud/config.ts";
import { resolveServerSelfUpdateCapability } from "../cloud/selfUpdate.ts";
import { resolveServiceLauncherMode } from "../cloud/serviceLauncherClient.ts";
import * as ServerConfig from "../config.ts";
import * as ProcessRunner from "../processRunner.ts";
import { resolveServerEnvironmentLabel } from "./ServerEnvironmentLabel.ts";
import { detectServerEnvironmentMachineKind } from "./ServerEnvironmentMachine.ts";
export class ServerEnvironmentIdPersistenceError extends Schema.TaggedError<ServerEnvironmentIdPersistenceError>()(
"ServerEnvironmentIdPersistenceError",
{
operation: Schema.Literals(["check", "read", "write", "initialize"]),
environmentIdPath: Schema.String,
cause: Schema.optional(Schema.Defect()),
},
) {
override get message(): string {
if (this.operation === "initialize") {
return `Server environment ID file is missing or empty after initialization at '${this.environmentIdPath}'.`;
}
return `Server environment ID ${this.operation} failed at '${this.environmentIdPath}'.`;
}
}
export class ServerEnvironment extends Context.Service<
ServerEnvironment,
{
readonly getEnvironmentId: Effect.Effect<EnvironmentId>;
readonly getDescriptor: Effect.Effect<ExecutionEnvironmentDescriptor>;
}
>()("t3/environment/ServerEnvironment") {}
export class ServerEnvironmentIdentity extends Context.Service<
ServerEnvironmentIdentity,
{
readonly getEnvironmentId: Effect.Effect<EnvironmentId>;
}
>()("t3/environment/ServerEnvironment/ServerEnvironmentIdentity") {}
function platformOs(platform: NodeJS.Platform): ExecutionEnvironmentDescriptor["platform"]["os"] {
switch (platform) {
case "darwin":
return "darwin";
case "linux":
return "linux";
case "win32":
return "windows";
default:
return "unknown";
}
}
function platformArch(
architecture: NodeJS.Architecture,
): ExecutionEnvironmentDescriptor["platform"]["arch"] {
switch (architecture) {
case "arm64":
return "arm64";
case "x64":
return "x64";
default:
return "other";
}
}
const makeIdentity = Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
const serverConfig = yield* ServerConfig.ServerConfig;
const crypto = yield* Crypto.Crypto;
const readPersistedEnvironmentId = Effect.gen(function* () {
const exists = yield* fileSystem.exists(serverConfig.environmentIdPath).pipe(
Effect.mapError(
(cause) =>
new ServerEnvironmentIdPersistenceError({
operation: "check",
environmentIdPath: serverConfig.environmentIdPath,
cause,
}),
),
);
if (!exists) {
return null;
}
const raw = yield* fileSystem.readFileString(serverConfig.environmentIdPath).pipe(
Effect.map((value) => value.trim()),
Effect.mapError(
(cause) =>
new ServerEnvironmentIdPersistenceError({
operation: "read",
environmentIdPath: serverConfig.environmentIdPath,
cause,
}),
),
);
return raw.length > 0 ? raw : null;
});
const persistEnvironmentId = Effect.fn("ServerEnvironmentIdentity.persistEnvironmentId")(
function* (value: string, mode: "create" | "recover") {
const destinationPath =
mode === "recover"
? `${serverConfig.environmentIdPath}.recovery`
: serverConfig.environmentIdPath;
const tempPath = yield* fileSystem.makeTempFileScoped({
directory: serverConfig.stateDir,
prefix: ".environment-id-",
});
yield* fileSystem.writeFileString(tempPath, `${value}\n`);
// Publish the completed file without replacing an ID created by another process.
yield* fileSystem
.link(tempPath, destinationPath)
.pipe(
Effect.catch((cause) =>
cause.reason._tag === "AlreadyExists" ? Effect.void : Effect.fail(cause),
),
);
if (mode === "recover") {
// Keep the recovery ID so delayed initializers also publish the same winner.
yield* fileSystem.remove(tempPath);
yield* fileSystem.copyFile(destinationPath, tempPath);
yield* fileSystem.rename(tempPath, serverConfig.environmentIdPath);
}
},
Effect.scoped,
Effect.mapError(
(cause) =>
new ServerEnvironmentIdPersistenceError({
operation: "write",
environmentIdPath: serverConfig.environmentIdPath,
cause,
}),
),
);
const environmentIdRaw = yield* Effect.gen(function* () {
const persisted = yield* readPersistedEnvironmentId;
if (persisted) {
return persisted;
}
const generated = yield* crypto.randomUUIDv4;
yield* persistEnvironmentId(generated, "create");
let winner = yield* readPersistedEnvironmentId;
if (winner === null) {
yield* persistEnvironmentId(generated, "recover");
winner = yield* readPersistedEnvironmentId;
}
if (winner === null) {
return yield* new ServerEnvironmentIdPersistenceError({
operation: "initialize",
environmentIdPath: serverConfig.environmentIdPath,
});
}
return winner;
});
const environmentId = EnvironmentId.make(environmentIdRaw);
return ServerEnvironmentIdentity.of({
getEnvironmentId: Effect.succeed(environmentId),
});
});
/** @public Service construction is part of the canonical Effect module API. */
export const make = Effect.gen(function* () {
const path = yield* Path.Path;
const serverConfig = yield* ServerConfig.ServerConfig;
const secrets = yield* ServerSecretStore.ServerSecretStore;
const identity = yield* ServerEnvironmentIdentity;
const hostPlatform = yield* HostProcessPlatform;
const hostArchitecture = yield* HostProcessArchitecture;
const environmentId = yield* identity.getEnvironmentId;
const cwdBaseName = path.basename(serverConfig.cwd).trim();
const label = yield* resolveServerEnvironmentLabel({ cwdBaseName });
const machine = yield* detectServerEnvironmentMachineKind();
const launcher = yield* resolveServiceLauncherMode();
const serverSelfUpdate = resolveServerSelfUpdateCapability({
desktopManaged: serverConfig.mode === "desktop",
launcherManaged: launcher.managed,
});
// Static is correct: the control fd is known at bootstrap, and the desktop
// app and its bundled server ship in one artifact, so a present fd means
// the app speaks the requestDesktopUpdate protocol. WSL backends never get
// the fd and correctly do not advertise.
const desktopAppUpdate =
serverSelfUpdate === "desktop-managed" && serverConfig.desktopTelemetryControlFd !== undefined;
const descriptor: ExecutionEnvironmentDescriptor = {
environmentId,
label,
platform: {
os: platformOs(hostPlatform),
arch: platformArch(hostArchitecture),
...(machine === null ? {} : { machine }),
},
serverVersion: packageJson.version,
capabilities: {
repositoryIdentity: true,
connectionProbe: true,
attachmentUploads: true,
questionAttachments: true,
fileAttachments: { maxUploadBytes: PROVIDER_SEND_TURN_MAX_FILE_BYTES },
pullRequests: true,
threadSettlement: true,
threadAutoSettlement: true,
threadRestartContinuation: true,
threadSnooze: true,
environmentThemes: true,
usageLimitSources: true,
usagePriceOverrides: true,
threadPinning: true,
threadPinReorder: true,
threadActiveReorder: true,
threadTitleRegeneration: true,
threadPullRequests: true,
pullRequestStackActions: true,
threadPullRequestLinking: true,
environmentIcon: true,
...(serverSelfUpdate === null ? {} : { serverSelfUpdate }),
...(serverSelfUpdate === "boot-service" || desktopAppUpdate
? {
serverSelfUpdateProgress: true,
serverUpdateThreadContinuation: true,
}
: {}),
...(desktopAppUpdate ? { desktopAppUpdate: true } : {}),
},
};
return ServerEnvironment.of({
getEnvironmentId: Effect.succeed(environmentId),
// The publish opt-in and relay link change at runtime (`t3 connect
// publish`, the client settings toggle), so the capability is read per
// descriptor request rather than baked in at startup.
getDescriptor: readAgentActivityPublishingActive(secrets).pipe(
Effect.map((agentActivityPublishing) => ({
...descriptor,
capabilities: { ...descriptor.capabilities, agentActivityPublishing },
})),
),
});
});
export const identityLayer = Layer.effect(ServerEnvironmentIdentity, makeIdentity);
/**
* ServerEnvironment is acquired from persisted filesystem and host-process
* state. It intentionally has no fallback Layer.succeed value: callers must
* provide the external platform services, a ServerConfig, and the
* ServerSecretStore backing the descriptor's publishing capability.
*/
export const layer = Layer.effect(ServerEnvironment, make).pipe(
Layer.provideMerge(identityLayer),
Layer.provide(ProcessRunner.layer),
);