| ... | @@ -55,11 +55,15 @@ import { | ... | @@ -55,11 +55,15 @@ import { |
| 55 | makeSqlitePersistenceLive, | 55 | makeSqlitePersistenceLive, |
| 56 | SqlitePersistenceMemory, | 56 | SqlitePersistenceMemory, |
| 57 | } from "../../persistence/Layers/Sqlite.ts"; | 57 | } from "../../persistence/Layers/Sqlite.ts"; |
| | 58 | import * as ServerConfig from "../../config.ts"; |
| 58 | import * as ServerSettings from "../../serverSettings.ts"; | 59 | import * as ServerSettings from "../../serverSettings.ts"; |
| 59 | import * as AnalyticsService from "../../telemetry/AnalyticsService.ts"; | 60 | import * as AnalyticsService from "../../telemetry/AnalyticsService.ts"; |
| 60 | import { makeAdapterRegistryMock } from "../testUtils/providerAdapterRegistryMock.ts"; | 61 | import { makeAdapterRegistryMock } from "../testUtils/providerAdapterRegistryMock.ts"; |
| 61 | | 62 | |
| 62 | const defaultServerSettingsLayer = ServerSettings.ServerSettingsService.layerTest(); | 63 | const defaultServerSettingsLayer = ServerSettings.ServerSettingsService.layerTest(); |
| | 64 | const serverConfigTestLayer = ServerConfig.layerTest(process.cwd(), process.cwd()).pipe( |
| | 65 | Layer.provide(NodeServices.layer), |
| | 66 | ); |
| 63 | | 67 | |
| 64 | const asRequestId = (value: string): ApprovalRequestId => ApprovalRequestId.make(value); | 68 | const asRequestId = (value: string): ApprovalRequestId => ApprovalRequestId.make(value); |
| 65 | const asEventId = (value: string): EventId => EventId.make(value); | 69 | const asEventId = (value: string): EventId => EventId.make(value); |
| ... | @@ -292,6 +296,7 @@ function makeProviderServiceLayer() { | ... | @@ -292,6 +296,7 @@ function makeProviderServiceLayer() { |
| 292 | Layer.provide(providerAdapterLayer), | 296 | Layer.provide(providerAdapterLayer), |
| 293 | Layer.provide(directoryLayer), | 297 | Layer.provide(directoryLayer), |
| 294 | Layer.provide(defaultServerSettingsLayer), | 298 | Layer.provide(defaultServerSettingsLayer), |
| | 299 | Layer.provide(serverConfigTestLayer), |
| 295 | Layer.provideMerge(AnalyticsService.layerTest), | 300 | Layer.provideMerge(AnalyticsService.layerTest), |
| 296 | Layer.provide( | 301 | Layer.provide( |
| 297 | Layer.succeed( | 302 | Layer.succeed( |
| ... | @@ -343,6 +348,7 @@ it.effect("ProviderServiceLive catches stopAll failures during shutdown", () => | ... | @@ -343,6 +348,7 @@ it.effect("ProviderServiceLive catches stopAll failures during shutdown", () => |
| 343 | Layer.provide(providerAdapterLayer), | 348 | Layer.provide(providerAdapterLayer), |
| 344 | Layer.provide(directoryLayer), | 349 | Layer.provide(directoryLayer), |
| 345 | Layer.provide(defaultServerSettingsLayer), | 350 | Layer.provide(defaultServerSettingsLayer), |
| | 351 | Layer.provide(serverConfigTestLayer), |
| 346 | Layer.provideMerge(AnalyticsService.layerTest), | 352 | Layer.provideMerge(AnalyticsService.layerTest), |
| 347 | Layer.provide( | 353 | Layer.provide( |
| 348 | Layer.succeed( | 354 | Layer.succeed( |
| ... | @@ -402,6 +408,7 @@ it.effect("ProviderServiceLive rejects new sessions for disabled providers", () | ... | @@ -402,6 +408,7 @@ it.effect("ProviderServiceLive rejects new sessions for disabled providers", () |
| 402 | Layer.provide(providerAdapterLayer), | 408 | Layer.provide(providerAdapterLayer), |
| 403 | Layer.provide(directoryLayer), | 409 | Layer.provide(directoryLayer), |
| 404 | Layer.provide(defaultServerSettingsLayer), | 410 | Layer.provide(defaultServerSettingsLayer), |
| | 411 | Layer.provide(serverConfigTestLayer), |
| 405 | Layer.provide(AnalyticsService.layerTest), | 412 | Layer.provide(AnalyticsService.layerTest), |
| 406 | Layer.provide( | 413 | Layer.provide( |
| 407 | Layer.succeed( | 414 | Layer.succeed( |
| ... | @@ -486,6 +493,7 @@ it.effect( | ... | @@ -486,6 +493,7 @@ it.effect( |
| 486 | Layer.provide(providerAdapterLayer), | 493 | Layer.provide(providerAdapterLayer), |
| 487 | Layer.provide(directoryLayer), | 494 | Layer.provide(directoryLayer), |
| 488 | Layer.provide(serverSettingsLayer), | 495 | Layer.provide(serverSettingsLayer), |
| | 496 | Layer.provide(serverConfigTestLayer), |
| 489 | Layer.provide(AnalyticsService.layerTest), | 497 | Layer.provide(AnalyticsService.layerTest), |
| 490 | Layer.provide( | 498 | Layer.provide( |
| 491 | Layer.succeed( | 499 | Layer.succeed( |
| ... | @@ -556,6 +564,7 @@ it.effect("ProviderServiceLive rejects new sessions for disabled custom instance | ... | @@ -556,6 +564,7 @@ it.effect("ProviderServiceLive rejects new sessions for disabled custom instance |
| 556 | Layer.provide(providerAdapterLayer), | 564 | Layer.provide(providerAdapterLayer), |
| 557 | Layer.provide(directoryLayer), | 565 | Layer.provide(directoryLayer), |
| 558 | Layer.provide(defaultServerSettingsLayer), | 566 | Layer.provide(defaultServerSettingsLayer), |
| | 567 | Layer.provide(serverConfigTestLayer), |
| 559 | Layer.provide(AnalyticsService.layerTest), | 568 | Layer.provide(AnalyticsService.layerTest), |
| 560 | Layer.provide( | 569 | Layer.provide( |
| 561 | Layer.succeed( | 570 | Layer.succeed( |
| ... | @@ -611,6 +620,7 @@ it.effect("ProviderServiceLive writes canonical events to the emitting thread se | ... | @@ -611,6 +620,7 @@ it.effect("ProviderServiceLive writes canonical events to the emitting thread se |
| 611 | Layer.provide(Layer.succeed(ProviderAdapterRegistry.ProviderAdapterRegistry, registry)), | 620 | Layer.provide(Layer.succeed(ProviderAdapterRegistry.ProviderAdapterRegistry, registry)), |
| 612 | Layer.provide(directoryLayer), | 621 | Layer.provide(directoryLayer), |
| 613 | Layer.provide(defaultServerSettingsLayer), | 622 | Layer.provide(defaultServerSettingsLayer), |
| | 623 | Layer.provide(serverConfigTestLayer), |
| 614 | Layer.provide(AnalyticsService.layerTest), | 624 | Layer.provide(AnalyticsService.layerTest), |
| 615 | Layer.provide( | 625 | Layer.provide( |
| 616 | Layer.succeed( | 626 | Layer.succeed( |
| ... | @@ -671,6 +681,7 @@ it.effect("ProviderServiceLive keeps persisted resumable sessions on startup", ( | ... | @@ -671,6 +681,7 @@ it.effect("ProviderServiceLive keeps persisted resumable sessions on startup", ( |
| 671 | Layer.provide(Layer.succeed(ProviderAdapterRegistry.ProviderAdapterRegistry, registry)), | 681 | Layer.provide(Layer.succeed(ProviderAdapterRegistry.ProviderAdapterRegistry, registry)), |
| 672 | Layer.provide(directoryLayer), | 682 | Layer.provide(directoryLayer), |
| 673 | Layer.provide(defaultServerSettingsLayer), | 683 | Layer.provide(defaultServerSettingsLayer), |
| | 684 | Layer.provide(serverConfigTestLayer), |
| 674 | Layer.provide(AnalyticsService.layerTest), | 685 | Layer.provide(AnalyticsService.layerTest), |
| 675 | Layer.provide( | 686 | Layer.provide( |
| 676 | Layer.succeed( | 687 | Layer.succeed( |
| ... | @@ -737,6 +748,7 @@ it.effect( | ... | @@ -737,6 +748,7 @@ it.effect( |
| 737 | ), | 748 | ), |
| 738 | Layer.provide(firstDirectoryLayer), | 749 | Layer.provide(firstDirectoryLayer), |
| 739 | Layer.provide(defaultServerSettingsLayer), | 750 | Layer.provide(defaultServerSettingsLayer), |
| | 751 | Layer.provide(serverConfigTestLayer), |
| 740 | Layer.provide(AnalyticsService.layerTest), | 752 | Layer.provide(AnalyticsService.layerTest), |
| 741 | Layer.provide( | 753 | Layer.provide( |
| 742 | Layer.succeed( | 754 | Layer.succeed( |
| ... | @@ -796,6 +808,7 @@ it.effect( | ... | @@ -796,6 +808,7 @@ it.effect( |
| 796 | ), | 808 | ), |
| 797 | Layer.provide(secondDirectoryLayer), | 809 | Layer.provide(secondDirectoryLayer), |
| 798 | Layer.provide(defaultServerSettingsLayer), | 810 | Layer.provide(defaultServerSettingsLayer), |
| | 811 | Layer.provide(serverConfigTestLayer), |
| 799 | Layer.provide(AnalyticsService.layerTest), | 812 | Layer.provide(AnalyticsService.layerTest), |
| 800 | Layer.provide( | 813 | Layer.provide( |
| 801 | Layer.succeed( | 814 | Layer.succeed( |
| ... | @@ -927,6 +940,54 @@ routing.layer("ProviderServiceLive routing", (it) => { | ... | @@ -927,6 +940,54 @@ routing.layer("ProviderServiceLive routing", (it) => { |
| 927 | }), | 940 | }), |
| 928 | ); | 941 | ); |
| 929 | | 942 | |
| | 943 | it.effect("appends attachment file paths to the turn input text", () => |
| | 944 | Effect.gen(function* () { |
| | 945 | const provider = yield* ProviderService.ProviderService; |
| | 946 | |
| | 947 | const session = yield* provider.startSession(asThreadId("thread-attach"), { |
| | 948 | provider: ProviderDriverKind.make("codex"), |
| | 949 | providerInstanceId: codexInstanceId, |
| | 950 | threadId: asThreadId("thread-attach"), |
| | 951 | cwd: "/tmp/project", |
| | 952 | runtimeMode: "full-access", |
| | 953 | }); |
| | 954 | |
| | 955 | const attachment = { |
| | 956 | type: "image" as const, |
| | 957 | id: "thread-attach-12345678-1234-1234-1234-123456789abc", |
| | 958 | name: "screenshot.png", |
| | 959 | mimeType: "image/png", |
| | 960 | sizeBytes: 123, |
| | 961 | }; |
| | 962 | |
| | 963 | routing.codex.sendTurn.mockClear(); |
| | 964 | yield* provider.sendTurn({ |
| | 965 | threadId: session.threadId, |
| | 966 | input: "use this screenshot", |
| | 967 | attachments: [attachment], |
| | 968 | }); |
| | 969 | |
| | 970 | const turnInput = routing.codex.sendTurn.mock.calls[0]?.[0] as ProviderSendTurnInput; |
| | 971 | assert.equal(typeof turnInput.input, "string"); |
| | 972 | const turnText = turnInput.input ?? ""; |
| | 973 | assert.equal(turnText.startsWith("use this screenshot"), true); |
| | 974 | assert.include(turnText, '[Attached image "screenshot.png" is saved at: '); |
| | 975 | assert.equal(turnText.endsWith(`${attachment.id}.png]`), true); |
| | 976 | |
| | 977 | // An attachment-only turn stays valid and the injected line becomes the |
| | 978 | // whole input text, so the agent still learns the path. |
| | 979 | routing.codex.sendTurn.mockClear(); |
| | 980 | yield* provider.sendTurn({ |
| | 981 | threadId: session.threadId, |
| | 982 | attachments: [attachment], |
| | 983 | }); |
| | 984 | const imageOnlyInput = routing.codex.sendTurn.mock.calls[0]?.[0] as ProviderSendTurnInput; |
| | 985 | assert.equal(imageOnlyInput.input?.startsWith('[Attached image "screenshot.png"'), true); |
| | 986 | |
| | 987 | yield* provider.stopSession({ threadId: session.threadId }); |
| | 988 | }), |
| | 989 | ); |
| | 990 | |
| 930 | it.effect("recovers stale persisted sessions for rollback by resuming thread identity", () => | 991 | it.effect("recovers stale persisted sessions for rollback by resuming thread identity", () => |
| 931 | Effect.gen(function* () { | 992 | Effect.gen(function* () { |
| 932 | const provider = yield* ProviderService.ProviderService; | 993 | const provider = yield* ProviderService.ProviderService; |
| ... | @@ -1307,6 +1368,7 @@ routing.layer("ProviderServiceLive routing", (it) => { | ... | @@ -1307,6 +1368,7 @@ routing.layer("ProviderServiceLive routing", (it) => { |
| 1307 | ), | 1368 | ), |
| 1308 | Layer.provide(firstDirectoryLayer), | 1369 | Layer.provide(firstDirectoryLayer), |
| 1309 | Layer.provide(defaultServerSettingsLayer), | 1370 | Layer.provide(defaultServerSettingsLayer), |
| | 1371 | Layer.provide(serverConfigTestLayer), |
| 1310 | Layer.provide(AnalyticsService.layerTest), | 1372 | Layer.provide(AnalyticsService.layerTest), |
| 1311 | Layer.provide( | 1373 | Layer.provide( |
| 1312 | Layer.succeed( | 1374 | Layer.succeed( |
| ... | @@ -1345,6 +1407,7 @@ routing.layer("ProviderServiceLive routing", (it) => { | ... | @@ -1345,6 +1407,7 @@ routing.layer("ProviderServiceLive routing", (it) => { |
| 1345 | ), | 1407 | ), |
| 1346 | Layer.provide(secondDirectoryLayer), | 1408 | Layer.provide(secondDirectoryLayer), |
| 1347 | Layer.provide(defaultServerSettingsLayer), | 1409 | Layer.provide(defaultServerSettingsLayer), |
| | 1410 | Layer.provide(serverConfigTestLayer), |
| 1348 | Layer.provide(AnalyticsService.layerTest), | 1411 | Layer.provide(AnalyticsService.layerTest), |
| 1349 | Layer.provide( | 1412 | Layer.provide( |
| 1350 | Layer.succeed( | 1413 | Layer.succeed( |
| ... | @@ -1413,6 +1476,7 @@ routing.layer("ProviderServiceLive routing", (it) => { | ... | @@ -1413,6 +1476,7 @@ routing.layer("ProviderServiceLive routing", (it) => { |
| 1413 | ), | 1476 | ), |
| 1414 | Layer.provide(firstDirectoryLayer), | 1477 | Layer.provide(firstDirectoryLayer), |
| 1415 | Layer.provide(defaultServerSettingsLayer), | 1478 | Layer.provide(defaultServerSettingsLayer), |
| | 1479 | Layer.provide(serverConfigTestLayer), |
| 1416 | Layer.provide(AnalyticsService.layerTest), | 1480 | Layer.provide(AnalyticsService.layerTest), |
| 1417 | Layer.provide( | 1481 | Layer.provide( |
| 1418 | Layer.succeed( | 1482 | Layer.succeed( |
| ... | @@ -1446,6 +1510,7 @@ routing.layer("ProviderServiceLive routing", (it) => { | ... | @@ -1446,6 +1510,7 @@ routing.layer("ProviderServiceLive routing", (it) => { |
| 1446 | ), | 1510 | ), |
| 1447 | Layer.provide(secondDirectoryLayer), | 1511 | Layer.provide(secondDirectoryLayer), |
| 1448 | Layer.provide(defaultServerSettingsLayer), | 1512 | Layer.provide(defaultServerSettingsLayer), |
| | 1513 | Layer.provide(serverConfigTestLayer), |
| 1449 | Layer.provide(AnalyticsService.layerTest), | 1514 | Layer.provide(AnalyticsService.layerTest), |
| 1450 | Layer.provide( | 1515 | Layer.provide( |
| 1451 | Layer.succeed( | 1516 | Layer.succeed( |