| ... | @@ -203,6 +203,9 @@ export function codexProviderTurnTokenUsage( | ... | @@ -203,6 +203,9 @@ export function codexProviderTurnTokenUsage( |
| 203 | } | 203 | } |
| 204 | const DEFAULT_CODEX_SETTINGS = Schema.decodeSync(CodexSettings)({}); | 204 | const DEFAULT_CODEX_SETTINGS = Schema.decodeSync(CodexSettings)({}); |
| 205 | const CODEX_ASSISTANT_DELTA_FLUSH_INTERVAL_MS = 50; | 205 | const CODEX_ASSISTANT_DELTA_FLUSH_INTERVAL_MS = 50; |
| | 206 | // Each plan flush stores the whole plan three times (node, plan, turn item), so |
| | 207 | // flush at the cadence clients already see for assistant text. |
| | 208 | const CODEX_PLAN_DELTA_FLUSH_INTERVAL_MS = 400; |
| 206 | const CodexBackgroundTerminalTerminateResponse = Schema.Struct({ | 209 | const CodexBackgroundTerminalTerminateResponse = Schema.Struct({ |
| 207 | terminated: Schema.Boolean, | 210 | terminated: Schema.Boolean, |
| 208 | }); | 211 | }); |
| ... | @@ -1864,8 +1867,6 @@ export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* ( | ... | @@ -1864,8 +1867,6 @@ export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* ( |
| 1864 | const providerRetries = yield* Ref.make( | 1867 | const providerRetries = yield* Ref.make( |
| 1865 | new Map<ProviderTurnId, ActiveCodexProviderRetry>(), | 1868 | new Map<ProviderTurnId, ActiveCodexProviderRetry>(), |
| 1866 | ); | 1869 | ); |
| 1867 | // Streamed plan text per plan item, dropped when the item completes. | | |
| 1868 | const planDeltas = new Map<string, string>(); | | |
| 1869 | const planIds = yield* Ref.make(new Map<string, OrchestrationV2PlanArtifact["id"]>()); | 1870 | const planIds = yield* Ref.make(new Map<string, OrchestrationV2PlanArtifact["id"]>()); |
| 1870 | const pendingRuntimeRequests = yield* Ref.make( | 1871 | const pendingRuntimeRequests = yield* Ref.make( |
| 1871 | new Map<string, PendingCodexRuntimeRequest>(), | 1872 | new Map<string, PendingCodexRuntimeRequest>(), |
| ... | @@ -3948,6 +3949,43 @@ export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* ( | ... | @@ -3948,6 +3949,43 @@ export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* ( |
| 3948 | return { node, plan, turnItem }; | 3949 | return { node, plan, turnItem }; |
| 3949 | }); | 3950 | }); |
| 3950 | | 3951 | |
| | 3952 | // Streamed plan text, flushed at most once per interval. A turn that ends |
| | 3953 | // early leaves its partial plan streaming, as an unfinished plan item. |
| | 3954 | const planDeltas = yield* makeProviderTextDeltaCoalescer({ |
| | 3955 | flushIntervalMs: CODEX_PLAN_DELTA_FLUSH_INTERVAL_MS, |
| | 3956 | emit: (update) => |
| | 3957 | Effect.gen(function* () { |
| | 3958 | const resolved = yield* resolveItemEventContext(update.turnId); |
| | 3959 | if (resolved === undefined) return; |
| | 3960 | const interrupted = (yield* Ref.get(terminalizedNonCompletedNativeTurns)).has( |
| | 3961 | update.turnId, |
| | 3962 | ); |
| | 3963 | const artifacts = yield* buildProposedPlanArtifacts({ |
| | 3964 | context: resolved.context, |
| | 3965 | nativeItemId: update.itemId, |
| | 3966 | // A finished proposal stays active until Implement consumes it. |
| | 3967 | status: "active", |
| | 3968 | markdown: update.text, |
| | 3969 | completed: update.completed && !interrupted, |
| | 3970 | }); |
| | 3971 | yield* emitProviderEvent({ |
| | 3972 | type: "node.updated", |
| | 3973 | driver: CODEX_PROVIDER, |
| | 3974 | node: artifacts.node, |
| | 3975 | }); |
| | 3976 | yield* emitProviderEvent({ |
| | 3977 | type: "plan.updated", |
| | 3978 | driver: CODEX_PROVIDER, |
| | 3979 | plan: artifacts.plan, |
| | 3980 | }); |
| | 3981 | yield* emitProviderEvent({ |
| | 3982 | type: "turn_item.updated", |
| | 3983 | driver: CODEX_PROVIDER, |
| | 3984 | turnItem: artifacts.turnItem, |
| | 3985 | }); |
| | 3986 | }).pipe(Effect.orDie), |
| | 3987 | }); |
| | 3988 | |
| 3951 | const buildTodoListArtifacts = (input: { | 3989 | const buildTodoListArtifacts = (input: { |
| 3952 | readonly context: ActiveCodexTurnContext; | 3990 | readonly context: ActiveCodexTurnContext; |
| 3953 | readonly nativeItemId: string; | 3991 | readonly nativeItemId: string; |
| ... | @@ -4245,28 +4283,10 @@ export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* ( | ... | @@ -4245,28 +4283,10 @@ export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* ( |
| 4245 | return; | 4283 | return; |
| 4246 | } | 4284 | } |
| 4247 | yield* completeProviderRetry(context, yield* DateTime.now); | 4285 | yield* completeProviderRetry(context, yield* DateTime.now); |
| 4248 | const markdown = `${planDeltas.get(payload.itemId) ?? ""}${payload.delta}`; | 4286 | yield* planDeltas.append({ |
| 4249 | planDeltas.set(payload.itemId, markdown); | 4287 | turnId: payload.turnId, |
| 4250 | const artifacts = yield* buildProposedPlanArtifacts({ | 4288 | itemId: payload.itemId, |
| 4251 | context, | 4289 | delta: payload.delta, |
| 4252 | nativeItemId: payload.itemId, | | |
| 4253 | status: "active", | | |
| 4254 | markdown, | | |
| 4255 | }); | | |
| 4256 | yield* emitProviderEvent({ | | |
| 4257 | type: "node.updated", | | |
| 4258 | driver: CODEX_PROVIDER, | | |
| 4259 | node: artifacts.node, | | |
| 4260 | }); | | |
| 4261 | yield* emitProviderEvent({ | | |
| 4262 | type: "plan.updated", | | |
| 4263 | driver: CODEX_PROVIDER, | | |
| 4264 | plan: artifacts.plan, | | |
| 4265 | }); | | |
| 4266 | yield* emitProviderEvent({ | | |
| 4267 | type: "turn_item.updated", | | |
| 4268 | driver: CODEX_PROVIDER, | | |
| 4269 | turnItem: artifacts.turnItem, | | |
| 4270 | }); | 4290 | }); |
| 4271 | }).pipe(Effect.orDie), | 4291 | }).pipe(Effect.orDie), |
| 4272 | ); | 4292 | ); |
| ... | @@ -4916,33 +4936,10 @@ export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* ( | ... | @@ -4916,33 +4936,10 @@ export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* ( |
| 4916 | } | 4936 | } |
| 4917 | | 4937 | |
| 4918 | if (payload.item.type === "plan") { | 4938 | if (payload.item.type === "plan") { |
| 4919 | const markdown = | 4939 | yield* planDeltas.complete({ |
| 4920 | payload.item.text.length > 0 | 4940 | turnId: payload.turnId, |
| 4921 | ? payload.item.text | 4941 | itemId: payload.item.id, |
| 4922 | : (planDeltas.get(payload.item.id) ?? ""); | 4942 | ...(payload.item.text.length > 0 ? { finalText: payload.item.text } : {}), |
| 4923 | planDeltas.delete(payload.item.id); | | |
| 4924 | // A finished proposal stays active until Implement consumes it. | | |
| 4925 | const artifacts = yield* buildProposedPlanArtifacts({ | | |
| 4926 | context, | | |
| 4927 | nativeItemId: payload.item.id, | | |
| 4928 | status: "active", | | |
| 4929 | markdown, | | |
| 4930 | completed: true, | | |
| 4931 | }); | | |
| 4932 | yield* emitProviderEvent({ | | |
| 4933 | type: "node.updated", | | |
| 4934 | driver: CODEX_PROVIDER, | | |
| 4935 | node: artifacts.node, | | |
| 4936 | }); | | |
| 4937 | yield* emitProviderEvent({ | | |
| 4938 | type: "plan.updated", | | |
| 4939 | driver: CODEX_PROVIDER, | | |
| 4940 | plan: artifacts.plan, | | |
| 4941 | }); | | |
| 4942 | yield* emitProviderEvent({ | | |
| 4943 | type: "turn_item.updated", | | |
| 4944 | driver: CODEX_PROVIDER, | | |
| 4945 | turnItem: artifacts.turnItem, | | |
| 4946 | }); | 4943 | }); |
| 4947 | return; | 4944 | return; |
| 4948 | } | 4945 | } |
| ... | @@ -5780,6 +5777,7 @@ export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* ( | ... | @@ -5780,6 +5777,7 @@ export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* ( |
| 5780 | } | 5777 | } |
| 5781 | yield* agentMessageDeltas.flushTurn(input.nativeTurnId); | 5778 | yield* agentMessageDeltas.flushTurn(input.nativeTurnId); |
| 5782 | yield* reasoningDeltas.flushTurn(input.nativeTurnId); | 5779 | yield* reasoningDeltas.flushTurn(input.nativeTurnId); |
| | 5780 | yield* planDeltas.flushTurn(input.nativeTurnId); |
| 5783 | for (const [key, part] of reasoningParts) { | 5781 | for (const [key, part] of reasoningParts) { |
| 5784 | if (part.turnId === input.nativeTurnId) reasoningParts.delete(key); | 5782 | if (part.turnId === input.nativeTurnId) reasoningParts.delete(key); |
| 5785 | } | 5783 | } |