Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -688,7 +688,8 @@ function layerTest(input: {
layerConfiguredMcpRegistry,
layerTestStores,
...(input.serverSettingsLayer === undefined ? [] : [input.serverSettingsLayer]),
...(input.projectServiceLayer === undefined ? [] : [input.projectServiceLayer]),
input.projectServiceLayer ??
Layer.mock(ProjectService.ProjectService)({ getById: () => Effect.succeedNone }),
),
),
),
Expand Down
7 changes: 3 additions & 4 deletions apps/server/src/orchestration-v2/ProviderSessionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ export const layerWithOptions = (
| IdAllocator.IdAllocatorV2
| McpProviderSessions.McpProviderSessions
| McpSessionRegistry.McpSessionRegistry
| ProjectService.ProjectService
| ProjectionStore.ProjectionStoreV2
| ProviderEventIngestor.ProviderEventIngestorV2
| ProviderAdapterRegistry.ProviderAdapterRegistryV2
Expand All @@ -350,7 +351,7 @@ export const layerWithOptions = (
const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
/** Unreadable settings must preserve explicit browser-access opt-outs. */
const serverSettings = yield* Effect.serviceOption(ServerSettings.ServerSettingsService);
const projectService = yield* Effect.serviceOption(ProjectService.ProjectService);
const projectService = yield* ProjectService.ProjectService;
const eventSink = yield* EventSink.EventSinkV2;
const commandOutput = yield* CommandOutputHub.CommandOutputHub;
const promptSuggestionStore = yield* SubscriptionRef.make<
Expand Down Expand Up @@ -382,9 +383,7 @@ export const layerWithOptions = (
(entry) => entry.enableAgentDeviceAccess !== undefined,
);
if (browserOverridden || deviceOverridden) {
const project = Option.isSome(projectService)
? yield* projectService.value.getById(thread.projectId)
: Option.none();
const project = yield* projectService.getById(thread.projectId);
if (Option.isNone(project))
return {
browser: browserOverridden ? false : settings.enableAgentBrowserAccess,
Expand Down
121 changes: 121 additions & 0 deletions apps/server/src/orchestration-v2/runtimeLayer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@ import * as OrchestrationEventStore from "../persistence/OrchestrationEventStore
import * as ProjectEnrichmentService from "../project/ProjectEnrichmentService.ts";
import * as ProjectService from "../project/ProjectService.ts";
import * as ServerSettings from "../serverSettings.ts";
import * as AnalyticsService from "../telemetry/AnalyticsService.ts";
import * as ManagedEndpointRuntime from "../cloud/ManagedEndpointRuntime.ts";
import * as ServerSecretStore from "../auth/ServerSecretStore.ts";
import * as TextGeneration from "../textGeneration/TextGeneration.ts";
import * as ProjectCloneTracker from "../project/ProjectCloneTracker.ts";
import * as WorktreeSetupTracker from "../project/WorktreeSetupTracker.ts";
import * as ProviderRegistry from "../provider/ProviderRegistry.ts";
import * as GitVcsDriver from "../vcs/GitVcsDriver.ts";
import * as McpSessionRegistry from "../mcp/McpSessionRegistry.ts";
import * as SecretResolver from "@t3tools/secret-provider-core/server/SecretResolver";
import * as VcsStatusBroadcaster from "../vcs/VcsStatusBroadcaster.ts";
import * as McpSessionRegistryTestkit from "../mcp/McpSessionRegistry.testkit.ts";
import * as ProviderInstanceRegistry from "../provider/ProviderInstanceRegistry.ts";
import type { ProviderInstance } from "@t3tools/provider-core/server/driver";
Expand Down Expand Up @@ -9165,3 +9176,113 @@ it.layer(layerQueueResumeDisabledInstanceTest)(
);
},
);

const agentDeviceAccessProjectId = ProjectId.make("project:agent-device-access");

const layerAgentDeviceAccessProduction = (
issued: Deferred.Deferred<ReadonlySet<string> | undefined>,
) =>
RuntimeLayer.layerProduction.pipe(
Layer.provide(
Layer.mergeAll(
Layer.mock(AnalyticsService.AnalyticsService)({}),
Layer.mock(ManagedEndpointRuntime.CloudManagedEndpointRuntime)({}),
Layer.mock(TextGeneration.TextGeneration)({}),
Layer.mock(ProjectCloneTracker.ProjectCloneTracker)({}),
Layer.mock(WorktreeSetupTracker.WorktreeSetupTracker)({}),
Layer.mock(ProviderRegistry.ProviderRegistry)({}),
Layer.mock(GitVcsDriver.GitVcsDriver)({}),
Layer.mock(SecretResolver.SecretResolver)({}),
Layer.mock(VcsStatusBroadcaster.VcsStatusBroadcaster)({}),
Layer.mock(PreviewManager.PreviewManager)({}),
Layer.mock(TerminalManager.TerminalManager)({}),
Layer.mock(WorkspacePaths.WorkspacePaths)({
normalizeWorkspaceRoot: (workspaceRoot) => Effect.succeed(workspaceRoot),
}),
Layer.mock(ProjectEnrichmentService.ProjectEnrichmentService)({
getAvailable: () =>
Effect.succeed({
repositoryIdentity: null,
faviconPath: null,
repositoryIdentityResolved: false,
}),
invalidate: () => Effect.void,
}),
ServerSecretStore.layer,
ManagedPolicy.layerTest(),
ProjectStore.layer,
McpProviderSessions.layer,
Layer.effect(
McpSessionRegistry.McpSessionRegistry,
Effect.map(McpSessionRegistry.McpSessionRegistry, (registry) => ({
...registry,
issue: (request) =>
Deferred.succeed(issued, request.capabilities).pipe(
Effect.andThen(registry.issue(request)),
),
})),
).pipe(Layer.provide(McpSessionRegistryTestkit.layer)),
),
),
Layer.provide(SqlitePersistence.layerMemory),
Layer.provide(layerCheckpointStoreTest),
Layer.provide(layerServerConfig),
Layer.provide(
ServerSettings.layerTest({
enableAgentDeviceAccess: false,
projectSettingsOverrides: {
[agentDeviceAccessProjectId]: { enableAgentDeviceAccess: true },
},
}),
),
Layer.provide(layerTestProviderInstanceRegistry),
Layer.provide(layerGitWorkflowTest),
Layer.provide(layerPlatformTest),
);

it.effect("production wiring gives a thread its project's agent device access override", () =>
Effect.gen(function* () {
const issued = yield* Deferred.make<ReadonlySet<string> | undefined>();
yield* Effect.gen(function* () {
const projects = yield* ProjectService.ProjectService;
const orchestrator = yield* Orchestrator.OrchestratorV2;
const manager = yield* ProviderSessionManager.ProviderSessionManagerV2;
const threadId = ThreadId.make("thread:agent-device-access");
yield* projects.create({
commandId: CommandId.make("agent-device-access-project-create"),
projectId: agentDeviceAccessProjectId,
title: "Agent device access",
workspaceRoot: "/tmp/agent-device-access",
});
yield* orchestrator.dispatch({
type: "thread.create",
createdBy: "user",
creationSource: "web",
commandId: CommandId.make("agent-device-access-thread-create"),
threadId,
projectId: agentDeviceAccessProjectId,
title: "Agent device access",
modelSelection,
runtimeMode: "full-access",
interactionMode: "default",
branch: null,
worktreePath: null,
});
// The fixture adapter refuses to open; the credential is issued before that.
yield* manager
.open({
threadId,
providerSessionId: ProviderSessionId.make("provider-session:agent-device-access"),
modelSelection,
runtimePolicy: { runtimeMode: "full-access", interactionMode: "default", cwd: "/tmp" },
})
.pipe(Effect.exit);
}).pipe(Effect.provide(layerAgentDeviceAccessProduction(issued)));

const capabilities = yield* Deferred.await(issued);
assert.isTrue(
capabilities?.has("device"),
`the project's enableAgentDeviceAccess override must grant device tools; issued: ${[...(capabilities ?? [])].join(", ")}`,
);
}),
);
7 changes: 2 additions & 5 deletions apps/server/src/orchestration-v2/runtimeLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,6 @@ const layerAgentSessionImporterProvided = AgentSessionImporter.layer.pipe(
Layer.provide(
Layer.mergeAll(
AgentSessionScanner.layer,
layerProjectService,
layerOrchestratorProvided,
layerEventSink,
IdAllocator.layer,
Expand Down Expand Up @@ -269,7 +268,6 @@ const layerManagedProjectFoldersProvided = ManagedProjectFolders.layer.pipe(
const layerThreadLaunchProvided = ThreadLaunchService.layer.pipe(
Layer.provide(
Layer.mergeAll(
layerProjectService,
layerProjectSetupScriptRunner,
layerManagedProjectFoldersProvided,
layerThreadManagementProvided,
Expand All @@ -294,7 +292,6 @@ const layerScheduledTaskProvided = ScheduledTaskService.layer.pipe(
const layerScratchThreadPromotionProvided = ScratchThreadPromotion.layer.pipe(
Layer.provide(
Layer.mergeAll(
layerProjectService,
layerManagedProjectFoldersProvided,
layerThreadManagementProvided,
layerScheduledTaskProvided,
Expand Down Expand Up @@ -372,8 +369,7 @@ export const layerProduction = Layer.mergeAll(
WorktreeBranchSync.layer.pipe(
Layer.provide(Layer.merge(layerThreadManagementProvided, ProjectionStore.layer)),
),
layer.pipe(Layer.provide(layerProjectService)),
layerProjectService,
layer,
layerManagedProjectFoldersProvided,
layerThreadLaunchProvided,
layerScheduledTaskProvided,
Expand All @@ -397,5 +393,6 @@ export const layerProduction = Layer.mergeAll(
).pipe(
Layer.provide(ResourceCleanupService.layer.pipe(Layer.provide(layerScheduledTaskProvided))),
Layer.provide(Scheduler.layer),
Layer.provideMerge(layerProjectService),
Layer.provideMerge(layerEventInfrastructure),
);
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
import * as Orchestrator from "../Orchestrator.ts";
import * as ProjectionStore from "../ProjectionStore.ts";
import * as ProjectStore from "../ProjectStore.ts";
import * as ProjectService from "../../project/ProjectService.ts";
import * as ProviderAdapterRegistry from "../ProviderAdapterRegistry.ts";
import * as ProviderAuthService from "../../provider/ProviderAuthService.ts";
import * as ProviderContinuationRequests from "@t3tools/provider-core/server/ProviderContinuationRequests";
Expand Down Expand Up @@ -394,6 +395,7 @@ export function layerWithRegistry<Error>(
McpSessionRegistryTestkit.layer,
layerProviderEventIngestorProvided,
layerStores,
Layer.mock(ProjectService.ProjectService)({ getById: () => Effect.succeedNone }),
),
),
);
Expand Down
Loading