Skip to content
Merged
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 @@ -656,6 +656,55 @@ it.effect(
}).pipe(Effect.provide(layerTest)),
);

it.effect("keeps the store's read failure as the cause when a command projection fails", () =>
Effect.gen(function* () {
const orchestrator = yield* Orchestrator.OrchestratorV2;
const sql = yield* SqlClient.SqlClient;
const now = DateTime.formatIso(yield* DateTime.now);
const unreadableMessage = ThreadId.make("thread:control-unreadable-message");
const unreadableRequest = ThreadId.make("thread:control-unreadable-request");
for (const threadId of [unreadableMessage, unreadableRequest]) {
yield* orchestrator.dispatch({
type: "thread.create",
commandId: CommandId.make(`create:${threadId}`),
threadId,
projectId: ProjectId.make("project:control-unreadable"),
title: "Unreadable",
modelSelection,
runtimeMode: "full-access",
interactionMode: "default",
branch: null,
worktreePath: null,
createdBy: "user",
creationSource: "web",
});
}
yield* sql`INSERT INTO orchestration_v2_projection_messages
(message_id, thread_id, run_id, node_id, role, streaming, created_at, updated_at, payload_json)
VALUES ('obsolete-user', ${unreadableMessage}, NULL, NULL, 'user', 0, ${now}, ${now}, '{"obsolete":true}')`;
yield* sql`INSERT INTO orchestration_v2_projection_runtime_requests
(runtime_request_id, thread_id, node_id, kind, status, created_at, payload_json)
VALUES ('request:unreadable', ${unreadableRequest}, 'node:unreadable', 'user-input', 'pending', ${now}, '{}')`;
for (const command of [
{
type: "queue.resume",
commandId: CommandId.make("resume-unreadable"),
threadId: unreadableMessage,
},
{
type: "thread.user-input.dismiss",
commandId: CommandId.make("dismiss-unreadable"),
threadId: unreadableRequest,
requestId: RuntimeRequestId.make("request:unreadable"),
},
] as const) {
const failure = yield* orchestrator.dispatch(command).pipe(Effect.flip);
assert.instanceOf(failure, Orchestrator.OrchestratorProjectionError);
assert.instanceOf(failure.cause, ProjectionStore.ProjectionStoreReadError);
}
}).pipe(Effect.provide(layerTest)),
);

it.effect("implements a proposed plan that the command projection leaves out", () =>
Effect.gen(function* () {
const orchestrator = yield* Orchestrator.OrchestratorV2;
Expand Down
72 changes: 35 additions & 37 deletions apps/server/src/orchestration-v2/Orchestrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8793,7 +8793,9 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
const context = yield* projectionStore
.getRuntimeResponseContext(command.threadId, command.requestId)
.pipe(
Effect.mapError(() => new OrchestratorProjectionError({ threadId: command.threadId })),
Effect.mapError(
(cause) => new OrchestratorProjectionError({ threadId: command.threadId, cause }),
),
);
const runtimeRequest = context.request;
if (runtimeRequest === undefined) {
Expand Down Expand Up @@ -9206,7 +9208,9 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
const request = yield* projectionStore
.getRuntimeRequest(command.threadId, command.requestId)
.pipe(
Effect.mapError(() => new OrchestratorProjectionError({ threadId: command.threadId })),
Effect.mapError(
(cause) => new OrchestratorProjectionError({ threadId: command.threadId, cause }),
),
);
if (request === undefined || request.status !== "pending") {
return yield* new OrchestratorDispatchError({
Expand Down Expand Up @@ -9243,24 +9247,22 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
events: Ref.Ref<Array<OrchestrationV2DomainEvent>>,
effects: Ref.Ref<Array<PendingOrchestrationEffectV2>>,
) {
const projection = yield* projectionStore
.getThreadRecords(
command.threadId,
[
"runs",
"nodes",
"attempts",
"messages",
"providerThreads",
"providerTurns",
"providerSessions",
"turnItems",
"runtimeRequests",
"subagents",
],
{ turnItemTypes: [], messageRoles: ["user"] },
)
.pipe(Effect.mapError(() => new OrchestratorProjectionError({ threadId: command.threadId })));
const projection = yield* loadProjectionForCommand(
command,
[
"runs",
"nodes",
"attempts",
"messages",
"providerThreads",
"providerTurns",
"providerSessions",
"turnItems",
"runtimeRequests",
"subagents",
],
{ turnItemTypes: [], messageRoles: ["user"] },
);
if (projection.thread.archivedAt !== null || projection.thread.deletedAt !== null) {
return yield* new OrchestratorDispatchError({
commandId: command.commandId,
Expand Down Expand Up @@ -9454,9 +9456,9 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
command: Extract<OrchestrationV2Command, { readonly type: "queued-run.reorder" }>,
events: Ref.Ref<Array<OrchestrationV2DomainEvent>>,
) {
const projection = yield* projectionStore
.getThreadRecords(command.threadId, ["runs", "messages"], { messageRoles: ["user"] })
.pipe(Effect.mapError(() => new OrchestratorProjectionError({ threadId: command.threadId })));
const projection = yield* loadProjectionForCommand(command, ["runs", "messages"], {
messageRoles: ["user"],
});
const queuedRuns = queuedRunsInDeliveryOrder(projection);
const moving = queuedRuns.find((run) => run.id === command.runId);
if (moving === undefined) {
Expand Down Expand Up @@ -9540,13 +9542,11 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
command: Extract<OrchestrationV2Command, { readonly type: "queued-run.cancel" }>,
events: Ref.Ref<Array<OrchestrationV2DomainEvent>>,
) {
const projection = yield* projectionStore
.getThreadRecords(
command.threadId,
["runs", "messages", "nodes", "attempts", "subagents", "turnItems"],
{ turnItemTypes: [], messageRoles: ["user"] },
)
.pipe(Effect.mapError(() => new OrchestratorProjectionError({ threadId: command.threadId })));
const projection = yield* loadProjectionForCommand(
command,
["runs", "messages", "nodes", "attempts", "subagents", "turnItems"],
{ turnItemTypes: [], messageRoles: ["user"] },
);
const queuedRun = projection.runs.find((candidate) => candidate.id === command.runId);
if (queuedRun === undefined || queuedRun.status !== "queued") {
return yield* new OrchestratorDispatchError({
Expand Down Expand Up @@ -9651,12 +9651,10 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
cause: `Queued run ${command.runId} cannot be edited to an empty message.`,
});
}
const projection = yield* projectionStore
.getThreadRecords(command.threadId, ["runs", "messages", "turnItems"], {
turnItemTypes: ["user_message"],
messageRoles: ["user"],
})
.pipe(Effect.mapError(() => new OrchestratorProjectionError({ threadId: command.threadId })));
const projection = yield* loadProjectionForCommand(command, ["runs", "messages", "turnItems"], {
turnItemTypes: ["user_message"],
messageRoles: ["user"],
});
const queuedRun = projection.runs.find((candidate) => candidate.id === command.runId);
if (queuedRun === undefined || queuedRun.status !== "queued") {
return yield* new OrchestratorDispatchError({
Expand Down Expand Up @@ -9754,7 +9752,7 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
.getThreadRecords(commandThreadId(command), fields, filter)
.pipe(
Effect.mapError(
() => new OrchestratorProjectionError({ threadId: commandThreadId(command) }),
(cause) => new OrchestratorProjectionError({ threadId: commandThreadId(command), cause }),
),
);

Expand Down
Loading