11import { expect , it } from "@effect/vitest" ;
22import {
3+ type AgentSessionImportSource ,
34 ProjectId ,
45 ProviderInstanceId ,
56 ThreadId ,
67 type OrchestrationV2DomainEvent ,
78} from "@t3tools/contracts" ;
89import * as Effect from "effect/Effect" ;
10+ import * as DateTime from "effect/DateTime" ;
911import * as Layer from "effect/Layer" ;
1012import * as Option from "effect/Option" ;
1113import * as Stream from "effect/Stream" ;
@@ -23,6 +25,152 @@ const providerInstanceId = ProviderInstanceId.make("codex");
2325const providerSessionId = "native-codex-thread" ;
2426const threadId = ThreadId . make ( `import:${ providerInstanceId } :${ providerSessionId } ` ) ;
2527
28+ for ( const scenario of [
29+ "canonical" ,
30+ "fallback" ,
31+ "manual-title" ,
32+ "other-project" ,
33+ "native-history" ,
34+ "other-provider" ,
35+ "repair-failure" ,
36+ "changed-transcript" ,
37+ "changed-repair-failure" ,
38+ "regenerating" ,
39+ "deleted" ,
40+ ] as const ) {
41+ it . effect ( `repairs only eligible imported Codex titles (${ scenario } )` , ( ) => {
42+ let title = scenario === "manual-title" ? "My custom title" : "<recommended_plugins>" ;
43+ const originalTitle = title ;
44+ const source : AgentSessionImportSource = {
45+ provider : "codex" ,
46+ providerInstanceId,
47+ providerSessionId,
48+ filePath : "/tmp/native-codex-thread.jsonl" ,
49+ size : 100 ,
50+ mtimeMs : 2 ,
51+ device : 3 ,
52+ inode : 4 ,
53+ birthtimeMs : 1 ,
54+ } ;
55+ const messages = [
56+ { role : "user" , text : "<recommended_plugins>\nInjected setup\n</recommended_plugins>" } ,
57+ { role : "user" , text : "Build a trade replication prototype" } ,
58+ ] ;
59+ const recorded : Array < unknown > = [ ] ;
60+ const renamed : Array < unknown > = [ ] ;
61+ const canonicalTitle = "Prototype MetaApi trade replication" ;
62+ const scanner = AgentSessionScanner . AgentSessionScanner . of ( {
63+ scan : Effect . die ( "unused" ) ,
64+ recentThreads : ( ) =>
65+ Stream . succeed (
66+ scenario === "changed-transcript" || scenario === "changed-repair-failure"
67+ ? {
68+ _tag : "Importable" ,
69+ source,
70+ thread : {
71+ source : "codex" ,
72+ providerInstanceId,
73+ providerSessionId,
74+ title : canonicalTitle ,
75+ model : null ,
76+ createdAt : "2026-09-01T10:00:00.000Z" ,
77+ updatedAt : "2026-09-01T10:01:00.000Z" ,
78+ messages : [ ] ,
79+ } ,
80+ }
81+ : {
82+ _tag : "AlreadyImported" ,
83+ source,
84+ canonicalTitle : scenario === "fallback" ? null : canonicalTitle ,
85+ } ,
86+ ) ,
87+ } ) ;
88+ const testLayer = AgentSessionImporter . layer . pipe (
89+ Layer . provide (
90+ Layer . mergeAll (
91+ Layer . succeed ( AgentSessionScanner . AgentSessionScanner , scanner ) ,
92+ Layer . mock ( ProjectService . ProjectService ) ( {
93+ getById : ( ) =>
94+ Effect . succeed (
95+ Option . some ( { id : projectId , workspaceRoot : "/workspace/project" } as never ) ,
96+ ) ,
97+ } ) ,
98+ Layer . mock ( Orchestrator . OrchestratorV2 ) ( {
99+ getThreadRecords : ( ) =>
100+ Effect . succeed ( {
101+ thread : {
102+ id : threadId ,
103+ projectId : scenario === "other-project" ? ProjectId . make ( "other" ) : projectId ,
104+ title,
105+ providerInstanceId :
106+ scenario === "other-provider"
107+ ? ProviderInstanceId . make ( "claudeAgent" )
108+ : providerInstanceId ,
109+ historyOrigin : scenario === "native-history" ? "native" : "v1_import" ,
110+ deletedAt :
111+ scenario === "deleted" ? DateTime . makeUnsafe ( "2026-09-02T10:00:00.000Z" ) : null ,
112+ titleRegeneration :
113+ scenario === "regenerating" ? { requestId : "regenerate-title" } : null ,
114+ } ,
115+ messages : messages . map ( ( message , index ) => ( {
116+ ...message ,
117+ id : `${ threadId } :${ String ( index ) . padStart ( 6 , "0" ) } ` ,
118+ createdAt : DateTime . makeUnsafe ( "2026-09-01T10:00:00.000Z" ) ,
119+ } ) ) ,
120+ } as never ) ,
121+ dispatch : ( command ) => {
122+ if ( scenario === "repair-failure" || scenario === "changed-repair-failure" ) {
123+ return Effect . fail ( new Orchestrator . OrchestratorProjectionError ( { threadId } ) ) ;
124+ }
125+ return Effect . sync ( ( ) => {
126+ if ( command . type === "thread.metadata.update" && command . title !== undefined ) {
127+ title = command . title ;
128+ renamed . push ( command ) ;
129+ }
130+ return { sequence : 1 , storedEvents : [ ] } ;
131+ } ) ;
132+ } ,
133+ } ) ,
134+ Layer . mock ( EventSink . EventSinkV2 ) ( { write : ( ) => Effect . die ( "history must not change" ) } ) ,
135+ Layer . mock ( ProviderSessionRuntime . ProviderSessionRuntimeRepository ) ( {
136+ list : ( ) => Effect . succeed ( [ ] ) ,
137+ recordImportedTranscript : ( input ) => Effect . sync ( ( ) => void recorded . push ( input ) ) ,
138+ } ) ,
139+ IdAllocator . layer ,
140+ ) ,
141+ ) ,
142+ ) ;
143+ return Effect . gen ( function * ( ) {
144+ const importer = yield * AgentSessionImporter . AgentSessionImporter ;
145+ expect ( yield * importer . importRecentAgentThreads ( { projectId } ) ) . toEqual ( {
146+ importedCount : 1 ,
147+ skippedCount : 0 ,
148+ } ) ;
149+ const shouldRepair = [ "canonical" , "fallback" , "changed-transcript" ] . includes ( scenario ) ;
150+ expect ( title ) . toBe (
151+ shouldRepair
152+ ? scenario === "fallback"
153+ ? "Build a trade replication prototype"
154+ : canonicalTitle
155+ : originalTitle ,
156+ ) ;
157+ expect ( renamed ) . toHaveLength ( shouldRepair ? 1 : 0 ) ;
158+ if ( shouldRepair )
159+ expect ( renamed [ 0 ] ) . toMatchObject ( {
160+ expectedTitle : originalTitle ,
161+ expectedTitleRegenerationRequestId : null ,
162+ } ) ;
163+ expect ( recorded ) . toHaveLength (
164+ scenario === "changed-transcript" || scenario === "changed-repair-failure" ? 1 : 0 ,
165+ ) ;
166+ expect ( messages . map ( ( message ) => message . text ) ) . toEqual ( [
167+ "<recommended_plugins>\nInjected setup\n</recommended_plugins>" ,
168+ "Build a trade replication prototype" ,
169+ ] ) ;
170+ } ) . pipe ( Effect . provide ( testLayer ) ) ;
171+ } ) ;
172+ }
173+
26174it . effect ( "imports messages once and preserves the provider native resume binding" , ( ) => {
27175 const writes : Array < ReadonlyArray < OrchestrationV2DomainEvent > > = [ ] ;
28176 const upserts : Array < unknown > = [ ] ;
0 commit comments