authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-26 16:53:39-08:00
committergravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-26 16:53:39-08:00
log8f6b21a12e8c6509649883da8e36d375e5aa1fdf
treecb25078bb068c6f579039c82c7fdabf6ef180c0b
parentdc530f7a7e21dac998126024b2b112fc893df404

fix dmg build, enable some lsp diagnostics


71 files changed, 353 insertions(+), 148 deletions(-)

apps/server/integration/OrchestrationEngineHarness.integration.ts+1-1
...@@ -3,7 +3,7 @@ import os from "node:os";...@@ -3,7 +3,7 @@ import os from "node:os";
3import path from "node:path";3import path from "node:path";
4import { execFileSync } from "node:child_process";4import { execFileSync } from "node:child_process";
55
6import { NodeServices } from "@effect/platform-node";6import * as NodeServices from "@effect/platform-node/NodeServices";
7import {7import {
8 ApprovalRequestId,8 ApprovalRequestId,
9 type OrchestrationEvent,9 type OrchestrationEvent,
apps/server/integration/providerService.integration.test.ts+1-1
...@@ -1,6 +1,6 @@...@@ -1,6 +1,6 @@
1import type { ProviderRuntimeEvent, ProviderSessionId } from "@t3tools/contracts";1import type { ProviderRuntimeEvent, ProviderSessionId } from "@t3tools/contracts";
2import { ThreadId } from "@t3tools/contracts";2import { ThreadId } from "@t3tools/contracts";
3import { NodeServices } from "@effect/platform-node";3import * as NodeServices from "@effect/platform-node/NodeServices";
4import { it, assert } from "@effect/vitest";4import { it, assert } from "@effect/vitest";
5import { Effect, FileSystem, Layer, Path, Queue, Stream } from "effect";5import { Effect, FileSystem, Layer, Path, Queue, Stream } from "effect";
66
apps/server/package.json+4-4
...@@ -18,17 +18,17 @@...@@ -18,17 +18,17 @@
18 "test": "vitest run"18 "test": "vitest run"
19 },19 },
20 "dependencies": {20 "dependencies": {
21 "@effect/platform-node": "4.0.0-beta.10",21 "@effect/platform-node": "catalog:",
22 "@effect/sql-sqlite-bun": "4.0.0-beta.10",22 "@effect/sql-sqlite-bun": "catalog:",
23 "@pierre/diffs": "^1.1.0-beta.16",23 "@pierre/diffs": "^1.1.0-beta.16",
24 "effect": "4.0.0-beta.10",24 "effect": "catalog:",
25 "node-pty": "^1.1.0",25 "node-pty": "^1.1.0",
26 "open": "^10.1.0",26 "open": "^10.1.0",
27 "ws": "^8.18.0"27 "ws": "^8.18.0"
28 },28 },
29 "devDependencies": {29 "devDependencies": {
30 "@effect/language-service": "0.75.1",30 "@effect/language-service": "0.75.1",
31 "@effect/vitest": "4.0.0-beta.10",31 "@effect/vitest": "catalog:",
32 "@t3tools/contracts": "workspace:*",32 "@t3tools/contracts": "workspace:*",
33 "@t3tools/web": "workspace:*",33 "@t3tools/web": "workspace:*",
34 "@types/bun": "^1.3.9",34 "@types/bun": "^1.3.9",
apps/server/src/checkpointing/Services/CheckpointDiffQuery.ts+1-1
...@@ -46,4 +46,4 @@ export interface CheckpointDiffQueryShape {...@@ -46,4 +46,4 @@ export interface CheckpointDiffQueryShape {
46export class CheckpointDiffQuery extends ServiceMap.Service<46export class CheckpointDiffQuery extends ServiceMap.Service<
47 CheckpointDiffQuery,47 CheckpointDiffQuery,
48 CheckpointDiffQueryShape48 CheckpointDiffQueryShape
49>()("checkpointing/CheckpointDiffQuery") {}49>()("t3/checkpointing/Services/CheckpointDiffQuery") {}
apps/server/src/checkpointing/Services/CheckpointStore.ts+1-1
...@@ -96,5 +96,5 @@ export interface CheckpointStoreShape {...@@ -96,5 +96,5 @@ export interface CheckpointStoreShape {
96 * CheckpointStore - Service tag for checkpoint persistence and restore operations.96 * CheckpointStore - Service tag for checkpoint persistence and restore operations.
97 */97 */
98export class CheckpointStore extends ServiceMap.Service<CheckpointStore, CheckpointStoreShape>()(98export class CheckpointStore extends ServiceMap.Service<CheckpointStore, CheckpointStoreShape>()(
99 "checkpointing/CheckpointStore",99 "t3/checkpointing/Services/CheckpointStore",
100) {}100) {}
apps/server/src/config.ts+7-7
...@@ -35,7 +35,7 @@ export interface ServerConfigShape {...@@ -35,7 +35,7 @@ export interface ServerConfigShape {
35 * ServerConfig - Service tag for server runtime configuration.35 * ServerConfig - Service tag for server runtime configuration.
36 */36 */
37export class ServerConfig extends ServiceMap.Service<ServerConfig, ServerConfigShape>()(37export class ServerConfig extends ServiceMap.Service<ServerConfig, ServerConfigShape>()(
38 "server/ServerConfig",38 "t3/config/ServerConfig",
39) {}39) {}
4040
41// Helpers41// Helpers
...@@ -61,7 +61,7 @@ export interface NetServiceShape {...@@ -61,7 +61,7 @@ export interface NetServiceShape {
61 * NetService - Service tag for startup networking helpers.61 * NetService - Service tag for startup networking helpers.
62 */62 */
63export class NetService extends ServiceMap.Service<NetService, NetServiceShape>()(63export class NetService extends ServiceMap.Service<NetService, NetServiceShape>()(
64 "server/NetService",64 "t3/config/NetService",
65) {65) {
66 static readonly layer = Layer.succeed(NetService, {66 static readonly layer = Layer.succeed(NetService, {
67 findAvailablePort: (preferred) =>67 findAvailablePort: (preferred) =>
...@@ -102,18 +102,18 @@ export class NetService extends ServiceMap.Service<NetService, NetServiceShape>(...@@ -102,18 +102,18 @@ export class NetService extends ServiceMap.Service<NetService, NetServiceShape>(
102102
103export const resolveStaticDir = Effect.fn(function* () {103export const resolveStaticDir = Effect.fn(function* () {
104 const { join, resolve } = yield* Path.Path;104 const { join, resolve } = yield* Path.Path;
105 const { stat } = yield* FileSystem.FileSystem;105 const { exists } = yield* FileSystem.FileSystem;
106 const bundledClient = resolve(join(import.meta.dirname, "client"));106 const bundledClient = resolve(join(import.meta.dirname, "client"));
107 const bundledStat = yield* stat(join(bundledClient, "index.html")).pipe(107 const bundledStat = yield* exists(join(bundledClient, "index.html")).pipe(
108 Effect.orElseSucceed(() => undefined),108 Effect.orElseSucceed(() => false),
109 );109 );
110 if (bundledStat) {110 if (bundledStat) {
111 return bundledClient;111 return bundledClient;
112 }112 }
113113
114 const monorepoClient = resolve(join(import.meta.dirname, "../../web/dist"));114 const monorepoClient = resolve(join(import.meta.dirname, "../../web/dist"));
115 const monorepoStat = yield* stat(join(monorepoClient, "index.html")).pipe(115 const monorepoStat = yield* exists(join(monorepoClient, "index.html")).pipe(
116 Effect.orElseSucceed(() => undefined),116 Effect.orElseSucceed(() => false),
117 );117 );
118 if (monorepoStat) {118 if (monorepoStat) {
119 return monorepoClient;119 return monorepoClient;
apps/server/src/git/Layers/CodexTextGeneration.test.ts+1-1
...@@ -1,4 +1,4 @@...@@ -1,4 +1,4 @@
1import { NodeServices } from "@effect/platform-node";1import * as NodeServices from "@effect/platform-node/NodeServices";
2import { it } from "@effect/vitest";2import { it } from "@effect/vitest";
3import { Effect, FileSystem, Layer, Path } from "effect";3import { Effect, FileSystem, Layer, Path } from "effect";
4import { expect } from "vitest";4import { expect } from "vitest";
apps/server/src/git/Layers/GitCore.test.ts+1-1
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1import { existsSync } from "node:fs";1import { existsSync } from "node:fs";
2import path from "node:path";2import path from "node:path";
33
4import { NodeServices } from "@effect/platform-node";4import * as NodeServices from "@effect/platform-node/NodeServices";
5import { it } from "@effect/vitest";5import { it } from "@effect/vitest";
6import { Effect, FileSystem, Layer, PlatformError, Scope } from "effect";6import { Effect, FileSystem, Layer, PlatformError, Scope } from "effect";
7import { describe, expect, vi } from "vitest";7import { describe, expect, vi } from "vitest";
apps/server/src/git/Layers/GitManager.test.ts+1-1
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1import fs from "node:fs";1import fs from "node:fs";
2import path from "node:path";2import path from "node:path";
33
4import { NodeServices } from "@effect/platform-node";4import * as NodeServices from "@effect/platform-node/NodeServices";
5import { it } from "@effect/vitest";5import { it } from "@effect/vitest";
6import { Effect, FileSystem, Layer, PlatformError, Scope } from "effect";6import { Effect, FileSystem, Layer, PlatformError, Scope } from "effect";
7import { expect } from "vitest";7import { expect } from "vitest";
apps/server/src/git/Layers/GitService.test.ts+1-1
...@@ -1,4 +1,4 @@...@@ -1,4 +1,4 @@
1import { NodeServices } from "@effect/platform-node";1import * as NodeServices from "@effect/platform-node/NodeServices";
2import { it, assert } from "@effect/vitest";2import { it, assert } from "@effect/vitest";
3import { Effect, Layer } from "effect";3import { Effect, Layer } from "effect";
44
apps/server/src/git/Services/GitCore.ts+3-1
...@@ -143,4 +143,6 @@ export interface GitCoreShape {...@@ -143,4 +143,6 @@ export interface GitCoreShape {
143/**143/**
144 * GitCore - Service tag for low-level Git repository operations.144 * GitCore - Service tag for low-level Git repository operations.
145 */145 */
146export class GitCore extends ServiceMap.Service<GitCore, GitCoreShape>()("git/GitCore") {}146export class GitCore extends ServiceMap.Service<GitCore, GitCoreShape>()(
147 "t3/git/Services/GitCore",
148) {}
apps/server/src/git/Services/GitHubCli.ts+3-1
...@@ -63,4 +63,6 @@ export interface GitHubCliShape {...@@ -63,4 +63,6 @@ export interface GitHubCliShape {
63/**63/**
64 * GitHubCli - Service tag for GitHub CLI process execution.64 * GitHubCli - Service tag for GitHub CLI process execution.
65 */65 */
66export class GitHubCli extends ServiceMap.Service<GitHubCli, GitHubCliShape>()("git/GitHubCli") {}66export class GitHubCli extends ServiceMap.Service<GitHubCli, GitHubCliShape>()(
67 "t3/git/Services/GitHubCli",
68) {}
apps/server/src/git/Services/GitManager.ts+4-2
...@@ -23,7 +23,9 @@ export interface GitManagerShape {...@@ -23,7 +23,9 @@ export interface GitManagerShape {
23 /**23 /**
24 * Read current repository Git status plus open PR metadata when available.24 * Read current repository Git status plus open PR metadata when available.
25 */25 */
26 readonly status: (input: GitStatusInput) => Effect.Effect<GitStatusResult, GitManagerServiceError>;26 readonly status: (
27 input: GitStatusInput,
28 ) => Effect.Effect<GitStatusResult, GitManagerServiceError>;
2729
28 /**30 /**
29 * Run a stacked Git action (`commit`, `commit_push`, `commit_push_pr`).31 * Run a stacked Git action (`commit`, `commit_push`, `commit_push_pr`).
...@@ -37,5 +39,5 @@ export interface GitManagerShape {...@@ -37,5 +39,5 @@ export interface GitManagerShape {
37 * GitManager - Service tag for stacked Git workflow orchestration.39 * GitManager - Service tag for stacked Git workflow orchestration.
38 */40 */
39export class GitManager extends ServiceMap.Service<GitManager, GitManagerShape>()(41export class GitManager extends ServiceMap.Service<GitManager, GitManagerShape>()(
40 "git/GitManager",42 "t3/git/Services/GitManager",
41) {}43) {}
apps/server/src/git/Services/GitService.ts+1-1
...@@ -41,5 +41,5 @@ export interface GitServiceShape {...@@ -41,5 +41,5 @@ export interface GitServiceShape {
41 * GitService - Service for Git command execution.41 * GitService - Service for Git command execution.
42 */42 */
43export class GitService extends ServiceMap.Service<GitService, GitServiceShape>()(43export class GitService extends ServiceMap.Service<GitService, GitServiceShape>()(
44 "git/GitService",44 "t3/git/Services/GitService",
45) {}45) {}
apps/server/src/git/Services/TextGeneration.ts+1-1
...@@ -67,5 +67,5 @@ export interface TextGenerationShape {...@@ -67,5 +67,5 @@ export interface TextGenerationShape {
67 * TextGeneration - Service tag for commit and PR text generation.67 * TextGeneration - Service tag for commit and PR text generation.
68 */68 */
69export class TextGeneration extends ServiceMap.Service<TextGeneration, TextGenerationShape>()(69export class TextGeneration extends ServiceMap.Service<TextGeneration, TextGenerationShape>()(
70 "git/TextGeneration",70 "t3/git/Services/TextGeneration",
71) {}71) {}
apps/server/src/keybindings.test.ts+22-5
...@@ -1,5 +1,5 @@...@@ -1,5 +1,5 @@
1import { KeybindingCommand, KeybindingRule, KeybindingsConfig } from "@t3tools/contracts";1import { KeybindingCommand, KeybindingRule, KeybindingsConfig } from "@t3tools/contracts";
2import { NodeServices } from "@effect/platform-node";2import * as NodeServices from "@effect/platform-node/NodeServices";
3import { assert, it } from "@effect/vitest";3import { assert, it } from "@effect/vitest";
4import { assertFailure } from "@effect/vitest/utils";4import { assertFailure } from "@effect/vitest/utils";
5import { Effect, FileSystem, Layer, Logger, Path, Schema } from "effect";5import { Effect, FileSystem, Layer, Logger, Path, Schema } from "effect";
...@@ -178,7 +178,10 @@ it.layer(NodeServices.layer)("keybindings", (it) => {...@@ -178,7 +178,10 @@ it.layer(NodeServices.layer)("keybindings", (it) => {
178 return yield* keybindings.loadConfigState;178 return yield* keybindings.loadConfigState;
179 });179 });
180180
181 assert.deepEqual(configState.keybindings, compileResolvedKeybindingsConfig(DEFAULT_KEYBINDINGS));181 assert.deepEqual(
182 configState.keybindings,
183 compileResolvedKeybindingsConfig(DEFAULT_KEYBINDINGS),
184 );
182 assert.deepEqual(configState.issues, [185 assert.deepEqual(configState.issues, [
183 {186 {
184 kind: "keybindings.malformed-config",187 kind: "keybindings.malformed-config",
...@@ -208,10 +211,24 @@ it.layer(NodeServices.layer)("keybindings", (it) => {...@@ -208,10 +211,24 @@ it.layer(NodeServices.layer)("keybindings", (it) => {
208 });211 });
209212
210 assert.isTrue(configState.keybindings.some((entry) => entry.command === "terminal.toggle"));213 assert.isTrue(configState.keybindings.some((entry) => entry.command === "terminal.toggle"));
211 assert.isFalse(configState.keybindings.some((entry) => entry.command === "invalid.command"));214 assert.isFalse(
215 configState.keybindings.some(
216 (entry) =>
217 // @ts-expect-error - invalid command
218 entry.command === "invalid.command",
219 ),
220 );
212 assert.deepEqual(configState.issues, [221 assert.deepEqual(configState.issues, [
213 { kind: "keybindings.invalid-entry", index: 1, message: configState.issues[0]?.message ?? "" },222 {
214 { kind: "keybindings.invalid-entry", index: 2, message: configState.issues[1]?.message ?? "" },223 kind: "keybindings.invalid-entry",
224 index: 1,
225 message: configState.issues[0]?.message ?? "",
226 },
227 {
228 kind: "keybindings.invalid-entry",
229 index: 2,
230 message: configState.issues[1]?.message ?? "",
231 },
215 ]);232 ]);
216 }).pipe(Effect.provide(makeKeybindingsLayer())),233 }).pipe(Effect.provide(makeKeybindingsLayer())),
217 );234 );
apps/server/src/keybindings.ts+1-1
...@@ -498,7 +498,7 @@ export interface KeybindingsShape {...@@ -498,7 +498,7 @@ export interface KeybindingsShape {
498 * Keybindings - Service tag for keybinding configuration operations.498 * Keybindings - Service tag for keybinding configuration operations.
499 */499 */
500export class Keybindings extends ServiceMap.Service<Keybindings, KeybindingsShape>()(500export class Keybindings extends ServiceMap.Service<Keybindings, KeybindingsShape>()(
501 "server/Keybindings",501 "t3/keybindings",
502) {}502) {}
503503
504const makeKeybindings = Effect.gen(function* () {504const makeKeybindings = Effect.gen(function* () {
apps/server/src/main.test.ts+1-1
...@@ -1,5 +1,5 @@...@@ -1,5 +1,5 @@
1import * as Http from "node:http";1import * as Http from "node:http";
2import { NodeServices } from "@effect/platform-node";2import * as NodeServices from "@effect/platform-node/NodeServices";
3import { assert, it, vi } from "@effect/vitest";3import { assert, it, vi } from "@effect/vitest";
4import * as ConfigProvider from "effect/ConfigProvider";4import * as ConfigProvider from "effect/ConfigProvider";
5import * as Effect from "effect/Effect";5import * as Effect from "effect/Effect";
apps/server/src/main.ts+3-1
...@@ -63,7 +63,9 @@ export interface CliConfigShape {...@@ -63,7 +63,9 @@ export interface CliConfigShape {
63/**63/**
64 * CliConfig - Service tag for startup CLI/runtime helpers.64 * CliConfig - Service tag for startup CLI/runtime helpers.
65 */65 */
66export class CliConfig extends ServiceMap.Service<CliConfig, CliConfigShape>()("server/CliConfig") {66export class CliConfig extends ServiceMap.Service<CliConfig, CliConfigShape>()(
67 "t3/main/CliConfig",
68) {
67 static readonly layer = Layer.effect(69 static readonly layer = Layer.effect(
68 CliConfig,70 CliConfig,
69 Effect.gen(function* () {71 Effect.gen(function* () {
apps/server/src/open.ts+1-1
...@@ -50,7 +50,7 @@ export interface OpenShape {...@@ -50,7 +50,7 @@ export interface OpenShape {
50/**50/**
51 * Open - Service tag for browser/editor launch operations.51 * Open - Service tag for browser/editor launch operations.
52 */52 */
53export class Open extends ServiceMap.Service<Open, OpenShape>()("server/Open") {}53export class Open extends ServiceMap.Service<Open, OpenShape>()("t3/open") {}
5454
55// ==============================55// ==============================
56// Implementations56// Implementations
apps/server/src/orchestration/Layers/CheckpointReactor.test.ts+1-1
...@@ -14,7 +14,7 @@ import {...@@ -14,7 +14,7 @@ import {
14 ThreadId,14 ThreadId,
15 TurnId,15 TurnId,
16} from "@t3tools/contracts";16} from "@t3tools/contracts";
17import { NodeServices } from "@effect/platform-node";17import * as NodeServices from "@effect/platform-node/NodeServices";
18import { Effect, Exit, Layer, ManagedRuntime, PubSub, Scope, Stream } from "effect";18import { Effect, Exit, Layer, ManagedRuntime, PubSub, Scope, Stream } from "effect";
19import { afterEach, describe, expect, it, vi } from "vitest";19import { afterEach, describe, expect, it, vi } from "vitest";
2020
apps/server/src/orchestration/Layers/OrchestrationEngine.ts+2-2
...@@ -5,7 +5,7 @@ import type {...@@ -5,7 +5,7 @@ import type {
5 ThreadId,5 ThreadId,
6} from "@t3tools/contracts";6} from "@t3tools/contracts";
7import { OrchestrationCommand } from "@t3tools/contracts";7import { OrchestrationCommand } from "@t3tools/contracts";
8import { Deferred, Effect, Layer, Option, PubSub, Queue, Stream } from "effect";8import { Deferred, Effect, Layer, Option, PubSub, Queue, Schema, Stream } from "effect";
9import * as SqlClient from "effect/unstable/sql/SqlClient";9import * as SqlClient from "effect/unstable/sql/SqlClient";
1010
11import { toPersistenceSqlError } from "../../persistence/Errors.ts";11import { toPersistenceSqlError } from "../../persistence/Errors.ts";
...@@ -181,7 +181,7 @@ const makeOrchestrationEngine = Effect.gen(function* () {...@@ -181,7 +181,7 @@ const makeOrchestrationEngine = Effect.gen(function* () {
181 ),181 ),
182 );182 );
183183
184 if (error instanceof OrchestrationCommandInvariantError) {184 if (Schema.is(OrchestrationCommandInvariantError)(error)) {
185 const aggregateRef = commandToAggregateRef(envelope.command);185 const aggregateRef = commandToAggregateRef(envelope.command);
186 yield* commandReceiptRepository186 yield* commandReceiptRepository
187 .upsert({187 .upsert({
apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts+1-1
...@@ -10,7 +10,7 @@ import {...@@ -10,7 +10,7 @@ import {
10 ThreadId,10 ThreadId,
11 TurnId,11 TurnId,
12} from "@t3tools/contracts";12} from "@t3tools/contracts";
13import { NodeServices } from "@effect/platform-node";13import * as NodeServices from "@effect/platform-node/NodeServices";
14import { assert, it } from "@effect/vitest";14import { assert, it } from "@effect/vitest";
15import { Effect, Layer } from "effect";15import { Effect, Layer } from "effect";
16import fs from "node:fs";16import fs from "node:fs";
apps/server/src/orchestration/Services/CheckpointReactor.ts+1-1
...@@ -31,4 +31,4 @@ export interface CheckpointReactorShape {...@@ -31,4 +31,4 @@ export interface CheckpointReactorShape {
31export class CheckpointReactor extends ServiceMap.Service<31export class CheckpointReactor extends ServiceMap.Service<
32 CheckpointReactor,32 CheckpointReactor,
33 CheckpointReactorShape33 CheckpointReactorShape
34>()("orchestration/CheckpointReactor") {}34>()("t3/orchestration/Services/CheckpointReactor") {}
apps/server/src/orchestration/Services/OrchestrationEngine.ts+1-1
...@@ -77,4 +77,4 @@ export interface OrchestrationEngineShape {...@@ -77,4 +77,4 @@ export interface OrchestrationEngineShape {
77export class OrchestrationEngineService extends ServiceMap.Service<77export class OrchestrationEngineService extends ServiceMap.Service<
78 OrchestrationEngineService,78 OrchestrationEngineService,
79 OrchestrationEngineShape79 OrchestrationEngineShape
80>()("orchestration/Engine") {}80>()("t3/orchestration/Services/OrchestrationEngine/OrchestrationEngineService") {}
apps/server/src/orchestration/Services/OrchestrationReactor.ts+1-1
...@@ -28,4 +28,4 @@ export interface OrchestrationReactorShape {...@@ -28,4 +28,4 @@ export interface OrchestrationReactorShape {
28export class OrchestrationReactor extends ServiceMap.Service<28export class OrchestrationReactor extends ServiceMap.Service<
29 OrchestrationReactor,29 OrchestrationReactor,
30 OrchestrationReactorShape30 OrchestrationReactorShape
31>()("orchestration/OrchestrationReactor") {}31>()("t3/orchestration/Services/OrchestrationReactor") {}
apps/server/src/orchestration/Services/ProjectionPipeline.ts+1-1
...@@ -39,4 +39,4 @@ export interface OrchestrationProjectionPipelineShape {...@@ -39,4 +39,4 @@ export interface OrchestrationProjectionPipelineShape {
39export class OrchestrationProjectionPipeline extends ServiceMap.Service<39export class OrchestrationProjectionPipeline extends ServiceMap.Service<
40 OrchestrationProjectionPipeline,40 OrchestrationProjectionPipeline,
41 OrchestrationProjectionPipelineShape41 OrchestrationProjectionPipelineShape
42>()("orchestration/ProjectionPipeline") {}42>()("t3/orchestration/Services/ProjectionPipeline/OrchestrationProjectionPipeline") {}
apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts+1-1
...@@ -31,4 +31,4 @@ export interface ProjectionSnapshotQueryShape {...@@ -31,4 +31,4 @@ export interface ProjectionSnapshotQueryShape {
31export class ProjectionSnapshotQuery extends ServiceMap.Service<31export class ProjectionSnapshotQuery extends ServiceMap.Service<
32 ProjectionSnapshotQuery,32 ProjectionSnapshotQuery,
33 ProjectionSnapshotQueryShape33 ProjectionSnapshotQueryShape
34>()("orchestration/ProjectionSnapshotQuery") {}34>()("t3/orchestration/Services/ProjectionSnapshotQuery") {}
apps/server/src/orchestration/Services/ProviderCommandReactor.ts+1-1
...@@ -31,4 +31,4 @@ export interface ProviderCommandReactorShape {...@@ -31,4 +31,4 @@ export interface ProviderCommandReactorShape {
31export class ProviderCommandReactor extends ServiceMap.Service<31export class ProviderCommandReactor extends ServiceMap.Service<
32 ProviderCommandReactor,32 ProviderCommandReactor,
33 ProviderCommandReactorShape33 ProviderCommandReactorShape
34>()("orchestration/ProviderCommandReactor") {}34>()("t3/orchestration/Services/ProviderCommandReactor") {}
apps/server/src/orchestration/Services/ProviderRuntimeIngestion.ts+1-1
...@@ -31,4 +31,4 @@ export interface ProviderRuntimeIngestionShape {...@@ -31,4 +31,4 @@ export interface ProviderRuntimeIngestionShape {
31export class ProviderRuntimeIngestionService extends ServiceMap.Service<31export class ProviderRuntimeIngestionService extends ServiceMap.Service<
32 ProviderRuntimeIngestionService,32 ProviderRuntimeIngestionService,
33 ProviderRuntimeIngestionShape33 ProviderRuntimeIngestionShape
34>()("orchestration/ProviderRuntimeIngestion") {}34>()("t3/orchestration/Services/ProviderRuntimeIngestion/ProviderRuntimeIngestionService") {}
apps/server/src/persistence/Layers/OrchestrationEventStore.test.ts+2-2
...@@ -1,6 +1,6 @@...@@ -1,6 +1,6 @@
1import { CommandId, EventId, ProjectId } from "@t3tools/contracts";1import { CommandId, EventId, ProjectId } from "@t3tools/contracts";
2import { assert, it } from "@effect/vitest";2import { assert, it } from "@effect/vitest";
3import { Effect, Layer, Stream } from "effect";3import { Effect, Layer, Schema, Stream } from "effect";
4import * as SqlClient from "effect/unstable/sql/SqlClient";4import * as SqlClient from "effect/unstable/sql/SqlClient";
55
6import { PersistenceDecodeError } from "../Errors.ts";6import { PersistenceDecodeError } from "../Errors.ts";
...@@ -107,7 +107,7 @@ layer("OrchestrationEventStore", (it) => {...@@ -107,7 +107,7 @@ layer("OrchestrationEventStore", (it) => {
107 );107 );
108 assert.equal(replayResult._tag, "Failure");108 assert.equal(replayResult._tag, "Failure");
109 if (replayResult._tag === "Failure") {109 if (replayResult._tag === "Failure") {
110 assert.ok(replayResult.failure instanceof PersistenceDecodeError);110 assert.ok(Schema.is(PersistenceDecodeError)(replayResult.failure));
111 assert.ok(111 assert.ok(
112 replayResult.failure.operation.includes(112 replayResult.failure.operation.includes(
113 "OrchestrationEventStore.readFromSequence:decodeRows",113 "OrchestrationEventStore.readFromSequence:decodeRows",
apps/server/src/persistence/Layers/ProviderSessions.ts+2-6
...@@ -51,14 +51,10 @@ function toValidationError(...@@ -51,14 +51,10 @@ function toValidationError(
51 });51 });
52}52}
5353
54function decodeInput<A>(54function decodeInput<S extends Schema.Top>(schema: S, input: unknown, operation: string) {
55 schema: Schema.Schema<A>,
56 input: unknown,
57 operation: string,
58): Effect.Effect<A, ProviderSessionRepositoryValidationError> {
59 return Schema.decodeUnknownEffect(schema)(input).pipe(55 return Schema.decodeUnknownEffect(schema)(input).pipe(
60 Effect.mapError((cause) => toValidationError(operation, cause)),56 Effect.mapError((cause) => toValidationError(operation, cause)),
61 ) as Effect.Effect<A, ProviderSessionRepositoryValidationError>;57 );
62}58}
6359
64function toPersistenceError(60function toPersistenceError(
apps/server/src/persistence/NodeSqliteClient.ts+1-1
...@@ -32,7 +32,7 @@ export type TypeId = "~local/sqlite-node/SqliteClient";...@@ -32,7 +32,7 @@ export type TypeId = "~local/sqlite-node/SqliteClient";
32/**32/**
33 * SqliteClient - Effect service tag for the sqlite SQL client.33 * SqliteClient - Effect service tag for the sqlite SQL client.
34 */34 */
35export const SqliteClient = ServiceMap.Service<Client.SqlClient>("@local/sqlite-node/SqliteClient");35export const SqliteClient = ServiceMap.Service<Client.SqlClient>("t3/persistence/NodeSqliteClient");
3636
37export interface SqliteClientConfig {37export interface SqliteClientConfig {
38 readonly filename: string;38 readonly filename: string;
apps/server/src/persistence/Services/OrchestrationCommandReceipts.ts+1-1
...@@ -66,4 +66,4 @@ export interface OrchestrationCommandReceiptRepositoryShape {...@@ -66,4 +66,4 @@ export interface OrchestrationCommandReceiptRepositoryShape {
66export class OrchestrationCommandReceiptRepository extends ServiceMap.Service<66export class OrchestrationCommandReceiptRepository extends ServiceMap.Service<
67 OrchestrationCommandReceiptRepository,67 OrchestrationCommandReceiptRepository,
68 OrchestrationCommandReceiptRepositoryShape68 OrchestrationCommandReceiptRepositoryShape
69>()("persistence/OrchestrationCommandReceiptRepository") {}69>()("t3/persistence/Services/OrchestrationCommandReceipts/OrchestrationCommandReceiptRepository") {}
apps/server/src/persistence/Services/OrchestrationEventStore.ts+1-1
...@@ -67,4 +67,4 @@ export interface OrchestrationEventStoreShape {...@@ -67,4 +67,4 @@ export interface OrchestrationEventStoreShape {
67export class OrchestrationEventStore extends ServiceMap.Service<67export class OrchestrationEventStore extends ServiceMap.Service<
68 OrchestrationEventStore,68 OrchestrationEventStore,
69 OrchestrationEventStoreShape69 OrchestrationEventStoreShape
70>()("orchestration/EventStore") {}70>()("t3/persistence/Services/OrchestrationEventStore") {}
apps/server/src/persistence/Services/ProjectionCheckpoints.ts+1-1
...@@ -90,4 +90,4 @@ export interface ProjectionCheckpointRepositoryShape {...@@ -90,4 +90,4 @@ export interface ProjectionCheckpointRepositoryShape {
90export class ProjectionCheckpointRepository extends ServiceMap.Service<90export class ProjectionCheckpointRepository extends ServiceMap.Service<
91 ProjectionCheckpointRepository,91 ProjectionCheckpointRepository,
92 ProjectionCheckpointRepositoryShape92 ProjectionCheckpointRepositoryShape
93>()("persistence/ProjectionCheckpointRepository") {}93>()("t3/persistence/Services/ProjectionCheckpoints/ProjectionCheckpointRepository") {}
apps/server/src/persistence/Services/ProjectionPendingApprovals.ts+1-1
...@@ -88,4 +88,4 @@ export interface ProjectionPendingApprovalRepositoryShape {...@@ -88,4 +88,4 @@ export interface ProjectionPendingApprovalRepositoryShape {
88export class ProjectionPendingApprovalRepository extends ServiceMap.Service<88export class ProjectionPendingApprovalRepository extends ServiceMap.Service<
89 ProjectionPendingApprovalRepository,89 ProjectionPendingApprovalRepository,
90 ProjectionPendingApprovalRepositoryShape90 ProjectionPendingApprovalRepositoryShape
91>()("persistence/ProjectionPendingApprovalRepository") {}91>()("t3/persistence/Services/ProjectionPendingApprovals/ProjectionPendingApprovalRepository") {}
apps/server/src/persistence/Services/ProjectionProjects.ts+1-1
...@@ -76,4 +76,4 @@ export interface ProjectionProjectRepositoryShape {...@@ -76,4 +76,4 @@ export interface ProjectionProjectRepositoryShape {
76export class ProjectionProjectRepository extends ServiceMap.Service<76export class ProjectionProjectRepository extends ServiceMap.Service<
77 ProjectionProjectRepository,77 ProjectionProjectRepository,
78 ProjectionProjectRepositoryShape78 ProjectionProjectRepositoryShape
79>()("persistence/ProjectionProjectRepository") {}79>()("t3/persistence/Services/ProjectionProjects/ProjectionProjectRepository") {}
apps/server/src/persistence/Services/ProjectionState.ts+1-1
...@@ -61,4 +61,4 @@ export interface ProjectionStateRepositoryShape {...@@ -61,4 +61,4 @@ export interface ProjectionStateRepositoryShape {
61export class ProjectionStateRepository extends ServiceMap.Service<61export class ProjectionStateRepository extends ServiceMap.Service<
62 ProjectionStateRepository,62 ProjectionStateRepository,
63 ProjectionStateRepositoryShape63 ProjectionStateRepositoryShape
64>()("persistence/ProjectionStateRepository") {}64>()("t3/persistence/Services/ProjectionState/ProjectionStateRepository") {}
apps/server/src/persistence/Services/ProjectionThreadActivities.ts+1-1
...@@ -77,4 +77,4 @@ export interface ProjectionThreadActivityRepositoryShape {...@@ -77,4 +77,4 @@ export interface ProjectionThreadActivityRepositoryShape {
77export class ProjectionThreadActivityRepository extends ServiceMap.Service<77export class ProjectionThreadActivityRepository extends ServiceMap.Service<
78 ProjectionThreadActivityRepository,78 ProjectionThreadActivityRepository,
79 ProjectionThreadActivityRepositoryShape79 ProjectionThreadActivityRepositoryShape
80>()("persistence/ProjectionThreadActivityRepository") {}80>()("t3/persistence/Services/ProjectionThreadActivities/ProjectionThreadActivityRepository") {}
apps/server/src/persistence/Services/ProjectionThreadMessages.ts+1-1
...@@ -76,4 +76,4 @@ export interface ProjectionThreadMessageRepositoryShape {...@@ -76,4 +76,4 @@ export interface ProjectionThreadMessageRepositoryShape {
76export class ProjectionThreadMessageRepository extends ServiceMap.Service<76export class ProjectionThreadMessageRepository extends ServiceMap.Service<
77 ProjectionThreadMessageRepository,77 ProjectionThreadMessageRepository,
78 ProjectionThreadMessageRepositoryShape78 ProjectionThreadMessageRepositoryShape
79>()("persistence/ProjectionThreadMessageRepository") {}79>()("t3/persistence/Services/ProjectionThreadMessages/ProjectionThreadMessageRepository") {}
apps/server/src/persistence/Services/ProjectionThreadSessions.ts+1-1
...@@ -73,4 +73,4 @@ export interface ProjectionThreadSessionRepositoryShape {...@@ -73,4 +73,4 @@ export interface ProjectionThreadSessionRepositoryShape {
73export class ProjectionThreadSessionRepository extends ServiceMap.Service<73export class ProjectionThreadSessionRepository extends ServiceMap.Service<
74 ProjectionThreadSessionRepository,74 ProjectionThreadSessionRepository,
75 ProjectionThreadSessionRepositoryShape75 ProjectionThreadSessionRepositoryShape
76>()("persistence/ProjectionThreadSessionRepository") {}76>()("t3/persistence/Services/ProjectionThreadSessions/ProjectionThreadSessionRepository") {}
apps/server/src/persistence/Services/ProjectionThreads.ts+1-1
...@@ -82,4 +82,4 @@ export interface ProjectionThreadRepositoryShape {...@@ -82,4 +82,4 @@ export interface ProjectionThreadRepositoryShape {
82export class ProjectionThreadRepository extends ServiceMap.Service<82export class ProjectionThreadRepository extends ServiceMap.Service<
83 ProjectionThreadRepository,83 ProjectionThreadRepository,
84 ProjectionThreadRepositoryShape84 ProjectionThreadRepositoryShape
85>()("persistence/ProjectionThreadRepository") {}85>()("t3/persistence/Services/ProjectionThreads/ProjectionThreadRepository") {}
apps/server/src/persistence/Services/ProjectionTurns.ts+1-1
...@@ -158,4 +158,4 @@ export interface ProjectionTurnRepositoryShape {...@@ -158,4 +158,4 @@ export interface ProjectionTurnRepositoryShape {
158export class ProjectionTurnRepository extends ServiceMap.Service<158export class ProjectionTurnRepository extends ServiceMap.Service<
159 ProjectionTurnRepository,159 ProjectionTurnRepository,
160 ProjectionTurnRepositoryShape160 ProjectionTurnRepositoryShape
161>()("persistence/ProjectionTurnRepository") {}161>()("t3/persistence/Services/ProjectionTurns/ProjectionTurnRepository") {}
apps/server/src/persistence/Services/ProviderSessionRuntime.ts+1-1
...@@ -84,4 +84,4 @@ export interface ProviderSessionRuntimeRepositoryShape {...@@ -84,4 +84,4 @@ export interface ProviderSessionRuntimeRepositoryShape {
84export class ProviderSessionRuntimeRepository extends ServiceMap.Service<84export class ProviderSessionRuntimeRepository extends ServiceMap.Service<
85 ProviderSessionRuntimeRepository,85 ProviderSessionRuntimeRepository,
86 ProviderSessionRuntimeRepositoryShape86 ProviderSessionRuntimeRepositoryShape
87>()("persistence/ProviderSessionRuntimeRepository") {}87>()("t3/persistence/Services/ProviderSessionRuntime/ProviderSessionRuntimeRepository") {}
apps/server/src/persistence/Services/ProviderSessions.ts+1-1
...@@ -78,4 +78,4 @@ export interface ProviderSessionRepositoryShape {...@@ -78,4 +78,4 @@ export interface ProviderSessionRepositoryShape {
78export class ProviderSessionRepository extends ServiceMap.Service<78export class ProviderSessionRepository extends ServiceMap.Service<
79 ProviderSessionRepository,79 ProviderSessionRepository,
80 ProviderSessionRepositoryShape80 ProviderSessionRepositoryShape
81>()("persistence/ProviderSessionRepository") {}81>()("t3/persistence/Services/ProviderSessions/ProviderSessionRepository") {}
apps/server/src/provider/Layers/ProviderAdapterRegistry.test.ts+1-1
...@@ -8,7 +8,7 @@ import { CodexAdapter, CodexAdapterShape } from "../Services/CodexAdapter.ts";...@@ -8,7 +8,7 @@ import { CodexAdapter, CodexAdapterShape } from "../Services/CodexAdapter.ts";
8import { ProviderAdapterRegistry } from "../Services/ProviderAdapterRegistry.ts";8import { ProviderAdapterRegistry } from "../Services/ProviderAdapterRegistry.ts";
9import { ProviderAdapterRegistryLive } from "./ProviderAdapterRegistry.ts";9import { ProviderAdapterRegistryLive } from "./ProviderAdapterRegistry.ts";
10import { ProviderUnsupportedError } from "../Errors.ts";10import { ProviderUnsupportedError } from "../Errors.ts";
11import { NodeServices } from "@effect/platform-node";11import * as NodeServices from "@effect/platform-node/NodeServices";
1212
13const fakeCodexAdapter: CodexAdapterShape = {13const fakeCodexAdapter: CodexAdapterShape = {
14 provider: "codex",14 provider: "codex",
apps/server/src/provider/Layers/ProviderService.test.ts+1-1
...@@ -37,7 +37,7 @@ import { ProviderService } from "../Services/ProviderService.ts";...@@ -37,7 +37,7 @@ import { ProviderService } from "../Services/ProviderService.ts";
37import { ProviderSessionDirectory } from "../Services/ProviderSessionDirectory.ts";37import { ProviderSessionDirectory } from "../Services/ProviderSessionDirectory.ts";
38import { makeProviderServiceLive } from "./ProviderService.ts";38import { makeProviderServiceLive } from "./ProviderService.ts";
39import { ProviderSessionDirectoryLive } from "./ProviderSessionDirectory.ts";39import { ProviderSessionDirectoryLive } from "./ProviderSessionDirectory.ts";
40import { NodeServices } from "@effect/platform-node";40import * as NodeServices from "@effect/platform-node/NodeServices";
41import { ProviderSessionRuntimeRepositoryLive } from "../../persistence/Layers/ProviderSessionRuntime.ts";41import { ProviderSessionRuntimeRepositoryLive } from "../../persistence/Layers/ProviderSessionRuntime.ts";
42import { ProviderSessionRuntimeRepository } from "../../persistence/Services/ProviderSessionRuntime.ts";42import { ProviderSessionRuntimeRepository } from "../../persistence/Services/ProviderSessionRuntime.ts";
43import {43import {
apps/server/src/provider/Layers/ProviderSessionDirectory.test.ts+1-1
...@@ -2,7 +2,7 @@ import fs from "node:fs";...@@ -2,7 +2,7 @@ import fs from "node:fs";
2import os from "node:os";2import os from "node:os";
3import path from "node:path";3import path from "node:path";
44
5import { NodeServices } from "@effect/platform-node";5import * as NodeServices from "@effect/platform-node/NodeServices";
6import { ProviderSessionId, ProviderThreadId, ThreadId } from "@t3tools/contracts";6import { ProviderSessionId, ProviderThreadId, ThreadId } from "@t3tools/contracts";
7import { it, assert } from "@effect/vitest";7import { it, assert } from "@effect/vitest";
8import { assertFailure, assertSome } from "@effect/vitest/utils";8import { assertFailure, assertSome } from "@effect/vitest/utils";
apps/server/src/provider/Services/CodexAdapter.ts+1-1
...@@ -26,5 +26,5 @@ export interface CodexAdapterShape extends ProviderAdapterShape<ProviderAdapterE...@@ -26,5 +26,5 @@ export interface CodexAdapterShape extends ProviderAdapterShape<ProviderAdapterE
26 * CodexAdapter - Service tag for Codex provider adapter operations.26 * CodexAdapter - Service tag for Codex provider adapter operations.
27 */27 */
28export class CodexAdapter extends ServiceMap.Service<CodexAdapter, CodexAdapterShape>()(28export class CodexAdapter extends ServiceMap.Service<CodexAdapter, CodexAdapterShape>()(
29 "provider/CodexAdapter",29 "t3/provider/Services/CodexAdapter",
30) {}30) {}
apps/server/src/provider/Services/ProviderAdapterRegistry.ts+1-1
...@@ -37,6 +37,6 @@ export interface ProviderAdapterRegistryShape {...@@ -37,6 +37,6 @@ export interface ProviderAdapterRegistryShape {
37export class ProviderAdapterRegistry extends ServiceMap.Service<37export class ProviderAdapterRegistry extends ServiceMap.Service<
38 ProviderAdapterRegistry,38 ProviderAdapterRegistry,
39 ProviderAdapterRegistryShape39 ProviderAdapterRegistryShape
40>()("provider/ProviderAdapterRegistry") {}40>()("t3/provider/Services/ProviderAdapterRegistry") {}
4141
42// Dummy comment for workflow testing.42// Dummy comment for workflow testing.
apps/server/src/provider/Services/ProviderService.ts+1-1
...@@ -105,5 +105,5 @@ export interface ProviderServiceShape {...@@ -105,5 +105,5 @@ export interface ProviderServiceShape {
105 * ProviderService - Service tag for provider orchestration.105 * ProviderService - Service tag for provider orchestration.
106 */106 */
107export class ProviderService extends ServiceMap.Service<ProviderService, ProviderServiceShape>()(107export class ProviderService extends ServiceMap.Service<ProviderService, ProviderServiceShape>()(
108 "provider/ProviderService",108 "t3/provider/Services/ProviderService",
109) {}109) {}
apps/server/src/provider/Services/ProviderSessionDirectory.ts+1-1
...@@ -99,4 +99,4 @@ export interface ProviderSessionDirectoryShape {...@@ -99,4 +99,4 @@ export interface ProviderSessionDirectoryShape {
99export class ProviderSessionDirectory extends ServiceMap.Service<99export class ProviderSessionDirectory extends ServiceMap.Service<
100 ProviderSessionDirectory,100 ProviderSessionDirectory,
101 ProviderSessionDirectoryShape101 ProviderSessionDirectoryShape
102>()("provider/ProviderSessionDirectory") {}102>()("t3/provider/Services/ProviderSessionDirectory") {}
apps/server/src/serverLayers.ts+1-1
...@@ -1,6 +1,6 @@...@@ -1,6 +1,6 @@
1import path from "node:path";1import path from "node:path";
22
3import { NodeServices } from "@effect/platform-node";3import * as NodeServices from "@effect/platform-node/NodeServices";
4import { Effect, Layer } from "effect";4import { Effect, Layer } from "effect";
5import * as SqlClient from "effect/unstable/sql/SqlClient";5import * as SqlClient from "effect/unstable/sql/SqlClient";
66
apps/server/src/terminal/Layers/Manager.test.ts+42-4
...@@ -9,7 +9,13 @@ import {...@@ -9,7 +9,13 @@ import {
9} from "@t3tools/contracts";9} from "@t3tools/contracts";
10import { afterEach, describe, expect, it } from "vitest";10import { afterEach, describe, expect, it } from "vitest";
1111
12import type { PtyAdapterShape, PtyExitEvent, PtyProcess, PtySpawnInput } from "../Services/PTY";12import {
13 PtySpawnError,
14 type PtyAdapterShape,
15 type PtyExitEvent,
16 type PtyProcess,
17 type PtySpawnInput,
18} from "../Services/PTY";
13import { TerminalManagerRuntime } from "./Manager";19import { TerminalManagerRuntime } from "./Manager";
14import { Effect, Encoding } from "effect";20import { Effect, Encoding } from "effect";
1521
...@@ -69,14 +75,33 @@ class FakePtyAdapter implements PtyAdapterShape {...@@ -69,14 +75,33 @@ class FakePtyAdapter implements PtyAdapterShape {
69 readonly spawnFailures: Error[] = [];75 readonly spawnFailures: Error[] = [];
70 private nextPid = 9000;76 private nextPid = 9000;
7177
72 spawn(input: PtySpawnInput): Effect.Effect<PtyProcess> {78 constructor(private readonly mode: "sync" | "async" = "sync") {}
79
80 spawn(input: PtySpawnInput): Effect.Effect<PtyProcess, PtySpawnError> {
73 this.spawnInputs.push(input);81 this.spawnInputs.push(input);
74 const failure = this.spawnFailures.shift();82 const failure = this.spawnFailures.shift();
75 if (failure) {83 if (failure) {
76 throw failure;84 return Effect.fail(
85 new PtySpawnError({
86 adapter: "fake",
87 message: "Failed to spawn PTY process",
88 cause: failure,
89 }),
90 );
77 }91 }
78 const process = new FakePtyProcess(this.nextPid++);92 const process = new FakePtyProcess(this.nextPid++);
79 this.processes.push(process);93 this.processes.push(process);
94 if (this.mode === "async") {
95 return Effect.tryPromise({
96 try: async () => process,
97 catch: (cause) =>
98 new PtySpawnError({
99 adapter: "fake",
100 message: "Failed to spawn PTY process",
101 cause,
102 }),
103 });
104 }
80 return Effect.succeed(process);105 return Effect.succeed(process);
81 }106 }
82}107}
...@@ -150,11 +175,12 @@ describe("TerminalManager", () => {...@@ -150,11 +175,12 @@ describe("TerminalManager", () => {
150 subprocessPollIntervalMs?: number;175 subprocessPollIntervalMs?: number;
151 processKillGraceMs?: number;176 processKillGraceMs?: number;
152 maxRetainedInactiveSessions?: number;177 maxRetainedInactiveSessions?: number;
178 ptyAdapter?: FakePtyAdapter;
153 } = {},179 } = {},
154 ) {180 ) {
155 const logsDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3code-terminal-"));181 const logsDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3code-terminal-"));
156 tempDirs.push(logsDir);182 tempDirs.push(logsDir);
157 const ptyAdapter = new FakePtyAdapter();183 const ptyAdapter = options.ptyAdapter ?? new FakePtyAdapter();
158 const manager = new TerminalManagerRuntime({184 const manager = new TerminalManagerRuntime({
159 logsDir,185 logsDir,
160 ptyAdapter,186 ptyAdapter,
...@@ -189,6 +215,18 @@ describe("TerminalManager", () => {...@@ -189,6 +215,18 @@ describe("TerminalManager", () => {
189 manager.dispose();215 manager.dispose();
190 });216 });
191217
218 it("supports asynchronous PTY spawn effects", async () => {
219 const { manager, ptyAdapter } = makeManager(5, { ptyAdapter: new FakePtyAdapter("async") });
220
221 const snapshot = await manager.open(openInput());
222
223 expect(snapshot.status).toBe("running");
224 expect(ptyAdapter.spawnInputs).toHaveLength(1);
225 expect(ptyAdapter.processes).toHaveLength(1);
226
227 manager.dispose();
228 });
229
192 it("forwards write and resize to active pty process", async () => {230 it("forwards write and resize to active pty process", async () => {
193 const { manager, ptyAdapter } = makeManager();231 const { manager, ptyAdapter } = makeManager();
194 await manager.open(openInput());232 await manager.open(openInput());
apps/server/src/terminal/Layers/Manager.ts+75-20
...@@ -117,8 +117,43 @@ function resolveShellCandidates(shellResolver: () => string): ShellCandidate[] {...@@ -117,8 +117,43 @@ function resolveShellCandidates(shellResolver: () => string): ShellCandidate[] {
117}117}
118118
119function isRetryableShellSpawnError(error: unknown): boolean {119function isRetryableShellSpawnError(error: unknown): boolean {
120 if (!(error instanceof Error)) return false;120 const queue: unknown[] = [error];
121 const message = error.message.toLowerCase();121 const seen = new Set<unknown>();
122 const messages: string[] = [];
123
124 while (queue.length > 0) {
125 const current = queue.shift();
126 if (!current || seen.has(current)) {
127 continue;
128 }
129 seen.add(current);
130
131 if (typeof current === "string") {
132 messages.push(current);
133 continue;
134 }
135
136 if (current instanceof Error) {
137 messages.push(current.message);
138 const cause = (current as { cause?: unknown }).cause;
139 if (cause) {
140 queue.push(cause);
141 }
142 continue;
143 }
144
145 if (typeof current === "object") {
146 const value = current as { message?: unknown; cause?: unknown };
147 if (typeof value.message === "string") {
148 messages.push(value.message);
149 }
150 if (value.cause) {
151 queue.push(value.cause);
152 }
153 }
154 }
155
156 const message = messages.join(" ").toLowerCase();
122 return (157 return (
123 message.includes("posix_spawnp failed") ||158 message.includes("posix_spawnp failed") ||
124 message.includes("enoent") ||159 message.includes("enoent") ||
...@@ -354,7 +389,7 @@ export class TerminalManagerRuntime extends EventEmitter<TerminalManagerEvents>...@@ -354,7 +389,7 @@ export class TerminalManagerRuntime extends EventEmitter<TerminalManagerEvents>
354 };389 };
355 this.sessions.set(sessionKey, session);390 this.sessions.set(sessionKey, session);
356 this.evictInactiveSessionsIfNeeded();391 this.evictInactiveSessionsIfNeeded();
357 this.startSession(session, { ...input, cols, rows }, "started");392 await this.startSession(session, { ...input, cols, rows }, "started");
358 return this.snapshot(session);393 return this.snapshot(session);
359 }394 }
360395
...@@ -380,7 +415,7 @@ export class TerminalManagerRuntime extends EventEmitter<TerminalManagerEvents>...@@ -380,7 +415,7 @@ export class TerminalManagerRuntime extends EventEmitter<TerminalManagerEvents>
380 }415 }
381416
382 if (!existing.process) {417 if (!existing.process) {
383 this.startSession(existing, { ...input, cols: targetCols, rows: targetRows }, "started");418 await this.startSession(existing, { ...input, cols: targetCols, rows: targetRows }, "started");
384 return this.snapshot(existing);419 return this.snapshot(existing);
385 }420 }
386421
...@@ -477,7 +512,7 @@ export class TerminalManagerRuntime extends EventEmitter<TerminalManagerEvents>...@@ -477,7 +512,7 @@ export class TerminalManagerRuntime extends EventEmitter<TerminalManagerEvents>
477512
478 session.history = "";513 session.history = "";
479 await this.persistHistory(input.threadId, input.terminalId, session.history);514 await this.persistHistory(input.threadId, input.terminalId, session.history);
480 this.startSession(session, { ...input, cols, rows }, "restarted");515 await this.startSession(session, { ...input, cols, rows }, "restarted");
481 return this.snapshot(session);516 return this.snapshot(session);
482 });517 });
483 }518 }
...@@ -528,11 +563,11 @@ export class TerminalManagerRuntime extends EventEmitter<TerminalManagerEvents>...@@ -528,11 +563,11 @@ export class TerminalManagerRuntime extends EventEmitter<TerminalManagerEvents>
528 this.persistQueues.clear();563 this.persistQueues.clear();
529 }564 }
530565
531 private startSession(566 private async startSession(
532 session: TerminalSessionState,567 session: TerminalSessionState,
533 input: TerminalStartInput,568 input: TerminalStartInput,
534 eventType: "started" | "restarted",569 eventType: "started" | "restarted",
535 ): void {570 ): Promise<void> {
536 this.stopProcess(session);571 this.stopProcess(session);
537572
538 session.status = "starting";573 session.status = "starting";
...@@ -551,26 +586,46 @@ export class TerminalManagerRuntime extends EventEmitter<TerminalManagerEvents>...@@ -551,26 +586,46 @@ export class TerminalManagerRuntime extends EventEmitter<TerminalManagerEvents>
551 const terminalEnv = createTerminalSpawnEnv(process.env, session.runtimeEnv);586 const terminalEnv = createTerminalSpawnEnv(process.env, session.runtimeEnv);
552 let lastSpawnError: unknown = null;587 let lastSpawnError: unknown = null;
553588
554 for (const candidate of shellCandidates) {589 const spawnWithCandidate = (candidate: ShellCandidate) =>
590 Effect.runPromise(
591 this.ptyAdapter.spawn({
592 shell: candidate.shell,
593 ...(candidate.args ? { args: candidate.args } : {}),
594 cwd: session.cwd,
595 cols: session.cols,
596 rows: session.rows,
597 env: terminalEnv,
598 }),
599 );
600
601 const trySpawn = async (
602 candidates: ShellCandidate[],
603 index = 0,
604 ): Promise<{ process: PtyProcess; shellLabel: string } | null> => {
605 if (index >= candidates.length) {
606 return null;
607 }
608 const candidate = candidates[index];
609 if (!candidate) {
610 return null;
611 }
612
555 try {613 try {
556 ptyProcess = Effect.runSync(614 const process = await spawnWithCandidate(candidate);
557 this.ptyAdapter.spawn({615 return { process, shellLabel: formatShellCandidate(candidate) };
558 shell: candidate.shell,
559 ...(candidate.args ? { args: candidate.args } : {}),
560 cwd: session.cwd,
561 cols: session.cols,
562 rows: session.rows,
563 env: terminalEnv,
564 }),
565 );
566 startedShell = formatShellCandidate(candidate);
567 break;
568 } catch (error) {616 } catch (error) {
569 lastSpawnError = error;617 lastSpawnError = error;
570 if (!isRetryableShellSpawnError(error)) {618 if (!isRetryableShellSpawnError(error)) {
571 throw error;619 throw error;
572 }620 }
621 return trySpawn(candidates, index + 1);
573 }622 }
623 };
624
625 const spawnResult = await trySpawn(shellCandidates);
626 if (spawnResult) {
627 ptyProcess = spawnResult.process;
628 startedShell = spawnResult.shellLabel;
574 }629 }
575630
576 if (!ptyProcess) {631 if (!ptyProcess) {
apps/server/src/terminal/Layers/NodePTY.test.ts+1-1
...@@ -2,7 +2,7 @@ import { FileSystem, Path, Effect } from "effect";...@@ -2,7 +2,7 @@ import { FileSystem, Path, Effect } from "effect";
2import { assert, it } from "@effect/vitest";2import { assert, it } from "@effect/vitest";
33
4import { ensureNodePtySpawnHelperExecutable } from "./NodePTY";4import { ensureNodePtySpawnHelperExecutable } from "./NodePTY";
5import { NodeServices } from "@effect/platform-node";5import * as NodeServices from "@effect/platform-node/NodeServices";
66
7it.layer(NodeServices.layer)("ensureNodePtySpawnHelperExecutable", (it) => {7it.layer(NodeServices.layer)("ensureNodePtySpawnHelperExecutable", (it) => {
8 it.effect("adds executable bits when helper exists but is not executable", () =>8 it.effect("adds executable bits when helper exists but is not executable", () =>
apps/server/src/terminal/Layers/NodePTY.ts+5-4
...@@ -37,11 +37,12 @@ export const ensureNodePtySpawnHelperExecutable = Effect.fn(function* (explicitP...@@ -37,11 +37,12 @@ export const ensureNodePtySpawnHelperExecutable = Effect.fn(function* (explicitP
37 didEnsureSpawnHelperExecutable = true;37 didEnsureSpawnHelperExecutable = true;
38 }38 }
3939
40 const stat = yield* fs.stat(helperPath);40 if (!(yield* fs.exists(helperPath))) {
41 const mode = stat.mode & 0o777;41 return;
42 if ((mode & 0o111) === 0) {
43 yield* fs.chmod(helperPath, mode | 0o111);
44 }42 }
43
44 // Best-effort: avoid FileSystem.stat in packaged mode where some fs metadata can be missing.
45 yield* fs.chmod(helperPath, 0o755).pipe(Effect.orElseSucceed(() => undefined));
45});46});
4647
47class NodePtyProcess implements PtyProcess {48class NodePtyProcess implements PtyProcess {
apps/server/src/terminal/Services/Manager.ts+1-1
...@@ -113,5 +113,5 @@ export interface TerminalManagerShape {...@@ -113,5 +113,5 @@ export interface TerminalManagerShape {
113 * TerminalManager - Service tag for terminal session orchestration.113 * TerminalManager - Service tag for terminal session orchestration.
114 */114 */
115export class TerminalManager extends ServiceMap.Service<TerminalManager, TerminalManagerShape>()(115export class TerminalManager extends ServiceMap.Service<TerminalManager, TerminalManagerShape>()(
116 "terminal/TerminalManager",116 "t3/terminal/Services/Manager/TerminalManager",
117) {}117) {}
apps/server/src/terminal/Services/PTY.ts+12-3
...@@ -6,7 +6,16 @@...@@ -6,7 +6,16 @@
6 *6 *
7 * @module PtyAdapter7 * @module PtyAdapter
8 */8 */
9import { Effect, ServiceMap } from "effect";9import { Effect, Schema, ServiceMap } from "effect";
10
11/**
12 * PtyError - Error type for PTY adapter operations.
13 */
14export class PtySpawnError extends Schema.TaggedErrorClass<PtySpawnError>()("PtySpawnError", {
15 adapter: Schema.String,
16 message: Schema.String,
17 cause: Schema.optional(Schema.Defect),
18}) {}
1019
11export interface PtyExitEvent {20export interface PtyExitEvent {
12 exitCode: number;21 exitCode: number;
...@@ -38,12 +47,12 @@ export interface PtyAdapterShape {...@@ -38,12 +47,12 @@ export interface PtyAdapterShape {
38 /**47 /**
39 * Spawn a PTY process for a terminal session.48 * Spawn a PTY process for a terminal session.
40 */49 */
41 spawn(input: PtySpawnInput): Effect.Effect<PtyProcess>;50 spawn(input: PtySpawnInput): Effect.Effect<PtyProcess, PtySpawnError>;
42}51}
4352
44/**53/**
45 * PtyAdapter - Service tag for PTY process integration.54 * PtyAdapter - Service tag for PTY process integration.
46 */55 */
47export class PtyAdapter extends ServiceMap.Service<PtyAdapter, PtyAdapterShape>()(56export class PtyAdapter extends ServiceMap.Service<PtyAdapter, PtyAdapterShape>()(
48 "terminal/PtyAdapter",57 "t3/terminal/Services/PTY/PtyAdapter",
49) {}58) {}
apps/server/src/wsServer.ts+10-14
...@@ -23,7 +23,7 @@ import {...@@ -23,7 +23,7 @@ import {
23 WsPush,23 WsPush,
24 WsResponse,24 WsResponse,
25} from "@t3tools/contracts";25} from "@t3tools/contracts";
26import { NodeHttpServer } from "@effect/platform-node";26import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer";
27import { Cause, Effect, Exit, Layer, Ref, Schema, Scope, ServiceMap, Stream, Struct } from "effect";27import { Cause, Effect, Exit, Layer, Ref, Schema, Scope, ServiceMap, Stream, Struct } from "effect";
28import { WebSocketServer, type WebSocket } from "ws";28import { WebSocketServer, type WebSocket } from "ws";
2929
...@@ -79,7 +79,7 @@ export interface ServerShape {...@@ -79,7 +79,7 @@ export interface ServerShape {
79/**79/**
80 * Server - Service tag for HTTP/WebSocket lifecycle management.80 * Server - Service tag for HTTP/WebSocket lifecycle management.
81 */81 */
82export class Server extends ServiceMap.Service<Server, ServerShape>()("server/Server") {}82export class Server extends ServiceMap.Service<Server, ServerShape>()("t3/wsServer/Server") {}
8383
84const isServerNotRunningError = (error: unknown): boolean => {84const isServerNotRunningError = (error: unknown): boolean => {
85 if (!(error instanceof Error)) return false;85 if (!(error instanceof Error)) return false;
...@@ -320,22 +320,18 @@ export const createServer = Effect.fn(function* (): Effect.fn.Return<...@@ -320,22 +320,18 @@ export const createServer = Effect.fn(function* (): Effect.fn.Return<
320 yield* Effect.addFinalizer(() => Scope.close(subscriptionsScope, Exit.void));320 yield* Effect.addFinalizer(() => Scope.close(subscriptionsScope, Exit.void));
321321
322 yield* Stream.runForEach(orchestrationEngine.streamDomainEvents, (event) =>322 yield* Stream.runForEach(orchestrationEngine.streamDomainEvents, (event) =>
323 Effect.gen(function* () {323 broadcastPush({
324 yield* broadcastPush({324 type: "push",
325 type: "push",325 channel: ORCHESTRATION_WS_CHANNELS.domainEvent,
326 channel: ORCHESTRATION_WS_CHANNELS.domainEvent,326 data: event,
327 data: event,
328 });
329 }),327 }),
330 ).pipe(Effect.forkIn(subscriptionsScope));328 ).pipe(Effect.forkIn(subscriptionsScope));
331329
332 yield* Stream.runForEach(keybindingsManager.changes, (event) =>330 yield* Stream.runForEach(keybindingsManager.changes, (event) =>
333 Effect.gen(function* () {331 broadcastPush({
334 yield* broadcastPush({332 type: "push",
335 type: "push",333 channel: WS_CHANNELS.serverConfigUpdated,
336 channel: WS_CHANNELS.serverConfigUpdated,334 data: event,
337 data: event,
338 });
339 }),335 }),
340 ).pipe(Effect.forkIn(subscriptionsScope));336 ).pipe(Effect.forkIn(subscriptionsScope));
341337
apps/server/tsconfig.json+8-1
...@@ -8,7 +8,14 @@...@@ -8,7 +8,14 @@
8 "allowImportingTsExtensions": true,8 "allowImportingTsExtensions": true,
9 "plugins": [9 "plugins": [
10 {10 {
11 "name": "@effect/language-service"11 "name": "@effect/language-service",
12 "namespaceImportPackages": ["@effect/platform-node"],
13 "diagnosticSeverity": {
14 "importFromBarrel": "error",
15 "anyUnknownInErrorContext": "warning",
16 "instanceOfSchema": "warning",
17 "deterministicKeys": "warning"
18 }
12 }19 }
13 ]20 ]
14 },21 },
apps/web/package.json+2-1
...@@ -21,7 +21,7 @@...@@ -21,7 +21,7 @@
21 "@xterm/addon-fit": "^0.11.0",21 "@xterm/addon-fit": "^0.11.0",
22 "@xterm/xterm": "^6.0.0",22 "@xterm/xterm": "^6.0.0",
23 "class-variance-authority": "^0.7.1",23 "class-variance-authority": "^0.7.1",
24 "effect": "4.0.0-beta.10",24 "effect": "catalog:",
25 "highlight.js": "^11.11.1",25 "highlight.js": "^11.11.1",
26 "lucide-react": "^0.564.0",26 "lucide-react": "^0.564.0",
27 "react": "^19.0.0",27 "react": "^19.0.0",
...@@ -32,6 +32,7 @@...@@ -32,6 +32,7 @@
32 "tailwind-merge": "^3.4.0"32 "tailwind-merge": "^3.4.0"
33 },33 },
34 "devDependencies": {34 "devDependencies": {
35 "@effect/language-service": "catalog:",
35 "@tailwindcss/vite": "^4.0.0",36 "@tailwindcss/vite": "^4.0.0",
36 "@tanstack/router-plugin": "^1.161.0",37 "@tanstack/router-plugin": "^1.161.0",
37 "@types/react": "^19.0.0",38 "@types/react": "^19.0.0",
apps/web/src/components/DiffWorkerPoolProvider.tsx+2-2
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1import { WorkerPoolContextProvider } from "@pierre/diffs/react";1import { WorkerPoolContextProvider } from "@pierre/diffs/react";
2import DiffsWorker from "@pierre/diffs/worker/worker.js?worker";
2import { useMemo, type ReactNode } from "react";3import { useMemo, type ReactNode } from "react";
34
4export function DiffWorkerPoolProvider({ children }: { children?: ReactNode }) {5export function DiffWorkerPoolProvider({ children }: { children?: ReactNode }) {
...@@ -11,8 +12,7 @@ export function DiffWorkerPoolProvider({ children }: { children?: ReactNode }) {...@@ -11,8 +12,7 @@ export function DiffWorkerPoolProvider({ children }: { children?: ReactNode }) {
11 return (12 return (
12 <WorkerPoolContextProvider13 <WorkerPoolContextProvider
13 poolOptions={{14 poolOptions={{
14 workerFactory: () =>15 workerFactory: () => new DiffsWorker(),
15 new Worker(new URL("../workers/diffs.worker.ts", import.meta.url), { type: "module" }),
16 poolSize: workerPoolSize,16 poolSize: workerPoolSize,
17 totalASTLRUCacheSize: 240,17 totalASTLRUCacheSize: 240,
18 }}18 }}
apps/web/src/workers/diffs.worker.ts deleted-1
...@@ -1 +0,0 @@
1import "@pierre/diffs/worker/worker.js";
apps/web/tsconfig.json+13-1
...@@ -7,7 +7,19 @@...@@ -7,7 +7,19 @@
7 "types": ["vite/client"],7 "types": ["vite/client"],
8 "paths": {8 "paths": {
9 "~/*": ["./src/*"]9 "~/*": ["./src/*"]
10 }10 },
11 "plugins": [
12 {
13 "name": "@effect/language-service",
14 "namespaceImportPackages": ["@effect/platform-node"],
15 "diagnosticSeverity": {
16 "importFromBarrel": "error",
17 "anyUnknownInErrorContext": "warning",
18 "instanceOfSchema": "warning",
19 "deterministicKeys": "warning"
20 }
21 }
22 ]
11 },23 },
12 "include": ["src", "vite.config.ts"]24 "include": ["src", "vite.config.ts"]
13}25}
bun.lock+20-12
...@@ -35,17 +35,17 @@...@@ -35,17 +35,17 @@
35 "t3": "./dist/index.mjs",35 "t3": "./dist/index.mjs",
36 },36 },
37 "dependencies": {37 "dependencies": {
38 "@effect/platform-node": "4.0.0-beta.10",38 "@effect/platform-node": "catalog:",
39 "@effect/sql-sqlite-bun": "4.0.0-beta.10",39 "@effect/sql-sqlite-bun": "catalog:",
40 "@pierre/diffs": "^1.1.0-beta.16",40 "@pierre/diffs": "^1.1.0-beta.16",
41 "effect": "4.0.0-beta.10",41 "effect": "catalog:",
42 "node-pty": "^1.1.0",42 "node-pty": "^1.1.0",
43 "open": "^10.1.0",43 "open": "^10.1.0",
44 "ws": "^8.18.0",44 "ws": "^8.18.0",
45 },45 },
46 "devDependencies": {46 "devDependencies": {
47 "@effect/language-service": "0.75.1",47 "@effect/language-service": "0.75.1",
48 "@effect/vitest": "4.0.0-beta.10",48 "@effect/vitest": "catalog:",
49 "@t3tools/contracts": "workspace:*",49 "@t3tools/contracts": "workspace:*",
50 "@t3tools/web": "workspace:*",50 "@t3tools/web": "workspace:*",
51 "@types/bun": "^1.3.9",51 "@types/bun": "^1.3.9",
...@@ -70,7 +70,7 @@...@@ -70,7 +70,7 @@
70 "@xterm/addon-fit": "^0.11.0",70 "@xterm/addon-fit": "^0.11.0",
71 "@xterm/xterm": "^6.0.0",71 "@xterm/xterm": "^6.0.0",
72 "class-variance-authority": "^0.7.1",72 "class-variance-authority": "^0.7.1",
73 "effect": "4.0.0-beta.10",73 "effect": "catalog:",
74 "highlight.js": "^11.11.1",74 "highlight.js": "^11.11.1",
75 "lucide-react": "^0.564.0",75 "lucide-react": "^0.564.0",
76 "react": "^19.0.0",76 "react": "^19.0.0",
...@@ -81,6 +81,7 @@...@@ -81,6 +81,7 @@
81 "tailwind-merge": "^3.4.0",81 "tailwind-merge": "^3.4.0",
82 },82 },
83 "devDependencies": {83 "devDependencies": {
84 "@effect/language-service": "catalog:",
84 "@tailwindcss/vite": "^4.0.0",85 "@tailwindcss/vite": "^4.0.0",
85 "@tanstack/router-plugin": "^1.161.0",86 "@tanstack/router-plugin": "^1.161.0",
86 "@types/react": "^19.0.0",87 "@types/react": "^19.0.0",
...@@ -96,16 +97,23 @@...@@ -96,16 +97,23 @@
96 "name": "@t3tools/contracts",97 "name": "@t3tools/contracts",
97 "version": "0.0.0",98 "version": "0.0.0",
98 "dependencies": {99 "dependencies": {
99 "effect": "4.0.0-beta.10",100 "effect": "catalog:",
100 },101 },
101 "devDependencies": {102 "devDependencies": {
102 "@effect/language-service": "0.75.1",103 "@effect/language-service": "0.75.1",
103 "@effect/vitest": "4.0.0-beta.10",104 "@effect/vitest": "catalog:",
104 "tsdown": "^0.20.3",105 "tsdown": "^0.20.3",
105 "typescript": "^5.7.3",106 "typescript": "^5.7.3",
106 },107 },
107 },108 },
108 },109 },
110 "catalog": {
111 "@effect/language-service": "0.75.1",
112 "@effect/platform-node": "https://pkg.pr.new/Effect-TS/effect-smol/@effect/platform-node@f591738",
113 "@effect/sql-sqlite-bun": "https://pkg.pr.new/Effect-TS/effect-smol/@effect/sql-sqlite-bun@f591738",
114 "@effect/vitest": "https://pkg.pr.new/Effect-TS/effect-smol/@effect/vitest@f591738",
115 "effect": "https://pkg.pr.new/Effect-TS/effect-smol/effect@f591738",
116 },
109 "packages": {117 "packages": {
110 "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],118 "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
111119
...@@ -157,13 +165,13 @@...@@ -157,13 +165,13 @@
157165
158 "@effect/language-service": ["@effect/language-service@0.75.1", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-g9xD2tAQgRFpYC2YgpZq02VeSL5fBbFJ0B/g1o+14NuNmwtaYJc7SjiLWAA9eyhJHosNrn6h1Ye+Kx6j5mN0AA=="],166 "@effect/language-service": ["@effect/language-service@0.75.1", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-g9xD2tAQgRFpYC2YgpZq02VeSL5fBbFJ0B/g1o+14NuNmwtaYJc7SjiLWAA9eyhJHosNrn6h1Ye+Kx6j5mN0AA=="],
159167
160 "@effect/platform-node": ["@effect/platform-node@4.0.0-beta.10", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-beta.10", "mime": "^4.1.0", "undici": "^7.20.0" }, "peerDependencies": { "effect": "^4.0.0-beta.10", "ioredis": "^5.7.0" } }, "sha512-ikZ/uyos5okYAHE+wWOwZkHgriFUTY3aY8Kdqbj/ro8xw0z9YXWN4fbyQAZUWkBcieDKS36N+QfHRMPG4fEWRA=="],168 "@effect/platform-node": ["@effect/platform-node@https://pkg.pr.new/Effect-TS/effect-smol/@effect/platform-node@f591738", { "dependencies": { "@effect/platform-node-shared": "https://pkg.pr.new/Effect-TS/effect-smol/@effect/platform-node-shared@f59173885df0eb490dc2e6619ae89467feac0413", "mime": "^4.1.0", "undici": "^7.20.0" }, "peerDependencies": { "effect": "^4.0.0-beta.19", "ioredis": "^5.7.0" } }],
161169
162 "@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0-beta.10", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.19.0" }, "peerDependencies": { "effect": "^4.0.0-beta.10" } }, "sha512-gBSeOG2bfchXFdgdi5oipHKynOzSd7BlzTy5QcjFOoCUTtLP9igqohPB9hTkV3a6seWE2JJqsgsQ79VuLb7fAg=="],170 "@effect/platform-node-shared": ["@effect/platform-node-shared@https://pkg.pr.new/Effect-TS/effect-smol/@effect/platform-node-shared@f59173885df0eb490dc2e6619ae89467feac0413", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.19.0" }, "peerDependencies": { "effect": "^4.0.0-beta.19" } }],
163171
164 "@effect/sql-sqlite-bun": ["@effect/sql-sqlite-bun@4.0.0-beta.10", "", { "peerDependencies": { "effect": "^4.0.0-beta.10" } }, "sha512-LY1jNu2uqGYsJCVjVU73nzcZ+kAYBDsG7O8x5ddmkIXj10RmnIhjYMStWBa0NZOFg4Hsl+CadxtsDy3edaOVeA=="],172 "@effect/sql-sqlite-bun": ["@effect/sql-sqlite-bun@https://pkg.pr.new/Effect-TS/effect-smol/@effect/sql-sqlite-bun@f591738", { "peerDependencies": { "effect": "^4.0.0-beta.19" } }],
165173
166 "@effect/vitest": ["@effect/vitest@4.0.0-beta.10", "", { "peerDependencies": { "effect": "^4.0.0-beta.10", "vitest": "^3.0.0" } }, "sha512-WmE8vCNw+eDoAq9CwwymrAdIBw1f+8hDP3AUF7jyC7/Cj/kyAYOFwkbtKmDXIFaTlf7G/2fmulEXeCO5W8j0kQ=="],174 "@effect/vitest": ["@effect/vitest@https://pkg.pr.new/Effect-TS/effect-smol/@effect/vitest@f591738", { "peerDependencies": { "effect": "^4.0.0-beta.19", "vitest": "^3.0.0" } }],
167175
168 "@electron/get": ["@electron/get@2.0.3", "", { "dependencies": { "debug": "^4.1.1", "env-paths": "^2.2.0", "fs-extra": "^8.1.0", "got": "^11.8.5", "progress": "^2.0.3", "semver": "^6.2.0", "sumchecker": "^3.0.1" }, "optionalDependencies": { "global-agent": "^3.0.0" } }, "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ=="],176 "@electron/get": ["@electron/get@2.0.3", "", { "dependencies": { "debug": "^4.1.1", "env-paths": "^2.2.0", "fs-extra": "^8.1.0", "got": "^11.8.5", "progress": "^2.0.3", "semver": "^6.2.0", "sumchecker": "^3.0.1" }, "optionalDependencies": { "global-agent": "^3.0.0" } }, "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ=="],
169177
...@@ -729,7 +737,7 @@...@@ -729,7 +737,7 @@
729737
730 "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],738 "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
731739
732 "effect": ["effect@4.0.0-beta.10", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.5.3", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.8", "multipasta": "^0.2.7", "toml": "^3.0.0", "uuid": "^13.0.0", "yaml": "^2.8.2" } }, "sha512-UvxWmT0uzWZqNmQODfW7y1zb0LMlH3xtSn/UESpck2Wvv6SogxibYPWKKNcUsRMm5wwprOoW2IwItYLQyZpe8Q=="],740 "effect": ["effect@https://pkg.pr.new/Effect-TS/effect-smol/effect@f591738", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.5.3", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.8", "multipasta": "^0.2.7", "toml": "^3.0.0", "uuid": "^13.0.0", "yaml": "^2.8.2" } }],
733741
734 "electron": ["electron@40.6.0", "", { "dependencies": { "@electron/get": "^2.0.0", "@types/node": "^24.9.0", "extract-zip": "^2.0.1" }, "bin": { "electron": "cli.js" } }, "sha512-ett8W+yOFGDuM0vhJMamYSkrbV3LoaffzJd9GfjI96zRAxyrNqUSKqBpf/WGbQCweDxX2pkUCUfrv4wwKpsFZA=="],742 "electron": ["electron@40.6.0", "", { "dependencies": { "@electron/get": "^2.0.0", "@types/node": "^24.9.0", "extract-zip": "^2.0.1" }, "bin": { "electron": "cli.js" } }, "sha512-ett8W+yOFGDuM0vhJMamYSkrbV3LoaffzJd9GfjI96zRAxyrNqUSKqBpf/WGbQCweDxX2pkUCUfrv4wwKpsFZA=="],
735743
package.json+13-4
...@@ -1,10 +1,19 @@...@@ -1,10 +1,19 @@
1{1{
2 "name": "@t3tools/monorepo",2 "name": "@t3tools/monorepo",
3 "private": true,3 "private": true,
4 "workspaces": [4 "workspaces": {
5 "apps/*",5 "packages": [
6 "packages/*"6 "apps/*",
7 ],7 "packages/*"
8 ],
9 "catalog": {
10 "effect": "https://pkg.pr.new/Effect-TS/effect-smol/effect@f591738",
11 "@effect/platform-node": "https://pkg.pr.new/Effect-TS/effect-smol/@effect/platform-node@f591738",
12 "@effect/sql-sqlite-bun": "https://pkg.pr.new/Effect-TS/effect-smol/@effect/sql-sqlite-bun@f591738",
13 "@effect/vitest": "https://pkg.pr.new/Effect-TS/effect-smol/@effect/vitest@f591738",
14 "@effect/language-service": "0.75.1"
15 }
16 },
8 "scripts": {17 "scripts": {
9 "dev": "bun run scripts/dev-runner.mjs dev",18 "dev": "bun run scripts/dev-runner.mjs dev",
10 "dev:server": "bun run scripts/dev-runner.mjs dev:server",19 "dev:server": "bun run scripts/dev-runner.mjs dev:server",
packages/contracts/package.json+2-2
...@@ -24,11 +24,11 @@...@@ -24,11 +24,11 @@
24 "test": "vitest run"24 "test": "vitest run"
25 },25 },
26 "dependencies": {26 "dependencies": {
27 "effect": "4.0.0-beta.10"27 "effect": "catalog:"
28 },28 },
29 "devDependencies": {29 "devDependencies": {
30 "@effect/language-service": "0.75.1",30 "@effect/language-service": "0.75.1",
31 "@effect/vitest": "4.0.0-beta.10",31 "@effect/vitest": "catalog:",
32 "tsdown": "^0.20.3",32 "tsdown": "^0.20.3",
33 "typescript": "^5.7.3"33 "typescript": "^5.7.3"
34 }34 }
packages/contracts/tsconfig.json+8-1
...@@ -3,7 +3,14 @@...@@ -3,7 +3,14 @@
3 "compilerOptions": {3 "compilerOptions": {
4 "plugins": [4 "plugins": [
5 {5 {
6 "name": "@effect/language-service"6 "name": "@effect/language-service",
7 "namespaceImportPackages": ["@effect/platform-node"],
8 "diagnosticSeverity": {
9 "importFromBarrel": "error",
10 "anyUnknownInErrorContext": "warning",
11 "instanceOfSchema": "warning",
12 "deterministicKeys": "warning"
13 }
7 }14 }
8 ]15 ]
9 },16 },
scripts/build-desktop-dmg.mjs+43-1
...@@ -206,6 +206,42 @@ async function validateBundledClientAssets(clientDir) {...@@ -206,6 +206,42 @@ async function validateBundledClientAssets(clientDir) {
206 }206 }
207}207}
208208
209function readWorkspaceCatalog(rootPackageJson) {
210 const workspaces = rootPackageJson?.workspaces;
211 if (!workspaces || typeof workspaces !== "object" || Array.isArray(workspaces)) {
212 return {};
213 }
214
215 const catalog = workspaces.catalog;
216 if (!catalog || typeof catalog !== "object" || Array.isArray(catalog)) {
217 return {};
218 }
219
220 return catalog;
221}
222
223function resolveCatalogDependencies(dependencies, catalog, dependencySourceLabel) {
224 return Object.fromEntries(
225 Object.entries(dependencies).map(([dependencyName, spec]) => {
226 if (typeof spec !== "string" || !spec.startsWith("catalog:")) {
227 return [dependencyName, spec];
228 }
229
230 const catalogKey = spec.slice("catalog:".length).trim();
231 const lookupKey = catalogKey.length > 0 ? catalogKey : dependencyName;
232 const resolvedSpec = catalog[lookupKey];
233 if (typeof resolvedSpec !== "string" || resolvedSpec.length === 0) {
234 fail(
235 `Unable to resolve '${spec}' for ${dependencySourceLabel} dependency '${dependencyName}'. ` +
236 `Expected key '${lookupKey}' in root workspace catalog.`,
237 );
238 }
239
240 return [dependencyName, resolvedSpec];
241 }),
242 );
243}
244
209async function main() {245async function main() {
210 const options = parseArgs(process.argv.slice(2));246 const options = parseArgs(process.argv.slice(2));
211 if (options.help) {247 if (options.help) {
...@@ -217,6 +253,7 @@ async function main() {...@@ -217,6 +253,7 @@ async function main() {
217 fail("This script only builds macOS .dmg artifacts and must run on macOS.");253 fail("This script only builds macOS .dmg artifacts and must run on macOS.");
218 }254 }
219255
256 const rootPkg = await readJson(path.join(repoRoot, "package.json"));
220 const desktopPkg = await readJson(path.join(repoRoot, "apps/desktop/package.json"));257 const desktopPkg = await readJson(path.join(repoRoot, "apps/desktop/package.json"));
221 const serverPkg = await readJson(path.join(repoRoot, "apps/server/package.json"));258 const serverPkg = await readJson(path.join(repoRoot, "apps/server/package.json"));
222259
...@@ -229,6 +266,11 @@ async function main() {...@@ -229,6 +266,11 @@ async function main() {
229 if (!dependencies || Object.keys(dependencies).length === 0) {266 if (!dependencies || Object.keys(dependencies).length === 0) {
230 fail("Could not resolve production dependencies from apps/server/package.json.");267 fail("Could not resolve production dependencies from apps/server/package.json.");
231 }268 }
269 const resolvedDependencies = resolveCatalogDependencies(
270 dependencies,
271 readWorkspaceCatalog(rootPkg),
272 "apps/server",
273 );
232274
233 const appVersion = options.version ?? serverPkg.version ?? "0.1.0";275 const appVersion = options.version ?? serverPkg.version ?? "0.1.0";
234 const stageRoot = path.join(os.tmpdir(), "t3code-desktop-dmg-stage");276 const stageRoot = path.join(os.tmpdir(), "t3code-desktop-dmg-stage");
...@@ -291,7 +333,7 @@ async function main() {...@@ -291,7 +333,7 @@ async function main() {
291 category: "public.app-category.developer-tools",333 category: "public.app-category.developer-tools",
292 },334 },
293 },335 },
294 dependencies,336 dependencies: resolvedDependencies,
295 devDependencies: {337 devDependencies: {
296 electron: electronVersion,338 electron: electronVersion,
297 },339 },