| ... | @@ -88,6 +88,13 @@ import * as Socket from "effect/unstable/socket/Socket"; | ... | @@ -88,6 +88,13 @@ import * as Socket from "effect/unstable/socket/Socket"; |
| 88 | import { vi } from "vite-plus/test"; | 88 | import { vi } from "vite-plus/test"; |
| 89 | | 89 | |
| 90 | const TEST_EPOCH = DateTime.makeUnsafe("1970-01-01T00:00:00.000Z"); | 90 | const TEST_EPOCH = DateTime.makeUnsafe("1970-01-01T00:00:00.000Z"); |
| | 91 | const SUCCESSFUL_GIT_EXECUTION = { |
| | 92 | exitCode: ChildProcessSpawner.ExitCode(0), |
| | 93 | stdout: "", |
| | 94 | stderr: "", |
| | 95 | stdoutTruncated: false, |
| | 96 | stderrTruncated: false, |
| | 97 | }; |
| 91 | const decodeTransferThreadSnapshot = Schema.decodeUnknownEffect( | 98 | const decodeTransferThreadSnapshot = Schema.decodeUnknownEffect( |
| 92 | Schema.fromJsonString(OrchestrationThreadDetailSnapshot), | 99 | Schema.fromJsonString(OrchestrationThreadDetailSnapshot), |
| 93 | ); | 100 | ); |
| ... | @@ -10558,7 +10565,11 @@ it.layer(NodeServices.layer)("server router seam", (it) => { | ... | @@ -10558,7 +10565,11 @@ it.layer(NodeServices.layer)("server router seam", (it) => { |
| 10558 | | 10565 | |
| 10559 | yield* buildAppUnderTest({ | 10566 | yield* buildAppUnderTest({ |
| 10560 | layers: { | 10567 | layers: { |
| | 10568 | vcsDriver: { |
| | 10569 | isInsideWorkTree: () => Effect.succeed(true), |
| | 10570 | }, |
| 10561 | gitVcsDriver: { | 10571 | gitVcsDriver: { |
| | 10572 | execute: () => Effect.succeed(SUCCESSFUL_GIT_EXECUTION), |
| 10562 | remoteExists, | 10573 | remoteExists, |
| 10563 | fetchRemote, | 10574 | fetchRemote, |
| 10564 | remoteBranchExists, | 10575 | remoteBranchExists, |
| ... | @@ -10722,7 +10733,11 @@ it.layer(NodeServices.layer)("server router seam", (it) => { | ... | @@ -10722,7 +10733,11 @@ it.layer(NodeServices.layer)("server router seam", (it) => { |
| 10722 | | 10733 | |
| 10723 | yield* buildAppUnderTest({ | 10734 | yield* buildAppUnderTest({ |
| 10724 | layers: { | 10735 | layers: { |
| | 10736 | vcsDriver: { |
| | 10737 | isInsideWorkTree: () => Effect.succeed(true), |
| | 10738 | }, |
| 10725 | gitVcsDriver: { | 10739 | gitVcsDriver: { |
| | 10740 | execute: () => Effect.succeed(SUCCESSFUL_GIT_EXECUTION), |
| 10726 | remoteExists, | 10741 | remoteExists, |
| 10727 | fetchRemote, | 10742 | fetchRemote, |
| 10728 | remoteBranchExists, | 10743 | remoteBranchExists, |
| ... | @@ -10804,6 +10819,167 @@ it.layer(NodeServices.layer)("server router seam", (it) => { | ... | @@ -10804,6 +10819,167 @@ it.layer(NodeServices.layer)("server router seam", (it) => { |
| 10804 | }).pipe(Effect.provide(NodeHttpServer.layerTest)), | 10819 | }).pipe(Effect.provide(NodeHttpServer.layerTest)), |
| 10805 | ); | 10820 | ); |
| 10806 | | 10821 | |
| | 10822 | it.effect("falls back to the project checkout when worktree mode targets a non-repository", () => |
| | 10823 | Effect.gen(function* () { |
| | 10824 | const dispatchedCommands: Array<OrchestrationCommand> = []; |
| | 10825 | const createWorktree = vi.fn( |
| | 10826 | (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["createWorktree"]>[0]) => |
| | 10827 | Effect.die(new Error("createWorktree must not run for a non-repository")), |
| | 10828 | ); |
| | 10829 | |
| | 10830 | yield* buildAppUnderTest({ |
| | 10831 | layers: { |
| | 10832 | gitVcsDriver: { |
| | 10833 | execute: () => Effect.succeed(SUCCESSFUL_GIT_EXECUTION), |
| | 10834 | createWorktree, |
| | 10835 | }, |
| | 10836 | orchestrationEngine: { |
| | 10837 | dispatch: (command) => |
| | 10838 | Effect.sync(() => { |
| | 10839 | dispatchedCommands.push(command); |
| | 10840 | return { sequence: dispatchedCommands.length }; |
| | 10841 | }), |
| | 10842 | readEvents: () => Stream.empty, |
| | 10843 | }, |
| | 10844 | }, |
| | 10845 | }); |
| | 10846 | |
| | 10847 | const createdAt = "2026-01-01T00:00:00.000Z"; |
| | 10848 | const wsUrl = yield* getWsServerUrl("/ws"); |
| | 10849 | const response = yield* Effect.scoped( |
| | 10850 | withWsRpcClient(wsUrl, (client) => |
| | 10851 | client[ORCHESTRATION_WS_METHODS.dispatchCommand]({ |
| | 10852 | type: "thread.turn.start", |
| | 10853 | commandId: CommandId.make("cmd-bootstrap-turn-start-non-repo"), |
| | 10854 | threadId: ThreadId.make("thread-bootstrap-non-repo"), |
| | 10855 | message: { |
| | 10856 | messageId: MessageId.make("msg-bootstrap-non-repo"), |
| | 10857 | role: "user", |
| | 10858 | text: "hello", |
| | 10859 | attachments: [], |
| | 10860 | }, |
| | 10861 | modelSelection: defaultModelSelection, |
| | 10862 | runtimeMode: "full-access", |
| | 10863 | interactionMode: "default", |
| | 10864 | bootstrap: { |
| | 10865 | createThread: { |
| | 10866 | projectId: defaultProjectId, |
| | 10867 | title: "Bootstrap Thread", |
| | 10868 | modelSelection: defaultModelSelection, |
| | 10869 | runtimeMode: "full-access", |
| | 10870 | interactionMode: "default", |
| | 10871 | branch: null, |
| | 10872 | worktreePath: null, |
| | 10873 | createdAt, |
| | 10874 | }, |
| | 10875 | prepareWorktree: { |
| | 10876 | projectCwd: "/tmp/project", |
| | 10877 | baseBranch: "main", |
| | 10878 | branch: "t3code/bootstrap-refName", |
| | 10879 | }, |
| | 10880 | runSetupScript: true, |
| | 10881 | }, |
| | 10882 | createdAt, |
| | 10883 | }), |
| | 10884 | ), |
| | 10885 | ); |
| | 10886 | |
| | 10887 | assert.equal(response.sequence, 2); |
| | 10888 | assert.equal(createWorktree.mock.calls.length, 0); |
| | 10889 | assert.deepEqual( |
| | 10890 | dispatchedCommands.map((command) => command.type), |
| | 10891 | ["thread.create", "thread.turn.start"], |
| | 10892 | ); |
| | 10893 | const finalCommand = dispatchedCommands[1]; |
| | 10894 | assertTrue(finalCommand?.type === "thread.turn.start"); |
| | 10895 | if (finalCommand?.type === "thread.turn.start") { |
| | 10896 | assert.equal(finalCommand.bootstrap, undefined); |
| | 10897 | } |
| | 10898 | }).pipe(Effect.provide(NodeHttpServer.layerTest)), |
| | 10899 | ); |
| | 10900 | |
| | 10901 | it.effect("falls back to the project checkout when the worktree base has no commit", () => |
| | 10902 | Effect.gen(function* () { |
| | 10903 | const dispatchedCommands: Array<OrchestrationCommand> = []; |
| | 10904 | const createWorktree = vi.fn( |
| | 10905 | (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["createWorktree"]>[0]) => |
| | 10906 | Effect.die(new Error("createWorktree must not run without a base commit")), |
| | 10907 | ); |
| | 10908 | |
| | 10909 | yield* buildAppUnderTest({ |
| | 10910 | layers: { |
| | 10911 | vcsDriver: { |
| | 10912 | isInsideWorkTree: () => Effect.succeed(true), |
| | 10913 | }, |
| | 10914 | gitVcsDriver: { |
| | 10915 | execute: () => |
| | 10916 | Effect.succeed({ |
| | 10917 | ...SUCCESSFUL_GIT_EXECUTION, |
| | 10918 | exitCode: ChildProcessSpawner.ExitCode(128), |
| | 10919 | stderr: "fatal: Needed a single revision", |
| | 10920 | }), |
| | 10921 | createWorktree, |
| | 10922 | }, |
| | 10923 | orchestrationEngine: { |
| | 10924 | dispatch: (command) => |
| | 10925 | Effect.sync(() => { |
| | 10926 | dispatchedCommands.push(command); |
| | 10927 | return { sequence: dispatchedCommands.length }; |
| | 10928 | }), |
| | 10929 | readEvents: () => Stream.empty, |
| | 10930 | }, |
| | 10931 | }, |
| | 10932 | }); |
| | 10933 | |
| | 10934 | const createdAt = "2026-01-01T00:00:00.000Z"; |
| | 10935 | const wsUrl = yield* getWsServerUrl("/ws"); |
| | 10936 | const response = yield* Effect.scoped( |
| | 10937 | withWsRpcClient(wsUrl, (client) => |
| | 10938 | client[ORCHESTRATION_WS_METHODS.dispatchCommand]({ |
| | 10939 | type: "thread.turn.start", |
| | 10940 | commandId: CommandId.make("cmd-bootstrap-turn-start-unborn-head"), |
| | 10941 | threadId: ThreadId.make("thread-bootstrap-unborn-head"), |
| | 10942 | message: { |
| | 10943 | messageId: MessageId.make("msg-bootstrap-unborn-head"), |
| | 10944 | role: "user", |
| | 10945 | text: "hello", |
| | 10946 | attachments: [], |
| | 10947 | }, |
| | 10948 | modelSelection: defaultModelSelection, |
| | 10949 | runtimeMode: "full-access", |
| | 10950 | interactionMode: "default", |
| | 10951 | bootstrap: { |
| | 10952 | createThread: { |
| | 10953 | projectId: defaultProjectId, |
| | 10954 | title: "Bootstrap Thread", |
| | 10955 | modelSelection: defaultModelSelection, |
| | 10956 | runtimeMode: "full-access", |
| | 10957 | interactionMode: "default", |
| | 10958 | branch: "main", |
| | 10959 | worktreePath: null, |
| | 10960 | createdAt, |
| | 10961 | }, |
| | 10962 | prepareWorktree: { |
| | 10963 | projectCwd: "/tmp/project", |
| | 10964 | baseBranch: "main", |
| | 10965 | branch: "t3code/bootstrap-refName", |
| | 10966 | }, |
| | 10967 | runSetupScript: true, |
| | 10968 | }, |
| | 10969 | createdAt, |
| | 10970 | }), |
| | 10971 | ), |
| | 10972 | ); |
| | 10973 | |
| | 10974 | assert.equal(response.sequence, 2); |
| | 10975 | assert.equal(createWorktree.mock.calls.length, 0); |
| | 10976 | assert.deepEqual( |
| | 10977 | dispatchedCommands.map((command) => command.type), |
| | 10978 | ["thread.create", "thread.turn.start"], |
| | 10979 | ); |
| | 10980 | }).pipe(Effect.provide(NodeHttpServer.layerTest)), |
| | 10981 | ); |
| | 10982 | |
| 10807 | it.effect("records setup-script failures without aborting bootstrap turn start", () => | 10983 | it.effect("records setup-script failures without aborting bootstrap turn start", () => |
| 10808 | Effect.gen(function* () { | 10984 | Effect.gen(function* () { |
| 10809 | const dispatchedCommands: Array<OrchestrationCommand> = []; | 10985 | const dispatchedCommands: Array<OrchestrationCommand> = []; |
| ... | @@ -10834,7 +11010,11 @@ it.layer(NodeServices.layer)("server router seam", (it) => { | ... | @@ -10834,7 +11010,11 @@ it.layer(NodeServices.layer)("server router seam", (it) => { |
| 10834 | | 11010 | |
| 10835 | yield* buildAppUnderTest({ | 11011 | yield* buildAppUnderTest({ |
| 10836 | layers: { | 11012 | layers: { |
| | 11013 | vcsDriver: { |
| | 11014 | isInsideWorkTree: () => Effect.succeed(true), |
| | 11015 | }, |
| 10837 | gitVcsDriver: { | 11016 | gitVcsDriver: { |
| | 11017 | execute: () => Effect.succeed(SUCCESSFUL_GIT_EXECUTION), |
| 10838 | createWorktree, | 11018 | createWorktree, |
| 10839 | }, | 11019 | }, |
| 10840 | orchestrationEngine: { | 11020 | orchestrationEngine: { |
| ... | @@ -10939,7 +11119,11 @@ it.layer(NodeServices.layer)("server router seam", (it) => { | ... | @@ -10939,7 +11119,11 @@ it.layer(NodeServices.layer)("server router seam", (it) => { |
| 10939 | | 11119 | |
| 10940 | yield* buildAppUnderTest({ | 11120 | yield* buildAppUnderTest({ |
| 10941 | layers: { | 11121 | layers: { |
| | 11122 | vcsDriver: { |
| | 11123 | isInsideWorkTree: () => Effect.succeed(true), |
| | 11124 | }, |
| 10942 | gitVcsDriver: { | 11125 | gitVcsDriver: { |
| | 11126 | execute: () => Effect.succeed(SUCCESSFUL_GIT_EXECUTION), |
| 10943 | createWorktree, | 11127 | createWorktree, |
| 10944 | }, | 11128 | }, |
| 10945 | orchestrationEngine: { | 11129 | orchestrationEngine: { |
| ... | @@ -11044,7 +11228,11 @@ it.layer(NodeServices.layer)("server router seam", (it) => { | ... | @@ -11044,7 +11228,11 @@ it.layer(NodeServices.layer)("server router seam", (it) => { |
| 11044 | | 11228 | |
| 11045 | const config = yield* buildAppUnderTest({ | 11229 | const config = yield* buildAppUnderTest({ |
| 11046 | layers: { | 11230 | layers: { |
| | 11231 | vcsDriver: { |
| | 11232 | isInsideWorkTree: () => Effect.succeed(true), |
| | 11233 | }, |
| 11047 | gitVcsDriver: { | 11234 | gitVcsDriver: { |
| | 11235 | execute: () => Effect.succeed(SUCCESSFUL_GIT_EXECUTION), |
| 11048 | createWorktree, | 11236 | createWorktree, |
| 11049 | }, | 11237 | }, |
| 11050 | orchestrationEngine: { | 11238 | orchestrationEngine: { |
| ... | @@ -11253,7 +11441,11 @@ it.layer(NodeServices.layer)("server router seam", (it) => { | ... | @@ -11253,7 +11441,11 @@ it.layer(NodeServices.layer)("server router seam", (it) => { |
| 11253 | | 11441 | |
| 11254 | yield* buildAppUnderTest({ | 11442 | yield* buildAppUnderTest({ |
| 11255 | layers: { | 11443 | layers: { |
| | 11444 | vcsDriver: { |
| | 11445 | isInsideWorkTree: () => Effect.succeed(true), |
| | 11446 | }, |
| 11256 | gitVcsDriver: { | 11447 | gitVcsDriver: { |
| | 11448 | execute: () => Effect.succeed(SUCCESSFUL_GIT_EXECUTION), |
| 11257 | createWorktree, | 11449 | createWorktree, |
| 11258 | }, | 11450 | }, |
| 11259 | orchestrationEngine: { | 11451 | orchestrationEngine: { |