| author | |
| committer | |
| log | d76b24dd15a219666941ab1b4967d8f738adcda0 |
| tree | 9eddf7c38b4a6c31740a11ec210224df3d427acc |
| parent | e3723e06b5b154a3e0ede55c02e2cabc5301d3bf |
| signature | Signed by PGP key B5690EEEBB952194 |
26 files changed, 962 insertions(+), 24 deletions(-)
apps/mobile/src/lib/threadActivity.test.ts+23| ... | @@ -88,6 +88,29 @@ const nativeQuestion = { | ... | @@ -88,6 +88,29 @@ const nativeQuestion = { |
| 88 | } as const; | 88 | } as const; |
| 89 | 89 | ||
| 90 | describe("pending user input answers", () => { | 90 | describe("pending user input answers", () => { |
| 91 | it("accepts free-text answers to async questions without options", () => { | ||
| 92 | const question = { | ||
| 93 | id: "0", | ||
| 94 | header: "Question", | ||
| 95 | question: "What should it be named?", | ||
| 96 | options: [], | ||
| 97 | allowCustomAnswer: true, | ||
| 98 | multiSelect: false, | ||
| 99 | }; | ||
| 100 | const requested = makeActivity({ | ||
| 101 | id: EventId.make("async-question"), | ||
| 102 | kind: "user-input.requested", | ||
| 103 | summary: "User input requested", | ||
| 104 | createdAt: "2026-09-03T00:00:00.000Z", | ||
| 105 | payload: { requestId: "async-1", responseMode: "message", questions: [question] }, | ||
| 106 | }); | ||
| 107 | const questions = derivePendingUserInputs([requested])[0]?.questions; | ||
| 108 | expect(questions).toEqual([question]); | ||
| 109 | expect(buildPendingUserInputAnswers(questions!, { "0": { customAnswer: "Example" } })).toEqual({ | ||
| 110 | "0": "Example", | ||
| 111 | }); | ||
| 112 | }); | ||
| 113 | |||
| 91 | it("preserves native choice values and custom-answer rules from activities", () => { | 114 | it("preserves native choice values and custom-answer rules from activities", () => { |
| 92 | const requested = makeActivity({ | 115 | const requested = makeActivity({ |
| 93 | id: EventId.make("native-question"), | 116 | id: EventId.make("native-question"), |
apps/mobile/src/lib/threadActivity.ts+1-1| ... | @@ -245,7 +245,7 @@ function parseUserInputQuestions( | ... | @@ -245,7 +245,7 @@ function parseUserInputQuestions( |
| 245 | }; | 245 | }; |
| 246 | }) | 246 | }) |
| 247 | .filter((option): option is UserInputQuestion["options"][number] => option !== null); | 247 | .filter((option): option is UserInputQuestion["options"][number] => option !== null); |
| 248 | if (options.length === 0) { | 248 | if (options.length === 0 && question.allowCustomAnswer === false) { |
| 249 | return null; | 249 | return null; |
| 250 | } | 250 | } |
| 251 | return { | 251 | return { |
apps/server/src/checkpointing/CheckpointDiffQuery.test.ts+5| ... | @@ -75,6 +75,7 @@ describe("CheckpointDiffQuery.layer", () => { | ... | @@ -75,6 +75,7 @@ describe("CheckpointDiffQuery.layer", () => { |
| 75 | Layer.provideMerge(Layer.succeed(CheckpointStore.CheckpointStore, checkpointStore)), | 75 | Layer.provideMerge(Layer.succeed(CheckpointStore.CheckpointStore, checkpointStore)), |
| 76 | Layer.provideMerge( | 76 | Layer.provideMerge( |
| 77 | Layer.succeed(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { | 77 | Layer.succeed(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { |
| 78 | getUserInputActivity: () => Effect.die("unused"), | ||
| 78 | getCommandReadModel: () => | 79 | getCommandReadModel: () => |
| 79 | Effect.die("CheckpointDiffQuery should not request the command read model"), | 80 | Effect.die("CheckpointDiffQuery should not request the command read model"), |
| 80 | getSnapshot: () => | 81 | getSnapshot: () => |
| ... | @@ -185,6 +186,7 @@ describe("CheckpointDiffQuery.layer", () => { | ... | @@ -185,6 +186,7 @@ describe("CheckpointDiffQuery.layer", () => { |
| 185 | Layer.provideMerge(Layer.succeed(CheckpointStore.CheckpointStore, checkpointStore)), | 186 | Layer.provideMerge(Layer.succeed(CheckpointStore.CheckpointStore, checkpointStore)), |
| 186 | Layer.provideMerge( | 187 | Layer.provideMerge( |
| 187 | Layer.succeed(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { | 188 | Layer.succeed(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { |
| 189 | getUserInputActivity: () => Effect.die("unused"), | ||
| 188 | getCommandReadModel: () => | 190 | getCommandReadModel: () => |
| 189 | Effect.die("CheckpointDiffQuery should not request the command read model"), | 191 | Effect.die("CheckpointDiffQuery should not request the command read model"), |
| 190 | getSnapshot: () => | 192 | getSnapshot: () => |
| ... | @@ -270,6 +272,7 @@ describe("CheckpointDiffQuery.layer", () => { | ... | @@ -270,6 +272,7 @@ describe("CheckpointDiffQuery.layer", () => { |
| 270 | Layer.provideMerge(Layer.succeed(CheckpointStore.CheckpointStore, checkpointStore)), | 272 | Layer.provideMerge(Layer.succeed(CheckpointStore.CheckpointStore, checkpointStore)), |
| 271 | Layer.provideMerge( | 273 | Layer.provideMerge( |
| 272 | Layer.succeed(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { | 274 | Layer.succeed(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { |
| 275 | getUserInputActivity: () => Effect.die("unused"), | ||
| 273 | getCommandReadModel: () => | 276 | getCommandReadModel: () => |
| 274 | Effect.die("CheckpointDiffQuery should not request the command read model"), | 277 | Effect.die("CheckpointDiffQuery should not request the command read model"), |
| 275 | getSnapshot: () => | 278 | getSnapshot: () => |
| ... | @@ -340,6 +343,7 @@ describe("CheckpointDiffQuery.layer", () => { | ... | @@ -340,6 +343,7 @@ describe("CheckpointDiffQuery.layer", () => { |
| 340 | Layer.provideMerge(Layer.succeed(CheckpointStore.CheckpointStore, checkpointStore)), | 343 | Layer.provideMerge(Layer.succeed(CheckpointStore.CheckpointStore, checkpointStore)), |
| 341 | Layer.provideMerge( | 344 | Layer.provideMerge( |
| 342 | Layer.succeed(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { | 345 | Layer.succeed(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { |
| 346 | getUserInputActivity: () => Effect.die("unused"), | ||
| 343 | getCommandReadModel: () => | 347 | getCommandReadModel: () => |
| 344 | Effect.die("CheckpointDiffQuery should not request the command read model"), | 348 | Effect.die("CheckpointDiffQuery should not request the command read model"), |
| 345 | getSnapshot: () => | 349 | getSnapshot: () => |
| ... | @@ -395,6 +399,7 @@ describe("CheckpointDiffQuery.layer", () => { | ... | @@ -395,6 +399,7 @@ describe("CheckpointDiffQuery.layer", () => { |
| 395 | Layer.provideMerge(Layer.succeed(CheckpointStore.CheckpointStore, checkpointStore)), | 399 | Layer.provideMerge(Layer.succeed(CheckpointStore.CheckpointStore, checkpointStore)), |
| 396 | Layer.provideMerge( | 400 | Layer.provideMerge( |
| 397 | Layer.succeed(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { | 401 | Layer.succeed(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { |
| 402 | getUserInputActivity: () => Effect.die("unused"), | ||
| 398 | getCommandReadModel: () => | 403 | getCommandReadModel: () => |
| 399 | Effect.die("CheckpointDiffQuery should not request the command read model"), | 404 | Effect.die("CheckpointDiffQuery should not request the command read model"), |
| 400 | getSnapshot: () => | 405 | getSnapshot: () => |
apps/server/src/orchestration/Layers/OrchestrationEngine.test.ts+211-5| ... | @@ -1,4 +1,11 @@ | ... | @@ -1,4 +1,11 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 2 | import * as NodeFSP from "node:fs/promises"; | ||
| 3 | import * as NodeOS from "node:os"; | ||
| 4 | import * as NodePath from "node:path"; | ||
| 5 | |||
| 1 | import { | 6 | import { |
| 7 | ApprovalRequestId, | ||
| 8 | EventId, | ||
| 2 | CheckpointRef, | 9 | CheckpointRef, |
| 3 | CommandId, | 10 | CommandId, |
| 4 | DEFAULT_PROVIDER_INTERACTION_MODE, | 11 | DEFAULT_PROVIDER_INTERACTION_MODE, |
| ... | @@ -25,7 +32,10 @@ import { PersistenceSqlError } from "../../persistence/Errors.ts"; | ... | @@ -25,7 +32,10 @@ import { PersistenceSqlError } from "../../persistence/Errors.ts"; |
| 25 | import { OrchestrationCommandReceiptRepositoryLive } from "../../persistence/Layers/OrchestrationCommandReceipts.ts"; | 32 | import { OrchestrationCommandReceiptRepositoryLive } from "../../persistence/Layers/OrchestrationCommandReceipts.ts"; |
| 26 | import * as OrchestrationCommandReceipts from "../../persistence/Services/OrchestrationCommandReceipts.ts"; | 33 | import * as OrchestrationCommandReceipts from "../../persistence/Services/OrchestrationCommandReceipts.ts"; |
| 27 | import { OrchestrationEventStoreLive } from "../../persistence/Layers/OrchestrationEventStore.ts"; | 34 | import { OrchestrationEventStoreLive } from "../../persistence/Layers/OrchestrationEventStore.ts"; |
| 28 | import { SqlitePersistenceMemory } from "../../persistence/Layers/Sqlite.ts"; | 35 | import { |
| 36 | makeSqlitePersistenceLive, | ||
| 37 | SqlitePersistenceMemory, | ||
| 38 | } from "../../persistence/Layers/Sqlite.ts"; | ||
| 29 | import { | 39 | import { |
| 30 | OrchestrationEventStore, | 40 | OrchestrationEventStore, |
| 31 | type OrchestrationEventStoreShape, | 41 | type OrchestrationEventStoreShape, |
| ... | @@ -49,7 +59,10 @@ const asMessageId = (value: string): MessageId => MessageId.make(value); | ... | @@ -49,7 +59,10 @@ const asMessageId = (value: string): MessageId => MessageId.make(value); |
| 49 | const asTurnId = (value: string): TurnId => TurnId.make(value); | 59 | const asTurnId = (value: string): TurnId => TurnId.make(value); |
| 50 | const asCheckpointRef = (value: string): CheckpointRef => CheckpointRef.make(value); | 60 | const asCheckpointRef = (value: string): CheckpointRef => CheckpointRef.make(value); |
| 51 | 61 | ||
| 52 | function makeOrchestrationLayer() { | 62 | function makeOrchestrationLayer(databasePath?: string) { |
| 63 | const persistence = databasePath | ||
| 64 | ? makeSqlitePersistenceLive(databasePath) | ||
| 65 | : SqlitePersistenceMemory; | ||
| 53 | const ServerConfigLayer = ServerConfig.layerTest(process.cwd(), { | 66 | const ServerConfigLayer = ServerConfig.layerTest(process.cwd(), { |
| 54 | prefix: "t3-orchestration-engine-test-", | 67 | prefix: "t3-orchestration-engine-test-", |
| 55 | }); | 68 | }); |
| ... | @@ -65,19 +78,21 @@ function makeOrchestrationLayer() { | ... | @@ -65,19 +78,21 @@ function makeOrchestrationLayer() { |
| 65 | Layer.provide(OrchestrationEventStoreLive), | 78 | Layer.provide(OrchestrationEventStoreLive), |
| 66 | Layer.provideMerge(OrchestrationCommandReceiptRepositoryLive), | 79 | Layer.provideMerge(OrchestrationCommandReceiptRepositoryLive), |
| 67 | Layer.provide(RepositoryIdentityResolver.layer), | 80 | Layer.provide(RepositoryIdentityResolver.layer), |
| 68 | Layer.provide(SqlitePersistenceMemory), | 81 | Layer.provide(persistence), |
| 69 | Layer.provideMerge(ServerConfigLayer), | 82 | Layer.provideMerge(ServerConfigLayer), |
| 70 | Layer.provideMerge(NodeServices.layer), | 83 | Layer.provideMerge(NodeServices.layer), |
| 71 | ); | 84 | ); |
| 72 | } | 85 | } |
| 73 | 86 | ||
| 74 | async function createOrchestrationSystem() { | 87 | async function createOrchestrationSystem(databasePath?: string) { |
| 75 | const runtime = ManagedRuntime.make(makeOrchestrationLayer()); | 88 | const runtime = ManagedRuntime.make(makeOrchestrationLayer(databasePath)); |
| 76 | const engine = await runtime.runPromise(Effect.service(OrchestrationEngineService)); | 89 | const engine = await runtime.runPromise(Effect.service(OrchestrationEngineService)); |
| 77 | const snapshotQuery = await runtime.runPromise(Effect.service(ProjectionSnapshotQuery)); | 90 | const snapshotQuery = await runtime.runPromise(Effect.service(ProjectionSnapshotQuery)); |
| 78 | return { | 91 | return { |
| 79 | engine, | 92 | engine, |
| 80 | readModel: () => runtime.runPromise(snapshotQuery.getSnapshot()), | 93 | readModel: () => runtime.runPromise(snapshotQuery.getSnapshot()), |
| 94 | readThread: (threadId: ThreadId) => | ||
| 95 | runtime.runPromise(snapshotQuery.getThreadDetailById(threadId)), | ||
| 81 | run: <A, E>(effect: Effect.Effect<A, E>) => runtime.runPromise(effect), | 96 | run: <A, E>(effect: Effect.Effect<A, E>) => runtime.runPromise(effect), |
| 82 | dispose: () => runtime.dispose(), | 97 | dispose: () => runtime.dispose(), |
| 83 | }; | 98 | }; |
| ... | @@ -99,6 +114,196 @@ const hasMetricSnapshot = ( | ... | @@ -99,6 +114,196 @@ const hasMetricSnapshot = ( |
| 99 | ); | 114 | ); |
| 100 | 115 | ||
| 101 | describe("OrchestrationEngine", () => { | 116 | describe("OrchestrationEngine", () => { |
| 117 | it.each(["running", "stopped"] as const)( | ||
| 118 | "sends async answers with a %s session and rejects old duplicate replies", | ||
| 119 | async (status) => { | ||
| 120 | const directory = await NodeFSP.mkdtemp( | ||
| 121 | NodePath.join(NodeOS.tmpdir(), "t3-async-questions-"), | ||
| 122 | ); | ||
| 123 | const databasePath = NodePath.join(directory, "state.sqlite"); | ||
| 124 | let system = await createOrchestrationSystem(databasePath); | ||
| 125 | const threadId = ThreadId.make("async-thread"); | ||
| 126 | const projectId = ProjectId.make("async-project"); | ||
| 127 | const requestId = ApprovalRequestId.make("codex-async:question-1"); | ||
| 128 | try { | ||
| 129 | await system.run( | ||
| 130 | system.engine.dispatch({ | ||
| 131 | type: "project.create", | ||
| 132 | commandId: CommandId.make("async-project"), | ||
| 133 | projectId, | ||
| 134 | title: "Async questions", | ||
| 135 | workspaceRoot: "/tmp/async-questions", | ||
| 136 | createdAt: now(), | ||
| 137 | }), | ||
| 138 | ); | ||
| 139 | await system.run( | ||
| 140 | system.engine.dispatch({ | ||
| 141 | type: "thread.create", | ||
| 142 | commandId: CommandId.make("async-thread"), | ||
| 143 | threadId, | ||
| 144 | projectId, | ||
| 145 | title: "Async questions", | ||
| 146 | modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.4" }, | ||
| 147 | runtimeMode: "full-access", | ||
| 148 | interactionMode: "default", | ||
| 149 | branch: null, | ||
| 150 | worktreePath: null, | ||
| 151 | createdAt: now(), | ||
| 152 | }), | ||
| 153 | ); | ||
| 154 | await system.run( | ||
| 155 | system.engine.dispatch({ | ||
| 156 | type: "thread.session.set", | ||
| 157 | commandId: CommandId.make("async-session"), | ||
| 158 | threadId, | ||
| 159 | createdAt: now(), | ||
| 160 | session: { | ||
| 161 | threadId, | ||
| 162 | status, | ||
| 163 | providerName: "codex", | ||
| 164 | runtimeMode: "full-access", | ||
| 165 | activeTurnId: status === "running" ? TurnId.make("turn-1") : null, | ||
| 166 | lastError: null, | ||
| 167 | updatedAt: now(), | ||
| 168 | }, | ||
| 169 | }), | ||
| 170 | ); | ||
| 171 | await system.run( | ||
| 172 | system.engine.dispatch({ | ||
| 173 | type: "thread.activity.append", | ||
| 174 | commandId: CommandId.make("async-question"), | ||
| 175 | threadId, | ||
| 176 | createdAt: now(), | ||
| 177 | activity: { | ||
| 178 | id: EventId.make("async-question"), | ||
| 179 | kind: "user-input.requested", | ||
| 180 | summary: "User input requested", | ||
| 181 | tone: "info", | ||
| 182 | turnId: TurnId.make("turn-1"), | ||
| 183 | createdAt: now(), | ||
| 184 | payload: { | ||
| 185 | requestId, | ||
| 186 | responseMode: "message", | ||
| 187 | questions: [ | ||
| 188 | { | ||
| 189 | id: "0", | ||
| 190 | header: "Question", | ||
| 191 | question: "Which package manager?", | ||
| 192 | options: [{ label: "pnpm", description: "" }], | ||
| 193 | }, | ||
| 194 | { | ||
| 195 | id: "1", | ||
| 196 | header: "Question", | ||
| 197 | question: "What should it be named?", | ||
| 198 | options: [], | ||
| 199 | }, | ||
| 200 | ], | ||
| 201 | }, | ||
| 202 | }, | ||
| 203 | }), | ||
| 204 | ); | ||
| 205 | const appendWork = async (prefix: string, createdAt: string) => { | ||
| 206 | for (let index = 0; index < 501; index += 1) { | ||
| 207 | await system.run( | ||
| 208 | system.engine.dispatch({ | ||
| 209 | type: "thread.activity.append", | ||
| 210 | commandId: CommandId.make(`${prefix}-${index}`), | ||
| 211 | threadId, | ||
| 212 | createdAt, | ||
| 213 | activity: { | ||
| 214 | id: EventId.make(`${prefix}-${index}`), | ||
| 215 | kind: "tool.completed", | ||
| 216 | summary: "Work continued", | ||
| 217 | payload: {}, | ||
| 218 | tone: "info", | ||
| 219 | turnId: TurnId.make("turn-1"), | ||
| 220 | createdAt, | ||
| 221 | }, | ||
| 222 | }), | ||
| 223 | ); | ||
| 224 | } | ||
| 225 | }; | ||
| 226 | await appendWork("work", "2026-01-01T00:00:01.000Z"); | ||
| 227 | const before = await system.readModel(); | ||
| 228 | expect( | ||
| 229 | before.threads[0]?.activities.some((activity) => activity.id === "async-question"), | ||
| 230 | ).toBe(true); | ||
| 231 | if (status === "stopped") { | ||
| 232 | await system.dispose(); | ||
| 233 | system = await createOrchestrationSystem(databasePath); | ||
| 234 | } | ||
| 235 | const response = { | ||
| 236 | type: "thread.user-input.respond" as const, | ||
| 237 | commandId: CommandId.make("async-response"), | ||
| 238 | threadId, | ||
| 239 | requestId, | ||
| 240 | answers: { "0": "pnpm", "1": "Example" }, | ||
| 241 | createdAt: "2026-01-01T00:00:02.000Z", | ||
| 242 | }; | ||
| 243 | await expect( | ||
| 244 | system.run( | ||
| 245 | system.engine.dispatch({ | ||
| 246 | ...response, | ||
| 247 | commandId: CommandId.make("incomplete-answer"), | ||
| 248 | answers: { "0": "pnpm" }, | ||
| 249 | }), | ||
| 250 | ), | ||
| 251 | ).rejects.toThrow("Answer each question before sending."); | ||
| 252 | await system.run(system.engine.dispatch(response)); | ||
| 253 | const after = await system.readModel(); | ||
| 254 | const userMessages = after.threads[0]?.messages.filter( | ||
| 255 | (message) => message.role === "user", | ||
| 256 | ); | ||
| 257 | expect(userMessages).toHaveLength(1); | ||
| 258 | expect(userMessages?.[0]?.text).toBe( | ||
| 259 | "Which package manager?\npnpm\n\nWhat should it be named?\nExample", | ||
| 260 | ); | ||
| 261 | expect( | ||
| 262 | after.threads[0]?.activities.find((activity) => activity.kind === "user-input.resolved") | ||
| 263 | ?.payload, | ||
| 264 | ).toMatchObject({ requestId, responseMode: "message", answers: response.answers }); | ||
| 265 | const events = await system.run(Stream.runCollect(system.engine.readEvents(0))); | ||
| 266 | expect( | ||
| 267 | Array.from(events) | ||
| 268 | .filter((event) => event.commandId === response.commandId) | ||
| 269 | .map((event) => event.type), | ||
| 270 | ).toEqual([ | ||
| 271 | "thread.activity-appended", | ||
| 272 | "thread.message-sent", | ||
| 273 | "thread.turn-start-requested", | ||
| 274 | ]); | ||
| 275 | await expect( | ||
| 276 | system.run( | ||
| 277 | system.engine.dispatch({ | ||
| 278 | ...response, | ||
| 279 | commandId: CommandId.make("second-client-reply"), | ||
| 280 | }), | ||
| 281 | ), | ||
| 282 | ).rejects.toThrow("This question has already been answered."); | ||
| 283 | await appendWork("later-work", "2026-01-01T00:00:03.000Z"); | ||
| 284 | const afterEviction = Option.getOrThrow(await system.readThread(threadId)); | ||
| 285 | expect( | ||
| 286 | afterEviction.activities.some((activity) => activity.kind === "user-input.resolved"), | ||
| 287 | ).toBe(false); | ||
| 288 | if (status === "stopped") { | ||
| 289 | await system.dispose(); | ||
| 290 | system = await createOrchestrationSystem(databasePath); | ||
| 291 | } | ||
| 292 | await expect( | ||
| 293 | system.run( | ||
| 294 | system.engine.dispatch({ | ||
| 295 | ...response, | ||
| 296 | commandId: CommandId.make("reply-after-eviction"), | ||
| 297 | }), | ||
| 298 | ), | ||
| 299 | ).rejects.toThrow("This question has already been answered."); | ||
| 300 | } finally { | ||
| 301 | await system.dispose(); | ||
| 302 | await NodeFSP.rm(directory, { recursive: true, force: true }); | ||
| 303 | } | ||
| 304 | }, | ||
| 305 | ); | ||
| 306 | |||
| 102 | it("bootstraps command handling from persisted projections without reading the full snapshot", async () => { | 307 | it("bootstraps command handling from persisted projections without reading the full snapshot", async () => { |
| 103 | let nextSequence = 8; | 308 | let nextSequence = 8; |
| 104 | const eventStore: OrchestrationEventStoreShape = { | 309 | const eventStore: OrchestrationEventStoreShape = { |
| ... | @@ -183,6 +388,7 @@ describe("OrchestrationEngine", () => { | ... | @@ -183,6 +388,7 @@ describe("OrchestrationEngine", () => { |
| 183 | const layer = OrchestrationEngineLive.pipe( | 388 | const layer = OrchestrationEngineLive.pipe( |
| 184 | Layer.provide( | 389 | Layer.provide( |
| 185 | Layer.succeed(ProjectionSnapshotQuery, { | 390 | Layer.succeed(ProjectionSnapshotQuery, { |
| 391 | getUserInputActivity: () => Effect.die("unused"), | ||
| 186 | getCommandReadModel: () => Effect.succeed(commandReadModel), | 392 | getCommandReadModel: () => Effect.succeed(commandReadModel), |
| 187 | getSnapshot: () => | 393 | getSnapshot: () => |
| 188 | Effect.sync(() => { | 394 | Effect.sync(() => { |
apps/server/src/orchestration/Layers/OrchestrationEngine.ts+9| ... | @@ -195,9 +195,18 @@ const makeOrchestrationEngine = Effect.gen(function* () { | ... | @@ -195,9 +195,18 @@ const makeOrchestrationEngine = Effect.gen(function* () { |
| 195 | }); | 195 | }); |
| 196 | } | 196 | } |
| 197 | 197 | ||
| 198 | // Command snapshots omit activities at startup and cap them while running. | ||
| 199 | // Read this request's durable state before deciding how to send the answer. | ||
| 200 | const userInputActivity = | ||
| 201 | envelope.command.type === "thread.user-input.respond" | ||
| 202 | ? yield* projectionSnapshotQuery.getUserInputActivity(envelope.command) | ||
| 203 | : Option.none(); | ||
| 198 | const eventBase = yield* decideOrchestrationCommand({ | 204 | const eventBase = yield* decideOrchestrationCommand({ |
| 199 | command: envelope.command, | 205 | command: envelope.command, |
| 200 | readModel: commandReadModel, | 206 | readModel: commandReadModel, |
| 207 | ...(Option.isSome(userInputActivity) | ||
| 208 | ? { userInputActivity: userInputActivity.value } | ||
| 209 | : {}), | ||
| 201 | }).pipe( | 210 | }).pipe( |
| 202 | Effect.provideService(Crypto.Crypto, crypto), | 211 | Effect.provideService(Crypto.Crypto, crypto), |
| 203 | Effect.mapError((cause) => | 212 | Effect.mapError((cause) => |
apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts+36| ... | @@ -1,4 +1,5 @@ | ... | @@ -1,4 +1,5 @@ |
| 1 | import { | 1 | import { |
| 2 | ApprovalRequestId, | ||
| 2 | ChatAttachment, | 3 | ChatAttachment, |
| 3 | CheckpointRef, | 4 | CheckpointRef, |
| 4 | IsoDateTime, | 5 | IsoDateTime, |
| ... | @@ -1119,6 +1120,40 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { | ... | @@ -1119,6 +1120,40 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { |
| 1119 | `, | 1120 | `, |
| 1120 | }); | 1121 | }); |
| 1121 | 1122 | ||
| 1123 | const getUserInputActivityRow = SqlSchema.findOneOption({ | ||
| 1124 | Request: Schema.Struct({ threadId: ThreadId, requestId: ApprovalRequestId }), | ||
| 1125 | Result: ProjectionThreadActivityDbRowSchema, | ||
| 1126 | execute: ({ threadId, requestId }) => sql` | ||
| 1127 | SELECT | ||
| 1128 | activity_id AS "activityId", | ||
| 1129 | thread_id AS "threadId", | ||
| 1130 | turn_id AS "turnId", | ||
| 1131 | tone, | ||
| 1132 | kind, | ||
| 1133 | summary, | ||
| 1134 | payload_json AS "payload", | ||
| 1135 | sequence, | ||
| 1136 | created_at AS "createdAt" | ||
| 1137 | FROM projection_thread_activities | ||
| 1138 | WHERE thread_id = ${threadId} | ||
| 1139 | AND kind IN ('user-input.requested', 'user-input.resolved') | ||
| 1140 | AND json_extract(payload_json, '$.requestId') = ${requestId} | ||
| 1141 | ORDER BY sequence DESC, created_at DESC, activity_id DESC | ||
| 1142 | LIMIT 1 | ||
| 1143 | `, | ||
| 1144 | }); | ||
| 1145 | |||
| 1146 | const getUserInputActivity: ProjectionSnapshotQueryShape["getUserInputActivity"] = (input) => | ||
| 1147 | getUserInputActivityRow(input).pipe( | ||
| 1148 | Effect.map(Option.map(mapThreadActivityRow)), | ||
| 1149 | Effect.mapError( | ||
| 1150 | toPersistenceSqlOrDecodeError( | ||
| 1151 | "ProjectionSnapshotQuery.getUserInputActivity:query", | ||
| 1152 | "ProjectionSnapshotQuery.getUserInputActivity:decodeRow", | ||
| 1153 | ), | ||
| 1154 | ), | ||
| 1155 | ); | ||
| 1156 | |||
| 1122 | const listThreadActivityIdsByThread = SqlSchema.findAll({ | 1157 | const listThreadActivityIdsByThread = SqlSchema.findAll({ |
| 1123 | Request: ThreadIdLookupInput, | 1158 | Request: ThreadIdLookupInput, |
| 1124 | Result: ProjectionThreadActivityIdRowSchema, | 1159 | Result: ProjectionThreadActivityIdRowSchema, |
| ... | @@ -3151,6 +3186,7 @@ pending_approval_requests AS ( | ... | @@ -3151,6 +3186,7 @@ pending_approval_requests AS ( |
| 3151 | 3186 | ||
| 3152 | return { | 3187 | return { |
| 3153 | getCommandReadModel, | 3188 | getCommandReadModel, |
| 3189 | getUserInputActivity, | ||
| 3154 | getSnapshot, | 3190 | getSnapshot, |
| 3155 | getShellSnapshot, | 3191 | getShellSnapshot, |
| 3156 | getArchivedShellSnapshot, | 3192 | getArchivedShellSnapshot, |
apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts+50| ... | @@ -2108,6 +2108,56 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -2108,6 +2108,56 @@ describe("ProviderRuntimeIngestion", () => { |
| 2108 | expect(message?.streaming).toBe(false); | 2108 | expect(message?.streaming).toBe(false); |
| 2109 | }); | 2109 | }); |
| 2110 | 2110 | ||
| 2111 | it("keeps streaming while an async question is pending", async () => { | ||
| 2112 | const harness = await createHarness({ serverSettings: { enableLegacyTokenStreaming: true } }); | ||
| 2113 | const base = { | ||
| 2114 | provider: ProviderDriverKind.make("codex"), | ||
| 2115 | createdAt: "2026-01-01T00:00:00.000Z", | ||
| 2116 | threadId: asThreadId("thread-1"), | ||
| 2117 | turnId: asTurnId("turn-async"), | ||
| 2118 | }; | ||
| 2119 | harness.emit({ ...base, type: "turn.started", eventId: asEventId("async-start") }); | ||
| 2120 | harness.emit({ | ||
| 2121 | ...base, | ||
| 2122 | type: "content.delta", | ||
| 2123 | eventId: asEventId("async-before"), | ||
| 2124 | itemId: asItemId("message-1"), | ||
| 2125 | payload: { streamKind: "assistant_text", delta: "Before. " }, | ||
| 2126 | }); | ||
| 2127 | harness.emit({ | ||
| 2128 | ...base, | ||
| 2129 | type: "user-input.requested", | ||
| 2130 | eventId: asEventId("async-request"), | ||
| 2131 | requestId: ApprovalRequestId.make("codex-async:question-1"), | ||
| 2132 | payload: { | ||
| 2133 | responseMode: "message", | ||
| 2134 | questions: [ | ||
| 2135 | { | ||
| 2136 | id: "0", | ||
| 2137 | header: "Question", | ||
| 2138 | question: "Which name?", | ||
| 2139 | options: [], | ||
| 2140 | allowCustomAnswer: true, | ||
| 2141 | }, | ||
| 2142 | ], | ||
| 2143 | }, | ||
| 2144 | }); | ||
| 2145 | harness.emit({ | ||
| 2146 | ...base, | ||
| 2147 | type: "content.delta", | ||
| 2148 | eventId: asEventId("async-after"), | ||
| 2149 | itemId: asItemId("message-1"), | ||
| 2150 | payload: { streamKind: "assistant_text", delta: "After." }, | ||
| 2151 | }); | ||
| 2152 | await harness.drain(); | ||
| 2153 | const thread = (await harness.readModel()).threads[0]; | ||
| 2154 | expect(thread?.session?.status).toBe("running"); | ||
| 2155 | expect(thread?.messages).toMatchObject([{ text: "Before. After.", streaming: true }]); | ||
| 2156 | expect( | ||
| 2157 | thread?.activities.find((activity) => activity.kind === "user-input.requested")?.payload, | ||
| 2158 | ).toMatchObject({ responseMode: "message", requestId: "codex-async:question-1" }); | ||
| 2159 | }); | ||
| 2160 | |||
| 2111 | it("does not create assistant segments for whitespace-only buffered text at approval boundaries", async () => { | 2161 | it("does not create assistant segments for whitespace-only buffered text at approval boundaries", async () => { |
| 2112 | const harness = await createHarness(); | 2162 | const harness = await createHarness(); |
| 2113 | const startedAt = "2026-03-28T06:28:00.000Z"; | 2163 | const startedAt = "2026-03-28T06:28:00.000Z"; |
apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts+3-1| ... | @@ -520,6 +520,7 @@ export function runtimeEventToActivities( | ... | @@ -520,6 +520,7 @@ export function runtimeEventToActivities( |
| 520 | payload: { | 520 | payload: { |
| 521 | ...(event.requestId ? { requestId: event.requestId } : {}), | 521 | ...(event.requestId ? { requestId: event.requestId } : {}), |
| 522 | questions: event.payload.questions, | 522 | questions: event.payload.questions, |
| 523 | ...(event.payload.responseMode ? { responseMode: event.payload.responseMode } : {}), | ||
| 523 | }, | 524 | }, |
| 524 | turnId: toTurnId(event.turnId) ?? null, | 525 | turnId: toTurnId(event.turnId) ?? null, |
| 525 | ...maybeSequence, | 526 | ...maybeSequence, |
| ... | @@ -1739,7 +1740,8 @@ const make = Effect.gen(function* () { | ... | @@ -1739,7 +1740,8 @@ const make = Effect.gen(function* () { |
| 1739 | } | 1740 | } |
| 1740 | 1741 | ||
| 1741 | const pauseForUserTurnId = | 1742 | const pauseForUserTurnId = |
| 1742 | event.type === "request.opened" || event.type === "user-input.requested" | 1743 | event.type === "request.opened" || |
| 1744 | (event.type === "user-input.requested" && event.payload.responseMode !== "message") | ||
| 1743 | ? toTurnId(event.turnId) | 1745 | ? toTurnId(event.turnId) |
| 1744 | : undefined; | 1746 | : undefined; |
| 1745 | if (pauseForUserTurnId) { | 1747 | if (pauseForUserTurnId) { |
apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts+8| ... | @@ -7,6 +7,7 @@ | ... | @@ -7,6 +7,7 @@ |
| 7 | * @module ProjectionSnapshotQuery | 7 | * @module ProjectionSnapshotQuery |
| 8 | */ | 8 | */ |
| 9 | import type { | 9 | import type { |
| 10 | ApprovalRequestId, | ||
| 10 | CheckpointRef, | 11 | CheckpointRef, |
| 11 | OrchestrationCheckpointSummary, | 12 | OrchestrationCheckpointSummary, |
| 12 | OrchestrationProject, | 13 | OrchestrationProject, |
| ... | @@ -16,6 +17,7 @@ import type { | ... | @@ -16,6 +17,7 @@ import type { |
| 16 | OrchestrationSearchThreadsResult, | 17 | OrchestrationSearchThreadsResult, |
| 17 | OrchestrationShellSnapshot, | 18 | OrchestrationShellSnapshot, |
| 18 | OrchestrationThread, | 19 | OrchestrationThread, |
| 20 | OrchestrationThreadActivity, | ||
| 19 | OrchestrationThreadDetailSnapshot, | 21 | OrchestrationThreadDetailSnapshot, |
| 20 | OrchestrationThreadDetailWindow, | 22 | OrchestrationThreadDetailWindow, |
| 21 | OrchestrationThreadShell, | 23 | OrchestrationThreadShell, |
| ... | @@ -72,6 +74,12 @@ export interface ProjectionThreadDetailQuery { | ... | @@ -72,6 +74,12 @@ export interface ProjectionThreadDetailQuery { |
| 72 | * ProjectionSnapshotQueryShape - Service API for read-model snapshots. | 74 | * ProjectionSnapshotQueryShape - Service API for read-model snapshots. |
| 73 | */ | 75 | */ |
| 74 | export interface ProjectionSnapshotQueryShape { | 76 | export interface ProjectionSnapshotQueryShape { |
| 77 | /** Read the latest request or resolution without loading the thread history. */ | ||
| 78 | readonly getUserInputActivity: (input: { | ||
| 79 | readonly threadId: ThreadId; | ||
| 80 | readonly requestId: ApprovalRequestId; | ||
| 81 | }) => Effect.Effect<Option.Option<OrchestrationThreadActivity>, ProjectionRepositoryError>; | ||
| 82 | |||
| 75 | /** | 83 | /** |
| 76 | * Read the lightweight command snapshot used to bootstrap the in-memory | 84 | * Read the lightweight command snapshot used to bootstrap the in-memory |
| 77 | * orchestration engine without hydrating message/activity/checkpoint bodies. | 85 | * orchestration engine without hydrating message/activity/checkpoint bodies. |
apps/server/src/orchestration/decider.ts+77-7| ... | @@ -1,13 +1,19 @@ | ... | @@ -1,13 +1,19 @@ |
| 1 | import { | 1 | import { |
| 2 | EventId, | 2 | EventId, |
| 3 | MessageId, | ||
| 4 | UserInputRequestedPayload, | ||
| 3 | type OrchestrationCommand, | 5 | type OrchestrationCommand, |
| 4 | type OrchestrationEvent, | 6 | type OrchestrationEvent, |
| 5 | type OrchestrationReadModel, | 7 | type OrchestrationReadModel, |
| 6 | type OrchestrationThread, | 8 | type OrchestrationThread, |
| 9 | type OrchestrationThreadActivity, | ||
| 7 | } from "@t3tools/contracts"; | 10 | } from "@t3tools/contracts"; |
| 8 | import * as DateTime from "effect/DateTime"; | 11 | import * as DateTime from "effect/DateTime"; |
| 9 | import * as Crypto from "effect/Crypto"; | 12 | import * as Crypto from "effect/Crypto"; |
| 10 | import * as Effect from "effect/Effect"; | 13 | import * as Effect from "effect/Effect"; |
| 14 | import * as Schema from "effect/Schema"; | ||
| 15 | import * as Option from "effect/Option"; | ||
| 16 | import * as Predicate from "effect/Predicate"; | ||
| 11 | import type * as PlatformError from "effect/PlatformError"; | 17 | import type * as PlatformError from "effect/PlatformError"; |
| 12 | 18 | ||
| 13 | import { | 19 | import { |
| ... | @@ -29,6 +35,7 @@ import { projectEvent } from "./projector.ts"; | ... | @@ -29,6 +35,7 @@ import { projectEvent } from "./projector.ts"; |
| 29 | import { threadHasQueuedTurnStart } from "./ThreadSettlementPolicy.ts"; | 35 | import { threadHasQueuedTurnStart } from "./ThreadSettlementPolicy.ts"; |
| 30 | 36 | ||
| 31 | const nowIso = Effect.map(DateTime.now, DateTime.formatIso); | 37 | const nowIso = Effect.map(DateTime.now, DateTime.formatIso); |
| 38 | const decodeUserInputRequestedPayload = Schema.decodeUnknownOption(UserInputRequestedPayload); | ||
| 32 | 39 | ||
| 33 | /** | 40 | /** |
| 34 | * Blocked-on-you work derived from the thread's retained activities: an | 41 | * Blocked-on-you work derived from the thread's retained activities: an |
| ... | @@ -55,12 +62,8 @@ function isStaleRequestFailureDetail(payload: Record<string, unknown> | null): b | ... | @@ -55,12 +62,8 @@ function isStaleRequestFailureDetail(payload: Record<string, unknown> | null): b |
| 55 | } | 62 | } |
| 56 | 63 | ||
| 57 | // Scans the read model's activities, which the projector caps at the most | 64 | // Scans the read model's activities, which the projector caps at the most |
| 58 | // recent 500. That bound is safe here: an OPEN approval/user-input request | 65 | // recent 500 plus pending async questions. Async questions remain actionable |
| 59 | // blocks its turn, so the thread cannot accumulate hundreds of later | 66 | // while the agent works, so they must not expire with the activity window. |
| 60 | // activities while one is outstanding — a request that has scrolled out of | ||
| 61 | // the window is one whose turn kept running, i.e. it was resolved or went | ||
| 62 | // stale. (The projection pipeline's pendingApprovalCount reads the same | ||
| 63 | // capped stream and stays consistent with this view.) | ||
| 64 | function hasOpenBlockingRequest(thread: { | 67 | function hasOpenBlockingRequest(thread: { |
| 65 | readonly activities: ReadonlyArray<{ readonly kind: string; readonly payload: unknown }>; | 68 | readonly activities: ReadonlyArray<{ readonly kind: string; readonly payload: unknown }>; |
| 66 | }): boolean { | 69 | }): boolean { |
| ... | @@ -185,9 +188,11 @@ const decideCommandSequence = Effect.fn("decideCommandSequence")(function* ({ | ... | @@ -185,9 +188,11 @@ const decideCommandSequence = Effect.fn("decideCommandSequence")(function* ({ |
| 185 | export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand")(function* ({ | 188 | export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand")(function* ({ |
| 186 | command, | 189 | command, |
| 187 | readModel, | 190 | readModel, |
| 191 | userInputActivity, | ||
| 188 | }: { | 192 | }: { |
| 189 | readonly command: OrchestrationCommand; | 193 | readonly command: OrchestrationCommand; |
| 190 | readonly readModel: OrchestrationReadModel; | 194 | readonly readModel: OrchestrationReadModel; |
| 195 | readonly userInputActivity?: OrchestrationThreadActivity; | ||
| 191 | }): Effect.fn.Return< | 196 | }): Effect.fn.Return< |
| 192 | DecideOrchestrationCommandResult, | 197 | DecideOrchestrationCommandResult, |
| 193 | OrchestrationCommandRejection | PlatformError.PlatformError, | 198 | OrchestrationCommandRejection | PlatformError.PlatformError, |
| ... | @@ -1061,11 +1066,76 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" | ... | @@ -1061,11 +1066,76 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" |
| 1061 | } | 1066 | } |
| 1062 | 1067 | ||
| 1063 | case "thread.user-input.respond": { | 1068 | case "thread.user-input.respond": { |
| 1064 | yield* requireThread({ | 1069 | const thread = yield* requireThread({ |
| 1065 | readModel, | 1070 | readModel, |
| 1066 | command, | 1071 | command, |
| 1067 | threadId: command.threadId, | 1072 | threadId: command.threadId, |
| 1068 | }); | 1073 | }); |
| 1074 | const request = userInputActivity; | ||
| 1075 | if ( | ||
| 1076 | request && | ||
| 1077 | Predicate.isObject(request.payload) && | ||
| 1078 | request.payload.responseMode === "message" | ||
| 1079 | ) { | ||
| 1080 | const payload = decodeUserInputRequestedPayload(request.payload); | ||
| 1081 | if (request.kind !== "user-input.requested" || Option.isNone(payload)) { | ||
| 1082 | return yield* new OrchestrationCommandInvariantError({ | ||
| 1083 | commandType: command.type, | ||
| 1084 | detail: "This question has already been answered.", | ||
| 1085 | }); | ||
| 1086 | } | ||
| 1087 | const replies: string[] = []; | ||
| 1088 | for (const question of payload.value.questions) { | ||
| 1089 | const answer = command.answers[question.id]; | ||
| 1090 | if (typeof answer !== "string" || answer.trim().length === 0) { | ||
| 1091 | return yield* new OrchestrationCommandInvariantError({ | ||
| 1092 | commandType: command.type, | ||
| 1093 | detail: "Answer each question before sending.", | ||
| 1094 | }); | ||
| 1095 | } | ||
| 1096 | replies.push(`${question.question}\n${answer.trim()}`); | ||
| 1097 | } | ||
| 1098 | // Commit the answer and its message together. The normal turn path | ||
| 1099 | // steers a running agent or resumes an idle session. | ||
| 1100 | return yield* decideCommandSequence({ | ||
| 1101 | readModel, | ||
| 1102 | commands: [ | ||
| 1103 | { | ||
| 1104 | type: "thread.activity.append", | ||
| 1105 | commandId: command.commandId, | ||
| 1106 | threadId: command.threadId, | ||
| 1107 | createdAt: command.createdAt, | ||
| 1108 | activity: { | ||
| 1109 | id: EventId.make(`async-answer:${command.requestId}`), | ||
| 1110 | kind: "user-input.resolved", | ||
| 1111 | summary: "User input submitted", | ||
| 1112 | tone: "info", | ||
| 1113 | turnId: request.turnId, | ||
| 1114 | createdAt: command.createdAt, | ||
| 1115 | payload: { | ||
| 1116 | requestId: command.requestId, | ||
| 1117 | responseMode: "message", | ||
| 1118 | answers: command.answers, | ||
| 1119 | }, | ||
| 1120 | }, | ||
| 1121 | }, | ||
| 1122 | { | ||
| 1123 | type: "thread.turn.start", | ||
| 1124 | commandId: command.commandId, | ||
| 1125 | threadId: command.threadId, | ||
| 1126 | createdAt: command.createdAt, | ||
| 1127 | runtimeMode: thread.runtimeMode, | ||
| 1128 | interactionMode: thread.interactionMode, | ||
| 1129 | message: { | ||
| 1130 | messageId: MessageId.make(`async-answer:${command.requestId}`), | ||
| 1131 | role: "user", | ||
| 1132 | text: replies.join("\n\n"), | ||
| 1133 | attachments: [], | ||
| 1134 | }, | ||
| 1135 | }, | ||
| 1136 | ], | ||
| 1137 | }); | ||
| 1138 | } | ||
| 1069 | return { | 1139 | return { |
| 1070 | ...(yield* withEventBase({ | 1140 | ...(yield* withEventBase({ |
| 1071 | aggregateKind: "thread", | 1141 | aggregateKind: "thread", |
apps/server/src/orchestration/projector.ts+29-6| ... | @@ -7,6 +7,7 @@ import { | ... | @@ -7,6 +7,7 @@ import { |
| 7 | } from "@t3tools/contracts"; | 7 | } from "@t3tools/contracts"; |
| 8 | import * as Effect from "effect/Effect"; | 8 | import * as Effect from "effect/Effect"; |
| 9 | import * as Schema from "effect/Schema"; | 9 | import * as Schema from "effect/Schema"; |
| 10 | import * as Predicate from "effect/Predicate"; | ||
| 10 | 11 | ||
| 11 | import { toProjectorDecodeError, type OrchestrationProjectorDecodeError } from "./Errors.ts"; | 12 | import { toProjectorDecodeError, type OrchestrationProjectorDecodeError } from "./Errors.ts"; |
| 12 | import { | 13 | import { |
| ... | @@ -39,6 +40,28 @@ type ThreadPatch = Partial<Omit<OrchestrationThread, "id" | "projectId">>; | ... | @@ -39,6 +40,28 @@ type ThreadPatch = Partial<Omit<OrchestrationThread, "id" | "projectId">>; |
| 39 | const MAX_THREAD_MESSAGES = 2_000; | 40 | const MAX_THREAD_MESSAGES = 2_000; |
| 40 | const MAX_THREAD_CHECKPOINTS = 500; | 41 | const MAX_THREAD_CHECKPOINTS = 500; |
| 41 | 42 | ||
| 43 | // Async questions can stay open while the agent produces more activity. | ||
| 44 | // Match the database snapshot's pending-question retention. | ||
| 45 | function retainThreadActivities(activities: OrchestrationThread["activities"]) { | ||
| 46 | const recentStart = activities.length - 500; | ||
| 47 | if (recentStart <= 0) return activities; | ||
| 48 | const pending = new Map<string, OrchestrationThread["activities"][number]>(); | ||
| 49 | for (const activity of activities) { | ||
| 50 | if (!Predicate.isObject(activity.payload)) continue; | ||
| 51 | const requestId = activity.payload.requestId; | ||
| 52 | if (typeof requestId !== "string") continue; | ||
| 53 | if (activity.kind === "user-input.requested" && activity.payload.responseMode === "message") { | ||
| 54 | pending.set(requestId, activity); | ||
| 55 | } else if (activity.kind === "user-input.resolved") { | ||
| 56 | pending.delete(requestId); | ||
| 57 | } | ||
| 58 | } | ||
| 59 | const pendingActivities = new Set(pending.values()); | ||
| 60 | return activities.filter( | ||
| 61 | (activity, index) => index >= recentStart || pendingActivities.has(activity), | ||
| 62 | ); | ||
| 63 | } | ||
| 64 | |||
| 42 | function checkpointStatusToLatestTurnState(status: "ready" | "missing" | "error") { | 65 | function checkpointStatusToLatestTurnState(status: "ready" | "missing" | "error") { |
| 43 | if (status === "error") return "error" as const; | 66 | if (status === "error") return "error" as const; |
| 44 | if (status === "missing") return "interrupted" as const; | 67 | if (status === "missing") return "interrupted" as const; |
| ... | @@ -804,12 +827,12 @@ export function projectEvent( | ... | @@ -804,12 +827,12 @@ export function projectEvent( |
| 804 | return nextBase; | 827 | return nextBase; |
| 805 | } | 828 | } |
| 806 | 829 | ||
| 807 | const activities = [ | 830 | const activities = retainThreadActivities( |
| 808 | ...thread.activities.filter((entry) => entry.id !== payload.activity.id), | 831 | [ |
| 809 | payload.activity, | 832 | ...thread.activities.filter((entry) => entry.id !== payload.activity.id), |
| 810 | ] | 833 | payload.activity, |
| 811 | .toSorted(compareThreadActivities) | 834 | ].toSorted(compareThreadActivities), |
| 812 | .slice(-500); | 835 | ); |
| 813 | 836 | ||
| 814 | return { | 837 | return { |
| 815 | ...nextBase, | 838 | ...nextBase, |
apps/server/src/project/ProjectSetupScriptRunner.test.ts+1| ... | @@ -26,6 +26,7 @@ const makeProject = (scripts: OrchestrationProject["scripts"]): OrchestrationPro | ... | @@ -26,6 +26,7 @@ const makeProject = (scripts: OrchestrationProject["scripts"]): OrchestrationPro |
| 26 | 26 | ||
| 27 | const makeProjectionSnapshotQueryLayer = (project: OrchestrationProject) => | 27 | const makeProjectionSnapshotQueryLayer = (project: OrchestrationProject) => |
| 28 | Layer.succeed(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { | 28 | Layer.succeed(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { |
| 29 | getUserInputActivity: () => Effect.die("unused"), | ||
| 29 | getCommandReadModel: () => Effect.die("unused"), | 30 | getCommandReadModel: () => Effect.die("unused"), |
| 30 | getSnapshot: () => Effect.die("unused"), | 31 | getSnapshot: () => Effect.die("unused"), |
| 31 | getShellSnapshot: () => Effect.die("unused"), | 32 | getShellSnapshot: () => Effect.die("unused"), |
apps/server/src/provider/Layers/CodexAdapter.test.ts+75| ... | @@ -1648,6 +1648,81 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { | ... | @@ -1648,6 +1648,81 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { |
| 1648 | }), | 1648 | }), |
| 1649 | ); | 1649 | ); |
| 1650 | 1650 | ||
| 1651 | it.effect("maps async agent questions without ending the turn", () => | ||
| 1652 | Effect.gen(function* () { | ||
| 1653 | const { adapter, runtime } = yield* startLifecycleRuntime(); | ||
| 1654 | const eventsFiber = yield* Stream.runCollect(Stream.take(adapter.streamEvents, 2)).pipe( | ||
| 1655 | Effect.forkChild, | ||
| 1656 | ); | ||
| 1657 | yield* runtime.emit({ | ||
| 1658 | id: asEventId("evt-async-question"), | ||
| 1659 | kind: "notification", | ||
| 1660 | provider: ProviderDriverKind.make("codex"), | ||
| 1661 | threadId: asThreadId("thread-1"), | ||
| 1662 | createdAt: "2026-01-01T00:00:00.000Z", | ||
| 1663 | method: "item/completed", | ||
| 1664 | payload: { | ||
| 1665 | completedAtMs: 0, | ||
| 1666 | threadId: "thread-1", | ||
| 1667 | turnId: "turn-1", | ||
| 1668 | item: { | ||
| 1669 | type: "agentMessage", | ||
| 1670 | id: "async-question-1", | ||
| 1671 | text: "Which package manager?\n- pnpm\n- npm\n\nWhat should it be named?", | ||
| 1672 | phase: "final_answer", | ||
| 1673 | delivery: "async", | ||
| 1674 | questions: [ | ||
| 1675 | { title: "Which package manager?", options: ["pnpm", "npm"] }, | ||
| 1676 | { title: "What should it be named?" }, | ||
| 1677 | ], | ||
| 1678 | }, | ||
| 1679 | }, | ||
| 1680 | }); | ||
| 1681 | yield* runtime.emit({ | ||
| 1682 | id: asEventId("evt-async-continued"), | ||
| 1683 | kind: "notification", | ||
| 1684 | provider: ProviderDriverKind.make("codex"), | ||
| 1685 | threadId: asThreadId("thread-1"), | ||
| 1686 | createdAt: "2026-01-01T00:00:01.000Z", | ||
| 1687 | method: "item/agentMessage/delta", | ||
| 1688 | payload: { | ||
| 1689 | threadId: "thread-1", | ||
| 1690 | turnId: "turn-1", | ||
| 1691 | itemId: "message-2", | ||
| 1692 | delta: "I will keep working.", | ||
| 1693 | }, | ||
| 1694 | }); | ||
| 1695 | const events = Array.from(yield* Fiber.join(eventsFiber)); | ||
| 1696 | NodeAssert.equal(events[0]?.type, "user-input.requested"); | ||
| 1697 | NodeAssert.equal(events[0]?.requestId, "codex-async:thread-1:async-question-1"); | ||
| 1698 | NodeAssert.deepEqual(events[0]?.payload, { | ||
| 1699 | responseMode: "message", | ||
| 1700 | questions: [ | ||
| 1701 | { | ||
| 1702 | id: "0", | ||
| 1703 | header: "Question", | ||
| 1704 | question: "Which package manager?", | ||
| 1705 | options: [ | ||
| 1706 | { label: "pnpm", description: "" }, | ||
| 1707 | { label: "npm", description: "" }, | ||
| 1708 | ], | ||
| 1709 | allowCustomAnswer: true, | ||
| 1710 | multiSelect: false, | ||
| 1711 | }, | ||
| 1712 | { | ||
| 1713 | id: "1", | ||
| 1714 | header: "Question", | ||
| 1715 | question: "What should it be named?", | ||
| 1716 | options: [], | ||
| 1717 | allowCustomAnswer: true, | ||
| 1718 | multiSelect: false, | ||
| 1719 | }, | ||
| 1720 | ], | ||
| 1721 | }); | ||
| 1722 | NodeAssert.equal(events[1]?.type, "content.delta"); | ||
| 1723 | }), | ||
| 1724 | ); | ||
| 1725 | |||
| 1651 | it.effect("unwraps Codex token usage payloads for context window events", () => | 1726 | it.effect("unwraps Codex token usage payloads for context window events", () => |
| 1652 | Effect.gen(function* () { | 1727 | Effect.gen(function* () { |
| 1653 | const { adapter, runtime } = yield* startLifecycleRuntime(); | 1728 | const { adapter, runtime } = yield* startLifecycleRuntime(); |
apps/server/src/provider/Layers/CodexAdapter.ts+22| ... | @@ -8,6 +8,7 @@ | ... | @@ -8,6 +8,7 @@ |
| 8 | * @module CodexAdapterLive | 8 | * @module CodexAdapterLive |
| 9 | */ | 9 | */ |
| 10 | import { | 10 | import { |
| 11 | EventId, | ||
| 11 | type CanonicalItemType, | 12 | type CanonicalItemType, |
| 12 | type CanonicalRequestType, | 13 | type CanonicalRequestType, |
| 13 | type CodexSettings, | 14 | type CodexSettings, |
| ... | @@ -1445,6 +1446,27 @@ function mapToRuntimeEvents( | ... | @@ -1445,6 +1446,27 @@ function mapToRuntimeEvents( |
| 1445 | if (!item) { | 1446 | if (!item) { |
| 1446 | return []; | 1447 | return []; |
| 1447 | } | 1448 | } |
| 1449 | if (item.type === "agentMessage" && item.delivery === "async" && item.questions?.length) { | ||
| 1450 | return [ | ||
| 1451 | { | ||
| 1452 | ...runtimeEventBase(event, canonicalThreadId), | ||
| 1453 | type: "user-input.requested", | ||
| 1454 | requestId: RuntimeRequestId.make(`codex-async:${canonicalThreadId}:${item.id}`), | ||
| 1455 | eventId: EventId.make(`codex-async:${canonicalThreadId}:${item.id}`), | ||
| 1456 | payload: { | ||
| 1457 | responseMode: "message", | ||
| 1458 | questions: item.questions.map((question, index) => ({ | ||
| 1459 | id: String(index), | ||
| 1460 | header: "Question", | ||
| 1461 | question: question.title, | ||
| 1462 | options: (question.options ?? []).map((label) => ({ label, description: "" })), | ||
| 1463 | allowCustomAnswer: true, | ||
| 1464 | multiSelect: false, | ||
| 1465 | })), | ||
| 1466 | }, | ||
| 1467 | }, | ||
| 1468 | ]; | ||
| 1469 | } | ||
| 1448 | const itemType = toCanonicalItemType(item.type); | 1470 | const itemType = toCanonicalItemType(item.type); |
| 1449 | if (itemType === "plan") { | 1471 | if (itemType === "plan") { |
| 1450 | const detail = itemDetail(itemType, item); | 1472 | const detail = itemDetail(itemType, item); |
apps/server/src/provider/Layers/ProviderSessionReaper.test.ts+1| ... | @@ -204,6 +204,7 @@ describe("ProviderSessionReaper", () => { | ... | @@ -204,6 +204,7 @@ describe("ProviderSessionReaper", () => { |
| 204 | Layer.provideMerge(Layer.succeed(ProviderService, providerService)), | 204 | Layer.provideMerge(Layer.succeed(ProviderService, providerService)), |
| 205 | Layer.provideMerge( | 205 | Layer.provideMerge( |
| 206 | Layer.succeed(ProjectionSnapshotQuery, { | 206 | Layer.succeed(ProjectionSnapshotQuery, { |
| 207 | getUserInputActivity: () => Effect.die("unused"), | ||
| 207 | getCommandReadModel: () => Effect.die("unused"), | 208 | getCommandReadModel: () => Effect.die("unused"), |
| 208 | getSnapshot: () => Effect.die("unused"), | 209 | getSnapshot: () => Effect.die("unused"), |
| 209 | getShellSnapshot: () => Effect.die("unused"), | 210 | getShellSnapshot: () => Effect.die("unused"), |
apps/server/src/server.test.ts+1| ... | @@ -926,6 +926,7 @@ const buildAppUnderTest = (options?: { | ... | @@ -926,6 +926,7 @@ const buildAppUnderTest = (options?: { |
| 926 | ), | 926 | ), |
| 927 | Layer.provide( | 927 | Layer.provide( |
| 928 | Layer.mock(ProjectionSnapshotQuery.ProjectionSnapshotQuery)({ | 928 | Layer.mock(ProjectionSnapshotQuery.ProjectionSnapshotQuery)({ |
| 929 | getUserInputActivity: () => Effect.die("unused"), | ||
| 929 | getCommandReadModel: () => Effect.succeed(makeDefaultOrchestrationReadModel()), | 930 | getCommandReadModel: () => Effect.succeed(makeDefaultOrchestrationReadModel()), |
| 930 | getSnapshot: () => Effect.succeed(makeDefaultOrchestrationReadModel()), | 931 | getSnapshot: () => Effect.succeed(makeDefaultOrchestrationReadModel()), |
| 931 | getShellSnapshot: () => | 932 | getShellSnapshot: () => |
apps/server/src/serverRuntimeStartup.reconcile.test.ts+2| ... | @@ -66,6 +66,7 @@ const makeProviderService = (liveThreadIds: ReadonlyArray<ThreadId> = []) => | ... | @@ -66,6 +66,7 @@ const makeProviderService = (liveThreadIds: ReadonlyArray<ThreadId> = []) => |
| 66 | 66 | ||
| 67 | const queryWithThreads = (threads: ReadonlyArray<ReturnType<typeof makeThread>>) => | 67 | const queryWithThreads = (threads: ReadonlyArray<ReturnType<typeof makeThread>>) => |
| 68 | ({ | 68 | ({ |
| 69 | getUserInputActivity: () => Effect.die("unused"), | ||
| 69 | getCommandReadModel: () => Effect.succeed({ threads } as never), | 70 | getCommandReadModel: () => Effect.succeed({ threads } as never), |
| 70 | }) as unknown as ProjectionSnapshotQuery.ProjectionSnapshotQuery["Service"]; | 71 | }) as unknown as ProjectionSnapshotQuery.ProjectionSnapshotQuery["Service"]; |
| 71 | 72 | ||
| ... | @@ -631,6 +632,7 @@ it.effect("does not fail startup when the live provider session inventory cannot | ... | @@ -631,6 +632,7 @@ it.effect("does not fail startup when the live provider session inventory cannot |
| 631 | let queried = false; | 632 | let queried = false; |
| 632 | return ServerRuntimeStartup.reconcileProviderSessions.pipe( | 633 | return ServerRuntimeStartup.reconcileProviderSessions.pipe( |
| 633 | Effect.provideService(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { | 634 | Effect.provideService(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { |
| 635 | getUserInputActivity: () => Effect.die("unused"), | ||
| 634 | getCommandReadModel: () => | 636 | getCommandReadModel: () => |
| 635 | Effect.sync(() => { | 637 | Effect.sync(() => { |
| 636 | queried = true; | 638 | queried = true; |
apps/server/src/serverRuntimeStartup.test.ts+4| ... | @@ -118,6 +118,7 @@ it.effect("launchStartupHeartbeat does not block the caller while counts are loa | ... | @@ -118,6 +118,7 @@ it.effect("launchStartupHeartbeat does not block the caller while counts are loa |
| 118 | 118 | ||
| 119 | yield* ServerRuntimeStartup.launchStartupHeartbeat.pipe( | 119 | yield* ServerRuntimeStartup.launchStartupHeartbeat.pipe( |
| 120 | Effect.provideService(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { | 120 | Effect.provideService(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { |
| 121 | getUserInputActivity: () => Effect.die("unused"), | ||
| 121 | getCommandReadModel: () => Effect.die("unused"), | 122 | getCommandReadModel: () => Effect.die("unused"), |
| 122 | getSnapshot: () => Effect.die("unused"), | 123 | getSnapshot: () => Effect.die("unused"), |
| 123 | getShellSnapshot: () => Effect.die("unused"), | 124 | getShellSnapshot: () => Effect.die("unused"), |
| ... | @@ -185,6 +186,7 @@ it.effect("resolveAutoBootstrapWelcomeTargets returns existing project and threa | ... | @@ -185,6 +186,7 @@ it.effect("resolveAutoBootstrapWelcomeTargets returns existing project and threa |
| 185 | autoBootstrapProjectFromCwd: true, | 186 | autoBootstrapProjectFromCwd: true, |
| 186 | } as never), | 187 | } as never), |
| 187 | Effect.provideService(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { | 188 | Effect.provideService(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { |
| 189 | getUserInputActivity: () => Effect.die("unused"), | ||
| 188 | getCommandReadModel: () => Effect.die("unused"), | 190 | getCommandReadModel: () => Effect.die("unused"), |
| 189 | getSnapshot: () => Effect.die("unused"), | 191 | getSnapshot: () => Effect.die("unused"), |
| 190 | getShellSnapshot: () => Effect.die("unused"), | 192 | getShellSnapshot: () => Effect.die("unused"), |
| ... | @@ -250,6 +252,7 @@ it.effect("resolveAutoBootstrapWelcomeTargets creates a project and thread when | ... | @@ -250,6 +252,7 @@ it.effect("resolveAutoBootstrapWelcomeTargets creates a project and thread when |
| 250 | autoBootstrapProjectFromCwd: true, | 252 | autoBootstrapProjectFromCwd: true, |
| 251 | } as never), | 253 | } as never), |
| 252 | Effect.provideService(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { | 254 | Effect.provideService(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { |
| 255 | getUserInputActivity: () => Effect.die("unused"), | ||
| 253 | getCommandReadModel: () => Effect.die("unused"), | 256 | getCommandReadModel: () => Effect.die("unused"), |
| 254 | getSnapshot: () => Effect.die("unused"), | 257 | getSnapshot: () => Effect.die("unused"), |
| 255 | getShellSnapshot: () => Effect.die("unused"), | 258 | getShellSnapshot: () => Effect.die("unused"), |
| ... | @@ -312,6 +315,7 @@ it.effect("resolveAutoBootstrapWelcomeTargets preserves typed UUID generation fa | ... | @@ -312,6 +315,7 @@ it.effect("resolveAutoBootstrapWelcomeTargets preserves typed UUID generation fa |
| 312 | autoBootstrapProjectFromCwd: true, | 315 | autoBootstrapProjectFromCwd: true, |
| 313 | } as never), | 316 | } as never), |
| 314 | Effect.provideService(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { | 317 | Effect.provideService(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { |
| 318 | getUserInputActivity: () => Effect.die("unused"), | ||
| 315 | getCommandReadModel: () => Effect.die("unused"), | 319 | getCommandReadModel: () => Effect.die("unused"), |
| 316 | getSnapshot: () => Effect.die("unused"), | 320 | getSnapshot: () => Effect.die("unused"), |
| 317 | getShellSnapshot: () => Effect.die("unused"), | 321 | getShellSnapshot: () => Effect.die("unused"), |
apps/web/src/session-logic.test.ts+20| ... | @@ -250,6 +250,26 @@ describe("derivePendingApprovals", () => { | ... | @@ -250,6 +250,26 @@ describe("derivePendingApprovals", () => { |
| 250 | }); | 250 | }); |
| 251 | 251 | ||
| 252 | describe("derivePendingUserInputs", () => { | 252 | describe("derivePendingUserInputs", () => { |
| 253 | it("keeps free-text questions without suggested answers", () => { | ||
| 254 | const question = { | ||
| 255 | id: "0", | ||
| 256 | header: "Question", | ||
| 257 | question: "What should it be named?", | ||
| 258 | options: [], | ||
| 259 | allowCustomAnswer: true, | ||
| 260 | multiSelect: false, | ||
| 261 | }; | ||
| 262 | const activities = [ | ||
| 263 | makeActivity({ | ||
| 264 | id: "async-question", | ||
| 265 | kind: "user-input.requested", | ||
| 266 | summary: "User input requested", | ||
| 267 | payload: { requestId: "async-1", responseMode: "message", questions: [question] }, | ||
| 268 | }), | ||
| 269 | ]; | ||
| 270 | expect(derivePendingUserInputs(activities)[0]?.questions).toEqual([question]); | ||
| 271 | }); | ||
| 272 | |||
| 253 | it("preserves native choice values and the custom-answer restriction", () => { | 273 | it("preserves native choice values and the custom-answer restriction", () => { |
| 254 | const question = { | 274 | const question = { |
| 255 | id: "interaction-result", | 275 | id: "interaction-result", |
apps/web/src/session-logic.ts+1-1| ... | @@ -532,7 +532,7 @@ function parseUserInputQuestions( | ... | @@ -532,7 +532,7 @@ function parseUserInputQuestions( |
| 532 | }; | 532 | }; |
| 533 | }) | 533 | }) |
| 534 | .filter((option): option is UserInputQuestion["options"][number] => option !== null); | 534 | .filter((option): option is UserInputQuestion["options"][number] => option !== null); |
| 535 | if (options.length === 0) { | 535 | if (options.length === 0 && question.allowCustomAnswer === false) { |
| 536 | return null; | 536 | return null; |
| 537 | } | 537 | } |
| 538 | return { | 538 | return { |
docs/internals/providers.md+21| ... | @@ -24,6 +24,27 @@ adapter in a child scope. Adapter implementations live beside them in | ... | @@ -24,6 +24,27 @@ adapter in a child scope. Adapter implementations live beside them in |
| 24 | [`ProviderAdapter.ts`][adapter]. Read the driver plus its adapter to see how a specific agent's | 24 | [`ProviderAdapter.ts`][adapter]. Read the driver plus its adapter to see how a specific agent's |
| 25 | transport, config, and event shapes are mapped. | 25 | transport, config, and event shapes are mapped. |
| 26 | 26 | ||
| 27 | ## Codex async questions | ||
| 28 | |||
| 29 | Codex 0.153 exposes `request_user_input_async` through `item/started` and `item/completed` | ||
| 30 | notifications. The item has `type: "agentMessage"`, `delivery: "async"`, and a `questions` array. | ||
| 31 | Each question has a `title` and an optional `options` array of strings. The tool returns `{"accepted":true}` | ||
| 32 | without waiting. This is separate from the `item/tool/requestUserInput` server request. | ||
| 33 | See the [Codex tool handler](https://github.com/openai/codex/blob/d979df154cf60e13eafb5453e75b6d84f21c67bf/codex-rs/core/src/tools/handlers/request_user_input_async.rs). | ||
| 34 | |||
| 35 | The Codex adapter maps completed question items to `user-input.requested` with | ||
| 36 | `responseMode: "message"` and stable request and event IDs. Questions use the existing web, | ||
| 37 | desktop, and mobile panels. They stay pending while the turn runs and after it finishes. | ||
| 38 | |||
| 39 | The engine reads the request's latest stored activity before deciding a reply. This works after | ||
| 40 | startup, when the command snapshot has no activities, and after a resolution leaves the recent | ||
| 41 | activity window. The query returns one activity, not the full thread history. | ||
| 42 | |||
| 43 | For these requests, the decider saves the resolution and a user message in one transaction. | ||
| 44 | The standard turn path delivers the message, including session resume and active-turn input. | ||
| 45 | It does not send a JSON-RPC response to Codex. Other providers and blocking Codex questions | ||
| 46 | keep their existing response paths. | ||
| 47 | |||
| 27 | ## Registry and routing | 48 | ## Registry and routing |
| 28 | 49 | ||
| 29 | Two registries separate configuration from live processes: | 50 | Two registries separate configuration from live processes: |
docs/user/providers-codex.md+9| ... | @@ -34,6 +34,15 @@ In an existing Codex thread, send `/feedback` or `/feedback` followed by a descr | ... | @@ -34,6 +34,15 @@ In an existing Codex thread, send `/feedback` or `/feedback` followed by a descr |
| 34 | issue. T3 Code uploads the thread and Codex logs to OpenAI and shows a thread ID that you can copy | 34 | issue. T3 Code uploads the thread and Codex logs to OpenAI and shows a thread ID that you can copy |
| 35 | and share with OpenAI employees. | 35 | and share with OpenAI employees. |
| 36 | 36 | ||
| 37 | ## Answer questions while Codex works | ||
| 38 | |||
| 39 | Codex can ask questions without stopping its work. Choose a suggested answer or enter your own | ||
| 40 | in the question panel. Questions without suggested answers accept text. | ||
| 41 | |||
| 42 | Your answers are sent as a new message. They reach the current turn while Codex is working, or | ||
| 43 | start a new turn if it has finished. Unanswered questions stay available after you reconnect. | ||
| 44 | This works in the web, desktop, and mobile apps. Codex must support async questions. | ||
| 45 | |||
| 37 | ## Sub-agent models | 46 | ## Sub-agent models |
| 38 | 47 | ||
| 39 | The web and desktop Agents panel shows each sub-agent's model and reasoning effort when Codex | 48 | The web and desktop Agents panel shows each sub-agent's model and reasoning effort when Codex |
packages/contracts/src/providerRuntime.ts+3-2| ... | @@ -491,7 +491,7 @@ export type RequestResolvedPayload = typeof RequestResolvedPayload.Type; | ... | @@ -491,7 +491,7 @@ export type RequestResolvedPayload = typeof RequestResolvedPayload.Type; |
| 491 | 491 | ||
| 492 | const UserInputQuestionOption = Schema.Struct({ | 492 | const UserInputQuestionOption = Schema.Struct({ |
| 493 | label: TrimmedNonEmptyStringSchema, | 493 | label: TrimmedNonEmptyStringSchema, |
| 494 | description: TrimmedNonEmptyStringSchema, | 494 | description: Schema.String, |
| 495 | value: Schema.optional(Schema.String), | 495 | value: Schema.optional(Schema.String), |
| 496 | }); | 496 | }); |
| 497 | export type UserInputQuestionOption = typeof UserInputQuestionOption.Type; | 497 | export type UserInputQuestionOption = typeof UserInputQuestionOption.Type; |
| ... | @@ -508,8 +508,9 @@ export const UserInputQuestion = Schema.Struct({ | ... | @@ -508,8 +508,9 @@ export const UserInputQuestion = Schema.Struct({ |
| 508 | }); | 508 | }); |
| 509 | export type UserInputQuestion = typeof UserInputQuestion.Type; | 509 | export type UserInputQuestion = typeof UserInputQuestion.Type; |
| 510 | 510 | ||
| 511 | const UserInputRequestedPayload = Schema.Struct({ | 511 | export const UserInputRequestedPayload = Schema.Struct({ |
| 512 | questions: Schema.Array(UserInputQuestion), | 512 | questions: Schema.Array(UserInputQuestion), |
| 513 | responseMode: Schema.optional(Schema.Literal("message")), | ||
| 513 | }); | 514 | }); |
| 514 | export type UserInputRequestedPayload = typeof UserInputRequestedPayload.Type; | 515 | export type UserInputRequestedPayload = typeof UserInputRequestedPayload.Type; |
| 515 | 516 |
packages/effect-codex-app-server/scripts/generate.ts+55-1| ... | @@ -330,6 +330,60 @@ function stripNullDefaults(value: Schema.Json): Schema.Json { | ... | @@ -330,6 +330,60 @@ function stripNullDefaults(value: Schema.Json): Schema.Json { |
| 330 | ) as Schema.Json; | 330 | ) as Schema.Json; |
| 331 | } | 331 | } |
| 332 | 332 | ||
| 333 | // Codex 0.153 adds async questions to agent messages. Keep older protocol | ||
| 334 | // fields until the next full refresh, including every thread history namespace. | ||
| 335 | function addAsyncQuestionFields(value: Schema.Json): Schema.Json { | ||
| 336 | if (Array.isArray(value)) { | ||
| 337 | return value.map(addAsyncQuestionFields); | ||
| 338 | } | ||
| 339 | if (value === null || typeof value !== "object") { | ||
| 340 | return value; | ||
| 341 | } | ||
| 342 | const properties = "properties" in value ? value.properties : undefined; | ||
| 343 | const itemType = | ||
| 344 | properties && typeof properties === "object" && "type" in properties | ||
| 345 | ? properties.type | ||
| 346 | : undefined; | ||
| 347 | if ( | ||
| 348 | properties && | ||
| 349 | typeof properties === "object" && | ||
| 350 | itemType && | ||
| 351 | typeof itemType === "object" && | ||
| 352 | "enum" in itemType && | ||
| 353 | Array.isArray(itemType.enum) && | ||
| 354 | itemType.enum.includes("agentMessage") | ||
| 355 | ) { | ||
| 356 | return { | ||
| 357 | ...value, | ||
| 358 | properties: { | ||
| 359 | ...properties, | ||
| 360 | delivery: { anyOf: [{ type: "string", enum: ["async"] }, { type: "null" }] }, | ||
| 361 | questions: { | ||
| 362 | anyOf: [ | ||
| 363 | { | ||
| 364 | type: "array", | ||
| 365 | items: { | ||
| 366 | type: "object", | ||
| 367 | properties: { | ||
| 368 | title: { type: "string" }, | ||
| 369 | options: { | ||
| 370 | anyOf: [{ type: "array", items: { type: "string" } }, { type: "null" }], | ||
| 371 | }, | ||
| 372 | }, | ||
| 373 | required: ["title"], | ||
| 374 | }, | ||
| 375 | }, | ||
| 376 | { type: "null" }, | ||
| 377 | ], | ||
| 378 | }, | ||
| 379 | }, | ||
| 380 | }; | ||
| 381 | } | ||
| 382 | return Object.fromEntries( | ||
| 383 | Object.entries(value).map(([key, child]) => [key, addAsyncQuestionFields(child)]), | ||
| 384 | ); | ||
| 385 | } | ||
| 386 | |||
| 333 | function toPascalCaseMethod(method: string) { | 387 | function toPascalCaseMethod(method: string) { |
| 334 | return method | 388 | return method |
| 335 | .split("/") | 389 | .split("/") |
| ... | @@ -648,7 +702,7 @@ const generateFiles = Effect.fn("generateFiles")(function* () { | ... | @@ -648,7 +702,7 @@ const generateFiles = Effect.fn("generateFiles")(function* () { |
| 648 | for (const [name, schema] of Object.entries(aggregateSchemas).toSorted(([left], [right]) => | 702 | for (const [name, schema] of Object.entries(aggregateSchemas).toSorted(([left], [right]) => |
| 649 | left.localeCompare(right), | 703 | left.localeCompare(right), |
| 650 | )) { | 704 | )) { |
| 651 | generator.addSchema(name, schema as never); | 705 | generator.addSchema(name, addAsyncQuestionFields(schema) as never); |
| 652 | } | 706 | } |
| 653 | 707 | ||
| 654 | const generatedEntries = new Map<string, string>(); | 708 | const generatedEntries = new Map<string, string>(); |
packages/effect-codex-app-server/src/_generated/schema.gen.ts+272| ... | @@ -20405,6 +20405,11 @@ export type ServerNotification__ThreadItem = | ... | @@ -20405,6 +20405,11 @@ export type ServerNotification__ThreadItem = |
| 20405 | readonly memoryCitation?: ServerNotification__MemoryCitation | null; | 20405 | readonly memoryCitation?: ServerNotification__MemoryCitation | null; |
| 20406 | readonly phase?: ServerNotification__MessagePhase | null; | 20406 | readonly phase?: ServerNotification__MessagePhase | null; |
| 20407 | readonly text: string; | 20407 | readonly text: string; |
| 20408 | readonly delivery?: "async" | null; | ||
| 20409 | readonly questions?: ReadonlyArray<{ | ||
| 20410 | readonly title: string; | ||
| 20411 | readonly options?: ReadonlyArray<string> | null; | ||
| 20412 | }> | null; | ||
| 20408 | readonly type: "agentMessage"; | 20413 | readonly type: "agentMessage"; |
| 20409 | } | 20414 | } |
| 20410 | | { readonly id: string; readonly text: string; readonly type: "plan" } | 20415 | | { readonly id: string; readonly text: string; readonly type: "plan" } |
| ... | @@ -20530,6 +20535,18 @@ export const ServerNotification__ThreadItem = Schema.Union( | ... | @@ -20530,6 +20535,18 @@ export const ServerNotification__ThreadItem = Schema.Union( |
| 20530 | ), | 20535 | ), |
| 20531 | phase: Schema.optionalKey(Schema.Union([ServerNotification__MessagePhase, Schema.Null])), | 20536 | phase: Schema.optionalKey(Schema.Union([ServerNotification__MessagePhase, Schema.Null])), |
| 20532 | text: Schema.String, | 20537 | text: Schema.String, |
| 20538 | delivery: Schema.optionalKey(Schema.Union([Schema.Literal("async"), Schema.Null])), | ||
| 20539 | questions: Schema.optionalKey( | ||
| 20540 | Schema.Union([ | ||
| 20541 | Schema.Array( | ||
| 20542 | Schema.Struct({ | ||
| 20543 | title: Schema.String, | ||
| 20544 | options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), | ||
| 20545 | }), | ||
| 20546 | ), | ||
| 20547 | Schema.Null, | ||
| 20548 | ]), | ||
| 20549 | ), | ||
| 20533 | type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), | 20550 | type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), |
| 20534 | }).annotate({ title: "AgentMessageThreadItem" }), | 20551 | }).annotate({ title: "AgentMessageThreadItem" }), |
| 20535 | Schema.Struct({ | 20552 | Schema.Struct({ |
| ... | @@ -21499,6 +21516,11 @@ export type V2ItemCompletedNotification__ThreadItem = | ... | @@ -21499,6 +21516,11 @@ export type V2ItemCompletedNotification__ThreadItem = |
| 21499 | readonly memoryCitation?: V2ItemCompletedNotification__MemoryCitation | null; | 21516 | readonly memoryCitation?: V2ItemCompletedNotification__MemoryCitation | null; |
| 21500 | readonly phase?: V2ItemCompletedNotification__MessagePhase | null; | 21517 | readonly phase?: V2ItemCompletedNotification__MessagePhase | null; |
| 21501 | readonly text: string; | 21518 | readonly text: string; |
| 21519 | readonly delivery?: "async" | null; | ||
| 21520 | readonly questions?: ReadonlyArray<{ | ||
| 21521 | readonly title: string; | ||
| 21522 | readonly options?: ReadonlyArray<string> | null; | ||
| 21523 | }> | null; | ||
| 21502 | readonly type: "agentMessage"; | 21524 | readonly type: "agentMessage"; |
| 21503 | } | 21525 | } |
| 21504 | | { readonly id: string; readonly text: string; readonly type: "plan" } | 21526 | | { readonly id: string; readonly text: string; readonly type: "plan" } |
| ... | @@ -21628,6 +21650,18 @@ export const V2ItemCompletedNotification__ThreadItem = Schema.Union( | ... | @@ -21628,6 +21650,18 @@ export const V2ItemCompletedNotification__ThreadItem = Schema.Union( |
| 21628 | Schema.Union([V2ItemCompletedNotification__MessagePhase, Schema.Null]), | 21650 | Schema.Union([V2ItemCompletedNotification__MessagePhase, Schema.Null]), |
| 21629 | ), | 21651 | ), |
| 21630 | text: Schema.String, | 21652 | text: Schema.String, |
| 21653 | delivery: Schema.optionalKey(Schema.Union([Schema.Literal("async"), Schema.Null])), | ||
| 21654 | questions: Schema.optionalKey( | ||
| 21655 | Schema.Union([ | ||
| 21656 | Schema.Array( | ||
| 21657 | Schema.Struct({ | ||
| 21658 | title: Schema.String, | ||
| 21659 | options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), | ||
| 21660 | }), | ||
| 21661 | ), | ||
| 21662 | Schema.Null, | ||
| 21663 | ]), | ||
| 21664 | ), | ||
| 21631 | type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), | 21665 | type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), |
| 21632 | }).annotate({ title: "AgentMessageThreadItem" }), | 21666 | }).annotate({ title: "AgentMessageThreadItem" }), |
| 21633 | Schema.Struct({ | 21667 | Schema.Struct({ |
| ... | @@ -21965,6 +21999,11 @@ export type V2ItemStartedNotification__ThreadItem = | ... | @@ -21965,6 +21999,11 @@ export type V2ItemStartedNotification__ThreadItem = |
| 21965 | readonly memoryCitation?: V2ItemStartedNotification__MemoryCitation | null; | 21999 | readonly memoryCitation?: V2ItemStartedNotification__MemoryCitation | null; |
| 21966 | readonly phase?: V2ItemStartedNotification__MessagePhase | null; | 22000 | readonly phase?: V2ItemStartedNotification__MessagePhase | null; |
| 21967 | readonly text: string; | 22001 | readonly text: string; |
| 22002 | readonly delivery?: "async" | null; | ||
| 22003 | readonly questions?: ReadonlyArray<{ | ||
| 22004 | readonly title: string; | ||
| 22005 | readonly options?: ReadonlyArray<string> | null; | ||
| 22006 | }> | null; | ||
| 21968 | readonly type: "agentMessage"; | 22007 | readonly type: "agentMessage"; |
| 21969 | } | 22008 | } |
| 21970 | | { readonly id: string; readonly text: string; readonly type: "plan" } | 22009 | | { readonly id: string; readonly text: string; readonly type: "plan" } |
| ... | @@ -22092,6 +22131,18 @@ export const V2ItemStartedNotification__ThreadItem = Schema.Union( | ... | @@ -22092,6 +22131,18 @@ export const V2ItemStartedNotification__ThreadItem = Schema.Union( |
| 22092 | Schema.Union([V2ItemStartedNotification__MessagePhase, Schema.Null]), | 22131 | Schema.Union([V2ItemStartedNotification__MessagePhase, Schema.Null]), |
| 22093 | ), | 22132 | ), |
| 22094 | text: Schema.String, | 22133 | text: Schema.String, |
| 22134 | delivery: Schema.optionalKey(Schema.Union([Schema.Literal("async"), Schema.Null])), | ||
| 22135 | questions: Schema.optionalKey( | ||
| 22136 | Schema.Union([ | ||
| 22137 | Schema.Array( | ||
| 22138 | Schema.Struct({ | ||
| 22139 | title: Schema.String, | ||
| 22140 | options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), | ||
| 22141 | }), | ||
| 22142 | ), | ||
| 22143 | Schema.Null, | ||
| 22144 | ]), | ||
| 22145 | ), | ||
| 22095 | type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), | 22146 | type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), |
| 22096 | }).annotate({ title: "AgentMessageThreadItem" }), | 22147 | }).annotate({ title: "AgentMessageThreadItem" }), |
| 22097 | Schema.Struct({ | 22148 | Schema.Struct({ |
| ... | @@ -22601,6 +22652,11 @@ export type V2ReviewStartResponse__ThreadItem = | ... | @@ -22601,6 +22652,11 @@ export type V2ReviewStartResponse__ThreadItem = |
| 22601 | readonly memoryCitation?: V2ReviewStartResponse__MemoryCitation | null; | 22652 | readonly memoryCitation?: V2ReviewStartResponse__MemoryCitation | null; |
| 22602 | readonly phase?: V2ReviewStartResponse__MessagePhase | null; | 22653 | readonly phase?: V2ReviewStartResponse__MessagePhase | null; |
| 22603 | readonly text: string; | 22654 | readonly text: string; |
| 22655 | readonly delivery?: "async" | null; | ||
| 22656 | readonly questions?: ReadonlyArray<{ | ||
| 22657 | readonly title: string; | ||
| 22658 | readonly options?: ReadonlyArray<string> | null; | ||
| 22659 | }> | null; | ||
| 22604 | readonly type: "agentMessage"; | 22660 | readonly type: "agentMessage"; |
| 22605 | } | 22661 | } |
| 22606 | | { readonly id: string; readonly text: string; readonly type: "plan" } | 22662 | | { readonly id: string; readonly text: string; readonly type: "plan" } |
| ... | @@ -22726,6 +22782,18 @@ export const V2ReviewStartResponse__ThreadItem = Schema.Union( | ... | @@ -22726,6 +22782,18 @@ export const V2ReviewStartResponse__ThreadItem = Schema.Union( |
| 22726 | ), | 22782 | ), |
| 22727 | phase: Schema.optionalKey(Schema.Union([V2ReviewStartResponse__MessagePhase, Schema.Null])), | 22783 | phase: Schema.optionalKey(Schema.Union([V2ReviewStartResponse__MessagePhase, Schema.Null])), |
| 22728 | text: Schema.String, | 22784 | text: Schema.String, |
| 22785 | delivery: Schema.optionalKey(Schema.Union([Schema.Literal("async"), Schema.Null])), | ||
| 22786 | questions: Schema.optionalKey( | ||
| 22787 | Schema.Union([ | ||
| 22788 | Schema.Array( | ||
| 22789 | Schema.Struct({ | ||
| 22790 | title: Schema.String, | ||
| 22791 | options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), | ||
| 22792 | }), | ||
| 22793 | ), | ||
| 22794 | Schema.Null, | ||
| 22795 | ]), | ||
| 22796 | ), | ||
| 22729 | type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), | 22797 | type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), |
| 22730 | }).annotate({ title: "AgentMessageThreadItem" }), | 22798 | }).annotate({ title: "AgentMessageThreadItem" }), |
| 22731 | Schema.Struct({ | 22799 | Schema.Struct({ |
| ... | @@ -23050,6 +23118,11 @@ export type V2ThreadForkResponse__ThreadItem = | ... | @@ -23050,6 +23118,11 @@ export type V2ThreadForkResponse__ThreadItem = |
| 23050 | readonly memoryCitation?: V2ThreadForkResponse__MemoryCitation | null; | 23118 | readonly memoryCitation?: V2ThreadForkResponse__MemoryCitation | null; |
| 23051 | readonly phase?: V2ThreadForkResponse__MessagePhase | null; | 23119 | readonly phase?: V2ThreadForkResponse__MessagePhase | null; |
| 23052 | readonly text: string; | 23120 | readonly text: string; |
| 23121 | readonly delivery?: "async" | null; | ||
| 23122 | readonly questions?: ReadonlyArray<{ | ||
| 23123 | readonly title: string; | ||
| 23124 | readonly options?: ReadonlyArray<string> | null; | ||
| 23125 | }> | null; | ||
| 23053 | readonly type: "agentMessage"; | 23126 | readonly type: "agentMessage"; |
| 23054 | } | 23127 | } |
| 23055 | | { readonly id: string; readonly text: string; readonly type: "plan" } | 23128 | | { readonly id: string; readonly text: string; readonly type: "plan" } |
| ... | @@ -23175,6 +23248,18 @@ export const V2ThreadForkResponse__ThreadItem = Schema.Union( | ... | @@ -23175,6 +23248,18 @@ export const V2ThreadForkResponse__ThreadItem = Schema.Union( |
| 23175 | ), | 23248 | ), |
| 23176 | phase: Schema.optionalKey(Schema.Union([V2ThreadForkResponse__MessagePhase, Schema.Null])), | 23249 | phase: Schema.optionalKey(Schema.Union([V2ThreadForkResponse__MessagePhase, Schema.Null])), |
| 23177 | text: Schema.String, | 23250 | text: Schema.String, |
| 23251 | delivery: Schema.optionalKey(Schema.Union([Schema.Literal("async"), Schema.Null])), | ||
| 23252 | questions: Schema.optionalKey( | ||
| 23253 | Schema.Union([ | ||
| 23254 | Schema.Array( | ||
| 23255 | Schema.Struct({ | ||
| 23256 | title: Schema.String, | ||
| 23257 | options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), | ||
| 23258 | }), | ||
| 23259 | ), | ||
| 23260 | Schema.Null, | ||
| 23261 | ]), | ||
| 23262 | ), | ||
| 23178 | type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), | 23263 | type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), |
| 23179 | }).annotate({ title: "AgentMessageThreadItem" }), | 23264 | }).annotate({ title: "AgentMessageThreadItem" }), |
| 23180 | Schema.Struct({ | 23265 | Schema.Struct({ |
| ... | @@ -23468,6 +23553,11 @@ export type V2ThreadListResponse__ThreadItem = | ... | @@ -23468,6 +23553,11 @@ export type V2ThreadListResponse__ThreadItem = |
| 23468 | readonly memoryCitation?: V2ThreadListResponse__MemoryCitation | null; | 23553 | readonly memoryCitation?: V2ThreadListResponse__MemoryCitation | null; |
| 23469 | readonly phase?: V2ThreadListResponse__MessagePhase | null; | 23554 | readonly phase?: V2ThreadListResponse__MessagePhase | null; |
| 23470 | readonly text: string; | 23555 | readonly text: string; |
| 23556 | readonly delivery?: "async" | null; | ||
| 23557 | readonly questions?: ReadonlyArray<{ | ||
| 23558 | readonly title: string; | ||
| 23559 | readonly options?: ReadonlyArray<string> | null; | ||
| 23560 | }> | null; | ||
| 23471 | readonly type: "agentMessage"; | 23561 | readonly type: "agentMessage"; |
| 23472 | } | 23562 | } |
| 23473 | | { readonly id: string; readonly text: string; readonly type: "plan" } | 23563 | | { readonly id: string; readonly text: string; readonly type: "plan" } |
| ... | @@ -23593,6 +23683,18 @@ export const V2ThreadListResponse__ThreadItem = Schema.Union( | ... | @@ -23593,6 +23683,18 @@ export const V2ThreadListResponse__ThreadItem = Schema.Union( |
| 23593 | ), | 23683 | ), |
| 23594 | phase: Schema.optionalKey(Schema.Union([V2ThreadListResponse__MessagePhase, Schema.Null])), | 23684 | phase: Schema.optionalKey(Schema.Union([V2ThreadListResponse__MessagePhase, Schema.Null])), |
| 23595 | text: Schema.String, | 23685 | text: Schema.String, |
| 23686 | delivery: Schema.optionalKey(Schema.Union([Schema.Literal("async"), Schema.Null])), | ||
| 23687 | questions: Schema.optionalKey( | ||
| 23688 | Schema.Union([ | ||
| 23689 | Schema.Array( | ||
| 23690 | Schema.Struct({ | ||
| 23691 | title: Schema.String, | ||
| 23692 | options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), | ||
| 23693 | }), | ||
| 23694 | ), | ||
| 23695 | Schema.Null, | ||
| 23696 | ]), | ||
| 23697 | ), | ||
| 23596 | type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), | 23698 | type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), |
| 23597 | }).annotate({ title: "AgentMessageThreadItem" }), | 23699 | }).annotate({ title: "AgentMessageThreadItem" }), |
| 23598 | Schema.Struct({ | 23700 | Schema.Struct({ |
| ... | @@ -23886,6 +23988,11 @@ export type V2ThreadMetadataUpdateResponse__ThreadItem = | ... | @@ -23886,6 +23988,11 @@ export type V2ThreadMetadataUpdateResponse__ThreadItem = |
| 23886 | readonly memoryCitation?: V2ThreadMetadataUpdateResponse__MemoryCitation | null; | 23988 | readonly memoryCitation?: V2ThreadMetadataUpdateResponse__MemoryCitation | null; |
| 23887 | readonly phase?: V2ThreadMetadataUpdateResponse__MessagePhase | null; | 23989 | readonly phase?: V2ThreadMetadataUpdateResponse__MessagePhase | null; |
| 23888 | readonly text: string; | 23990 | readonly text: string; |
| 23991 | readonly delivery?: "async" | null; | ||
| 23992 | readonly questions?: ReadonlyArray<{ | ||
| 23993 | readonly title: string; | ||
| 23994 | readonly options?: ReadonlyArray<string> | null; | ||
| 23995 | }> | null; | ||
| 23889 | readonly type: "agentMessage"; | 23996 | readonly type: "agentMessage"; |
| 23890 | } | 23997 | } |
| 23891 | | { readonly id: string; readonly text: string; readonly type: "plan" } | 23998 | | { readonly id: string; readonly text: string; readonly type: "plan" } |
| ... | @@ -24015,6 +24122,18 @@ export const V2ThreadMetadataUpdateResponse__ThreadItem = Schema.Union( | ... | @@ -24015,6 +24122,18 @@ export const V2ThreadMetadataUpdateResponse__ThreadItem = Schema.Union( |
| 24015 | Schema.Union([V2ThreadMetadataUpdateResponse__MessagePhase, Schema.Null]), | 24122 | Schema.Union([V2ThreadMetadataUpdateResponse__MessagePhase, Schema.Null]), |
| 24016 | ), | 24123 | ), |
| 24017 | text: Schema.String, | 24124 | text: Schema.String, |
| 24125 | delivery: Schema.optionalKey(Schema.Union([Schema.Literal("async"), Schema.Null])), | ||
| 24126 | questions: Schema.optionalKey( | ||
| 24127 | Schema.Union([ | ||
| 24128 | Schema.Array( | ||
| 24129 | Schema.Struct({ | ||
| 24130 | title: Schema.String, | ||
| 24131 | options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), | ||
| 24132 | }), | ||
| 24133 | ), | ||
| 24134 | Schema.Null, | ||
| 24135 | ]), | ||
| 24136 | ), | ||
| 24018 | type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), | 24137 | type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), |
| 24019 | }).annotate({ title: "AgentMessageThreadItem" }), | 24138 | }).annotate({ title: "AgentMessageThreadItem" }), |
| 24020 | Schema.Struct({ | 24139 | Schema.Struct({ |
| ... | @@ -24309,6 +24428,11 @@ export type V2ThreadReadResponse__ThreadItem = | ... | @@ -24309,6 +24428,11 @@ export type V2ThreadReadResponse__ThreadItem = |
| 24309 | readonly memoryCitation?: V2ThreadReadResponse__MemoryCitation | null; | 24428 | readonly memoryCitation?: V2ThreadReadResponse__MemoryCitation | null; |
| 24310 | readonly phase?: V2ThreadReadResponse__MessagePhase | null; | 24429 | readonly phase?: V2ThreadReadResponse__MessagePhase | null; |
| 24311 | readonly text: string; | 24430 | readonly text: string; |
| 24431 | readonly delivery?: "async" | null; | ||
| 24432 | readonly questions?: ReadonlyArray<{ | ||
| 24433 | readonly title: string; | ||
| 24434 | readonly options?: ReadonlyArray<string> | null; | ||
| 24435 | }> | null; | ||
| 24312 | readonly type: "agentMessage"; | 24436 | readonly type: "agentMessage"; |
| 24313 | } | 24437 | } |
| 24314 | | { readonly id: string; readonly text: string; readonly type: "plan" } | 24438 | | { readonly id: string; readonly text: string; readonly type: "plan" } |
| ... | @@ -24434,6 +24558,18 @@ export const V2ThreadReadResponse__ThreadItem = Schema.Union( | ... | @@ -24434,6 +24558,18 @@ export const V2ThreadReadResponse__ThreadItem = Schema.Union( |
| 24434 | ), | 24558 | ), |
| 24435 | phase: Schema.optionalKey(Schema.Union([V2ThreadReadResponse__MessagePhase, Schema.Null])), | 24559 | phase: Schema.optionalKey(Schema.Union([V2ThreadReadResponse__MessagePhase, Schema.Null])), |
| 24436 | text: Schema.String, | 24560 | text: Schema.String, |
| 24561 | delivery: Schema.optionalKey(Schema.Union([Schema.Literal("async"), Schema.Null])), | ||
| 24562 | questions: Schema.optionalKey( | ||
| 24563 | Schema.Union([ | ||
| 24564 | Schema.Array( | ||
| 24565 | Schema.Struct({ | ||
| 24566 | title: Schema.String, | ||
| 24567 | options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), | ||
| 24568 | }), | ||
| 24569 | ), | ||
| 24570 | Schema.Null, | ||
| 24571 | ]), | ||
| 24572 | ), | ||
| 24437 | type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), | 24573 | type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), |
| 24438 | }).annotate({ title: "AgentMessageThreadItem" }), | 24574 | }).annotate({ title: "AgentMessageThreadItem" }), |
| 24439 | Schema.Struct({ | 24575 | Schema.Struct({ |
| ... | @@ -24735,6 +24871,11 @@ export type V2ThreadResumeResponse__ThreadItem = | ... | @@ -24735,6 +24871,11 @@ export type V2ThreadResumeResponse__ThreadItem = |
| 24735 | readonly memoryCitation?: V2ThreadResumeResponse__MemoryCitation | null; | 24871 | readonly memoryCitation?: V2ThreadResumeResponse__MemoryCitation | null; |
| 24736 | readonly phase?: V2ThreadResumeResponse__MessagePhase | null; | 24872 | readonly phase?: V2ThreadResumeResponse__MessagePhase | null; |
| 24737 | readonly text: string; | 24873 | readonly text: string; |
| 24874 | readonly delivery?: "async" | null; | ||
| 24875 | readonly questions?: ReadonlyArray<{ | ||
| 24876 | readonly title: string; | ||
| 24877 | readonly options?: ReadonlyArray<string> | null; | ||
| 24878 | }> | null; | ||
| 24738 | readonly type: "agentMessage"; | 24879 | readonly type: "agentMessage"; |
| 24739 | } | 24880 | } |
| 24740 | | { readonly id: string; readonly text: string; readonly type: "plan" } | 24881 | | { readonly id: string; readonly text: string; readonly type: "plan" } |
| ... | @@ -24860,6 +25001,18 @@ export const V2ThreadResumeResponse__ThreadItem = Schema.Union( | ... | @@ -24860,6 +25001,18 @@ export const V2ThreadResumeResponse__ThreadItem = Schema.Union( |
| 24860 | ), | 25001 | ), |
| 24861 | phase: Schema.optionalKey(Schema.Union([V2ThreadResumeResponse__MessagePhase, Schema.Null])), | 25002 | phase: Schema.optionalKey(Schema.Union([V2ThreadResumeResponse__MessagePhase, Schema.Null])), |
| 24862 | text: Schema.String, | 25003 | text: Schema.String, |
| 25004 | delivery: Schema.optionalKey(Schema.Union([Schema.Literal("async"), Schema.Null])), | ||
| 25005 | questions: Schema.optionalKey( | ||
| 25006 | Schema.Union([ | ||
| 25007 | Schema.Array( | ||
| 25008 | Schema.Struct({ | ||
| 25009 | title: Schema.String, | ||
| 25010 | options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), | ||
| 25011 | }), | ||
| 25012 | ), | ||
| 25013 | Schema.Null, | ||
| 25014 | ]), | ||
| 25015 | ), | ||
| 24863 | type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), | 25016 | type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), |
| 24864 | }).annotate({ title: "AgentMessageThreadItem" }), | 25017 | }).annotate({ title: "AgentMessageThreadItem" }), |
| 24865 | Schema.Struct({ | 25018 | Schema.Struct({ |
| ... | @@ -25153,6 +25306,11 @@ export type V2ThreadRollbackResponse__ThreadItem = | ... | @@ -25153,6 +25306,11 @@ export type V2ThreadRollbackResponse__ThreadItem = |
| 25153 | readonly memoryCitation?: V2ThreadRollbackResponse__MemoryCitation | null; | 25306 | readonly memoryCitation?: V2ThreadRollbackResponse__MemoryCitation | null; |
| 25154 | readonly phase?: V2ThreadRollbackResponse__MessagePhase | null; | 25307 | readonly phase?: V2ThreadRollbackResponse__MessagePhase | null; |
| 25155 | readonly text: string; | 25308 | readonly text: string; |
| 25309 | readonly delivery?: "async" | null; | ||
| 25310 | readonly questions?: ReadonlyArray<{ | ||
| 25311 | readonly title: string; | ||
| 25312 | readonly options?: ReadonlyArray<string> | null; | ||
| 25313 | }> | null; | ||
| 25156 | readonly type: "agentMessage"; | 25314 | readonly type: "agentMessage"; |
| 25157 | } | 25315 | } |
| 25158 | | { readonly id: string; readonly text: string; readonly type: "plan" } | 25316 | | { readonly id: string; readonly text: string; readonly type: "plan" } |
| ... | @@ -25280,6 +25438,18 @@ export const V2ThreadRollbackResponse__ThreadItem = Schema.Union( | ... | @@ -25280,6 +25438,18 @@ export const V2ThreadRollbackResponse__ThreadItem = Schema.Union( |
| 25280 | Schema.Union([V2ThreadRollbackResponse__MessagePhase, Schema.Null]), | 25438 | Schema.Union([V2ThreadRollbackResponse__MessagePhase, Schema.Null]), |
| 25281 | ), | 25439 | ), |
| 25282 | text: Schema.String, | 25440 | text: Schema.String, |
| 25441 | delivery: Schema.optionalKey(Schema.Union([Schema.Literal("async"), Schema.Null])), | ||
| 25442 | questions: Schema.optionalKey( | ||
| 25443 | Schema.Union([ | ||
| 25444 | Schema.Array( | ||
| 25445 | Schema.Struct({ | ||
| 25446 | title: Schema.String, | ||
| 25447 | options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), | ||
| 25448 | }), | ||
| 25449 | ), | ||
| 25450 | Schema.Null, | ||
| 25451 | ]), | ||
| 25452 | ), | ||
| 25283 | type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), | 25453 | type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), |
| 25284 | }).annotate({ title: "AgentMessageThreadItem" }), | 25454 | }).annotate({ title: "AgentMessageThreadItem" }), |
| 25285 | Schema.Struct({ | 25455 | Schema.Struct({ |
| ... | @@ -25583,6 +25753,11 @@ export type V2ThreadStartedNotification__ThreadItem = | ... | @@ -25583,6 +25753,11 @@ export type V2ThreadStartedNotification__ThreadItem = |
| 25583 | readonly memoryCitation?: V2ThreadStartedNotification__MemoryCitation | null; | 25753 | readonly memoryCitation?: V2ThreadStartedNotification__MemoryCitation | null; |
| 25584 | readonly phase?: V2ThreadStartedNotification__MessagePhase | null; | 25754 | readonly phase?: V2ThreadStartedNotification__MessagePhase | null; |
| 25585 | readonly text: string; | 25755 | readonly text: string; |
| 25756 | readonly delivery?: "async" | null; | ||
| 25757 | readonly questions?: ReadonlyArray<{ | ||
| 25758 | readonly title: string; | ||
| 25759 | readonly options?: ReadonlyArray<string> | null; | ||
| 25760 | }> | null; | ||
| 25586 | readonly type: "agentMessage"; | 25761 | readonly type: "agentMessage"; |
| 25587 | } | 25762 | } |
| 25588 | | { readonly id: string; readonly text: string; readonly type: "plan" } | 25763 | | { readonly id: string; readonly text: string; readonly type: "plan" } |
| ... | @@ -25712,6 +25887,18 @@ export const V2ThreadStartedNotification__ThreadItem = Schema.Union( | ... | @@ -25712,6 +25887,18 @@ export const V2ThreadStartedNotification__ThreadItem = Schema.Union( |
| 25712 | Schema.Union([V2ThreadStartedNotification__MessagePhase, Schema.Null]), | 25887 | Schema.Union([V2ThreadStartedNotification__MessagePhase, Schema.Null]), |
| 25713 | ), | 25888 | ), |
| 25714 | text: Schema.String, | 25889 | text: Schema.String, |
| 25890 | delivery: Schema.optionalKey(Schema.Union([Schema.Literal("async"), Schema.Null])), | ||
| 25891 | questions: Schema.optionalKey( | ||
| 25892 | Schema.Union([ | ||
| 25893 | Schema.Array( | ||
| 25894 | Schema.Struct({ | ||
| 25895 | title: Schema.String, | ||
| 25896 | options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), | ||
| 25897 | }), | ||
| 25898 | ), | ||
| 25899 | Schema.Null, | ||
| 25900 | ]), | ||
| 25901 | ), | ||
| 25715 | type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), | 25902 | type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), |
| 25716 | }).annotate({ title: "AgentMessageThreadItem" }), | 25903 | }).annotate({ title: "AgentMessageThreadItem" }), |
| 25717 | Schema.Struct({ | 25904 | Schema.Struct({ |
| ... | @@ -26006,6 +26193,11 @@ export type V2ThreadStartResponse__ThreadItem = | ... | @@ -26006,6 +26193,11 @@ export type V2ThreadStartResponse__ThreadItem = |
| 26006 | readonly memoryCitation?: V2ThreadStartResponse__MemoryCitation | null; | 26193 | readonly memoryCitation?: V2ThreadStartResponse__MemoryCitation | null; |
| 26007 | readonly phase?: V2ThreadStartResponse__MessagePhase | null; | 26194 | readonly phase?: V2ThreadStartResponse__MessagePhase | null; |
| 26008 | readonly text: string; | 26195 | readonly text: string; |
| 26196 | readonly delivery?: "async" | null; | ||
| 26197 | readonly questions?: ReadonlyArray<{ | ||
| 26198 | readonly title: string; | ||
| 26199 | readonly options?: ReadonlyArray<string> | null; | ||
| 26200 | }> | null; | ||
| 26009 | readonly type: "agentMessage"; | 26201 | readonly type: "agentMessage"; |
| 26010 | } | 26202 | } |
| 26011 | | { readonly id: string; readonly text: string; readonly type: "plan" } | 26203 | | { readonly id: string; readonly text: string; readonly type: "plan" } |
| ... | @@ -26131,6 +26323,18 @@ export const V2ThreadStartResponse__ThreadItem = Schema.Union( | ... | @@ -26131,6 +26323,18 @@ export const V2ThreadStartResponse__ThreadItem = Schema.Union( |
| 26131 | ), | 26323 | ), |
| 26132 | phase: Schema.optionalKey(Schema.Union([V2ThreadStartResponse__MessagePhase, Schema.Null])), | 26324 | phase: Schema.optionalKey(Schema.Union([V2ThreadStartResponse__MessagePhase, Schema.Null])), |
| 26133 | text: Schema.String, | 26325 | text: Schema.String, |
| 26326 | delivery: Schema.optionalKey(Schema.Union([Schema.Literal("async"), Schema.Null])), | ||
| 26327 | questions: Schema.optionalKey( | ||
| 26328 | Schema.Union([ | ||
| 26329 | Schema.Array( | ||
| 26330 | Schema.Struct({ | ||
| 26331 | title: Schema.String, | ||
| 26332 | options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), | ||
| 26333 | }), | ||
| 26334 | ), | ||
| 26335 | Schema.Null, | ||
| 26336 | ]), | ||
| 26337 | ), | ||
| 26134 | type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), | 26338 | type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), |
| 26135 | }).annotate({ title: "AgentMessageThreadItem" }), | 26339 | }).annotate({ title: "AgentMessageThreadItem" }), |
| 26136 | Schema.Struct({ | 26340 | Schema.Struct({ |
| ... | @@ -26424,6 +26628,11 @@ export type V2ThreadUnarchiveResponse__ThreadItem = | ... | @@ -26424,6 +26628,11 @@ export type V2ThreadUnarchiveResponse__ThreadItem = |
| 26424 | readonly memoryCitation?: V2ThreadUnarchiveResponse__MemoryCitation | null; | 26628 | readonly memoryCitation?: V2ThreadUnarchiveResponse__MemoryCitation | null; |
| 26425 | readonly phase?: V2ThreadUnarchiveResponse__MessagePhase | null; | 26629 | readonly phase?: V2ThreadUnarchiveResponse__MessagePhase | null; |
| 26426 | readonly text: string; | 26630 | readonly text: string; |
| 26631 | readonly delivery?: "async" | null; | ||
| 26632 | readonly questions?: ReadonlyArray<{ | ||
| 26633 | readonly title: string; | ||
| 26634 | readonly options?: ReadonlyArray<string> | null; | ||
| 26635 | }> | null; | ||
| 26427 | readonly type: "agentMessage"; | 26636 | readonly type: "agentMessage"; |
| 26428 | } | 26637 | } |
| 26429 | | { readonly id: string; readonly text: string; readonly type: "plan" } | 26638 | | { readonly id: string; readonly text: string; readonly type: "plan" } |
| ... | @@ -26551,6 +26760,18 @@ export const V2ThreadUnarchiveResponse__ThreadItem = Schema.Union( | ... | @@ -26551,6 +26760,18 @@ export const V2ThreadUnarchiveResponse__ThreadItem = Schema.Union( |
| 26551 | Schema.Union([V2ThreadUnarchiveResponse__MessagePhase, Schema.Null]), | 26760 | Schema.Union([V2ThreadUnarchiveResponse__MessagePhase, Schema.Null]), |
| 26552 | ), | 26761 | ), |
| 26553 | text: Schema.String, | 26762 | text: Schema.String, |
| 26763 | delivery: Schema.optionalKey(Schema.Union([Schema.Literal("async"), Schema.Null])), | ||
| 26764 | questions: Schema.optionalKey( | ||
| 26765 | Schema.Union([ | ||
| 26766 | Schema.Array( | ||
| 26767 | Schema.Struct({ | ||
| 26768 | title: Schema.String, | ||
| 26769 | options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), | ||
| 26770 | }), | ||
| 26771 | ), | ||
| 26772 | Schema.Null, | ||
| 26773 | ]), | ||
| 26774 | ), | ||
| 26554 | type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), | 26775 | type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), |
| 26555 | }).annotate({ title: "AgentMessageThreadItem" }), | 26776 | }).annotate({ title: "AgentMessageThreadItem" }), |
| 26556 | Schema.Struct({ | 26777 | Schema.Struct({ |
| ... | @@ -26845,6 +27066,11 @@ export type V2TurnCompletedNotification__ThreadItem = | ... | @@ -26845,6 +27066,11 @@ export type V2TurnCompletedNotification__ThreadItem = |
| 26845 | readonly memoryCitation?: V2TurnCompletedNotification__MemoryCitation | null; | 27066 | readonly memoryCitation?: V2TurnCompletedNotification__MemoryCitation | null; |
| 26846 | readonly phase?: V2TurnCompletedNotification__MessagePhase | null; | 27067 | readonly phase?: V2TurnCompletedNotification__MessagePhase | null; |
| 26847 | readonly text: string; | 27068 | readonly text: string; |
| 27069 | readonly delivery?: "async" | null; | ||
| 27070 | readonly questions?: ReadonlyArray<{ | ||
| 27071 | readonly title: string; | ||
| 27072 | readonly options?: ReadonlyArray<string> | null; | ||
| 27073 | }> | null; | ||
| 26848 | readonly type: "agentMessage"; | 27074 | readonly type: "agentMessage"; |
| 26849 | } | 27075 | } |
| 26850 | | { readonly id: string; readonly text: string; readonly type: "plan" } | 27076 | | { readonly id: string; readonly text: string; readonly type: "plan" } |
| ... | @@ -26974,6 +27200,18 @@ export const V2TurnCompletedNotification__ThreadItem = Schema.Union( | ... | @@ -26974,6 +27200,18 @@ export const V2TurnCompletedNotification__ThreadItem = Schema.Union( |
| 26974 | Schema.Union([V2TurnCompletedNotification__MessagePhase, Schema.Null]), | 27200 | Schema.Union([V2TurnCompletedNotification__MessagePhase, Schema.Null]), |
| 26975 | ), | 27201 | ), |
| 26976 | text: Schema.String, | 27202 | text: Schema.String, |
| 27203 | delivery: Schema.optionalKey(Schema.Union([Schema.Literal("async"), Schema.Null])), | ||
| 27204 | questions: Schema.optionalKey( | ||
| 27205 | Schema.Union([ | ||
| 27206 | Schema.Array( | ||
| 27207 | Schema.Struct({ | ||
| 27208 | title: Schema.String, | ||
| 27209 | options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), | ||
| 27210 | }), | ||
| 27211 | ), | ||
| 27212 | Schema.Null, | ||
| 27213 | ]), | ||
| 27214 | ), | ||
| 26977 | type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), | 27215 | type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), |
| 26978 | }).annotate({ title: "AgentMessageThreadItem" }), | 27216 | }).annotate({ title: "AgentMessageThreadItem" }), |
| 26979 | Schema.Struct({ | 27217 | Schema.Struct({ |
| ... | @@ -27268,6 +27506,11 @@ export type V2TurnStartedNotification__ThreadItem = | ... | @@ -27268,6 +27506,11 @@ export type V2TurnStartedNotification__ThreadItem = |
| 27268 | readonly memoryCitation?: V2TurnStartedNotification__MemoryCitation | null; | 27506 | readonly memoryCitation?: V2TurnStartedNotification__MemoryCitation | null; |
| 27269 | readonly phase?: V2TurnStartedNotification__MessagePhase | null; | 27507 | readonly phase?: V2TurnStartedNotification__MessagePhase | null; |
| 27270 | readonly text: string; | 27508 | readonly text: string; |
| 27509 | readonly delivery?: "async" | null; | ||
| 27510 | readonly questions?: ReadonlyArray<{ | ||
| 27511 | readonly title: string; | ||
| 27512 | readonly options?: ReadonlyArray<string> | null; | ||
| 27513 | }> | null; | ||
| 27271 | readonly type: "agentMessage"; | 27514 | readonly type: "agentMessage"; |
| 27272 | } | 27515 | } |
| 27273 | | { readonly id: string; readonly text: string; readonly type: "plan" } | 27516 | | { readonly id: string; readonly text: string; readonly type: "plan" } |
| ... | @@ -27395,6 +27638,18 @@ export const V2TurnStartedNotification__ThreadItem = Schema.Union( | ... | @@ -27395,6 +27638,18 @@ export const V2TurnStartedNotification__ThreadItem = Schema.Union( |
| 27395 | Schema.Union([V2TurnStartedNotification__MessagePhase, Schema.Null]), | 27638 | Schema.Union([V2TurnStartedNotification__MessagePhase, Schema.Null]), |
| 27396 | ), | 27639 | ), |
| 27397 | text: Schema.String, | 27640 | text: Schema.String, |
| 27641 | delivery: Schema.optionalKey(Schema.Union([Schema.Literal("async"), Schema.Null])), | ||
| 27642 | questions: Schema.optionalKey( | ||
| 27643 | Schema.Union([ | ||
| 27644 | Schema.Array( | ||
| 27645 | Schema.Struct({ | ||
| 27646 | title: Schema.String, | ||
| 27647 | options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), | ||
| 27648 | }), | ||
| 27649 | ), | ||
| 27650 | Schema.Null, | ||
| 27651 | ]), | ||
| 27652 | ), | ||
| 27398 | type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), | 27653 | type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), |
| 27399 | }).annotate({ title: "AgentMessageThreadItem" }), | 27654 | }).annotate({ title: "AgentMessageThreadItem" }), |
| 27400 | Schema.Struct({ | 27655 | Schema.Struct({ |
| ... | @@ -27689,6 +27944,11 @@ export type V2TurnStartResponse__ThreadItem = | ... | @@ -27689,6 +27944,11 @@ export type V2TurnStartResponse__ThreadItem = |
| 27689 | readonly memoryCitation?: V2TurnStartResponse__MemoryCitation | null; | 27944 | readonly memoryCitation?: V2TurnStartResponse__MemoryCitation | null; |
| 27690 | readonly phase?: V2TurnStartResponse__MessagePhase | null; | 27945 | readonly phase?: V2TurnStartResponse__MessagePhase | null; |
| 27691 | readonly text: string; | 27946 | readonly text: string; |
| 27947 | readonly delivery?: "async" | null; | ||
| 27948 | readonly questions?: ReadonlyArray<{ | ||
| 27949 | readonly title: string; | ||
| 27950 | readonly options?: ReadonlyArray<string> | null; | ||
| 27951 | }> | null; | ||
| 27692 | readonly type: "agentMessage"; | 27952 | readonly type: "agentMessage"; |
| 27693 | } | 27953 | } |
| 27694 | | { readonly id: string; readonly text: string; readonly type: "plan" } | 27954 | | { readonly id: string; readonly text: string; readonly type: "plan" } |
| ... | @@ -27814,6 +28074,18 @@ export const V2TurnStartResponse__ThreadItem = Schema.Union( | ... | @@ -27814,6 +28074,18 @@ export const V2TurnStartResponse__ThreadItem = Schema.Union( |
| 27814 | ), | 28074 | ), |
| 27815 | phase: Schema.optionalKey(Schema.Union([V2TurnStartResponse__MessagePhase, Schema.Null])), | 28075 | phase: Schema.optionalKey(Schema.Union([V2TurnStartResponse__MessagePhase, Schema.Null])), |
| 27816 | text: Schema.String, | 28076 | text: Schema.String, |
| 28077 | delivery: Schema.optionalKey(Schema.Union([Schema.Literal("async"), Schema.Null])), | ||
| 28078 | questions: Schema.optionalKey( | ||
| 28079 | Schema.Union([ | ||
| 28080 | Schema.Array( | ||
| 28081 | Schema.Struct({ | ||
| 28082 | title: Schema.String, | ||
| 28083 | options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), | ||
| 28084 | }), | ||
| 28085 | ), | ||
| 28086 | Schema.Null, | ||
| 28087 | ]), | ||
| 28088 | ), | ||
| 27817 | type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), | 28089 | type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), |
| 27818 | }).annotate({ title: "AgentMessageThreadItem" }), | 28090 | }).annotate({ title: "AgentMessageThreadItem" }), |
| 27819 | Schema.Struct({ | 28091 | Schema.Struct({ |
packages/effect-codex-app-server/src/schema.test.ts+23| ... | @@ -5,6 +5,29 @@ import * as CodexSchema from "./schema.ts"; | ... | @@ -5,6 +5,29 @@ import * as CodexSchema from "./schema.ts"; |
| 5 | 5 | ||
| 6 | const isGetAccountResponse = Schema.is(CodexSchema.V2GetAccountResponse); | 6 | const isGetAccountResponse = Schema.is(CodexSchema.V2GetAccountResponse); |
| 7 | 7 | ||
| 8 | it("keeps async questions in live notifications and thread history", () => { | ||
| 9 | const item = { | ||
| 10 | type: "agentMessage", | ||
| 11 | id: "question-1", | ||
| 12 | text: "Which package?\n- pnpm\n- npm\n\nWhat should it be named?", | ||
| 13 | phase: "final_answer", | ||
| 14 | delivery: "async", | ||
| 15 | questions: [ | ||
| 16 | { title: "Which package manager?", options: ["pnpm", "npm"] }, | ||
| 17 | { title: "What should it be named?" }, | ||
| 18 | ], | ||
| 19 | } as const; | ||
| 20 | for (const schema of [ | ||
| 21 | CodexSchema.ServerNotification__ThreadItem, | ||
| 22 | CodexSchema.V2ItemStartedNotification__ThreadItem, | ||
| 23 | CodexSchema.V2ItemCompletedNotification__ThreadItem, | ||
| 24 | CodexSchema.V2ThreadReadResponse__ThreadItem, | ||
| 25 | CodexSchema.V2ThreadResumeResponse__ThreadItem, | ||
| 26 | ]) { | ||
| 27 | assert.deepEqual(Schema.decodeUnknownSync(schema)(item), item); | ||
| 28 | } | ||
| 29 | }); | ||
| 30 | |||
| 8 | it("accepts Codex 0.150 multi-agent values", () => { | 31 | it("accepts Codex 0.150 multi-agent values", () => { |
| 9 | const schemas = [ | 32 | const schemas = [ |
| 10 | CodexSchema.ServerNotification__SubAgentActivityKind, | 33 | CodexSchema.ServerNotification__SubAgentActivityKind, |