authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-03-28 22:57:32-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-03-29 05:57:32+00:00
loga60daa1c764a97e88f2eef63bbac960b79274ed6
treec5f5c89b0c0d456fed78b8ccd2ed1578d34a543a
parentbc1024caf09aa349f18dd72aaf823588f3ff1fec
signature Signed by PGP key B5690EEEBB952194

Wrap orchestration reactor starts in Effect.fn (#1513)


15 files changed, 448 insertions(+), 450 deletions(-)

apps/server/integration/OrchestrationEngineHarness.integration.ts+1-1
......@@ -360,7 +360,7 @@ export const makeOrchestrationIntegrationHarness = (
360360
361361 const scope = yield* Scope.make("sequential");
362362 yield* tryRuntimePromise("start OrchestrationReactor", () =>
363 runtime.runPromise(reactor.start.pipe(Scope.provide(scope))),
363 runtime.runPromise(reactor.start().pipe(Scope.provide(scope))),
364364 ).pipe(Effect.orDie);
365365 const receiptHistory = yield* Ref.make<ReadonlyArray<OrchestrationRuntimeReceipt>>([]);
366366 yield* Stream.runForEach(runtimeReceiptBus.stream, (receipt) =>
apps/server/src/orchestration/Layers/CheckpointReactor.test.ts+1-1
......@@ -271,7 +271,7 @@ describe("CheckpointReactor", () => {
271271 const reactor = await runtime.runPromise(Effect.service(CheckpointReactor));
272272 const checkpointStore = await runtime.runPromise(Effect.service(CheckpointStore));
273273 scope = await Effect.runPromise(Scope.make("sequential"));
274 await Effect.runPromise(reactor.start.pipe(Scope.provide(scope)));
274 await Effect.runPromise(reactor.start().pipe(Scope.provide(scope)));
275275 const drain = () => Effect.runPromise(reactor.drain);
276276
277277 const createdAt = new Date().toISOString();
apps/server/src/orchestration/Layers/CheckpointReactor.ts+1-1
......@@ -766,7 +766,7 @@ const make = Effect.gen(function* () {
766766
767767 const worker = yield* makeDrainableWorker(processInputSafely);
768768
769 const start: CheckpointReactorShape["start"] = Effect.gen(function* () {
769 const start: CheckpointReactorShape["start"] = Effect.fn("start")(function* () {
770770 yield* Effect.forkScoped(
771771 Stream.runForEach(orchestrationEngine.streamDomainEvents, (event) => {
772772 if (
apps/server/src/orchestration/Layers/OrchestrationReactor.test.ts+10-7
......@@ -24,25 +24,28 @@ describe("OrchestrationReactor", () => {
2424 Layer.effect(OrchestrationReactor, makeOrchestrationReactor).pipe(
2525 Layer.provideMerge(
2626 Layer.succeed(ProviderRuntimeIngestionService, {
27 start: Effect.sync(() => {
27 start: () => {
2828 started.push("provider-runtime-ingestion");
29 }),
29 return Effect.void;
30 },
3031 drain: Effect.void,
3132 }),
3233 ),
3334 Layer.provideMerge(
3435 Layer.succeed(ProviderCommandReactor, {
35 start: Effect.sync(() => {
36 start: () => {
3637 started.push("provider-command-reactor");
37 }),
38 return Effect.void;
39 },
3840 drain: Effect.void,
3941 }),
4042 ),
4143 Layer.provideMerge(
4244 Layer.succeed(CheckpointReactor, {
43 start: Effect.sync(() => {
45 start: () => {
4446 started.push("checkpoint-reactor");
45 }),
47 return Effect.void;
48 },
4649 drain: Effect.void,
4750 }),
4851 ),
......@@ -51,7 +54,7 @@ describe("OrchestrationReactor", () => {
5154
5255 const reactor = await runtime.runPromise(Effect.service(OrchestrationReactor));
5356 const scope = await Effect.runPromise(Scope.make("sequential"));
54 await Effect.runPromise(reactor.start.pipe(Scope.provide(scope)));
57 await Effect.runPromise(reactor.start().pipe(Scope.provide(scope)));
5558
5659 expect(started).toEqual([
5760 "provider-runtime-ingestion",
apps/server/src/orchestration/Layers/OrchestrationReactor.ts+4-4
......@@ -13,10 +13,10 @@ export const makeOrchestrationReactor = Effect.gen(function* () {
1313 const providerCommandReactor = yield* ProviderCommandReactor;
1414 const checkpointReactor = yield* CheckpointReactor;
1515
16 const start: OrchestrationReactorShape["start"] = Effect.gen(function* () {
17 yield* providerRuntimeIngestion.start;
18 yield* providerCommandReactor.start;
19 yield* checkpointReactor.start;
16 const start: OrchestrationReactorShape["start"] = Effect.fn("start")(function* () {
17 yield* providerRuntimeIngestion.start();
18 yield* providerCommandReactor.start();
19 yield* checkpointReactor.start();
2020 });
2121
2222 return {
apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts+1-1
......@@ -235,7 +235,7 @@ describe("ProviderCommandReactor", () => {
235235 const engine = await runtime.runPromise(Effect.service(OrchestrationEngineService));
236236 const reactor = await runtime.runPromise(Effect.service(ProviderCommandReactor));
237237 scope = await Effect.runPromise(Scope.make("sequential"));
238 await Effect.runPromise(reactor.start.pipe(Scope.provide(scope)));
238 await Effect.runPromise(reactor.start().pipe(Scope.provide(scope)));
239239 const drain = () => Effect.runPromise(reactor.drain);
240240
241241 await Effect.runPromise(
apps/server/src/orchestration/Layers/ProviderCommandReactor.ts+14-12
......@@ -773,22 +773,24 @@ const make = Effect.gen(function* () {
773773
774774 const worker = yield* makeDrainableWorker(processDomainEventSafely);
775775
776 const start: ProviderCommandReactorShape["start"] = Effect.forkScoped(
777 Stream.runForEach(orchestrationEngine.streamDomainEvents, (event) => {
776 const start: ProviderCommandReactorShape["start"] = Effect.fn("start")(function* () {
777 const processEvent = Effect.fn("processEvent")(function* (event: OrchestrationEvent) {
778778 if (
779 event.type !== "thread.runtime-mode-set" &&
780 event.type !== "thread.turn-start-requested" &&
781 event.type !== "thread.turn-interrupt-requested" &&
782 event.type !== "thread.approval-response-requested" &&
783 event.type !== "thread.user-input-response-requested" &&
784 event.type !== "thread.session-stop-requested"
779 event.type === "thread.runtime-mode-set" ||
780 event.type === "thread.turn-start-requested" ||
781 event.type === "thread.turn-interrupt-requested" ||
782 event.type === "thread.approval-response-requested" ||
783 event.type === "thread.user-input-response-requested" ||
784 event.type === "thread.session-stop-requested"
785785 ) {
786 return Effect.void;
786 return yield* worker.enqueue(event);
787787 }
788 });
788789
789 return worker.enqueue(event);
790 }),
791 ).pipe(Effect.asVoid);
790 yield* Effect.forkScoped(
791 Stream.runForEach(orchestrationEngine.streamDomainEvents, processEvent),
792 );
793 });
792794
793795 return {
794796 start,
apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts+1-1
......@@ -214,7 +214,7 @@ describe("ProviderRuntimeIngestion", () => {
214214 const engine = await runtime.runPromise(Effect.service(OrchestrationEngineService));
215215 const ingestion = await runtime.runPromise(Effect.service(ProviderRuntimeIngestionService));
216216 scope = await Effect.runPromise(Scope.make("sequential"));
217 await Effect.runPromise(ingestion.start.pipe(Scope.provide(scope)));
217 await Effect.runPromise(ingestion.start().pipe(Scope.provide(scope)));
218218 const drain = () => Effect.runPromise(ingestion.drain);
219219
220220 const createdAt = new Date().toISOString();
apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts+403-410
......@@ -500,7 +500,7 @@ function runtimeEventToActivities(
500500 return [];
501501}
502502
503const make = Effect.gen(function* () {
503const make = Effect.fn("make")(function* () {
504504 const orchestrationEngine = yield* OrchestrationEngineService;
505505 const providerService = yield* ProviderService;
506506 const projectionTurnRepository = yield* ProjectionTurnRepository;
......@@ -587,8 +587,8 @@ const make = Effect.gen(function* () {
587587
588588 const appendBufferedAssistantText = (messageId: MessageId, delta: string) =>
589589 Cache.getOption(bufferedAssistantTextByMessageId, messageId).pipe(
590 Effect.flatMap((existingText) =>
591 Effect.gen(function* () {
590 Effect.flatMap(
591 Effect.fn("appendBufferedAssistantText")(function* (existingText) {
592592 const nextText = Option.match(existingText, {
593593 onNone: () => delta,
594594 onSome: (text) => `${text}${delta}`,
......@@ -644,7 +644,7 @@ const make = Effect.gen(function* () {
644644 const clearAssistantMessageState = (messageId: MessageId) =>
645645 clearBufferedAssistantText(messageId);
646646
647 const finalizeAssistantMessage = (input: {
647 const finalizeAssistantMessage = Effect.fn("finalizeAssistantMessage")(function* (input: {
648648 event: ProviderRuntimeEvent;
649649 threadId: ThreadId;
650650 messageId: MessageId;
......@@ -653,40 +653,39 @@ const make = Effect.gen(function* () {
653653 commandTag: string;
654654 finalDeltaCommandTag: string;
655655 fallbackText?: string;
656 }) =>
657 Effect.gen(function* () {
658 const bufferedText = yield* takeBufferedAssistantText(input.messageId);
659 const text =
660 bufferedText.length > 0
661 ? bufferedText
662 : (input.fallbackText?.trim().length ?? 0) > 0
663 ? input.fallbackText!
664 : "";
665
666 if (text.length > 0) {
667 yield* orchestrationEngine.dispatch({
668 type: "thread.message.assistant.delta",
669 commandId: providerCommandId(input.event, input.finalDeltaCommandTag),
670 threadId: input.threadId,
671 messageId: input.messageId,
672 delta: text,
673 ...(input.turnId ? { turnId: input.turnId } : {}),
674 createdAt: input.createdAt,
675 });
676 }
677
656 }) {
657 const bufferedText = yield* takeBufferedAssistantText(input.messageId);
658 const text =
659 bufferedText.length > 0
660 ? bufferedText
661 : (input.fallbackText?.trim().length ?? 0) > 0
662 ? input.fallbackText!
663 : "";
664
665 if (text.length > 0) {
678666 yield* orchestrationEngine.dispatch({
679 type: "thread.message.assistant.complete",
680 commandId: providerCommandId(input.event, input.commandTag),
667 type: "thread.message.assistant.delta",
668 commandId: providerCommandId(input.event, input.finalDeltaCommandTag),
681669 threadId: input.threadId,
682670 messageId: input.messageId,
671 delta: text,
683672 ...(input.turnId ? { turnId: input.turnId } : {}),
684673 createdAt: input.createdAt,
685674 });
686 yield* clearAssistantMessageState(input.messageId);
675 }
676
677 yield* orchestrationEngine.dispatch({
678 type: "thread.message.assistant.complete",
679 commandId: providerCommandId(input.event, input.commandTag),
680 threadId: input.threadId,
681 messageId: input.messageId,
682 ...(input.turnId ? { turnId: input.turnId } : {}),
683 createdAt: input.createdAt,
687684 });
685 yield* clearAssistantMessageState(input.messageId);
686 });
688687
689 const upsertProposedPlan = (input: {
688 const upsertProposedPlan = Effect.fn("upsertProposedPlan")(function* (input: {
690689 event: ProviderRuntimeEvent;
691690 threadId: ThreadId;
692691 threadProposedPlans: ReadonlyArray<{
......@@ -700,32 +699,31 @@ const make = Effect.gen(function* () {
700699 planMarkdown: string | undefined;
701700 createdAt: string;
702701 updatedAt: string;
703 }) =>
704 Effect.gen(function* () {
705 const planMarkdown = normalizeProposedPlanMarkdown(input.planMarkdown);
706 if (!planMarkdown) {
707 return;
708 }
702 }) {
703 const planMarkdown = normalizeProposedPlanMarkdown(input.planMarkdown);
704 if (!planMarkdown) {
705 return;
706 }
709707
710 const existingPlan = input.threadProposedPlans.find((entry) => entry.id === input.planId);
711 yield* orchestrationEngine.dispatch({
712 type: "thread.proposed-plan.upsert",
713 commandId: providerCommandId(input.event, "proposed-plan-upsert"),
714 threadId: input.threadId,
715 proposedPlan: {
716 id: input.planId,
717 turnId: input.turnId ?? null,
718 planMarkdown,
719 implementedAt: existingPlan?.implementedAt ?? null,
720 implementationThreadId: existingPlan?.implementationThreadId ?? null,
721 createdAt: existingPlan?.createdAt ?? input.createdAt,
722 updatedAt: input.updatedAt,
723 },
724 createdAt: input.updatedAt,
725 });
708 const existingPlan = input.threadProposedPlans.find((entry) => entry.id === input.planId);
709 yield* orchestrationEngine.dispatch({
710 type: "thread.proposed-plan.upsert",
711 commandId: providerCommandId(input.event, "proposed-plan-upsert"),
712 threadId: input.threadId,
713 proposedPlan: {
714 id: input.planId,
715 turnId: input.turnId ?? null,
716 planMarkdown,
717 implementedAt: existingPlan?.implementedAt ?? null,
718 implementationThreadId: existingPlan?.implementationThreadId ?? null,
719 createdAt: existingPlan?.createdAt ?? input.createdAt,
720 updatedAt: input.updatedAt,
721 },
722 createdAt: input.updatedAt,
726723 });
724 });
727725
728 const finalizeBufferedProposedPlan = (input: {
726 const finalizeBufferedProposedPlan = Effect.fn("finalizeBufferedProposedPlan")(function* (input: {
729727 event: ProviderRuntimeEvent;
730728 threadId: ThreadId;
731729 threadProposedPlans: ReadonlyArray<{
......@@ -738,66 +736,65 @@ const make = Effect.gen(function* () {
738736 turnId?: TurnId;
739737 fallbackMarkdown?: string;
740738 updatedAt: string;
741 }) =>
742 Effect.gen(function* () {
743 const bufferedPlan = yield* takeBufferedProposedPlan(input.planId);
744 const bufferedMarkdown = normalizeProposedPlanMarkdown(bufferedPlan?.text);
745 const fallbackMarkdown = normalizeProposedPlanMarkdown(input.fallbackMarkdown);
746 const planMarkdown = bufferedMarkdown ?? fallbackMarkdown;
747 if (!planMarkdown) {
748 return;
749 }
739 }) {
740 const bufferedPlan = yield* takeBufferedProposedPlan(input.planId);
741 const bufferedMarkdown = normalizeProposedPlanMarkdown(bufferedPlan?.text);
742 const fallbackMarkdown = normalizeProposedPlanMarkdown(input.fallbackMarkdown);
743 const planMarkdown = bufferedMarkdown ?? fallbackMarkdown;
744 if (!planMarkdown) {
745 return;
746 }
750747
751 yield* upsertProposedPlan({
752 event: input.event,
753 threadId: input.threadId,
754 threadProposedPlans: input.threadProposedPlans,
755 planId: input.planId,
756 ...(input.turnId ? { turnId: input.turnId } : {}),
757 planMarkdown,
758 createdAt:
759 bufferedPlan?.createdAt && bufferedPlan.createdAt.length > 0
760 ? bufferedPlan.createdAt
761 : input.updatedAt,
762 updatedAt: input.updatedAt,
763 });
764 yield* clearBufferedProposedPlan(input.planId);
748 yield* upsertProposedPlan({
749 event: input.event,
750 threadId: input.threadId,
751 threadProposedPlans: input.threadProposedPlans,
752 planId: input.planId,
753 ...(input.turnId ? { turnId: input.turnId } : {}),
754 planMarkdown,
755 createdAt:
756 bufferedPlan?.createdAt && bufferedPlan.createdAt.length > 0
757 ? bufferedPlan.createdAt
758 : input.updatedAt,
759 updatedAt: input.updatedAt,
765760 });
761 yield* clearBufferedProposedPlan(input.planId);
762 });
766763
767 const clearTurnStateForSession = (threadId: ThreadId) =>
768 Effect.gen(function* () {
769 const prefix = `${threadId}:`;
770 const proposedPlanPrefix = `plan:${threadId}:`;
771 const turnKeys = Array.from(yield* Cache.keys(turnMessageIdsByTurnKey));
772 const proposedPlanKeys = Array.from(yield* Cache.keys(bufferedProposedPlanById));
773 yield* Effect.forEach(
774 turnKeys,
775 (key) =>
776 Effect.gen(function* () {
777 if (!key.startsWith(prefix)) {
778 return;
779 }
764 const clearTurnStateForSession = Effect.fn("clearTurnStateForSession")(function* (
765 threadId: ThreadId,
766 ) {
767 const prefix = `${threadId}:`;
768 const proposedPlanPrefix = `plan:${threadId}:`;
769 const turnKeys = Array.from(yield* Cache.keys(turnMessageIdsByTurnKey));
770 const proposedPlanKeys = Array.from(yield* Cache.keys(bufferedProposedPlanById));
771 yield* Effect.forEach(
772 turnKeys,
773 Effect.fn(function* (key) {
774 if (!key.startsWith(prefix)) {
775 return;
776 }
780777
781 const messageIds = yield* Cache.getOption(turnMessageIdsByTurnKey, key);
782 if (Option.isSome(messageIds)) {
783 yield* Effect.forEach(messageIds.value, clearAssistantMessageState, {
784 concurrency: 1,
785 }).pipe(Effect.asVoid);
786 }
778 const messageIds = yield* Cache.getOption(turnMessageIdsByTurnKey, key);
779 if (Option.isSome(messageIds)) {
780 yield* Effect.forEach(messageIds.value, clearAssistantMessageState, {
781 concurrency: 1,
782 }).pipe(Effect.asVoid);
783 }
787784
788 yield* Cache.invalidate(turnMessageIdsByTurnKey, key);
789 }),
790 { concurrency: 1 },
791 ).pipe(Effect.asVoid);
792 yield* Effect.forEach(
793 proposedPlanKeys,
794 (key) =>
795 key.startsWith(proposedPlanPrefix)
796 ? Cache.invalidate(bufferedProposedPlanById, key)
797 : Effect.void,
798 { concurrency: 1 },
799 ).pipe(Effect.asVoid);
800 });
785 yield* Cache.invalidate(turnMessageIdsByTurnKey, key);
786 }),
787 { concurrency: 1 },
788 ).pipe(Effect.asVoid);
789 yield* Effect.forEach(
790 proposedPlanKeys,
791 (key) =>
792 key.startsWith(proposedPlanPrefix)
793 ? Cache.invalidate(bufferedProposedPlanById, key)
794 : Effect.void,
795 { concurrency: 1 },
796 ).pipe(Effect.asVoid);
797 });
801798
802799 const getSourceProposedPlanReferenceForPendingTurnStart = Effect.fnUntraced(function* (
803800 threadId: ThreadId,
......@@ -872,359 +869,355 @@ const make = Effect.gen(function* () {
872869 });
873870 });
874871
875 const processRuntimeEvent = (event: ProviderRuntimeEvent) =>
876 Effect.gen(function* () {
877 const readModel = yield* orchestrationEngine.getReadModel();
878 const thread = readModel.threads.find((entry) => entry.id === event.threadId);
879 if (!thread) return;
872 const processRuntimeEvent = Effect.fn("processRuntimeEvent")(function* (
873 event: ProviderRuntimeEvent,
874 ) {
875 const readModel = yield* orchestrationEngine.getReadModel();
876 const thread = readModel.threads.find((entry) => entry.id === event.threadId);
877 if (!thread) return;
880878
881 const now = event.createdAt;
882 const eventTurnId = toTurnId(event.turnId);
883 const activeTurnId = thread.session?.activeTurnId ?? null;
879 const now = event.createdAt;
880 const eventTurnId = toTurnId(event.turnId);
881 const activeTurnId = thread.session?.activeTurnId ?? null;
884882
885 const conflictsWithActiveTurn =
886 activeTurnId !== null && eventTurnId !== undefined && !sameId(activeTurnId, eventTurnId);
887 const missingTurnForActiveTurn = activeTurnId !== null && eventTurnId === undefined;
883 const conflictsWithActiveTurn =
884 activeTurnId !== null && eventTurnId !== undefined && !sameId(activeTurnId, eventTurnId);
885 const missingTurnForActiveTurn = activeTurnId !== null && eventTurnId === undefined;
888886
889 const shouldApplyThreadLifecycle = (() => {
890 if (!STRICT_PROVIDER_LIFECYCLE_GUARD) {
887 const shouldApplyThreadLifecycle = (() => {
888 if (!STRICT_PROVIDER_LIFECYCLE_GUARD) {
889 return true;
890 }
891 switch (event.type) {
892 case "session.exited":
891893 return true;
892 }
894 case "session.started":
895 case "thread.started":
896 return true;
897 case "turn.started":
898 return !conflictsWithActiveTurn;
899 case "turn.completed":
900 if (conflictsWithActiveTurn || missingTurnForActiveTurn) {
901 return false;
902 }
903 // Only the active turn may close the lifecycle state.
904 if (activeTurnId !== null && eventTurnId !== undefined) {
905 return sameId(activeTurnId, eventTurnId);
906 }
907 // If no active turn is tracked, accept completion scoped to this thread.
908 return true;
909 default:
910 return true;
911 }
912 })();
913 const acceptedTurnStartedSourcePlan =
914 event.type === "turn.started" && shouldApplyThreadLifecycle
915 ? yield* getSourceProposedPlanReferenceForAcceptedTurnStart(thread.id, eventTurnId)
916 : null;
917
918 if (
919 event.type === "session.started" ||
920 event.type === "session.state.changed" ||
921 event.type === "session.exited" ||
922 event.type === "thread.started" ||
923 event.type === "turn.started" ||
924 event.type === "turn.completed"
925 ) {
926 const nextActiveTurnId =
927 event.type === "turn.started"
928 ? (eventTurnId ?? null)
929 : event.type === "turn.completed" || event.type === "session.exited"
930 ? null
931 : activeTurnId;
932 const status = (() => {
893933 switch (event.type) {
934 case "session.state.changed":
935 return orchestrationSessionStatusFromRuntimeState(event.payload.state);
936 case "turn.started":
937 return "running";
894938 case "session.exited":
895 return true;
939 return "stopped";
940 case "turn.completed":
941 return normalizeRuntimeTurnState(event.payload.state) === "failed" ? "error" : "ready";
896942 case "session.started":
897943 case "thread.started":
898 return true;
899 case "turn.started":
900 return !conflictsWithActiveTurn;
901 case "turn.completed":
902 if (conflictsWithActiveTurn || missingTurnForActiveTurn) {
903 return false;
904 }
905 // Only the active turn may close the lifecycle state.
906 if (activeTurnId !== null && eventTurnId !== undefined) {
907 return sameId(activeTurnId, eventTurnId);
908 }
909 // If no active turn is tracked, accept completion scoped to this thread.
910 return true;
911 default:
912 return true;
944 // Provider thread/session start notifications can arrive during an
945 // active turn; preserve turn-running state in that case.
946 return activeTurnId !== null ? "running" : "ready";
913947 }
914948 })();
915 const acceptedTurnStartedSourcePlan =
916 event.type === "turn.started" && shouldApplyThreadLifecycle
917 ? yield* getSourceProposedPlanReferenceForAcceptedTurnStart(thread.id, eventTurnId)
918 : null;
919
920 if (
921 event.type === "session.started" ||
922 event.type === "session.state.changed" ||
923 event.type === "session.exited" ||
924 event.type === "thread.started" ||
925 event.type === "turn.started" ||
926 event.type === "turn.completed"
927 ) {
928 const nextActiveTurnId =
929 event.type === "turn.started"
930 ? (eventTurnId ?? null)
931 : event.type === "turn.completed" || event.type === "session.exited"
949 const lastError =
950 event.type === "session.state.changed" && event.payload.state === "error"
951 ? (event.payload.reason ?? thread.session?.lastError ?? "Provider session error")
952 : event.type === "turn.completed" &&
953 normalizeRuntimeTurnState(event.payload.state) === "failed"
954 ? (event.payload.errorMessage ?? thread.session?.lastError ?? "Turn failed")
955 : status === "ready"
932956 ? null
933 : activeTurnId;
934 const status = (() => {
935 switch (event.type) {
936 case "session.state.changed":
937 return orchestrationSessionStatusFromRuntimeState(event.payload.state);
938 case "turn.started":
939 return "running";
940 case "session.exited":
941 return "stopped";
942 case "turn.completed":
943 return normalizeRuntimeTurnState(event.payload.state) === "failed"
944 ? "error"
945 : "ready";
946 case "session.started":
947 case "thread.started":
948 // Provider thread/session start notifications can arrive during an
949 // active turn; preserve turn-running state in that case.
950 return activeTurnId !== null ? "running" : "ready";
951 }
952 })();
953 const lastError =
954 event.type === "session.state.changed" && event.payload.state === "error"
955 ? (event.payload.reason ?? thread.session?.lastError ?? "Provider session error")
956 : event.type === "turn.completed" &&
957 normalizeRuntimeTurnState(event.payload.state) === "failed"
958 ? (event.payload.errorMessage ?? thread.session?.lastError ?? "Turn failed")
959 : status === "ready"
960 ? null
961 : (thread.session?.lastError ?? null);
962
963 if (shouldApplyThreadLifecycle) {
964 if (event.type === "turn.started" && acceptedTurnStartedSourcePlan !== null) {
965 yield* markSourceProposedPlanImplemented(
966 acceptedTurnStartedSourcePlan.sourceThreadId,
967 acceptedTurnStartedSourcePlan.sourcePlanId,
968 thread.id,
969 now,
970 ).pipe(
971 Effect.catchCause((cause) =>
972 Effect.logWarning(
973 "provider runtime ingestion failed to mark source proposed plan",
974 {
975 eventId: event.eventId,
976 eventType: event.type,
977 cause: Cause.pretty(cause),
978 },
979 ),
980 ),
981 );
982 }
957 : (thread.session?.lastError ?? null);
958
959 if (shouldApplyThreadLifecycle) {
960 if (event.type === "turn.started" && acceptedTurnStartedSourcePlan !== null) {
961 yield* markSourceProposedPlanImplemented(
962 acceptedTurnStartedSourcePlan.sourceThreadId,
963 acceptedTurnStartedSourcePlan.sourcePlanId,
964 thread.id,
965 now,
966 ).pipe(
967 Effect.catchCause((cause) =>
968 Effect.logWarning("provider runtime ingestion failed to mark source proposed plan", {
969 eventId: event.eventId,
970 eventType: event.type,
971 cause: Cause.pretty(cause),
972 }),
973 ),
974 );
975 }
983976
984 yield* orchestrationEngine.dispatch({
985 type: "thread.session.set",
986 commandId: providerCommandId(event, "thread-session-set"),
977 yield* orchestrationEngine.dispatch({
978 type: "thread.session.set",
979 commandId: providerCommandId(event, "thread-session-set"),
980 threadId: thread.id,
981 session: {
987982 threadId: thread.id,
988 session: {
989 threadId: thread.id,
990 status,
991 providerName: event.provider,
992 runtimeMode: thread.session?.runtimeMode ?? "full-access",
993 activeTurnId: nextActiveTurnId,
994 lastError,
995 updatedAt: now,
996 },
997 createdAt: now,
998 });
999 }
983 status,
984 providerName: event.provider,
985 runtimeMode: thread.session?.runtimeMode ?? "full-access",
986 activeTurnId: nextActiveTurnId,
987 lastError,
988 updatedAt: now,
989 },
990 createdAt: now,
991 });
1000992 }
993 }
1001994
1002 const assistantDelta =
1003 event.type === "content.delta" && event.payload.streamKind === "assistant_text"
1004 ? event.payload.delta
1005 : undefined;
1006 const proposedPlanDelta =
1007 event.type === "turn.proposed.delta" ? event.payload.delta : undefined;
1008
1009 if (assistantDelta && assistantDelta.length > 0) {
1010 const assistantMessageId = MessageId.makeUnsafe(
1011 `assistant:${event.itemId ?? event.turnId ?? event.eventId}`,
1012 );
1013 const turnId = toTurnId(event.turnId);
1014 if (turnId) {
1015 yield* rememberAssistantMessageId(thread.id, turnId, assistantMessageId);
1016 }
995 const assistantDelta =
996 event.type === "content.delta" && event.payload.streamKind === "assistant_text"
997 ? event.payload.delta
998 : undefined;
999 const proposedPlanDelta =
1000 event.type === "turn.proposed.delta" ? event.payload.delta : undefined;
1001
1002 if (assistantDelta && assistantDelta.length > 0) {
1003 const assistantMessageId = MessageId.makeUnsafe(
1004 `assistant:${event.itemId ?? event.turnId ?? event.eventId}`,
1005 );
1006 const turnId = toTurnId(event.turnId);
1007 if (turnId) {
1008 yield* rememberAssistantMessageId(thread.id, turnId, assistantMessageId);
1009 }
10171010
1018 const assistantDeliveryMode: AssistantDeliveryMode = yield* Effect.map(
1019 serverSettingsService.getSettings,
1020 (settings) => (settings.enableAssistantStreaming ? "streaming" : "buffered"),
1021 );
1022 if (assistantDeliveryMode === "buffered") {
1023 const spillChunk = yield* appendBufferedAssistantText(assistantMessageId, assistantDelta);
1024 if (spillChunk.length > 0) {
1025 yield* orchestrationEngine.dispatch({
1026 type: "thread.message.assistant.delta",
1027 commandId: providerCommandId(event, "assistant-delta-buffer-spill"),
1028 threadId: thread.id,
1029 messageId: assistantMessageId,
1030 delta: spillChunk,
1031 ...(turnId ? { turnId } : {}),
1032 createdAt: now,
1033 });
1034 }
1035 } else {
1011 const assistantDeliveryMode: AssistantDeliveryMode = yield* Effect.map(
1012 serverSettingsService.getSettings,
1013 (settings) => (settings.enableAssistantStreaming ? "streaming" : "buffered"),
1014 );
1015 if (assistantDeliveryMode === "buffered") {
1016 const spillChunk = yield* appendBufferedAssistantText(assistantMessageId, assistantDelta);
1017 if (spillChunk.length > 0) {
10361018 yield* orchestrationEngine.dispatch({
10371019 type: "thread.message.assistant.delta",
1038 commandId: providerCommandId(event, "assistant-delta"),
1020 commandId: providerCommandId(event, "assistant-delta-buffer-spill"),
10391021 threadId: thread.id,
10401022 messageId: assistantMessageId,
1041 delta: assistantDelta,
1023 delta: spillChunk,
10421024 ...(turnId ? { turnId } : {}),
10431025 createdAt: now,
10441026 });
10451027 }
1046 }
1047
1048 if (proposedPlanDelta && proposedPlanDelta.length > 0) {
1049 const planId = proposedPlanIdFromEvent(event, thread.id);
1050 yield* appendBufferedProposedPlan(planId, proposedPlanDelta, now);
1051 }
1052
1053 const assistantCompletion =
1054 event.type === "item.completed" && event.payload.itemType === "assistant_message"
1055 ? {
1056 messageId: MessageId.makeUnsafe(
1057 `assistant:${event.itemId ?? event.turnId ?? event.eventId}`,
1058 ),
1059 fallbackText: event.payload.detail,
1060 }
1061 : undefined;
1062 const proposedPlanCompletion =
1063 event.type === "turn.proposed.completed"
1064 ? {
1065 planId: proposedPlanIdFromEvent(event, thread.id),
1066 turnId: toTurnId(event.turnId),
1067 planMarkdown: event.payload.planMarkdown,
1068 }
1069 : undefined;
1070
1071 if (assistantCompletion) {
1072 const assistantMessageId = assistantCompletion.messageId;
1073 const turnId = toTurnId(event.turnId);
1074 const existingAssistantMessage = thread.messages.find(
1075 (entry) => entry.id === assistantMessageId,
1076 );
1077 const shouldApplyFallbackCompletionText =
1078 !existingAssistantMessage || existingAssistantMessage.text.length === 0;
1079 if (turnId) {
1080 yield* rememberAssistantMessageId(thread.id, turnId, assistantMessageId);
1081 }
1082
1083 yield* finalizeAssistantMessage({
1084 event,
1028 } else {
1029 yield* orchestrationEngine.dispatch({
1030 type: "thread.message.assistant.delta",
1031 commandId: providerCommandId(event, "assistant-delta"),
10851032 threadId: thread.id,
10861033 messageId: assistantMessageId,
1034 delta: assistantDelta,
10871035 ...(turnId ? { turnId } : {}),
10881036 createdAt: now,
1089 commandTag: "assistant-complete",
1090 finalDeltaCommandTag: "assistant-delta-finalize",
1091 ...(assistantCompletion.fallbackText !== undefined && shouldApplyFallbackCompletionText
1092 ? { fallbackText: assistantCompletion.fallbackText }
1093 : {}),
10941037 });
1038 }
1039 }
10951040
1096 if (turnId) {
1097 yield* forgetAssistantMessageId(thread.id, turnId, assistantMessageId);
1098 }
1041 if (proposedPlanDelta && proposedPlanDelta.length > 0) {
1042 const planId = proposedPlanIdFromEvent(event, thread.id);
1043 yield* appendBufferedProposedPlan(planId, proposedPlanDelta, now);
1044 }
1045
1046 const assistantCompletion =
1047 event.type === "item.completed" && event.payload.itemType === "assistant_message"
1048 ? {
1049 messageId: MessageId.makeUnsafe(
1050 `assistant:${event.itemId ?? event.turnId ?? event.eventId}`,
1051 ),
1052 fallbackText: event.payload.detail,
1053 }
1054 : undefined;
1055 const proposedPlanCompletion =
1056 event.type === "turn.proposed.completed"
1057 ? {
1058 planId: proposedPlanIdFromEvent(event, thread.id),
1059 turnId: toTurnId(event.turnId),
1060 planMarkdown: event.payload.planMarkdown,
1061 }
1062 : undefined;
1063
1064 if (assistantCompletion) {
1065 const assistantMessageId = assistantCompletion.messageId;
1066 const turnId = toTurnId(event.turnId);
1067 const existingAssistantMessage = thread.messages.find(
1068 (entry) => entry.id === assistantMessageId,
1069 );
1070 const shouldApplyFallbackCompletionText =
1071 !existingAssistantMessage || existingAssistantMessage.text.length === 0;
1072 if (turnId) {
1073 yield* rememberAssistantMessageId(thread.id, turnId, assistantMessageId);
1074 }
1075
1076 yield* finalizeAssistantMessage({
1077 event,
1078 threadId: thread.id,
1079 messageId: assistantMessageId,
1080 ...(turnId ? { turnId } : {}),
1081 createdAt: now,
1082 commandTag: "assistant-complete",
1083 finalDeltaCommandTag: "assistant-delta-finalize",
1084 ...(assistantCompletion.fallbackText !== undefined && shouldApplyFallbackCompletionText
1085 ? { fallbackText: assistantCompletion.fallbackText }
1086 : {}),
1087 });
1088
1089 if (turnId) {
1090 yield* forgetAssistantMessageId(thread.id, turnId, assistantMessageId);
10991091 }
1092 }
1093
1094 if (proposedPlanCompletion) {
1095 yield* finalizeBufferedProposedPlan({
1096 event,
1097 threadId: thread.id,
1098 threadProposedPlans: thread.proposedPlans,
1099 planId: proposedPlanCompletion.planId,
1100 ...(proposedPlanCompletion.turnId ? { turnId: proposedPlanCompletion.turnId } : {}),
1101 fallbackMarkdown: proposedPlanCompletion.planMarkdown,
1102 updatedAt: now,
1103 });
1104 }
1105
1106 if (event.type === "turn.completed") {
1107 const turnId = toTurnId(event.turnId);
1108 if (turnId) {
1109 const assistantMessageIds = yield* getAssistantMessageIdsForTurn(thread.id, turnId);
1110 yield* Effect.forEach(
1111 assistantMessageIds,
1112 (assistantMessageId) =>
1113 finalizeAssistantMessage({
1114 event,
1115 threadId: thread.id,
1116 messageId: assistantMessageId,
1117 turnId,
1118 createdAt: now,
1119 commandTag: "assistant-complete-finalize",
1120 finalDeltaCommandTag: "assistant-delta-finalize-fallback",
1121 }),
1122 { concurrency: 1 },
1123 ).pipe(Effect.asVoid);
1124 yield* clearAssistantMessageIdsForTurn(thread.id, turnId);
11001125
1101 if (proposedPlanCompletion) {
11021126 yield* finalizeBufferedProposedPlan({
11031127 event,
11041128 threadId: thread.id,
11051129 threadProposedPlans: thread.proposedPlans,
1106 planId: proposedPlanCompletion.planId,
1107 ...(proposedPlanCompletion.turnId ? { turnId: proposedPlanCompletion.turnId } : {}),
1108 fallbackMarkdown: proposedPlanCompletion.planMarkdown,
1130 planId: proposedPlanIdForTurn(thread.id, turnId),
1131 turnId,
11091132 updatedAt: now,
11101133 });
11111134 }
1135 }
11121136
1113 if (event.type === "turn.completed") {
1114 const turnId = toTurnId(event.turnId);
1115 if (turnId) {
1116 const assistantMessageIds = yield* getAssistantMessageIdsForTurn(thread.id, turnId);
1117 yield* Effect.forEach(
1118 assistantMessageIds,
1119 (assistantMessageId) =>
1120 finalizeAssistantMessage({
1121 event,
1122 threadId: thread.id,
1123 messageId: assistantMessageId,
1124 turnId,
1125 createdAt: now,
1126 commandTag: "assistant-complete-finalize",
1127 finalDeltaCommandTag: "assistant-delta-finalize-fallback",
1128 }),
1129 { concurrency: 1 },
1130 ).pipe(Effect.asVoid);
1131 yield* clearAssistantMessageIdsForTurn(thread.id, turnId);
1137 if (event.type === "session.exited") {
1138 yield* clearTurnStateForSession(thread.id);
1139 }
1140
1141 if (event.type === "runtime.error") {
1142 const runtimeErrorMessage = event.payload.message;
11321143
1133 yield* finalizeBufferedProposedPlan({
1134 event,
1144 const shouldApplyRuntimeError = !STRICT_PROVIDER_LIFECYCLE_GUARD
1145 ? true
1146 : activeTurnId === null || eventTurnId === undefined || sameId(activeTurnId, eventTurnId);
1147
1148 if (shouldApplyRuntimeError) {
1149 yield* orchestrationEngine.dispatch({
1150 type: "thread.session.set",
1151 commandId: providerCommandId(event, "runtime-error-session-set"),
1152 threadId: thread.id,
1153 session: {
11351154 threadId: thread.id,
1136 threadProposedPlans: thread.proposedPlans,
1137 planId: proposedPlanIdForTurn(thread.id, turnId),
1138 turnId,
1155 status: "error",
1156 providerName: event.provider,
1157 runtimeMode: thread.session?.runtimeMode ?? "full-access",
1158 activeTurnId: eventTurnId ?? null,
1159 lastError: runtimeErrorMessage,
11391160 updatedAt: now,
1140 });
1141 }
1142 }
1143
1144 if (event.type === "session.exited") {
1145 yield* clearTurnStateForSession(thread.id);
1161 },
1162 createdAt: now,
1163 });
11461164 }
1165 }
11471166
1148 if (event.type === "runtime.error") {
1149 const runtimeErrorMessage = event.payload.message;
1150
1151 const shouldApplyRuntimeError = !STRICT_PROVIDER_LIFECYCLE_GUARD
1152 ? true
1153 : activeTurnId === null || eventTurnId === undefined || sameId(activeTurnId, eventTurnId);
1167 if (event.type === "thread.metadata.updated" && event.payload.name) {
1168 yield* orchestrationEngine.dispatch({
1169 type: "thread.meta.update",
1170 commandId: providerCommandId(event, "thread-meta-update"),
1171 threadId: thread.id,
1172 title: event.payload.name,
1173 });
1174 }
11541175
1155 if (shouldApplyRuntimeError) {
1176 if (event.type === "turn.diff.updated") {
1177 const turnId = toTurnId(event.turnId);
1178 if (turnId && (yield* isGitRepoForThread(thread.id))) {
1179 // Skip if a checkpoint already exists for this turn. A real
1180 // (non-placeholder) capture from CheckpointReactor should not
1181 // be clobbered, and dispatching a duplicate placeholder for the
1182 // same turnId would produce an unstable checkpointTurnCount.
1183 if (thread.checkpoints.some((c) => c.turnId === turnId)) {
1184 // Already tracked; no-op.
1185 } else {
1186 const assistantMessageId = MessageId.makeUnsafe(
1187 `assistant:${event.itemId ?? event.turnId ?? event.eventId}`,
1188 );
1189 const maxTurnCount = thread.checkpoints.reduce(
1190 (max, c) => Math.max(max, c.checkpointTurnCount),
1191 0,
1192 );
11561193 yield* orchestrationEngine.dispatch({
1157 type: "thread.session.set",
1158 commandId: providerCommandId(event, "runtime-error-session-set"),
1194 type: "thread.turn.diff.complete",
1195 commandId: providerCommandId(event, "thread-turn-diff-complete"),
11591196 threadId: thread.id,
1160 session: {
1161 threadId: thread.id,
1162 status: "error",
1163 providerName: event.provider,
1164 runtimeMode: thread.session?.runtimeMode ?? "full-access",
1165 activeTurnId: eventTurnId ?? null,
1166 lastError: runtimeErrorMessage,
1167 updatedAt: now,
1168 },
1197 turnId,
1198 completedAt: now,
1199 checkpointRef: CheckpointRef.makeUnsafe(`provider-diff:${event.eventId}`),
1200 status: "missing",
1201 files: [],
1202 assistantMessageId,
1203 checkpointTurnCount: maxTurnCount + 1,
11691204 createdAt: now,
11701205 });
11711206 }
11721207 }
1208 }
11731209
1174 if (event.type === "thread.metadata.updated" && event.payload.name) {
1175 yield* orchestrationEngine.dispatch({
1176 type: "thread.meta.update",
1177 commandId: providerCommandId(event, "thread-meta-update"),
1178 threadId: thread.id,
1179 title: event.payload.name,
1180 });
1181 }
1182
1183 if (event.type === "turn.diff.updated") {
1184 const turnId = toTurnId(event.turnId);
1185 if (turnId && (yield* isGitRepoForThread(thread.id))) {
1186 // Skip if a checkpoint already exists for this turn. A real
1187 // (non-placeholder) capture from CheckpointReactor should not
1188 // be clobbered, and dispatching a duplicate placeholder for the
1189 // same turnId would produce an unstable checkpointTurnCount.
1190 if (thread.checkpoints.some((c) => c.turnId === turnId)) {
1191 // Already tracked; no-op.
1192 } else {
1193 const assistantMessageId = MessageId.makeUnsafe(
1194 `assistant:${event.itemId ?? event.turnId ?? event.eventId}`,
1195 );
1196 const maxTurnCount = thread.checkpoints.reduce(
1197 (max, c) => Math.max(max, c.checkpointTurnCount),
1198 0,
1199 );
1200 yield* orchestrationEngine.dispatch({
1201 type: "thread.turn.diff.complete",
1202 commandId: providerCommandId(event, "thread-turn-diff-complete"),
1203 threadId: thread.id,
1204 turnId,
1205 completedAt: now,
1206 checkpointRef: CheckpointRef.makeUnsafe(`provider-diff:${event.eventId}`),
1207 status: "missing",
1208 files: [],
1209 assistantMessageId,
1210 checkpointTurnCount: maxTurnCount + 1,
1211 createdAt: now,
1212 });
1213 }
1214 }
1215 }
1216
1217 const activities = runtimeEventToActivities(event);
1218 yield* Effect.forEach(activities, (activity) =>
1219 orchestrationEngine.dispatch({
1220 type: "thread.activity.append",
1221 commandId: providerCommandId(event, "thread-activity-append"),
1222 threadId: thread.id,
1223 activity,
1224 createdAt: activity.createdAt,
1225 }),
1226 ).pipe(Effect.asVoid);
1227 });
1210 const activities = runtimeEventToActivities(event);
1211 yield* Effect.forEach(activities, (activity) =>
1212 orchestrationEngine.dispatch({
1213 type: "thread.activity.append",
1214 commandId: providerCommandId(event, "thread-activity-append"),
1215 threadId: thread.id,
1216 activity,
1217 createdAt: activity.createdAt,
1218 }),
1219 ).pipe(Effect.asVoid);
1220 });
12281221
12291222 const processDomainEvent = (_event: TurnStartRequestedDomainEvent) => Effect.void;
12301223
......@@ -1248,7 +1241,7 @@ const make = Effect.gen(function* () {
12481241
12491242 const worker = yield* makeDrainableWorker(processInputSafely);
12501243
1251 const start: ProviderRuntimeIngestionShape["start"] = Effect.gen(function* () {
1244 const start: ProviderRuntimeIngestionShape["start"] = Effect.fn("start")(function* () {
12521245 yield* Effect.forkScoped(
12531246 Stream.runForEach(providerService.streamEvents, (event) =>
12541247 worker.enqueue({ source: "runtime", event }),
......@@ -1272,5 +1265,5 @@ const make = Effect.gen(function* () {
12721265
12731266export const ProviderRuntimeIngestionLive = Layer.effect(
12741267 ProviderRuntimeIngestionService,
1275 make,
1268 make(),
12761269).pipe(Layer.provide(ProjectionTurnRepositoryLive));
apps/server/src/orchestration/Services/CheckpointReactor.ts+1-1
......@@ -22,7 +22,7 @@ export interface CheckpointReactorShape {
2222 * Consumes both orchestration-domain and provider-runtime events via an
2323 * internal queue.
2424 */
25 readonly start: Effect.Effect<void, never, Scope.Scope>;
25 readonly start: () => Effect.Effect<void, never, Scope.Scope>;
2626
2727 /**
2828 * Resolves when the internal processing queue is empty and idle.
apps/server/src/orchestration/Services/OrchestrationReactor.ts+1-1
......@@ -19,7 +19,7 @@ export interface OrchestrationReactorShape {
1919 * The returned effect must be run in a scope so all worker fibers can be
2020 * finalized on shutdown.
2121 */
22 readonly start: Effect.Effect<void, never, Scope.Scope>;
22 readonly start: () => Effect.Effect<void, never, Scope.Scope>;
2323}
2424
2525/**
apps/server/src/orchestration/Services/ProviderCommandReactor.ts+1-1
......@@ -22,7 +22,7 @@ export interface ProviderCommandReactorShape {
2222 * Filters orchestration domain events to provider-intent types before
2323 * processing.
2424 */
25 readonly start: Effect.Effect<void, never, Scope.Scope>;
25 readonly start: () => Effect.Effect<void, never, Scope.Scope>;
2626
2727 /**
2828 * Resolves when the internal processing queue is empty and idle.
apps/server/src/orchestration/Services/ProviderRuntimeIngestion.ts+1-1
......@@ -22,7 +22,7 @@ export interface ProviderRuntimeIngestionShape {
2222 * Uses an internal queue and continues after non-interrupt failures by
2323 * logging warnings.
2424 */
25 readonly start: Effect.Effect<void, never, Scope.Scope>;
25 readonly start: () => Effect.Effect<void, never, Scope.Scope>;
2626
2727 /**
2828 * Resolves when the internal processing queue is empty and idle.
apps/server/src/wsServer.ts+1-1
......@@ -646,7 +646,7 @@ export const createServer = Effect.fn(function* (): Effect.fn.Return<
646646 }),
647647 ).pipe(Effect.forkIn(subscriptionsScope));
648648
649 yield* Scope.provide(orchestrationReactor.start, subscriptionsScope);
649 yield* Scope.provide(orchestrationReactor.start(), subscriptionsScope);
650650 yield* readiness.markOrchestrationSubscriptionsReady;
651651
652652 let welcomeBootstrapProjectId: ProjectId | undefined;
docs/effect-fn-checklist.md+7-7
......@@ -46,7 +46,7 @@ Effect.fn("name")(
4646- [x] `apps/server/src/provider/Layers/CodexAdapter.ts`
4747- [x] `apps/server/src/git/Layers/GitCore.ts`
4848- [x] `apps/server/src/git/Layers/GitManager.ts`
49- [ ] `apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts`
49- [x] `apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts`
5050- [x] `apps/server/src/orchestration/Layers/ProjectionPipeline.ts`
5151- [ ] `apps/server/src/orchestration/Layers/OrchestrationEngine.ts`
5252- [ ] `apps/server/src/provider/Layers/EventNdjsonLogger.ts`
......@@ -115,12 +115,12 @@ Effect.fn("name")(
115115
116116### `apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts` (`14`)
117117
118- [ ] [finalizeAssistantMessage](/Users/julius/Development/Work/codething-mvp/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts#L680)
119- [ ] [upsertProposedPlan](/Users/julius/Development/Work/codething-mvp/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts#L722)
120- [ ] [finalizeBufferedProposedPlan](/Users/julius/Development/Work/codething-mvp/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts#L761)
121- [ ] [clearTurnStateForSession](/Users/julius/Development/Work/codething-mvp/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts#L800)
122- [ ] [processRuntimeEvent](/Users/julius/Development/Work/codething-mvp/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts#L908)
123- [ ] Nested callback wrappers in this file
118- [x] [finalizeAssistantMessage](/Users/julius/Development/Work/codething-mvp/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts#L680)
119- [x] [upsertProposedPlan](/Users/julius/Development/Work/codething-mvp/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts#L722)
120- [x] [finalizeBufferedProposedPlan](/Users/julius/Development/Work/codething-mvp/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts#L761)
121- [x] [clearTurnStateForSession](/Users/julius/Development/Work/codething-mvp/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts#L800)
122- [x] [processRuntimeEvent](/Users/julius/Development/Work/codething-mvp/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts#L908)
123- [x] Nested callback wrappers in this file
124124
125125### `apps/server/src/provider/Layers/CodexAdapter.ts` (`12`)
126126