| ... | ... | @@ -1839,6 +1839,89 @@ describe("OrchestrationEngine", () => { |
| 1839 | 1839 | await runtime.dispose(); |
| 1840 | 1840 | }); |
| 1841 | 1841 | |
| 1842 | it("does not republish another server's turn when a local dispatch fails", async () => { |
| 1843 | const directory = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "t3-shared-db-")); |
| 1844 | const databasePath = NodePath.join(directory, "state.sqlite"); |
| 1845 | const serverA = await createOrchestrationSystem(databasePath); |
| 1846 | const serverB = await createOrchestrationSystem(databasePath); |
| 1847 | const threadId = ThreadId.make("thread-shared"); |
| 1848 | const createdAt = now(); |
| 1849 | const sentinelCommandId = CommandId.make("cmd-shared-rename"); |
| 1850 | try { |
| 1851 | await serverA.run( |
| 1852 | serverA.engine.dispatch({ |
| 1853 | type: "project.create", |
| 1854 | commandId: CommandId.make("cmd-shared-project-create"), |
| 1855 | projectId: asProjectId("project-shared"), |
| 1856 | title: "Shared Project", |
| 1857 | workspaceRoot: "/tmp/project-shared", |
| 1858 | createdAt, |
| 1859 | }), |
| 1860 | ); |
| 1861 | await serverA.run( |
| 1862 | serverA.engine.dispatch({ |
| 1863 | type: "thread.create", |
| 1864 | commandId: CommandId.make("cmd-shared-thread-create"), |
| 1865 | threadId, |
| 1866 | projectId: asProjectId("project-shared"), |
| 1867 | title: "shared", |
| 1868 | modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5-codex" }, |
| 1869 | interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, |
| 1870 | runtimeMode: "approval-required", |
| 1871 | branch: null, |
| 1872 | worktreePath: null, |
| 1873 | createdAt, |
| 1874 | }), |
| 1875 | ); |
| 1876 | await serverA.run( |
| 1877 | serverA.engine.dispatch({ |
| 1878 | type: "thread.turn.start", |
| 1879 | commandId: CommandId.make("cmd-shared-turn-start"), |
| 1880 | threadId, |
| 1881 | message: { |
| 1882 | messageId: asMessageId("msg-shared"), |
| 1883 | role: "user", |
| 1884 | text: "hello", |
| 1885 | attachments: [], |
| 1886 | }, |
| 1887 | interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, |
| 1888 | runtimeMode: "approval-required", |
| 1889 | createdAt, |
| 1890 | }), |
| 1891 | ); |
| 1892 | |
| 1893 | const published = await serverB.run( |
| 1894 | Effect.gen(function* () { |
| 1895 | const events = yield* serverB.engine.subscribeDomainEvents; |
| 1896 | // B's command model is still empty, so this fails and reconciles. |
| 1897 | yield* serverB.engine |
| 1898 | .dispatch({ |
| 1899 | type: "thread.meta.update", |
| 1900 | commandId: CommandId.make("cmd-shared-stale-rename"), |
| 1901 | threadId, |
| 1902 | title: "stale", |
| 1903 | }) |
| 1904 | .pipe(Effect.flip); |
| 1905 | yield* serverB.engine.dispatch({ |
| 1906 | type: "thread.meta.update", |
| 1907 | commandId: sentinelCommandId, |
| 1908 | threadId, |
| 1909 | title: "renamed on B", |
| 1910 | }); |
| 1911 | return yield* Stream.runCollect( |
| 1912 | Stream.takeUntil(events, (event) => event.commandId === sentinelCommandId), |
| 1913 | ); |
| 1914 | }).pipe(Effect.scoped), |
| 1915 | ); |
| 1916 | |
| 1917 | expect(Array.from(published).map((event) => event.type)).toEqual(["thread.meta-updated"]); |
| 1918 | } finally { |
| 1919 | await serverA.dispose(); |
| 1920 | await serverB.dispose(); |
| 1921 | await NodeFSP.rm(directory, { recursive: true, force: true }); |
| 1922 | } |
| 1923 | }); |
| 1924 | |
| 1842 | 1925 | it("fails command dispatch when command invariants are violated", async () => { |
| 1843 | 1926 | const system = await createOrchestrationSystem(); |
| 1844 | 1927 | const { engine } = system; |