| ... | @@ -20,6 +20,7 @@ import { CommandId, ProviderInstanceId } from "@t3tools/contracts"; | ... | @@ -20,6 +20,7 @@ import { CommandId, ProviderInstanceId } from "@t3tools/contracts"; |
| 20 | import { RelayClientTracer } from "@t3tools/shared/relayTracing"; | 20 | import { RelayClientTracer } from "@t3tools/shared/relayTracing"; |
| 21 | import { RELAY_ACTIVITY_PUBLISH_TYP, verifyRelayJwt } from "@t3tools/shared/relayJwt"; | 21 | import { RELAY_ACTIVITY_PUBLISH_TYP, verifyRelayJwt } from "@t3tools/shared/relayJwt"; |
| 22 | import { describe, expect, it } from "@effect/vitest"; | 22 | import { describe, expect, it } from "@effect/vitest"; |
| | 23 | import * as DateTime from "effect/DateTime"; |
| 23 | import * as Deferred from "effect/Deferred"; | 24 | import * as Deferred from "effect/Deferred"; |
| 24 | import * as Effect from "effect/Effect"; | 25 | import * as Effect from "effect/Effect"; |
| 25 | import * as Layer from "effect/Layer"; | 26 | import * as Layer from "effect/Layer"; |
| ... | @@ -325,6 +326,9 @@ describe.sequential("signRelayAgentActivityPublishProof", () => { | ... | @@ -325,6 +326,9 @@ describe.sequential("signRelayAgentActivityPublishProof", () => { |
| 325 | const projectId = "project-1" as ProjectId; | 326 | const projectId = "project-1" as ProjectId; |
| 326 | const activeThreadId = "thread-active" as ThreadId; | 327 | const activeThreadId = "thread-active" as ThreadId; |
| 327 | const idleThreadId = "thread-idle" as ThreadId; | 328 | const idleThreadId = "thread-idle" as ThreadId; |
| | 329 | const oldCompletedId = "thread-old-completed" as ThreadId; |
| | 330 | const newCompletedId = "thread-new-completed" as ThreadId; |
| | 331 | const freshMessageId = "thread-fresh-message" as ThreadId; |
| 328 | | 332 | |
| 329 | const baseThread = { | 333 | const baseThread = { |
| 330 | projectId, | 334 | projectId, |
| ... | @@ -351,6 +355,7 @@ describe.sequential("signRelayAgentActivityPublishProof", () => { | ... | @@ -351,6 +355,7 @@ describe.sequential("signRelayAgentActivityPublishProof", () => { |
| 351 | expect( | 355 | expect( |
| 352 | AgentAwarenessRelay.resolveAgentAwarenessRelayActiveThreadIds({ | 356 | AgentAwarenessRelay.resolveAgentAwarenessRelayActiveThreadIds({ |
| 353 | environmentId, | 357 | environmentId, |
| | 358 | startedAt: Date.parse(now), |
| 354 | projects: [ | 359 | projects: [ |
| 355 | { | 360 | { |
| 356 | id: projectId, | 361 | id: projectId, |
| ... | @@ -374,6 +379,44 @@ describe.sequential("signRelayAgentActivityPublishProof", () => { | ... | @@ -374,6 +379,44 @@ describe.sequential("signRelayAgentActivityPublishProof", () => { |
| 374 | ...baseThread, | 379 | ...baseThread, |
| 375 | id: idleThreadId, | 380 | id: idleThreadId, |
| 376 | }, | 381 | }, |
| | 382 | { |
| | 383 | ...baseThread, |
| | 384 | id: oldCompletedId, |
| | 385 | latestTurn: { |
| | 386 | turnId: "turn-old" as TurnId, |
| | 387 | state: "completed", |
| | 388 | requestedAt: "2026-05-24T00:00:00.000Z", |
| | 389 | startedAt: "2026-05-24T00:00:00.000Z", |
| | 390 | completedAt: "2026-05-24T00:01:00.000Z", |
| | 391 | assistantMessageId: null, |
| | 392 | }, |
| | 393 | }, |
| | 394 | { |
| | 395 | ...baseThread, |
| | 396 | id: newCompletedId, |
| | 397 | latestTurn: { |
| | 398 | turnId: "turn-new" as TurnId, |
| | 399 | state: "completed", |
| | 400 | requestedAt: "2026-05-25T00:00:01.000Z", |
| | 401 | startedAt: "2026-05-25T00:00:01.000Z", |
| | 402 | completedAt: "2026-05-25T00:00:02.000Z", |
| | 403 | assistantMessageId: null, |
| | 404 | }, |
| | 405 | }, |
| | 406 | { |
| | 407 | ...baseThread, |
| | 408 | id: freshMessageId, |
| | 409 | latestUserMessageAt: "2026-05-25T00:00:01.000Z", |
| | 410 | session: { |
| | 411 | threadId: freshMessageId, |
| | 412 | status: "ready", |
| | 413 | providerName: "Codex", |
| | 414 | runtimeMode: "full-access", |
| | 415 | activeTurnId: null, |
| | 416 | lastError: null, |
| | 417 | updatedAt: "2026-05-25T00:00:02.000Z", |
| | 418 | }, |
| | 419 | }, |
| 377 | { | 420 | { |
| 378 | ...baseThread, | 421 | ...baseThread, |
| 379 | id: "thread-missing-project" as ThreadId, | 422 | id: "thread-missing-project" as ThreadId, |
| ... | @@ -389,7 +432,7 @@ describe.sequential("signRelayAgentActivityPublishProof", () => { | ... | @@ -389,7 +432,7 @@ describe.sequential("signRelayAgentActivityPublishProof", () => { |
| 389 | }, | 432 | }, |
| 390 | ], | 433 | ], |
| 391 | }), | 434 | }), |
| 392 | ).toEqual([activeThreadId]); | 435 | ).toEqual([activeThreadId, newCompletedId]); |
| 393 | }); | 436 | }); |
| 394 | | 437 | |
| 395 | it.effect("signs the activity publish JWT and rejects tampering", () => | 438 | it.effect("signs the activity publish JWT and rejects tampering", () => |
| ... | @@ -797,4 +840,138 @@ describe.sequential("signRelayAgentActivityPublishProof", () => { | ... | @@ -797,4 +840,138 @@ describe.sequential("signRelayAgentActivityPublishProof", () => { |
| 797 | }), | 840 | }), |
| 798 | ), | 841 | ), |
| 799 | ); | 842 | ); |
| | 843 | |
| | 844 | it.effect("does not alert for historical completions after startup", () => |
| | 845 | Effect.scoped( |
| | 846 | Effect.gen(function* () { |
| | 847 | const secrets = makeMemorySecretStore(); |
| | 848 | const now = yield* DateTime.now; |
| | 849 | const old = DateTime.formatIso(DateTime.add(now, { days: -7 })); |
| | 850 | const threadId = "thread-old" as ThreadId; |
| | 851 | const projectId = "project-1" as ProjectId; |
| | 852 | const environmentId = "env-1" as EnvironmentId; |
| | 853 | const project = { |
| | 854 | id: projectId, |
| | 855 | title: "T3 Code", |
| | 856 | workspaceRoot: "/workspace", |
| | 857 | repositoryIdentity: null, |
| | 858 | defaultModelSelection: null, |
| | 859 | scripts: [], |
| | 860 | createdAt: old, |
| | 861 | updatedAt: old, |
| | 862 | } satisfies OrchestrationProjectShell; |
| | 863 | const completedTurn = { |
| | 864 | turnId: "turn-1" as TurnId, |
| | 865 | state: "completed", |
| | 866 | requestedAt: old, |
| | 867 | startedAt: old, |
| | 868 | completedAt: old, |
| | 869 | assistantMessageId: null, |
| | 870 | } as const; |
| | 871 | const completedThread = { |
| | 872 | id: threadId, |
| | 873 | projectId, |
| | 874 | title: "Old task", |
| | 875 | modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.4" }, |
| | 876 | runtimeMode: "full-access", |
| | 877 | interactionMode: "default", |
| | 878 | branch: null, |
| | 879 | worktreePath: null, |
| | 880 | pullRequests: [], |
| | 881 | latestTurn: completedTurn, |
| | 882 | createdAt: old, |
| | 883 | updatedAt: old, |
| | 884 | archivedAt: null, |
| | 885 | settledOverride: null, |
| | 886 | settledAt: null, |
| | 887 | session: null, |
| | 888 | latestUserMessageAt: old, |
| | 889 | hasPendingApprovals: false, |
| | 890 | hasPendingUserInput: false, |
| | 891 | hasActionableProposedPlan: false, |
| | 892 | } satisfies OrchestrationThreadShell; |
| | 893 | let currentThread: OrchestrationThreadShell | null = completedThread; |
| | 894 | let publishes = 0; |
| | 895 | const originalFetch = globalThis.fetch; |
| | 896 | globalThis.fetch = (() => { |
| | 897 | publishes += 1; |
| | 898 | return Promise.resolve(Response.json({ ok: true, deliveries: [] })); |
| | 899 | }) as unknown as typeof fetch; |
| | 900 | yield* Effect.addFinalizer(() => |
| | 901 | Effect.sync(() => { |
| | 902 | globalThis.fetch = originalFetch; |
| | 903 | }), |
| | 904 | ); |
| | 905 | yield* secrets.setString(RELAY_URL_SECRET, "https://relay.example.test"); |
| | 906 | yield* secrets.setString(RELAY_ENVIRONMENT_CREDENTIAL_SECRET, "relay-credential"); |
| | 907 | yield* secrets.setString(PUBLISH_AGENT_ACTIVITY_SECRET, "true"); |
| | 908 | |
| | 909 | const layer = Layer.mergeAll( |
| | 910 | Layer.succeed(ServerSecretStore.ServerSecretStore, secrets.store), |
| | 911 | Layer.succeed(ServerEnvironment.ServerEnvironment, { |
| | 912 | getEnvironmentId: Effect.succeed(environmentId), |
| | 913 | getDescriptor: Effect.die("unused descriptor"), |
| | 914 | }), |
| | 915 | Layer.succeed(OrchestrationEngineService, {} as OrchestrationEngineShape), |
| | 916 | Layer.succeed(ProjectionSnapshotQuery, { |
| | 917 | getThreadShellById: () => Effect.sync(() => Option.fromNullishOr(currentThread)), |
| | 918 | getProjectShellById: () => Effect.succeed(Option.some(project)), |
| | 919 | } as unknown as ProjectionSnapshotQueryShape), |
| | 920 | ); |
| | 921 | |
| | 922 | yield* Effect.gen(function* () { |
| | 923 | const relay = yield* AgentAwarenessRelay.AgentAwarenessRelay; |
| | 924 | yield* relay.publishThread(threadId); |
| | 925 | expect(publishes).toBe(0); |
| | 926 | |
| | 927 | currentThread = { |
| | 928 | ...completedThread, |
| | 929 | latestTurn: null, |
| | 930 | latestUserMessageAt: DateTime.formatIso(DateTime.add(now, { seconds: 1 })), |
| | 931 | session: { |
| | 932 | threadId, |
| | 933 | status: "ready", |
| | 934 | providerName: "Codex", |
| | 935 | runtimeMode: "full-access", |
| | 936 | activeTurnId: null, |
| | 937 | lastError: null, |
| | 938 | updatedAt: DateTime.formatIso(DateTime.add(now, { seconds: 2 })), |
| | 939 | }, |
| | 940 | }; |
| | 941 | expect( |
| | 942 | AgentAwarenessRelay.resolveAgentAwarenessRelayPublishSnapshot({ |
| | 943 | environmentId, |
| | 944 | threadId, |
| | 945 | thread: Option.some(currentThread), |
| | 946 | project: Option.some(project), |
| | 947 | }).state?.phase, |
| | 948 | ).toBe("completed"); |
| | 949 | yield* relay.publishThread(threadId); |
| | 950 | expect(publishes).toBe(0); |
| | 951 | |
| | 952 | currentThread = { |
| | 953 | ...completedThread, |
| | 954 | session: { |
| | 955 | threadId, |
| | 956 | status: "error", |
| | 957 | providerName: "Codex", |
| | 958 | runtimeMode: "full-access", |
| | 959 | activeTurnId: null, |
| | 960 | lastError: "old failure", |
| | 961 | updatedAt: old, |
| | 962 | }, |
| | 963 | }; |
| | 964 | yield* relay.publishThread(threadId); |
| | 965 | expect(publishes).toBe(0); |
| | 966 | }).pipe( |
| | 967 | Effect.provide( |
| | 968 | AgentAwarenessRelay.layer.pipe( |
| | 969 | Layer.provide(layer), |
| | 970 | Layer.provideMerge(NodeServices.layer), |
| | 971 | ), |
| | 972 | ), |
| | 973 | ); |
| | 974 | }), |
| | 975 | ), |
| | 976 | ); |
| 800 | }); | 977 | }); |