121 lines · 3.8 KB · TypeScript
RawDownload
import {  CommandId,  DEFAULT_PROVIDER_INTERACTION_MODE,  EventId,  ProjectId,  ThreadId,  ProviderInstanceId,} from "@t3tools/contracts";import * as Effect from "effect/Effect";import * as NodeServices from "@effect/platform-node/NodeServices";import { expect, it } from "@effect/vitest"; import { decideOrchestrationCommand } from "./decider.ts";import { createEmptyReadModel, projectEvent } from "./projector.ts"; const asCommandId = (value: string): CommandId => CommandId.make(value);const asEventId = (value: string): EventId => EventId.make(value);const asProjectId = (value: string): ProjectId => ProjectId.make(value);const asThreadId = (value: string): ThreadId => ThreadId.make(value); const now = "2026-01-01T00:00:00.000Z"; const seedReadModel = Effect.gen(function* () {  const initial = createEmptyReadModel(now);  const withProject = yield* projectEvent(initial, {    sequence: 1,    eventId: asEventId("evt-project-create"),    aggregateKind: "project",    aggregateId: asProjectId("project-workflow"),    type: "project.created",    occurredAt: now,    commandId: asCommandId("cmd-project-create"),    causationEventId: null,    correlationId: asCommandId("cmd-project-create"),    metadata: {},    payload: {      projectId: asProjectId("project-workflow"),      title: "Project Workflow",      workspaceRoot: "/tmp/project-workflow",      defaultModelSelection: null,      scripts: [],      createdAt: now,      updatedAt: now,    },  });   return yield* projectEvent(withProject, {    sequence: 2,    eventId: asEventId("evt-thread-create"),    aggregateKind: "thread",    aggregateId: asThreadId("thread-workflow"),    type: "thread.created",    occurredAt: now,    commandId: asCommandId("cmd-thread-create"),    causationEventId: null,    correlationId: asCommandId("cmd-thread-create"),    metadata: {},    payload: {      threadId: asThreadId("thread-workflow"),      projectId: asProjectId("project-workflow"),      title: "Thread Workflow",      modelSelection: {        instanceId: ProviderInstanceId.make("codex"),        model: "gpt-5-codex",      },      interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE,      runtimeMode: "approval-required",      branch: null,      worktreePath: null,      createdAt: now,      updatedAt: now,    },  });}); it.layer(NodeServices.layer)("decider thread.task.stop", (it) => {  it.effect("emits a thread.task-stop-requested event carrying the task id", () =>    Effect.gen(function* () {      const readModel = yield* seedReadModel;      const result = yield* decideOrchestrationCommand({        command: {          type: "thread.task.stop",          commandId: asCommandId("cmd-task-stop"),          threadId: asThreadId("thread-workflow"),          taskId: "task-9",          createdAt: now,        },        readModel,      });      const events = Array.isArray(result) ? result : [result];      expect(events).toHaveLength(1);      const event = events[0]!;      expect(event.type).toBe("thread.task-stop-requested");      expect(event.payload).toMatchObject({        threadId: asThreadId("thread-workflow"),        taskId: "task-9",        createdAt: now,      });    }),  );   it.effect("rejects a task stop for a thread that does not exist", () =>    Effect.gen(function* () {      const readModel = yield* seedReadModel;      const error = yield* Effect.flip(        decideOrchestrationCommand({          command: {            type: "thread.task.stop",            commandId: asCommandId("cmd-task-stop-missing"),            threadId: asThreadId("thread-missing"),            taskId: "task-9",            createdAt: now,          },          readModel,        }),      );      expect(error.message).toContain("thread-missing");      expect(error.message).toContain("does not exist");    }),  );});