Unified · Split
| @@ -1,14 +1,52 @@ | |||
| 1 | import { assert, describe, expect, it, vi } from "@effect/vitest"; | 1 | import { assert, describe, expect, it, vi } from "@effect/vitest"; |
| 2 | import * as DateTime from "effect/DateTime"; | ||
| 2 | import * as Effect from "effect/Effect"; | 3 | import * as Effect from "effect/Effect"; |
| 3 | import * as Layer from "effect/Layer"; | 4 | import * as Layer from "effect/Layer"; |
| 5 | import * as Option from "effect/Option"; | ||
| 4 | 6 | ||
| 5 | import { GitCommandError, VcsRepositoryDetectionError } from "@t3tools/contracts"; | 7 | import { GitCommandError, VcsRepositoryDetectionError } from "@t3tools/contracts"; |
| 6 | 8 | ||
| 7 | import * as GitManager from "./GitManager.ts"; | 9 | import * as GitManager from "./GitManager.ts"; |
| 8 | import * as GitWorkflowService from "./GitWorkflowService.ts"; | 10 | import * as GitWorkflowService from "./GitWorkflowService.ts"; |
| 9 | import * as GitVcsDriver from "../vcs/GitVcsDriver.ts"; | 11 | import * as GitVcsDriver from "../vcs/GitVcsDriver.ts"; |
| 12 | import * as VcsDriver from "../vcs/VcsDriver.ts"; | ||
| 10 | import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts"; | 13 | import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts"; |
| 11 | 14 | ||
| 15 | const unusedVcsDriver = VcsDriver.VcsDriver.of({ | ||
| 16 | capabilities: { | ||
| 17 | kind: "git", | ||
| 18 | supportsWorktrees: true, | ||
| 19 | supportsBookmarks: false, | ||
| 20 | supportsAtomicSnapshot: false, | ||
| 21 | supportsPushDefaultRemote: true, | ||
| 22 | ignoreClassifier: "native", | ||
| 23 | }, | ||
| 24 | execute: () => Effect.die("unused VCS driver execute"), | ||
| 25 | detectRepository: () => Effect.die("unused VCS repository detection"), | ||
| 26 | isInsideWorkTree: () => Effect.die("unused VCS worktree lookup"), | ||
| 27 | listWorkspaceFiles: () => Effect.die("unused VCS workspace listing"), | ||
| 28 | listRemotes: () => Effect.die("unused VCS remote listing"), | ||
| 29 | filterIgnoredPaths: () => Effect.die("unused VCS ignore classification"), | ||
| 30 | initRepository: () => Effect.die("unused VCS repository initialization"), | ||
| 31 | }); | ||
| 32 | |||
| 33 | function gitHandle(cwd: string): VcsDriverRegistry.VcsDriverHandle { | ||
| 34 | return { | ||
| 35 | kind: "git", | ||
| 36 | repository: { | ||
| 37 | kind: "git", | ||
| 38 | rootPath: cwd, | ||
| 39 | metadataPath: `${cwd}/.git`, | ||
| 40 | freshness: { | ||
| 41 | source: "live-local", | ||
| 42 | observedAt: DateTime.makeUnsafe("2026-08-30T00:00:00.000Z"), | ||
| 43 | expiresAt: Option.none(), | ||
| 44 | }, | ||
| 45 | }, | ||
| 46 | driver: unusedVcsDriver, | ||
| 47 | }; | ||
| 48 | } | ||
| 49 | |||
| 12 | function makeLayer(input: { | 50 | function makeLayer(input: { |
| 13 | readonly detect: VcsDriverRegistry.VcsDriverRegistry["Service"]["detect"]; | 51 | readonly detect: VcsDriverRegistry.VcsDriverRegistry["Service"]["detect"]; |
| 14 | readonly resolve?: VcsDriverRegistry.VcsDriverRegistry["Service"]["resolve"]; | 52 | readonly resolve?: VcsDriverRegistry.VcsDriverRegistry["Service"]["resolve"]; |
| @@ -221,7 +259,7 @@ describe("GitWorkflowService", () => { · 206 unmodified lines | |||
| 221 | Effect.provide( | 259 | Effect.provide( |
| 222 | makeLayer({ | 260 | makeLayer({ |
| 223 | detect: () => Effect.succeed(null), | 261 | detect: () => Effect.succeed(null), |
| 224 | resolve: () => Effect.succeed({ kind: "git" } as VcsDriverRegistry.VcsDriverHandle), | 262 | resolve: ({ cwd }) => Effect.succeed(gitHandle(cwd)), |
| 225 | execute: (input) => Effect.fail(input.cwd === "/detached" ? detached : corrupt), | 263 | execute: (input) => Effect.fail(input.cwd === "/detached" ? detached : corrupt), |
| 226 | }), | 264 | }), |
| 227 | ), | 265 | ), |
| @@ -202,13 +202,13 @@ function threadLaunchPolicyFailure( · 201 unmodified lines | |||
| 202 | if (isRuntimeModeCeilingError(cause)) { | 202 | if (isRuntimeModeCeilingError(cause)) { |
| 203 | return failure( | 203 | return failure( |
| 204 | "runtime_mode_escalation_denied", | 204 | "runtime_mode_escalation_denied", |
| 205 | `Child runtime mode ${cause.targetMode} is broader than the caller ceiling ${cause.capturedCallerMode}.`, | 205 | `Child runtime mode ${cause.targetMode} exceeds the caller ceiling (captured ${cause.capturedCallerMode}; current ${cause.currentCallerMode}).`, |
| 206 | ); | 206 | ); |
| 207 | } | 207 | } |
| 208 | if (isInteractionModeCeilingError(cause)) { | 208 | if (isInteractionModeCeilingError(cause)) { |
| 209 | return failure( | 209 | return failure( |
| 210 | "interaction_mode_escalation_denied", | 210 | "interaction_mode_escalation_denied", |
| 211 | `Child interaction mode ${cause.targetMode} is broader than the caller ceiling ${cause.capturedCallerMode}.`, | 211 | `Child interaction mode ${cause.targetMode} exceeds the caller ceiling (captured ${cause.capturedCallerMode}; current ${cause.currentCallerMode}).`, |
| 212 | ); | 212 | ); |
| 213 | } | 213 | } |
| 214 | return null; | 214 | return null; |
| @@ -636,12 +636,19 @@ describe("orchestrator MCP toolkit", () => { · 635 unmodified lines | |||
| 636 | ...actual, | 636 | ...actual, |
| 637 | withProjectCreationAdmission: (input, effect) => | 637 | withProjectCreationAdmission: (input, effect) => |
| 638 | actual.withProjectCreationAdmission(input, (receipt) => | 638 | actual.withProjectCreationAdmission(input, (receipt) => |
| 639 | input.commandId === deletionRaceCommandId && Option.isNone(receipt) | 639 | Effect.gen(function* () { |
| 640 | ? Deferred.succeed(creationAdmissionEntered, undefined).pipe( | 640 | if (input.commandId === deletionRaceCommandId && Option.isNone(receipt)) { |
| 641 | Effect.andThen(Deferred.await(allowCreationAdmission)), | 641 | yield* Deferred.succeed(creationAdmissionEntered, undefined); |
| 642 | Effect.andThen(effect(receipt)), | 642 | yield* Deferred.await(allowCreationAdmission); |
| 643 | ) | 643 | } |
| 644 | : effect(receipt), | 644 | const gate = launchReceiptReadGates.get(input.commandId); |
| 645 | if (gate !== undefined) { | ||
| 646 | yield* Deferred.succeed(gate.entered, undefined); | ||
| 647 | yield* Deferred.await(gate.release); | ||
| 648 | launchReceiptReadGates.delete(input.commandId); | ||
| 649 | } | ||
| 650 | return yield* effect(receipt); | ||
| 651 | }), | ||
| 645 | ), | 652 | ), |
| 646 | }); | 653 | }); |
| 647 | }), | 654 | }), |
| @@ -2287,6 +2294,8 @@ describe("orchestrator MCP toolkit", () => { · 1639 unmodified lines | |||
| 2287 | const preCreateCall = yield* Fiber.join(preCreateFiber); | 2294 | const preCreateCall = yield* Fiber.join(preCreateFiber); |
| 2288 | expect(preCreateCall.structuredContent).toMatchObject({ | 2295 | expect(preCreateCall.structuredContent).toMatchObject({ |
| 2289 | code: "interaction_mode_escalation_denied", | 2296 | code: "interaction_mode_escalation_denied", |
| 2297 | message: | ||
| 2298 | "Child interaction mode default exceeds the caller ceiling (captured default; current plan).", | ||
| 2290 | }); | 2299 | }); |
| 2291 | const preCreateThreadId = ThreadId.make( | 2300 | const preCreateThreadId = ThreadId.make( |
| 2292 | `thread:mcp:mcp-provider-session-parent:${preCreateKey}:0`, | 2301 | `thread:mcp:mcp-provider-session-parent:${preCreateKey}:0`, |
| @@ -2339,6 +2348,8 @@ describe("orchestrator MCP toolkit", () => { · 46 unmodified lines | |||
| 2339 | const betweenCreateAndMessageCall = yield* Fiber.join(betweenCreateAndMessageFiber); | 2348 | const betweenCreateAndMessageCall = yield* Fiber.join(betweenCreateAndMessageFiber); |
| 2340 | expect(betweenCreateAndMessageCall.structuredContent).toMatchObject({ | 2349 | expect(betweenCreateAndMessageCall.structuredContent).toMatchObject({ |
| 2341 | code: "runtime_mode_escalation_denied", | 2350 | code: "runtime_mode_escalation_denied", |
| 2351 | message: | ||
| 2352 | "Child runtime mode full-access exceeds the caller ceiling (captured full-access; current approval-required).", | ||
| 2342 | }); | 2353 | }); |
| 2343 | const partialProjection = yield* orchestrator.getThreadProjection( | 2354 | const partialProjection = yield* orchestrator.getThreadProjection( |
| 2344 | betweenCreateAndMessageThreadId, | 2355 | betweenCreateAndMessageThreadId, |